summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays
diff options
context:
space:
mode:
authorAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
committerAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
commit70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch)
treeab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays
parent5d94c0a7d44a2255b81815a52a7056a94a39842d (diff)
downloadRaveOS-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')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/HyprlandOverview.qml291
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml322
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWidget.qml443
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/WorkspaceOverlays/OverviewWindow.qml142
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
+ }
+ }
+ }
+ }
+ }
+}