diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 2c312a79a..d497154ec 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -520,6 +520,7 @@ Singleton { property var appDrawerSectionViewModes: ({}) onAppDrawerSectionViewModesChanged: saveSettings() property bool niriOverviewOverlayEnabled: true + property string niriOverviewLauncherStyle: "full" property string dankLauncherV2Size: "compact" property bool dankLauncherV2ShowSourceBadges: true property bool dankLauncherV2BorderEnabled: false diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index c53f8c9ad..6f0778301 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -251,6 +251,7 @@ var SPEC = { spotlightSectionViewModes: { def: {} }, appDrawerSectionViewModes: { def: {} }, niriOverviewOverlayEnabled: { def: true }, + niriOverviewLauncherStyle: { def: "full" }, dankLauncherV2Size: { def: "compact" }, dankLauncherV2ShowSourceBadges: { def: true }, dankLauncherV2BorderEnabled: { def: false }, diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml index b5f6bb126..04d6aef2b 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml @@ -120,18 +120,6 @@ Item { readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0 readonly property bool useSingleWindow: CompositorService.isHyprland || useBackgroundDarken - // Blur region isn't auto-committed on geometry changes; kick twice to catch resize settling. - function _kickBlurCommit() { - launcherBlur.kick(); - Qt.callLater(launcherBlur.kick); - } - - onAlignedXChanged: _kickBlurCommit() - onAlignedYChanged: _kickBlurCommit() - onAlignedWidthChanged: _kickBlurCommit() - on_ContentImplicitHChanged: _kickBlurCommit() - onContentVisibleChanged: _kickBlurCommit() - signal dialogClosed function _ensureContentLoadedAndInitialize(query, mode) { @@ -354,9 +342,6 @@ Item { blurRadius: root.cornerRadius } - onWidthChanged: root._kickBlurCommit() - onHeightChanged: root._kickBlurCommit() - WlrLayershell.namespace: "dms:spotlight" WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 @@ -441,9 +426,6 @@ Item { opacity: contentVisible ? 1 : 0 - onOpacityChanged: root._kickBlurCommit() - onSlideOffsetChanged: root._kickBlurCommit() - Behavior on opacity { NumberAnimation { duration: contentVisible ? root._openDuration : root._closeDuration diff --git a/quickshell/Modules/DankDash/DankDashPopout.qml b/quickshell/Modules/DankDash/DankDashPopout.qml index 4dfbd3e8a..89e6a263b 100644 --- a/quickshell/Modules/DankDash/DankDashPopout.qml +++ b/quickshell/Modules/DankDash/DankDashPopout.qml @@ -322,9 +322,13 @@ DankPopout { DankTabBar { id: tabBar + // Effective visibility is false while the popout window is unmapped, so gating + // height on `visible` collapses the bar between opens and resizes the surface + // mid-animation. Gate on the model instead. + readonly property bool hasTabs: (model?.length ?? 0) > 0 width: parent.width - height: visible ? 48 : 0 - visible: model.length > 0 + height: hasTabs ? 48 : 0 + visible: hasTabs currentIndex: root.currentTabIndex spacing: Theme.spacingS equalWidthTabs: true @@ -356,8 +360,8 @@ DankPopout { Item { width: parent.width - height: visible ? Theme.spacingXS : 0 - visible: tabBar.visible + height: tabBar.hasTabs ? Theme.spacingXS : 0 + visible: tabBar.hasTabs } Item { diff --git a/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml b/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml index 39d5ea8ce..7bbf6b25d 100644 --- a/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml +++ b/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml @@ -223,8 +223,9 @@ Rectangle { Rectangle { id: dankWarning width: parent.width - visible: CalendarService && CalendarService.dankNeedsLaunch - height: visible ? Math.max(28, warningRow.implicitHeight) + Theme.spacingS : 0 + readonly property bool showWarning: CalendarService?.dankNeedsLaunch ?? false + visible: showWarning + height: showWarning ? Math.max(28, warningRow.implicitHeight) + Theme.spacingS : 0 radius: Theme.cornerRadius color: Theme.warningHover border.color: Theme.withAlpha(Theme.warning, 0.35) diff --git a/quickshell/Modules/Settings/LauncherTab.qml b/quickshell/Modules/Settings/LauncherTab.qml index 45e779807..b6ef548d6 100644 --- a/quickshell/Modules/Settings/LauncherTab.qml +++ b/quickshell/Modules/Settings/LauncherTab.qml @@ -770,6 +770,20 @@ Item { checked: SettingsData.niriOverviewOverlayEnabled onToggled: checked => SettingsData.set("niriOverviewOverlayEnabled", checked) } + + SettingsButtonGroupRow { + visible: SettingsData.niriOverviewOverlayEnabled + settingKey: "niriOverviewLauncherStyle" + tags: ["launcher", "niri", "overview", "overlay", "style", "spotlight", "full"] + text: I18n.tr("Default Opens") + model: [I18n.tr("Full"), I18n.tr("Spotlight")] + currentIndex: SettingsData.niriOverviewLauncherStyle === "spotlight" ? 1 : 0 + onSelectionChanged: (index, selected) => { + if (!selected) + return; + SettingsData.set("niriOverviewLauncherStyle", index === 1 ? "spotlight" : "full"); + } + } } SettingsCard { diff --git a/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml b/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml index 03cb54134..6897f5a4d 100644 --- a/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml +++ b/quickshell/Modules/WorkspaceOverlays/NiriOverviewOverlay.qml @@ -101,6 +101,23 @@ Scope { readonly property bool overlayVisible: NiriService.inOverview || niriOverviewScope.isClosing property bool hasActivePopout: !!PopoutManager.currentPopoutsByScreen[screen.name] property bool hasActiveModal: !!ModalManager.currentModalsByScreen[screen.name] + readonly property bool useSpotlightStyle: SettingsData.niriOverviewLauncherStyle === "spotlight" + readonly property var launcherContent: useSpotlightStyle ? spotlightLauncherLoader.item : fullLauncherLoader.item + + readonly property QtObject fakeParentModal: QtObject { + readonly property bool spotlightOpen: spotlightContainer.visible + readonly property bool isClosing: niriOverviewScope.isClosing + readonly property real alignedX: spotlightContainer.x + readonly property real alignedY: spotlightContainer.y + readonly property real screenHeight: overlayWindow.screen?.height ?? 1080 + function hide() { + if (niriOverviewScope.searchActive) { + niriOverviewScope.hideSpotlight(); + return; + } + NiriService.toggleOverview(); + } + } Connections { target: PopoutManager @@ -219,13 +236,13 @@ Scope { if (event.isAutoRepeat || !event.text) return; - if (!launcherContent?.searchField) + if (!overlayWindow.launcherContent?.searchField) return; const trimmedText = event.text.trim(); - launcherContent.searchField.text = trimmedText; - launcherContent.controller.setSearchQuery(trimmedText); + overlayWindow.launcherContent.searchField.text = trimmedText; + overlayWindow.launcherContent.controller.setSearchQuery(trimmedText); niriOverviewScope.showSpotlight(overlayWindow.screen.name); - Qt.callLater(() => launcherContent.searchField.forceActiveFocus()); + Qt.callLater(() => overlayWindow.launcherContent.searchField.forceActiveFocus()); event.accepted = true; } } @@ -237,7 +254,7 @@ Scope { // edge and slide in from beyond that edge. In any other mode the // spotlight stays centered — identical to master. readonly property string connectedEmergeSide: SettingsData.frameLauncherEmergeSide || "bottom" - readonly property real _centerY: (parent.height - height) / 2 + readonly property real _centerY: overlayWindow.useSpotlightStyle ? Math.max(0, parent.height * 0.33 - 28) : (parent.height - height) / 2 readonly property real _connectedRestY: { if (!Theme.isConnectedEffect || !overlayWindow.screen) return _centerY; @@ -277,8 +294,8 @@ Scope { return 600; } } - width: Math.min(baseWidth, overlayWindow.screen.width - 100) - height: Math.min(baseHeight, overlayWindow.screen.height - 100) + width: overlayWindow.useSpotlightStyle ? Math.min(680, overlayWindow.screen.width - 80) : Math.min(baseWidth, overlayWindow.screen.width - 100) + height: overlayWindow.useSpotlightStyle ? Math.ceil(overlayWindow.launcherContent?.implicitHeight ?? 56) : Math.min(baseHeight, overlayWindow.screen.height - 100) readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen @@ -344,57 +361,51 @@ Scope { anchors.fill: parent focus: true - Keys.onPressed: event => launcherContent.activeContextMenu?.handleKey(event) + Keys.onPressed: event => overlayWindow.launcherContent?.activeContextMenu?.handleKey(event) Keys.onEscapePressed: event => { - launcherContent.activeContextMenu?.handleKey(event); + overlayWindow.launcherContent?.activeContextMenu?.handleKey(event); if (!event.accepted) - launcherContent.parentModal?.hide(); + overlayWindow.launcherContent?.parentModal?.hide(); event.accepted = true; } - LauncherContent { - id: launcherContent + Loader { + id: fullLauncherLoader anchors.fill: parent - anchors.margins: 0 - - property var fakeParentModal: QtObject { - property bool spotlightOpen: spotlightContainer.visible - property bool isClosing: niriOverviewScope.isClosing - property real alignedX: spotlightContainer.x - property real alignedY: spotlightContainer.y - function hide() { - if (niriOverviewScope.searchActive) { - niriOverviewScope.hideSpotlight(); - return; - } - NiriService.toggleOverview(); - } + active: !overlayWindow.useSpotlightStyle + sourceComponent: LauncherContent { + parentModal: overlayWindow.fakeParentModal } + } - Connections { - target: launcherContent.searchField - function onTextChanged() { - if (launcherContent.searchField.text.length > 0 || !niriOverviewScope.searchActive) - return; - niriOverviewScope.hideSpotlight(); - } + Loader { + id: spotlightLauncherLoader + anchors.fill: parent + active: overlayWindow.useSpotlightStyle + sourceComponent: SpotlightLauncherContent { + parentModal: overlayWindow.fakeParentModal } + } - Component.onCompleted: { - parentModal = fakeParentModal; + Connections { + target: overlayWindow.launcherContent?.searchField ?? null + function onTextChanged() { + if (overlayWindow.launcherContent.searchField.text.length > 0 || !niriOverviewScope.searchActive) + return; + niriOverviewScope.hideSpotlight(); } + } - Connections { - target: launcherContent.controller - function onItemExecuted() { - niriOverviewScope.releaseKeyboard = true; - } - function onModeChanged(mode) { - if (launcherContent.controller.autoSwitchedToFiles) - return; - SessionData.setNiriOverviewLastMode(mode); - } + Connections { + target: overlayWindow.launcherContent?.controller ?? null + function onItemExecuted() { + niriOverviewScope.releaseKeyboard = true; + } + function onModeChanged(mode) { + if (overlayWindow.launcherContent.controller.autoSwitchedToFiles) + return; + SessionData.setNiriOverviewLastMode(mode); } } } diff --git a/quickshell/Widgets/WindowBlur.qml b/quickshell/Widgets/WindowBlur.qml index de337ac0e..564128626 100644 --- a/quickshell/Widgets/WindowBlur.qml +++ b/quickshell/Widgets/WindowBlur.qml @@ -60,6 +60,42 @@ Item { targetWindow.BackgroundEffect.blurRegion = _active ? blurRegion : null; } + // Republish after geometry settles, plus one trailing pass. + onBlurXChanged: settleKickAction.restart() + onBlurYChanged: settleKickAction.restart() + onBlurWidthChanged: settleKickAction.restart() + onBlurHeightChanged: settleKickAction.restart() + onBlurRadiusChanged: settleKickAction.restart() + onClipEnabledChanged: settleKickAction.restart() + onClipXChanged: settleKickAction.restart() + onClipYChanged: settleKickAction.restart() + onClipWidthChanged: settleKickAction.restart() + onClipHeightChanged: settleKickAction.restart() + + function _runSettleKick() { + if (!targetWindow?.visible) + return; + kick(); + settleRepeatTimer.restart(); + } + + DeferredAction { + id: settleKickAction + interval: 16 + onTriggered: root._runSettleKick() + } + + Timer { + id: settleRepeatTimer + interval: 96 + repeat: false + onTriggered: { + if (!root.targetWindow?.visible) + return; + root.kick(); + } + } + function _scheduleLifecycleKick() { lifecycleKickAction.restart(); } @@ -98,6 +134,12 @@ Item { else root._clear(); } + function onWidthChanged() { + settleKickAction.restart(); + } + function onHeightChanged() { + settleKickAction.restart(); + } function onResourcesLost() { root._clear(); // Re-arm so blur self-heals since instances are no longer recreated per open diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 09a11dad0..7d4ca0d84 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -2398,6 +2398,26 @@ "style" ] }, + { + "section": "niriOverviewLauncherStyle", + "label": "Default Opens", + "tabIndex": 9, + "category": "Launcher", + "keywords": [ + "default", + "drawer", + "full", + "launcher", + "menu", + "niri", + "opens", + "overlay", + "overview", + "spotlight", + "start", + "style" + ] + }, { "section": "niriOverviewOverlayEnabled", "label": "Enable Overview Overlay",