import QtQuick import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import qs.Common import qs.Services import qs.Widgets Item { id: root required property var panelWindow required property bool overviewOpen readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) readonly property real dpr: CompositorService.getScreenScale(panelWindow.screen) readonly property int workspacesShown: SettingsData.overviewRows * SettingsData.overviewColumns readonly property var allWorkspaces: Hyprland.workspaces?.values || [] readonly property var allWorkspaceIds: { const workspaces = allWorkspaces; if (!workspaces || workspaces.length === 0) return []; try { const ids = workspaces.map(ws => ws?.id).filter(id => id !== null && id !== undefined); return ids.sort((a, b) => a - b); } catch (e) { return []; } } readonly property var thisMonitorWorkspaceIds: { const workspaces = allWorkspaces; const mon = monitor; if (!workspaces || workspaces.length === 0 || !mon) return []; try { const filtered = workspaces.filter(ws => ws?.monitor?.name === mon.name); return filtered.map(ws => ws?.id).filter(id => id !== null && id !== undefined).sort((a, b) => a - b); } catch (e) { return []; } } readonly property var displayedWorkspaceIds: { if (!allWorkspaceIds || allWorkspaceIds.length === 0) { const result = []; for (let i = 1; i <= workspacesShown; i++) { result.push(i); } return result; } try { const maxExisting = Math.max(...allWorkspaceIds); const totalNeeded = Math.max(workspacesShown, allWorkspaceIds.length); const result = []; for (let i = 1; i <= maxExisting; i++) { result.push(i); } let nextId = maxExisting + 1; while (result.length < totalNeeded) { result.push(nextId); nextId++; } return result; } catch (e) { const result = []; for (let i = 1; i <= workspacesShown; i++) { result.push(i); } return result; } } readonly property int minWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[0] : 1 readonly property int maxWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[displayedWorkspaceIds.length - 1] : workspacesShown readonly property int displayWorkspaceCount: displayedWorkspaceIds.length readonly property int effectiveColumns: SettingsData.overviewColumns readonly property int effectiveRows: Math.max(SettingsData.overviewRows, Math.ceil(displayWorkspaceCount / effectiveColumns)) function getWorkspaceMonitorName(workspaceId) { if (!allWorkspaces || !workspaceId) return ""; try { const ws = allWorkspaces.find(w => w?.id === workspaceId); return ws?.monitor?.name ?? ""; } catch (e) { return ""; } } function workspaceHasWindows(workspaceId) { if (!workspaceId) return false; try { const workspace = allWorkspaces.find(ws => ws?.id === workspaceId); if (!workspace) return false; const toplevels = workspace?.toplevels?.values || []; return toplevels.length > 0; } catch (e) { return false; } } property bool monitorIsFocused: monitor?.focused ?? false property real scale: SettingsData.overviewScale property color activeBorderColor: Theme.primary readonly property real monitorPhysicalWidth: panelWindow.screen ? (panelWindow.screen.width / root.dpr) : (monitor?.width ?? 1920) readonly property real monitorPhysicalHeight: panelWindow.screen ? (panelWindow.screen.height / root.dpr) : (monitor?.height ?? 1080) property real workspaceImplicitWidth: monitorPhysicalWidth * root.scale property real workspaceImplicitHeight: monitorPhysicalHeight * root.scale property int workspaceZ: 0 property int windowZ: 1 property int monitorLabelZ: 2 property int windowDraggingZ: 99999 property real workspaceSpacing: 5 property int draggingFromWorkspace: -1 property int draggingTargetWorkspace: -1 implicitWidth: overviewBackground.implicitWidth + Theme.spacingL * 2 implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2 Component.onCompleted: { Hyprland.refreshToplevels(); Hyprland.refreshWorkspaces(); Hyprland.refreshMonitors(); } onOverviewOpenChanged: { if (overviewOpen) { Hyprland.refreshToplevels(); Hyprland.refreshWorkspaces(); Hyprland.refreshMonitors(); } } Rectangle { id: overviewBackground property real padding: 10 anchors.fill: parent anchors.margins: Theme.spacingL implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 radius: Theme.cornerRadius color: Theme.surfaceContainer layer.enabled: Theme.elevationEnabled layer.effect: MultiEffect { shadowEnabled: Theme.elevationEnabled shadowBlur: Theme.elevationEnabled ? Math.max(0, Math.min(1, (Theme.elevationLevel2 && Theme.elevationLevel2.blurPx !== undefined ? Theme.elevationLevel2.blurPx : 8) / Theme.elevationBlurMax)) : 0 shadowHorizontalOffset: 0 shadowVerticalOffset: Theme.elevationLevel2 && Theme.elevationLevel2.offsetY !== undefined ? Theme.elevationLevel2.offsetY : 4 blurMax: Theme.elevationBlurMax shadowColor: Theme.elevationShadowColor(Theme.elevationLevel2) shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 } ColumnLayout { id: workspaceColumnLayout z: root.workspaceZ anchors.centerIn: parent spacing: workspaceSpacing Repeater { model: root.effectiveRows delegate: RowLayout { id: row property int rowIndex: index spacing: workspaceSpacing Repeater { model: root.effectiveColumns Rectangle { id: workspace property int colIndex: index property int workspaceIndex: rowIndex * root.effectiveColumns + colIndex property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1 property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null property bool isActive: workspaceObj?.active ?? false property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : "" property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3) property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1) property color hoveredBorderColor: Theme.surfaceVariant property bool hoveredWhileDragging: false property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows visible: workspaceValue !== -1 implicitWidth: root.workspaceImplicitWidth implicitHeight: root.workspaceImplicitHeight color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor radius: Theme.cornerRadius border.width: 2 border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : "transparent") StyledText { anchors.centerIn: parent text: workspaceValue font.pixelSize: Theme.fontSizeXLarge * 6 font.weight: Font.DemiBold color: Theme.withAlpha(Theme.surfaceText, workspaceExists ? 0.2 : 0.1) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { id: workspaceArea anchors.fill: parent acceptedButtons: Qt.LeftButton onClicked: { if (root.draggingTargetWorkspace === -1) { root.overviewOpen = 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 { id: windowSpace anchors.centerIn: parent implicitWidth: workspaceColumnLayout.implicitWidth implicitHeight: workspaceColumnLayout.implicitHeight Repeater { model: ScriptModel { values: { const workspaces = root.allWorkspaces; const minId = root.minWorkspaceId; const maxId = root.maxWorkspaceId; if (!workspaces || workspaces.length === 0) return []; try { const result = []; for (const workspace of workspaces) { const wsId = workspace?.id ?? -1; if (wsId >= minId && wsId <= maxId) { const toplevels = workspace?.toplevels?.values || []; for (const toplevel of toplevels) { result.push(toplevel); } } } return result; } catch (e) { console.error("OverviewWidget filter error:", e); return []; } } } delegate: OverviewWindow { id: window required property var modelData overviewOpen: root.overviewOpen readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1 function getWorkspaceIndex() { if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0) return 0; if (!windowWorkspaceId || windowWorkspaceId < 0) return 0; try { for (let i = 0; i < root.displayedWorkspaceIds.length; i++) { if (root.displayedWorkspaceIds[i] === windowWorkspaceId) { return i; } } return 0; } catch (e) { return 0; } } readonly property int workspaceIndex: getWorkspaceIndex() readonly property int workspaceColIndex: workspaceIndex % root.effectiveColumns readonly property int workspaceRowIndex: Math.floor(workspaceIndex / root.effectiveColumns) toplevel: modelData scale: root.scale monitorDpr: root.dpr availableWorkspaceWidth: root.workspaceImplicitWidth availableWorkspaceHeight: root.workspaceImplicitHeight widgetMonitorId: root.monitor.id xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex z: atInitPosition ? root.windowZ : root.windowDraggingZ property bool atInitPosition: (initX == x && initY == y) Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: dragArea anchors.fill: parent hoverEnabled: true onEntered: window.hovered = true onExited: window.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; root.draggingTargetWorkspace = -1; if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace},address:${windowData?.address}`); Qt.callLater(() => { Hyprland.refreshToplevels(); Hyprland.refreshWorkspaces(); Qt.callLater(() => { window.x = window.initX; window.y = window.initY; }); }); } else { window.x = window.initX; window.y = window.initY; } } onClicked: event => { if (!windowData || !windowData.address) return; if (event.button === Qt.LeftButton) { root.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; } } } } } } Item { id: monitorLabelSpace anchors.centerIn: parent implicitWidth: workspaceColumnLayout.implicitWidth implicitHeight: workspaceColumnLayout.implicitHeight z: root.monitorLabelZ Repeater { model: root.effectiveRows delegate: Item { id: labelRow property int rowIndex: index y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex width: parent.width height: root.workspaceImplicitHeight Repeater { model: root.effectiveColumns delegate: Item { id: labelItem property int colIndex: index property int workspaceIndex: labelRow.rowIndex * root.effectiveColumns + colIndex property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1 property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : "" x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex width: root.workspaceImplicitWidth height: root.workspaceImplicitHeight Rectangle { anchors.right: parent.right anchors.top: parent.top anchors.margins: Theme.spacingS width: monitorNameText.contentWidth + Theme.spacingS * 2 height: monitorNameText.contentHeight + Theme.spacingXS * 2 radius: Theme.cornerRadius color: Theme.surface visible: labelItem.workspaceExists && labelItem.workspaceMonitorName !== "" StyledText { id: monitorNameText anchors.centerIn: parent text: labelItem.workspaceMonitorName font.pixelSize: Theme.fontSizeSmall font.weight: Font.Medium color: Theme.surfaceText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } } } } } }