mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Add DockMenu actions
* Adds app-specific actions from the DesktopEntry to the context menu / DockMenu * Only displays actionable actions (ie: does not show Close or Focus if the app is not running, as those would do nothing)
This commit is contained in:
+112
-126
@@ -18,15 +18,67 @@ PopupWindow {
|
||||
property var onAppClosed: null // Callback function for when an app is closed
|
||||
|
||||
// Track which menu item is hovered
|
||||
property int hoveredItem: -1 // -1: none, 0: focus, 1: pin, 2: close
|
||||
property int hoveredItem: -1 // -1: none, otherwise the index of the item in `items`
|
||||
|
||||
property var items: []
|
||||
|
||||
signal requestClose
|
||||
|
||||
implicitWidth: 140 * scaling
|
||||
implicitWidth: 160 * scaling
|
||||
implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * scaling * 2)
|
||||
color: Color.transparent
|
||||
visible: false
|
||||
|
||||
function initItems() {
|
||||
// Is this a running app?
|
||||
const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
|
||||
|
||||
// Is this a pinned app?
|
||||
const isPinned = root.toplevel && root.isAppPinned(root.toplevel.appId)
|
||||
|
||||
var next = []
|
||||
if (isRunning) {
|
||||
// Focus item
|
||||
next.push({
|
||||
"icon": "eye",
|
||||
"text": I18n.tr("dock.menu.focus"),
|
||||
"action": function() { handleFocus() }
|
||||
})
|
||||
}
|
||||
|
||||
// Pin/Unpin item
|
||||
next.push({
|
||||
"icon": !isPinned ? "pin" : "unpin",
|
||||
"text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"),
|
||||
"action": function() { handlePin() }
|
||||
})
|
||||
|
||||
if (isRunning) {
|
||||
// Close item
|
||||
next.push({
|
||||
"icon": "close",
|
||||
"text": I18n.tr("dock.menu.close"),
|
||||
"action": function() { handleClose() }
|
||||
})
|
||||
}
|
||||
|
||||
// Create a menu entry for each app-specific action definied in its .desktop file
|
||||
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) {
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
|
||||
if (entry != null) {
|
||||
entry.actions.forEach(function(action) {
|
||||
next.push({
|
||||
"icon": "",
|
||||
"text": action.name,
|
||||
"action": function() { action.execute() }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
root.items = next
|
||||
}
|
||||
|
||||
// Helper functions for pin/unpin functionality
|
||||
function isAppPinned(appId) {
|
||||
if (!appId)
|
||||
@@ -66,11 +118,13 @@ PopupWindow {
|
||||
|
||||
anchorItem = item
|
||||
toplevel = toplevelData
|
||||
initItems()
|
||||
visible = true
|
||||
}
|
||||
|
||||
function hide() {
|
||||
visible = false
|
||||
root.items.length = 0
|
||||
}
|
||||
|
||||
// Helper function to determine which menu item is under the mouse
|
||||
@@ -83,44 +137,38 @@ PopupWindow {
|
||||
return -1
|
||||
|
||||
const itemIndex = Math.floor(relativeY / itemHeight)
|
||||
return itemIndex >= 0 && itemIndex < 3 ? itemIndex : -1
|
||||
return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1
|
||||
}
|
||||
|
||||
// Handle menu item clicks
|
||||
function handleItemClick(itemIndex) {
|
||||
switch (itemIndex) {
|
||||
case 0:
|
||||
// Focus
|
||||
if (root.toplevel?.activate) {
|
||||
root.toplevel.activate()
|
||||
}
|
||||
root.requestClose()
|
||||
break
|
||||
case 1:
|
||||
// Pin/Unpin
|
||||
if (root.toplevel?.appId) {
|
||||
root.toggleAppPin(root.toplevel.appId)
|
||||
}
|
||||
root.requestClose()
|
||||
break
|
||||
case 2:
|
||||
// Close
|
||||
// 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) {
|
||||
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()
|
||||
root.requestClose()
|
||||
break
|
||||
function handleFocus() {
|
||||
if (root.toplevel?.activate) {
|
||||
root.toplevel.activate()
|
||||
}
|
||||
root.requestClose()
|
||||
}
|
||||
|
||||
function handlePin() {
|
||||
if (root.toplevel?.appId) {
|
||||
root.toggleAppPin(root.toplevel.appId)
|
||||
}
|
||||
root.requestClose()
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
// 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) {
|
||||
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()
|
||||
root.requestClose()
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -163,7 +211,7 @@ PopupWindow {
|
||||
onClicked: mouse => {
|
||||
const clickedItem = root.getHoveredItem(mouse.y)
|
||||
if (clickedItem >= 0) {
|
||||
root.handleItemClick(clickedItem)
|
||||
root.items[clickedItem].action.call()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,99 +222,37 @@ PopupWindow {
|
||||
anchors.margins: Style.marginM * scaling
|
||||
spacing: 0
|
||||
|
||||
// Focus item
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 32 * scaling
|
||||
color: root.hoveredItem === 0 ? Color.mTertiary : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
Repeater {
|
||||
model: root.items
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 32 * scaling
|
||||
color: root.hoveredItem === index ? Color.mTertiary : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "eye"
|
||||
pointSize: Style.fontSizeL * scaling
|
||||
color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NText {
|
||||
text: I18n.tr("dock.menu.focus")
|
||||
pointSize: Style.fontSizeS * scaling
|
||||
color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
NIcon {
|
||||
icon: modelData.icon
|
||||
pointSize: Style.fontSizeL * scaling
|
||||
color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// Pin/Unpin item
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 32 * scaling
|
||||
color: root.hoveredItem === 1 ? Color.mTertiary : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
RowLayout {
|
||||
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) ? "unpin" : "pin"
|
||||
NText {
|
||||
text: modelData.text
|
||||
pointSize: Style.fontSizeS * scaling
|
||||
color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
pointSize: Style.fontSizeL * scaling
|
||||
color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (!root.toplevel)
|
||||
return I18n.tr("dock.menu.pin")
|
||||
return root.isAppPinned(root.toplevel.appId) ? I18n.tr("dock.menu.unpin") : I18n.tr("dock.menu.pin")
|
||||
}
|
||||
pointSize: Style.fontSizeS * scaling
|
||||
color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close item
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 32 * scaling
|
||||
color: root.hoveredItem === 2 ? Color.mTertiary : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "close"
|
||||
pointSize: Style.fontSizeL * scaling
|
||||
color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("dock.menu.close")
|
||||
pointSize: Style.fontSizeS * scaling
|
||||
color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user