mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-06-03 19:01:50 +00:00
Added pinning to dock & right click menu to dock
Dock: display pinned apps on the left even when not running (lower opacity) DockMenu: Let users close, activate and pin/unpin apps Settings: add pinned list for docks
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"settingsVersion": 3,
|
||||
"settingsVersion": 4,
|
||||
"bar": {
|
||||
"position": "top",
|
||||
"backgroundOpacity": 1,
|
||||
@@ -116,7 +116,8 @@
|
||||
"exclusive": false,
|
||||
"backgroundOpacity": 1,
|
||||
"floatingRatio": 1,
|
||||
"monitors": []
|
||||
"monitors": [],
|
||||
"pinnedApps": []
|
||||
},
|
||||
"network": {
|
||||
"wifiEnabled": true,
|
||||
@@ -125,6 +126,7 @@
|
||||
"notifications": {
|
||||
"doNotDisturb": false,
|
||||
"monitors": [],
|
||||
"location": "top_right",
|
||||
"lastSeenTs": 0,
|
||||
"lowUrgencyDuration": 3,
|
||||
"normalUrgencyDuration": 8,
|
||||
@@ -149,7 +151,7 @@
|
||||
},
|
||||
"colorSchemes": {
|
||||
"useWallpaperColors": false,
|
||||
"predefinedScheme": "",
|
||||
"predefinedScheme": "Noctalia (default)",
|
||||
"darkMode": true
|
||||
},
|
||||
"matugen": {
|
||||
|
||||
@@ -113,7 +113,7 @@ Singleton {
|
||||
JsonAdapter {
|
||||
id: adapter
|
||||
|
||||
property int settingsVersion: 3
|
||||
property int settingsVersion: 4
|
||||
|
||||
// bar
|
||||
property JsonObject bar: JsonObject {
|
||||
@@ -233,6 +233,8 @@ Singleton {
|
||||
property real backgroundOpacity: 1.0
|
||||
property real floatingRatio: 1.0
|
||||
property list<string> monitors: []
|
||||
// Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop")
|
||||
property list<string> pinnedApps: []
|
||||
}
|
||||
|
||||
// network
|
||||
|
||||
+194
-16
@@ -13,6 +13,7 @@ Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: Item {
|
||||
id: root
|
||||
required property ShellScreen modelData
|
||||
property real scaling: ScalingService.getScreenScale(modelData)
|
||||
|
||||
@@ -25,6 +26,47 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// Update dock apps when toplevels change
|
||||
Connections {
|
||||
target: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
function onValuesChanged() {
|
||||
Logger.log("Dock", "ToplevelManager.toplevels.onValuesChanged triggered")
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Also listen to model changes (for ObjectModel)
|
||||
Connections {
|
||||
target: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
function onCountChanged() {
|
||||
Logger.log("Dock", "ToplevelManager.toplevels.onCountChanged triggered, count:", ToplevelManager.toplevels.count)
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Update dock apps when pinned apps change
|
||||
Connections {
|
||||
target: Settings.data.dock
|
||||
function onPinnedAppsChanged() {
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Initial update when component is ready
|
||||
Component.onCompleted: {
|
||||
if (Settings.isLoaded && ToplevelManager) {
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Update when Settings are loaded
|
||||
Connections {
|
||||
target: Settings
|
||||
function onSettingsLoaded() {
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Shared properties between peek and dock windows
|
||||
readonly property bool autoHide: Settings.data.dock.autoHide
|
||||
readonly property int hideDelay: 500
|
||||
@@ -43,12 +85,78 @@ Variants {
|
||||
// Shared state between windows
|
||||
property bool dockHovered: false
|
||||
property bool anyAppHovered: false
|
||||
property bool menuHovered: false
|
||||
property bool hidden: autoHide
|
||||
property bool peekHovered: false
|
||||
|
||||
// Separate property to control Loader - stays true during animations
|
||||
property bool dockLoaded: !autoHide // Start loaded if autoHide is off
|
||||
|
||||
// Track the currently open context menu
|
||||
property var currentContextMenu: null
|
||||
|
||||
// Combined model of running apps and pinned apps
|
||||
property var dockApps: []
|
||||
|
||||
// Function to close any open context menu
|
||||
function closeAllContextMenus() {
|
||||
if (currentContextMenu && currentContextMenu.visible) {
|
||||
currentContextMenu.hide()
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update the combined dock apps model
|
||||
function updateDockApps() {
|
||||
const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : []
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || []
|
||||
const combined = []
|
||||
|
||||
Logger.log("Dock", "Updating dock apps. Running:", runningApps.length, "Pinned:", pinnedApps.length)
|
||||
|
||||
// First, add pinned apps (both running and non-running) in their pinned order
|
||||
pinnedApps.forEach(pinnedAppId => {
|
||||
const runningApp = runningApps.find(toplevel => toplevel && toplevel.appId === pinnedAppId)
|
||||
if (runningApp) {
|
||||
// Pinned app that is currently running
|
||||
combined.push({
|
||||
"type": "pinned-running",
|
||||
"toplevel": runningApp,
|
||||
"appId": runningApp.appId,
|
||||
"title": runningApp.title
|
||||
})
|
||||
Logger.log("Dock", "Added pinned-running app:", runningApp.appId)
|
||||
} else {
|
||||
// Pinned app that is not running
|
||||
combined.push({
|
||||
"type": "pinned",
|
||||
"toplevel": null,
|
||||
"appId": pinnedAppId,
|
||||
"title": pinnedAppId
|
||||
})
|
||||
Logger.log("Dock", "Added pinned app:", pinnedAppId)
|
||||
}
|
||||
})
|
||||
|
||||
// Then, add running apps that are not pinned
|
||||
runningApps.forEach(toplevel => {
|
||||
if (toplevel && toplevel.appId) {
|
||||
const isPinned = pinnedApps.includes(toplevel.appId)
|
||||
if (!isPinned) {
|
||||
combined.push({
|
||||
"type": "running",
|
||||
"toplevel": toplevel,
|
||||
"appId": toplevel.appId,
|
||||
"title": toplevel.title
|
||||
})
|
||||
Logger.log("Dock", "Added running app:", toplevel.appId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Logger.log("Dock", "Total dock apps:", combined.length)
|
||||
dockApps = combined
|
||||
}
|
||||
|
||||
// Timer to unload dock after hide animation completes
|
||||
Timer {
|
||||
id: unloadTimer
|
||||
@@ -65,7 +173,7 @@ Variants {
|
||||
id: hideTimer
|
||||
interval: hideDelay
|
||||
onTriggered: {
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered) {
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hidden = true
|
||||
unloadTimer.restart() // Start unload timer when hiding
|
||||
}
|
||||
@@ -137,7 +245,7 @@ Variants {
|
||||
|
||||
onExited: {
|
||||
peekHovered = false
|
||||
if (!hidden && !dockHovered && !anyAppHovered) {
|
||||
if (!hidden && !dockHovered && !anyAppHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
@@ -147,7 +255,7 @@ Variants {
|
||||
|
||||
// DOCK WINDOW
|
||||
Loader {
|
||||
active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && dockLoaded && ToplevelManager && (ToplevelManager.toplevels.values.length > 0)
|
||||
active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && dockLoaded && ToplevelManager && (dockApps.length > 0)
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: dockWindow
|
||||
@@ -235,10 +343,15 @@ Variants {
|
||||
|
||||
onExited: {
|
||||
dockHovered = false
|
||||
if (autoHide && !anyAppHovered && !peekHovered) {
|
||||
if (autoHide && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
// Close any open context menu when clicking on the dock background
|
||||
closeAllContextMenus()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -247,10 +360,10 @@ Variants {
|
||||
height: parent.height - (Style.marginM * 2 * scaling)
|
||||
anchors.centerIn: parent
|
||||
|
||||
function getAppIcon(toplevel: Toplevel): string {
|
||||
if (!toplevel)
|
||||
function getAppIcon(appData): string {
|
||||
if (!appData || !appData.appId)
|
||||
return ""
|
||||
return AppIcons.iconForAppId(toplevel.appId?.toLowerCase())
|
||||
return AppIcons.iconForAppId(appData.appId?.toLowerCase())
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -260,7 +373,7 @@ Variants {
|
||||
anchors.centerIn: parent
|
||||
|
||||
Repeater {
|
||||
model: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
model: dockApps
|
||||
|
||||
delegate: Item {
|
||||
id: appButton
|
||||
@@ -268,10 +381,20 @@ Variants {
|
||||
Layout.preferredHeight: iconSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
|
||||
property bool isActive: modelData.toplevel && ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData.toplevel
|
||||
property bool hovered: appMouseArea.containsMouse
|
||||
property string appId: modelData ? modelData.appId : ""
|
||||
property string appTitle: modelData ? modelData.title : ""
|
||||
property string appTitle: modelData ? (modelData.title || modelData.appId) : ""
|
||||
property bool isRunning: modelData && (modelData.type === "running" || modelData.type === "pinned-running")
|
||||
|
||||
// Listen for the toplevel being closed
|
||||
Connections {
|
||||
target: modelData?.toplevel
|
||||
function onClosed() {
|
||||
Logger.log("Dock", "Toplevel closed signal received for:", appButton.appId)
|
||||
Qt.callLater(root.updateDockApps)
|
||||
}
|
||||
}
|
||||
|
||||
// Individual tooltip for this app
|
||||
NTooltip {
|
||||
@@ -296,6 +419,9 @@ Variants {
|
||||
fillMode: Image.PreserveAspectFit
|
||||
cache: true
|
||||
|
||||
// Dim pinned apps that aren't running
|
||||
opacity: appButton.isRunning ? 1.0 : 0.6
|
||||
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
@@ -305,6 +431,13 @@ Variants {
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back if no icon
|
||||
@@ -314,6 +447,7 @@ Variants {
|
||||
icon: "question-mark"
|
||||
font.pointSize: iconSize * 0.7
|
||||
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
opacity: appButton.isRunning ? 1.0 : 0.6
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
@@ -323,6 +457,29 @@ Variants {
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context menu popup
|
||||
DockMenu {
|
||||
id: contextMenu
|
||||
scaling: root.scaling
|
||||
onHoveredChanged: menuHovered = hovered
|
||||
onRequestClose: contextMenu.hide()
|
||||
onAppClosed: root.updateDockApps // Force immediate dock update when app is closed
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
root.currentContextMenu = contextMenu
|
||||
} else if (root.currentContextMenu === contextMenu) {
|
||||
root.currentContextMenu = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -330,7 +487,7 @@ Variants {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
|
||||
onEntered: {
|
||||
anyAppHovered = true
|
||||
@@ -347,17 +504,38 @@ Variants {
|
||||
onExited: {
|
||||
anyAppHovered = false
|
||||
appTooltip.hide()
|
||||
if (autoHide && !dockHovered && !peekHovered) {
|
||||
if (autoHide && !dockHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.MiddleButton && modelData?.close) {
|
||||
modelData.close()
|
||||
// Close any existing context menu first
|
||||
if (mouse.button !== Qt.RightButton || root.currentContextMenu !== contextMenu) {
|
||||
root.closeAllContextMenus()
|
||||
}
|
||||
if (mouse.button === Qt.LeftButton && modelData?.activate) {
|
||||
modelData.activate()
|
||||
|
||||
// Check if toplevel is still valid (not a stale reference)
|
||||
const isValidToplevel = modelData?.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(modelData.toplevel)
|
||||
|
||||
if (mouse.button === Qt.MiddleButton && isValidToplevel && modelData.toplevel.close) {
|
||||
Logger.log("Dock", "Middle-click closing app:", modelData.appId)
|
||||
modelData.toplevel.close()
|
||||
Qt.callLater(root.updateDockApps) // Force immediate dock update
|
||||
} else if (mouse.button === Qt.LeftButton) {
|
||||
if (isValidToplevel && modelData.toplevel.activate) {
|
||||
// Running app - activate it
|
||||
Logger.log("Dock", "Activating running app:", modelData.appId)
|
||||
modelData.toplevel.activate()
|
||||
} else if (modelData?.appId) {
|
||||
// Pinned app not running - launch it
|
||||
Logger.log("Dock", "Launching pinned app:", modelData.appId)
|
||||
Quickshell.execDetached(["gtk-launch", modelData.appId])
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
// Hide tooltip when showing context menu
|
||||
appTooltip.hide()
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PopupWindow {
|
||||
id: root
|
||||
|
||||
property var toplevel: null
|
||||
property Item anchorItem: null
|
||||
property real scaling: 1.0
|
||||
property bool hovered: menuMouseArea.containsMouse || activateMouseArea.containsMouse || pinMouseArea.containsMouse || closeMouseArea.containsMouse
|
||||
property var onAppClosed: null // Callback function for when an app is closed
|
||||
|
||||
signal requestClose
|
||||
|
||||
implicitWidth: 160 * scaling
|
||||
implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * scaling * 2)
|
||||
color: Color.transparent
|
||||
visible: false
|
||||
|
||||
// Helper functions for pin/unpin functionality
|
||||
function isAppPinned(appId) {
|
||||
if (!appId)
|
||||
return false
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || []
|
||||
return pinnedApps.includes(appId)
|
||||
}
|
||||
|
||||
function toggleAppPin(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
|
||||
let pinnedApps = (Settings.data.dock.pinnedApps || []).slice() // Create a copy
|
||||
const isPinned = pinnedApps.includes(appId)
|
||||
|
||||
if (isPinned) {
|
||||
// Unpin: remove from array
|
||||
pinnedApps = pinnedApps.filter(id => id !== appId)
|
||||
} else {
|
||||
// Pin: add to array
|
||||
pinnedApps.push(appId)
|
||||
}
|
||||
|
||||
// Update the settings
|
||||
Settings.data.dock.pinnedApps = pinnedApps
|
||||
Logger.log("DockMenu", isPinned ? "Unpinned" : "Pinned", "app:", appId)
|
||||
}
|
||||
|
||||
anchor.item: anchorItem
|
||||
anchor.rect.x: anchorItem ? (anchorItem.width - implicitWidth) / 2 : 0
|
||||
anchor.rect.y: anchorItem ? -implicitHeight - (Style.marginM * scaling) : 0
|
||||
|
||||
function show(item, toplevelData) {
|
||||
if (!item) {
|
||||
Logger.warn("DockMenu", "anchorItem is undefined, won't show menu.")
|
||||
return
|
||||
}
|
||||
|
||||
anchorItem = item
|
||||
toplevel = toplevelData
|
||||
visible = true
|
||||
}
|
||||
|
||||
function hide() {
|
||||
visible = false
|
||||
}
|
||||
|
||||
// Close menu when clicking on background, track hover for the whole menu area
|
||||
MouseArea {
|
||||
id: menuMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: root.hide() // Close when clicking on the background (outside menu content)
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Escape"]
|
||||
enabled: root.visible
|
||||
onActivated: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusS * scaling
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
|
||||
// Prevent clicks inside the menu from closing it
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
|
||||
} // Do nothing, just consume the click
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contextMenuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM * scaling
|
||||
spacing: 0
|
||||
|
||||
// Activate/Focus item
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32 * scaling
|
||||
color: activateMouseArea.containsMouse ? Qt.alpha(Color.mSecondary, 0.2) : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "eye"
|
||||
font.pointSize: 12 * scaling
|
||||
color: Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (!root.toplevel)
|
||||
return "Activate"
|
||||
// Check if this toplevel is active by comparing with ToplevelManager.activeToplevel
|
||||
const isActive = ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === root.toplevel
|
||||
return isActive ? "Focus" : "Activate"
|
||||
}
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: activateMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (root.toplevel?.activate) {
|
||||
root.toplevel.activate()
|
||||
}
|
||||
root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pin/Unpin item
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32 * scaling
|
||||
color: pinMouseArea.containsMouse ? Qt.alpha(Color.mTertiary, 0.2) : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: {
|
||||
if (!root.toplevel)
|
||||
return "pin"
|
||||
return root.isAppPinned(root.toplevel.appId) ? "pinned-off" : "pin"
|
||||
}
|
||||
font.pointSize: 12 * scaling
|
||||
color: Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (!root.toplevel)
|
||||
return "Pin"
|
||||
return root.isAppPinned(root.toplevel.appId) ? "Unpin" : "Pin"
|
||||
}
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pinMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (root.toplevel?.appId) {
|
||||
root.toggleAppPin(root.toplevel.appId)
|
||||
}
|
||||
root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close item
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32 * scaling
|
||||
color: closeMouseArea.containsMouse ? Qt.alpha(Color.mPrimary, 0.2) : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "x"
|
||||
font.pointSize: 12 * scaling
|
||||
color: closeMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Close"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: closeMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
// Check if toplevel is still valid before trying to close it
|
||||
const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
|
||||
|
||||
if (isValidToplevel && root.toplevel.close) {
|
||||
Logger.log("DockMenu", "Closing app via menu:", root.toplevel.appId)
|
||||
root.toplevel.close()
|
||||
// Trigger immediate dock update callback if provided
|
||||
if (root.onAppClosed && typeof root.onAppClosed === "function") {
|
||||
Qt.callLater(root.onAppClosed)
|
||||
}
|
||||
} else {
|
||||
Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference")
|
||||
}
|
||||
root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user