shift-shell/components/mobileshell/qml/navigationpanel/NavigationPanel.qml
Marco Allegretti 00a9ac1c4d Add desktop switcher and task context menus
Show numbered desktop indicator buttons between the nav buttons and
the keyboard toggle corner button. Each button highlights the current
desktop and switches on click via VirtualDesktopInfo.requestActivate.
Only visible in convergence mode when multiple desktops exist.

Add Minimize and Maximize/Restore actions to the task strip
right-click context menu alongside the existing Close action.
2026-04-08 20:12:38 +02:00

424 lines
15 KiB
QML

/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Window
import QtQuick.Effects
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.taskmanager 0.1 as TaskManager
import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.private.mobileshell.state as MobileShellState
Item {
id: root
property bool shadow: false
property color backgroundColor
property var foregroundColorGroup
property NavigationPanelAction leftAction
property NavigationPanelAction middleAction
property NavigationPanelAction rightAction
property NavigationPanelAction leftCornerAction
property NavigationPanelAction rightCornerAction
property real leftPadding: 0
property real rightPadding: 0
property bool isVertical: false
// Convergence mode: show running-app task strip
property bool convergenceMode: false
property var taskModel: null
property var virtualDesktopInfo: null
// drop shadow for icons
MultiEffect {
anchors.fill: icons
visible: shadow
source: icons
blurMax: 16
shadowEnabled: true
shadowVerticalOffset: 1
shadowOpacity: 0.8
}
// background colour
Rectangle {
anchors.fill: parent
color: root.backgroundColor
}
Item {
id: icons
anchors.fill: parent
property real buttonLength: 0
NavigationPanelButton {
id: leftCornerButton
visible: root.leftCornerAction.visible
Kirigami.Theme.colorSet: root.foregroundColorGroup
Kirigami.Theme.inherit: false
enabled: root.leftCornerAction.enabled
shrinkSize: root.leftCornerAction.shrinkSize
iconSource: root.leftCornerAction.iconSource
onClicked: {
if (enabled) {
root.leftCornerAction.triggered();
}
}
}
// button row (anchors provided by state)
NavigationPanelButton {
id: leftButton
visible: root.leftAction.visible
Kirigami.Theme.colorSet: root.foregroundColorGroup
Kirigami.Theme.inherit: false
enabled: root.leftAction.enabled
shrinkSize: root.leftAction.shrinkSize
iconSource: root.leftAction.iconSource
onClicked: {
if (enabled) {
root.leftAction.triggered();
}
}
}
NavigationPanelButton {
id: middleButton
anchors.centerIn: parent
visible: root.middleAction.visible
Kirigami.Theme.colorSet: root.foregroundColorGroup
Kirigami.Theme.inherit: false
enabled: root.middleAction.enabled
shrinkSize: root.middleAction.shrinkSize
iconSource: root.middleAction.iconSource
onClicked: {
if (enabled) {
root.middleAction.triggered();
}
}
}
NavigationPanelButton {
id: rightButton
visible: root.rightAction.visible
Kirigami.Theme.colorSet: root.foregroundColorGroup
Kirigami.Theme.inherit: false
enabled: root.rightAction.enabled
shrinkSize: root.rightAction.shrinkSize
iconSource: root.rightAction.iconSource
onClicked: {
if (enabled) {
root.rightAction.triggered();
}
}
}
NavigationPanelButton {
id: rightCornerButton
visible: root.rightCornerAction.visible
Kirigami.Theme.colorSet: root.foregroundColorGroup
Kirigami.Theme.inherit: false
enabled: root.rightCornerAction.enabled
shrinkSize: root.rightCornerAction.shrinkSize
iconSource: root.rightCornerAction.iconSource
onClicked: {
if (enabled) {
root.rightCornerAction.triggered();
}
}
}
// Running-app task strip for convergence (desktop) mode
ListView {
id: taskStrip
visible: root.convergenceMode && root.taskModel !== null && root.taskModel.count > 0
orientation: root.isVertical ? ListView.Vertical : ListView.Horizontal
spacing: Kirigami.Units.smallSpacing
clip: true
interactive: contentWidth > width
model: root.taskModel
delegate: NavigationPanelButton {
id: taskDelegate
required property int index
required property var model
width: taskStrip.orientation === ListView.Horizontal ? height : taskStrip.width
height: taskStrip.orientation === ListView.Horizontal ? taskStrip.height : taskStrip.width
Kirigami.Theme.colorSet: root.foregroundColorGroup
Kirigami.Theme.inherit: false
iconSource: taskDelegate.model.decoration
enabled: true
shrinkSize: 0
onClicked: {
root.taskModel.requestActivate(root.taskModel.makeModelIndex(taskDelegate.index));
}
// Right-click context menu
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: taskContextMenu.popup()
}
Controls.Menu {
id: taskContextMenu
Controls.MenuItem {
text: taskDelegate.model.IsMinimized ? i18n("Restore") : i18n("Minimize")
icon.name: taskDelegate.model.IsMinimized ? "window-restore" : "window-minimize"
onTriggered: root.taskModel.requestToggleMinimized(root.taskModel.makeModelIndex(taskDelegate.index))
}
Controls.MenuItem {
text: taskDelegate.model.IsMaximized ? i18n("Restore") : i18n("Maximize")
icon.name: taskDelegate.model.IsMaximized ? "window-restore" : "window-maximize"
onTriggered: root.taskModel.requestToggleMaximized(root.taskModel.makeModelIndex(taskDelegate.index))
}
Controls.MenuSeparator {}
Controls.MenuItem {
text: i18n("Close")
icon.name: "window-close"
onTriggered: root.taskModel.requestClose(root.taskModel.makeModelIndex(taskDelegate.index))
}
}
// Active-window indicator dot
Rectangle {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: Kirigami.Units.smallSpacing / 2
width: Kirigami.Units.smallSpacing * 2
height: width
radius: width / 2
color: Kirigami.Theme.highlightColor
visible: taskDelegate.model.IsActive === true
}
}
}
// Virtual desktop switcher (convergence mode, multiple desktops)
Row {
id: workspaceIndicator
visible: root.convergenceMode && root.virtualDesktopInfo !== null && root.virtualDesktopInfo.numberOfDesktops > 1
spacing: Kirigami.Units.smallSpacing / 2
Repeater {
model: root.virtualDesktopInfo ? root.virtualDesktopInfo.desktopIds : []
delegate: Rectangle {
required property int index
required property var modelData
width: Kirigami.Units.gridUnit * 1.5
height: width
radius: Kirigami.Units.smallSpacing
color: modelData === root.virtualDesktopInfo.currentDesktop
? Kirigami.Theme.highlightColor
: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2)
Text {
anchors.centerIn: parent
text: index + 1
color: parent.modelData === root.virtualDesktopInfo.currentDesktop
? Kirigami.Theme.highlightedTextColor
: Kirigami.Theme.textColor
font.pixelSize: parent.height * 0.6
}
MouseArea {
anchors.fill: parent
onClicked: root.virtualDesktopInfo.requestActivate(parent.modelData)
}
}
}
}
}
states: [
State {
name: "vertical"
when: root.isVertical
PropertyChanges {
target: icons
anchors {
topMargin: root.leftPadding
bottomMargin: root.rightPadding
}
buttonLength: Math.min(Kirigami.Units.gridUnit * 10, icons.height * 0.7 / 3)
}
AnchorChanges {
target: leftButton
anchors {
horizontalCenter: parent.horizontalCenter
top: middleButton.bottom
}
}
PropertyChanges {
target: leftButton
width: parent.width
height: icons.buttonLength
}
PropertyChanges {
target: middleButton
width: parent.width
height: icons.buttonLength
}
AnchorChanges {
target: rightButton
anchors {
horizontalCenter: parent.horizontalCenter
bottom: middleButton.top
}
}
PropertyChanges {
target: rightButton
height: icons.buttonLength
width: icons.width
}
AnchorChanges {
target: rightCornerButton
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
}
}
PropertyChanges {
target: rightCornerButton
height: Kirigami.Units.gridUnit * 2
width: icons.width
}
AnchorChanges {
target: leftCornerButton
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
}
}
PropertyChanges {
target: leftCornerButton
height: Kirigami.Units.gridUnit * 2
width: icons.width
}
// Task strip: vertical layout — positioned between leftCornerButton (bottom) and leftButton (above middle)
AnchorChanges {
target: taskStrip
anchors {
horizontalCenter: parent.horizontalCenter
bottom: leftButton.top
top: undefined
}
}
PropertyChanges {
target: taskStrip
width: parent.width
// Fill space between leftCorner (bottom) and the nav button group
height: taskStrip.visible ? (leftButton.y - leftCornerButton.y - leftCornerButton.height - Kirigami.Units.smallSpacing * 2) : 0
}
// Workspace indicator: vertical — above rightCornerButton (top)
AnchorChanges {
target: workspaceIndicator
anchors {
horizontalCenter: parent.horizontalCenter
top: rightCornerButton.bottom
}
}
}, State {
name: "horizontal"
when: !root.isVertical
PropertyChanges {
target: icons
anchors {
leftMargin: root.leftPadding
rightMargin: root.rightPadding
}
buttonLength: Math.min(Kirigami.Units.gridUnit * 8, icons.width * 0.7 / 3)
}
AnchorChanges {
target: leftButton
anchors {
verticalCenter: parent.verticalCenter
right: middleButton.left
}
}
PropertyChanges {
target: leftButton
height: parent.height
width: icons.buttonLength
}
PropertyChanges {
target: middleButton
height: parent.height
width: icons.buttonLength
}
AnchorChanges {
target: rightButton
anchors {
verticalCenter: parent.verticalCenter
left: middleButton.right
}
}
PropertyChanges {
target: rightButton
height: parent.height
width: icons.buttonLength
}
AnchorChanges {
target: rightCornerButton
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
}
}
PropertyChanges {
target: rightCornerButton
height: parent.height
width: Kirigami.Units.gridUnit * 2
}
AnchorChanges {
target: leftCornerButton
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
}
PropertyChanges {
target: leftCornerButton
height: parent.height
width: Kirigami.Units.gridUnit * 2
}
// Task strip: horizontal layout — positioned between leftCornerButton (left) and leftButton (near center)
AnchorChanges {
target: taskStrip
anchors {
verticalCenter: parent.verticalCenter
left: leftCornerButton.right
right: leftButton.left
}
}
PropertyChanges {
target: taskStrip
height: parent.height
}
// Workspace indicator: horizontal — between rightButton and rightCornerButton
AnchorChanges {
target: workspaceIndicator
anchors {
verticalCenter: parent.verticalCenter
left: rightButton.right
}
}
}
]
}