diff --git a/dots/.config/quickshell/ii/modules/common/functions/ColorUtils.qml b/dots/.config/quickshell/ii/modules/common/functions/ColorUtils.qml index 74305c8fa..6a123d9b2 100644 --- a/dots/.config/quickshell/ii/modules/common/functions/ColorUtils.qml +++ b/dots/.config/quickshell/ii/modules/common/functions/ColorUtils.qml @@ -109,7 +109,8 @@ Singleton { */ function transparentize(color, percentage = 1) { var c = Qt.color(color); - return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage)); + var a = c.a * (1 - clamp01(percentage)); + return Qt.rgba(c.r, c.g, c.b, a); } /** @@ -121,7 +122,7 @@ Singleton { */ function applyAlpha(color, alpha) { var c = Qt.color(color); - var a = Math.max(0, Math.min(1, alpha)); + var a = clamp01(alpha); return Qt.rgba(c.r, c.g, c.b, a); } diff --git a/dots/.config/quickshell/ii/modules/common/functions/NumberUtils.qml b/dots/.config/quickshell/ii/modules/common/functions/NumberUtils.qml new file mode 100644 index 000000000..94da6d371 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/functions/NumberUtils.qml @@ -0,0 +1,16 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + /** + * Rounds the given number to the nearest even integer. + * + * @param {number} num - The number to round. + * @returns {number} The nearest even integer. + */ + function roundToEven(num) { + return Math.round(num / 2) * 2; + } +} diff --git a/dots/.config/quickshell/ii/modules/common/models/WorkspaceModel.qml b/dots/.config/quickshell/ii/modules/common/models/WorkspaceModel.qml new file mode 100644 index 000000000..17b27c9f8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/models/WorkspaceModel.qml @@ -0,0 +1,59 @@ +import QtQuick +import Quickshell.Wayland +import Quickshell.Hyprland +import qs.services +import qs.modules.common as C + +NestableObject { + id: root + + required property HyprlandMonitor monitor + readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + readonly property int activeWorkspace: monitor?.activeWorkspace?.id + readonly property bool currentWorkspaceNotFake: activeWindow?.activated ?? false // Active empty workspace = fake. At least, that's how I like to call it. + readonly property int fakeWorkspace: currentWorkspaceNotFake ? -9999 : activeWorkspace + readonly property int shownCount: C.Config.options.bar.workspaces.shown + readonly property int group: Math.floor((activeWorkspace - 1) / shownCount) + + property list occupied: [] + property list biggestWindow: occupied.map((_, index) => { + const wsId = getWorkspaceIdAt(index); + var biggestWindow = HyprlandData.biggestWindowForWorkspace(wsId); + return biggestWindow; + }) + + function getWorkspaceId(group, index) { + return group * root.shownCount + index + 1; + } + function getWorkspaceIdAt(index) { + return root.getWorkspaceId(root.group, index); + } + + // Function to update workspaceOccupied + function updateWorkspaceOccupied() { + root.occupied = Array.from({ + length: root.shownCount + }, (_, i) => { + const thisWorkspaceId = getWorkspaceId(root.group, i); + return Hyprland.workspaces.values.some(ws => ws.id === thisWorkspaceId); + }); + } + + // Occupied workspace updates + Component.onCompleted: updateWorkspaceOccupied() + Connections { + target: Hyprland.workspaces + function onValuesChanged() { + root.updateWorkspaceOccupied(); + } + } + Connections { + target: Hyprland + function onFocusedWorkspaceChanged() { + root.updateWorkspaceOccupied(); + } + } + onGroupChanged: { + updateWorkspaceOccupied(); + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/AppIcon.qml b/dots/.config/quickshell/ii/modules/common/widgets/AppIcon.qml new file mode 100644 index 000000000..6e011793c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/AppIcon.qml @@ -0,0 +1,15 @@ +import QtQuick +import org.kde.kirigami as Kirigami +import qs.services +import qs.modules.common + +Kirigami.Icon { + id: root + + property real implicitSize: 26 + implicitWidth: implicitSize + implicitHeight: implicitSize + + roundToIconSize: false + animated: true // It's just fading from one icon to another +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/Circle.qml b/dots/.config/quickshell/ii/modules/common/widgets/Circle.qml index ed137a94d..d3b727a97 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/Circle.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/Circle.qml @@ -1,7 +1,7 @@ import QtQuick Rectangle { - property double diameter + property real diameter implicitWidth: diameter implicitHeight: diameter diff --git a/dots/.config/quickshell/ii/modules/common/widgets/MaskMultiEffect.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaskMultiEffect.qml new file mode 100644 index 000000000..18e2177d4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaskMultiEffect.qml @@ -0,0 +1,9 @@ +import QtQuick +import QtQuick.Effects + +// Note: You still have to set sizes yourself +MultiEffect { + maskEnabled: true + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StateLayer.qml b/dots/.config/quickshell/ii/modules/common/widgets/StateLayer.qml new file mode 100644 index 000000000..19c825982 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/StateLayer.qml @@ -0,0 +1,19 @@ +import QtQuick + +Rectangle { + id: root + + // https://m3.material.io/foundations/interaction/states/state-layers + enum State { + Hover, Focus, Press, Drag + } + + property var state: StateLayer.State.Hover + opacity: switch(state) { + case StateLayer.State.Hover: return 0.08; + case StateLayer.State.Focus: return 0.1; + case StateLayer.State.Press: return 0.1; + case StateLayer.State.Drag: return 0.16; + default: return 0; + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StateOverlay.qml b/dots/.config/quickshell/ii/modules/common/widgets/StateOverlay.qml new file mode 100644 index 000000000..65cabc218 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/StateOverlay.qml @@ -0,0 +1,53 @@ +pragma ComponentBehavior: Bound +import QtQuick + +Rectangle { + id: root + + property bool hover: false + property bool press: false + property bool drag: false + property color contentColor: Appearance.m3colors.m3onBackground + color: "transparent" + + FadeLoader { + id: hoverLoader + anchors.fill: parent + shown: root.hover + sourceComponent: StateLayer { + radius: root.radius + state: StateLayer.State.Hover + color: root.contentColor + } + } + FadeLoader { + id: focusLoader + anchors.fill: parent + shown: root.focus + sourceComponent: StateLayer { + radius: root.radius + state: StateLayer.State.Focus + color: root.contentColor + } + } + FadeLoader { + id: pressLoader + anchors.fill: parent + shown: root.press + sourceComponent: StateLayer { + radius: root.radius + state: StateLayer.State.Press + color: root.contentColor + } + } + FadeLoader { + id: dragLoader + anchors.fill: parent + shown: root.drag + sourceComponent: StateLayer { + radius: root.radius + state: StateLayer.State.Drag + color: root.contentColor + } + } +} diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml index 0a6e829f8..ff1600c9d 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml @@ -67,7 +67,7 @@ PanelWindow { borderWidth: (root.currentPanel === bar && Config.options.bar.cornerStyle !== 1) ? 0 : 1 borderColor: Appearance.colors.colLayer0Border visible: false // cuz there's already the shadow - debug: true + // debug: true } DropShadow { id: shadow diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml index 58a3aee0f..54bb0ef43 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml @@ -50,7 +50,7 @@ Item { id: layout columns: C.Config.options.bar.vertical ? 1 : -1 anchors.centerIn: parent - property real spacing: 0 + property real spacing: 4 columnSpacing: spacing rowSpacing: spacing } diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWorkspaces.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWorkspaces.qml new file mode 100644 index 000000000..e7895fd2e --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWorkspaces.qml @@ -0,0 +1,363 @@ +pragma ComponentBehavior: Bound +import qs +import qs.modules.common +import qs.modules.common.models +import qs.modules.common.widgets +import qs.modules.common.functions +import qs.services +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland + +Item { + id: root + + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) + WorkspaceModel { + id: wsModel + monitor: root.monitor + } + + property bool vertical: Config.options.bar.vertical + property bool superPressAndHeld: false // Relevant modifications at bottom of file + + property real workspaceButtonWidth: 26 + property real activeWorkspaceMargin: 2 + property real activeWorkspaceSize: workspaceButtonWidth - activeWorkspaceMargin * 2 + property real workspaceIconSize: workspaceButtonWidth * 0.69 + property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 + property real workspaceIconOpacityShrinked: 1 + property real workspaceIconMarginShrinked: -4 + property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % wsModel.shownCount + + Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter + Layout.fillWidth: vertical + Layout.fillHeight: !vertical + implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : occupiedIndicators.implicitWidth + implicitHeight: vertical ? occupiedIndicators.implicitHeight : Appearance.sizes.barHeight + + /////////////////// Occupied indicators /////////////////// + StyledRectangle { + id: occupiedIndicatorsBg + anchors.fill: parent + contentLayer: StyledRectangle.ContentLayer.Group + color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) + visible: false + } + + WorkspaceLayout { + id: occupiedIndicators + anchors.centerIn: parent + + // rowSpacing: 0 + // columnSpacing: 0 + // columns: root.vertical ? 1 : -1 + // rows: root.vertical ? -1 : 1 + + layer.enabled: true + visible: false + + Repeater { + model: wsModel.shownCount + delegate: Item { + id: wsBg + required property int index + readonly property int wsId: wsModel.getWorkspaceIdAt(index) + property bool currentOccupied: wsModel.occupied[index] && wsId != wsModel.fakeWorkspace + property bool previousOccupied: index > 0 && wsModel.occupied[index - 1] && (wsId - 1) != wsModel.fakeWorkspace + property bool nextOccupied: index < wsModel.shownCount - 1 && wsModel.occupied[index + 1] && (wsId + 1) != wsModel.fakeWorkspace + implicitWidth: root.workspaceButtonWidth + implicitHeight: root.workspaceButtonWidth + + // The idea: over-stretch to occupied sides, animate this for a smooth transition. + // masking already prevents weird overlaps + Circle { + property real undirectionalWidth: root.workspaceButtonWidth * wsBg.currentOccupied + property real undirectionalLength: root.workspaceButtonWidth * (1 + 0.5 * wsBg.previousOccupied + 0.5 * wsBg.nextOccupied) * currentOccupied + property real undirectionalOffset: (!wsBg.currentOccupied ? 0.5 : -0.5 * wsBg.previousOccupied) * root.workspaceButtonWidth + radius: undirectionalWidth / 2 + anchors.verticalCenter: root.vertical ? undefined : parent.verticalCenter + anchors.horizontalCenter: root.vertical ? parent.horizontalCenter : undefined + x: root.vertical ? 0 : undirectionalOffset + y: root.vertical ? undirectionalOffset : 0 + implicitWidth: root.vertical ? undirectionalWidth : undirectionalLength + implicitHeight: root.vertical ? undirectionalLength : undirectionalWidth + + Behavior on undirectionalWidth { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on undirectionalLength { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on undirectionalOffset { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } + } + } + } + + MaskMultiEffect { + id: occupiedIndicatorsMultiEffect + z: 1 + anchors.centerIn: parent + implicitWidth: occupiedIndicators.implicitWidth + implicitHeight: occupiedIndicators.implicitHeight + source: occupiedIndicatorsBg + maskSource: occupiedIndicators + } + + /////////////////// Active indicator /////////////////// + TrailingIndicator { + id: activeIndicator + anchors.fill: parent + z: 2 + + index: root.workspaceIndexInGroup + layer.enabled: true // For the masking + } + + /////////////////// Hover /////////////////// + MouseArea { + id: interactionMouseArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + property int hoverIndex: { + const position = root.vertical ? mouseY : mouseX; + return Math.floor(position / root.workspaceButtonWidth); + } + + onPressed: Hyprland.dispatch(`workspace ${wsModel.getWorkspaceIdAt(hoverIndex)}`) + + TrailingIndicator { + id: interactionIndicator + index: interactionMouseArea.containsMouse ? interactionMouseArea.hoverIndex : root.workspaceIndexInGroup + color: "transparent" + StateOverlay { + id: hoverOverlay + anchors.fill: interactionIndicator.indicatorRectangle + radius: root.activeWorkspaceSize / 2 + hover: interactionMouseArea.containsMouse + press: interactionMouseArea.containsPress + contentColor: Appearance.colors.colPrimary + } + } + } + + /////////////////// Numbers /////////////////// + WorkspaceLayout { + id: numbersGrid + z: 4 + layer.enabled: true // For the masking + + Repeater { + model: wsModel.shownCount + delegate: WorkspaceItem { + id: wsNum + property bool hasBiggestWindow: !!wsModel.biggestWindow[index] + property color contentColor: wsModel.occupied[wsNum.index] ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1Inactive + + FadeLoader { + shown: !(Config.options?.bar.workspaces.alwaysShowNumbers + || root.superPressAndHeld + || (Config.options?.bar.workspaces.showAppIcons && wsNum.hasBiggestWindow) + ) + anchors.centerIn: parent + Circle { + anchors.centerIn: parent + diameter: root.workspaceButtonWidth * 0.18 + color: wsNum.contentColor + } + } + FadeLoader { + shown: root.superPressAndHeld + || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !wsNum.hasBiggestWindow || root.showNumbers)) + || (root.superPressAndHeld && !Config.options?.bar.workspaces.showAppIcons) + ) + anchors.centerIn: parent + StyledText { + anchors.centerIn: parent + font { + pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2) + family: Config.options?.bar.workspaces.useNerdFont ? Appearance.font.family.iconNerd : defaultFont + } + color: wsNum.contentColor + text: wsNum.wsId + } + } + } + } + } + Colorizer { + z: 5 + anchors.fill: numbersGrid + colorizationColor: Appearance.colors.colOnPrimary + sourceColor: Appearance.colors.colOnSecondaryContainer + + source: activeIndicator + maskEnabled: true + maskSource: numbersGrid + + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 + } + + /////////////////// App icons /////////////////// + WorkspaceLayout { + id: appsGrid + z: 6 + + Repeater { + model: wsModel.shownCount + delegate: WorkspaceItem { + id: wsApp + property var biggestWindow: wsModel.biggestWindow[index] + property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") + + AppIcon { + id: appIcon + property real cornerMargin: (!root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? + (root.workspaceButtonWidth - root.workspaceIconSize) / 2 : root.workspaceIconMarginShrinked + anchors { + bottom: parent.bottom + right: parent.right + bottomMargin: (parent.implicitHeight - root.workspaceButtonWidth) / 2 + cornerMargin + rightMargin: (parent.implicitWidth - root.workspaceButtonWidth) / 2 + cornerMargin + } + + animated: !wsApp.biggestWindow // Prevent the "image-missing" icon + visible: false // Prevent dupe: the colorizer already copies the icon + + source: wsApp.mainAppIconSource + implicitSize: NumberUtils.roundToEven((!root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? root.workspaceIconSize : root.workspaceIconSizeShrinked) + + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on cornerMargin { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on implicitSize { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } + + Circle { + id: iconMask + visible: false + layer.enabled: true + diameter: appIcon.implicitSize + } + + Colorizer { + anchors.fill: appIcon + implicitWidth: appIcon.implicitWidth + implicitHeight: appIcon.implicitHeight + colorizationColor: Appearance.colors.colOnSecondaryContainer + colorization: Config.options.bar.workspaces.monochromeIcons * 0.7 + brightness: 0 + source: appIcon + + opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : + (wsApp.biggestWindow && !root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? + 1 : wsApp.biggestWindow ? root.workspaceIconOpacityShrinked : 0 + visible: opacity > 0 + + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + maskEnabled: true + maskSource: iconMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 + } + } + } + } + + /////////////////// Components /////////////////// + component WorkspaceLayout: Grid { + anchors { + top: !vertical ? parent.top : undefined + bottom: !vertical ? parent.bottom : undefined + left: vertical ? parent.left : undefined + right: vertical ? parent.right : undefined + } + + rowSpacing: 0 + columnSpacing: 0 + columns: root.vertical ? 1 : -1 + rows: root.vertical ? -1 : 1 + } + + component WorkspaceItem: Item { + required property int index + readonly property int wsId: wsModel.getWorkspaceIdAt(index) + implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : root.workspaceButtonWidth + implicitHeight: root.vertical ? root.workspaceButtonWidth : Appearance.sizes.barHeight + } + + component TrailingIndicator: Item { + id: trailingIndicator + anchors.fill: parent + required property int index + property alias indicatorRectangle: indicatorRect + property alias color: indicatorRect.color + + StyledRectangle { + id: indicatorRect + anchors { + verticalCenter: vertical ? undefined : parent.verticalCenter + horizontalCenter: vertical ? parent.horizontalCenter : undefined + } + + AnimatedTabIndexPair { + id: idxPair + index: trailingIndicator.index + } + + property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin + property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceSize + property real indicatorThickness: root.activeWorkspaceSize + + contentLayer: StyledRectangle.ContentLayer.Group + radius: indicatorThickness / 2 + color: Appearance.colors.colPrimary + + x: root.vertical ? null : indicatorPosition + y: root.vertical ? indicatorPosition : null + implicitWidth: root.vertical ? indicatorThickness : indicatorLength + implicitHeight: root.vertical ? indicatorLength : indicatorThickness + } + } + + /////////////////// Super key press handling /////////////////// + Timer { + id: superPressAndHeldTimer + interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100) + repeat: false + onTriggered: { + root.superPressAndHeld = true; + } + } + Connections { + target: GlobalStates + function onSuperDownChanged() { + if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) + return; + if (GlobalStates.superDown) + superPressAndHeldTimer.restart(); + else { + superPressAndHeldTimer.stop(); + root.superPressAndHeld = false; + } + } + function onSuperReleaseMightTriggerChanged() { + superPressAndHeldTimer.stop(); + } + } +} diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/Workspaces.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/IIWorkspaces.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/Workspaces.qml rename to dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/IIWorkspaces.qml diff --git a/dots/.config/quickshell/ii/modules/ii/bar/Workspaces.qml b/dots/.config/quickshell/ii/modules/ii/bar/Workspaces.qml index 7652b8b9f..05c7399b9 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/Workspaces.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/Workspaces.qml @@ -1,3 +1,4 @@ +pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common @@ -15,22 +16,22 @@ import Qt5Compat.GraphicalEffects Item { id: root property bool vertical: false - property bool borderless: Config.options.bar.borderless readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel - readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1 - - readonly property int workspacesShown: Config.options.bar.workspaces.shown - readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / root.workspacesShown) - property list workspaceOccupied: [] - property int widgetPadding: 4 + readonly property bool activeActuallyFocused: activeWindow?.activated ?? false + + WorkspaceModel { + id: wsModel + monitor: root.monitor + } + property int workspaceButtonWidth: 26 property real activeWorkspaceMargin: 2 property real workspaceIconSize: workspaceButtonWidth * 0.69 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconOpacityShrinked: 1 property real workspaceIconMarginShrinked: -4 - property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown + property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % wsModel.shownCount property bool showNumbers: false Timer { @@ -56,33 +57,8 @@ Item { } } - // Function to update workspaceOccupied - function updateWorkspaceOccupied() { - workspaceOccupied = Array.from({ length: root.workspacesShown }, (_, i) => { - return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * root.workspacesShown + i + 1); - }) - } - - // Occupied workspace updates - Component.onCompleted: updateWorkspaceOccupied() - Connections { - target: Hyprland.workspaces - function onValuesChanged() { - updateWorkspaceOccupied(); - } - } - Connections { - target: Hyprland - function onFocusedWorkspaceChanged() { - updateWorkspaceOccupied(); - } - } - onWorkspaceGroupChanged: { - updateWorkspaceOccupied(); - } - - implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * root.workspacesShown) - implicitHeight: root.vertical ? (root.workspaceButtonWidth * root.workspacesShown) : Appearance.sizes.barHeight + implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * wsModel.shownCount) + implicitHeight: root.vertical ? (root.workspaceButtonWidth * wsModel.shownCount) : Appearance.sizes.barHeight // Scroll to switch workspaces WheelHandler { @@ -116,15 +92,18 @@ Item { rows: root.vertical ? -1 : 1 Repeater { - model: root.workspacesShown + model: wsModel.shownCount + + delegate: Rectangle { + required property int index - Rectangle { z: 1 - implicitWidth: workspaceButtonWidth - implicitHeight: workspaceButtonWidth + implicitWidth: root.workspaceButtonWidth + implicitHeight: root.workspaceButtonWidth radius: (width / 2) - property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index)) - property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+2)) + property bool thisOccupied: (wsModel.occupied[index] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index+1)) + property var previousOccupied: (wsModel.occupied[index-1] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index)) + property var rightOccupied: (wsModel.occupied[index+1] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index+2)) property var radiusPrev: previousOccupied ? 0 : (width / 2) property var radiusNext: rightOccupied ? 0 : (width / 2) @@ -134,7 +113,7 @@ Item { bottomRightRadius: radiusNext color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) - opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+1)) ? 1 : 0 + opacity: thisOccupied ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) @@ -169,9 +148,9 @@ Item { id: idxPair index: root.workspaceIndexInGroup } - property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * workspaceButtonWidth + root.activeWorkspaceMargin - property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2 - property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2 + property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin + property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * root.workspaceButtonWidth + root.workspaceButtonWidth - root.activeWorkspaceMargin * 2 + property real indicatorThickness: root.workspaceButtonWidth - root.activeWorkspaceMargin * 2 x: root.vertical ? null : indicatorPosition implicitWidth: root.vertical ? indicatorThickness : indicatorLength @@ -184,36 +163,35 @@ Item { Grid { id: wsNumbers z: 3 + anchors.fill: parent columns: root.vertical ? 1 : -1 rows: root.vertical ? -1 : 1 columnSpacing: 0 rowSpacing: 0 - anchors.fill: parent - Repeater { - model: root.workspacesShown - - Button { + model: wsModel.shownCount + delegate: Button { id: button - property int workspaceValue: workspaceGroup * root.workspacesShown + index + 1 + required property int index + property int workspaceValue: wsModel.getWorkspaceIdAt(index) implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`) - width: vertical ? undefined : workspaceButtonWidth - height: vertical ? workspaceButtonWidth : undefined + width: vertical ? undefined : root.workspaceButtonWidth + height: vertical ? root.workspaceButtonWidth : undefined background: Item { id: workspaceButtonBackground - implicitWidth: workspaceButtonWidth - implicitHeight: workspaceButtonWidth + implicitWidth: root.workspaceButtonWidth + implicitHeight: root.workspaceButtonWidth property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue) property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") property color numberColor: (monitor?.activeWorkspace?.id == button.workspaceValue) ? Appearance.m3colors.m3onPrimary : - (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : + (wsModel.occupied[index] ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1Inactive) StyledText { // Workspace number text @@ -246,7 +224,7 @@ Item { ) ? 0 : 1 visible: opacity > 0 anchors.centerIn: parent - width: workspaceButtonWidth * 0.18 + width: root.workspaceButtonWidth * 0.18 height: width radius: width / 2 color: workspaceButtonBackground.numberColor @@ -257,8 +235,8 @@ Item { } Item { // Main app icon anchors.centerIn: parent - width: workspaceButtonWidth - height: workspaceButtonWidth + width: root.workspaceButtonWidth + height: root.workspaceButtonWidth opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : (workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 @@ -268,9 +246,9 @@ Item { anchors.bottom: parent.bottom anchors.right: parent.right anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? - (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked + (root.workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? - (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked + (root.workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked source: workspaceButtonBackground.mainAppIconSource implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml index bd0f2fce4..4d9e491b2 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml @@ -1,19 +1,14 @@ import QtQuick -import org.kde.kirigami as Kirigami import qs.services import qs.modules.common +import qs.modules.common.widgets as W -Kirigami.Icon { +W.AppIcon { id: root required property string iconName property bool separateLightDark: false property bool tryCustomIcon: true - property real implicitSize: 26 - implicitWidth: implicitSize - implicitHeight: implicitSize - - animated: true roundToIconSize: false fallback: root.iconName source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback