mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge branch 'launcher-overview-support'
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user