diff --git a/dots/.config/quickshell/ii/modules/ii/bar/Bar.qml b/dots/.config/quickshell/ii/modules/ii/bar/Bar.qml index 0bf1ad917..67e730677 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/Bar.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/Bar.qml @@ -1,3 +1,5 @@ +pragma ComponentBehavior: Bound + import QtQuick import Quickshell import Quickshell.Io @@ -60,6 +62,7 @@ Scope { } color: "transparent" + // Positioning anchors { top: !Config.options.bar.bottom bottom: Config.options.bar.bottom @@ -72,6 +75,14 @@ Scope { bottom: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.bottom) * -1 } + // Include in focus grab + Component.onCompleted: { + GlobalFocusGrab.addPersistent(barRoot); + } + Component.onDestruction: { + GlobalFocusGrab.removePersistent(barRoot); + } + MouseArea { id: hoverRegion hoverEnabled: true diff --git a/dots/.config/quickshell/ii/modules/ii/cheatsheet/Cheatsheet.qml b/dots/.config/quickshell/ii/modules/ii/cheatsheet/Cheatsheet.qml index f7fb68f68..c5df4fe82 100644 --- a/dots/.config/quickshell/ii/modules/ii/cheatsheet/Cheatsheet.qml +++ b/dots/.config/quickshell/ii/modules/ii/cheatsheet/Cheatsheet.qml @@ -54,13 +54,16 @@ Scope { // Scope item: cheatsheetBackground } - HyprlandFocusGrab { // Click outside to close - id: grab - windows: [cheatsheetRoot] - active: cheatsheetRoot.visible - onCleared: () => { - if (!active) - cheatsheetRoot.hide(); + Component.onCompleted: { + GlobalFocusGrab.addDismissable(cheatsheetRoot); + } + Component.onDestruction: { + GlobalFocusGrab.removeDismissable(cheatsheetRoot); + } + Connections { + target: GlobalFocusGrab + function onDismissed() { + cheatsheetRoot.hide(); } } diff --git a/dots/.config/quickshell/ii/modules/ii/mediaControls/MediaControls.qml b/dots/.config/quickshell/ii/modules/ii/mediaControls/MediaControls.qml index 3cd620834..3d98be7ca 100644 --- a/dots/.config/quickshell/ii/modules/ii/mediaControls/MediaControls.qml +++ b/dots/.config/quickshell/ii/modules/ii/mediaControls/MediaControls.qml @@ -81,7 +81,7 @@ Scope { } sourceComponent: PanelWindow { - id: mediaControlsRoot + id: panelWindow visible: true exclusionMode: ExclusionMode.Ignore @@ -98,9 +98,9 @@ Scope { right: Config.options.bar.vertical && Config.options.bar.bottom } margins { - top: Config.options.bar.vertical ? ((mediaControlsRoot.screen.height / 2) - widgetHeight * 1.5) : Appearance.sizes.barHeight + top: Config.options.bar.vertical ? ((panelWindow.screen.height / 2) - widgetHeight * 1.5) : Appearance.sizes.barHeight bottom: Appearance.sizes.barHeight - left: Config.options.bar.vertical ? Appearance.sizes.barHeight : ((mediaControlsRoot.screen.width / 2) - (osdWidth / 2) - widgetWidth) + left: Config.options.bar.vertical ? Appearance.sizes.barHeight : ((panelWindow.screen.width / 2) - (osdWidth / 2) - widgetWidth) right: Appearance.sizes.barHeight } @@ -108,13 +108,16 @@ Scope { item: playerColumnLayout } - HyprlandFocusGrab { - windows: [mediaControlsRoot] - active: mediaControlsLoader.active - onCleared: () => { - if (!active) { - GlobalStates.mediaControlsOpen = false; - } + Component.onCompleted: { + GlobalFocusGrab.addDismissable(panelWindow); + } + Component.onDestruction: { + GlobalFocusGrab.removeDismissable(panelWindow); + } + Connections { + target: GlobalFocusGrab + function onDismissed() { + GlobalStates.mediaControlsOpen = false; } } @@ -137,10 +140,13 @@ Scope { } } - Item { // No player placeholder + Item { + // No player placeholder Layout.alignment: { - if (mediaControlsRoot.anchors.left) return Qt.AlignLeft; - if (mediaControlsRoot.anchors.right) return Qt.AlignRight; + if (panelWindow.anchors.left) + return Qt.AlignLeft; + if (panelWindow.anchors.right) + return Qt.AlignRight; return Qt.AlignHCenter; } Layout.leftMargin: Appearance.sizes.hyprlandGapsOut @@ -153,7 +159,7 @@ Scope { target: placeholderBackground } - Rectangle { + Rectangle { id: placeholderBackground anchors.centerIn: parent color: Appearance.colors.colLayer0 diff --git a/dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OnScreenKeyboard.qml b/dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OnScreenKeyboard.qml index 3407c217f..9dc1068d8 100644 --- a/dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OnScreenKeyboard.qml +++ b/dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OnScreenKeyboard.qml @@ -57,6 +57,13 @@ Scope { // Scope item: oskBackground } + // Make it usable with other panels + Component.onCompleted: { + GlobalFocusGrab.addPersistent(oskRoot); + } + Component.onDestruction: { + GlobalFocusGrab.removePersistent(oskRoot); + } // Background StyledRectangularShadow { diff --git a/dots/.config/quickshell/ii/modules/ii/overview/Overview.qml b/dots/.config/quickshell/ii/modules/ii/overview/Overview.qml index 0c583f1b0..d94404721 100644 --- a/dots/.config/quickshell/ii/modules/ii/overview/Overview.qml +++ b/dots/.config/quickshell/ii/modules/ii/overview/Overview.qml @@ -23,7 +23,7 @@ Scope { visible: GlobalStates.overviewOpen WlrLayershell.namespace: "quickshell:overview" - WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.layer: WlrLayer.Top // WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None color: "transparent" @@ -38,43 +38,28 @@ Scope { right: true } - HyprlandFocusGrab { - id: grab - windows: [panelWindow] - property bool canBeActive: panelWindow.monitorIsFocused - active: false - onCleared: () => { - if (!active) - GlobalStates.overviewOpen = false; - } - } - Connections { target: GlobalStates function onOverviewOpenChanged() { if (!GlobalStates.overviewOpen) { searchWidget.disableExpandAnimation(); overviewScope.dontAutoCancelSearch = false; + GlobalFocusGrab.dismiss(); } else { if (!overviewScope.dontAutoCancelSearch) { searchWidget.cancelSearch(); } - delayedGrabTimer.start(); + GlobalFocusGrab.addDismissable(panelWindow); } } } - Timer { - id: delayedGrabTimer - interval: Config.options.hacks.arbitraryRaceConditionDelay - repeat: false - onTriggered: { - if (!grab.canBeActive) - return; - grab.active = GlobalStates.overviewOpen; + Connections { + target: GlobalFocusGrab + function onDismissed() { + GlobalStates.overviewOpen = false; } } - implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarLeft/SidebarLeft.qml b/dots/.config/quickshell/ii/modules/ii/sidebarLeft/SidebarLeft.qml index fc275976c..1952557bc 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarLeft/SidebarLeft.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarLeft/SidebarLeft.qml @@ -84,11 +84,11 @@ Scope { // Scope active: true sourceComponent: PanelWindow { // Window - id: sidebarRoot + id: panelWindow visible: GlobalStates.sidebarLeftOpen property bool extend: false - property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth + property real sidebarWidth: panelWindow.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth property var contentParent: sidebarLeftBackground function hide() { @@ -113,15 +113,17 @@ Scope { // Scope item: sidebarLeftBackground } - HyprlandFocusGrab { // Click outside to close - id: grab - windows: [ sidebarRoot ] - active: sidebarRoot.visible && !root.pin - onActiveChanged: { // Focus the selected tab - if (active) sidebarLeftBackground.children[0].focusActiveItem() + onVisibleChanged: { + if (visible) { + GlobalFocusGrab.addDismissable(panelWindow); + } else { + GlobalFocusGrab.removeDismissable(panelWindow); } - onCleared: () => { - if (!active) sidebarRoot.hide() + } + Connections { + target: GlobalFocusGrab + function onDismissed() { + panelWindow.hide(); } } @@ -136,7 +138,7 @@ Scope { // Scope anchors.left: parent.left anchors.topMargin: Appearance.sizes.hyprlandGapsOut anchors.leftMargin: Appearance.sizes.hyprlandGapsOut - width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin + width: panelWindow.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 color: Appearance.colors.colLayer0 border.width: 1 @@ -149,11 +151,11 @@ Scope { // Scope Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { - sidebarRoot.hide(); + panelWindow.hide(); } if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_O) { - sidebarRoot.extend = !sidebarRoot.extend; + panelWindow.extend = !panelWindow.extend; } else if (event.key === Qt.Key_D) { root.toggleDetach(); } else if (event.key === Qt.Key_P) { diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/SidebarRight.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/SidebarRight.qml index fcba76710..62946cb61 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/SidebarRight.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/SidebarRight.qml @@ -12,11 +12,11 @@ Scope { property int sidebarWidth: Appearance.sizes.sidebarWidth PanelWindow { - id: sidebarRoot + id: panelWindow visible: GlobalStates.sidebarRightOpen function hide() { - GlobalStates.sidebarRightOpen = false + GlobalStates.sidebarRightOpen = false; } exclusiveZone: 0 @@ -32,12 +32,17 @@ Scope { bottom: true } - HyprlandFocusGrab { - id: grab - windows: [ sidebarRoot ] - active: GlobalStates.sidebarRightOpen - onCleared: () => { - if (!active) sidebarRoot.hide() + onVisibleChanged: { + if (visible) { + GlobalFocusGrab.addDismissable(panelWindow); + } else { + GlobalFocusGrab.removeDismissable(panelWindow); + } + } + Connections { + target: GlobalFocusGrab + function onDismissed() { + panelWindow.hide(); } } @@ -53,16 +58,14 @@ Scope { height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 focus: GlobalStates.sidebarRightOpen - Keys.onPressed: (event) => { + Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { - sidebarRoot.hide(); + panelWindow.hide(); } } sourceComponent: SidebarRightContent {} } - - } IpcHandler { @@ -105,5 +108,4 @@ Scope { GlobalStates.sidebarRightOpen = false; } } - } diff --git a/dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalBar.qml b/dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalBar.qml index 3851f06d3..bf11091d6 100644 --- a/dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalBar.qml +++ b/dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalBar.qml @@ -66,6 +66,7 @@ Scope { } color: "transparent" + // Positioning anchors { left: !Config.options.bar.bottom right: Config.options.bar.bottom @@ -73,6 +74,14 @@ Scope { bottom: true } + // Include in focus grab + Component.onCompleted: { + GlobalFocusGrab.addPersistent(barRoot); + } + Component.onDestruction: { + GlobalFocusGrab.removePersistent(barRoot); + } + MouseArea { id: hoverRegion hoverEnabled: true diff --git a/dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelector.qml b/dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelector.qml index 197acbf2e..5bd79b56b 100644 --- a/dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelector.qml +++ b/dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelector.qml @@ -39,12 +39,16 @@ Scope { implicitHeight: Appearance.sizes.wallpaperSelectorHeight implicitWidth: Appearance.sizes.wallpaperSelectorWidth - HyprlandFocusGrab { // Click outside to close - id: grab - windows: [ panelWindow ] - active: wallpaperSelectorLoader.active - onCleared: () => { - if (!active) GlobalStates.wallpaperSelectorOpen = false; + Component.onCompleted: { + GlobalFocusGrab.addDismissable(panelWindow); + } + Component.onDestruction: { + GlobalFocusGrab.removeDismissable(panelWindow); + } + Connections { + target: GlobalFocusGrab + function onDismissed() { + GlobalStates.wallpaperSelectorOpen = false; } } diff --git a/dots/.config/quickshell/ii/services/GlobalFocusGrab.qml b/dots/.config/quickshell/ii/services/GlobalFocusGrab.qml new file mode 100644 index 000000000..547d79e17 --- /dev/null +++ b/dots/.config/quickshell/ii/services/GlobalFocusGrab.qml @@ -0,0 +1,64 @@ +pragma Singleton +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell +import Quickshell.Hyprland + +/** + * Manages a HyprlandFocusGrab that's to be shared by all windows. + * "Persistent" is for windows that should always be included but not closed on dismiss, like bar and onscreen keyboard. + * "Dismissable" is for stuff like sidebars + */ +Singleton { + id: root + + signal dismissed() + + property list persistent: [] + property list dismissable: [] + + function dismiss() { + root.dismissable = []; + root.dismissed(); + } + + Component.onCompleted: { + console.log("[GlobalFocusGrab] Initialized"); + } + + function addPersistent(window) { + if (root.persistent.indexOf(window) === -1) { + root.persistent.push(window); + } + } + + function removePersistent(window) { + var index = root.persistent.indexOf(window); + if (index !== -1) { + root.persistent.splice(index, 1); + } + } + + function addDismissable(window) { + if (root.dismissable.indexOf(window) === -1) { + root.dismissable.push(window); + } + } + + function removeDismissable(window) { + var index = root.dismissable.indexOf(window); + if (index !== -1) { + root.dismissable.splice(index, 1); + } + } + + HyprlandFocusGrab { + id: grab + windows: [...root.persistent, ...root.dismissable] + active: root.dismissable.length > 0 + onCleared: () => { + root.dismiss(); + } + } + +}