import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Quickshell 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: Config.options.overview.rows * Config.options.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.options.overview.scale property color activeBorderColor: Appearance.colors.colSecondary property real workspaceImplicitWidth: (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: (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: 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 implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 property Component windowComponent: OverviewWindow {} property list windowWidgets: [] StyledRectangularShadow { target: 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.options.overview.rows delegate: RowLayout { id: row property int rowIndex: index spacing: workspaceSpacing Repeater { // Workspace repeater model: Config.options.overview.columns Rectangle { // Workspace id: workspace property int colIndex: index property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.options.overview.columns + colIndex + 1 property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredBorderColor: Appearance.colors.colLayer2Hover property bool hoveredWhileDragging: false implicitWidth: root.workspaceImplicitWidth implicitHeight: root.workspaceImplicitHeight color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor radius: Appearance.rounding.screenRounding * root.scale border.width: 2 border.color: hoveredWhileDragging ? hoveredBorderColor : "transparent" StyledText { anchors.centerIn: parent text: workspaceValue font.pixelSize: root.workspaceNumberSize * root.scale font.weight: Font.DemiBold color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { id: workspaceArea anchors.fill: parent acceptedButtons: Qt.LeftButton onClicked: { if (root.draggingTargetWorkspace === -1) { GlobalStates.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 { // Windows & focused workspace indicator id: windowSpace anchors.centerIn: parent implicitWidth: workspaceColumnLayout.implicitWidth implicitHeight: workspaceColumnLayout.implicitHeight Repeater { // Window repeater model: ScriptModel { values: { // console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2)) 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) const inMonitor = root.monitor.id === win.monitor return inWorkspaceGroup && inMonitor; }) } } delegate: OverviewWindow { id: window required property var modelData property var address: `0x${modelData.HyprlandToplevel.address}` windowData: windowByAddress[address] toplevel: modelData monitorData: HyprlandData.monitors[monitorId] scale: root.scale availableWorkspaceWidth: root.workspaceImplicitWidth availableWorkspaceHeight: root.workspaceImplicitHeight property int monitorId: windowData?.monitor property var monitor: HyprlandData.monitors[monitorId] property bool atInitPosition: (initX == x && initY == y) property int workspaceColIndex: (windowData?.workspace.id - 1) % Config.options.overview.columns property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.options.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]) * root.scale, 0) + xOffset) window.y = Math.round(Math.max((windowData?.at[1] - (monitor?.y ?? 0) - monitorData?.reserved[1]) * 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 // For hover color change onExited: hovered = false // For hover color change 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 // console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.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 } } 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) / Config.options.overview.columns) property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.options.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 { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on y { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } } } }