diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWidget.qml')
| -rw-r--r-- | raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWidget.qml | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWidget.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWidget.qml new file mode 100644 index 0000000..03d39fb --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWidget.qml @@ -0,0 +1,443 @@ +import QtQuick +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 + + ElevationShadow { + anchors.fill: parent + z: -1 + level: Theme.elevationLevel2 + fallbackOffset: 4 + targetRadius: Theme.cornerRadius + targetColor: Theme.surfaceContainer + shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 + shadowEnabled: Theme.elevationEnabled + } + + 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 + } + } + } + } + } + } + } + } +} |