Add task strip and Overview to navigation panel

In convergence mode, show a running-app icon strip in the navigation
panel using the existing TaskManager.TasksModel. Each icon activates
its window on click, with an indicator dot for the active window.

Replace the mobile task switcher button with a KWin Overview trigger:
add triggerOverview() to TaskPanel (D-Bus call to kglobalaccel), swap
the button icon to view-grid-symbolic, and enable the Overview effect
in the envmanager KWin config when convergence mode is active.

Wire convergenceMode and taskModel properties from
NavigationPanelComponent through to NavigationPanel so the task strip
populates from the existing TasksModel instance.
This commit is contained in:
Marco Allegretti 2026-04-08 19:07:37 +02:00
parent 60163ee15e
commit 5dfae0c45c
5 changed files with 112 additions and 5 deletions

View file

@ -9,6 +9,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window import QtQuick.Window
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.taskmanager 0.1 as TaskManager import org.kde.taskmanager 0.1 as TaskManager
@ -34,6 +35,10 @@ Item {
property bool isVertical: false property bool isVertical: false
// Convergence mode: show running-app task strip
property bool convergenceMode: false
property var taskModel: null
// drop shadow for icons // drop shadow for icons
MultiEffect { MultiEffect {
anchors.fill: icons anchors.fill: icons
@ -133,6 +138,63 @@ Item {
} }
} }
} }
// 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: 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
}
}
}
} }
states: [ states: [
@ -200,6 +262,21 @@ Item {
height: Kirigami.Units.gridUnit * 2 height: Kirigami.Units.gridUnit * 2
width: icons.width 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
}
}, State { }, State {
name: "horizontal" name: "horizontal"
when: !root.isVertical when: !root.isVertical
@ -264,6 +341,19 @@ Item {
height: parent.height height: parent.height
width: Kirigami.Units.gridUnit * 2 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
}
} }
] ]
} }

View file

@ -35,6 +35,10 @@ MobileShell.NavigationPanel {
foregroundColorGroup: forcedComplementary ? Kirigami.Theme.Complementary : Kirigami.Theme.Window foregroundColorGroup: forcedComplementary ? Kirigami.Theme.Complementary : Kirigami.Theme.Window
shadow: forcedComplementary shadow: forcedComplementary
// Convergence mode: expose running-app task strip
convergenceMode: ShellSettings.Settings.convergenceModeEnabled
taskModel: tasksModel
MobileShellState.PanelSettingsDBusClient { MobileShellState.PanelSettingsDBusClient {
id: panelSettings id: panelSettings
screenName: Screen.name screenName: Screen.name
@ -68,18 +72,22 @@ MobileShell.NavigationPanel {
// ~~~~ // ~~~~
// navigation panel actions // navigation panel actions
// toggle task switcher button // toggle task switcher button (KWin Overview in convergence mode, mobile task switcher otherwise)
leftAction: MobileShell.NavigationPanelAction { leftAction: MobileShell.NavigationPanelAction {
id: taskSwitcherAction id: taskSwitcherAction
enabled: true enabled: true
iconSource: "mobile-task-switcher" iconSource: ShellSettings.Settings.convergenceModeEnabled ? "view-grid-symbolic" : "mobile-task-switcher"
shrinkSize: 4 shrinkSize: ShellSettings.Settings.convergenceModeEnabled ? 0 : 4
onTriggered: { onTriggered: {
if (ShellSettings.Settings.convergenceModeEnabled) {
Plasmoid.triggerOverview();
} else {
Plasmoid.triggerTaskSwitcher(); Plasmoid.triggerTaskSwitcher();
} }
} }
}
// home button // home button
middleAction: MobileShell.NavigationPanelAction { middleAction: MobileShell.NavigationPanelAction {

View file

@ -31,4 +31,11 @@ void TaskPanel::triggerTaskSwitcher() const
QDBusConnection::sessionBus().send(message); QDBusConnection::sessionBus().send(message);
} }
void TaskPanel::triggerOverview() const
{
QDBusMessage message = QDBusMessage::createMethodCall("org.kde.kglobalaccel", "/component/kwin", "org.kde.kglobalaccel.Component", "invokeShortcut");
message.setArguments({QStringLiteral("Overview")});
QDBusConnection::sessionBus().send(message);
}
#include "taskpanel.moc" #include "taskpanel.moc"

View file

@ -15,4 +15,5 @@ class TaskPanel : public Plasma::Containment
public: public:
TaskPanel(QObject *parent, const KPluginMetaData &data, const QVariantList &args); TaskPanel(QObject *parent, const KPluginMetaData &data, const QVariantList &args);
Q_INVOKABLE void triggerTaskSwitcher() const; Q_INVOKABLE void triggerTaskSwitcher() const;
Q_INVOKABLE void triggerOverview() const;
}; };

View file

@ -56,6 +56,7 @@ QMap<QString, QMap<QString, QVariant>> getKwinrcSettings(KSharedConfig::Ptr m_mo
{"blurEnabled", false}, // disable blur for performance reasons, we could reconsider in the future for more powerful devices {"blurEnabled", false}, // disable blur for performance reasons, we could reconsider in the future for more powerful devices
{"convergentwindowsEnabled", true}, // enable our convergent window plugin {"convergentwindowsEnabled", true}, // enable our convergent window plugin
{"mobiletaskswitcherEnabled", true}, // ensure the mobile task switcher plugin is enabled {"mobiletaskswitcherEnabled", true}, // ensure the mobile task switcher plugin is enabled
{"overviewEnabled", convergenceModeEnabled}, // enable KWin Overview effect in convergence mode for desktop-style task switching
{"screenedgeEnabled", false} // disable the blue highlighting of screen edge effects. TODO would be nice if we could only deactivate it on {"screenedgeEnabled", false} // disable the blue highlighting of screen edge effects. TODO would be nice if we could only deactivate it on
// touchscreen gestures and not mouse as well // touchscreen gestures and not mouse as well
}}, }},
@ -76,7 +77,7 @@ QMap<QString, QMap<QString, QVariant>> getKwinrcSettings(KSharedConfig::Ptr m_mo
// Have a separate list here because we need to trigger DBus calls to load/unload each effect/script. // Have a separate list here because we need to trigger DBus calls to load/unload each effect/script.
// Make sure that the effect/script is added to the kwinrc "Plugins" section above! // Make sure that the effect/script is added to the kwinrc "Plugins" section above!
const QList<QString> KWIN_EFFECTS = {"blur", "mobiletaskswitcher", "screenedge"}; const QList<QString> KWIN_EFFECTS = {"blur", "mobiletaskswitcher", "overview", "screenedge"};
const QList<QString> KWIN_SCRIPTS = {"convergentwindows"}; const QList<QString> KWIN_SCRIPTS = {"convergentwindows"};
// .config/plasma-mobile/ksmserver - immutable settings: // .config/plasma-mobile/ksmserver - immutable settings: