From 159920eb998f28e7b5138e1cab82d71a1bbaa14c Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Tue, 27 Jan 2026 14:16:23 +0100 Subject: [PATCH] window switcher start --- Bar.qml | 24 ++- Components/CustomAudioSlider.qml | 76 +++++++ Modules/AudioPopup.qml | 8 +- Modules/Bar/BarLoader.qml | 29 ++- Modules/Content.qml | 10 + Modules/TrayItem.qml | 11 +- Modules/TrayWidget.qml | 4 +- Modules/WSOverview/ColorUtils.qml | 68 ------ Modules/WSOverview/HyprlandData.qml | 134 ------------ Modules/WSOverview/Overview.qml | 147 ------------- Modules/WSOverview/OverviewPopout.qml | 84 ++++++++ Modules/WSOverview/OverviewWidget.qml | 298 -------------------------- Modules/WSOverview/OverviewWindow.qml | 103 --------- shell.qml | 3 - 14 files changed, 232 insertions(+), 767 deletions(-) create mode 100644 Components/CustomAudioSlider.qml delete mode 100644 Modules/WSOverview/ColorUtils.qml delete mode 100644 Modules/WSOverview/HyprlandData.qml delete mode 100644 Modules/WSOverview/Overview.qml create mode 100644 Modules/WSOverview/OverviewPopout.qml delete mode 100644 Modules/WSOverview/OverviewWidget.qml delete mode 100644 Modules/WSOverview/OverviewWindow.qml diff --git a/Bar.qml b/Bar.qml index 50e10ae..112ee32 100644 --- a/Bar.qml +++ b/Bar.qml @@ -52,11 +52,14 @@ Scope { x: 0 y: 34 - width: bar.width - height: bar.screen.height - backgroundRect.implicitHeight + property list nullRegions: [] + property bool hcurrent: panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") + + width: hcurrent ? 0 : bar.width + height: hcurrent ? 0 : bar.screen.height - backgroundRect.implicitHeight intersection: Intersection.Xor - regions: panels.popouts.hasCurrent ? None : popoutRegions.instances + regions: hcurrent ? nullRegions : popoutRegions.instances } Variants { @@ -69,7 +72,7 @@ Scope { x: modelData.x y: modelData.y + backgroundRect.implicitHeight width: modelData.width - height: panels.popouts.hasCurrent ? modelData.height + 70 : 0 + height: panels.popouts.hasCurrent ? modelData.height : 0 intersection: Intersection.Subtract } } @@ -110,6 +113,19 @@ Scope { } } + onPressed: event => { + var withinX = mouseX >= panels.popouts.x + 8 && mouseX < panels.popouts.x + panels.popouts.implicitWidth; + var withinY = mouseY >= panels.popouts.y + exclusionZone.implicitHeight && mouseY < panels.popouts.y + exclusionZone.implicitHeight + panels.popouts.implicitHeight; + + + if ( panels.popouts.hasCurrent ) { + if ( withinX && withinY ) { + } else { + panels.popouts.hasCurrent = false; + } + } + } + Panels { id: panels screen: bar.modelData diff --git a/Components/CustomAudioSlider.qml b/Components/CustomAudioSlider.qml new file mode 100644 index 0000000..e37738e --- /dev/null +++ b/Components/CustomAudioSlider.qml @@ -0,0 +1,76 @@ +import QtQuick +import QtQuick.Templates +import qs.Config +import qs.Modules + +Slider { + id: root + + required property real peak + + background: Item { + CustomRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.topMargin: root.implicitHeight / 3 + anchors.bottomMargin: root.implicitHeight / 3 + + implicitWidth: root.handle.x - root.implicitHeight / 6 + + color: DynamicColors.palette.m3primaryContainer + radius: 1000 + topRightRadius: root.implicitHeight / 15 + bottomRightRadius: root.implicitHeight / 15 + + CustomRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + + implicitWidth: parent.width * root.peak + radius: 1000 + topRightRadius: root.implicitHeight / 15 + bottomRightRadius: root.implicitHeight / 15 + + color: DynamicColors.palette.m3primary + + Behavior on implicitWidth { + Anim { duration: 50 } + } + } + } + + CustomRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.topMargin: root.implicitHeight / 3 + anchors.bottomMargin: root.implicitHeight / 3 + + implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 6 + + color: DynamicColors.tPalette.m3surfaceContainer + radius: 1000 + topLeftRadius: root.implicitHeight / 15 + bottomLeftRadius: root.implicitHeight / 15 + } + } + + handle: CustomRect { + x: root.visualPosition * root.availableWidth - implicitWidth / 2 + + implicitWidth: 5 + implicitHeight: 15 + anchors.verticalCenter: parent.verticalCenter + + color: DynamicColors.palette.m3primary + radius: 1000 + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: Qt.PointingHandCursor + } + } +} diff --git a/Modules/AudioPopup.qml b/Modules/AudioPopup.qml index 8544e41..a84e14c 100644 --- a/Modules/AudioPopup.qml +++ b/Modules/AudioPopup.qml @@ -339,6 +339,11 @@ Item { objects: appBox.modelData ? [appBox.modelData] : [] } + PwNodePeakMonitor { + id: peak + node: appBox.modelData + } + readonly property bool isCaptureStream: { if (!modelData || !modelData.properties) return false; @@ -409,9 +414,10 @@ Item { Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.bottomMargin: 5 implicitHeight: 10 - CustomSlider { + CustomAudioSlider { anchors.fill: parent value: appBox.modelData.audio.volume + peak: peak.peak onMoved: { Audio.setAppAudioVolume(appBox.modelData, value) console.log(icon.iconPath1, icon.iconPath2) diff --git a/Modules/Bar/BarLoader.qml b/Modules/Bar/BarLoader.qml index c179dbd..af63ff4 100644 --- a/Modules/Bar/BarLoader.qml +++ b/Modules/Bar/BarLoader.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import Quickshell +import Quickshell.Hyprland import QtQuick import QtQuick.Layouts import qs.Modules @@ -19,7 +20,7 @@ RowLayout { function checkPopout(x: real): void { const ch = childAt(x, height / 2) as WrappedLoader; - if (!ch) { + if (!ch && !popouts.currentName.includes("traymenu")) { popouts.hasCurrent = false; return; } @@ -42,11 +43,11 @@ RowLayout { const index = Math.floor((( x - top ) / item.implicitWidth ) * item.items.count ); const trayItem = item.items.itemAt( index ); if ( trayItem ) { - popouts.currentName = `traymenu${ index }`; - popouts.currentCenter = Qt.binding( () => trayItem.mapToItem( root, trayItem.implicitWidth / 2, 0 ).x ); - popouts.hasCurrent = true; + // popouts.currentName = `traymenu${ index }`; + // popouts.currentCenter = Qt.binding( () => trayItem.mapToItem( root, trayItem.implicitWidth / 2, 0 ).x ); + // popouts.hasCurrent = true; } else { - popouts.hasCurrent = false; + // popouts.hasCurrent = false; } } else if ( id === "clock" && Config.barConfig.popouts.clock ) { Calendar.displayYear = new Date().getFullYear(); @@ -57,6 +58,23 @@ RowLayout { } } + GlobalShortcut { + name: "toggle-overview" + appid: "zshell" + + onPressed: { + Hyprland.refreshWorkspaces(); + Hyprland.refreshMonitors(); + if ( root.popouts.hasCurrent && root.popouts.currentName === "overview" ) { + root.popouts.hasCurrent = false; + } else { + root.popouts.currentName = "overview"; + root.popouts.currentCenter = root.width / 2; + root.popouts.hasCurrent = true; + } + } + } + Repeater { id: repeater model: Config.barConfig.entries @@ -90,6 +108,7 @@ RowLayout { sourceComponent: TrayWidget { bar: root.bar popouts: root.popouts + loader: root } } } diff --git a/Modules/Content.qml b/Modules/Content.qml index e239665..3d6d580 100644 --- a/Modules/Content.qml +++ b/Modules/Content.qml @@ -5,6 +5,7 @@ import Quickshell.Services.SystemTray import QtQuick import qs.Config import qs.Modules.Calendar +import qs.Modules.WSOverview Item { id: root @@ -77,6 +78,15 @@ Item { wrapper: root.wrapper } } + + Popout { + name: "overview" + + sourceComponent: OverviewPopout { + wrapper: root.wrapper + screen: root.wrapper.screen + } + } } component Popout: Loader { diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml index afde255..b4bb9dd 100644 --- a/Modules/TrayItem.qml +++ b/Modules/TrayItem.qml @@ -1,11 +1,13 @@ +import QtQuick.Layouts +import QtQuick.Effects import QtQuick import Quickshell import Quickshell.Services.SystemTray import Quickshell.Io import Quickshell.Widgets import qs.Modules +import qs.Components import qs.Config -import QtQuick.Effects Item { id: root @@ -14,17 +16,20 @@ Item { required property PanelWindow bar required property int ind required property Wrapper popouts + required property RowLayout loader property bool hasLoaded: false - MouseArea { + StateLayer { anchors.fill: parent + anchors.margins: 3 + radius: 6 acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { if ( mouse.button === Qt.LeftButton ) { root.item.activate(); } else if ( mouse.button === Qt.RightButton ) { root.popouts.currentName = `traymenu${ root.ind }`; - root.popouts.currentCenter = Qt.binding( () => root.mapToItem( root.bar, root.implicitWidth / 2, 0 ).x ); + root.popouts.currentCenter = Qt.binding( () => root.mapToItem( root.loader, root.implicitWidth / 2, 0 ).x ); root.popouts.hasCurrent = true; } } diff --git a/Modules/TrayWidget.qml b/Modules/TrayWidget.qml index dc4c97c..4648b80 100644 --- a/Modules/TrayWidget.qml +++ b/Modules/TrayWidget.qml @@ -13,6 +13,7 @@ Row { required property PanelWindow bar required property Wrapper popouts + required property RowLayout loader readonly property alias items: repeater spacing: 0 @@ -26,8 +27,9 @@ Row { required property int index ind: index popouts: root.popouts + loader: root.loader implicitHeight: 34 - implicitWidth: 28 + implicitWidth: 34 item: modelData bar: root.bar } diff --git a/Modules/WSOverview/ColorUtils.qml b/Modules/WSOverview/ColorUtils.qml deleted file mode 100644 index 6162df1..0000000 --- a/Modules/WSOverview/ColorUtils.qml +++ /dev/null @@ -1,68 +0,0 @@ -pragma Singleton -import Quickshell - -Singleton { - id: root - - function colorWithHueOf(color1, color2) { - var c1 = Qt.color(color1); - var c2 = Qt.color(color2); - var hue = c2.hsvHue; - var sat = c1.hsvSaturation; - var val = c1.hsvValue; - var alpha = c1.a; - return Qt.hsva(hue, sat, val, alpha); - } - - function colorWithSaturationOf(color1, color2) { - var c1 = Qt.color(color1); - var c2 = Qt.color(color2); - var hue = c1.hsvHue; - var sat = c2.hsvSaturation; - var val = c1.hsvValue; - var alpha = c1.a; - return Qt.hsva(hue, sat, val, alpha); - } - - function colorWithLightness(color, lightness) { - var c = Qt.color(color); - return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a); - } - - function colorWithLightnessOf(color1, color2) { - var c2 = Qt.color(color2); - return colorWithLightness(color1, c2.hslLightness); - } - - function adaptToAccent(color1, color2) { - var c1 = Qt.color(color1); - var c2 = Qt.color(color2); - var hue = c2.hslHue; - var sat = c2.hslSaturation; - var light = c1.hslLightness; - var alpha = c1.a; - return Qt.hsla(hue, sat, light, alpha); - } - - function mix(color1, color2, percentage = 0.5) { - var c1 = Qt.color(color1); - var c2 = Qt.color(color2); - return Qt.rgba( - percentage * c1.r + (1 - percentage) * c2.r, - percentage * c1.g + (1 - percentage) * c2.g, - percentage * c1.b + (1 - percentage) * c2.b, - percentage * c1.a + (1 - percentage) * c2.a - ); - } - - function transparentize(color, percentage = 1) { - var c = Qt.color(color); - return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage)); - } - - function applyAlpha(color, alpha) { - var c = Qt.color(color); - var a = Math.max(0, Math.min(1, alpha)); - return Qt.rgba(c.r, c.g, c.b, a); - } -} diff --git a/Modules/WSOverview/HyprlandData.qml b/Modules/WSOverview/HyprlandData.qml deleted file mode 100644 index 371bf54..0000000 --- a/Modules/WSOverview/HyprlandData.qml +++ /dev/null @@ -1,134 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import QtQuick -import Quickshell -import Quickshell.Io -import Quickshell.Hyprland - -Singleton { - id: root - property var windowList: [] - property var addresses: [] - property var windowByAddress: ({}) - property var workspaces: [] - property var workspaceIds: [] - property var workspaceById: ({}) - property var activeWorkspace: null - property var monitors: [] - property var layers: ({}) - - function updateWindowList() { - getClients.running = true; - } - - function updateLayers() { - getLayers.running = true; - } - - function updateMonitors() { - getMonitors.running = true; - } - - function updateWorkspaces() { - getWorkspaces.running = true; - getActiveWorkspace.running = true; - } - - function updateAll() { - updateWindowList(); - updateMonitors(); - updateLayers(); - updateWorkspaces(); - } - - function biggestWindowForWorkspace(workspaceId) { - const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == workspaceId); - return windowsInThisWorkspace.reduce((maxWin, win) => { - const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0); - const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0); - return winArea > maxArea ? win : maxWin; - }, null); - } - - Component.onCompleted: { - updateAll(); - } - - Connections { - target: Hyprland - - function onRawEvent(event) { - updateAll() - } - } - - Process { - id: getClients - command: ["hyprctl", "clients", "-j"] - stdout: StdioCollector { - id: clientsCollector - onStreamFinished: { - root.windowList = JSON.parse(clientsCollector.text) - let tempWinByAddress = {}; - for (var i = 0; i < root.windowList.length; ++i) { - var win = root.windowList[i]; - tempWinByAddress[win.address] = win; - } - root.windowByAddress = tempWinByAddress; - root.addresses = root.windowList.map(win => win.address); - } - } - } - - Process { - id: getMonitors - command: ["hyprctl", "monitors", "-j"] - stdout: StdioCollector { - id: monitorsCollector - onStreamFinished: { - root.monitors = JSON.parse(monitorsCollector.text); - } - } - } - - Process { - id: getLayers - command: ["hyprctl", "layers", "-j"] - stdout: StdioCollector { - id: layersCollector - onStreamFinished: { - root.layers = JSON.parse(layersCollector.text); - } - } - } - - Process { - id: getWorkspaces - command: ["hyprctl", "workspaces", "-j"] - stdout: StdioCollector { - id: workspacesCollector - onStreamFinished: { - root.workspaces = JSON.parse(workspacesCollector.text); - let tempWorkspaceById = {}; - for (var i = 0; i < root.workspaces.length; ++i) { - var ws = root.workspaces[i]; - tempWorkspaceById[ws.id] = ws; - } - root.workspaceById = tempWorkspaceById; - root.workspaceIds = root.workspaces.map(ws => ws.id); - } - } - } - - Process { - id: getActiveWorkspace - command: ["hyprctl", "activeworkspace", "-j"] - stdout: StdioCollector { - id: activeWorkspaceCollector - onStreamFinished: { - root.activeWorkspace = JSON.parse(activeWorkspaceCollector.text); - } - } - } -} diff --git a/Modules/WSOverview/Overview.qml b/Modules/WSOverview/Overview.qml deleted file mode 100644 index 803763f..0000000 --- a/Modules/WSOverview/Overview.qml +++ /dev/null @@ -1,147 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Io -import Quickshell.Wayland -import Quickshell.Hyprland -import qs.Config - -Scope { - id: overviewScope - Variants { - id: overviewVariants - model: Quickshell.screens - PanelWindow { - id: root - required property var modelData - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) - property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) - screen: modelData - visible: false - - WlrLayershell.namespace: "quickshell:overview" - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive - color: "transparent" - - mask: Region { - item: keyHandler - } - - anchors { - top: true - bottom: true - left: true - right: true - } - - HyprlandFocusGrab { - id: grab - windows: [root] - property bool canBeActive: root.monitorIsFocused - active: false - onCleared: () => { - if (!active) - root.visible = false; - } - } - - implicitWidth: columnLayout.implicitWidth - implicitHeight: columnLayout.implicitHeight - - Item { - id: keyHandler - anchors.fill: parent - visible: root.visible - focus: root.visible - - Keys.onPressed: event => { - // close: Escape or Enter - if (event.key === Qt.Key_Escape || event.key === Qt.Key_Return) { - root.visible = false; - event.accepted = true; - return; - } - - // Helper: compute current group bounds - const workspacesPerGroup = Config.overview.rows * Config.overview.columns; - const currentId = Hyprland.focusedMonitor?.activeWorkspace?.id ?? 1; - const currentGroup = Math.floor((currentId - 1) / workspacesPerGroup); - const minWorkspaceId = currentGroup * workspacesPerGroup + 1; - const maxWorkspaceId = minWorkspaceId + workspacesPerGroup - 1; - - let targetId = null; - - // Arrow keys and vim-style hjkl - if (event.key === Qt.Key_Left || event.key === Qt.Key_H) { - targetId = currentId - 1; - if (targetId < minWorkspaceId) targetId = maxWorkspaceId; - } else if (event.key === Qt.Key_Right || event.key === Qt.Key_L) { - targetId = currentId + 1; - if (targetId > maxWorkspaceId) targetId = minWorkspaceId; - } else if (event.key === Qt.Key_Up || event.key === Qt.Key_K) { - targetId = currentId - Config.overview.columns; - if (targetId < minWorkspaceId) targetId += workspacesPerGroup; - } else if (event.key === Qt.Key_Down || event.key === Qt.Key_J) { - targetId = currentId + Config.overview.columns; - if (targetId > maxWorkspaceId) targetId -= workspacesPerGroup; - } - - // Number keys: jump to workspace within the current group - // 1-9 map to positions 1-9, 0 maps to position 10 - else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) { - const position = event.key - Qt.Key_0; // 1-9 - if (position <= workspacesPerGroup) { - targetId = minWorkspaceId + position - 1; - } - } else if (event.key === Qt.Key_0) { - // 0 = 10th workspace in the group (if group has 10+ workspaces) - if (workspacesPerGroup >= 10) { - targetId = minWorkspaceId + 9; // 10th position = offset 9 - } - } - - if (targetId !== null) { - Hyprland.dispatch("workspace " + targetId); - event.accepted = true; - } - } - } - - ColumnLayout { - id: columnLayout - visible: root.visible - anchors { - horizontalCenter: parent.horizontalCenter - top: parent.top - topMargin: 100 - } - - Loader { - id: overviewLoader - active: true && (Config.overview.enable ?? true) - sourceComponent: OverviewWidget { - panelWindow: root - visible: true - } - } - } - IpcHandler { - target: "overview" - - function toggle() { - root.visible = !root.visible; - } - function close() { - root.visible = false; - } - function open() { - root.visible = true; - } - } - } - } -} diff --git a/Modules/WSOverview/OverviewPopout.qml b/Modules/WSOverview/OverviewPopout.qml new file mode 100644 index 0000000..7ce7349 --- /dev/null +++ b/Modules/WSOverview/OverviewPopout.qml @@ -0,0 +1,84 @@ +import Quickshell +import Quickshell.Hyprland +import Quickshell.Wayland +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules +import qs.Helpers + +Item { + id: root + + required property Item wrapper + required property ShellScreen screen + + implicitWidth: layout.implicitWidth + 16 + implicitHeight: layout.implicitHeight + 16 + + GridLayout { + id: layout + anchors.centerIn: parent + + columnSpacing: 8 + rowSpacing: 8 + + Repeater { + model: Hypr.workspaces + + CustomRect { + id: workspacePreview + required property HyprlandWorkspace modelData + + border.color: "white" + border.width: 1 + radius: 8 + Layout.preferredWidth: 320 + 10 + Layout.preferredHeight: 180 + 10 + + Repeater { + model: workspacePreview.modelData.toplevels + + Item { + id: preview + anchors.fill: parent + anchors.margins: 5 + + required property HyprlandToplevel modelData + property rect appPosition: { + let { + at: [cx, cy], + size: [cw, ch] + } = modelData.lastIpcObject; + + cx -= modelData.monitor.x; + cy -= modelData.monitor.y; + + return Qt.rect( (cx / 8), (cy / 8), (cw / 8), (ch / 8) ) + } + + CustomRect { + border.color: DynamicColors.tPalette.m3outline + border.width: 1 + radius: 4 + implicitWidth: preview.appPosition.width + implicitHeight: preview.appPosition.height + + x: preview.appPosition.x + y: preview.appPosition.y - 3.4 + + ScreencopyView { + id: previewCopy + anchors.fill: parent + + captureSource: preview.modelData.wayland + live: true + } + } + } + } + } + } + } +} diff --git a/Modules/WSOverview/OverviewWidget.qml b/Modules/WSOverview/OverviewWidget.qml deleted file mode 100644 index 899768a..0000000 --- a/Modules/WSOverview/OverviewWidget.qml +++ /dev/null @@ -1,298 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland -import Quickshell.Hyprland -import qs.Config -import qs.Modules -import qs.Components -import qs.Effects - -Item { - id: root - required property var panelWindow - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) - readonly property var toplevels: ToplevelManager.toplevels - readonly property int workspacesShown: Config.overview.rows * Config.overview.columns - readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) - property bool monitorIsFocused: (Hyprland.focusedMonitor?.name == monitor.name) - property var windows: HyprlandData.windowList - property var windowByAddress: HyprlandData.windowByAddress - property var windowAddresses: HyprlandData.addresses - property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor?.id) - property real scale: Config.overview.scale - property color activeBorderColor: Appearance.colors.colSecondary - - property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? - ((monitor.height / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) : - ((monitor.width / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) - property real workspaceImplicitHeight: (monitorData?.transform % 2 === 1) ? - ((monitor.width / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) : - ((monitor.height / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) - - property real workspaceNumberMargin: 80 - property real workspaceNumberSize: 250 * monitor.scale - property int workspaceZ: 0 - property int windowZ: 1 - property int windowDraggingZ: 99999 - property real workspaceSpacing: 5 - - property int draggingFromWorkspace: -1 - property int draggingTargetWorkspace: -1 - - implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 - implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 - - property Component windowComponent: OverviewWindow {} - property list windowWidgets: [] - - ShadowRect { - anchors.fill: overviewBackground - } - - Rectangle { // Background - id: overviewBackground - property real padding: 10 - anchors.fill: parent - anchors.margins: Appearance.sizes.elevationMargin - - implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 - implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 - radius: Appearance.rounding.screenRounding * root.scale + padding - color: Appearance.colors.colLayer0 - border.width: 1 - border.color: Appearance.colors.colLayer0Border - - ColumnLayout { // Workspaces - id: workspaceColumnLayout - - z: root.workspaceZ - anchors.centerIn: parent - spacing: workspaceSpacing - Repeater { - model: Config.overview.rows - delegate: RowLayout { - id: row - property int rowIndex: index - spacing: workspaceSpacing - - Repeater { // Workspace repeater - model: Config.overview.columns - Rectangle { // Workspace - id: workspace - property int colIndex: index - property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.overview.columns + colIndex + 1 - property color defaultWorkspaceColor: DynamicColors.tPalette.m3surfaceContainerLow - property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) - property color hoveredBorderColor: DynamicColors.tPalette.m3surfaceContainer - property bool hoveredWhileDragging: false - - implicitWidth: root.workspaceImplicitWidth - implicitHeight: root.workspaceImplicitHeight - color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor - radius: 8 - border.width: 2 - border.color: hoveredWhileDragging ? hoveredBorderColor : "transparent" - - CustomText { - anchors.centerIn: parent - text: workspaceValue - color: DynamicColor.tPalette.m3onSurfaceVariant - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - MouseArea { - id: workspaceArea - anchors.fill: parent - acceptedButtons: Qt.LeftButton - onClicked: { - if (root.draggingTargetWorkspace === -1) { - root.panelWindow.visible = false - Hyprland.dispatch(`workspace ${workspaceValue}`) - } - } - } - - DropArea { - anchors.fill: parent - onEntered: { - root.draggingTargetWorkspace = workspaceValue - if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return; - hoveredWhileDragging = true - } - onExited: { - hoveredWhileDragging = false - if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 - } - } - } - } - } - } - } - - Item { // Windows & focused workspace indicator - id: windowSpace - anchors.centerIn: parent - implicitWidth: workspaceColumnLayout.implicitWidth - implicitHeight: workspaceColumnLayout.implicitHeight - - Repeater { // Window repeater - model: ScriptModel { - values: { - return ToplevelManager.toplevels.values.filter((toplevel) => { - const address = `0x${toplevel.HyprlandToplevel.address}` - var win = windowByAddress[address] - const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) - return inWorkspaceGroup; - }).sort((a, b) => { - // Proper stacking order based on Hyprland's window properties - const addrA = `0x${a.HyprlandToplevel.address}` - const addrB = `0x${b.HyprlandToplevel.address}` - const winA = windowByAddress[addrA] - const winB = windowByAddress[addrB] - - // 1. Pinned windows are always on top - if (winA?.pinned !== winB?.pinned) { - return winA?.pinned ? 1 : -1 - } - - // 2. Floating windows above tiled windows - if (winA?.floating !== winB?.floating) { - return winA?.floating ? 1 : -1 - } - - // 3. Within same category, sort by focus history - // Lower focusHistoryID = more recently focused = higher in stack - return (winB?.focusHistoryID ?? 0) - (winA?.focusHistoryID ?? 0) - }) - } - } - delegate: OverviewWindow { - id: window - required property var modelData - required property int index - property var overviewRoot: root.panelWindow - property int monitorId: windowData?.monitor - property var monitor: HyprlandData.monitors.find(m => m.id === monitorId) - property var address: `0x${modelData.HyprlandToplevel.address}` - windowData: windowByAddress[address] - toplevel: modelData - monitorData: monitor - - // Calculate scale relative to window's source monitor - property real sourceMonitorWidth: (monitor?.transform % 2 === 1) ? - (monitor?.height ?? 1920) / (monitor?.scale ?? 1) - (monitor?.reserved?.[0] ?? 0) - (monitor?.reserved?.[2] ?? 0) : - (monitor?.width ?? 1920) / (monitor?.scale ?? 1) - (monitor?.reserved?.[0] ?? 0) - (monitor?.reserved?.[2] ?? 0) - property real sourceMonitorHeight: (monitor?.transform % 2 === 1) ? - (monitor?.width ?? 1080) / (monitor?.scale ?? 1) - (monitor?.reserved?.[1] ?? 0) - (monitor?.reserved?.[3] ?? 0) : - (monitor?.height ?? 1080) / (monitor?.scale ?? 1) - (monitor?.reserved?.[1] ?? 0) - (monitor?.reserved?.[3] ?? 0) - - // Scale windows to fit the workspace size, accounting for different monitor sizes - scale: Math.min( - root.workspaceImplicitWidth / sourceMonitorWidth, - root.workspaceImplicitHeight / sourceMonitorHeight - ) - - availableWorkspaceWidth: root.workspaceImplicitWidth - availableWorkspaceHeight: root.workspaceImplicitHeight - widgetMonitorId: root.monitor.id - - property bool atInitPosition: (initX == x && initY == y) - - property int workspaceColIndex: (windowData?.workspace.id - 1) % Config.overview.columns - property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.overview.columns) - xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - - Timer { - id: updateWindowPosition - interval: Config.options.hacks.arbitraryRaceConditionDelay - repeat: false - running: false - onTriggered: { - window.x = Math.round(Math.max((windowData?.at[0] - (monitor?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset) - window.y = Math.round(Math.max((windowData?.at[1] - (monitor?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset) - } - } - - z: atInitPosition ? (root.windowZ + index) : root.windowDraggingZ - Drag.hotSpot.x: targetWindowWidth / 2 - Drag.hotSpot.y: targetWindowHeight / 2 - MouseArea { - id: dragArea - anchors.fill: parent - hoverEnabled: true - onEntered: hovered = true - onExited: hovered = false - acceptedButtons: Qt.LeftButton | Qt.MiddleButton - drag.target: parent - onPressed: (mouse) => { - root.draggingFromWorkspace = windowData?.workspace.id - window.pressed = true - window.Drag.active = true - window.Drag.source = window - window.Drag.hotSpot.x = mouse.x - window.Drag.hotSpot.y = mouse.y - } - onReleased: { - const targetWorkspace = root.draggingTargetWorkspace - window.pressed = false - window.Drag.active = false - root.draggingFromWorkspace = -1 - if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { - Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`) - updateWindowPosition.restart() - } - else { - window.x = window.initX - window.y = window.initY - } - } - onClicked: (event) => { - if (!windowData) return; - - if (event.button === Qt.LeftButton) { - GlobalStates.overviewOpen = false - Hyprland.dispatch(`focuswindow address:${windowData.address}`) - event.accepted = true - } else if (event.button === Qt.MiddleButton) { - Hyprland.dispatch(`closewindow address:${windowData.address}`) - event.accepted = true - } - } - - CustomTooltip { - extraVisibleCondition: false - alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active - text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}` - } - } - } - } - - Rectangle { - id: focusedWorkspaceIndicator - property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown) - property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.overview.columns) - property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.overview.columns - x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex - y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex - z: root.windowZ - width: root.workspaceImplicitWidth - height: root.workspaceImplicitHeight - color: "transparent" - radius: Appearance.rounding.screenRounding * root.scale - border.width: 2 - border.color: root.activeBorderColor - Behavior on x { - Anim {} - } - Behavior on y { - Anim {} - } - } - } - } -} diff --git a/Modules/WSOverview/OverviewWindow.qml b/Modules/WSOverview/OverviewWindow.qml deleted file mode 100644 index 00f9cc7..0000000 --- a/Modules/WSOverview/OverviewWindow.qml +++ /dev/null @@ -1,103 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland -import qs.Config -import qs.Modules - -Item { // Window - id: root - - required property var overviewRoot - - property var toplevel - property var windowData - property var monitorData - property var scale - property var availableWorkspaceWidth - property var availableWorkspaceHeight - property bool restrictToWorkspace: true - property real initX: Math.max(((windowData?.at[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset - property real initY: Math.max(((windowData?.at[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset - property real xOffset: 0 - property real yOffset: 0 - property int widgetMonitorId: 0 - - property var targetWindowWidth: (windowData?.size[0] ?? 100) * scale - property var targetWindowHeight: (windowData?.size[1] ?? 100) * scale - property bool hovered: false - property bool pressed: false - - property var iconToWindowRatio: 0.25 - property var xwaylandIndicatorToIconRatio: 0.35 - property var iconToWindowRatioCompact: 0.45 - property var entry: DesktopEntries.heuristicLookup(windowData?.class) - property var iconPath: Quickshell.iconPath(entry?.icon ?? windowData?.class ?? "application-x-executable", "image-missing") - property bool compactMode: false - - property bool indicateXWayland: windowData?.xwayland ?? false - - x: initX - y: initY - width: Math.min((windowData?.size[0] ?? 100) * root.scale, availableWorkspaceWidth) - height: Math.min((windowData?.size[1] ?? 100) * root.scale, availableWorkspaceHeight) - opacity: (windowData?.monitor ?? -1) == widgetMonitorId ? 1 : 0.4 - - clip: true - - Behavior on x { - Anim {} - } - Behavior on y { - Anim {} - } - Behavior on width { - Anim {} - } - Behavior on height { - Anim {} - } - - ScreencopyView { - id: windowPreview - anchors.fill: parent - captureSource: root.overviewRoot.visible ? root.toplevel : null - live: true - - Rectangle { - anchors.fill: parent - radius: 8 - color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) : - hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) : - ColorUtils.transparentize(Appearance.colors.colLayer2) - border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7) - border.width : 1 - } - - ColumnLayout { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.right: parent.right - spacing: 8 - - Image { - id: windowIcon - property var iconSize: { - return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1); - } - Layout.alignment: Qt.AlignHCenter - source: root.iconPath - width: iconSize - height: iconSize - sourceSize: Qt.size(iconSize, iconSize) - - Behavior on width { - Anim {} - } - Behavior on height { - Anim {} - } - } - } - } -} diff --git a/shell.qml b/shell.qml index 7da699d..6b28ebf 100644 --- a/shell.qml +++ b/shell.qml @@ -3,7 +3,6 @@ import Quickshell import qs.Modules import qs.Modules.Lock -import qs.Modules.WSOverview import qs.Helpers Scope { @@ -22,6 +21,4 @@ Scope { NotificationCenter { } - - Overview {} }