Merge branch 'launcher-overview-support'

This commit is contained in:
Lemmy
2026-02-08 11:30:52 -05:00
14 changed files with 2031 additions and 1664 deletions
+2 -2
View File
@@ -98,7 +98,7 @@ NIconButton {
PanelService.closeContextMenu(screen);
if (action === "open-launcher") {
PanelService.getPanel("launcherPanel", screen)?.toggle();
PanelService.toggleLauncher(screen);
} else if (action === "open-settings") {
var panel = PanelService.getPanel("settingsPanel", screen);
panel.requestedTab = SettingsPanel.Tab.General;
@@ -121,7 +121,7 @@ NIconButton {
onRightClicked: {
PanelService.showContextMenu(contextMenu, root, screen);
}
onMiddleClicked: PanelService.getPanel("launcherPanel", screen)?.toggle()
onMiddleClicked: PanelService.toggleLauncher(screen)
IconImage {
id: customOrDistroLogo
+2 -2
View File
@@ -75,8 +75,8 @@ NIconButton {
}
}
onClicked: PanelService.getPanel("launcherPanel", screen)?.toggle()
onMiddleClicked: PanelService.getPanel("launcherPanel", screen)?.toggle()
onClicked: PanelService.toggleLauncher(screen)
onMiddleClicked: PanelService.toggleLauncher(screen)
onRightClicked: {
PanelService.showContextMenu(contextMenu, root, screen);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,304 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Modules.MainScreen.Backgrounds
import qs.Services.UI
import qs.Widgets
// Standalone launcher window for Overlay layer mode.
// This window appears above fullscreen windows and does not attach to the bar.
Variants {
id: launcherVariants
model: Quickshell.screens.filter(screen => Settings.data.appLauncher.overviewLayer)
delegate: Loader {
id: windowLoader
required property ShellScreen modelData
active: PanelService.overlayLauncherOpen && PanelService.overlayLauncherScreen === modelData
sourceComponent: PanelWindow {
id: launcherWindow
screen: windowLoader.modelData
anchors {
top: true
bottom: true
left: true
right: true
}
color: "transparent"
WlrLayershell.namespace: "noctalia-launcher-overlay-" + (screen?.name || "unknown")
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusionMode: ExclusionMode.Ignore
// Positioning logic (respects settings but doesn't attach to bar)
readonly property string barPosition: Settings.data.bar.position
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property int barThickness: Math.round(Style.barHeight + Style.marginL)
readonly property string panelPosition: {
var pos = Settings.data.appLauncher.position;
if (pos === "follow_bar") {
if (barIsVertical) {
return "center_" + barPosition;
} else {
return barPosition + "_center";
}
}
return pos;
}
// Dimmer background (click to close)
Rectangle {
anchors.fill: parent
color: Qt.alpha(Color.mSurface, Settings.data.general.dimmerOpacity || 0.2)
MouseArea {
anchors.fill: parent
onClicked: PanelService.closeOverlayLauncher()
}
}
// Shadow for launcher panel
NDropShadow {
source: launcherPanel
anchors.fill: launcherPanel
autoPaddingEnabled: true
}
// Launcher panel with position-based anchoring
Item {
id: launcherPanel
width: Math.round(500 * Style.uiScaleRatio) + (Style.marginL * 2)
height: Math.round(600 * Style.uiScaleRatio)
clip: false
// Entrance animation
opacity: 0
transformOrigin: {
if (touchingTop && touchingLeft)
return Item.TopLeft;
if (touchingTop && touchingRight)
return Item.TopRight;
if (touchingBottom && touchingLeft)
return Item.BottomLeft;
if (touchingBottom && touchingRight)
return Item.BottomRight;
if (touchingTop)
return Item.Top;
if (touchingBottom)
return Item.Bottom;
if (touchingLeft)
return Item.Left;
if (touchingRight)
return Item.Right;
return Item.Center;
}
Component.onCompleted: {
opacity = 1;
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
// Horizontal positioning
anchors.horizontalCenter: (panelPosition === "center" || panelPosition.endsWith("_center")) ? parent.horizontalCenter : undefined
anchors.left: panelPosition.endsWith("_left") ? parent.left : undefined
anchors.right: panelPosition.endsWith("_right") ? parent.right : undefined
// Vertical positioning
anchors.verticalCenter: (panelPosition === "center" || panelPosition.startsWith("center_")) ? parent.verticalCenter : undefined
anchors.top: panelPosition.startsWith("top_") ? parent.top : undefined
anchors.bottom: panelPosition.startsWith("bottom_") ? parent.bottom : undefined
// Margins - only add bar clearance on the bar's edge
anchors.leftMargin: barPosition === "left" ? barThickness : 0
anchors.rightMargin: barPosition === "right" ? barThickness : 0
anchors.topMargin: barPosition === "top" ? barThickness : 0
anchors.bottomMargin: barPosition === "bottom" ? barThickness : 0
// Edge detection - based on position setting and bar location
readonly property bool touchingLeft: panelPosition.endsWith("_left") && barPosition !== "left"
readonly property bool touchingRight: panelPosition.endsWith("_right") && barPosition !== "right"
readonly property bool touchingTop: panelPosition.startsWith("top_") && barPosition !== "top"
readonly property bool touchingBottom: panelPosition.startsWith("bottom_") && barPosition !== "bottom"
// Corner states based on edge touching
// State 0: Normal rounded, State 1: Horizontal inversion, State 2: Vertical inversion
readonly property int topLeftCornerState: {
if (touchingLeft && touchingTop)
return 0;
if (touchingLeft)
return 2;
if (touchingTop)
return 1;
return 0;
}
readonly property int topRightCornerState: {
if (touchingRight && touchingTop)
return 0;
if (touchingRight)
return 2;
if (touchingTop)
return 1;
return 0;
}
readonly property int bottomLeftCornerState: {
if (touchingLeft && touchingBottom)
return 0;
if (touchingLeft)
return 2;
if (touchingBottom)
return 1;
return 0;
}
readonly property int bottomRightCornerState: {
if (touchingRight && touchingBottom)
return 0;
if (touchingRight)
return 2;
if (touchingBottom)
return 1;
return 0;
}
// Background with inverted corners - extends beyond panel for inverted corners
Shape {
id: panelShape
// Extend shape to allow inverted corners to render outside panel bounds
x: -radius
y: -radius
width: launcherPanel.width + radius * 2
height: launcherPanel.height + radius * 2
opacity: launcherPanel.opacity
layer.enabled: true
layer.samples: 4
readonly property real radius: Style.radiusL
// Panel dimensions (for path calculations)
readonly property real panelW: launcherPanel.width
readonly property real panelH: launcherPanel.height
// Helper functions for corner rendering
function getMultX(state) {
return state === 1 ? -1 : 1;
}
function getMultY(state) {
return state === 2 ? -1 : 1;
}
function getArcDir(multX, multY) {
return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise;
}
readonly property real tlMultX: getMultX(launcherPanel.topLeftCornerState)
readonly property real tlMultY: getMultY(launcherPanel.topLeftCornerState)
readonly property real trMultX: getMultX(launcherPanel.topRightCornerState)
readonly property real trMultY: getMultY(launcherPanel.topRightCornerState)
readonly property real blMultX: getMultX(launcherPanel.bottomLeftCornerState)
readonly property real blMultY: getMultY(launcherPanel.bottomLeftCornerState)
readonly property real brMultX: getMultX(launcherPanel.bottomRightCornerState)
readonly property real brMultY: getMultY(launcherPanel.bottomRightCornerState)
ShapePath {
strokeWidth: -1
fillColor: Color.mSurface
// Offset by radius to account for Shape's extended bounds
startX: panelShape.radius + panelShape.radius * panelShape.tlMultX
startY: panelShape.radius
// Top edge
PathLine {
relativeX: panelShape.panelW - panelShape.radius * panelShape.tlMultX - panelShape.radius * panelShape.trMultX
relativeY: 0
}
// Top-right corner
PathArc {
relativeX: panelShape.radius * panelShape.trMultX
relativeY: panelShape.radius * panelShape.trMultY
radiusX: panelShape.radius
radiusY: panelShape.radius
direction: panelShape.getArcDir(panelShape.trMultX, panelShape.trMultY)
}
// Right edge
PathLine {
relativeX: 0
relativeY: panelShape.panelH - panelShape.radius * panelShape.trMultY - panelShape.radius * panelShape.brMultY
}
// Bottom-right corner
PathArc {
relativeX: -panelShape.radius * panelShape.brMultX
relativeY: panelShape.radius * panelShape.brMultY
radiusX: panelShape.radius
radiusY: panelShape.radius
direction: panelShape.getArcDir(panelShape.brMultX, panelShape.brMultY)
}
// Bottom edge
PathLine {
relativeX: -(panelShape.panelW - panelShape.radius * panelShape.brMultX - panelShape.radius * panelShape.blMultX)
relativeY: 0
}
// Bottom-left corner
PathArc {
relativeX: -panelShape.radius * panelShape.blMultX
relativeY: -panelShape.radius * panelShape.blMultY
radiusX: panelShape.radius
radiusY: panelShape.radius
direction: panelShape.getArcDir(panelShape.blMultX, panelShape.blMultY)
}
// Left edge
PathLine {
relativeX: 0
relativeY: -(panelShape.panelH - panelShape.radius * panelShape.blMultY - panelShape.radius * panelShape.tlMultY)
}
// Top-left corner
PathArc {
relativeX: panelShape.radius * panelShape.tlMultX
relativeY: -panelShape.radius * panelShape.tlMultY
radiusX: panelShape.radius
radiusY: panelShape.radius
direction: panelShape.getArcDir(panelShape.tlMultX, panelShape.tlMultY)
}
}
}
// Border
Rectangle {
anchors.fill: parent
color: "transparent"
radius: Style.radiusL
border.color: Style.boxBorderColor
border.width: Style.borderS
visible: !launcherPanel.touchingLeft && !launcherPanel.touchingRight && !launcherPanel.touchingTop && !launcherPanel.touchingBottom
}
LauncherCore {
id: launcherCore
anchors.fill: parent
screen: windowLoader.modelData
isOpen: true
onRequestClose: PanelService.closeOverlayLauncher()
onRequestCloseImmediately: PanelService.closeOverlayLauncherImmediately()
Component.onCompleted: PanelService.overlayLauncherCore = launcherCore
Component.onDestruction: PanelService.overlayLauncherCore = null
}
}
}
}
}
@@ -548,6 +548,11 @@ Item {
"_score": (score !== undefined ? score : 0),
"provider": root,
"onActivate": function () {
// Record usage before closing (provider may be destroyed after close)
if (Settings.data.appLauncher.sortByMostUsed) {
root.recordUsage(app);
}
// Close the launcher/SmartPanel immediately without any animations.
// Ensures we are not preventing the future focusing of the app
launcher.closeImmediately();
@@ -555,10 +560,6 @@ Item {
// Defer execution to next event loop iteration to ensure panel is fully closed
Qt.callLater(() => {
Logger.d("ApplicationsProvider", `Launching: ${app.name}`);
// Record usage and persist asynchronously
if (Settings.data.appLauncher.sortByMostUsed) {
recordUsage(app);
}
if (Settings.data.appLauncher.customLaunchPrefixEnabled && Settings.data.appLauncher.customLaunchPrefix) {
// Use custom launch prefix
@@ -55,6 +55,14 @@ ColumnLayout {
defaultValue: Settings.getDefaultValue("appLauncher.position")
}
NToggle {
label: I18n.tr("panels.launcher.settings-overlay-layer-label")
description: I18n.tr("panels.launcher.settings-overlay-layer-description")
checked: Settings.data.appLauncher.overviewLayer
onToggled: checked => Settings.data.appLauncher.overviewLayer = checked
defaultValue: Settings.getDefaultValue("appLauncher.overviewLayer")
}
NToggle {
label: I18n.tr("tooltips.grid-view")
description: I18n.tr("panels.launcher.settings-grid-view-description")