From b750421f655f02c1094a2757e8bb00a10970d85c Mon Sep 17 00:00:00 2001 From: Bloxx12 Date: Tue, 6 May 2025 09:38:56 +0200 Subject: [PATCH] progress --- AudioPopup.qml | 103 +++++++++++++++++ Bar.qml | 7 +- ClockWidget.qml | 17 ++- Launcher.qml | 297 ++++++++++++++++++++++++++++++++++++++++++++++-- SysTray.qml | 35 ++++-- shell.qml | 10 +- 6 files changed, 435 insertions(+), 34 deletions(-) create mode 100644 AudioPopup.qml diff --git a/AudioPopup.qml b/AudioPopup.qml new file mode 100644 index 0000000..b8e27ee --- /dev/null +++ b/AudioPopup.qml @@ -0,0 +1,103 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import QtQuick.Layouts +import Quickshell.Wayland +import Quickshell.Services.Pipewire + +Singleton { + id: audioPopup + property bool popupOpen: true + + property var volume: sink.ready ? audioPopup.sink.audio.volume : 0 + + property var visible: volume + + property PwNode sink: Pipewire.defaultAudioSink + + // bind the node so we can read its properties + PwObjectTracker { + objects: [audioPopup.sink] + } + + Timer { + id: timer + interval: 3000 + running: false + repeat: false + + onTriggered: audioPopup.visible = false + } + + LazyLoader { + id: loader + activeAsync: audioPopup.popupOpen + + PanelWindow { + id: popup + width: 400 + height: 30 + visible: true + + // Give the window an empty click mask so all clicks pass through it. + mask: Region {} + + // Use the wlroots specific layer property to ensure it displays over + // fullscreen windows. + WlrLayershell.layer: WlrLayer.Overlay + + color: "transparent" + + anchors { + bottom: true + } + + margins { + bottom: 250 + } + + Rectangle { + id: rect + Layout.fillWidth: true + anchors.verticalCenter: parent.verticalCenter + color: "white" + height: parent.height + width: parent.width + radius: 5 + opacity: 0 + + anchors { + left: parent.left + } + + Behavior on width { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + + Rectangle { + color: "black" + height: 20 + radius: height / 2 + + anchors { + left: parent.left + } + + topLeftRadius: 0 + bottomLeftRadius: 0 + + anchors.verticalCenter: parent.verticalCenter + width: parent.width * audioPopup.sink.audio.volume + } + } + } + } + + function init() { + } +} diff --git a/Bar.qml b/Bar.qml index 29882b9..fee6f9f 100644 --- a/Bar.qml +++ b/Bar.qml @@ -29,9 +29,10 @@ PanelWindow { WorkspaceWidget { bar: root + wsBaseIndex: root.screen.name == "DP-2" ? 11 : 1 + } + SysTray { + bar: root } - // SysTray { - // bar: root - // } } } diff --git a/ClockWidget.qml b/ClockWidget.qml index 450e6a5..4b75dd2 100644 --- a/ClockWidget.qml +++ b/ClockWidget.qml @@ -1,9 +1,10 @@ import QtQuick import QtQuick.Layouts +import Quickshell Rectangle { width: parent.width - height: width + height: width * 1.5 border.color: "black" border.width: 1 radius: 5 @@ -15,12 +16,17 @@ Rectangle { anchors.centerIn: parent + SystemClock { + id: clock + precision: SystemClock.Seconds + } + Text { id: text anchors.centerIn: parent property var date: Date() - text: Qt.formatDateTime(date, "hh\nmm") + text: Qt.formatDateTime(clock.date, "hh\nmm\nss") font.family: "Iosevka NF" font.weight: Font.ExtraBold @@ -29,11 +35,4 @@ Rectangle { color: "black" } } - - Timer { - interval: 1000 * 60 - running: true - repeat: true - onTriggered: text.date = new Date() - } } diff --git a/Launcher.qml b/Launcher.qml index 438658e..23522bd 100644 --- a/Launcher.qml +++ b/Launcher.qml @@ -1,34 +1,311 @@ pragma Singleton pragma ComponentBehavior: Bound - import QtQuick import QtQuick.Layouts -import QtQuick.Controls import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Widgets -import Quickshell.Services.SystemTray Singleton { - PersistentProperties { - id: persist - property bool launcherOpen: false - } + id: launcher + property bool launcherOpen: false IpcHandler { target: "launcher" function open(): void { - persist.launcherOpen = true; + launcher.launcherOpen = true; } function close(): void { - persist.launcherOpen = false; + launcher.launcherOpen = false; } function toggle(): void { - persist.launcherOpen = !persist.launcherOpen; + launcher.launcherOpen = !launcher.launcherOpen; } } + + LazyLoader { + id: loader + activeAsync: launcher.launcherOpen + + PanelWindow { + width: 450 + height: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10 + color: "transparent" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.namespace: "shell:launcher" + + Rectangle { + + height: 7 + searchContainer.implicitHeight + list.topMargin + list.bottomMargin + Math.min(list.contentHeight, list.delegateHeight * 10) + Behavior on height { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + width: 450 + color: "white" + radius: 5 + border.color: "black" + border.width: 1 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 7 + anchors.bottomMargin: 0 + spacing: 0 + + Rectangle { + id: searchContainer + Layout.fillWidth: true + implicitHeight: searchbox.implicitHeight + 10 + color: "#30c0ffff" + radius: 3 + border.color: "#50ffffff" + + RowLayout { + id: searchbox + anchors.fill: parent + anchors.margins: 5 + + IconImage { + implicitSize: parent.height + source: "root:icons/magnifying-glass.svg" + } + + TextInput { + id: search + Layout.fillWidth: true + color: "black" + + focus: true + Keys.forwardTo: [list] + Keys.onEscapePressed: launcher.launcherOpen = false + + Keys.onPressed: event => { + if (event.modifiers & Qt.ControlModifier) { + if (event.key == Qt.Key_J) { + list.currentIndex = list.currentIndex == list.count - 1 ? 0 : list.currentIndex + 1; + event.accepted = true; + } else if (event.key == Qt.Key_K) { + list.currentIndex = list.currentIndex == 0 ? list.count - 1 : list.currentIndex - 1; + event.accepted = true; + } + } + } + + onAccepted: { + if (list.currentItem) { + list.currentItem.clicked(null); + } + } + + onTextChanged: { + list.currentIndex = 0; + } + } + } + } + + ListView { + id: list + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + cacheBuffer: 0 // works around QTBUG-131106 + //reuseItems: true + model: ScriptModel { + values: DesktopEntries.applications.values.map(object => { + const stxt = search.text.toLowerCase(); + const ntxt = object.name.toLowerCase(); + let si = 0; + let ni = 0; + + let matches = []; + let startMatch = -1; + + for (let si = 0; si != stxt.length; ++si) { + const sc = stxt[si]; + + while (true) { + // Drop any entries with letters that don't exist in order + if (ni == ntxt.length) + return null; + + const nc = ntxt[ni++]; + + if (nc == sc) { + if (startMatch == -1) + startMatch = ni; + break; + } else { + if (startMatch != -1) { + matches.push({ + index: startMatch, + length: ni - startMatch + }); + + startMatch = -1; + } + } + } + } + + if (startMatch != -1) { + matches.push({ + index: startMatch, + length: ni - startMatch + 1 + }); + } + + return { + object: object, + matches: matches + }; + }).filter(entry => entry !== null).sort((a, b) => { + let ai = 0; + let bi = 0; + let s = 0; + + while (ai != a.matches.length && bi != b.matches.length) { + const am = a.matches[ai]; + const bm = b.matches[bi]; + + s = bm.length - am.length; + if (s != 0) + return s; + + s = am.index - bm.index; + if (s != 0) + return s; + + ++ai; + ++bi; + } + + s = a.matches.length - b.matches.length; + if (s != 0) + return s; + + s = a.object.name.length - b.object.name.length; + if (s != 0) + return s; + + return a.object.name.localeCompare(b.object.name); + }).map(entry => entry.object) + + onValuesChanged: list.currentIndex = 0 + } + + topMargin: 7 + bottomMargin: list.count == 0 ? 0 : 7 + + add: Transition { + NumberAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + } + + displaced: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 1 + duration: 100 + } + } + + move: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 1 + duration: 100 + } + } + + remove: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 0 + duration: 100 + } + } + + highlight: Rectangle { + radius: 5 + color: "green" + border.color: "#30ffffff" + border.width: 1 + } + keyNavigationEnabled: true + keyNavigationWraps: true + highlightMoveVelocity: -1 + highlightMoveDuration: 100 + preferredHighlightBegin: list.topMargin + preferredHighlightEnd: list.height - list.bottomMargin + highlightRangeMode: ListView.ApplyRange + snapMode: ListView.SnapToItem + + readonly property real delegateHeight: 44 + + delegate: MouseArea { + required property DesktopEntry modelData + + implicitHeight: list.delegateHeight + implicitWidth: ListView.view.width + + onClicked: { + modelData.execute(); + launcher.launcherOpen = false; + } + + RowLayout { + id: delegateLayout + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 5 + } + + IconImage { + Layout.alignment: Qt.AlignVCenter + asynchronous: true + implicitSize: 30 + source: Quickshell.iconPath(modelData.icon) + } + Text { + text: modelData.name + color: "black" + Layout.alignment: Qt.AlignVCenter + } + } + } + } + } + } + } + } + function init() { + } } diff --git a/SysTray.qml b/SysTray.qml index 8889b4e..cadc4ee 100644 --- a/SysTray.qml +++ b/SysTray.qml @@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import QtQuick.Effects +import QtQuick.Controls import Quickshell import Quickshell.Services.SystemTray @@ -10,14 +11,11 @@ Rectangle { id: root required property var bar - Column { - id: column - spacing: 5 + anchors.horizontalCenter: parent.horizontalCenter - anchors { - fill: parent - margins: 5 - } + ColumnLayout { + id: column + spacing: 35 Repeater { model: SystemTray.items @@ -27,8 +25,29 @@ Rectangle { required property SystemTrayItem modelData - implicitHeight: width + Item { + height: 30 + width: 30 + anchors.horizontalCenter: parent.horizontalCenter + Image { + source: item.modelData.icon + anchors.fill: parent + + MouseArea { + anchors.fill: parent + onClicked: function (mouse) { + if (mouse.button === Qt.LeftButton) { + item.modelData.activate(); + } + if (mouse.button === Qt.RightButton) { + if (item.modelData.hasMenu) { + } + } + } + } + } + } } } } diff --git a/shell.qml b/shell.qml index b87f084..e94cae1 100644 --- a/shell.qml +++ b/shell.qml @@ -1,11 +1,15 @@ +pragma ComponentBehavior: Bound + import Quickshell -import Quickshell.Io import QtQuick -import QtQuick.Layouts ShellRoot { id: shellroot + Component.onCompleted: [Launcher.init(), AudioPopup.init()] + + ReloadPopup {} + Variants { model: Quickshell.screens @@ -15,8 +19,6 @@ ShellRoot { Bar { screen: modelData } - - ReloadPopup {} } } }