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 {} } } } } }