fedora
This commit is contained in:
+426
@@ -0,0 +1,426 @@
|
||||
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
|
||||
|
||||
BackgroundEffect.blurRegion: Settings.data.general.enableBlurBehind ? launcherBlurRegion : null
|
||||
Region {
|
||||
id: launcherBlurRegion
|
||||
|
||||
Region {
|
||||
x: Math.round(launcherPanel.x)
|
||||
y: Math.round(launcherPanel.y)
|
||||
width: Math.round(launcherPanel.width)
|
||||
height: Math.round(launcherPanel.height)
|
||||
radius: Style.radiusL
|
||||
topLeftCorner: launcherPanel.topLeftCornerState
|
||||
topRightCorner: launcherPanel.topRightCornerState
|
||||
bottomLeftCorner: launcherPanel.bottomLeftCornerState
|
||||
bottomRightCorner: launcherPanel.bottomRightCornerState
|
||||
}
|
||||
|
||||
Region {
|
||||
x: Math.round(previewBox.visible ? previewBox.x : 0)
|
||||
y: Math.round(previewBox.visible ? previewBox.y : 0)
|
||||
width: Math.round(previewBox.visible ? previewBox.width : 0)
|
||||
height: Math.round(previewBox.visible ? previewBox.height : 0)
|
||||
radius: Style.radiusL
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Preview panel support
|
||||
readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio)
|
||||
readonly property int previewPanelWidth: Math.round(400 * Style.uiScaleRatio)
|
||||
readonly property bool previewActive: {
|
||||
if (!launcherCore)
|
||||
return false;
|
||||
var provider = launcherCore.activeProvider;
|
||||
if (!provider || !provider.hasPreview)
|
||||
return false;
|
||||
if (!Settings.data.appLauncher.enableClipPreview)
|
||||
return false;
|
||||
return launcherCore.selectedIndex >= 0 && launcherCore.results && !!launcherCore.results[launcherCore.selectedIndex];
|
||||
}
|
||||
|
||||
// Dimmer background (click to close)
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.general.dimmerOpacity)
|
||||
|
||||
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(Math.max(parent.width * 0.25, launcherWindow.listPanelWidth + Style.margin2L * 2))
|
||||
height: Math.round(Math.max(parent.height * 0.5, 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
|
||||
visible: panelW > 0 && panelH > 0
|
||||
opacity: launcherPanel.opacity
|
||||
layer.enabled: true
|
||||
|
||||
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: Qt.alpha(Color.mSurface, Color.adaptiveOpacity(Settings.data.ui.panelBackgroundOpacity))
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Preview Panel - positioned as sibling of launcherPanel to avoid shadow bleed
|
||||
NDropShadow {
|
||||
source: previewBox
|
||||
anchors.fill: previewBox
|
||||
autoPaddingEnabled: true
|
||||
visible: previewBox.visible
|
||||
z: previewBox.z - 1
|
||||
}
|
||||
|
||||
NBox {
|
||||
id: previewBox
|
||||
visible: launcherWindow.previewActive
|
||||
width: launcherWindow.previewPanelWidth
|
||||
height: Math.round(400 * Style.uiScaleRatio)
|
||||
forceOpaque: true
|
||||
x: {
|
||||
if (panelPosition.endsWith("_right"))
|
||||
return launcherPanel.x - launcherWindow.previewPanelWidth - Style.marginM;
|
||||
return launcherPanel.x + launcherPanel.width + Style.marginM;
|
||||
}
|
||||
y: {
|
||||
var view = launcherCore.resultsView;
|
||||
if (!view)
|
||||
return launcherPanel.y + Style.marginL;
|
||||
var row = launcherCore.isGridView ? Math.floor(launcherCore.selectedIndex / launcherCore.gridColumns) : launcherCore.selectedIndex;
|
||||
var gridCellSize = Math.floor((launcherWindow.listPanelWidth - (2 * Style.marginXS) - ((launcherCore.targetGridColumns - 1) * Style.marginS)) / launcherCore.targetGridColumns);
|
||||
var itemHeight = launcherCore.isGridView ? (gridCellSize + Style.marginXXS) : (launcherCore.entryHeight + (view.spacing || 0));
|
||||
var yPos = row * itemHeight - (view.contentY || 0);
|
||||
var mapped = view.mapToItem(launcherWindow.contentItem, 0, yPos);
|
||||
return Math.max(launcherPanel.y + Style.marginL, Math.min(mapped.y, launcherPanel.y + launcherPanel.height - previewBox.height - Style.marginL));
|
||||
}
|
||||
|
||||
opacity: visible ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: previewLoader
|
||||
anchors.fill: parent
|
||||
active: launcherWindow.previewActive
|
||||
source: {
|
||||
if (!active)
|
||||
return "";
|
||||
var provider = launcherCore.activeProvider;
|
||||
if (provider && provider.previewComponentPath)
|
||||
return provider.previewComponentPath;
|
||||
return "";
|
||||
}
|
||||
|
||||
onLoaded: updatePreviewItem()
|
||||
onItemChanged: updatePreviewItem()
|
||||
|
||||
function updatePreviewItem() {
|
||||
if (!item || launcherCore.selectedIndex < 0 || !launcherCore.results[launcherCore.selectedIndex])
|
||||
return;
|
||||
var provider = launcherCore.activeProvider;
|
||||
if (provider && provider.getPreviewData) {
|
||||
item.currentItem = provider.getPreviewData(launcherCore.results[launcherCore.selectedIndex]);
|
||||
} else {
|
||||
item.currentItem = launcherCore.results[launcherCore.selectedIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update preview when selection changes
|
||||
Connections {
|
||||
target: launcherCore
|
||||
function onSelectedIndexChanged() {
|
||||
if (previewLoader.item)
|
||||
previewLoader.updatePreviewItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user