diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays')
4 files changed, 1198 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/HyprlandOverview.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/HyprlandOverview.qml new file mode 100644 index 0000000..1933035 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/HyprlandOverview.qml @@ -0,0 +1,291 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland +import qs.Common +import qs.Services + +Scope { + id: overviewScope + + property bool overviewOpen: false + + Loader { + id: hyprlandLoader + active: overviewScope.overviewOpen + asynchronous: false + + sourceComponent: Variants { + id: overviewVariants + model: Quickshell.screens + + PanelWindow { + id: root + required property var modelData + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) + + screen: modelData + visible: overviewScope.overviewOpen + color: "transparent" + + WlrLayershell.namespace: "dms:workspace-overview" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.exclusiveZone: -1 + WlrLayershell.keyboardFocus: { + if (!overviewScope.overviewOpen) + return WlrKeyboardFocus.None; + if (CompositorService.useHyprlandFocusGrab) + return WlrKeyboardFocus.OnDemand; + return WlrKeyboardFocus.Exclusive; + } + + anchors { + top: true + left: true + right: true + bottom: true + } + + HyprlandFocusGrab { + id: grab + windows: [root] + active: false + property bool hasBeenActivated: false + onActiveChanged: { + if (active) { + hasBeenActivated = true + } + } + onCleared: () => { + if (hasBeenActivated && overviewScope.overviewOpen) { + overviewScope.overviewOpen = false + } + } + } + + Connections { + target: overviewScope + function onOverviewOpenChanged() { + if (overviewScope.overviewOpen) { + grab.hasBeenActivated = false + if (CompositorService.useHyprlandFocusGrab) + delayedGrabTimer.start() + } else { + delayedGrabTimer.stop() + grab.active = false + grab.hasBeenActivated = false + } + } + } + + Connections { + target: root + function onMonitorIsFocusedChanged() { + if (!CompositorService.useHyprlandFocusGrab) + return; + if (overviewScope.overviewOpen && root.monitorIsFocused && !grab.active) { + grab.hasBeenActivated = false + grab.active = true + } else if (overviewScope.overviewOpen && !root.monitorIsFocused && grab.active) { + grab.active = false + } + } + } + + Timer { + id: delayedGrabTimer + interval: 150 + repeat: false + onTriggered: { + if (CompositorService.useHyprlandFocusGrab && overviewScope.overviewOpen && root.monitorIsFocused) { + grab.active = true + } + } + } + + Timer { + id: closeTimer + interval: Theme.expressiveDurations.expressiveDefaultSpatial + 120 + onTriggered: { + root.visible = false + } + } + + Rectangle { + id: background + anchors.fill: parent + color: "black" + opacity: overviewScope.overviewOpen ? 0.5 : 0 + + Behavior on opacity { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized + } + } + + MouseArea { + anchors.fill: parent + onClicked: mouse => { + const localPos = mapToItem(contentContainer, mouse.x, mouse.y) + if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height) { + overviewScope.overviewOpen = false + closeTimer.restart() + } + } + } + } + + Item { + id: contentContainer + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 100 + width: childrenRect.width + height: childrenRect.height + + opacity: overviewScope.overviewOpen ? 1 : 0 + transform: [scaleTransform, motionTransform] + + Scale { + id: scaleTransform + origin.x: contentContainer.width / 2 + origin.y: contentContainer.height / 2 + xScale: overviewScope.overviewOpen ? 1 : 0.96 + yScale: overviewScope.overviewOpen ? 1 : 0.96 + + Behavior on xScale { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized + } + } + + Behavior on yScale { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized + } + } + } + + Translate { + id: motionTransform + x: 0 + y: overviewScope.overviewOpen ? 0 : Theme.spacingL + + Behavior on y { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized + } + } + } + + Behavior on opacity { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized + } + } + + Loader { + id: overviewLoader + active: overviewScope.overviewOpen + asynchronous: false + + sourceComponent: OverviewWidget { + panelWindow: root + overviewOpen: overviewScope.overviewOpen + } + } + } + + FocusScope { + id: focusScope + anchors.fill: parent + visible: overviewScope.overviewOpen + focus: overviewScope.overviewOpen && root.monitorIsFocused + + Keys.onEscapePressed: event => { + if (!root.monitorIsFocused) return + overviewScope.overviewOpen = false + closeTimer.restart() + event.accepted = true + } + + Keys.onPressed: event => { + if (!root.monitorIsFocused) return + + if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) { + if (!overviewLoader.item) return + + const thisMonitorWorkspaceIds = overviewLoader.item.thisMonitorWorkspaceIds + if (thisMonitorWorkspaceIds.length === 0) return + + const currentId = root.monitor.activeWorkspace?.id ?? thisMonitorWorkspaceIds[0] + const currentIndex = thisMonitorWorkspaceIds.indexOf(currentId) + + let targetIndex + if (event.key === Qt.Key_Left) { + targetIndex = currentIndex - 1 + if (targetIndex < 0) targetIndex = thisMonitorWorkspaceIds.length - 1 + } else { + targetIndex = currentIndex + 1 + if (targetIndex >= thisMonitorWorkspaceIds.length) targetIndex = 0 + } + + const targetId = thisMonitorWorkspaceIds[targetIndex] + Hyprland.dispatch("workspace " + targetId) + event.accepted = true + } + } + + onVisibleChanged: { + if (visible && overviewScope.overviewOpen && root.monitorIsFocused) { + Qt.callLater(() => focusScope.forceActiveFocus()) + } + } + + Connections { + target: root + function onMonitorIsFocusedChanged() { + if (root.monitorIsFocused && overviewScope.overviewOpen) { + Qt.callLater(() => focusScope.forceActiveFocus()) + } + } + } + } + + onVisibleChanged: { + if (visible && overviewScope.overviewOpen) { + Qt.callLater(() => focusScope.forceActiveFocus()) + } else if (!visible) { + grab.active = false + } + } + + Connections { + target: overviewScope + function onOverviewOpenChanged() { + if (overviewScope.overviewOpen) { + closeTimer.stop() + root.visible = true + Qt.callLater(() => focusScope.forceActiveFocus()) + } else { + closeTimer.restart() + grab.active = false + } + } + } + } + } + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml new file mode 100644 index 0000000..c7b1bf7 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml @@ -0,0 +1,322 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import qs.Common +import qs.Modals.DankLauncherV2 +import qs.Services + +Scope { + id: niriOverviewScope + + property bool searchActive: false + property string searchActiveScreen: "" + property bool isClosing: false + property bool releaseKeyboard: false + readonly property bool spotlightModalOpen: PopoutService.dankLauncherV2Modal?.spotlightOpen ?? false + property bool overlayActive: NiriService.inOverview || searchActive + + function showSpotlight(screenName) { + isClosing = false; + releaseKeyboard = false; + searchActive = true; + searchActiveScreen = screenName; + } + + function hideSpotlight() { + if (!searchActive) + return; + isClosing = true; + } + + function hideAndReleaseKeyboard() { + releaseKeyboard = true; + hideSpotlight(); + } + + function resetState() { + searchActive = false; + searchActiveScreen = ""; + isClosing = false; + releaseKeyboard = false; + } + + Connections { + target: NiriService + function onInOverviewChanged() { + if (NiriService.inOverview) { + resetState(); + return; + } + if (!searchActive) { + resetState(); + return; + } + isClosing = true; + } + + function onCurrentOutputChanged() { + if (!NiriService.inOverview || !searchActive || searchActiveScreen === "" || searchActiveScreen === NiriService.currentOutput) + return; + hideSpotlight(); + } + } + + onSpotlightModalOpenChanged: { + if (spotlightModalOpen && searchActive) + hideSpotlight(); + } + + Loader { + id: niriOverlayLoader + active: overlayActive || isClosing + asynchronous: false + sourceComponent: Variants { + id: overlayVariants + model: Quickshell.screens + + PanelWindow { + id: overlayWindow + required property var modelData + + readonly property real dpr: CompositorService.getScreenScale(screen) + readonly property bool isActiveScreen: screen.name === NiriService.currentOutput + readonly property bool shouldShowSpotlight: niriOverviewScope.searchActive && screen.name === niriOverviewScope.searchActiveScreen && !niriOverviewScope.isClosing + readonly property bool isSpotlightScreen: screen.name === niriOverviewScope.searchActiveScreen + readonly property bool overlayVisible: NiriService.inOverview || niriOverviewScope.isClosing + property bool hasActivePopout: !!PopoutManager.currentPopoutsByScreen[screen.name] + property bool hasActiveModal: !!ModalManager.currentModalsByScreen[screen.name] + + Connections { + target: PopoutManager + function onPopoutChanged() { + overlayWindow.hasActivePopout = !!PopoutManager.currentPopoutsByScreen[overlayWindow.screen.name]; + } + } + + Connections { + target: ModalManager + function onModalChanged() { + overlayWindow.hasActiveModal = !!ModalManager.currentModalsByScreen[overlayWindow.screen.name]; + } + } + + screen: modelData + visible: overlayVisible + color: "transparent" + + WlrLayershell.namespace: "dms:niri-overview-spotlight" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.exclusiveZone: -1 + WlrLayershell.keyboardFocus: { + if (!NiriService.inOverview) + return WlrKeyboardFocus.None; + if (!isActiveScreen) + return WlrKeyboardFocus.None; + if (niriOverviewScope.releaseKeyboard) + return WlrKeyboardFocus.None; + if (hasActivePopout || hasActiveModal) + return WlrKeyboardFocus.None; + return WlrKeyboardFocus.Exclusive; + } + + mask: Region { + item: overlayVisible && spotlightContainer.visible ? spotlightContainer : null + } + + onShouldShowSpotlightChanged: { + if (shouldShowSpotlight) { + if (launcherContent?.controller) { + launcherContent.controller.searchMode = SessionData.niriOverviewLastMode || "apps"; + launcherContent.controller.performSearch(); + } + return; + } + if (!isActiveScreen) + return; + Qt.callLater(() => keyboardFocusScope.forceActiveFocus()); + } + + anchors { + top: true + left: true + right: true + bottom: true + } + + FocusScope { + id: keyboardFocusScope + anchors.fill: parent + focus: true + + Keys.onPressed: event => { + if (overlayWindow.shouldShowSpotlight || niriOverviewScope.isClosing) + return; + if ([Qt.Key_Escape, Qt.Key_Return].includes(event.key)) { + NiriService.toggleOverview(); + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Left) { + NiriService.moveColumnLeft(); + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Right) { + NiriService.moveColumnRight(); + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Up) { + NiriService.moveWorkspaceUp(); + event.accepted = true; + return; + } + + if (event.key === Qt.Key_Down) { + NiriService.moveWorkspaceDown(); + event.accepted = true; + return; + } + + if (event.modifiers & (Qt.ControlModifier | Qt.MetaModifier) || [Qt.Key_Delete, Qt.Key_Backspace].includes(event.key)) { + event.accepted = false; + return; + } + + if (event.isAutoRepeat || !event.text) + return; + if (!launcherContent?.searchField) + return; + const trimmedText = event.text.trim(); + launcherContent.searchField.text = trimmedText; + launcherContent.controller.setSearchQuery(trimmedText); + niriOverviewScope.showSpotlight(overlayWindow.screen.name); + Qt.callLater(() => launcherContent.searchField.forceActiveFocus()); + event.accepted = true; + } + } + + Item { + id: spotlightContainer + x: Theme.snap((parent.width - width) / 2, overlayWindow.dpr) + y: Theme.snap((parent.height - height) / 2, overlayWindow.dpr) + + readonly property int baseWidth: { + switch (SettingsData.dankLauncherV2Size) { + case "micro": + return 500; + case "medium": + return 720; + case "large": + return 860; + default: + return 620; + } + } + readonly property int baseHeight: { + switch (SettingsData.dankLauncherV2Size) { + case "micro": + return 480; + case "medium": + return 720; + case "large": + return 860; + default: + return 600; + } + } + width: Math.min(baseWidth, overlayWindow.screen.width - 100) + height: Math.min(baseHeight, overlayWindow.screen.height - 100) + + readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen + + scale: overlayWindow.shouldShowSpotlight ? 1.0 : 0.96 + opacity: overlayWindow.shouldShowSpotlight ? 1 : 0 + visible: overlayWindow.shouldShowSpotlight || animatingOut + enabled: overlayWindow.shouldShowSpotlight + + layer.enabled: visible + layer.smooth: false + layer.textureSize: layer.enabled ? Qt.size(Math.round(width * overlayWindow.dpr), Math.round(height * overlayWindow.dpr)) : Qt.size(0, 0) + + Behavior on scale { + id: scaleAnimation + NumberAnimation { + duration: Theme.expressiveDurations.fast + easing.type: Easing.BezierSpline + easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel + onRunningChanged: { + if (running || !spotlightContainer.animatingOut) + return; + niriOverviewScope.resetState(); + } + } + } + + Behavior on opacity { + NumberAnimation { + duration: Theme.expressiveDurations.fast + easing.type: Easing.BezierSpline + easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel + } + } + + Rectangle { + anchors.fill: parent + color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + radius: Theme.cornerRadius + border.color: Theme.outlineMedium + border.width: 1 + } + + LauncherContent { + id: launcherContent + anchors.fill: parent + anchors.margins: 0 + + property var fakeParentModal: QtObject { + property bool spotlightOpen: spotlightContainer.visible + property bool isClosing: niriOverviewScope.isClosing + function hide() { + if (niriOverviewScope.searchActive) { + niriOverviewScope.hideSpotlight(); + return; + } + NiriService.toggleOverview(); + } + } + + Connections { + target: launcherContent.searchField + function onTextChanged() { + if (launcherContent.searchField.text.length > 0 || !niriOverviewScope.searchActive) + return; + niriOverviewScope.hideSpotlight(); + } + } + + Component.onCompleted: { + parentModal = fakeParentModal; + } + + Connections { + target: launcherContent.controller + function onItemExecuted() { + niriOverviewScope.releaseKeyboard = true; + } + function onModeChanged(mode) { + if (launcherContent.controller.autoSwitchedToFiles) + return; + SessionData.setNiriOverviewLastMode(mode); + } + } + } + } + } + } + } +} 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 + } + } + } + } + } + } + } + } +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWindow.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWindow.qml new file mode 100644 index 0000000..4389971 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWindow.qml @@ -0,0 +1,142 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Common + +Item { + id: root + property var toplevel + property var scale + required property bool overviewOpen + property var availableWorkspaceWidth + property var availableWorkspaceHeight + property bool restrictToWorkspace: true + property real monitorDpr: 1 + + readonly property var windowData: toplevel?.lastIpcObject || null + readonly property var monitorObj: toplevel?.monitor + readonly property var monitorData: monitorObj?.lastIpcObject || null + readonly property real effectiveScale: root.scale / root.monitorDpr + + property real initX: Math.max(((windowData?.at?.[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * effectiveScale, 0) + xOffset + property real initY: Math.max(((windowData?.at?.[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * effectiveScale, 0) + yOffset + property real xOffset: 0 + property real yOffset: 0 + property int widgetMonitorId: 0 + + property var targetWindowWidth: (windowData?.size?.[0] ?? 100) * effectiveScale + property var targetWindowHeight: (windowData?.size?.[1] ?? 100) * effectiveScale + property bool hovered: false + property bool pressed: false + + property var iconToWindowRatio: 0.25 + property var iconToWindowRatioCompact: 0.45 + property var entry: DesktopEntries.heuristicLookup(Paths.moddedAppId(windowData?.class ?? "")) + property var iconPath: Paths.getAppIcon(windowData?.class ?? "", entry) || Quickshell.iconPath("application-x-executable", "image-missing") + property bool compactMode: Theme.fontSizeSmall * 4 > targetWindowHeight || Theme.fontSizeSmall * 4 > targetWindowWidth + + x: initX + y: initY + width: Math.min((windowData?.size?.[0] ?? 100) * effectiveScale, availableWorkspaceWidth) + height: Math.min((windowData?.size?.[1] ?? 100) * effectiveScale, availableWorkspaceHeight) + opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4 + + Rectangle { + id: maskRect + width: root.width + height: root.height + radius: Theme.cornerRadius + visible: false + layer.enabled: true + } + + layer.enabled: true + layer.effect: MultiEffect { + maskEnabled: true + maskSource: maskRect + maskSpreadAtMin: 1 + maskThresholdMin: 0.5 + } + + Behavior on x { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel + } + } + Behavior on y { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel + } + } + Behavior on width { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel + } + } + Behavior on height { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel + } + } + + ScreencopyView { + id: windowPreview + anchors.fill: parent + captureSource: root.overviewOpen ? root.toplevel?.wayland : null + live: true + + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: pressed ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.5) : + hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.3) : + Theme.withAlpha(Theme.surfaceContainer, 0.1) + border.color: Theme.withAlpha(Theme.outline, 0.3) + border.width: 1 + } + + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + spacing: Theme.fontSizeSmall * 0.5 + + Image { + id: windowIcon + property var iconSize: { + return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1) + } + Layout.alignment: Qt.AlignHCenter + source: root.iconPath + width: iconSize + height: iconSize + sourceSize: Qt.size(iconSize, iconSize) + + Behavior on width { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel + } + } + Behavior on height { + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel + } + } + } + } + } +} |