From 65c56bc598291859deb3e8362179eed803a761b9 Mon Sep 17 00:00:00 2001 From: zach Date: Sat, 6 Jun 2026 00:49:19 +0200 Subject: [PATCH 1/3] updated components --- Components/Anim.qml | 61 ++++- Components/CustomSlider.qml | 187 ++++++++++--- Components/StateLayer.qml | 239 +++++++++++----- Config/Appearance.qml | 2 - Config/AppearanceConf.qml | 10 +- .../Settings/Controls/WallpaperCropper.qml | 8 +- Modules/SysTray/Popouts/AudioPopup.qml | 62 ++--- Plugins/ZShell/CMakeLists.txt | 1 + Plugins/ZShell/Components/CMakeLists.txt | 1 + Plugins/ZShell/Components/wavyline.cpp | 255 ++++++++++++++++++ Plugins/ZShell/Components/wavyline.hpp | 107 ++++++++ Plugins/ZShell/zutils.cpp | 145 ++++++++++ Plugins/ZShell/zutils.hpp | 31 +++ 13 files changed, 969 insertions(+), 140 deletions(-) create mode 100644 Plugins/ZShell/Components/wavyline.cpp create mode 100644 Plugins/ZShell/Components/wavyline.hpp create mode 100644 Plugins/ZShell/zutils.cpp create mode 100644 Plugins/ZShell/zutils.hpp diff --git a/Components/Anim.qml b/Components/Anim.qml index e5743e5..0b02612 100644 --- a/Components/Anim.qml +++ b/Components/Anim.qml @@ -2,7 +2,62 @@ import QtQuick import qs.Config NumberAnimation { - duration: Appearance.anim.durations.normal - easing.bezierCurve: Appearance.anim.curves.standard - easing.type: Easing.BezierSpline + enum Type { + StandardSmall = 0, + Standard, + StandardLarge, + StandardExtraLarge, + EmphasizedSmall, + Emphasized, + EmphasizedLarge, + EmphasizedExtraLarge, + FastSpatial, + DefaultSpatial, + SlowSpatial, + FastEffects, + DefaultEffects, + SlowEffects + } + + property int type: Anim.DefaultSpatial + + duration: { + if (type < Anim.StandardSmall || type > Anim.SlowEffects) + return Appearance.anim.durations.normal; + + if (type === Anim.FastSpatial) + return Appearance.anim.durations.expressiveFastSpatial; + if (type === Anim.DefaultSpatial) + return Appearance.anim.durations.expressiveDefaultSpatial; + if (type === Anim.SlowSpatial) + return Appearance.anim.durations.large; + if (type === Anim.FastEffects) + return Appearance.anim.durations.expressiveFastEffects; + if (type === Anim.DefaultEffects) + return Appearance.anim.durations.expressiveEffects; + if (type === Anim.SlowEffects) + return Appearance.anim.durations.expressiveSlowEffects; + + const types = ["small", "normal", "large", "extraLarge"]; + const idx = type % 4; // 0-7 are the 4 standard types + return Appearance.anim.durations[types[idx]]; + } + easing.bezierCurve: { + if (type === Anim.FastSpatial) + return Appearance.anim.curves.expressiveFastSpatial; + if (type === Anim.DefaultSpatial) + return Appearance.anim.curves.expressiveDefaultSpatial; + if (type === Anim.SlowSpatial) + return Appearance.anim.curves.expressiveSlowSpatial; + if (type === Anim.FastEffects) + return Appearance.anim.curves.expressiveFastEffects; + if (type === Anim.DefaultEffects) + return Appearance.anim.curves.expressiveDefaultEffects; + if (type === Anim.SlowEffects) + return Appearance.anim.curves.expressiveSlowEffects; + + if (type >= Anim.EmphasizedSmall && type <= Anim.EmphasizedExtraLarge) + return Appearance.anim.curves.emphasized; + return Appearance.anim.curves.standard; + } } diff --git a/Components/CustomSlider.qml b/Components/CustomSlider.qml index 311f861..3041698 100644 --- a/Components/CustomSlider.qml +++ b/Components/CustomSlider.qml @@ -1,51 +1,174 @@ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Templates +import ZShell.Components +import ZShell +import qs.Components import qs.Config Slider { id: root - property color color: DynamicColors.palette.m3primary + property bool animateWave + property color bgColor: enabled ? DynamicColors.palette.m3secondaryContainer : Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) + property color fgColor: enabled ? DynamicColors.palette.m3primary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) + property real filledWidth + property real pos: visualPosition + property int waveDuration: 1000 + property real waveFrequency: 6 + property bool wavy + + signal interaction(v: real) + + implicitHeight: 12 + implicitWidth: 200 + + contentItem: Item { + anchors.fill: parent - background: Item { CustomRect { - anchors.bottom: parent.bottom - anchors.bottomMargin: root.implicitHeight / 6 - anchors.left: parent.left - anchors.top: parent.top - anchors.topMargin: root.implicitHeight / 6 - bottomRightRadius: root.implicitHeight / 6 - color: root.color - implicitWidth: root.handle.x - root.implicitHeight / 6 - radius: root.implicitHeight / 6 - topRightRadius: root.implicitHeight / 6 + id: remaining + + anchors.left: handle.right + anchors.leftMargin: Appearance.spacing.extraSmall + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + bottomLeftRadius: Appearance.rounding.extraSmall / 2 + color: root.bgColor + implicitHeight: parent.height * (parent.height <= 12 ? opacity : Math.min(opacity * 2, 1)) + opacity: Math.min(width, 12) / 12 + radius: Appearance.rounding.small + topLeftRadius: Appearance.rounding.extraSmall / 2 } CustomRect { - anchors.bottom: parent.bottom - anchors.bottomMargin: root.implicitHeight / 6 anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: root.implicitHeight / 6 - bottomLeftRadius: root.implicitHeight / 6 - color: DynamicColors.tPalette.m3surfaceContainerHighest - implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 6 - radius: root.implicitHeight / 6 - topLeftRadius: root.implicitHeight / 6 + anchors.rightMargin: 4 * remaining.opacity + anchors.verticalCenter: parent.verticalCenter + color: root.fgColor + implicitHeight: 4 * remaining.opacity + implicitWidth: implicitHeight + opacity: remaining.opacity + radius: Appearance.rounding.full + } + + CustomRect { + id: handle + + anchors.left: filled.right + anchors.leftMargin: Appearance.spacing.extraSmall + anchors.verticalCenter: parent.verticalCenter + color: root.fgColor + implicitHeight: { + const mult = parent.height <= 12 ? 3 : 1.2; + const pressMult = parent.height <= 12 ? 4 : 1.5; + return parent.height * (mouse.pressed ? pressMult : mult); + } + implicitWidth: 4 + radius: Appearance.rounding.full + + Behavior on implicitHeight { + Anim { + type: Anim.FastSpatial + } + } + } + + Loader { + id: filled + + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + asynchronous: true + sourceComponent: root.wavy ? waveComp : lineComp + } + + Component { + id: lineComp + + CustomRect { + bottomRightRadius: Appearance.rounding.extraSmall / 2 + color: root.fgColor + implicitHeight: root.height + implicitWidth: root.filledWidth + radius: Appearance.rounding.small + topRightRadius: Appearance.rounding.extraSmall / 2 + } + } + + Component { + id: waveComp + + WavyLine { + color: root.fgColor + frequency: root.waveFrequency + fullLength: root.width - handle.implicitWidth - handle.anchors.leftMargin + implicitHeight: lineWidth * amplitudeMultiplier * 2 + lineWidth + implicitWidth: root.filledWidth + lineWidth: root.height * 0.7 + startX: x + + Behavior on color { + CAnim { + } + } + Anim on waveProgress { + duration: root.waveDuration + easing.type: Easing.Linear + from: 0 + loops: Animation.Infinite + paused: !root.animateWave + running: true + to: 1 + } + } } } - handle: CustomRect { - anchors.verticalCenter: parent.verticalCenter - color: root.color - implicitHeight: root.implicitHeight - implicitWidth: root.implicitHeight / 4.5 - radius: Appearance.rounding.full - x: root.visualPosition * root.availableWidth - implicitWidth / 2 + Behavior on filledWidth { + id: widthBehavior - MouseArea { - acceptedButtons: Qt.NoButton - anchors.fill: parent - cursorShape: Qt.PointingHandCursor + Anim { + } + } + + Component.onCompleted: filledWidth = Qt.binding(() => (width - handle.implicitWidth - handle.anchors.leftMargin) * pos) + + Binding { + id: posBinding + + property: "pos" + target: root + value: ZUtils.clamp(mouse.pressStartPos + mouse.dragMovement, 0, 1) + when: mouse.pressed + } + + MouseArea { + id: mouse + + property real dragMovement + property real pressStartPos + property real pressStartX + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + implicitHeight: handle.implicitHeight + preventStealing: true + + onPositionChanged: e => { + dragMovement = (e.x - pressStartX) / width; + root.interaction(posBinding.value); + } + onPressed: e => { + widthBehavior.enabled = false; + pressStartX = e.x; + pressStartPos = root.visualPosition; + } + onReleased: e => { + root.interaction(posBinding.value); + widthBehavior.enabled = true; + dragMovement = 0; } } } diff --git a/Components/StateLayer.qml b/Components/StateLayer.qml index 53dc9e3..aa41297 100644 --- a/Components/StateLayer.qml +++ b/Components/StateLayer.qml @@ -1,15 +1,53 @@ -import qs.Config import QtQuick +import QtQuick.Shapes +import ZShell +import ZShell.Components +import qs.Helpers +import qs.Config MouseArea { id: root - property color color: DynamicColors.palette.m3onSurface + property alias bottomLeftRadius: base.bottomLeftRadius + property alias bottomRightRadius: base.bottomRightRadius + property real circleRadius + property alias color: base.color property bool disabled - property real radius: parent?.radius ?? 0 - property alias rect: hoverLayer + readonly property real endRadius: { + const d1 = distSq(0, 0); + const d2 = distSq(width, 0); + const d3 = distSq(0, height); + const d4 = distSq(width, height); + return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3; + } + property real endRadiusAtPress + property bool manualPressOverride + property real pressX: width / 2 + property real pressY: height / 2 + property alias radius: base.radius + readonly property alias rect: base + property bool shapeMorph + property bool showHoverBackground: true + property real stateOpacity: containsMouse ? 0.08 : 0 + property alias topLeftRadius: base.topLeftRadius + property alias topRightRadius: base.topRightRadius - function onClicked(): void { + function clamp(r: real): real { + return Math.max(0, Math.min(r, width / 2, height / 2)); + } + + function distSq(x: real, y: real): real { + return (pressX - x) ** 2 + (pressY - y) ** 2; + } + + function press(x: real, y: real): void { + pressX = x; + pressY = y; + fadeAnim.complete(); + circleRadius = 0; + circle.opacity = 0.1; + rippleAnim.restart(); + endRadiusAtPress = endRadius; } anchors.fill: parent @@ -17,79 +55,146 @@ MouseArea { enabled: !disabled hoverEnabled: true - onClicked: event => !disabled && onClicked(event) - onPressed: event => { - if (disabled) - return; - - rippleAnim.x = event.x; - rippleAnim.y = event.y; - - const dist = (ox, oy) => ox * ox + oy * oy; - rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y))); - - rippleAnim.restart(); + Behavior on stateOpacity { + Anim { + type: Anim.DefaultEffects + } } - SequentialAnimation { + onCircleRadiusChanged: { + if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running) + fadeAnim.start(); + } + onClicked: event => !disabled && onClicked(event) + onManualPressOverrideChanged: { + if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running) + fadeAnim.start(); + } + onPressed: e => press(e.x, e.y) + onPressedChanged: { + if (!(pressed || manualPressOverride) && !rippleAnim.running && circle.opacity > 0) + fadeAnim.start(); + } + + Anim { id: rippleAnim - property real radius - property real x - property real y - - PropertyAction { - property: "x" - target: ripple - value: rippleAnim.x - } - - PropertyAction { - property: "y" - target: ripple - value: rippleAnim.y - } - - PropertyAction { - property: "opacity" - target: ripple - value: 0.08 - } - - Anim { - easing.bezierCurve: MaterialEasing.standardDecel - from: 0 - properties: "implicitWidth,implicitHeight" - target: ripple - to: rippleAnim.radius * 2 - } - - Anim { - property: "opacity" - target: ripple - to: 0 - } + alwaysRunToEnd: true + duration: Appearance.anim.durations.expressiveSlowEffects * 2 + easing.bezierCurve: Appearance.anim.curves.standard + property: "circleRadius" + target: root + to: root.endRadius } - CustomClippingRect { - id: hoverLayer + Anim { + id: fadeAnim + + property: "opacity" + target: circle + to: 0 + type: Anim.SlowEffects + } + + CustomRect { + id: base anchors.fill: parent - border.pixelAligned: false - color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0) - radius: root.radius + bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0 + bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0 + color: DynamicColors.palette.m3onSurface + opacity: root.stateOpacity + // Pick up radius from parent if it has one (parent can be anything with radius props) + // qmllint disable missing-property + radius: root.parent?.radius ?? 0 + topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0 + topRightRadius: root.parent?.topRightRadius ?? radius ?? 0 + // qmllint enable missing-property + } - CustomRect { - id: ripple + Shape { + id: circle - border.pixelAligned: false - color: root.color - opacity: 0 - radius: Appearance.rounding.full + anchors.fill: parent + opacity: 0 + preferredRendererType: Shape.CurveRenderer - transform: Translate { - x: -ripple.width / 2 - y: -ripple.height / 2 + ShapePath { + fillColor: base.color + startX: root.clamp(base.topLeftRadius) + startY: 0 + strokeColor: "transparent" + strokeWidth: 0 + + fillGradient: RadialGradient { + centerRadius: root.circleRadius + centerX: root.pressX + centerY: root.pressY + focalX: centerX + focalY: centerY + + GradientStop { + color: Qt.alpha(base.color, 1) + position: 0 + } + + GradientStop { + color: Qt.alpha(base.color, 1) + position: ZUtils.clamp(1 - 0.2 * root.endRadius / root.circleRadius, 0.01, 0.99) + } + + GradientStop { + color: Qt.alpha(base.color, ZUtils.clamp((root.circleRadius / root.endRadius - 0.9) / 0.1, 0, 1)) + position: 1 + } + } + + PathLine { + x: root.width - root.clamp(base.topRightRadius) + y: 0 + } + + PathArc { + radiusX: root.clamp(base.topRightRadius) + radiusY: root.clamp(base.topRightRadius) + relativeX: root.clamp(base.topRightRadius) + relativeY: root.clamp(base.topRightRadius) + } + + PathLine { + x: root.width + y: root.height - root.clamp(base.bottomRightRadius) + } + + PathArc { + radiusX: root.clamp(base.bottomRightRadius) + radiusY: root.clamp(base.bottomRightRadius) + relativeX: -root.clamp(base.bottomRightRadius) + relativeY: root.clamp(base.bottomRightRadius) + } + + PathLine { + x: root.clamp(base.bottomLeftRadius) + y: root.height + } + + PathArc { + radiusX: root.clamp(base.bottomLeftRadius) + radiusY: root.clamp(base.bottomLeftRadius) + relativeX: -root.clamp(base.bottomLeftRadius) + relativeY: -root.clamp(base.bottomLeftRadius) + } + + PathLine { + x: 0 + y: root.clamp(base.topLeftRadius) + } + + PathArc { + radiusX: root.clamp(base.topLeftRadius) + radiusY: root.clamp(base.topLeftRadius) + x: root.clamp(base.topLeftRadius) + y: 0 } } } diff --git a/Config/Appearance.qml b/Config/Appearance.qml index f78ffc1..7be277e 100644 --- a/Config/Appearance.qml +++ b/Config/Appearance.qml @@ -7,8 +7,6 @@ Singleton { readonly property AppearanceConf.Deform deform: Config.appearance.deform readonly property AppearanceConf.FontStuff font: Config.appearance.font readonly property AppearanceConf.Padding padding: Config.appearance.padding - // Literally just here to shorten accessing stuff :woe: - // Also kinda so I can keep accessing it with `Appearance.xxx` instead of `Conf.appearance.xxx` readonly property AppearanceConf.Rounding rounding: Config.appearance.rounding readonly property AppearanceConf.Spacing spacing: Config.appearance.spacing readonly property AppearanceConf.Transparency transparency: Config.appearance.transparency diff --git a/Config/AppearanceConf.qml b/Config/AppearanceConf.qml index 60bab0f..7d3b375 100644 --- a/Config/AppearanceConf.qml +++ b/Config/AppearanceConf.qml @@ -28,9 +28,13 @@ JsonObject { property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] + property list expressiveDefaultEffects: [0.34, 0.8, 0.34, 1, 1, 1] property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] property list expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] + property list expressiveFastEffects: [0.31, 0.94, 0.34, 1, 1, 1] property list expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] + property list expressiveSlowEffects: [0.34, 0.88, 0.34, 1, 1, 1] + property list expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] property list standard: [0.2, 0, 0, 1, 1, 1] property list standardAccel: [0.3, 0, 1, 1, 1, 1] property list standardDecel: [0, 0, 0, 1, 1, 1] @@ -38,7 +42,9 @@ JsonObject { component AnimDurations: JsonObject { property int expressiveDefaultSpatial: 500 * scale property int expressiveEffects: 200 * scale + property int expressiveFastEffects: 150 * scale property int expressiveFastSpatial: 350 * scale + property int expressiveSlowEffects: 300 * scale property int extraLarge: 1000 * scale property int large: 600 * scale property int normal: 400 * scale @@ -71,7 +77,7 @@ JsonObject { } component Padding: JsonObject { property int large: 15 * scale - property int larger: 13 * scale + property int larger: 12 * scale property int normal: 9 * scale property real scale: 1 property int small: 5 * scale @@ -79,6 +85,7 @@ JsonObject { property int smallest: 2 * scale } component Rounding: JsonObject { + property int extraSmall: 4 * scale property int full: 1000 * scale property int large: 24 * scale property int normal: 18 * scale @@ -87,6 +94,7 @@ JsonObject { property int smallest: 8 * scale } component Spacing: JsonObject { + property int extraSmall: 4 * scale property int large: 20 * scale property int larger: 16 * scale property int normal: 12 * scale diff --git a/Modules/Settings/Controls/WallpaperCropper.qml b/Modules/Settings/Controls/WallpaperCropper.qml index f3da6f1..ff90251 100644 --- a/Modules/Settings/Controls/WallpaperCropper.qml +++ b/Modules/Settings/Controls/WallpaperCropper.qml @@ -141,14 +141,14 @@ Item { id: zoomSlider Layout.fillWidth: true - Layout.preferredHeight: 30 + Layout.preferredHeight: Appearance.padding.larger * 3 from: 1.0 - implicitHeight: 30 + implicitHeight: Appearance.padding.larger * 3 to: 5.0 value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0 - onMoved: { - delegate.zoomClipRect(value); + onInteraction: value => { + delegate.zoomClipRect(1 + (value * 4)); wrapper.changesMade = true; } } diff --git a/Modules/SysTray/Popouts/AudioPopup.qml b/Modules/SysTray/Popouts/AudioPopup.qml index 9d2d92b..e6b8ca8 100644 --- a/Modules/SysTray/Popouts/AudioPopup.qml +++ b/Modules/SysTray/Popouts/AudioPopup.qml @@ -19,7 +19,7 @@ Item { required property var wrapper implicitHeight: vol.implicitHeight + Appearance.padding.small * 2 - implicitWidth: 400 + Appearance.padding.small * 2 + implicitWidth: 600 + Appearance.padding.small * 2 CustomRect { anchors.left: parent.left @@ -52,7 +52,7 @@ Item { CustomRect { Layout.fillWidth: true - Layout.preferredHeight: 50 + Appearance.spacing.smaller * 2 + Layout.preferredHeight: 65 + Appearance.spacing.smaller * 2 Layout.topMargin: root.topMargin color: DynamicColors.tPalette.m3surfaceContainer radius: root.rounding @@ -62,15 +62,15 @@ Item { anchors.bottom: parent.bottom anchors.left: parent.left - anchors.leftMargin: Appearance.padding.normal + anchors.leftMargin: Appearance.padding.larger anchors.top: parent.top implicitWidth: childrenRect.width CustomRect { anchors.centerIn: parent color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary - implicitHeight: 40 - implicitWidth: 40 + implicitHeight: 54 + implicitWidth: 54 radius: Appearance.rounding.full MaterialIcon { @@ -78,7 +78,7 @@ Item { anchors.centerIn: parent animate: true color: Audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary - font.pointSize: 22 + font.pointSize: Appearance.font.size.extraLarge text: Audio.muted ? "volume_off" : "volume_up" } @@ -98,7 +98,7 @@ Item { anchors.bottom: parent.bottom anchors.bottomMargin: Appearance.padding.smallest anchors.left: sinkIcon.right - anchors.leftMargin: Appearance.spacing.normal + anchors.leftMargin: Appearance.spacing.larger anchors.right: parent.right anchors.rightMargin: Appearance.padding.large anchors.top: parent.top @@ -122,14 +122,14 @@ Item { } CustomMouseArea { - Layout.bottomMargin: 5 - Layout.fillHeight: true + Layout.bottomMargin: Appearance.padding.normal Layout.fillWidth: true + Layout.preferredHeight: Appearance.padding.larger * 3 CustomSlider { anchors.left: parent.left anchors.right: parent.right - color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary + fgColor: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary implicitHeight: parent.height value: Audio.volume @@ -138,7 +138,7 @@ Item { } } - onMoved: Audio.setVolume(value) + onInteraction: value => Audio.setVolume(value) } } } @@ -146,7 +146,7 @@ Item { CustomClippingRect { Layout.fillWidth: true - Layout.preferredHeight: 50 + Appearance.spacing.smaller * 2 + Layout.preferredHeight: 65 + Appearance.spacing.smaller * 2 Layout.topMargin: root.topMargin color: DynamicColors.tPalette.m3surfaceContainer radius: root.rounding @@ -178,15 +178,15 @@ Item { anchors.bottom: parent.bottom anchors.left: parent.left - anchors.leftMargin: Appearance.padding.normal + anchors.leftMargin: Appearance.padding.larger anchors.top: parent.top implicitWidth: childrenRect.width CustomRect { anchors.centerIn: parent color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary - implicitHeight: 40 - implicitWidth: 40 + implicitHeight: 54 + implicitWidth: 54 radius: Appearance.rounding.full MaterialIcon { @@ -194,7 +194,7 @@ Item { anchors.centerIn: parent animate: true color: Audio.sourceMuted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary - font.pointSize: 22 + font.pointSize: Appearance.font.size.extraLarge text: Audio.sourceMuted ? "mic_off" : "mic" } @@ -214,7 +214,7 @@ Item { anchors.bottom: parent.bottom anchors.bottomMargin: Appearance.padding.smallest anchors.left: sourceIcon.right - anchors.leftMargin: Appearance.spacing.normal + anchors.leftMargin: Appearance.spacing.larger anchors.right: parent.right anchors.rightMargin: Appearance.padding.large anchors.top: parent.top @@ -238,14 +238,14 @@ Item { } CustomMouseArea { - Layout.bottomMargin: 5 - Layout.fillHeight: true + Layout.bottomMargin: Appearance.padding.normal Layout.fillWidth: true + Layout.preferredHeight: Appearance.padding.larger * 3 CustomSlider { anchors.left: parent.left anchors.right: parent.right - color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary + fgColor: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary implicitHeight: parent.height value: Audio.sourceVolume @@ -254,7 +254,7 @@ Item { } } - onMoved: Audio.setSourceVolume(value) + onInteraction: value => Audio.setSourceVolume(value) } } } @@ -280,7 +280,7 @@ Item { required property var modelData Layout.fillWidth: true - Layout.preferredHeight: 50 + Appearance.spacing.smaller * 2 + Layout.preferredHeight: 65 + Appearance.spacing.smaller * 2 Layout.topMargin: root.topMargin color: DynamicColors.tPalette.m3surfaceContainer radius: root.rounding @@ -312,15 +312,15 @@ Item { anchors.bottom: parent.bottom anchors.left: parent.left - anchors.leftMargin: Appearance.padding.normal + anchors.leftMargin: Appearance.padding.larger anchors.top: parent.top implicitWidth: childrenRect.width CustomRect { anchors.centerIn: parent color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary - implicitHeight: 40 - implicitWidth: 40 + implicitHeight: 54 + implicitWidth: 54 radius: Appearance.rounding.full MaterialIcon { @@ -329,7 +329,7 @@ Item { anchors.centerIn: parent animate: true color: appBox.modelData.audio.muted ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary - font.pointSize: 22 + font.pointSize: Appearance.font.size.extraLarge text: appBox.modelData.audio.muted ? "volume_off" : "volume_up" } @@ -356,7 +356,7 @@ Item { anchors.bottom: parent.bottom anchors.bottomMargin: Appearance.padding.smallest anchors.left: appBoxIcon.right - anchors.leftMargin: Appearance.spacing.normal + anchors.leftMargin: Appearance.spacing.larger anchors.right: parent.right anchors.rightMargin: Appearance.padding.large anchors.top: parent.top @@ -381,18 +381,18 @@ Item { } CustomMouseArea { - Layout.bottomMargin: 5 - Layout.fillHeight: true + Layout.bottomMargin: Appearance.padding.normal Layout.fillWidth: true + Layout.preferredHeight: Appearance.padding.larger * 3 CustomSlider { anchors.left: parent.left anchors.right: parent.right - color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary + fgColor: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary implicitHeight: parent.height value: appBox.modelData.audio.volume - onMoved: { + onInteraction: value => { Audio.setStreamVolume(appBox.modelData, value); } } diff --git a/Plugins/ZShell/CMakeLists.txt b/Plugins/ZShell/CMakeLists.txt index f691bc9..0217b06 100644 --- a/Plugins/ZShell/CMakeLists.txt +++ b/Plugins/ZShell/CMakeLists.txt @@ -45,6 +45,7 @@ qml_module(ZShell requests.hpp requests.cpp toaster.hpp toaster.cpp qalculator.hpp qalculator.cpp + zutils.hpp zutils.cpp LIBRARIES Qt::Gui Qt::Quick diff --git a/Plugins/ZShell/Components/CMakeLists.txt b/Plugins/ZShell/Components/CMakeLists.txt index f34e7fb..16d7aa3 100644 --- a/Plugins/ZShell/Components/CMakeLists.txt +++ b/Plugins/ZShell/Components/CMakeLists.txt @@ -2,6 +2,7 @@ qml_module(ZShell-components URI ZShell.Components SOURCES lazylistview.hpp lazylistview.cpp + wavyline.hpp wavyline.cpp LIBRARIES Qt::Quick ) diff --git a/Plugins/ZShell/Components/wavyline.cpp b/Plugins/ZShell/Components/wavyline.cpp new file mode 100644 index 0000000..757b7a2 --- /dev/null +++ b/Plugins/ZShell/Components/wavyline.cpp @@ -0,0 +1,255 @@ +#include "wavyline.hpp" + +#include +#include + +namespace ZShell::controls { + +WavyLine::WavyLine(QQuickItem* parent) + : QQuickPaintedItem(parent) + , m_lineWidth(4) + , m_amplitudeMultiplier(0.5) + , m_frequency(6) + , m_startX(0) + , m_fullLength(0) + , m_color(Qt::white) + , m_waveProgress(0) + , m_pathType(Linear) + , m_startAngle(0) + , m_fullAngle(360) + , m_radius(-1) + , m_value(1) + , m_startAngleRad(0) + , m_fullAngleRad(2 * M_PI) { + setAntialiasing(true); +} + +int WavyLine::lineWidth() const { + return m_lineWidth; +} + +void WavyLine::setLineWidth(int lineWidth) { + if (m_lineWidth != lineWidth) { + m_lineWidth = lineWidth; + emit lineWidthChanged(); + update(); + } +} + +qreal WavyLine::amplitudeMultiplier() const { + return m_amplitudeMultiplier; +} + +void WavyLine::setAmplitudeMultiplier(qreal amplitudeMultiplier) { + if (!qFuzzyCompare(m_amplitudeMultiplier + 1.0, amplitudeMultiplier + 1.0)) { + m_amplitudeMultiplier = amplitudeMultiplier; + emit amplitudeMultiplierChanged(); + update(); + } +} + +int WavyLine::frequency() const { + return m_frequency; +} + +void WavyLine::setFrequency(int frequency) { + if (m_frequency != frequency) { + m_frequency = frequency; + emit frequencyChanged(); + update(); + } +} + +qreal WavyLine::startX() const { + return m_startX; +} + +void WavyLine::setStartX(qreal startX) { + if (!qFuzzyCompare(m_startX + 1.0, startX + 1.0)) { + m_startX = startX; + emit startXChanged(); + update(); + } +} + +qreal WavyLine::fullLength() const { + return m_fullLength; +} + +void WavyLine::setFullLength(qreal fullLength) { + if (!qFuzzyCompare(m_fullLength + 1.0, fullLength + 1.0)) { + m_fullLength = fullLength; + emit fullLengthChanged(); + update(); + } +} + +QColor WavyLine::color() const { + return m_color; +} + +void WavyLine::setColor(const QColor& color) { + if (m_color != color) { + m_color = color; + emit colorChanged(); + update(); + } +} + +qreal WavyLine::waveProgress() const { + return m_waveProgress; +} + +void WavyLine::setWaveProgress(qreal progress) { + if (!qFuzzyCompare(m_waveProgress + 1.0, progress + 1.0)) { + m_waveProgress = progress; + emit waveProgressChanged(); + update(); + } +} + +WavyLine::PathType WavyLine::pathType() const { + return m_pathType; +} + +void WavyLine::setPathType(PathType pathType) { + if (m_pathType != pathType) { + m_pathType = pathType; + emit pathTypeChanged(); + update(); + } +} + +qreal WavyLine::startAngle() const { + return m_startAngle; +} + +void WavyLine::setStartAngle(qreal startAngle) { + if (!qFuzzyCompare(m_startAngle + 1.0, startAngle + 1.0)) { + m_startAngle = startAngle; + m_startAngleRad = startAngle * M_PI / 180.0; + emit startAngleChanged(); + update(); + } +} + +qreal WavyLine::fullAngle() const { + return m_fullAngle; +} + +void WavyLine::setFullAngle(qreal fullAngle) { + if (!qFuzzyCompare(m_fullAngle + 1.0, fullAngle + 1.0)) { + m_fullAngle = fullAngle; + m_fullAngleRad = fullAngle * M_PI / 180.0; + emit fullAngleChanged(); + update(); + } +} + +qreal WavyLine::radius() const { + return m_radius; +} + +void WavyLine::setRadius(qreal radius) { + if (!qFuzzyCompare(m_radius + 1.0, radius + 1.0)) { + m_radius = radius; + emit radiusChanged(); + update(); + } +} + +qreal WavyLine::value() const { + return m_value; +} + +void WavyLine::setValue(qreal value) { + if (!qFuzzyCompare(m_value + 1.0, value + 1.0)) { + m_value = value; + emit valueChanged(); + update(); + } +} + +void WavyLine::paint(QPainter* painter) { + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(QPen(m_color, m_lineWidth, Qt::SolidLine, Qt::RoundCap)); + + if (m_pathType == Arc) { + paintArc(painter); + } else { + paintLinear(painter); + } +} + +void WavyLine::paintLinear(QPainter* painter) { + const auto amplitude = m_lineWidth * m_amplitudeMultiplier; + const auto phase = m_waveProgress * 2 * M_PI; + const auto centerY = height() / 2; + const auto len = m_fullLength > 0 ? m_fullLength : 1; + const auto start = m_lineWidth / 2.0; + const auto fullEnd = width() - m_lineWidth / 2.0; + const auto drawEnd = start + (fullEnd - start) * m_value; + + QPainterPath path; + bool first = true; + + for (int x = m_lineWidth / 2; x <= drawEnd; ++x) { + const auto theta = m_frequency * 2 * M_PI * (x + m_startX) / len + phase; + const auto waveY = centerY + amplitude * qSin(theta); + if (first) { + path.moveTo(x, waveY); + first = false; + } else { + path.lineTo(x, waveY); + } + } + + painter->drawPath(path); +} + +void WavyLine::paintArc(QPainter* painter) { + if (m_fullAngleRad <= 0) { + return; + } + + const auto amplitude = m_lineWidth * m_amplitudeMultiplier; + const auto cx = width() / 2.0; + const auto cy = height() / 2.0; + const auto radius = m_radius > 0 ? m_radius : (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0; + + if (radius <= 0) { + return; + } + + const auto phase = m_waveProgress * 2 * M_PI; + const auto arcLen = radius * m_fullAngleRad; + const auto len = m_fullLength > 0 ? m_fullLength : arcLen; + const auto drawAngleRad = m_fullAngleRad * m_value; + + if (drawAngleRad <= 0) { + return; + } + + const auto N = qMax(64, qCeil(radius * drawAngleRad)); + const auto dTheta = drawAngleRad / N; + + QPainterPath path; + + for (int i = 0; i <= N; ++i) { + const auto theta = m_startAngleRad + i * dTheta; + const auto s = i * dTheta * radius; + const auto phi = m_frequency * 2 * M_PI * (s + m_startX) / len + phase; + const auto r = radius + amplitude * qSin(phi); + const auto px = cx + r * qCos(theta); + const auto py = cy + r * qSin(theta); + if (i == 0) { + path.moveTo(px, py); + } else { + path.lineTo(px, py); + } + } + + painter->drawPath(path); +} + +} // namespace ZShell::controls diff --git a/Plugins/ZShell/Components/wavyline.hpp b/Plugins/ZShell/Components/wavyline.hpp new file mode 100644 index 0000000..e9fab48 --- /dev/null +++ b/Plugins/ZShell/Components/wavyline.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +namespace ZShell::controls { + +class WavyLine : public QQuickPaintedItem { +Q_OBJECT +QML_ELEMENT + +Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged FINAL) +Q_PROPERTY(qreal amplitudeMultiplier READ amplitudeMultiplier WRITE setAmplitudeMultiplier NOTIFY + amplitudeMultiplierChanged FINAL) +Q_PROPERTY(int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged FINAL) +Q_PROPERTY(qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL) +Q_PROPERTY(qreal fullLength READ fullLength WRITE setFullLength NOTIFY fullLengthChanged FINAL) +Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL) +Q_PROPERTY(qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY waveProgressChanged FINAL) +Q_PROPERTY(PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged FINAL) +Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged FINAL) +Q_PROPERTY(qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY fullAngleChanged FINAL) +Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL) +Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: +enum PathType { + Linear, + Arc +}; +Q_ENUM(PathType) + +explicit WavyLine(QQuickItem* parent = nullptr); + +[[nodiscard]] int lineWidth() const; +void setLineWidth(int lineWidth); + +[[nodiscard]] qreal amplitudeMultiplier() const; +void setAmplitudeMultiplier(qreal amplitudeMultiplier); + +[[nodiscard]] int frequency() const; +void setFrequency(int frequency); + +[[nodiscard]] qreal startX() const; +void setStartX(qreal startX); + +[[nodiscard]] qreal fullLength() const; +void setFullLength(qreal fullLength); + +[[nodiscard]] QColor color() const; +void setColor(const QColor& color); + +[[nodiscard]] qreal waveProgress() const; +void setWaveProgress(qreal progress); + +[[nodiscard]] PathType pathType() const; +void setPathType(PathType pathType); + +[[nodiscard]] qreal startAngle() const; +void setStartAngle(qreal startAngle); + +[[nodiscard]] qreal fullAngle() const; +void setFullAngle(qreal fullAngle); + +[[nodiscard]] qreal radius() const; +void setRadius(qreal radius); + +[[nodiscard]] qreal value() const; +void setValue(qreal value); + +void paint(QPainter* painter) override; + +signals: +void lineWidthChanged(); +void amplitudeMultiplierChanged(); +void frequencyChanged(); +void startXChanged(); +void fullLengthChanged(); +void colorChanged(); +void waveProgressChanged(); +void pathTypeChanged(); +void startAngleChanged(); +void fullAngleChanged(); +void radiusChanged(); +void valueChanged(); + +private: +void paintLinear(QPainter* painter); +void paintArc(QPainter* painter); + +int m_lineWidth; +qreal m_amplitudeMultiplier; +int m_frequency; +qreal m_startX; +qreal m_fullLength; +QColor m_color; +qreal m_waveProgress; +PathType m_pathType; +qreal m_startAngle; +qreal m_fullAngle; +qreal m_radius; +qreal m_value; +qreal m_startAngleRad; +qreal m_fullAngleRad; +}; + +} // namespace ZShell::controls diff --git a/Plugins/ZShell/zutils.cpp b/Plugins/ZShell/zutils.cpp new file mode 100644 index 0000000..c26bd0f --- /dev/null +++ b/Plugins/ZShell/zutils.cpp @@ -0,0 +1,145 @@ +#include "zutils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(lcZUtils, "ZShell.cutils", QtInfoMsg) + +namespace ZShell { + +void ZUtils::saveItem(QQuickItem* target, const QUrl& path) { + this->saveItem(target, path, QRect(), QJSValue(), QJSValue()); +} + +void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect) { + this->saveItem(target, path, rect, QJSValue(), QJSValue()); +} + +void ZUtils::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved) { + this->saveItem(target, path, QRect(), onSaved, QJSValue()); +} + +void ZUtils::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) { + this->saveItem(target, path, QRect(), onSaved, onFailed); +} + +void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) { + this->saveItem(target, path, rect, onSaved, QJSValue()); +} + +void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed) { + if (!target) { + qCWarning(lcZUtils) << "saveItem: a target is required"; + return; + } + + if (!path.isLocalFile()) { + qCWarning(lcZUtils) << "saveItem:" << path << "is not a local file"; + return; + } + + if (!target->window()) { + qCWarning(lcZUtils) << "saveItem: unable to save target" << target << "without a window"; + return; + } + + auto scaledRect = rect; + const qreal scale = target->window()->devicePixelRatio(); + if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) { + scaledRect = + QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale).toRect(); + } + + const QSharedPointer grabResult = target->grabToImage(); + + QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, + [grabResult, scaledRect, path, onSaved, onFailed, this]() { + const auto future = QtConcurrent::run([=]() { + QImage image = grabResult->image(); + + if (scaledRect.isValid()) { + image = image.copy(scaledRect); + } + + const QString file = path.toLocalFile(); + const QString parent = QFileInfo(file).absolutePath(); + return QDir().mkpath(parent) && image.save(file); + }); + + auto* watcher = new QFutureWatcher(this); + auto* engine = qmlEngine(this); + + QObject::connect(watcher, &QFutureWatcher::finished, this, [=]() { + if (watcher->result()) { + if (onSaved.isCallable()) { + QJSValueList args = { QJSValue(path.toLocalFile()) }; + if (engine) { + args << engine->toScriptValue(QVariant::fromValue(path)); + } + onSaved.call(args); + } + } else { + qCWarning(lcZUtils) << "saveItem: failed to save" << path; + if (onFailed.isCallable()) { + if (engine) { + onFailed.call({ engine->toScriptValue(QVariant::fromValue(path)) }); + } else { + onFailed.call(); + } + } + } + watcher->deleteLater(); + }); + watcher->setFuture(future); + }); +} + +bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) { + if (!source.isLocalFile()) { + qCWarning(lcZUtils) << "copyFile: source" << source << "is not a local file"; + return false; + } + if (!target.isLocalFile()) { + qCWarning(lcZUtils) << "copyFile: target" << target << "is not a local file"; + return false; + } + + if (overwrite && QFile::exists(target.toLocalFile())) { + if (!QFile::remove(target.toLocalFile())) { + qCWarning(lcZUtils) << "copyFile: overwrite was specified but failed to remove" << target.toLocalFile(); + return false; + } + } + + return QFile::copy(source.toLocalFile(), target.toLocalFile()); +} + +bool ZUtils::deleteFile(const QUrl& path) { + if (!path.isLocalFile()) { + qCWarning(lcZUtils) << "deleteFile: path" << path << "is not a local file"; + return false; + } + + return QFile::remove(path.toLocalFile()); +} + +QString ZUtils::toLocalFile(const QUrl& url) { + if (!url.isLocalFile()) { + qCWarning(lcZUtils) << "toLocalFile: given url is not a local file" << url; + return QString(); + } + + return url.toLocalFile(); +} + +qreal ZUtils::clamp(qreal value, qreal min, qreal max) { + return qBound(min, value, max); +} + +} // namespace ZShell diff --git a/Plugins/ZShell/zutils.hpp b/Plugins/ZShell/zutils.hpp new file mode 100644 index 0000000..fdc413d --- /dev/null +++ b/Plugins/ZShell/zutils.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +namespace ZShell { + +class ZUtils : public QObject { +Q_OBJECT +QML_ELEMENT +QML_SINGLETON + +public: +// clang-format off +Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); +Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); +Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); +Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); +Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); +Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); +// clang-format on + +Q_INVOKABLE static bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true); +Q_INVOKABLE static bool deleteFile(const QUrl& path); +Q_INVOKABLE static QString toLocalFile(const QUrl& url); + +Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max); +}; + +} // namespace ZShell -- 2.47.3 From 50e99501de904d878753788e9effdd3ca3538a37 Mon Sep 17 00:00:00 2001 From: zach Date: Sat, 6 Jun 2026 21:13:44 +0200 Subject: [PATCH 2/3] switches and popouts --- Components/CustomSplitButton.qml | 103 ++-- Components/CustomSplitButtonRow.qml | 12 +- Components/CustomSwitch.qml | 26 +- Components/Elevation.qml | 3 +- Components/Menu.qml | 198 ++++--- Config/AppearanceConf.qml | 10 +- Drawers/Drawers.qml | 25 + Drawers/Interactions.qml | 2 +- Drawers/Windows.qml | 701 ++++++++++++------------ Modules/Settings/Categories/General.qml | 3 +- shell.qml | 2 +- 11 files changed, 566 insertions(+), 519 deletions(-) create mode 100644 Drawers/Drawers.qml diff --git a/Components/CustomSplitButton.qml b/Components/CustomSplitButton.qml index 3621a71..76364cc 100644 --- a/Components/CustomSplitButton.qml +++ b/Components/CustomSplitButton.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Layouts import qs.Config -import qs.Helpers Row { id: root @@ -12,62 +11,46 @@ Row { } property alias active: menu.active - property color color: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer + property color colour: type == CustomSplitButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer property bool disabled - property color disabledColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) - property color disabledTextColor: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) + property color disabledColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.1) + property color disabledTextColour: Qt.alpha(DynamicColors.palette.m3onSurface, 0.38) + readonly property alias expandBtn: expandBtn property alias expanded: menu.expanded property string fallbackIcon property string fallbackText - property real horizontalPadding: Appearance.padding.normal - property alias iconLabel: iconLabel - property alias label: label - property alias menu: menu + property real horizontalPadding: Appearance.padding.larger + readonly property alias iconLabel: iconLabel + readonly property alias label: label + readonly property alias menu: menu property alias menuItems: menu.items property bool menuOnTop - property alias stateLayer: stateLayer - property color textColor: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer + property real minLeftWidth + readonly property alias stateLayer: stateLayer + property color textColour: type == CustomSplitButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer + readonly property alias textRow: textRow property int type: CustomSplitButton.Filled - property real verticalPadding: Appearance.padding.smaller + property real verticalPadding: Appearance.padding.small - function closeDropdown(): void { - SettingsDropdowns.close(menu); - } - - function openDropdown(): void { - SettingsDropdowns.open(menu, root); - } - - function toggleDropdown(): void { - SettingsDropdowns.toggle(menu, root); - } - - spacing: Math.floor(Appearance.spacing.small / 2) - - onExpandedChanged: { - if (!expanded) - SettingsDropdowns.forget(menu); - } + spacing: Math.floor(Appearance.spacing.extraSmall) CustomRect { bottomRightRadius: Appearance.rounding.small / 2 - color: !root.enabled ? root.disabledColor : root.color + color: root.disabled ? root.disabledColour : root.colour implicitHeight: expandBtn.implicitHeight - implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2 + implicitWidth: Math.max(root.minLeftWidth, textRow.implicitWidth + root.horizontalPadding * 2) radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) topRightRadius: Appearance.rounding.small / 2 StateLayer { id: stateLayer - function onClicked(): void { - root.active?.clicked(); - } + bottomRightRadius: parent.bottomRightRadius + color: root.textColour + disabled: root.disabled + topRightRadius: parent.topRightRadius - color: root.textColor - disabled: !root.enabled - rect.bottomRightRadius: parent.bottomRightRadius - rect.topRightRadius: parent.topRightRadius + onClicked: root.active?.clicked() } RowLayout { @@ -82,7 +65,7 @@ Row { Layout.alignment: Qt.AlignVCenter animate: true - color: !root.enabled ? root.disabledTextColor : root.textColor + color: root.disabled ? root.disabledTextColour : root.textColour fill: 1 text: root.active?.activeIcon ?? root.fallbackIcon } @@ -94,12 +77,12 @@ Row { Layout.preferredWidth: implicitWidth animate: true clip: true - color: !root.enabled ? root.disabledTextColor : root.textColor + color: root.disabled ? root.disabledTextColour : root.textColour text: root.active?.activeText ?? root.fallbackText Behavior on Layout.preferredWidth { Anim { - easing.bezierCurve: Appearance.anim.curves.emphasized + type: Anim.Emphasized } } } @@ -112,7 +95,7 @@ Row { property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2 bottomLeftRadius: rad - color: !root.enabled ? root.disabledColor : root.color + color: root.disabled ? root.disabledColour : root.colour implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2 implicitWidth: implicitHeight radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) @@ -126,14 +109,12 @@ Row { StateLayer { id: expandStateLayer - function onClicked(): void { - root.toggleDropdown(); - } - - color: root.textColor - disabled: !root.enabled + color: root.textColour + disabled: root.disabled rect.bottomLeftRadius: parent.bottomLeftRadius rect.topLeftRadius: parent.topLeftRadius + + onClicked: root.expanded = !root.expanded } MaterialIcon { @@ -141,7 +122,7 @@ Row { anchors.centerIn: parent anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4) - color: !root.enabled ? root.disabledTextColor : root.textColor + color: root.disabled ? root.disabledTextColour : root.textColour rotation: root.expanded ? 180 : 0 text: "expand_more" @@ -154,24 +135,14 @@ Row { } } } + } - Menu { - id: menu + Menu { + id: menu - anchors.bottomMargin: Appearance.spacing.small - anchors.right: parent.right - anchors.top: parent.bottom - anchors.topMargin: Appearance.spacing.small - - states: State { - when: root.menuOnTop - - AnchorChanges { - anchors.bottom: expandBtn.top - anchors.top: undefined - target: menu - } - } - } + attachSideY: root.menuOnTop ? Menu.Top : Menu.Bottom + attachTo: expandBtn + marginY: Appearance.spacing.small * (root.menuOnTop ? -1 : 1) + thisSideY: root.menuOnTop ? Menu.Bottom : Menu.Top } } diff --git a/Components/CustomSplitButtonRow.qml b/Components/CustomSplitButtonRow.qml index 15bb182..2e59738 100644 --- a/Components/CustomSplitButtonRow.qml +++ b/Components/CustomSplitButtonRow.qml @@ -9,7 +9,6 @@ Item { property alias active: splitButton.active property alias buttonAlias: splitButton - property bool enabled: true property alias expanded: splitButton.expanded property int expandedZ: 100 required property string label @@ -25,7 +24,7 @@ Item { implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2 opacity: shouldBeActive ? 1 : 0 scale: shouldBeActive ? 1 : 0.8 - z: root.expanded ? expandedZ : -1 + z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1 Behavior on opacity { Anim { @@ -50,7 +49,6 @@ Item { color: root.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSurfaceVariant font.pointSize: Appearance.font.size.larger text: root.label - z: root.expanded ? root.expandedZ : -1 } CustomSplitButton { @@ -58,14 +56,16 @@ Item { enabled: root.enabled type: CustomSplitButton.Filled - z: root.expanded ? root.expandedZ : -1 + z: 2 menu.onItemSelected: item => { root.selected(item); - splitButton.closeDropdown(); + // splitButton.closeDropdown(); } stateLayer.onClicked: { - splitButton.toggleDropdown(); + // splitButton.toggleDropdown(); + splitButton.expanded = !splitButton.expanded; + console.log(root.z); } } } diff --git a/Components/CustomSwitch.qml b/Components/CustomSwitch.qml index 45f4246..e715f15 100644 --- a/Components/CustomSwitch.qml +++ b/Components/CustomSwitch.qml @@ -1,6 +1,6 @@ import QtQuick -import QtQuick.Templates import QtQuick.Shapes +import QtQuick.Templates import qs.Config Switch { @@ -13,26 +13,28 @@ Switch { indicator: CustomRect { color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) - implicitHeight: 13 + 7 * 2 + implicitHeight: Appearance.font.size.medium + Appearance.padding.normal * 2 implicitWidth: implicitHeight * 1.7 radius: Appearance.rounding.full CustomRect { - readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight + readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.2 : implicitHeight anchors.verticalCenter: parent.verticalCenter color: root.checked && root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1) - implicitHeight: parent.implicitHeight - 10 + implicitHeight: parent.implicitHeight - Appearance.padding.extraSmall implicitWidth: nonAnimWidth radius: Appearance.rounding.full - x: root.checked ? parent.implicitWidth - nonAnimWidth - 10 / 2 : 10 / 2 + x: root.checked ? parent.implicitWidth - nonAnimWidth - Appearance.padding.extraSmall / 2 : Appearance.padding.extraSmall / 2 Behavior on implicitWidth { Anim { + type: Anim.FastSpatial } } Behavior on x { Anim { + type: Anim.FastSpatial } } @@ -44,6 +46,7 @@ Switch { Behavior on opacity { Anim { + type: Anim.DefaultEffects } } } @@ -63,14 +66,14 @@ Switch { } property point end2: { if (root.pressed) - return Qt.point(width, height / 2); + return Qt.point(width * 0.8, height / 2); if (root.checked) return Qt.point(width * 0.85, height * 0.2); return Qt.point(width * 0.85, height * 0.15); } property point start1: { if (root.pressed) - return Qt.point(width * 0.1, height / 2); + return Qt.point(width * 0.2, height / 2); if (root.checked) return Qt.point(width * 0.15, height / 2); return Qt.point(width * 0.15, height * 0.15); @@ -88,7 +91,7 @@ Switch { anchors.centerIn: parent asynchronous: true - height: parent.implicitHeight - Appearance.padding.small * 2 + height: parent.implicitHeight - Appearance.padding.larger preferredRendererType: Shape.CurveRenderer width: height @@ -110,7 +113,7 @@ Switch { } ShapePath { - capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap + capStyle: ShapePath.RoundCap fillColor: "transparent" startX: icon.start1.x startY: icon.start1.y @@ -148,8 +151,7 @@ Switch { } component PropAnim: PropertyAnimation { - duration: MaterialEasing.expressiveEffectsTime - easing.bezierCurve: MaterialEasing.expressiveEffects - easing.type: Easing.BezierSpline + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial } } diff --git a/Components/Elevation.qml b/Components/Elevation.qml index 26b8fe6..bdef51e 100644 --- a/Components/Elevation.qml +++ b/Components/Elevation.qml @@ -1,6 +1,6 @@ -import qs.Config import QtQuick import QtQuick.Effects +import qs.Config RectangularShadow { property real dp: [0, 1, 3, 6, 8, 12][level] @@ -13,6 +13,7 @@ RectangularShadow { Behavior on dp { Anim { + type: Anim.SlowEffects } } } diff --git a/Components/Menu.qml b/Components/Menu.qml index 8222359..a633a9c 100644 --- a/Components/Menu.qml +++ b/Components/Menu.qml @@ -2,109 +2,169 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts +import Quickshell import qs.Config +import qs.Drawers -Elevation { +MouseArea { id: root + enum Side { + Top, + Bottom, + Left, + Right + } + property MenuItem active: items[0] ?? null + property int attachSideX: Menu.Right + property int attachSideY: Menu.Bottom + required property Item attachTo property bool expanded property list items + property real marginX + property real marginY + property int thisSideX: Menu.Right + property int thisSideY: Menu.Top signal itemSelected(item: MenuItem) - implicitHeight: root.expanded ? column.implicitHeight + Appearance.padding.small * 2 : 0 - implicitWidth: Math.max(200, column.implicitWidth) - level: 2 - opacity: root.expanded ? 1 : 0 - radius: Appearance.rounding.normal - - Behavior on implicitHeight { - Anim { - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } + anchors.fill: parent + enabled: expanded + layer.enabled: opacity < 1 + opacity: expanded ? 1 : 0 + parent: { + const win = QsWindow.window; + const contentWin = win as Windows; + return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem; } + Behavior on opacity { Anim { - duration: Appearance.anim.durations.expressiveDefaultSpatial + type: Anim.DefaultEffects } } - CustomClippingRect { - anchors.fill: parent - color: DynamicColors.palette.m3surfaceContainer - radius: parent.radius + onClicked: expanded = false - ColumnLayout { - id: column + TransformWatcher { + id: watcher - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: 5 + a: root.parent + b: root.attachTo + } - Repeater { - model: root.items + Elevation { + id: menu - CustomRect { - id: item + implicitHeight: column.implicitHeight + column.anchors.margins * 2 + implicitWidth: Math.max(200, column.implicitWidth + column.anchors.margins * 2) + level: 2 + radius: Appearance.rounding.medium + x: { + watcher.transform; + const item = root.attachTo; + let off = root.attachSideX === Menu.Left ? 0 : item.width; + if (root.thisSideX === Menu.Right) + off -= width; + return item.mapToItem(root.parent, off, 0).x + root.marginX; + } + y: { + watcher.transform; + const item = root.attachTo; + let off = root.attachSideY === Menu.Top ? 0 : item.height; + if (root.thisSideY === Menu.Bottom) + off -= height; + return item.mapToItem(root.parent, 0, off).y + root.marginY; + } - readonly property bool active: modelData === root.active - required property int index - required property MenuItem modelData + transform: Scale { + origin.y: root.thisSideY === Menu.Bottom ? menu.height : 0 + yScale: root.expanded ? 1 : 0.1 - Layout.fillWidth: true - implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.normal * 2 - implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.normal * 2 + Behavior on yScale { + Anim { + } + } + } + + CustomRect { + anchors.fill: parent + color: DynamicColors.palette.m3surfaceContainerLow + radius: parent.radius + + ColumnLayout { + id: column + + anchors.fill: parent + anchors.margins: Appearance.padding.extraSmall + spacing: Appearance.spacing.extraSmall + + Repeater { + id: repeater + + model: root.items CustomRect { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.small - anchors.rightMargin: Appearance.padding.small - color: Qt.alpha(DynamicColors.palette.m3secondaryContainer, active ? 1 : 0) - radius: Appearance.rounding.normal - Appearance.padding.small + id: item + + readonly property bool active: modelData === root.active + required property int index + required property MenuItem modelData + + Layout.fillWidth: true + color: Qt.alpha(DynamicColors.palette.m3tertiaryContainer, active ? 1 : 0) + implicitHeight: menuOptionRow.implicitHeight + Appearance.padding.larger * 2 + implicitWidth: menuOptionRow.implicitWidth + Appearance.padding.larger * 2 + radius: Appearance.rounding.small + + Behavior on radius { + Anim { + } + } StateLayer { - function onClicked(): void { + color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface + disabled: !root.expanded + + onClicked: { root.itemSelected(item.modelData); root.active = item.modelData; + item.modelData.clicked(); root.expanded = false; } - - color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface - disabled: !root.expanded - } - } - - RowLayout { - id: menuOptionRow - - anchors.fill: parent - anchors.margins: Appearance.padding.normal - spacing: Appearance.spacing.small - - MaterialIcon { - Layout.alignment: Qt.AlignVCenter - color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurfaceVariant - text: item.modelData.icon } - CustomText { - Layout.alignment: Qt.AlignVCenter - Layout.fillWidth: true - color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface - text: item.modelData.text - } + RowLayout { + id: menuOptionRow - Loader { - Layout.alignment: Qt.AlignVCenter - active: item.modelData.trailingIcon.length > 0 - visible: active + anchors.fill: parent + anchors.margins: Appearance.padding.larger + spacing: Appearance.spacing.small - sourceComponent: MaterialIcon { - color: item.active ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface - text: item.modelData.trailingIcon + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant + text: item.modelData.icon + } + + CustomText { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurface + text: item.modelData.text + } + + Loader { + Layout.alignment: Qt.AlignVCenter + active: item.modelData.trailingIcon.length > 0 + asynchronous: true + visible: active + + sourceComponent: MaterialIcon { + color: item.active ? DynamicColors.palette.m3onTertiaryContainer : DynamicColors.palette.m3onSurfaceVariant + text: item.modelData.trailingIcon + } } } } diff --git a/Config/AppearanceConf.qml b/Config/AppearanceConf.qml index 7d3b375..859e0a3 100644 --- a/Config/AppearanceConf.qml +++ b/Config/AppearanceConf.qml @@ -63,7 +63,8 @@ JsonObject { component FontSize: JsonObject { property int extraLarge: 28 * scale property int large: 18 * scale - property int larger: 15 * scale + property int larger: 16 * scale + property int medium: 14 * scale property int normal: 13 * scale property real scale: 1 property int small: 11 * scale @@ -76,9 +77,11 @@ JsonObject { } } component Padding: JsonObject { - property int large: 15 * scale + property int extraLargeIncreased: 32 * scale + property int extraSmall: 4 * scale + property int large: 16 * scale property int larger: 12 * scale - property int normal: 9 * scale + property int normal: 8 * scale property real scale: 1 property int small: 5 * scale property int smaller: 7 * scale @@ -88,6 +91,7 @@ JsonObject { property int extraSmall: 4 * scale property int full: 1000 * scale property int large: 24 * scale + property int medium: 16 * scale property int normal: 18 * scale property real scale: 1 property int small: 12 * scale diff --git a/Drawers/Drawers.qml b/Drawers/Drawers.qml new file mode 100644 index 0000000..b026775 --- /dev/null +++ b/Drawers/Drawers.qml @@ -0,0 +1,25 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell + +Variants { + model: Quickshell.screens + + Scope { + id: scope + + required property ShellScreen modelData + + Exclusions { + bar: content.bar + screen: scope.modelData + } + + Windows { + id: content + + screen: scope.modelData + } + } +} diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index ab7a246..56b1cb2 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -59,7 +59,7 @@ Item { cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined dragThreshold: 0 - grabPermissions: PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything + grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything maximumPointCount: 1 minimumPointCount: 1 target: null diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index f417c43..b59b95e 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -9,397 +9,382 @@ import Quickshell.Hyprland import ZShell.Blobs import qs.Daemons import qs.Components -import qs.Modules import qs.Modules.Bar import qs.Config import qs.Helpers import qs.Drawers -Variants { - model: Quickshell.screens +CustomWindow { + id: root - Scope { - id: scope + readonly property alias bar: bar + readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) + readonly property alias interactionWrapper: interactions + property var root: Quickshell.shellDir - required property var modelData + WlrLayershell.exclusionMode: ExclusionMode.Ignore + // WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + color: "transparent" + contentItem.focus: true + mask: visibilities.isDrawing ? null : region + name: "Bar" - Exclusions { - bar: bar - screen: scope.modelData + contentItem.Keys.onEscapePressed: { + if (Config.barConfig.autoHide) + visibilities.bar = false; + visibilities.sidebar = false; + visibilities.dashboard = false; + visibilities.osd = false; + visibilities.settings = false; + visibilities.resources = false; + } + onHasFullscreenChanged: { + visibilities.launcher = false; + visibilities.dashboard = false; + visibilities.osd = false; + visibilities.settings = false; + visibilities.resources = false; + } + + Region { + id: region + + height: root.height - bar.implicitHeight - Config.barConfig.border + intersection: Intersection.Xor + regions: popoutRegions.instances + width: root.width - Config.barConfig.border * 2 + x: Config.barConfig.border + y: bar.implicitHeight + } + + anchors { + bottom: true + left: true + right: true + top: true + } + + Variants { + id: popoutRegions + + model: panels.children + + Region { + required property Item modelData + + height: modelData.height + intersection: Intersection.Subtract + width: modelData.width + x: modelData.x + Config.barConfig.border + y: modelData.y + bar.implicitHeight + } + } + + HyprlandFocusGrab { + id: focusGrab + + active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu")) + windows: [root] + + onCleared: { + visibilities.launcher = false; + visibilities.sidebar = false; + visibilities.dashboard = false; + visibilities.osd = false; + visibilities.settings = false; + visibilities.resources = false; + visibilities.dock = false; + panels.popouts.hasCurrent = false; + } + } + + PersistentProperties { + id: visibilities + + property bool bar + property bool dashboard + property bool dock + property bool isDrawing + property bool launcher + property bool notif: NotifServer.popups.length > 0 + property bool osd + property bool resources + property bool settings + property bool sidebar + + Component.onCompleted: Visibilities.load(root.screen, this) + } + + IpcHandler { + function toggleLauncher(fix: string): void { + visibilities.launcher = !visibilities.launcher; } - CustomWindow { - id: win + target: "visibilities" + } - readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) - property var root: Quickshell.shellDir + Binding { + property: "bar" + target: visibilities + value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered + when: Config.barConfig.autoHide + } - WlrLayershell.exclusionMode: ExclusionMode.Ignore - // WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None - color: "transparent" - contentItem.focus: true - mask: visibilities.isDrawing ? null : region - name: "Bar" - screen: scope.modelData + Item { + anchors.fill: parent + layer.enabled: true + opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1 - contentItem.Keys.onEscapePressed: { - if (Config.barConfig.autoHide) - visibilities.bar = false; - visibilities.sidebar = false; - visibilities.dashboard = false; - visibilities.osd = false; - visibilities.settings = false; - visibilities.resources = false; - } - onHasFullscreenChanged: { - visibilities.launcher = false; - visibilities.dashboard = false; - visibilities.osd = false; - visibilities.settings = false; - visibilities.resources = false; - } + layer.effect: MultiEffect { + blurMax: 32 + shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1) + shadowEnabled: true + } - Region { - id: region + BlobGroup { + id: blobGroup - height: win.height - bar.implicitHeight - Config.barConfig.border - intersection: Intersection.Xor - regions: popoutRegions.instances - width: win.width - Config.barConfig.border * 2 - x: Config.barConfig.border - y: bar.implicitHeight - } + color: DynamicColors.palette.m3surface + smoothing: Config.barConfig.smoothing - anchors { - bottom: true - left: true - right: true - top: true - } - - Variants { - id: popoutRegions - - model: panels.children - - Region { - required property Item modelData - - height: modelData.height - intersection: Intersection.Subtract - width: modelData.width - x: modelData.x + Config.barConfig.border - y: modelData.y + bar.implicitHeight + Behavior on color { + CAnim { } } + } - HyprlandFocusGrab { - id: focusGrab + BlobInvertedRect { + anchors.fill: parent + anchors.margins: -50 + borderBottom: Config.barConfig.border - anchors.margins + borderLeft: Config.barConfig.border - anchors.margins + borderRight: Config.barConfig.border - anchors.margins + borderTop: bar.implicitHeight - anchors.margins + group: blobGroup + radius: Config.barConfig.rounding + } - active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu")) - windows: [win] + PanelBg { + id: dashBg - onCleared: { - visibilities.launcher = false; - visibilities.sidebar = false; - visibilities.dashboard = false; - visibilities.osd = false; - visibilities.settings = false; - visibilities.resources = false; - visibilities.dock = false; - panels.popouts.hasCurrent = false; + property real extraHeight: 0.2 + + deformAmount: 0.06 + implicitHeight: panels.dashboard.height * (1 + extraHeight) + implicitWidth: panels.dashboard.width + panel: panels.dashboardWrapper + radius: Appearance.rounding.normal + x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border + y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight + } + + PanelBg { + id: launcherBg + + property real extraHeight: 0.2 + + deformAmount: 0.06 + implicitHeight: panels.launcher.height * (1 + extraHeight) + panel: panels.launcher + radius: Appearance.rounding.smallest + 5 + y: panels.launcher.y + bar.implicitHeight + } + + PanelBg { + id: sidebarBg + + bottomLeftRadius: 0 + deformAmount: 0.04 + exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg] + implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2 + panel: panels.sidebar + } + + PanelBg { + id: osdBg + + deformAmount: 0.1 + implicitHeight: panels.osd.height + implicitWidth: panels.osd.width + panel: panels.osdWrapper + radius: 20 + x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border + y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight + } + + PanelBg { + id: notifsBg + + panel: panels.notifications + radius: Appearance.rounding.normal + } + + PanelBg { + id: utilsBg + + deformAmount: panels.sidebar.visible ? (0.1) : (0.1) + exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg] + panel: panels.utilities + topLeftRadius: 0 + } + + PanelBg { + id: popoutBg + + property real extraHeight: panels.popouts.isDetached ? 0 : 0.2 + + deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1 + implicitHeight: panels.popouts.height * (1 + extraHeight) + implicitWidth: panels.popouts.width + panel: panels.popoutsWrapper + radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale + x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border + y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight + + Behavior on extraHeight { + Anim { } } + } - PersistentProperties { - id: visibilities + PanelBg { + id: resourcesBg - property bool bar - property bool dashboard - property bool dock - property bool isDrawing - property bool launcher - property bool notif: NotifServer.popups.length > 0 - property bool osd - property bool resources - property bool settings - property bool sidebar + deformAmount: 0.05 + implicitHeight: panels.resources.height + implicitWidth: panels.resources.width + panel: panels.resourcesWrapper + radius: Appearance.rounding.normal + x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border + y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight + } - Component.onCompleted: Visibilities.load(scope.modelData, this) + PanelBg { + id: settingsBg + + property real extraHeight: 0.2 + + deformAmount: 0.03 + implicitHeight: panels.settings.height * (1 + extraHeight) + implicitWidth: panels.settings.width + panel: panels.settings + radius: Appearance.rounding.large + topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller + topRightRadius: Appearance.rounding.large + Appearance.padding.smaller + x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border + y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight + } + + PanelBg { + id: dockBg + + deformAmount: 0.08 + panel: panels.dock + radius: Appearance.rounding.normal + } + + PanelBg { + id: drawingBg + + deformAmount: 0.08 + panel: panels.drawing + radius: Appearance.rounding.normal + } + } + + Loader { + id: drawingLoader + + active: visibilities.isDrawing + anchors.fill: parent + z: 2 + + sourceComponent: Drawing { + id: drawing + } + } + + Loader { + id: inputLoader + + active: visibilities.isDrawing + anchors.fill: parent + z: 2 + + sourceComponent: DrawingInput { + id: input + + bar: bar + drawing: drawingLoader.item + panels: panels + popout: panels.drawing + visibilities: visibilities + } + } + + Interactions { + id: interactions + + anchors.fill: parent + bar: bar + drawing: drawingLoader.item + enabled: true + input: inputLoader.item + panels: panels + popouts: panels.popouts + screen: root.screen + visibilities: visibilities + z: 1 + + Panels { + id: panels + + bar: bar + drawingItem: drawingLoader.item + screen: root.screen + visibilities: visibilities + + dashboard.transform: Matrix4x4 { + matrix: dashBg.deformMatrix } - - IpcHandler { - function toggleLauncher(fix: string): void { - visibilities.launcher = !visibilities.launcher; - } - - target: "visibilities" + dock.transform: Matrix4x4 { + matrix: dockBg.deformMatrix } - - Binding { - property: "bar" - target: visibilities - value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered - when: Config.barConfig.autoHide + launcher.transform: Matrix4x4 { + matrix: launcherBg.deformMatrix } - - Item { - anchors.fill: parent - layer.enabled: true - opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1 - - layer.effect: MultiEffect { - blurMax: 32 - shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 1) - shadowEnabled: true - } - - BlobGroup { - id: blobGroup - - color: DynamicColors.palette.m3surface - smoothing: Config.barConfig.smoothing - - Behavior on color { - CAnim { - } - } - } - - BlobInvertedRect { - anchors.fill: parent - anchors.margins: -50 - borderBottom: Config.barConfig.border - anchors.margins - borderLeft: Config.barConfig.border - anchors.margins - borderRight: Config.barConfig.border - anchors.margins - borderTop: bar.implicitHeight - anchors.margins - group: blobGroup - radius: Config.barConfig.rounding - } - - PanelBg { - id: dashBg - - property real extraHeight: 0.2 - - deformAmount: 0.06 - implicitHeight: panels.dashboard.height * (1 + extraHeight) - implicitWidth: panels.dashboard.width - panel: panels.dashboardWrapper - radius: Appearance.rounding.normal - x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border - y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight - } - - PanelBg { - id: launcherBg - - property real extraHeight: 0.2 - - deformAmount: 0.06 - implicitHeight: panels.launcher.height * (1 + extraHeight) - panel: panels.launcher - radius: Appearance.rounding.smallest + 5 - y: panels.launcher.y + bar.implicitHeight - } - - PanelBg { - id: sidebarBg - - bottomLeftRadius: 0 - deformAmount: 0.04 - exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg] - implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2 - panel: panels.sidebar - } - - PanelBg { - id: osdBg - - deformAmount: 0.1 - implicitHeight: panels.osd.height - implicitWidth: panels.osd.width - panel: panels.osdWrapper - radius: 20 - x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border - y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight - } - - PanelBg { - id: notifsBg - - panel: panels.notifications - radius: Appearance.rounding.normal - } - - PanelBg { - id: utilsBg - - deformAmount: panels.sidebar.visible ? (0.1) : (0.1) - exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg] - panel: panels.utilities - topLeftRadius: 0 - } - - PanelBg { - id: popoutBg - - property real extraHeight: panels.popouts.isDetached ? 0 : 0.2 - - deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1 - implicitHeight: panels.popouts.height * (1 + extraHeight) - implicitWidth: panels.popouts.width - panel: panels.popoutsWrapper - radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale - x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border - y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight - - Behavior on extraHeight { - Anim { - } - } - } - - PanelBg { - id: resourcesBg - - deformAmount: 0.05 - implicitHeight: panels.resources.height - implicitWidth: panels.resources.width - panel: panels.resourcesWrapper - radius: Appearance.rounding.normal - x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border - y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight - } - - PanelBg { - id: settingsBg - - property real extraHeight: 0.2 - - deformAmount: 0.03 - implicitHeight: panels.settings.height * (1 + extraHeight) - implicitWidth: panels.settings.width - panel: panels.settings - radius: Appearance.rounding.large - topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller - topRightRadius: Appearance.rounding.large + Appearance.padding.smaller - x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border - y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight - } - - PanelBg { - id: dockBg - - deformAmount: 0.08 - panel: panels.dock - radius: Appearance.rounding.normal - } - - PanelBg { - id: drawingBg - - deformAmount: 0.08 - panel: panels.drawing - radius: Appearance.rounding.normal - } + notifications.transform: Matrix4x4 { + matrix: notifsBg.deformMatrix } - - Loader { - id: drawingLoader - - active: visibilities.isDrawing - anchors.fill: parent - z: 2 - - sourceComponent: Drawing { - id: drawing - } + osd.transform: Matrix4x4 { + matrix: osdBg.deformMatrix } - - Loader { - id: inputLoader - - active: visibilities.isDrawing - anchors.fill: parent - z: 2 - - sourceComponent: DrawingInput { - id: input - - bar: bar - drawing: drawingLoader.item - panels: panels - popout: panels.drawing - visibilities: visibilities - } + popouts.transform: Matrix4x4 { + matrix: popoutBg.deformMatrix } - - Interactions { - id: mouseArea - - anchors.fill: parent - bar: bar - drawing: drawingLoader.item - enabled: true - input: inputLoader.item - panels: panels - popouts: panels.popouts - screen: scope.modelData - visibilities: visibilities - z: 1 - - Panels { - id: panels - - bar: bar - drawingItem: drawingLoader.item - screen: scope.modelData - visibilities: visibilities - - dashboard.transform: Matrix4x4 { - matrix: dashBg.deformMatrix - } - dock.transform: Matrix4x4 { - matrix: dockBg.deformMatrix - } - launcher.transform: Matrix4x4 { - matrix: launcherBg.deformMatrix - } - notifications.transform: Matrix4x4 { - matrix: notifsBg.deformMatrix - } - osd.transform: Matrix4x4 { - matrix: osdBg.deformMatrix - } - popouts.transform: Matrix4x4 { - matrix: popoutBg.deformMatrix - } - resources.transform: Matrix4x4 { - matrix: resourcesBg.deformMatrix - } - settings.transform: Matrix4x4 { - matrix: settingsBg.deformMatrix - } - sidebar.transform: Matrix4x4 { - matrix: sidebarBg.deformMatrix - } - utilities.transform: Matrix4x4 { - matrix: utilsBg.deformMatrix - } - } - - BarLoader { - id: bar - - anchors.left: parent.left - anchors.right: parent.right - popouts: panels.popouts - popoutsWrapper: panels.popoutsWrapper - screen: scope.modelData - visibilities: visibilities - } + resources.transform: Matrix4x4 { + matrix: resourcesBg.deformMatrix } + settings.transform: Matrix4x4 { + matrix: settingsBg.deformMatrix + } + sidebar.transform: Matrix4x4 { + matrix: sidebarBg.deformMatrix + } + utilities.transform: Matrix4x4 { + matrix: utilsBg.deformMatrix + } + } + + BarLoader { + id: bar + + anchors.left: parent.left + anchors.right: parent.right + popouts: panels.popouts + popoutsWrapper: panels.popoutsWrapper + screen: root.screen + visibilities: visibilities } } diff --git a/Modules/Settings/Categories/General.qml b/Modules/Settings/Categories/General.qml index 59ba5d2..46efcdc 100644 --- a/Modules/Settings/Categories/General.qml +++ b/Modules/Settings/Categories/General.qml @@ -62,6 +62,7 @@ SettingsPage { SettingsSection { sectionId: "Color" + z: 1 SettingsHeader { name: "Color" @@ -105,7 +106,6 @@ SettingsPage { active: root.schemeTypeItem(menuItems, Config.colors.schemeType) enabled: Config.general.color.schemeGeneration label: qsTr("Scheme type") - z: 2 menuItems: [ MenuItem { @@ -250,7 +250,6 @@ SettingsPage { SettingsSection { sectionId: "Default Apps" - z: -1 SettingsHeader { name: "Default Apps" diff --git a/shell.qml b/shell.qml index c5c5b7f..7482c32 100644 --- a/shell.qml +++ b/shell.qml @@ -22,7 +22,7 @@ ShellRoot { settings.watchFiles: true - Windows { + Drawers { } Wallpaper { -- 2.47.3 From 04fb534a050c2f6448e99eb6bd32a1fac7ab5a5f Mon Sep 17 00:00:00 2001 From: zach Date: Sat, 6 Jun 2026 21:21:32 +0200 Subject: [PATCH 3/3] scroll deceleration env --- shell.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/shell.qml b/shell.qml index 7482c32..bbf27d7 100644 --- a/shell.qml +++ b/shell.qml @@ -4,6 +4,7 @@ //@ pragma Env QSG_NO_VSYNC=1 //@ pragma Env QS_NO_RELOAD_POPUP=1 //@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round +//@ pragma DefaultEnv QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000 //@ pragma DropExpensiveFonts import Quickshell import Quickshell.Services.UPower -- 2.47.3