diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index e69de29..114bc26 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -0,0 +1,45 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Services + +ShellRoot { + + property var modelData + property string wallpaperSource: "/home/lysec/Pictures/wallpapers/wallhaven-6lqvql.jpg" + + Variants { + model: Quickshell.screens + + PanelWindow { + required property ShellScreen modelData + + visible: wallpaperSource !== "" + anchors { + bottom: true + top: true + right: true + left: true + } + margins { + top: 0 + } + color: "transparent" + screen: modelData + WlrLayershell.layer: WlrLayer.Background + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "quickshell-wallpaper" + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + source: wallpaperSource + visible: wallpaperSource !== "" + cache: true + smooth: true + mipmap: false + } + } + } + + +} \ No newline at end of file diff --git a/Modules/Background/Overview.qml b/Modules/Background/Overview.qml index e69de29..fd65eca 100644 --- a/Modules/Background/Overview.qml +++ b/Modules/Background/Overview.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import Quickshell.Wayland +import qs.Services + +ShellRoot { + property string wallpaperSource: "/home/lysec/Pictures/wallpapers/wallhaven-6lqvql.jpg" + property var modelData + + Variants { + model: Quickshell.screens + + PanelWindow { + required property ShellScreen modelData + + visible: wallpaperSource !== "" + anchors { + top: true + bottom: true + right: true + left: true + } + color: "transparent" + screen: modelData + WlrLayershell.layer: WlrLayer.Background + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "quickshell-overview" + Image { + id: bgImage + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + source: wallpaperSource + cache: true + smooth: true + mipmap: false + visible: wallpaperSource !== "" + } + MultiEffect { + id: overviewBgBlur + anchors.fill: parent + source: bgImage + blurEnabled: true + blur: 0.48 + blurMax: 128 + } + Rectangle { + anchors.fill: parent + color: Qt.rgba( + Colors.backgroundPrimary.r, + Colors.backgroundPrimary.g, + Colors.backgroundPrimary.b, 0.5) + } + } + } +} diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index e4c7f3b..604a38e 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -45,6 +45,7 @@ PanelWindow { NText { text: "Left" + anchors.verticalCenter: parent.verticalCenter } } @@ -55,9 +56,8 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter spacing: Style.marginMedium * scaling - NText { - text: "Center" - } + Workspace {} + } Row { @@ -70,10 +70,12 @@ PanelWindow { NText { text: "Right" - Layout.alignment: Qt.AlignVCenter + anchors.verticalCenter: parent.verticalCenter } - Clock {} + Clock { + anchors.verticalCenter: parent.verticalCenter + } NIconButton { id: demoPanelToggler diff --git a/Modules/Bar/Workspace.qml b/Modules/Bar/Workspace.qml new file mode 100644 index 0000000..fa7a2de --- /dev/null +++ b/Modules/Bar/Workspace.qml @@ -0,0 +1,267 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Effects +import Quickshell +import Quickshell.Io +import qs.Services + +Item { + id: root + property bool isDestroying: false + property bool hovered: false + + readonly property real scaling: Scaling.scale(screen) + property var modelData + + signal workspaceChanged(int workspaceId, color accentColor) + + property ListModel localWorkspaces: ListModel {} + property real masterProgress: 0.0 + property bool effectsActive: false + property color effectColor: Colors.accentPrimary + + // Unified scale + property real s: scale + property int horizontalPadding: Math.round(16 * s) + property int spacingBetweenPills: Math.round(8 * s) + + width: { + let total = 0; + for (let i = 0; i < localWorkspaces.count; i++) { + const ws = localWorkspaces.get(i); + if (ws.isFocused) + total += Math.round(44 * s); + else if (ws.isActive) + total += Math.round(28 * s); + else + total += Math.round(16 * s); + } + total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills; + total += horizontalPadding * 2; + return total; + } + + height: Math.round(36 * s) + + Component.onCompleted: { + localWorkspaces.clear(); + for (let i = 0; i < Workspaces.workspaces.count; i++) { + const ws = Services.Workspaces.workspaces.get(i); + if (ws.output.toLowerCase() === screen.name.toLowerCase()) { + localWorkspaces.append(ws); + } + } + workspaceRepeater.model = localWorkspaces; + updateWorkspaceFocus(); + } + + Connections { + target: Workspaces + function onWorkspacesChanged() { + localWorkspaces.clear(); + for (let i = 0; i < Workspaces.workspaces.count; i++) { + const ws = Workspaces.workspaces.get(i); + if (ws.output.toLowerCase() === screen.name.toLowerCase()) { + localWorkspaces.append(ws); + } + } + + workspaceRepeater.model = localWorkspaces; + updateWorkspaceFocus(); + } + } + + function triggerUnifiedWave() { + effectColor = Colors.accentPrimary; + masterAnimation.restart(); + } + + SequentialAnimation { + id: masterAnimation + PropertyAction { + target: root + property: "effectsActive" + value: true + } + NumberAnimation { + target: root + property: "masterProgress" + from: 0.0 + to: 1.0 + duration: 1000 + easing.type: Easing.OutQuint + } + PropertyAction { + target: root + property: "effectsActive" + value: false + } + PropertyAction { + target: root + property: "masterProgress" + value: 0.0 + } + } + + function updateWorkspaceFocus() { + for (let i = 0; i < localWorkspaces.count; i++) { + const ws = localWorkspaces.get(i); + if (ws.isFocused === true) { + root.triggerUnifiedWave(); + root.workspaceChanged(ws.id, Colors.accentPrimary); + break; + } + } + } + + Rectangle { + id: workspaceBackground + width: parent.width - Math.round(15 * s) + height: Math.round(26 * s) + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + radius: Math.round(12 * s) + color: Colors.surfaceVariant + border.color: Qt.rgba(Colors.textPrimary.r, Colors.textPrimary.g, Colors.textPrimary.b, 0.1) + border.width: Math.max(1, Math.round(1 * s)) + layer.enabled: true + layer.effect: MultiEffect { + shadowColor: "black" + // radius: 12 + + shadowVerticalOffset: 0 + shadowHorizontalOffset: 0 + shadowOpacity: 0.10 + } + } + + Row { + id: pillRow + spacing: spacingBetweenPills + anchors.verticalCenter: workspaceBackground.verticalCenter + width: root.width - horizontalPadding * 2 + x: horizontalPadding + Repeater { + id: workspaceRepeater + model: localWorkspaces + Item { + id: workspacePillContainer + height: Math.round(12 * s) + width: { + if (model.isFocused) + return Math.round(44 * s); + else if (model.isActive) + return Math.round(28 * s); + else + return Math.round(16 * s); + } + + Rectangle { + id: workspacePill + anchors.fill: parent + radius: { + if (model.isFocused) + return Math.round(12 * s); + else + // half of focused height (if you want to animate this too) + return Math.round(6 * s); + } + color: { + if (model.isFocused) + return Colors.accentPrimary; + if (model.isUrgent) + return Colors.error; + if (model.isActive || model.isOccupied) + return Colors.accentTertiary; + if (model.isUrgent) + return Colors.error; + + return Colors.outline; + } + scale: model.isFocused ? 1.0 : 0.9 + z: 0 + + MouseArea { + id: pillMouseArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Workspaces.switchToWorkspace(model.idx); + } + hoverEnabled: true + } + // Material 3-inspired smooth animation for width, height, scale, color, opacity, and radius + Behavior on width { + NumberAnimation { + duration: 350 + easing.type: Easing.OutBack + } + } + Behavior on height { + NumberAnimation { + duration: 350 + easing.type: Easing.OutBack + } + } + Behavior on scale { + NumberAnimation { + duration: 300 + easing.type: Easing.OutBack + } + } + Behavior on color { + ColorAnimation { + duration: 200 + easing.type: Easing.InOutCubic + } + } + Behavior on opacity { + NumberAnimation { + duration: 200 + easing.type: Easing.InOutCubic + } + } + Behavior on radius { + NumberAnimation { + duration: 350 + easing.type: Easing.OutBack + } + } + } + + Behavior on width { + NumberAnimation { + duration: 350 + easing.type: Easing.OutBack + } + } + Behavior on height { + NumberAnimation { + duration: 350 + easing.type: Easing.OutBack + } + } + // Burst effect overlay for focused pill (smaller outline) + Rectangle { + id: pillBurst + anchors.centerIn: workspacePillContainer + width: workspacePillContainer.width + 18 * root.masterProgress * scale + height: workspacePillContainer.height + 18 * root.masterProgress * scale + radius: width / 2 + color: "transparent" + border.color: root.effectColor + border.width: Math.max(1, Math.round((2 + 6 * (1.0 - root.masterProgress)) * s)) + opacity: root.effectsActive && model.isFocused ? (1.0 - root.masterProgress) * 0.7 : 0 + visible: root.effectsActive && model.isFocused + z: 1 + } + } + } + } + + Component.onDestruction: { + root.isDestroying = true; + } +} diff --git a/Services/Workspaces.qml b/Services/Workspaces.qml index fc1ab9b..8f3fb5f 100644 --- a/Services/Workspaces.qml +++ b/Services/Workspaces.qml @@ -17,7 +17,7 @@ Singleton { property var hlWorkspaces: Hyprland.workspaces.values // Detect which compositor we're using Component.onCompleted: { - console.log("WorkspaceManager initializing...") + console.log("Workspace initializing...") detectCompositor() } diff --git a/shell.qml b/shell.qml index 8f3c314..0b8c344 100644 --- a/shell.qml +++ b/shell.qml @@ -1,16 +1,13 @@ +// Disable reload popup +//@ pragma Env QS_NO_RELOAD_POPUP=1 - -/* - Here we go, this is it. Rebuild time... - No spaghetti code, preset sizing, proper project structure - only "spawn" UI, do not do anything else here. -*/ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Widgets import qs.Modules.Bar import qs.Modules.DemoPanel +import qs.Modules.Background ShellRoot { id: root @@ -18,8 +15,16 @@ ShellRoot { Variants { model: Quickshell.screens - delegate: Bar { - modelData: item + delegate: Item { + required property ShellScreen modelData + + Bar { + modelData: parent.modelData + } + + // Background {} + + // Overview {} } }