From 6f5ab232a60eb9a0f45c92f3f8dac44893cecf6c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:40:23 +0100 Subject: [PATCH] hefty: bar: time: popout --- .../ii/modules/common/config/HeftyConfig.qml | 8 +- .../common/models/AnimatedTabIndexPair.qml | 2 +- .../modules/common/widgets/AxisRectangle.qml | 2 - .../widgets/AxisRectangularContainerShape.qml | 12 + .../widgets/RectangularContainerShape.qml | 91 +++ .../common/widgets/StyledRectangle.qml | 1 + .../hefty/topLayer/HAbstractMorphedPanel.qml | 37 +- .../modules/hefty/topLayer/HTopLayerPanel.qml | 4 +- .../hefty/topLayer/bar/HBarGroupContainer.qml | 37 +- .../modules/hefty/topLayer/bar/HBarPopout.qml | 42 ++ .../bar/HBarUserFallbackComponentRepeater.qml | 13 +- .../topLayer/bar/HBarWidgetContainer.qml | 9 + .../hefty/topLayer/bar/widgets/HTime.qml | 267 ++++++-- .../topLayer/bar/widgets/HWorkspaces.qml | 620 +++++++++--------- 14 files changed, 739 insertions(+), 406 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/AxisRectangularContainerShape.qml create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/RectangularContainerShape.qml create mode 100644 dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarPopout.qml create mode 100644 dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarWidgetContainer.qml diff --git a/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml b/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml index 509d4dccf..131e04e41 100644 --- a/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml +++ b/dots/.config/quickshell/ii/modules/common/config/HeftyConfig.qml @@ -4,10 +4,10 @@ import Quickshell.Io JsonObject { property JsonObject bar: JsonObject { - property list leftWidgets: [["HWindowInfo"]] - property list centerLeftWidgets: [["HTime"]] - property list centerWidgets: [["HWorkspaces"]] - property list centerRightWidgets: [["HBattery"]] + property list leftWidgets: ["HWindowInfo"] + property list centerLeftWidgets: ["HTime"] + property list centerWidgets: ["HWorkspaces"] + property list centerRightWidgets: ["HBattery"] property list rightWidgets: [] property bool m3ExpressiveGrouping: true } diff --git a/dots/.config/quickshell/ii/modules/common/models/AnimatedTabIndexPair.qml b/dots/.config/quickshell/ii/modules/common/models/AnimatedTabIndexPair.qml index c18e9ccf2..edb86c406 100644 --- a/dots/.config/quickshell/ii/modules/common/models/AnimatedTabIndexPair.qml +++ b/dots/.config/quickshell/ii/modules/common/models/AnimatedTabIndexPair.qml @@ -4,7 +4,7 @@ import QtQuick // The former animates faster than the latter, see the NumberAnimations below QtObject { id: root - required property int index + property int index property real idx1: index property real idx2: index diff --git a/dots/.config/quickshell/ii/modules/common/widgets/AxisRectangle.qml b/dots/.config/quickshell/ii/modules/common/widgets/AxisRectangle.qml index d54594ecd..73899ac0c 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/AxisRectangle.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/AxisRectangle.qml @@ -2,8 +2,6 @@ pragma ComponentBehavior: Bound import QtQuick StyledRectangle { - id: root - property bool vertical: false property real startRadius property real endRadius diff --git a/dots/.config/quickshell/ii/modules/common/widgets/AxisRectangularContainerShape.qml b/dots/.config/quickshell/ii/modules/common/widgets/AxisRectangularContainerShape.qml new file mode 100644 index 000000000..d218be004 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/AxisRectangularContainerShape.qml @@ -0,0 +1,12 @@ +import QtQuick + +RectangularContainerShape { + property bool vertical: false + property real startRadius + property real endRadius + + topLeftRadius: startRadius + topRightRadius: vertical ? startRadius : endRadius + bottomLeftRadius: vertical ? endRadius : startRadius + bottomRightRadius: endRadius +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/RectangularContainerShape.qml b/dots/.config/quickshell/ii/modules/common/widgets/RectangularContainerShape.qml new file mode 100644 index 000000000..d07578d6f --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/RectangularContainerShape.qml @@ -0,0 +1,91 @@ +import QtQuick +import qs.modules.common.models as M +import "shapes/material-shapes.js" as MaterialShapes +import "shapes/shapes/corner-rounding.js" as CornerRounding +import "shapes/geometry/offset.js" as Offset + +// For returning the points +M.NestableObject { + id: root + + required property real width + required property real height + property real radius: 0 + property real topLeftRadius: radius + property real topRightRadius: radius + property real bottomLeftRadius: radius + property real bottomRightRadius: radius + property real xOffset: 0 + property real yOffset: 0 + + readonly property real radiusLimit: Math.min(width, height) / 2 + readonly property real effectiveTopLeftRadius: Math.min(topLeftRadius, radiusLimit) + readonly property real effectiveTopRightRadius: Math.min(topRightRadius, radiusLimit) + readonly property real effectiveBottomLeftRadius: Math.min(bottomLeftRadius, radiusLimit) + readonly property real effectiveBottomRightRadius: Math.min(bottomRightRadius, radiusLimit) + + // Clockwise starting from bottom + property list bottomPoints: [ + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width - effectiveBottomRightRadius, yOffset + height), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width / 2, yOffset + height), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + effectiveBottomLeftRadius, yOffset + height), new CornerRounding.CornerRounding(0)), + ] + property list leftPoints: [ + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + height - effectiveBottomLeftRadius), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + height / 2), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + effectiveTopLeftRadius), new CornerRounding.CornerRounding(0)), + ] + property list topPoints: [ + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + effectiveTopLeftRadius, yOffset + 0), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width / 2, yOffset + 0), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width - effectiveTopRightRadius, yOffset + 0), new CornerRounding.CornerRounding(0)), + ] + property list rightPoints: [ + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + effectiveTopRightRadius), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + height / 2), new CornerRounding.CornerRounding(0)), + new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + height - effectiveBottomRightRadius), new CornerRounding.CornerRounding(0)), + ] + + function getFirstBottomPoints() { + return bottomPoints.slice(Math.floor(bottomPoints.length / 2)) + } + + function getLastBottomPoints() { + return bottomPoints.slice(0, Math.floor(bottomPoints.length / 2)) + } + + function getBottomLeftPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) { + if (radius === undefined) radius = effectiveBottomLeftRadius; + return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + 0, yOffset + extraYOffset + height), new CornerRounding.CornerRounding(radius)) + } + + function getTopLeftPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) { + if (radius === undefined) radius = effectiveTopLeftRadius; + return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + 0, yOffset + extraYOffset + 0), new CornerRounding.CornerRounding(radius)) + } + + function getTopRightPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) { + if (radius === undefined) radius = effectiveTopRightRadius; + return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + width, yOffset + extraYOffset + 0), new CornerRounding.CornerRounding(radius)) + } + + function getBottomRightPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) { + if (radius === undefined) radius = effectiveBottomRightRadius; + return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + width, yOffset + extraYOffset + height), new CornerRounding.CornerRounding(radius)) + } + + function getFullShape() { + const points = [ + ...getFirstBottomPoints(), + getBottomLeftPoint(), + ...leftPoints, + getTopLeftPoint(), + ...topPoints, + getTopRightPoint(), + ...rightPoints, + getBottomRightPoint(), + ...getLastBottomPoints(), + ] + return MaterialShapes.customPolygon(points); + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StyledRectangle.qml b/dots/.config/quickshell/ii/modules/common/widgets/StyledRectangle.qml index 1997dafcb..da5d3048a 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/StyledRectangle.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/StyledRectangle.qml @@ -6,6 +6,7 @@ import qs.modules.common as C // - osk.sh // - 3d // i hope i actually get to this and not shrimply forget +// aaaaa i realized for this to work i would have to make this for shapes in general not just rects Rectangle { enum ContentLayer { Background, Pane, Group, Subgroup, Control } property var contentLayer: StyledRectangle.ContentLayer.Pane // To appropriately add effects like shadows/3d-ization diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/HAbstractMorphedPanel.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/HAbstractMorphedPanel.qml index 5eb6e21aa..fa3486951 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/HAbstractMorphedPanel.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/HAbstractMorphedPanel.qml @@ -1,5 +1,6 @@ import QtQuick import Quickshell +import qs.services as S /** * Abstract morphed panel to be used in TopLayerPanel. @@ -21,6 +22,7 @@ Item { // Signals & loading signal requestFocus() signal dismissed() + signal focusGrabDismissed() property bool load: true property bool shown: true @@ -32,7 +34,40 @@ Item { // Main stuff property var backgroundPolygon + property list baseMaskItems: [root] + property list attachedMaskItems: [] + property list maskItems: [...baseMaskItems, ...attachedMaskItems] property Region maskRegion: Region { - item: root + regions: root.maskItems.map(item => regionComp.createObject(this, { "item": item })) + } + + function addAttachedMaskItem(item) { + if (root.attachedMaskItems.includes(item)) return; + root.attachedMaskItems.push(item); + } + + function removeAttachedMaskItem(item) { + root.attachedMaskItems = root.attachedMaskItems.filter(i => i !== item); + } + + onAttachedMaskItemsChanged: { + if (attachedMaskItems.length > 0) { + S.GlobalFocusGrab.addDismissable(root.QsWindow.window); + } else { + S.GlobalFocusGrab.removeDismissable(root.QsWindow.window); + } + } + + Connections { + target: S.GlobalFocusGrab + function onDismissed() { + root.attachedMaskItems = []; + root.focusGrabDismissed(); + } + } + + Component { + id: regionComp + Region {} } } diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml index ff1600c9d..ab2981707 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/HTopLayerPanel.qml @@ -29,9 +29,7 @@ PanelWindow { bottom: true } - mask: Region { - item: root.currentPanel - } + mask: root.currentPanel.maskRegion // HyprlandWindow.visibleMask: mask // TODO: use this later to optimize hyprland's rendering ///////////////// Content ////////////////// 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 a96eeb32b..91202f218 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarGroupContainer.qml @@ -28,30 +28,32 @@ Item { property alias topRightRadius: bg.topRightRadius property alias bottomLeftRadius: bg.bottomLeftRadius property alias bottomRightRadius: bg.bottomRightRadius + property real backgroundWidth: root.vertical ? root.backgroundUndirectionalWidth : root.width + property real backgroundHeight: root.vertical ? root.height : root.backgroundUndirectionalWidth + property real fullBackgroundRadius: Math.min(backgroundWidth, backgroundHeight) / 2 + function getBackgroundRadius(atSide) { + if (root.m3eRadius) { + if (atSide) return fullBackgroundRadius; + else return C.Appearance.rounding.unsharpenmore; + } else { + return 12; + } + } - W.AxisRectangle { + property Item background: W.AxisRectangle { id: bg anchors.centerIn: parent contentLayer: W.StyledRectangle.ContentLayer.Group - width: root.vertical ? root.backgroundUndirectionalWidth : root.width - height: root.vertical ? root.height : root.backgroundUndirectionalWidth + width: root.backgroundWidth + height: root.backgroundHeight - property real fullRadius: Math.min(width, height) / 2 - function getRadius(atSide) { - if (root.m3eRadius) { - if (atSide) return fullRadius; - else return C.Appearance.rounding.unsharpenmore; - } else { - return 12; - } - } vertical: root.vertical - startRadius: getRadius(root.startSide) - endRadius: getRadius(root.endSide) + startRadius: root.getBackgroundRadius(root.startSide) + endRadius: root.getBackgroundRadius(root.endSide) } - GridLayout { + property Item contentItem: GridLayout { id: layout columns: C.Config.options.bar.vertical ? 1 : -1 anchors.centerIn: parent @@ -59,4 +61,9 @@ Item { columnSpacing: spacing rowSpacing: spacing } + + children: [ + background, + contentItem + ] } diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarPopout.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarPopout.qml new file mode 100644 index 000000000..1f568dd51 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarPopout.qml @@ -0,0 +1,42 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs.modules.common as C +import qs.modules.common.widgets as W + +W.StyledRectangle { + id: root + contentLayer: W.StyledRectangle.ContentLayer.Pane + + color: C.Appearance.colors.colLayer2Base + + transitions: Transition { + AnchorAnimation { + duration: C.Appearance.animation.elementMove.duration + easing.type: C.Appearance.animation.elementMove.type + easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve + } + ColorAnimation { + duration: C.Appearance.animation.elementMoveFast.duration + easing.type: C.Appearance.animation.elementMoveFast.type + easing.bezierCurve: C.Appearance.animation.elementMoveFast.bezierCurve + } + PropertyAnimation { + properties: "topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius,intendedWidth,intendedHeight" + duration: C.Appearance.animation.elementMove.duration + easing.type: C.Appearance.animation.elementMove.type + easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve + } + PropertyAnimation { + properties: "opacity" + duration: C.Appearance.animation.elementMoveFast.duration + easing.type: C.Appearance.animation.elementMoveFast.type + easing.bezierCurve: C.Appearance.animation.elementMoveFast.bezierCurve + } + } + + W.StyledRectangularShadow { + target: root + z: -1 + radius: root.topLeftRadius + } +} diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarUserFallbackComponentRepeater.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarUserFallbackComponentRepeater.qml index 9d265c81b..7cc44ba89 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarUserFallbackComponentRepeater.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarUserFallbackComponentRepeater.qml @@ -23,9 +23,9 @@ Repeater { }); for (var i = 0;i < m.length; i++) { const item = m[i]; - if (item.type === "container") { - item.startSide = (i === 0) || (m[i - 1].type === "component"); - item.endSide = (i + 1 >= m.length) || (m[i + 1].type === "component"); + if (item.type === "container" || item.type === "component") { + item.startSide = (i === 0) || (m[i - 1].type !== m[i].type); + item.endSide = (i + 1 >= m.length) || (m[i + 1].type !== m[i].type); } } // print(JSON.stringify(m, null, 2)); @@ -43,8 +43,11 @@ Repeater { roleValue: "component" delegate: W.UserFallbackLoader { required property var modelData + required property int index componentName: modelData.value context: root.context + property bool startSide: index === 0 + property bool endSide: index === root.model.length - 1 } } @@ -57,11 +60,15 @@ Repeater { endSide: modelData.endSide Repeater { + id: containerRepeater model: group.modelData.value delegate: W.UserFallbackLoader { required property var modelData + required property int index componentName: modelData context: root.context + property bool startSide: index === 0 + property bool endSide: index === group.modelData.value.length - 1 } } } diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarWidgetContainer.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarWidgetContainer.qml new file mode 100644 index 000000000..666ed98d7 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/HBarWidgetContainer.qml @@ -0,0 +1,9 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common as C +import qs.modules.common.widgets as W + +HBarGroupContainer { + startSide: parent.startSide ?? true + endSide: parent.endSide ?? true +} diff --git a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HTime.qml b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HTime.qml index a0cb3a6b3..cfbb1fc86 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HTime.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HTime.qml @@ -1,91 +1,226 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts +import Quickshell import qs.modules.common as C import qs.modules.common.functions as F import qs.services as S import qs.modules.common.widgets as W +import qs.modules.common.widgets.shapes as Shapes +import ".." +import "../../../../common/widgets/shapes/material-shapes.js" as MaterialShapes +import "../../../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding +import "../../../../common/widgets/shapes/geometry/offset.js" as Offset -W.ButtonMouseArea { - id: root +HBarWidgetContainer { + id: containerRoot - property bool vertical: C.Config.options.bar.vertical - property bool showPopup: false + // Interactions + property var morphedPanelParent: F.ObjectUtils.findParentWithProperty(root, "maskItems") + Connections { + target: root + function onShowPopupChanged() { + if (root.showPopup) { + morphedPanelParent.addAttachedMaskItem(bgShape); + } else { + morphedPanelParent.removeAttachedMaskItem(bgShape); + } + } + } + Connections { + target: morphedPanelParent + function onFocusGrabDismissed() { + root.showPopup = false; + } + } - property var layoutParent: F.ObjectUtils.findParentWithProperty(root, "startSide") - property real layoutParentTopLeftRadius: layoutParent.topLeftRadius - property real layoutParentTopRightRadius: layoutParent.topRightRadius - property real layoutParentBottomLeftRadius: layoutParent.bottomLeftRadius - property real layoutParentBottomRightRadius: layoutParent.bottomRightRadius - - readonly property real barThickness: vertical ? C.Appearance.sizes.verticalBarWidth : C.Appearance.sizes.barHeight - property var activeContent: vertical ? verticalContent : horizontalContent - property real parentRadiusToPaddingRatio: 0.3 - implicitWidth: vertical ? barThickness : (activeContent.implicitWidth + (layoutParentTopLeftRadius + layoutParentBottomRightRadius) * parentRadiusToPaddingRatio) - implicitHeight: !vertical ? barThickness : (activeContent.implicitHeight + (layoutParentTopLeftRadius + layoutParentBottomRightRadius) * parentRadiusToPaddingRatio) - Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter - Layout.fillWidth: vertical - Layout.fillHeight: !vertical - - onClicked: showPopup = !showPopup - - W.StateOverlay { - id: hoverOverlay - anchors.fill: parent - property real parentMargins: 4 - property real ownMargins: 2 - property real edgeMargins: parentMargins + ownMargins - property real sideMargins: -2 + // Background container shape + background: Shapes.ShapeCanvas { + id: bgShape + property real baseTopMargin: (parent.height - containerShape.height) / 2 anchors { - leftMargin: root.vertical ? edgeMargins : sideMargins - rightMargin: root.vertical ? edgeMargins : sideMargins - topMargin: root.vertical ? sideMargins : edgeMargins - bottomMargin: root.vertical ? sideMargins : edgeMargins + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: { + if (!root.atBottom || !root.showPopup) return baseTopMargin; + else return baseTopMargin - popupShape.height - bgShape.spacing; + } } - topLeftRadius: root.layoutParentTopLeftRadius - ownMargins - topRightRadius: root.layoutParentTopRightRadius - ownMargins - bottomLeftRadius: root.layoutParentBottomLeftRadius - ownMargins - bottomRightRadius: root.layoutParentBottomRightRadius - ownMargins + width: root.showPopup ? Math.max(containerShape.width, popupShape.width) : containerShape.width + height: root.showPopup ? (containerShape.height + popupShape.height + bgShape.spacing) : containerShape.height + color: root.showPopup || progress < 1 ? C.Appearance.colors.colLayer3Base : C.Appearance.colors.colLayer1 + // color: "green" + // debug: true + xOffset: (width - containerShape.width) / 2 + yOffset: root.atBottom ? (height - containerShape.height) : 0 + animation: Anim {} - hover: root.containsMouse - press: root.containsPress + Behavior on width { + Anim {} + } + Behavior on height { + Anim {} + } + Behavior on anchors.topMargin { + Anim {} + } + + // Rectangle { + // anchors.fill: parent + // } + + polygonIsNormalized: false + property real spacing: baseTopMargin * 2 + W.AxisRectangularContainerShape { + id: containerShape + width: containerRoot.backgroundWidth + height: containerRoot.backgroundHeight + startRadius: containerRoot.getBackgroundRadius(containerRoot.startSide) + endRadius: containerRoot.getBackgroundRadius(containerRoot.endSide) + } + W.RectangularContainerShape { + id: popupShape + width: 400 // TODO + height: 500 // TODO + radius: C.Appearance.rounding.large + xOffset: -(width - containerShape.width) / 2 + yOffset: root.atBottom ? -(popupShape.height + bgShape.spacing) : (containerShape.height + bgShape.spacing) + } + + + roundedPolygon: { + if (!root.showPopup) return containerShape.getFullShape() + // return popupShape.getFullShape(); // debug + const points = [ + ...(root.atBottom ? containerShape.getFirstBottomPoints() : [ + ...popupShape.getFirstBottomPoints(), + popupShape.getBottomLeftPoint(), + ...popupShape.leftPoints, + popupShape.getTopLeftPoint(), + ]), + containerShape.getBottomLeftPoint(0, bgShape.spacing * (!root.atBottom ? 1 : 0), containerShape.radiusLimit), + // ...containerShape.leftPoints, + containerShape.getTopLeftPoint(0, bgShape.spacing * (root.atBottom ? -1 : 0), containerShape.radiusLimit), + ...(!root.atBottom ? containerShape.topPoints : [ + popupShape.getBottomLeftPoint(), + ...popupShape.leftPoints, + popupShape.getTopLeftPoint(), + ...popupShape.topPoints, + popupShape.getTopRightPoint(), + ...popupShape.rightPoints, + popupShape.getBottomRightPoint(), + ]), + containerShape.getTopRightPoint(0, bgShape.spacing * (root.atBottom ? -1 : 0), containerShape.radiusLimit), + // ...containerShape.rightPoints, + containerShape.getBottomRightPoint(0, bgShape.spacing * (!root.atBottom ? 1 : 0), containerShape.radiusLimit), + ...(root.atBottom ? containerShape.getLastBottomPoints() : [ + popupShape.getTopRightPoint(), + ...popupShape.rightPoints, + popupShape.getBottomRightPoint(), + ...popupShape.getLastBottomPoints(), + ]), + ]; + return MaterialShapes.customPolygon(points); + } + + component Anim: SpringAnimation { + spring: 3.5 + damping: 0.35 + } } - W.FadeLoader { - id: horizontalContent - anchors.fill: parent - shown: !root.vertical - sourceComponent: RowLayout { + // The button on the bar + W.ButtonMouseArea { + id: root + + property bool vertical: C.Config.options.bar.vertical + property bool atBottom: C.Config.options.bar.bottom + property bool showPopup: false + + property var layoutParent: F.ObjectUtils.findParentWithProperty(root, "startSide") + property real layoutParentTopLeftRadius: layoutParent.topLeftRadius + property real layoutParentTopRightRadius: layoutParent.topRightRadius + property real layoutParentBottomLeftRadius: layoutParent.bottomLeftRadius + property real layoutParentBottomRightRadius: layoutParent.bottomRightRadius + + readonly property real barThickness: vertical ? C.Appearance.sizes.verticalBarWidth : C.Appearance.sizes.barHeight + property var activeContent: vertical ? verticalContent : horizontalContent + property real parentRadiusToPaddingRatio: 0.3 + implicitWidth: vertical ? barThickness : (activeContent.implicitWidth + (layoutParentTopLeftRadius + layoutParentBottomRightRadius) * parentRadiusToPaddingRatio) + implicitHeight: !vertical ? barThickness : (activeContent.implicitHeight + (layoutParentTopLeftRadius + layoutParentBottomRightRadius) * parentRadiusToPaddingRatio) + Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter + Layout.fillWidth: vertical + Layout.fillHeight: !vertical + + onClicked: showPopup = !showPopup + + W.StateOverlay { + id: hoverOverlay anchors.fill: parent - - W.VisuallyCenteredStyledText { - Layout.leftMargin: root.layoutParentTopLeftRadius * root.parentRadiusToPaddingRatio - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.fillHeight: true - font.pixelSize: C.Appearance.font.pixelSize.large - color: C.Appearance.colors.colOnLayer1 - text: S.DateTime.time + property real parentMargins: 4 + property real ownMargins: 2 + property real edgeMargins: parentMargins + ownMargins + property real sideMargins: -2 + anchors { + leftMargin: root.vertical ? edgeMargins : sideMargins + rightMargin: root.vertical ? edgeMargins : sideMargins + topMargin: root.vertical ? sideMargins : edgeMargins + bottomMargin: root.vertical ? sideMargins : edgeMargins } + topLeftRadius: root.layoutParentTopLeftRadius - ownMargins + topRightRadius: root.layoutParentTopRightRadius - ownMargins + bottomLeftRadius: root.layoutParentBottomLeftRadius - ownMargins + bottomRightRadius: root.layoutParentBottomRightRadius - ownMargins - W.StyledText { - Layout.alignment: Qt.AlignVCenter - font.pixelSize: C.Appearance.font.pixelSize.small - color: C.Appearance.colors.colOnLayer1 - text: "•" - } + hover: root.containsMouse + press: root.containsPress + } + + W.FadeLoader { + id: horizontalContent + anchors.fill: parent + shown: !root.vertical + + sourceComponent: Item { + anchors.fill: parent + implicitWidth: contentLayout.implicitWidth + implicitHeight: contentLayout.implicitHeight + + RowLayout { + id: contentLayout + anchors.fill: parent + + W.VisuallyCenteredStyledText { + Layout.leftMargin: root.layoutParentTopLeftRadius * root.parentRadiusToPaddingRatio + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.fillHeight: true + font.pixelSize: C.Appearance.font.pixelSize.large + color: C.Appearance.colors.colOnLayer1 + text: S.DateTime.time + } + + W.StyledText { + Layout.alignment: Qt.AlignVCenter + font.pixelSize: C.Appearance.font.pixelSize.small + color: C.Appearance.colors.colOnLayer1 + text: "•" + } + + W.StyledText { + Layout.alignment: Qt.AlignVCenter + font.pixelSize: C.Appearance.font.pixelSize.small + color: C.Appearance.colors.colOnLayer1 + text: S.DateTime.longDate + } + } - W.StyledText { - Layout.alignment: Qt.AlignVCenter - font.pixelSize: C.Appearance.font.pixelSize.small - color: C.Appearance.colors.colOnLayer1 - text: S.DateTime.longDate } } - } - W.FadeLoader { - id: verticalContent - anchors.fill: parent + W.FadeLoader { + id: verticalContent + anchors.fill: parent + } } } 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 index 30878bf44..cf28e9232 100644 --- a/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWorkspaces.qml +++ b/dots/.config/quickshell/ii/modules/hefty/topLayer/bar/widgets/HWorkspaces.qml @@ -10,297 +10,327 @@ import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Hyprland +import ".." -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 - property real specialTextSize: workspaceButtonWidth * 0.5 - - Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter - Layout.fillWidth: vertical - Layout.fillHeight: !vertical - readonly property real barThickness: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight - implicitWidth: vertical ? barThickness : occupiedIndicators.implicitWidth - implicitHeight: vertical ? occupiedIndicators.implicitHeight : barThickness - - property real specialBlur: (wsModel.specialWorkspaceActive && !interactionMouseArea.containsMouse) ? 1 : 0 - Behavior on specialBlur { - animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) - } - +HBarWidgetContainer { Item { - id: regularWorkspaces - anchors.fill: parent + id: root - scale: 1 - 0.08 * root.specialBlur - layer.smooth: true - layer.enabled: root.specialBlur > 0 - layer.effect: MultiEffect { - brightness: -0.1 * root.specialBlur - blurEnabled: true - blur: root.specialBlur - blurMax: 32 + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) + WorkspaceModel { + id: wsModel + monitor: root.monitor } - /////////////////// Occupied indicators /////////////////// - StyledRectangle { - id: occupiedIndicatorsBg + 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 + property real specialTextSize: workspaceButtonWidth * 0.5 + + Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter + Layout.fillWidth: vertical + Layout.fillHeight: !vertical + readonly property real barThickness: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight + implicitWidth: vertical ? barThickness : occupiedIndicators.implicitWidth + implicitHeight: vertical ? occupiedIndicators.implicitHeight : barThickness + + property real specialBlur: (wsModel.specialWorkspaceActive && !interactionMouseArea.containsMouse) ? 1 : 0 + Behavior on specialBlur { + animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) + } + + Item { + id: regularWorkspaces anchors.fill: parent - contentLayer: StyledRectangle.ContentLayer.Group - color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) - visible: false - } - WorkspaceLayout { - id: occupiedIndicators - anchors.centerIn: parent - - 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 - Pill { - 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 - 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.elementMoveSmall.numberAnimation.createObject(this) - } - Behavior on undirectionalLength { - animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) - } - Behavior on undirectionalOffset { - animation: Appearance.animation.elementMoveSmall.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 - } - - /////////////////// Hover /////////////////// - ButtonMouseArea { - id: interactionMouseArea - z: 3 - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - property int hoverIndex: { - const position = root.vertical ? mouseY : mouseX; - return Math.floor(position / root.workspaceButtonWidth); + scale: 1 - 0.08 * root.specialBlur + layer.smooth: true + layer.enabled: root.specialBlur > 0 + layer.effect: MultiEffect { + brightness: -0.1 * root.specialBlur + blurEnabled: true + blur: root.specialBlur + blurMax: 32 } - onPressed: Hyprland.dispatch(`workspace ${wsModel.getWorkspaceIdAt(hoverIndex)}`) - onWheel: (event) => { - if (event.angleDelta.y < 0) - Hyprland.dispatch(`workspace r+1`); - else if (event.angleDelta.y > 0) - Hyprland.dispatch(`workspace r-1`); + /////////////////// Occupied indicators /////////////////// + StyledRectangle { + id: occupiedIndicatorsBg + anchors.fill: parent + contentLayer: StyledRectangle.ContentLayer.Group + color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) + visible: false } - 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 - drag: true // There are too many layers so we need to force this to be a lil more opaque - contentColor: Appearance.colors.colPrimary - } - } - } - - /////////////////// Numbers /////////////////// - WorkspaceLayout { - id: numbersGrid - z: 4 - layer.enabled: true // For the masking - - Repeater { - model: wsModel.shownCount - delegate: NumberWorkspaceItem {} - } - } - 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 && wsApp.biggestWindow) ? - (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.workspaceIconSize) - - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - Behavior on cornerMargin { - animation: Appearance.animation.elementMoveSmall.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.m3colors.darkmode ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary - colorization: Config.options.bar.workspaces.monochromeIcons ? 0.8 : 0.5 - 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 - scale: ((!root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? root.workspaceIconSize : root.workspaceIconSizeShrinked) / root.workspaceIconSize - - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - Behavior on scale { - animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) - } - - maskEnabled: true - maskSource: iconMask - maskThresholdMin: 0.5 - maskSpreadAtMin: 1 - } - } - } - } - } - - FadeLoader { - anchors.centerIn: parent - shown: wsModel.specialWorkspaceActive - scale: 0.8 + 0.2 * root.specialBlur - - opacity: root.specialBlur - Behavior on opacity {} // Don't animate, as specialBlur is already animated - - sourceComponent: Pill { - anchors.centerIn: parent - property real undirectionalWidth: root.activeWorkspaceSize - property real undirectionalLength: { - const base = root.workspaceButtonWidth * Math.min(1.35, wsModel.shownCount) // Who tf only configures only 2 workspaces shown anyway? - if (root.vertical) return base; - return specialWsText.implicitWidth + undirectionalWidth - } - color: Appearance.colors.colPrimary - - implicitWidth: root.vertical ? undirectionalWidth : undirectionalLength - implicitHeight: root.vertical ? undirectionalLength : undirectionalWidth - - StyledText { - id: specialWsText + WorkspaceLayout { + id: occupiedIndicators anchors.centerIn: parent - text: (!root.vertical ? wsModel.specialWorkspaceName : "S") - color: Appearance.colors.colOnPrimary - font.pixelSize: root.specialTextSize + + 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 + Pill { + 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 + 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.elementMoveSmall.numberAnimation.createObject(this) + } + Behavior on undirectionalLength { + animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) + } + Behavior on undirectionalOffset { + animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) + } + } + } + } } - Behavior on undirectionalLength { - animation: Appearance.animation.elementMoveEnter.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 + } + + /////////////////// Hover /////////////////// + ButtonMouseArea { + id: interactionMouseArea + z: 3 + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + property int hoverIndex: { + const position = root.vertical ? mouseY : mouseX; + return Math.floor(position / root.workspaceButtonWidth); + } + + function switchWorkspaceToHovered() { + Hyprland.dispatch(`workspace ${wsModel.getWorkspaceIdAt(hoverIndex)}`) + } + onPressed: switchWorkspaceToHovered() + onWheel: event => { + if (event.angleDelta.y < 0) + Hyprland.dispatch(`workspace r+1`); + else if (event.angleDelta.y > 0) + Hyprland.dispatch(`workspace r-1`); + } + + 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 + drag: true // There are too many layers so we need to force this to be a lil more opaque + contentColor: Appearance.colors.colPrimary + } + } + } + + /////////////////// Numbers /////////////////// + WorkspaceLayout { + id: numbersGrid + z: 4 + layer.enabled: true // For the masking + + Repeater { + model: wsModel.shownCount + delegate: NumberWorkspaceItem {} + } + } + 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 && wsApp.biggestWindow) ? (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.workspaceIconSize) + + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on cornerMargin { + animation: Appearance.animation.elementMoveSmall.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.m3colors.darkmode ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + colorization: Config.options.bar.workspaces.monochromeIcons ? 0.8 : 0.5 + 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 + scale: ((!root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? root.workspaceIconSize : root.workspaceIconSizeShrinked) / root.workspaceIconSize + + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on scale { + animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) + } + + maskEnabled: true + maskSource: iconMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 + } + } + } + } + } + + FadeLoader { + anchors.centerIn: parent + shown: wsModel.specialWorkspaceActive + scale: 0.8 + 0.2 * root.specialBlur + + opacity: root.specialBlur + Behavior on opacity {} // Don't animate, as specialBlur is already animated + + sourceComponent: Pill { + anchors.centerIn: parent + property real undirectionalWidth: root.activeWorkspaceSize + property real undirectionalLength: { + const base = root.workspaceButtonWidth * Math.min(1.35, wsModel.shownCount); // Who tf only configures only 2 workspaces shown anyway? + if (root.vertical) + return base; + return specialWsText.implicitWidth + undirectionalWidth; + } + color: Appearance.colors.colPrimary + + implicitWidth: root.vertical ? undirectionalWidth : undirectionalLength + implicitHeight: root.vertical ? undirectionalLength : undirectionalWidth + + StyledText { + id: specialWsText + anchors.centerIn: parent + text: (!root.vertical ? wsModel.specialWorkspaceName : "S") + color: Appearance.colors.colOnPrimary + font.pixelSize: root.specialTextSize + } + + Behavior on undirectionalLength { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + } + } + + /////////////////// 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(); } } } @@ -334,10 +364,7 @@ Item { property color contentColor: (wsModel.occupied[wsNum.index] && wsId !== wsModel.fakeWorkspace) ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1Inactive FadeLoader { - shown: !(Config.options?.bar.workspaces.alwaysShowNumbers - || root.superPressAndHeld - || (Config.options?.bar.workspaces.showAppIcons && wsNum.hasBiggestWindow) - ) + shown: !(Config.options?.bar.workspaces.alwaysShowNumbers || root.superPressAndHeld || (Config.options?.bar.workspaces.showAppIcons && wsNum.hasBiggestWindow)) anchors.centerIn: parent Circle { anchors.centerIn: parent @@ -346,10 +373,7 @@ Item { } } 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) - ) + 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 @@ -397,30 +421,4 @@ Item { 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(); - } - } }