import "root:/" import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland 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: ConfigOptions.overview.numOfRows * ConfigOptions.overview.numOfCols readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) 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: ConfigOptions.overview.scale property color activeBorderColor: Appearance.m3colors.m3accentSecondary property real workspaceImplicitWidth: Math.max(100, (monitorData?.transform % 2 === 1) ? ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : ((monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale)) property real workspaceImplicitHeight: Math.max(60, (monitorData?.transform % 2 === 1) ? ((monitor.width - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) : ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale)) property real workspaceNumberMargin: 80 property real workspaceNumberSize: (ConfigOptions.overview.workspaceNumberSize > 0) ? ConfigOptions.overview.workspaceNumberSize : Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * 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 // Debug logging function function debugMultiMonitorInfo() { console.log("=== Multi-Monitor Debug Info ===") console.log("Current monitor ID:", root.monitor.id) console.log("Current monitor name:", root.monitor.name) console.log("Current monitor scale:", root.monitor.scale) console.log("Current monitor position:", root.monitor.x, root.monitor.y) console.log("Total monitors:", HyprlandData.monitors.length) HyprlandData.monitors.forEach((mon, idx) => { console.log(`Monitor ${idx}: ID=${mon.id}, Name=${mon.name}, Scale=${mon.scale}, Pos=(${mon.x},${mon.y}), Size=${mon.width}x${mon.height}, Reserved=[${mon.reserved.join(",")}]`) }) console.log("Total windows:", windowAddresses.length) windowAddresses.forEach((address, idx) => { const win = windowByAddress[address] if (win) { console.log(`Window ${idx}: ${win.class} on monitor ${win.monitor}, workspace ${win.workspace?.id}, pos=(${win.at[0]},${win.at[1]})`) } }) console.log("=== End Debug Info ===") } Component.onCompleted: { debugMultiMonitorInfo() } implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 property Component windowComponent: OverviewWindow {} property list windowWidgets: [] // Shared wallpaper image - loaded once and reused Image { id: sharedWallpaper source: Appearance.background_image || "" visible: false // Hidden as it's only used as a source cache: true asynchronous: true smooth: true opacity: Appearance.workpaceTransparency // Adds slight transparency (0.0 = fully transparent, 1.0 = fully opaque) } StyledRectangularShadow { target: overviewBackground } Rectangle { // Background id: overviewBackground property real padding: 10 anchors.fill: parent anchors.margins: Appearance.sizes.elevationMargin border.color : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.2) border.width : 2 implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 radius: Appearance.rounding.screenRounding * root.scale + padding color: Appearance.colors.colLayer0 ColumnLayout { // Workspaces id: workspaceColumnLayout z: root.workspaceZ anchors.centerIn: parent spacing: workspaceSpacing Repeater { model: ConfigOptions.overview.numOfRows delegate: RowLayout { id: row property int rowIndex: index spacing: workspaceSpacing Repeater { // Workspace repeater model: ConfigOptions.overview.numOfCols Rectangle { // Workspace id: workspace property int colIndex: index property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1 property color defaultWorkspaceColor: Appearance.colors.colLayer1 property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredBorderColor: Appearance.colors.colLayer2Hover property bool hoveredWhileDragging: false readonly property int padding: ConfigOptions.overview.windowPadding Layout.preferredWidth: root.workspaceImplicitWidth Layout.preferredHeight: root.workspaceImplicitHeight Layout.minimumWidth: 100 Layout.minimumHeight: 60 width: root.workspaceImplicitWidth height: root.workspaceImplicitHeight color: "transparent" radius: Appearance.rounding.screenRounding * root.scale clip: true opacity: Appearance.workpaceTransparency // Adds slight transparency (0.0 = fully transparent, 1.0 = fully opaque) // Efficient wallpaper using ShaderEffectSource Rectangle { id: wallpaperContainer anchors.fill: parent anchors.margins: 2 // Leave space for border radius: workspace.radius - 2 color: workspace.defaultWorkspaceColor // Fallback color clip: true ShaderEffectSource { id: wallpaperSource anchors.fill: parent sourceItem: sharedWallpaper visible: sharedWallpaper.status === Image.Ready smooth: true // Scale to fill while preserving aspect ratio transform: Scale { property real aspectRatio: sharedWallpaper.implicitWidth / Math.max(1, sharedWallpaper.implicitHeight) property real containerRatio: wallpaperContainer.width / Math.max(1, wallpaperContainer.height) xScale: aspectRatio > containerRatio ? wallpaperContainer.height * aspectRatio / wallpaperContainer.width : 1 yScale: aspectRatio > containerRatio ? 1 : wallpaperContainer.width / (wallpaperContainer.height * aspectRatio) origin.x: wallpaperContainer.width / 2 origin.y: wallpaperContainer.height / 2 } } // Fallback when image fails to load or isn't ready Rectangle { anchors.fill: parent color: workspace.defaultWorkspaceColor visible: sharedWallpaper.status !== Image.Ready } // Optional: Add overlay for better text readability and hover effects Rectangle { anchors.fill: parent color: hoveredWhileDragging ? hoveredWorkspaceColor : "black" opacity: hoveredWhileDragging ? 0.3 : 0.1 } } // Border overlay - on top of wallpaper Rectangle { anchors.fill: parent color: "transparent" radius: parent.radius border.width: 1 border.color: hoveredWhileDragging ? hoveredBorderColor : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.6) z: 10 // Ensure it's on top } StyledText { // Position in top-left corner with padding anchors.top: parent.top anchors.left: parent.left anchors.topMargin: 12 // Padding from top edge anchors.leftMargin: 12 // Padding from left edge text: workspaceValue font.pixelSize: root.workspaceNumberSize * root.scale font.weight: Font.DemiBold color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8) horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignTop z: 15 // Above border } MouseArea { id: workspaceArea anchors.fill: parent acceptedButtons: Qt.LeftButton z: 20 // Above all visual elements onClicked: { if (root.draggingTargetWorkspace === -1) { GlobalStates.overviewOpen = false Hyprland.dispatch(`workspace ${workspaceValue}`) } } } DropArea { anchors.fill: parent z: 20 // Same level as MouseArea 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: windowAddresses.filter((address) => { var win = windowByAddress[address] if (!win) return false // Filter by workspace group AND monitor if configured const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) const inMonitor = ConfigOptions.overview.showAllMonitors || win.monitor === root.monitor.id return inWorkspaceGroup && inMonitor }) } delegate: OverviewWindow { id: window windowData: windowByAddress[modelData] monitorData: root.monitorData scale: root.scale availableWorkspaceWidth: root.workspaceImplicitWidth availableWorkspaceHeight: root.workspaceImplicitHeight property bool atInitPosition: (initX == x && initY == y) restrictToWorkspace: Drag.active || atInitPosition property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols) xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex Timer { id: updateWindowPosition interval: ConfigOptions.hacks.arbitraryRaceConditionDelay repeat: false running: false onTriggered: { window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0] - monitorData?.x) * root.scale, 0) + xOffset window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1] - monitorData?.y) * root.scale, 0) + yOffset } } z: atInitPosition ? root.windowZ : 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: { root.draggingFromWorkspace = windowData?.workspace.id window.pressed = true window.Drag.active = true window.Drag.source = window } 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 } } StyledToolTip { extraVisibleCondition: false alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active content: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}\n` } } } } Rectangle { // Focused workspace indicator id: focusedWorkspaceIndicator property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown) property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / ConfigOptions.overview.numOfCols) property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % ConfigOptions.overview.numOfCols x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex z: root.windowZ width: Math.max(100, root.workspaceImplicitWidth) height: Math.max(60, root.workspaceImplicitHeight) color: "transparent" radius: Appearance.rounding.screenRounding * root.scale border.width: 2 border.color: root.activeBorderColor visible: width > 0 && height > 0 && activeWorkspaceInGroup > 0 Behavior on x { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on y { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } } } }