1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-04 04:42:05 -04: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

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,19 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property string layerNamespace: "dms:modal"
property alias content: contentLoader.sourceComponent
property alias contentLoader: contentLoader
property Component content: 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 modalHeight: 300
property var targetScreen
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 var targetScreen: null
property bool showBackground: true
property real backgroundOpacity: 0.5
property string positioning: "center"
@@ -36,7 +31,6 @@ Item {
property real borderWidth: 1
property real cornerRadius: Theme.cornerRadius
property bool enableShadow: false
property alias modalFocusScope: focusScope
property bool shouldBeVisible: false
property bool shouldHaveFocus: shouldBeVisible
property bool allowFocusOverride: false
@@ -45,48 +39,41 @@ Item {
property bool keepPopoutsOpen: false
property var customKeyboardFocus: null
property bool useOverlayLayer: false
readonly property alias contentWindow: contentWindow
readonly property alias backgroundWindow: backgroundWindow
signal opened
signal dialogClosed
signal backgroundClicked
property bool animationsEnabled: true
readonly property bool useBackgroundWindow: true
onBackgroundClicked: {
if (closeOnBackgroundClick)
close();
}
function open() {
ModalManager.openModal(root);
closeTimer.stop();
shouldBeVisible = true;
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = true;
Qt.callLater(() => {
contentWindow.visible = true;
shouldHaveFocus = false;
Qt.callLater(() => {
shouldHaveFocus = Qt.binding(() => shouldBeVisible);
});
});
shouldHaveFocus = true;
DankModalWindow.showModal(root);
opened();
}
function openCentered() {
positioning = "center";
open();
}
function close() {
shouldBeVisible = false;
shouldHaveFocus = false;
closeTimer.restart();
DankModalWindow.hideModal();
dialogClosed();
}
function instantClose() {
animationsEnabled = false;
shouldBeVisible = false;
shouldHaveFocus = false;
closeTimer.stop();
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = false;
DankModalWindow.hideModalInstant();
dialogClosed();
Qt.callLater(() => animationsEnabled = true);
}
function toggle() {
@@ -96,322 +83,9 @@ Item {
Connections {
target: ModalManager
function onCloseAllModalsExcept(excludedModal) {
if (excludedModal !== root && !allowStacking && shouldBeVisible) {
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;
}
}
if (excludedModal === root || allowStacking || !shouldBeVisible)
return;
close();
}
}
}

View File

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

View File

@@ -1,5 +1,4 @@
import QtQuick
import Quickshell.Hyprland
import qs.Common
import qs.Modals.Common
import qs.Services
@@ -17,12 +16,6 @@ DankModal {
modalWidth: _maxW
modalHeight: _maxH
onBackgroundClicked: close()
onOpened: () => Qt.callLater(() => modalFocusScope.forceActiveFocus())
HyprlandFocusGrab {
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
function scrollDown() {
if (!root.activeFlickable)
@@ -40,25 +33,35 @@ DankModal {
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 {
Item {
FocusScope {
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 {
anchors.fill: parent

View File

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

View File

@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Hyprland
import qs.Common
import qs.Modals.Common
import qs.Services
@@ -13,11 +12,6 @@ DankModal {
layerNamespace: "dms:power-menu"
keepPopoutsOpen: true
HyprlandFocusGrab {
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
property int selectedIndex: 0
property int selectedRow: 0
property int selectedCol: 0
@@ -275,34 +269,11 @@ DankModal {
} else {
selectedIndex = defaultIndex;
}
Qt.callLater(() => modalFocusScope.forceActiveFocus());
}
onDialogClosed: () => {
cancelHold();
}
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) {
if (!isPressed) {
@@ -481,10 +452,33 @@ DankModal {
}
content: Component {
Item {
FocusScope {
anchors.fill: parent
focus: true
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 {
id: buttonGrid
visible: SettingsData.powerMenuGridLayout

View File

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

View File

@@ -1,7 +1,6 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Modals.Spotlight
@@ -11,54 +10,66 @@ PanelWindow {
WlrLayershell.namespace: "dms:spotlight-context-menu"
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
property var appLauncher: null
property var parentHandler: null
property var parentModal: null
property real menuPositionX: 0
property real menuPositionY: 0
readonly property real shadowBuffer: 5
screen: parentModal?.effectiveScreen
screen: DankModalWindow.targetScreen
function show(x, y, app, fromKeyboard) {
fromKeyboard = fromKeyboard || false;
menuContent.currentApp = app;
let screenX = x;
let screenY = y;
if (parentModal) {
if (fromKeyboard) {
screenX = x + parentModal.alignedX;
screenY = y + parentModal.alignedY;
} else {
screenX = x + (parentModal.alignedX - shadowBuffer);
screenY = y + (parentModal.alignedY - shadowBuffer);
}
const modalX = DankModalWindow.modalX;
const modalY = DankModalWindow.modalY;
if (fromKeyboard) {
screenX = x + modalX;
screenY = y + modalY;
} else {
screenX = x + (modalX - shadowBuffer);
screenY = y + (modalY - shadowBuffer);
}
menuPositionX = screenX;
menuPositionY = screenY;
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
menuContent.keyboardNavigation = true;
visible = true;
if (parentHandler) {
parentHandler.enabled = false;
}
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;
}
Qt.callLater(() => {
menuContent.keyboardHandler.forceActiveFocus();
});
}
function hide() {
if (parentHandler) {
parentHandler.enabled = true;
}
visible = false;
}
@@ -71,11 +82,6 @@ PanelWindow {
bottom: true
}
onVisibleChanged: {
if (!visible && parentHandler) {
parentHandler.enabled = true;
}
}
SpotlightContextMenuContent {
id: menuContent

View File

@@ -8,51 +8,57 @@ Popup {
id: root
property var appLauncher: null
property var parentHandler: null
property var searchField: null
function show(x, y, app, fromKeyboard) {
fromKeyboard = fromKeyboard || false;
menuContent.currentApp = app;
root.x = x + 4;
root.y = y + 4;
menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1;
menuContent.keyboardNavigation = true;
if (parentHandler) {
parentHandler.enabled = false;
}
open();
}
onOpened: {
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() {
if (parentHandler) {
parentHandler.enabled = true;
}
close();
}
width: menuContent.implicitWidth
height: menuContent.implicitHeight
padding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
closePolicy: Popup.CloseOnPressOutside
modal: true
dim: false
background: Item {}
onClosed: {
if (parentHandler) {
parentHandler.enabled = true;
}
if (searchField) {
Qt.callLater(() => {
searchField.forceActiveFocus();

View File

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