From 1a27b905bf59ea091a0f5b5f7fdea1c0743abc2a Mon Sep 17 00:00:00 2001 From: Bloxx12 Date: Tue, 24 Jun 2025 07:46:51 +0200 Subject: [PATCH] progress --- Bar.qml | 38 -------- ClockWidget.qml | 26 ++++-- SysTray.qml | 2 +- WorkspaceWidget.qml | 116 ------------------------ config/Config.qml | 46 ++++++++++ modules/bar/Bar.qml | 57 ++++++++++++ modules/bar/components/Clock.qml | 34 +++++++ modules/bar/components/Workspaces.qml | 103 ++++++++++++++++++++++ modules/drawers/Background.qml | 3 + modules/drawers/Border.qml | 54 ++++++++++++ modules/drawers/Drawers.qml | 117 +++++++++++++++++++++++++ modules/drawers/Exclusions.qml | 38 ++++++++ modules/notifications/Notification.qml | 18 ++++ services/Notification.qml | 42 +++++++++ services/niri/Niri.qml | 85 ++++++++++++++++++ shell.qml | 20 ++--- 16 files changed, 622 insertions(+), 177 deletions(-) delete mode 100644 Bar.qml delete mode 100644 WorkspaceWidget.qml create mode 100644 config/Config.qml create mode 100644 modules/bar/Bar.qml create mode 100644 modules/bar/components/Clock.qml create mode 100644 modules/bar/components/Workspaces.qml create mode 100644 modules/drawers/Background.qml create mode 100644 modules/drawers/Border.qml create mode 100644 modules/drawers/Drawers.qml create mode 100644 modules/drawers/Exclusions.qml create mode 100644 modules/notifications/Notification.qml create mode 100644 services/Notification.qml create mode 100644 services/niri/Niri.qml diff --git a/Bar.qml b/Bar.qml deleted file mode 100644 index 0837f5a..0000000 --- a/Bar.qml +++ /dev/null @@ -1,38 +0,0 @@ -pragma ComponentBehavior: Bound -import QtQuick -import QtQuick.Layouts -import Quickshell - -PanelWindow { - id: root - color: "transparent" - - anchors { - top: true - bottom: true - left: true - } - width: 60 - - ColumnLayout { - // anchors.fill: parent - anchors.margins: 2 - spacing: 2 - anchors { - left: parent.left - right: parent.right - top: parent.top - margins: 2 - } - - ClockWidget {} - - WorkspaceWidget { - bar: root - wsBaseIndex: root.screen.name == "DP-2" ? 11 : 1 - } - SysTray { - bar: root - } - } -} diff --git a/ClockWidget.qml b/ClockWidget.qml index 2e58c2e..6cf0ad7 100644 --- a/ClockWidget.qml +++ b/ClockWidget.qml @@ -1,18 +1,26 @@ import QtQuick -import QtQuick.Layouts import Quickshell Rectangle { - width: parent.width - height: text.height + 10 - border.color: "black" + + + width: text.width + implicitHeight: text.height + // border.color: "black" border.width: 2 - radius: 5 - color: "#30c0ffff" + radius: 0 + color: "transparent" + + Behavior on implicitHeight { + NumberAnimation { + duration: 100 + easing.type: Easing.OutCubic + } + } Item { width: parent.width - height: text.height * 2 + height: text.height anchors.centerIn: parent @@ -26,11 +34,11 @@ Rectangle { anchors.centerIn: parent property var date: Date() - text: Qt.formatDateTime(clock.date, "hh\nmm") + text: Qt.formatDateTime(clock.date, "hh mm") font.family: "ComicShannsMono Nerd Font Mono" font.weight: Font.ExtraBold - font.pointSize: 18 + font.pointSize: 12 color: "black" } diff --git a/SysTray.qml b/SysTray.qml index 3fe54c5..7445bcd 100644 --- a/SysTray.qml +++ b/SysTray.qml @@ -18,7 +18,7 @@ Rectangle { border.color: "black" border.width: 2 - ColumnLayout { + RowLayout { id: column spacing: 10 diff --git a/WorkspaceWidget.qml b/WorkspaceWidget.qml deleted file mode 100644 index e2ceecf..0000000 --- a/WorkspaceWidget.qml +++ /dev/null @@ -1,116 +0,0 @@ -// A workspace indicator. -// This is in big parts taken from outfoxxed, the creator of quickshell. -// Please make sure to check out his setup. -pragma ComponentBehavior: Bound -import QtQuick -import Quickshell.Hyprland - -Rectangle { - id: root - required property var bar - - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) - - // Amount of workspaces - property int wsCount: 10 - // Base index for the workspaces - property int wsBaseIndex: 1 - - // index of the current workspace - property int currentIndex: 0 - // count how many workspaces currently exist - property int existsCount: 0 - - implicitHeight: column.implicitHeight + 10 - - width: parent.width - height: width - border.color: "black" - color: "#30c0ffff" - border.width: 2 - radius: 5 - - // destructor takes care of nulling - signal workspaceAdded(workspace: HyprlandWorkspace) - - Column { - id: column - spacing: 0 - anchors { - fill: parent - margins: 5 - } - - Repeater { - model: root.wsCount - - Item { - id: wsItem - implicitHeight: 15 - - anchors { - right: parent.right - left: parent.left - } - - // index of the current workspace - required property int index - property int wsIndex: root.wsBaseIndex + index - - property HyprlandWorkspace workspace: null - // check if workspace exists - property bool exists: workspace != null - - property bool active: (root.monitor?.activeWorkspace ?? false) && root.monitor.activeWorkspace == workspace - - Connections { - target: root - - function onWorkspaceAdded(workspace: HyprlandWorkspace) { - if (workspace.id == wsItem.wsIndex) { - wsItem.workspace = workspace; - } - } - } - - property real animActive: active ? 1 : 0.65 - Behavior on animActive { - NumberAnimation { - duration: 150 - } - } - - property real animExists: exists ? 1 : 0 - Behavior on animExists { - NumberAnimation { - duration: 100 - } - } - - Rectangle { - anchors.centerIn: parent - height: wsItem.height - 5 - width: parent.width * wsItem.animActive - radius: height / 2 - border.color: "black" - border.width: 1 - color: "black" - } - } - } - } - - Connections { - target: Hyprland.workspaces - - function onObjectInsertedPost(workspace) { - root.workspaceAdded(workspace); - } - } - - Component.onCompleted: { - Hyprland.workspaces.values.forEach(workspace => { - root.workspaceAdded(workspace); - }); - } -} diff --git a/config/Config.qml b/config/Config.qml new file mode 100644 index 0000000..875a4df --- /dev/null +++ b/config/Config.qml @@ -0,0 +1,46 @@ +pragma Singleton + +import Quickshell +import QtQuick + +Singleton { + id: root + + readonly property QtObject bar: QtObject { + readonly property int width: 50 + readonly property var colors: QtObject { + readonly property color bar: "#1e1e2e" + readonly property color barOutline: "#50ffffff" + readonly property color widget: "#25ceffff" + readonly property color widgetActive: "#80ceffff" + readonly property color widgetOutline: "#40ffffff" + readonly property color widgetOutlineSeparate: "#20ffffff" + readonly property color separator: "#60ffffff" + } + } + + readonly property QtObject border: QtObject { + readonly property int thickness: 8 + readonly property color color: "#1e1e2e" + readonly property int rounding: 25 + } + + readonly property QtObject catppuccin: QtObject { + readonly property color base: "#1e1e2e" + readonly property color mantle: "#181825" + readonly property color surface0: "#313244" + readonly property color surface1: "#45475a" + readonly property color surface2: "#585b70" + readonly property color text: "#cdd6f4" + readonly property color rosewater: "#f5e0dc" + readonly property color lavender: "#b4befe" + readonly property color red: "#f38ba8" + readonly property color peach: "#fab387" + readonly property color yellow: "#f9e2af" + readonly property color green: "#a6e3a1" + readonly property color teal: "#a6e3a1" + readonly property color blue: "#89b4fa" + readonly property color mauve: "#cba6f7" + readonly property color flamingo: "#f2cdcd" + } +} diff --git a/modules/bar/Bar.qml b/modules/bar/Bar.qml new file mode 100644 index 0000000..4846949 --- /dev/null +++ b/modules/bar/Bar.qml @@ -0,0 +1,57 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell + +import "../../config" +import "components" + +Rectangle { + id: root + + color: Config.bar.colors.bar + + required property ShellScreen screen + + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + } + + implicitWidth: Config.bar.width + + Item { + id: child + + anchors { + top: parent.top + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + + implicitWidth: Math.max(clock.implicitWidth, workspaces.implicitWidth) + + ColumnLayout { + spacing: 2 + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 0 + } + Clock { + id: clock + } + Workspaces { + id: workspaces + screen: root.screen + } + } + + Text { + text: root.screen.name + color: "green" + } + } +} diff --git a/modules/bar/components/Clock.qml b/modules/bar/components/Clock.qml new file mode 100644 index 0000000..4826851 --- /dev/null +++ b/modules/bar/components/Clock.qml @@ -0,0 +1,34 @@ +import QtQuick +import Quickshell + +import "../../../config" + +Rectangle { + id: root + + width: text.width + 5 + height: text.height + 5 + implicitWidth: width + border.color: Config.catppuccin.rosewater + border.width: 0 + radius: 5 + color: "transparent" + + Text { + id: text + anchors.centerIn: parent + property var date: Date() + + text: Qt.formatDateTime(clock.date, "hh mm") + + font.family: "JetBrainsMono NF Mono" + font.pointSize: 15 + + color: Config.catppuccin.text + } + + SystemClock { + id: clock + precision: SystemClock.Seconds + } +} diff --git a/modules/bar/components/Workspaces.qml b/modules/bar/components/Workspaces.qml new file mode 100644 index 0000000..c6cd704 --- /dev/null +++ b/modules/bar/components/Workspaces.qml @@ -0,0 +1,103 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io +import QtQuick.Layouts + +import "../../../services/niri" +import "../../../config" + +Rectangle { + id: root + + required property ShellScreen screen + property var workspaces: Niri.workspaces + property var activeWorkspace: Niri.activeWorkspace + property var activeWorkspaceIndex: Niri.activeWorkspaceIndex + + property int wsItemHeight: 15 + + property bool _: log() + function log() { + console.debug("Screen name: " + screen.name); + console.debug("Found the following workspaces:"); + for (let i = 0; i < workspaces.length; i++) { + console.debug("Workspace " + workspaces[i].id + " On screen " + workspaces[i].output + " With name: " + workspaces[i].name); + // console.debug(workspaces[i].output); + } + return true; + } + + // Works + height: 300 + + // Gives warning + // height: workspaces.length * root.wsItemHeight + implicitWidth: list.implicitWidth + color: "transparent" + border.color: Config.catppuccin.rosewater + border.width: 0 + radius: 7 + + Layout.fillWidth: true + + ListView { + id: list + model: root.workspaces + implicitHeight: contentHeight + implicitWidth: contentItem.childrenRect.width + anchors.horizontalCenter: parent.horizontalCenter + + // anchors.fill: parent + + delegate: Item { + id: wsItem + // Name of the workspace + property string name: "VOID" + // ID of the workspace + required property string id + + required property string output + + property bool isActive: (id - 1 == root.activeWorkspaceIndex) + + property real animActive: isActive ? 1 : 0.65 + Behavior on animActive { + NumberAnimation { + duration: 150 + } + } + + // property bool isCorrectScreen: log() + // function log() { + // console.log("Screen name: " + root.screen.name); + // console.log(wsItem.output); + // console.log(wsItem.id); + + // let isCorrect = root.screen.name == wsItem.output; + // console.log("isCorrect: ", isCorrect); + // return root.screen.name == wsItem.output; + // } + + implicitHeight: root.wsItemHeight + implicitWidth: 50 + + anchors { + right: parent.right + left: parent.left + } + + Rectangle { + anchors.centerIn: parent + height: wsItem.height - 5 + width: parent.width * wsItem.animActive + radius: height / 2 + border.color: Config.catppuccin.mantle + border.width: 0 + color: Config.catppuccin.blue + // visible: wsItem.isCorrectScreen + } + } + } +} diff --git a/modules/drawers/Background.qml b/modules/drawers/Background.qml new file mode 100644 index 0000000..1e07cd8 --- /dev/null +++ b/modules/drawers/Background.qml @@ -0,0 +1,3 @@ +import QtQuick +import QtQuick.Shapes + diff --git a/modules/drawers/Border.qml b/modules/drawers/Border.qml new file mode 100644 index 0000000..bebd353 --- /dev/null +++ b/modules/drawers/Border.qml @@ -0,0 +1,54 @@ +import Quickshell +import QtQuick +import QtQuick.Effects + +import "../../config" + +Item { + id: root + + required property Rectangle bar + + anchors.fill: parent + + Rectangle { + id: rect + + anchors.fill: parent + color: Config.border.color + visible: false + + + Behavior on color { + ColorAnimation { + duration: 150 + easing.type: Easing.BezierSpline + } + } + } + + Item { + id: mask + + anchors.fill: parent + layer.enabled: true + visible: false + + Rectangle { + anchors.fill: parent + anchors.margins: Config.border.thickness + anchors.leftMargin: root.bar.implicitWidth + radius: Config.border.rounding + } + } + + MultiEffect { + anchors.fill: parent + maskEnabled: true + maskInverted: true + maskSource: mask + source: rect + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 + } +} diff --git a/modules/drawers/Drawers.qml b/modules/drawers/Drawers.qml new file mode 100644 index 0000000..ffbaa6c --- /dev/null +++ b/modules/drawers/Drawers.qml @@ -0,0 +1,117 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import Quickshell.Wayland +import Quickshell.Services.Notifications +import QtQuick +import QtQuick.Effects + +import "../bar" +import "../notifications" + +// import "../../services" + +Variants { + model: Quickshell.screens + + Scope { + id: scope + required property ShellScreen modelData + + Exclusions { + screen: scope.modelData + bar: bar + } + + PanelWindow { + id: win + + screen: scope.modelData + color: "transparent" + + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + // Clickthrough mask. + // Clickable areas of the window are determined by the provided region. + mask: Region { + x: bar.implicitWidth + y: 8 + width: win.width - bar.implicitWidth + height: win.height - 8 + + // Setting the intersection mode to Xor will invert the mask and make everything in the mask region not clickable and pass through clicks inside it through the window. + intersection: Intersection.Xor + } + + anchors { + top: true + bottom: true + left: true + right: true + } + + Item { + id: background + + anchors.fill: parent + visible: false + + Border { + bar: bar + } + } + + MultiEffect { + anchors.fill: source + source: background + shadowEnabled: true + blurMax: 15 + } + + Bar { + id: bar + screen: scope.modelData + } + Item { + id: notifs + + readonly property list list: [] + readonly property list popups: list.filter(n => n.popup) + + NotificationServer { + id: server + + keepOnReload: false + + onNotification: notif => { + notif.tracked = true; + console.log("Got notification: " + notif.body); + + root.list.push(notifComp.createObject(root, { + popup: true, + notification: notif, + body: notif.body, + appName: notif.appName + })); + } + } + + Component { + id: notifComp + + Notif {} + } + } + } + } + + component Notif: QtObject { + property bool popup + readonly property date time: new Date() + + required property Notification notification + readonly property string body: notification.body + readonly property string appName: notification.appName + } +} diff --git a/modules/drawers/Exclusions.qml b/modules/drawers/Exclusions.qml new file mode 100644 index 0000000..d99092d --- /dev/null +++ b/modules/drawers/Exclusions.qml @@ -0,0 +1,38 @@ + +pragma ComponentBehavior: Bound +import Quickshell +import QtQuick + +import "../../config" + +Scope { + id: root + required property ShellScreen screen + required property Item bar + + ExclusionZone { + anchors.left: true + exclusiveZone: root.bar.implicitWidth + } + + ExclusionZone { + anchors.top: true + } + + ExclusionZone { + anchors.right: true + } + + ExclusionZone { + anchors.bottom: true + } + + component ExclusionZone: PanelWindow { + screen: root.screen + color: "transparent" + exclusiveZone: Config.border.thickness + implicitHeight: Config.border.thickness + implicitWidth: Config.border.thickness + mask: Region {} + } +} diff --git a/modules/notifications/Notification.qml b/modules/notifications/Notification.qml new file mode 100644 index 0000000..2569210 --- /dev/null +++ b/modules/notifications/Notification.qml @@ -0,0 +1,18 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Services.Notifications +import QtQuick +import QtQuick.Layouts + +import "../../config" +import "../../services" + +Rectangle { + id: root + color: "transparent" + required property Notification.Notif modelData + + Text { + text: root.modelData.summary + } +} diff --git a/services/Notification.qml b/services/Notification.qml new file mode 100644 index 0000000..d539166 --- /dev/null +++ b/services/Notification.qml @@ -0,0 +1,42 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import Quickshell +import Quickshell.Services.Notifications +import QtQuick + +Singleton { + id: root + + readonly property list list: [] + readonly property list popups: list.filter(n => n.popup) + + NotificationServer { + id: server + + keepOnReload: false + + onNotification: notif => { + notif.tracked = true; + + root.list.push(notifComp.createObject(root, { + popup: true, + notification: notif + })); + } + } + + component Notif: QtObject { + property bool popup + readonly property date time: new Date() + + required property Notification notification + readonly property string summary: notification.summary + } + + Component { + id: notifComp + + Notif {} + } +} diff --git a/services/niri/Niri.qml b/services/niri/Niri.qml new file mode 100644 index 0000000..61c0aa4 --- /dev/null +++ b/services/niri/Niri.qml @@ -0,0 +1,85 @@ +// Kind thanks to https://github.com/MapoMagpie/nixos-flakes/blob/main/home/ui/quickshell/config/Data/Niri.qml +// This file was taken from there and further modified. + +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + id: root + // property var data + property var workspaces: [] + property var activeWorkspace: "VOID" + property var activeWorkspaceIndex: 0 + property var windows: [] + // property var activedWindowId: 0 + + Process { + id: proc + command: ["niri", "msg", "-j", "event-stream"] + + running: true + stdout: SplitParser { + onRead: data => { + var event = JSON.parse(data); + let workspaces = []; + if (event.WorkspacesChanged) { + root.workspaces = event.WorkspacesChanged.workspaces.filter(w => w.name); + root.workspaces = root.workspaces.sort((a, b) => a.id - b.id); + root.activeWorkspaceIndex = root.workspaces.findIndex(w => w.is_focused); + if (root.activeWorkspaceIndex < 0) { + root.activeWorkspaceIndex = 0; + } + root.activeWorkspace = root.workspaces[root.activeWorkspaceIndex].name; + } + if (event.WindowsChanged) { + root.windows = [...event.WindowsChanged.windows].sort((a, b) => a.id - b.id); + } + if (event.WindowOpenedOrChanged) { + const window = event.WindowOpenedOrChanged.window; + const index = root.windows.findIndex(w => w.id === window.id); + // console.log("window opened or changed: ", index, ", win id: ", window.id); + if (index >= 0) { + // console.log("replace window, old: ", root.windows[index].id, ", new: ", window.id); + root.windows[index] = window; + } else { + // console.log("push window, new: ", window.id); + root.windows.push(window); + } + root.windows = [...root.windows.sort((a, b) => a.id - b.id)]; + } + if (event.WindowClosed) { + const index = root.windows.findIndex(w => w.id === event.WindowClosed.id); + // console.log("window closed: ", index, ", win id: ", event.WindowClosed.id); + if (index >= 0) { + root.windows.splice(index, 1); + } + root.windows = [...root.windows.sort((a, b) => a.id - b.id)]; + } + if (event.WorkspaceActivated) { + root.activeWorkspaceIndex = root.workspaces.findIndex(w => w.id === event.WorkspaceActivated.id); + if (root.activeWorkspaceIndex < 0) { + root.activeWorkspaceIndex = 0; + } + root.activeWorkspace = root.workspaces[root.activeWorkspaceIndex].name; + } + } + } + } +} + +// { +// "workspaces": [ +// { +// "id": 5, +// "idx": 4, +// "name": "GAME", +// "output": "DP-3", +// "is_active": false, +// "is_focused": false, +// "active_window_id": null +// }, +// ] +// } diff --git a/shell.qml b/shell.qml index 4f7231f..1520716 100644 --- a/shell.qml +++ b/shell.qml @@ -1,24 +1,18 @@ -pragma ComponentBehavior: Bound +//@ pragma Env QS_NO_RELOAD_POPUP=1 import Quickshell import QtQuick +import "modules" +import "modules/drawers" +// import "modules/background" + ShellRoot { id: shellroot Component.onCompleted: [Launcher.init()] - ReloadPopup {} + Drawers {} + // Background {} - Variants { - model: Quickshell.screens - - Scope { - property var modelData - - Bar { - screen: modelData - } - } - } }