From f22dee69f0e034fee5cd4e3ff507366489916bb9 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Sun, 4 Jan 2026 00:20:14 -0800 Subject: [PATCH] all: misc --- .../quickshell/shell/launcher/Controller.qml | 252 ++----------- .../shell/launcher/LaunchContent.qml | 331 ++++++++++++++++++ .../quickshell/shell/launcher/Preview.qml | 5 + .../notifications/NotificationOverlay.qml | 9 +- .../user/modules/quickshell/shell/shell.qml | 8 + 5 files changed, 375 insertions(+), 230 deletions(-) create mode 100644 modules/user/modules/quickshell/shell/launcher/LaunchContent.qml create mode 100644 modules/user/modules/quickshell/shell/launcher/Preview.qml diff --git a/modules/user/modules/quickshell/shell/launcher/Controller.qml b/modules/user/modules/quickshell/shell/launcher/Controller.qml index e803bbc..47cb89d 100644 --- a/modules/user/modules/quickshell/shell/launcher/Controller.qml +++ b/modules/user/modules/quickshell/shell/launcher/Controller.qml @@ -8,8 +8,7 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Widgets -import Quickshell.Services.SystemTray -import ".." +import Quickshell.Hyprland Singleton { PersistentProperties { @@ -38,240 +37,37 @@ Singleton { activeAsync: persist.launcherOpen PanelWindow { - width: 450 - height: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10 + id: launcherWindow + //anchors { left: true; right: true; top: true; bottom: true } color: "transparent" - WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + implicitWidth: content.width + implicitHeight: content.height + //color: "#20ff0000" + //WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive WlrLayershell.namespace: "shell:launcher" - Rectangle { - //anchors.fill: parent - 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: ShellGlobals.colors.bar - radius: 5 - border.color: ShellGlobals.colors.barOutline - border.width: 1 + /*HyprlandWindow.visibleMask: Region { + item: content + }*/ - ColumnLayout { - anchors.fill: parent - anchors.margins: 7 - anchors.bottomMargin: 0 - spacing: 0 + HyprlandFocusGrab { + windows: [launcherWindow] + active: true + //onCleared: console.log("cleared") + onCleared: persist.launcherOpen = false + } - Rectangle { - id: searchContainer - Layout.fillWidth: true - implicitHeight: searchbox.implicitHeight + 10 - color: "#30c0ffff" - radius: 3 - border.color: "#50ffffff" + MouseArea { + anchors.fill: parent - RowLayout { - id: searchbox - anchors.fill: parent - anchors.margins: 5 + onPressed: persist.launcherOpen = false - IconImage { - implicitSize: parent.height - source: "root:icons/magnifying-glass.svg" - } + MouseArea { + anchors.centerIn: parent + width: content.width + height: content.height - TextInput { - id: search - Layout.fillWidth: true - color: "white" - - focus: true - Keys.forwardTo: [list] - Keys.onEscapePressed: persist.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: "#20e0ffff" - 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(); - persist.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: "#f0f0f0" - Layout.alignment: Qt.AlignVCenter - } - } - } - } + LaunchContent { id: content } } } } diff --git a/modules/user/modules/quickshell/shell/launcher/LaunchContent.qml b/modules/user/modules/quickshell/shell/launcher/LaunchContent.qml new file mode 100644 index 0000000..51e1f35 --- /dev/null +++ b/modules/user/modules/quickshell/shell/launcher/LaunchContent.qml @@ -0,0 +1,331 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import qs + +Item { + id: root + height: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10; + width: 450 + + Rectangle { + id: content + 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: ShellGlobals.colors.bar + radius: 5 + border.color: ShellGlobals.colors.barOutline + 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: "white" + + focus: true + Keys.forwardTo: [list] + Keys.onEscapePressed: persist.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: "#20e0ffff" + 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(); + persist.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: "#f0f0f0" + Layout.alignment: Qt.AlignVCenter + } + } + } + } + } + } + + Rectangle { + id: preview + + anchors { + left: content.right + leftMargin: 5 + } + + property real listYOffset: 7 + searchContainer.implicitHeight + property real yCenter: list.contentY + y: listYOffset - yCenter - height / 2 + (list.currentItem?.y ?? 0) + list.delegateHeight / 2 + + implicitWidth: previewContent.implicitWidth + previewContent.anchors.margins * 2 + implicitHeight: previewContent.implicitHeight + previewContent.anchors.margins * 2 + + color: ShellGlobals.colors.bar + radius: 5 + border.color: ShellGlobals.colors.barOutline + border.width: 1 + + property DesktopEntry entry: list.currentItem?.modelData ?? null + property var toplevels: !entry ? []: ToplevelManager.toplevels.values + .filter(toplevel => toplevel.appId.toLowerCase() == entry.id.toLowerCase()) + + // waits for hasContent before showing + // cant use visible because layout wont run + property real scaleMul: previewLayout.implicitWidth != 0 ? 1 : 0; + Behavior on scaleMul { SmoothedAnimation { velocity: 5 } } + + opacity: scaleMul + + transform: Scale { + origin.x: 0 + origin.y: preview.height / 2 + xScale: 0.9 + preview.scaleMul * 0.1 + yScale: xScale + } + + Item { + id: previewContent + anchors.fill: parent + anchors.margins: 10 + implicitWidth: previewLayout.implicitWidth + implicitHeight: previewLayout.implicitHeight + + RowLayout { + id: previewLayout + + Repeater { + model: preview.toplevels + + Rectangle { + id: delegate + required property Toplevel modelData; + color: "transparent" + + visible: view.hasContent + implicitWidth: previewContent.implicitWidth + implicitHeight: previewContent.implicitHeight + + ColumnLayout { + id: previewContent + anchors.centerIn: parent + width: 240 + + ScreencopyView { + id: view + implicitWidth: 240 + implicitHeight: 200 + captureSource: modelData + live: true + } + + Label { + text: modelData.title + Layout.fillWidth: true + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + } + } + } + } + } + } + } +} diff --git a/modules/user/modules/quickshell/shell/launcher/Preview.qml b/modules/user/modules/quickshell/shell/launcher/Preview.qml new file mode 100644 index 0000000..088a096 --- /dev/null +++ b/modules/user/modules/quickshell/shell/launcher/Preview.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property real yCenter: 0 +} diff --git a/modules/user/modules/quickshell/shell/notifications/NotificationOverlay.qml b/modules/user/modules/quickshell/shell/notifications/NotificationOverlay.qml index 4a554c0..e2df389 100644 --- a/modules/user/modules/quickshell/shell/notifications/NotificationOverlay.qml +++ b/modules/user/modules/quickshell/shell/notifications/NotificationOverlay.qml @@ -4,6 +4,8 @@ import Quickshell.Wayland import Quickshell.Hyprland PanelWindow { + id: root + WlrLayershell.namespace: "shell:notifications" exclusionMode: ExclusionMode.Ignore color: "transparent" @@ -16,6 +18,9 @@ PanelWindow { right: true } + property var bar: null + //property var fullscreen: Hyprland.monitorFor(root.screen)?.activeWorkspace?.hasFullscreen ?? false + property Component notifComponent: DaemonNotification {} NotificationDisplay { @@ -23,8 +28,8 @@ PanelWindow { anchors.fill: parent - stack.y: 5 + 55//(NotificationManager.showTrayNotifs ? 55 : 0) - stack.x: 72 + stack.y: root.bar.compactState * 10 + 50//5 + 55//(NotificationManager.showTrayNotifs ? 55 : 0) + stack.x: root.bar.leftMargin + root.bar.width - 10 } visible: display.stack.children.length != 0 diff --git a/modules/user/modules/quickshell/shell/shell.qml b/modules/user/modules/quickshell/shell/shell.qml index c2f5f4b..8a8b936 100644 --- a/modules/user/modules/quickshell/shell/shell.qml +++ b/modules/user/modules/quickshell/shell/shell.qml @@ -1,4 +1,5 @@ //@ pragma ShellId shell +//@ pragma IgnoreSystemSettings import Quickshell import Quickshell.Io @@ -13,6 +14,8 @@ import "launcher" as Launcher import "background" ShellRoot { + id: root + Component.onCompleted: [Lock.Controller, Launcher.Controller.init()] Process { @@ -38,8 +41,11 @@ ShellRoot { Notifs.NotificationOverlay { screen: Quickshell.screens.find(s => s.name == "DP-1") + bar: root.bars.find(b => b.screen == screen) } + property var bars: [] + Variants { model: Quickshell.screens @@ -48,6 +54,8 @@ ShellRoot { Bar.Bar { screen: modelData + Component.onCompleted: root.bars = [...root.bars, this] + Component.onDestruction: root.bars = root.bars.filter(b => b == this) } PanelWindow {