1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

modals: single window optimization

This commit is contained in:
bbedward
2025-12-01 17:49:32 -05:00
parent 139c99001a
commit 468e569bc7
19 changed files with 660 additions and 639 deletions

View File

@@ -0,0 +1,373 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Services
import qs.Widgets
Singleton {
id: root
property var activeModal: null
property bool windowsVisible: false
property var targetScreen: null
readonly property bool hasActiveModal: activeModal !== null
readonly property bool shouldShowModal: hasActiveModal
readonly property var screen: backgroundWindow.screen
readonly property real dpr: screen ? CompositorService.getScreenScale(screen) : 1
readonly property real shadowBuffer: 5
property bool wantsToHide: false
property real cachedModalWidth: 400
property real cachedModalHeight: 300
property real cachedModalX: 0
property real cachedModalY: 0
property var cachedModal: null
property int cachedAnimationDuration: Theme.shortDuration
property var cachedEnterCurve: Theme.expressiveCurves.expressiveFastSpatial
property var cachedExitCurve: Theme.expressiveCurves.expressiveFastSpatial
property real cachedScaleCollapsed: 0.96
readonly property real modalWidth: cachedModalWidth
readonly property real modalHeight: cachedModalHeight
readonly property real modalX: cachedModalX
readonly property real modalY: cachedModalY
Connections {
target: root.cachedModal
function onModalWidthChanged() {
if (!root.hasActiveModal)
return;
root.cachedModalWidth = Theme.px(root.cachedModal.modalWidth, root.dpr);
root.cachedModalX = root.calculateX(root.cachedModal);
}
function onModalHeightChanged() {
if (!root.hasActiveModal)
return;
root.cachedModalHeight = Theme.px(root.cachedModal.modalHeight, root.dpr);
root.cachedModalY = root.calculateY(root.cachedModal);
}
}
onScreenChanged: {
if (!cachedModal || !screen)
return;
cachedModalWidth = Theme.px(cachedModal.modalWidth, dpr);
cachedModalHeight = Theme.px(cachedModal.modalHeight, dpr);
cachedModalX = calculateX(cachedModal);
cachedModalY = calculateY(cachedModal);
}
function showModal(modal) {
wantsToHide = false;
targetScreen = CompositorService.focusedScreen;
activeModal = modal;
cachedModal = modal;
windowsVisible = true;
cachedModalWidth = Theme.px(modal.modalWidth, dpr);
cachedModalHeight = Theme.px(modal.modalHeight, dpr);
cachedModalX = calculateX(modal);
cachedModalY = calculateY(modal);
cachedAnimationDuration = modal.animationDuration ?? Theme.shortDuration;
cachedEnterCurve = modal.animationEnterCurve ?? Theme.expressiveCurves.expressiveFastSpatial;
cachedExitCurve = modal.animationExitCurve ?? Theme.expressiveCurves.expressiveFastSpatial;
cachedScaleCollapsed = modal.animationScaleCollapsed ?? 0.96;
if (modal.directContent)
Qt.callLater(focusDirectContent);
}
function focusDirectContent() {
if (!hasActiveModal)
return;
if (!cachedModal?.directContent)
return;
cachedModal.directContent.forceActiveFocus();
}
function hideModal() {
wantsToHide = true;
Qt.callLater(completeHide);
}
function completeHide() {
if (!wantsToHide)
return;
activeModal = null;
wantsToHide = false;
}
function hideModalInstant() {
wantsToHide = false;
activeModal = null;
windowsVisible = false;
cleanupInputMethod();
}
function onCloseAnimationFinished() {
if (hasActiveModal)
return;
if (cachedModal && typeof cachedModal.onFullyClosed === "function")
cachedModal.onFullyClosed();
cleanupInputMethod();
windowsVisible = false;
}
function cleanupInputMethod() {
if (!Qt.inputMethod)
return;
Qt.inputMethod.hide();
Qt.inputMethod.reset();
}
function calculateX(m) {
const screen = backgroundWindow.screen;
if (!screen)
return 0;
const w = Theme.px(m.modalWidth, dpr);
switch (m.positioning) {
case "center":
return Theme.snap((screen.width - w) / 2, dpr);
case "top-right":
return Theme.snap(Math.max(Theme.spacingL, screen.width - w - Theme.spacingL), dpr);
case "custom":
return Theme.snap(m.customPosition.x, dpr);
default:
return 0;
}
}
function calculateY(m) {
const screen = backgroundWindow.screen;
if (!screen)
return 0;
const h = Theme.px(m.modalHeight, dpr);
switch (m.positioning) {
case "center":
return Theme.snap((screen.height - h) / 2, dpr);
case "top-right":
return Theme.snap(Theme.barHeight + Theme.spacingXS, dpr);
case "custom":
return Theme.snap(m.customPosition.y, dpr);
default:
return 0;
}
}
PanelWindow {
id: backgroundWindow
visible: root.windowsVisible
screen: root.targetScreen
color: "transparent"
WlrLayershell.namespace: "dms:modal:background"
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
left: true
right: true
bottom: true
}
mask: Region {
item: backgroundMaskRect
intersection: Intersection.Xor
}
Item {
id: backgroundMaskRect
x: root.shouldShowModal ? root.modalX : 0
y: root.shouldShowModal ? root.modalY : 0
width: root.shouldShowModal ? root.modalWidth : (backgroundWindow.screen?.width ?? 1920)
height: root.shouldShowModal ? root.modalHeight : (backgroundWindow.screen?.height ?? 1080)
}
MouseArea {
anchors.fill: parent
enabled: root.windowsVisible
onClicked: mouse => {
if (!root.cachedModal || !root.shouldShowModal)
return;
if (!(root.cachedModal.closeOnBackgroundClick ?? true))
return;
const outside = mouse.x < root.modalX || mouse.x > root.modalX + root.modalWidth || mouse.y < root.modalY || mouse.y > root.modalY + root.modalHeight;
if (!outside)
return;
root.cachedModal.backgroundClicked();
}
}
Rectangle {
anchors.fill: parent
color: "black"
opacity: root.shouldShowModal && SettingsData.modalDarkenBackground ? (root.cachedModal?.backgroundOpacity ?? 0.5) : 0
visible: SettingsData.modalDarkenBackground
Behavior on opacity {
NumberAnimation {
duration: root.cachedAnimationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve
}
}
}
}
PanelWindow {
id: contentWindow
visible: root.windowsVisible
screen: root.targetScreen
color: "transparent"
WlrLayershell.namespace: root.cachedModal?.layerNamespace ?? "dms:modal"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
if (!root.hasActiveModal)
return WlrKeyboardFocus.None;
if (root.cachedModal?.customKeyboardFocus !== null && root.cachedModal?.customKeyboardFocus !== undefined)
return root.cachedModal.customKeyboardFocus;
if (CompositorService.isHyprland)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
anchors {
left: true
top: true
}
WlrLayershell.margins {
left: Math.max(0, Theme.snap(root.modalX - root.shadowBuffer, root.dpr))
top: Math.max(0, Theme.snap(root.modalY - root.shadowBuffer, root.dpr))
}
implicitWidth: root.modalWidth + (root.shadowBuffer * 2)
implicitHeight: root.modalHeight + (root.shadowBuffer * 2)
mask: Region {
item: contentMaskRect
}
Item {
id: contentMaskRect
x: root.shadowBuffer
y: root.shadowBuffer
width: root.shouldShowModal ? root.modalWidth : 0
height: root.shouldShowModal ? root.modalHeight : 0
}
HyprlandFocusGrab {
windows: [contentWindow]
active: CompositorService.isHyprland && root.hasActiveModal && (root.cachedModal?.shouldHaveFocus ?? false)
}
Item {
id: contentContainer
x: root.shadowBuffer
y: root.shadowBuffer
width: root.modalWidth
height: root.modalHeight
readonly property bool hasDirectContent: root.cachedModal ? (root.cachedModal.directContent !== null && root.cachedModal.directContent !== undefined) : false
opacity: root.shouldShowModal ? 1 : 0
scale: root.shouldShowModal ? 1 : root.cachedScaleCollapsed
onHasDirectContentChanged: {
if (!hasDirectContent)
return;
const dc = root.cachedModal.directContent;
if (dc.parent === directContentWrapper)
return;
dc.parent = directContentWrapper;
dc.anchors.fill = directContentWrapper;
}
Behavior on opacity {
NumberAnimation {
id: opacityAnimation
duration: root.cachedAnimationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve
onRunningChanged: {
if (running || root.shouldShowModal)
return;
root.onCloseAnimationFinished();
}
}
}
Behavior on scale {
NumberAnimation {
id: scaleAnimation
duration: root.cachedAnimationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve
}
}
DankRectangle {
anchors.fill: parent
color: root.cachedModal?.backgroundColor ?? Theme.surfaceContainer
borderColor: root.cachedModal?.borderColor ?? Theme.outlineMedium
borderWidth: root.cachedModal?.borderWidth ?? 1
radius: root.cachedModal?.cornerRadius ?? Theme.cornerRadius
z: -1
}
FocusScope {
id: modalFocusScope
anchors.fill: parent
focus: root.hasActiveModal
Keys.onEscapePressed: event => {
if (!root.cachedModal?.closeOnEscapeKey)
return;
root.cachedModal.close();
event.accepted = true;
}
Keys.forwardTo: contentContainer.hasDirectContent ? [directContentWrapper] : (contentLoader.item ? [contentLoader.item] : [])
Item {
id: directContentWrapper
anchors.fill: parent
visible: contentContainer.hasDirectContent
focus: contentContainer.hasDirectContent && root.hasActiveModal
}
Loader {
id: contentLoader
anchors.fill: parent
active: !contentContainer.hasDirectContent && root.windowsVisible
asynchronous: false
sourceComponent: root.cachedModal?.content ?? null
visible: !contentContainer.hasDirectContent
focus: !contentContainer.hasDirectContent && root.hasActiveModal
onLoaded: {
if (!item)
return;
if (root.cachedModal)
root.cachedModal.loadedContent = item;
if (root.hasActiveModal)
item.forceActiveFocus();
}
onActiveChanged: {
if (active || !root.cachedModal)
return;
root.cachedModal.loadedContent = null;
}
}
}
}
}
}

View File

@@ -25,6 +25,8 @@ import qs.Services
Item { Item {
id: root id: root
readonly property bool _forceDisplayService: DisplayService.brightnessAvailable !== undefined
Instantiator { Instantiator {
id: daemonPluginInstantiator id: daemonPluginInstantiator
asynchronous: true asynchronous: true

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services import qs.Services
@@ -10,11 +9,6 @@ DankModal {
layerNamespace: "dms:bluetooth-pairing" layerNamespace: "dms:bluetooth-pairing"
HyprlandFocusGrab {
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
property string deviceName: "" property string deviceName: ""
property string deviceAddress: "" property string deviceAddress: ""
property string requestType: "" property string requestType: ""

View File

@@ -1,10 +1,9 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modals.Clipboard import qs.Modals.Clipboard
Item { FocusScope {
id: clipboardContent id: clipboardContent
required property var modal required property var modal
@@ -15,6 +14,7 @@ Item {
property alias clipboardListView: clipboardListView property alias clipboardListView: clipboardListView
anchors.fill: parent anchors.fill: parent
focus: true
Column { Column {
anchors.fill: parent anchors.fill: parent
@@ -31,14 +31,13 @@ Item {
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
onClearAllClicked: { onClearAllClicked: {
clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () { clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () {
modal.clearAll() modal.clearAll();
modal.hide() modal.hide();
}, function () {}) }, function () {});
} }
onCloseClicked: modal.hide() onCloseClicked: modal.hide()
} }
// Search Field
DankTextField { DankTextField {
id: searchField id: searchField
width: parent.width width: parent.width
@@ -47,27 +46,24 @@ Item {
showClearButton: true showClearButton: true
focus: true focus: true
ignoreTabKeys: true ignoreTabKeys: true
keyForwardTargets: [modal.modalFocusScope]
onTextChanged: { onTextChanged: {
modal.searchText = text modal.searchText = text;
modal.updateFilteredModel() modal.updateFilteredModel();
} }
Keys.onEscapePressed: function (event) { Keys.onPressed: event => {
modal.hide() if (event.key === Qt.Key_Escape) {
event.accepted = true modal.hide();
event.accepted = true;
return;
} }
Component.onCompleted: { modal.keyboardController?.handleKey(event);
Qt.callLater(function () {
forceActiveFocus()
})
} }
Component.onCompleted: Qt.callLater(() => forceActiveFocus())
Connections { Connections {
target: modal target: modal
function onOpened() { function onOpened() {
Qt.callLater(function () { Qt.callLater(() => searchField.forceActiveFocus());
searchField.forceActiveFocus()
})
} }
} }
} }
@@ -97,21 +93,21 @@ Item {
function ensureVisible(index) { function ensureVisible(index) {
if (index < 0 || index >= count) { if (index < 0 || index >= count) {
return return;
} }
const itemHeight = ClipboardConstants.itemHeight + spacing const itemHeight = ClipboardConstants.itemHeight + spacing;
const itemY = index * itemHeight const itemY = index * itemHeight;
const itemBottom = itemY + itemHeight const itemBottom = itemY + itemHeight;
if (itemY < contentY) { if (itemY < contentY) {
contentY = itemY contentY = itemY;
} else if (itemBottom > contentY + height) { } else if (itemBottom > contentY + height) {
contentY = itemBottom - height contentY = itemBottom - height;
} }
} }
onCurrentIndexChanged: { onCurrentIndexChanged: {
if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) { if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) {
ensureVisible(currentIndex) ensureVisible(currentIndex);
} }
} }

View File

@@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
@@ -13,11 +12,6 @@ DankModal {
layerNamespace: "dms:clipboard" layerNamespace: "dms:clipboard"
HyprlandFocusGrab {
windows: [clipboardHistoryModal.contentWindow]
active: CompositorService.isHyprland && clipboardHistoryModal.shouldHaveFocus
}
property int totalCount: 0 property int totalCount: 0
property var clipboardEntries: [] property var clipboardEntries: []
property string searchText: "" property string searchText: ""
@@ -148,11 +142,10 @@ DankModal {
borderWidth: 1 borderWidth: 1
enableShadow: true enableShadow: true
onBackgroundClicked: hide() onBackgroundClicked: hide()
modalFocusScope.Keys.onPressed: function (event) {
keyboardController.handleKey(event);
}
content: clipboardContent content: clipboardContent
property alias keyboardController: keyboardController
ClipboardKeyboardController { ClipboardKeyboardController {
id: keyboardController id: keyboardController
modal: clipboardHistoryModal modal: clipboardHistoryModal
@@ -165,13 +158,11 @@ DankModal {
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
clipboardHistoryModal.shouldHaveFocus = false; clipboardHistoryModal.shouldHaveFocus = false;
} else if (clipboardHistoryModal.shouldBeVisible) { return;
}
if (!clipboardHistoryModal.shouldBeVisible)
return;
clipboardHistoryModal.shouldHaveFocus = true; clipboardHistoryModal.shouldHaveFocus = true;
clipboardHistoryModal.modalFocusScope.forceActiveFocus();
if (clipboardHistoryModal.contentLoader.item && clipboardHistoryModal.contentLoader.item.searchField) {
clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus();
}
}
} }
} }

View File

@@ -61,29 +61,21 @@ DankModal {
shouldBeVisible: false shouldBeVisible: false
allowStacking: true allowStacking: true
modalWidth: 350 modalWidth: 350
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 160 modalHeight: 160
enableShadow: true enableShadow: true
shouldHaveFocus: true shouldHaveFocus: true
onBackgroundClicked: { onBackgroundClicked: {
close(); close();
if (onCancel) { if (onCancel)
onCancel(); onCancel();
} }
}
onOpened: { function handleKey(event) {
Qt.callLater(function () {
modalFocusScope.forceActiveFocus();
modalFocusScope.focus = true;
shouldHaveFocus = true;
});
}
modalFocusScope.Keys.onPressed: function (event) {
switch (event.key) { switch (event.key) {
case Qt.Key_Escape: case Qt.Key_Escape:
close(); close();
if (onCancel) { if (onCancel)
onCancel(); onCancel();
}
event.accepted = true; event.accepted = true;
break; break;
case Qt.Key_Left: case Qt.Key_Left:
@@ -99,46 +91,46 @@ DankModal {
event.accepted = true; event.accepted = true;
break; break;
case Qt.Key_N: case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) { if (!(event.modifiers & Qt.ControlModifier))
return;
keyboardNavigation = true; keyboardNavigation = true;
selectedButton = (selectedButton + 1) % 2; selectedButton = (selectedButton + 1) % 2;
event.accepted = true; event.accepted = true;
}
break; break;
case Qt.Key_P: case Qt.Key_P:
if (event.modifiers & Qt.ControlModifier) { if (!(event.modifiers & Qt.ControlModifier))
return;
keyboardNavigation = true; keyboardNavigation = true;
selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2; selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2;
event.accepted = true; event.accepted = true;
}
break; break;
case Qt.Key_J: case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) { if (!(event.modifiers & Qt.ControlModifier))
return;
keyboardNavigation = true; keyboardNavigation = true;
selectedButton = 1; selectedButton = 1;
event.accepted = true; event.accepted = true;
}
break; break;
case Qt.Key_K: case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) { if (!(event.modifiers & Qt.ControlModifier))
return;
keyboardNavigation = true; keyboardNavigation = true;
selectedButton = 0; selectedButton = 0;
event.accepted = true; event.accepted = true;
}
break; break;
case Qt.Key_H: case Qt.Key_H:
if (event.modifiers & Qt.ControlModifier) { if (!(event.modifiers & Qt.ControlModifier))
return;
keyboardNavigation = true; keyboardNavigation = true;
selectedButton = 0; selectedButton = 0;
event.accepted = true; event.accepted = true;
}
break; break;
case Qt.Key_L: case Qt.Key_L:
if (event.modifiers & Qt.ControlModifier) { if (!(event.modifiers & Qt.ControlModifier))
return;
keyboardNavigation = true; keyboardNavigation = true;
selectedButton = 1; selectedButton = 1;
event.accepted = true; event.accepted = true;
}
break; break;
case Qt.Key_Tab: case Qt.Key_Tab:
keyboardNavigation = true; keyboardNavigation = true;
@@ -147,9 +139,9 @@ DankModal {
break; break;
case Qt.Key_Return: case Qt.Key_Return:
case Qt.Key_Enter: case Qt.Key_Enter:
if (selectedButton !== -1) { if (selectedButton !== -1)
selectButton(); selectButton();
} else { else {
selectedButton = 1; selectedButton = 1;
selectButton(); selectButton();
} }
@@ -159,10 +151,13 @@ DankModal {
} }
content: Component { content: Component {
Item { FocusScope {
anchors.fill: parent anchors.fill: parent
focus: true
implicitHeight: mainColumn.implicitHeight implicitHeight: mainColumn.implicitHeight
Keys.onPressed: event => root.handleKey(event)
Column { Column {
id: mainColumn id: mainColumn
anchors.left: parent.left anchors.left: parent.left

View File

@@ -1,24 +1,19 @@
import QtQuick import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services
import qs.Widgets
Item { Item {
id: root id: root
property string layerNamespace: "dms:modal" property string layerNamespace: "dms:modal"
property alias content: contentLoader.sourceComponent property Component content: null
property alias contentLoader: contentLoader
property Item directContent: null property Item directContent: null
property Item loadedContent: null
readonly property var contentLoader: QtObject {
readonly property var item: root.directContent ?? root.loadedContent
}
property real modalWidth: 400 property real modalWidth: 400
property real modalHeight: 300 property real modalHeight: 300
property var targetScreen property var targetScreen: null
readonly property var effectiveScreen: targetScreen || contentWindow.screen
readonly property real screenWidth: effectiveScreen?.width
readonly property real screenHeight: effectiveScreen?.height
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
property bool showBackground: true property bool showBackground: true
property real backgroundOpacity: 0.5 property real backgroundOpacity: 0.5
property string positioning: "center" property string positioning: "center"
@@ -36,7 +31,6 @@ Item {
property real borderWidth: 1 property real borderWidth: 1
property real cornerRadius: Theme.cornerRadius property real cornerRadius: Theme.cornerRadius
property bool enableShadow: false property bool enableShadow: false
property alias modalFocusScope: focusScope
property bool shouldBeVisible: false property bool shouldBeVisible: false
property bool shouldHaveFocus: shouldBeVisible property bool shouldHaveFocus: shouldBeVisible
property bool allowFocusOverride: false property bool allowFocusOverride: false
@@ -45,48 +39,41 @@ Item {
property bool keepPopoutsOpen: false property bool keepPopoutsOpen: false
property var customKeyboardFocus: null property var customKeyboardFocus: null
property bool useOverlayLayer: false property bool useOverlayLayer: false
readonly property alias contentWindow: contentWindow
readonly property alias backgroundWindow: backgroundWindow
signal opened signal opened
signal dialogClosed signal dialogClosed
signal backgroundClicked signal backgroundClicked
property bool animationsEnabled: true onBackgroundClicked: {
readonly property bool useBackgroundWindow: true if (closeOnBackgroundClick)
close();
}
function open() { function open() {
ModalManager.openModal(root); ModalManager.openModal(root);
closeTimer.stop();
shouldBeVisible = true; shouldBeVisible = true;
contentWindow.visible = false; shouldHaveFocus = true;
if (useBackgroundWindow) DankModalWindow.showModal(root);
backgroundWindow.visible = true; opened();
Qt.callLater(() => { }
contentWindow.visible = true;
shouldHaveFocus = false; function openCentered() {
Qt.callLater(() => { positioning = "center";
shouldHaveFocus = Qt.binding(() => shouldBeVisible); open();
});
});
} }
function close() { function close() {
shouldBeVisible = false; shouldBeVisible = false;
shouldHaveFocus = false; shouldHaveFocus = false;
closeTimer.restart(); DankModalWindow.hideModal();
dialogClosed();
} }
function instantClose() { function instantClose() {
animationsEnabled = false;
shouldBeVisible = false; shouldBeVisible = false;
shouldHaveFocus = false; shouldHaveFocus = false;
closeTimer.stop(); DankModalWindow.hideModalInstant();
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = false;
dialogClosed(); dialogClosed();
Qt.callLater(() => animationsEnabled = true);
} }
function toggle() { function toggle() {
@@ -96,322 +83,9 @@ Item {
Connections { Connections {
target: ModalManager target: ModalManager
function onCloseAllModalsExcept(excludedModal) { function onCloseAllModalsExcept(excludedModal) {
if (excludedModal !== root && !allowStacking && shouldBeVisible) { if (excludedModal === root || allowStacking || !shouldBeVisible)
return;
close(); close();
} }
} }
}
Timer {
id: closeTimer
interval: animationDuration + 120
onTriggered: {
if (!shouldBeVisible) {
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = false;
dialogClosed();
}
}
}
readonly property real shadowBuffer: 5
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
readonly property real alignedX: Theme.snap((() => {
switch (positioning) {
case "center":
return (screenWidth - alignedWidth) / 2;
case "top-right":
return Math.max(Theme.spacingL, screenWidth - alignedWidth - Theme.spacingL);
case "custom":
return customPosition.x;
default:
return 0;
}
})(), dpr)
readonly property real alignedY: Theme.snap((() => {
switch (positioning) {
case "center":
return (screenHeight - alignedHeight) / 2;
case "top-right":
return Theme.barHeight + Theme.spacingXS;
case "custom":
return customPosition.y;
default:
return 0;
}
})(), dpr)
PanelWindow {
id: backgroundWindow
visible: false
color: "transparent"
WlrLayershell.namespace: root.layerNamespace + ":background"
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
left: true
right: true
bottom: true
}
mask: Region {
item: Rectangle {
x: root.alignedX
y: root.alignedY
width: root.shouldBeVisible ? root.alignedWidth : 0
height: root.shouldBeVisible ? root.alignedHeight : 0
}
intersection: Intersection.Xor
}
MouseArea {
anchors.fill: parent
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
onClicked: mouse => {
const clickX = mouse.x;
const clickY = mouse.y;
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
if (!outsideContent)
return;
root.backgroundClicked();
}
}
Rectangle {
id: background
anchors.fill: parent
color: "black"
opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
visible: root.showBackground && SettingsData.modalDarkenBackground
Behavior on opacity {
enabled: root.animationsEnabled
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
}
}
PanelWindow {
id: contentWindow
visible: false
color: "transparent"
WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: {
if (root.useOverlayLayer)
return WlrLayershell.Overlay;
switch (Quickshell.env("DMS_MODAL_LAYER")) {
case "bottom":
console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
return WlrLayershell.Top;
case "background":
console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
return WlrLayershell.Top;
case "overlay":
return WlrLayershell.Overlay;
default:
return WlrLayershell.Top;
}
}
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
if (customKeyboardFocus !== null)
return customKeyboardFocus;
if (!shouldHaveFocus)
return WlrKeyboardFocus.None;
if (CompositorService.isHyprland)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
anchors {
left: true
top: true
}
WlrLayershell.margins {
left: Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
top: Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
}
implicitWidth: root.alignedWidth + (shadowBuffer * 2)
implicitHeight: root.alignedHeight + (shadowBuffer * 2)
onVisibleChanged: {
if (visible) {
opened();
} else {
if (Qt.inputMethod) {
Qt.inputMethod.hide();
Qt.inputMethod.reset();
}
}
}
Item {
id: modalContainer
x: shadowBuffer
y: shadowBuffer
width: root.alignedWidth
height: root.alignedHeight
readonly property bool slide: root.animationType === "slide"
readonly property real offsetX: slide ? 15 : 0
readonly property real offsetY: slide ? -30 : root.animationOffset
property real animX: 0
property real animY: 0
property real scaleValue: root.animationScaleCollapsed
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
Connections {
target: root
function onShouldBeVisibleChanged() {
modalContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetX, root.dpr);
modalContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetY, root.dpr);
modalContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
}
}
Behavior on animX {
enabled: root.animationsEnabled
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on animY {
enabled: root.animationsEnabled
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on scaleValue {
enabled: root.animationsEnabled
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Item {
id: contentContainer
anchors.centerIn: parent
width: parent.width
height: parent.height
clip: false
Item {
id: animatedContent
anchors.fill: parent
clip: false
opacity: root.shouldBeVisible ? 1 : 0
scale: modalContainer.scaleValue
x: Theme.snap(modalContainer.animX, root.dpr) + (parent.width - width) * (1 - modalContainer.scaleValue) * 0.5
y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5
Behavior on opacity {
enabled: root.animationsEnabled
NumberAnimation {
duration: animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
DankRectangle {
anchors.fill: parent
color: root.backgroundColor
borderColor: root.borderColor
borderWidth: root.borderWidth
radius: root.cornerRadius
}
FocusScope {
anchors.fill: parent
focus: root.shouldBeVisible
clip: false
Item {
id: directContentWrapper
anchors.fill: parent
visible: root.directContent !== null
focus: true
clip: false
Component.onCompleted: {
if (root.directContent) {
root.directContent.parent = directContentWrapper;
root.directContent.anchors.fill = directContentWrapper;
Qt.callLater(() => root.directContent.forceActiveFocus());
}
}
Connections {
target: root
function onDirectContentChanged() {
if (root.directContent) {
root.directContent.parent = directContentWrapper;
root.directContent.anchors.fill = directContentWrapper;
Qt.callLater(() => root.directContent.forceActiveFocus());
}
}
}
}
Loader {
id: contentLoader
anchors.fill: parent
active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
asynchronous: false
focus: true
clip: false
visible: root.directContent === null
onLoaded: {
if (item) {
Qt.callLater(() => item.forceActiveFocus());
}
}
}
}
}
}
}
FocusScope {
id: focusScope
objectName: "modalFocusScope"
anchors.fill: parent
visible: root.shouldBeVisible || contentWindow.visible
focus: root.shouldBeVisible
Keys.onEscapePressed: event => {
if (root.closeOnEscapeKey && shouldHaveFocus) {
root.close();
event.accepted = true;
}
}
}
}
} }

View File

@@ -1,6 +1,5 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services import qs.Services
@@ -11,11 +10,6 @@ DankModal {
layerNamespace: "dms:color-picker" layerNamespace: "dms:color-picker"
HyprlandFocusGrab {
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
property string pickerTitle: I18n.tr("Choose Color") property string pickerTitle: I18n.tr("Choose Color")
property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary
property var onColorSelectedCallback: null property var onColorSelectedCallback: null

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services import qs.Services
@@ -17,12 +16,6 @@ DankModal {
modalWidth: _maxW modalWidth: _maxW
modalHeight: _maxH modalHeight: _maxH
onBackgroundClicked: close() onBackgroundClicked: close()
onOpened: () => Qt.callLater(() => modalFocusScope.forceActiveFocus())
HyprlandFocusGrab {
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
function scrollDown() { function scrollDown() {
if (!root.activeFlickable) if (!root.activeFlickable)
@@ -40,25 +33,35 @@ DankModal {
root.activeFlickable.contentY = newY; root.activeFlickable.contentY = newY;
} }
modalFocusScope.Keys.onPressed: event => {
if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
scrollDown();
event.accepted = true;
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
scrollUp();
event.accepted = true;
} else if (event.key === Qt.Key_Down) {
scrollDown();
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
scrollUp();
event.accepted = true;
}
}
content: Component { content: Component {
Item { FocusScope {
anchors.fill: parent anchors.fill: parent
focus: true
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_J:
if (!(event.modifiers & Qt.ControlModifier))
return;
root.scrollDown();
event.accepted = true;
break;
case Qt.Key_K:
if (!(event.modifiers & Qt.ControlModifier))
return;
root.scrollUp();
event.accepted = true;
break;
case Qt.Key_Down:
root.scrollDown();
event.accepted = true;
break;
case Qt.Key_Up:
root.scrollUp();
event.accepted = true;
break;
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
@@ -11,11 +10,6 @@ DankModal {
layerNamespace: "dms:notification-center-modal" layerNamespace: "dms:notification-center-modal"
HyprlandFocusGrab {
windows: [notificationModal.contentWindow]
active: CompositorService.isHyprland && notificationModal.shouldHaveFocus
}
property bool notificationModalOpen: false property bool notificationModalOpen: false
property var notificationListRef: null property var notificationListRef: null
@@ -61,9 +55,6 @@ DankModal {
modalHeight: 700 modalHeight: 700
visible: false visible: false
onBackgroundClicked: hide() onBackgroundClicked: hide()
onOpened: () => {
Qt.callLater(() => modalFocusScope.forceActiveFocus());
}
onShouldBeVisibleChanged: shouldBeVisible => { onShouldBeVisibleChanged: shouldBeVisible => {
if (!shouldBeVisible) { if (!shouldBeVisible) {
notificationModalOpen = false; notificationModalOpen = false;
@@ -71,7 +62,6 @@ DankModal {
NotificationService.onOverlayClose(); NotificationService.onOverlayClose();
} }
} }
modalFocusScope.Keys.onPressed: event => modalKeyboardController.handleKey(event)
NotificationKeyboardController { NotificationKeyboardController {
id: modalKeyboardController id: modalKeyboardController
@@ -101,10 +91,12 @@ DankModal {
} }
content: Component { content: Component {
Item { FocusScope {
id: notificationKeyHandler id: notificationKeyHandler
anchors.fill: parent anchors.fill: parent
focus: true
Keys.onPressed: event => modalKeyboardController.handleKey(event)
Column { Column {
anchors.fill: parent anchors.fill: parent

View File

@@ -1,7 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services import qs.Services
@@ -13,11 +12,6 @@ DankModal {
layerNamespace: "dms:power-menu" layerNamespace: "dms:power-menu"
keepPopoutsOpen: true keepPopoutsOpen: true
HyprlandFocusGrab {
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
property int selectedIndex: 0 property int selectedIndex: 0
property int selectedRow: 0 property int selectedRow: 0
property int selectedCol: 0 property int selectedCol: 0
@@ -275,34 +269,11 @@ DankModal {
} else { } else {
selectedIndex = defaultIndex; selectedIndex = defaultIndex;
} }
Qt.callLater(() => modalFocusScope.forceActiveFocus());
} }
onDialogClosed: () => { onDialogClosed: () => {
cancelHold(); cancelHold();
} }
Component.onCompleted: updateVisibleActions() Component.onCompleted: updateVisibleActions()
modalFocusScope.Keys.onPressed: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (SettingsData.powerMenuGridLayout) {
handleGridNavigation(event, true);
} else {
handleListNavigation(event, true);
}
}
modalFocusScope.Keys.onReleased: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (SettingsData.powerMenuGridLayout) {
handleGridNavigation(event, false);
} else {
handleListNavigation(event, false);
}
}
function handleListNavigation(event, isPressed) { function handleListNavigation(event, isPressed) {
if (!isPressed) { if (!isPressed) {
@@ -481,10 +452,33 @@ DankModal {
} }
content: Component { content: Component {
Item { FocusScope {
anchors.fill: parent anchors.fill: parent
focus: true
implicitHeight: (SettingsData.powerMenuGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) + Theme.spacingL * 2 + (root.needsConfirmation ? hintRow.height + Theme.spacingM : 0) implicitHeight: (SettingsData.powerMenuGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) + Theme.spacingL * 2 + (root.needsConfirmation ? hintRow.height + Theme.spacingM : 0)
Keys.onPressed: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (SettingsData.powerMenuGridLayout)
root.handleGridNavigation(event, true);
else
root.handleListNavigation(event, true);
}
Keys.onReleased: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (SettingsData.powerMenuGridLayout)
root.handleGridNavigation(event, false);
else
root.handleListNavigation(event, false);
}
Grid { Grid {
id: buttonGrid id: buttonGrid
visible: SettingsData.powerMenuGridLayout visible: SettingsData.powerMenuGridLayout

View File

@@ -51,7 +51,21 @@ Item {
anchors.fill: parent anchors.fill: parent
focus: true focus: true
clip: false clip: false
onActiveFocusChanged: {
if (!activeFocus)
return;
if (!searchField)
return;
searchField.forceActiveFocus();
}
Keys.onPressed: event => { Keys.onPressed: event => {
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
if (menu?.visible) {
menu.handleKey(event);
return;
}
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
if (parentModal) if (parentModal)
parentModal.hide(); parentModal.hide();
@@ -197,7 +211,6 @@ Item {
parent: spotlightKeyHandler parent: spotlightKeyHandler
appLauncher: spotlightKeyHandler.appLauncher appLauncher: spotlightKeyHandler.appLauncher
parentHandler: spotlightKeyHandler
searchField: spotlightKeyHandler.searchField searchField: spotlightKeyHandler.searchField
visible: false visible: false
z: 1000 z: 1000
@@ -218,8 +231,6 @@ Item {
sourceComponent: Component { sourceComponent: Component {
SpotlightContextMenu { SpotlightContextMenu {
appLauncher: spotlightKeyHandler.appLauncher appLauncher: spotlightKeyHandler.appLauncher
parentHandler: spotlightKeyHandler
parentModal: spotlightKeyHandler.parentModal
} }
} }
} }
@@ -280,6 +291,12 @@ Item {
updateSearchMode(); updateSearchMode();
} }
Keys.onPressed: event => { Keys.onPressed: event => {
const menu = spotlightKeyHandler.usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
if (menu?.visible) {
menu.handleKey(event);
return;
}
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
if (parentModal) if (parentModal)
parentModal.hide(); parentModal.hide();
@@ -312,7 +329,7 @@ Item {
Row { Row {
id: viewModeButtons id: viewModeButtons
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: searchMode === "apps" && appLauncher.model.count > 0 visible: searchMode === "apps"
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -1,7 +1,6 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modals.Spotlight import qs.Modals.Spotlight
@@ -11,17 +10,15 @@ PanelWindow {
WlrLayershell.namespace: "dms:spotlight-context-menu" WlrLayershell.namespace: "dms:spotlight-context-menu"
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
property var appLauncher: null property var appLauncher: null
property var parentHandler: null
property var parentModal: null
property real menuPositionX: 0 property real menuPositionX: 0
property real menuPositionY: 0 property real menuPositionY: 0
readonly property real shadowBuffer: 5 readonly property real shadowBuffer: 5
screen: parentModal?.effectiveScreen screen: DankModalWindow.targetScreen
function show(x, y, app, fromKeyboard) { function show(x, y, app, fromKeyboard) {
fromKeyboard = fromKeyboard || false; fromKeyboard = fromKeyboard || false;
@@ -30,14 +27,15 @@ PanelWindow {
let screenX = x; let screenX = x;
let screenY = y; let screenY = y;
if (parentModal) { const modalX = DankModalWindow.modalX;
const modalY = DankModalWindow.modalY;
if (fromKeyboard) { if (fromKeyboard) {
screenX = x + parentModal.alignedX; screenX = x + modalX;
screenY = y + parentModal.alignedY; screenY = y + modalY;
} else { } else {
screenX = x + (parentModal.alignedX - shadowBuffer); screenX = x + (modalX - shadowBuffer);
screenY = y + (parentModal.alignedY - shadowBuffer); screenY = y + (modalY - shadowBuffer);
}
} }
menuPositionX = screenX; menuPositionX = screenX;
@@ -46,19 +44,32 @@ PanelWindow {
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1; menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
menuContent.keyboardNavigation = true; menuContent.keyboardNavigation = true;
visible = true; visible = true;
if (parentHandler) {
parentHandler.enabled = false;
} }
Qt.callLater(() => {
menuContent.keyboardHandler.forceActiveFocus(); function handleKey(event) {
}); switch (event.key) {
case Qt.Key_Down:
menuContent.selectNext();
event.accepted = true;
break;
case Qt.Key_Up:
menuContent.selectPrevious();
event.accepted = true;
break;
case Qt.Key_Return:
case Qt.Key_Enter:
menuContent.activateSelected();
event.accepted = true;
break;
case Qt.Key_Escape:
case Qt.Key_Menu:
hide();
event.accepted = true;
break;
}
} }
function hide() { function hide() {
if (parentHandler) {
parentHandler.enabled = true;
}
visible = false; visible = false;
} }
@@ -71,11 +82,6 @@ PanelWindow {
bottom: true bottom: true
} }
onVisibleChanged: {
if (!visible && parentHandler) {
parentHandler.enabled = true;
}
}
SpotlightContextMenuContent { SpotlightContextMenuContent {
id: menuContent id: menuContent

View File

@@ -8,7 +8,6 @@ Popup {
id: root id: root
property var appLauncher: null property var appLauncher: null
property var parentHandler: null
property var searchField: null property var searchField: null
function show(x, y, app, fromKeyboard) { function show(x, y, app, fromKeyboard) {
@@ -21,38 +20,45 @@ Popup {
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1; menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
menuContent.keyboardNavigation = true; menuContent.keyboardNavigation = true;
if (parentHandler) {
parentHandler.enabled = false;
}
open(); open();
} }
onOpened: { function handleKey(event) {
Qt.callLater(() => { switch (event.key) {
menuContent.keyboardHandler.forceActiveFocus(); case Qt.Key_Down:
}); menuContent.selectNext();
event.accepted = true;
break;
case Qt.Key_Up:
menuContent.selectPrevious();
event.accepted = true;
break;
case Qt.Key_Return:
case Qt.Key_Enter:
menuContent.activateSelected();
event.accepted = true;
break;
case Qt.Key_Escape:
case Qt.Key_Menu:
hide();
event.accepted = true;
break;
}
} }
function hide() { function hide() {
if (parentHandler) {
parentHandler.enabled = true;
}
close(); close();
} }
width: menuContent.implicitWidth width: menuContent.implicitWidth
height: menuContent.implicitHeight height: menuContent.implicitHeight
padding: 0 padding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnPressOutside
modal: true modal: true
dim: false dim: false
background: Item {} background: Item {}
onClosed: { onClosed: {
if (parentHandler) {
parentHandler.enabled = true;
}
if (searchField) { if (searchField) {
Qt.callLater(() => { Qt.callLater(() => {
searchField.forceActiveFocus(); searchField.forceActiveFocus();

View File

@@ -1,20 +1,13 @@
import QtQuick import QtQuick
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Services
DankModal { DankModal {
id: spotlightModal id: spotlightModal
layerNamespace: "dms:spotlight" layerNamespace: "dms:spotlight"
HyprlandFocusGrab {
windows: [spotlightModal.contentWindow]
active: CompositorService.isHyprland && spotlightModal.shouldHaveFocus
}
property bool spotlightOpen: false property bool spotlightOpen: false
property alias spotlightContent: spotlightContentInstance property alias spotlightContent: spotlightContentInstance
property bool openedFromOverview: false property bool openedFromOverview: false
@@ -23,32 +16,18 @@ DankModal {
openedFromOverview = false; openedFromOverview = false;
spotlightOpen = true; spotlightOpen = true;
open(); open();
Qt.callLater(() => {
if (spotlightContent && spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus();
}
});
} }
function showWithQuery(query) { function showWithQuery(query) {
if (spotlightContent) { if (spotlightContent) {
if (spotlightContent.appLauncher) { if (spotlightContent.appLauncher)
spotlightContent.appLauncher.searchQuery = query; spotlightContent.appLauncher.searchQuery = query;
} if (spotlightContent.searchField)
if (spotlightContent.searchField) {
spotlightContent.searchField.text = query; spotlightContent.searchField.text = query;
} }
}
spotlightOpen = true; spotlightOpen = true;
open(); open();
Qt.callLater(() => {
if (spotlightContent && spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus();
}
});
} }
function hide() { function hide() {
@@ -57,24 +36,25 @@ DankModal {
close(); close();
} }
onDialogClosed: { function onFullyClosed() {
if (spotlightContent) { resetContent();
}
function resetContent() {
if (!spotlightContent)
return;
if (spotlightContent.appLauncher) { if (spotlightContent.appLauncher) {
spotlightContent.appLauncher.searchQuery = ""; spotlightContent.appLauncher.searchQuery = "";
spotlightContent.appLauncher.selectedIndex = 0; spotlightContent.appLauncher.selectedIndex = 0;
spotlightContent.appLauncher.setCategory(I18n.tr("All")); spotlightContent.appLauncher.setCategory(I18n.tr("All"));
} }
if (spotlightContent.fileSearchController) { if (spotlightContent.fileSearchController)
spotlightContent.fileSearchController.reset(); spotlightContent.fileSearchController.reset();
} if (spotlightContent.resetScroll)
if (spotlightContent.resetScroll) {
spotlightContent.resetScroll(); spotlightContent.resetScroll();
} if (spotlightContent.searchField)
if (spotlightContent.searchField) {
spotlightContent.searchField.text = ""; spotlightContent.searchField.text = "";
} }
}
}
function toggle() { function toggle() {
if (spotlightOpen) { if (spotlightOpen) {
@@ -94,17 +74,11 @@ DankModal {
enableShadow: true enableShadow: true
keepContentLoaded: true keepContentLoaded: true
onVisibleChanged: () => { onVisibleChanged: () => {
if (visible && !spotlightOpen) { if (!visible)
return;
if (!spotlightOpen)
show(); show();
} }
if (visible && spotlightContent) {
Qt.callLater(() => {
if (spotlightContent.searchField) {
spotlightContent.searchField.forceActiveFocus();
}
});
}
}
onBackgroundClicked: () => { onBackgroundClicked: () => {
return hide(); return hide();
} }

View File

@@ -95,10 +95,6 @@ FloatingWindow {
if (!parentModal) if (!parentModal)
return; return;
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible); parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
Qt.callLater(() => {
if (parentModal.modalFocusScope)
parentModal.modalFocusScope.forceActiveFocus();
});
} }
objectName: "pluginBrowser" objectName: "pluginBrowser"

View File

@@ -82,10 +82,6 @@ FloatingWindow {
if (!parentModal) if (!parentModal)
return; return;
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible); parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
Qt.callLater(() => {
if (parentModal && parentModal.modalFocusScope)
parentModal.modalFocusScope.forceActiveFocus();
});
} }
objectName: "widgetSelectionPopup" objectName: "widgetSelectionPopup"
@@ -112,10 +108,6 @@ FloatingWindow {
if (!parentModal) if (!parentModal)
return; return;
parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible); parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible);
Qt.callLater(() => {
if (parentModal && parentModal.modalFocusScope)
parentModal.modalFocusScope.forceActiveFocus();
});
} }
FocusScope { FocusScope {

View File

@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.I3
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
import qs.Common import qs.Common
@@ -23,6 +24,31 @@ Singleton {
readonly property string labwcPid: Quickshell.env("LABWC_PID") readonly property string labwcPid: Quickshell.env("LABWC_PID")
property bool useNiriSorting: isNiri && NiriService property bool useNiriSorting: isNiri && NiriService
readonly property string focusedScreenName: {
if (isHyprland && Hyprland.focusedMonitor)
return Hyprland.focusedMonitor.name;
if (isNiri && NiriService.currentOutput)
return NiriService.currentOutput;
if (isDwl && DwlService.activeOutput)
return DwlService.activeOutput;
if (isSway) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
if (focusedWs?.monitor?.name)
return focusedWs.monitor.name;
}
return Quickshell.screens[0]?.name ?? "";
}
readonly property var focusedScreen: {
if (!focusedScreenName)
return Quickshell.screens[0] ?? null;
for (const s of Quickshell.screens) {
if (s.name === focusedScreenName)
return s;
}
return Quickshell.screens[0] ?? null;
}
property var sortedToplevels: [] property var sortedToplevels: []
property bool _sortScheduled: false property bool _sortScheduled: false

View File

@@ -666,11 +666,7 @@ Singleton {
return; return;
} }
if (SessionData.nightModeAutoEnabled) { evaluateNightMode();
startAutomation();
} else {
applyNightModeDirectly();
}
}); });
} }
} }
@@ -680,7 +676,7 @@ Singleton {
Timer { Timer {
id: restartTimer id: restartTimer
property string nextAction: "" property string nextAction: ""
interval: 100 interval: 250
repeat: false repeat: false
onTriggered: { onTriggered: {