mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
183 lines
6.5 KiB
QML
183 lines
6.5 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
|
|
import qs.Commons
|
|
import qs.Modules.MainScreen
|
|
import qs.Services.UI
|
|
import qs.Widgets
|
|
|
|
SmartPanel {
|
|
id: root
|
|
|
|
// Disable when overlay mode is enabled (LauncherOverlayWindow handles it)
|
|
enabled: !Settings.data.appLauncher.overviewLayer
|
|
visible: !Settings.data.appLauncher.overviewLayer
|
|
|
|
// Reference to core (set after panelContent loads)
|
|
property var launcherCoreRef: null
|
|
|
|
// Expose core launcher for external access (e.g., IPC)
|
|
readonly property string searchText: launcherCoreRef ? launcherCoreRef.searchText : ""
|
|
readonly property int selectedIndex: launcherCoreRef ? launcherCoreRef.selectedIndex : 0
|
|
readonly property var results: launcherCoreRef ? launcherCoreRef.results : []
|
|
readonly property var activeProvider: launcherCoreRef ? launcherCoreRef.activeProvider : null
|
|
readonly property var currentProvider: launcherCoreRef ? launcherCoreRef.currentProvider : null
|
|
readonly property bool isGridView: launcherCoreRef ? launcherCoreRef.isGridView : false
|
|
readonly property int gridColumns: launcherCoreRef ? launcherCoreRef.gridColumns : 5
|
|
|
|
function setSearchText(text) {
|
|
if (launcherCoreRef)
|
|
launcherCoreRef.setSearchText(text);
|
|
}
|
|
|
|
// Preview panel support
|
|
readonly property bool previewActive: {
|
|
if (!launcherCoreRef)
|
|
return false;
|
|
var provider = launcherCoreRef.activeProvider;
|
|
if (!provider || !provider.hasPreview)
|
|
return false;
|
|
if (!Settings.data.appLauncher.enableClipPreview)
|
|
return false;
|
|
return selectedIndex >= 0 && results && !!results[selectedIndex];
|
|
}
|
|
readonly property int previewPanelWidth: Math.round(400 * Style.uiScaleRatio)
|
|
|
|
// Panel sizing
|
|
readonly property int listPanelWidth: Math.round(500 * Style.uiScaleRatio)
|
|
readonly property int totalBaseWidth: listPanelWidth + Style.margin2L
|
|
|
|
preferredWidth: totalBaseWidth
|
|
preferredHeight: Math.round(600 * Style.uiScaleRatio)
|
|
preferredWidthRatio: 0.25
|
|
preferredHeightRatio: 0.5
|
|
|
|
// Positioning
|
|
readonly property string screenBarPosition: Settings.getBarPositionForScreen(screen?.name)
|
|
readonly property string panelPosition: {
|
|
if (Settings.data.appLauncher.position === "follow_bar") {
|
|
if (screenBarPosition === "left" || screenBarPosition === "right") {
|
|
return `center_${screenBarPosition}`;
|
|
} else {
|
|
return `${screenBarPosition}_center`;
|
|
}
|
|
} else {
|
|
return Settings.data.appLauncher.position;
|
|
}
|
|
}
|
|
panelAnchorHorizontalCenter: !root.useButtonPosition && (panelPosition === "center" || panelPosition.endsWith("_center"))
|
|
panelAnchorVerticalCenter: !root.useButtonPosition && panelPosition === "center"
|
|
panelAnchorLeft: !root.useButtonPosition && panelPosition !== "center" && panelPosition.endsWith("_left")
|
|
panelAnchorRight: !root.useButtonPosition && panelPosition !== "center" && panelPosition.endsWith("_right")
|
|
panelAnchorBottom: !root.useButtonPosition && panelPosition.startsWith("bottom_")
|
|
panelAnchorTop: !root.useButtonPosition && panelPosition.startsWith("top_")
|
|
|
|
panelContent: Rectangle {
|
|
id: ui
|
|
color: "transparent"
|
|
opacity: launcherCore.resultsReady ? 1.0 : 0.0
|
|
|
|
Component.onCompleted: root.launcherCoreRef = launcherCore
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationFast
|
|
easing.type: Easing.OutCirc
|
|
}
|
|
}
|
|
|
|
// Preview Panel (external) - uses provider's preview component
|
|
NDropShadow {
|
|
source: previewBox
|
|
anchors.fill: previewBox
|
|
autoPaddingEnabled: true
|
|
visible: previewBox.visible
|
|
z: previewBox.z - 1
|
|
}
|
|
|
|
NBox {
|
|
id: previewBox
|
|
visible: root.previewActive
|
|
width: root.previewPanelWidth
|
|
height: Math.round(400 * Style.uiScaleRatio)
|
|
forceOpaque: true // no blur for now
|
|
x: root.panelAnchorRight ? -(root.previewPanelWidth + Style.marginM) : ui.width + Style.marginM
|
|
y: {
|
|
var view = launcherCore.resultsView;
|
|
if (!view)
|
|
return Style.marginL;
|
|
var row = launcherCore.isGridView ? Math.floor(launcherCore.selectedIndex / launcherCore.gridColumns) : launcherCore.selectedIndex;
|
|
var gridCellSize = Math.floor((root.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(ui, 0, yPos);
|
|
return Math.max(Style.marginL, Math.min(mapped.y, ui.height - previewBox.height - Style.marginL));
|
|
}
|
|
z: -1
|
|
|
|
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: root.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];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Core launcher (state, providers, UI)
|
|
LauncherCore {
|
|
id: launcherCore
|
|
anchors.fill: parent
|
|
screen: root.screen
|
|
isOpen: root.isPanelOpen
|
|
onRequestClose: root.close()
|
|
// Defer so the signal emission completes before SmartPanel
|
|
// sets isPanelOpen=false and the contentLoader destroys us.
|
|
onRequestCloseImmediately: Qt.callLater(root.closeImmediately)
|
|
}
|
|
|
|
// Update preview when selection changes
|
|
Connections {
|
|
target: launcherCore
|
|
function onSelectedIndexChanged() {
|
|
if (previewLoader.item)
|
|
previewLoader.updatePreviewItem();
|
|
}
|
|
}
|
|
}
|
|
}
|