Replace navigation panel with unified dock bar

In convergence mode the separate navigation panel is redundant
with window title-bar controls. Remove it by setting its
thickness to zero and visibility to false, then embed Home and
Overview buttons at the left and right ends of the favourites
bar. Running application icons with context menus are shown
between the favourites and the Overview button.

Add HomeScreen::triggerOverview() to invoke the KWin Overview
shortcut over D-Bus so the homescreen containment can open it
without access to the task-panel Plasmoid.
This commit is contained in:
Marco Allegretti 2026-04-09 10:15:14 +02:00
parent 5770a4981f
commit d89a303337
7 changed files with 273 additions and 13 deletions

View file

@ -27,7 +27,9 @@ QtObject {
return root.panelSettings.statusBarHeight;
}
readonly property real defaultNavigationPanelThickness: Kirigami.Units.gridUnit * 2
readonly property real defaultNavigationPanelThickness: ShellSettings.Settings.convergenceModeEnabled
? 0
: Kirigami.Units.gridUnit * 2
readonly property real defaultGesturePanelThickness: Kirigami.Units.gridUnit
readonly property real navigationPanelThickness: {

View file

@ -141,9 +141,10 @@ Item {
}
// Running-app task strip for convergence (desktop) mode
// NOTE: Disabled running apps now shown in FavouritesBar dock
ListView {
id: taskStrip
visible: root.convergenceMode && root.taskModel !== null && root.taskModel.count > 0
visible: false
orientation: root.isVertical ? ListView.Vertical : ListView.Horizontal
spacing: Kirigami.Units.smallSpacing
clip: true
@ -334,6 +335,79 @@ Item {
top: rightCornerButton.bottom
}
}
}, State {
name: "convergence-horizontal"
when: !root.isVertical && root.convergenceMode
PropertyChanges {
target: icons
anchors {
leftMargin: root.leftPadding
rightMargin: root.rightPadding
}
buttonLength: Math.min(Kirigami.Units.gridUnit * 8, icons.width * 0.7 / 3)
}
// Home button: far left
AnchorChanges {
target: middleButton
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: undefined
}
}
PropertyChanges {
target: middleButton
height: parent.height
width: icons.buttonLength
anchors.centerIn: undefined
}
// Overview button: far right
AnchorChanges {
target: leftButton
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
left: undefined
}
}
PropertyChanges {
target: leftButton
height: parent.height
width: icons.buttonLength
}
// Hide close button (already not visible via action)
PropertyChanges {
target: rightButton
height: parent.height
width: 0
visible: false
}
// Hide corner buttons in convergence
PropertyChanges {
target: leftCornerButton
width: 0
visible: false
}
PropertyChanges {
target: rightCornerButton
width: 0
visible: false
}
// Workspace indicator: left of Overview button
AnchorChanges {
target: workspaceIndicator
anchors {
verticalCenter: parent.verticalCenter
right: leftButton.left
left: undefined
}
}
// Task strip hidden
PropertyChanges {
target: taskStrip
height: 0
visible: false
}
}, State {
name: "horizontal"
when: !root.isVertical

View file

@ -78,6 +78,7 @@ target_link_libraries(org.kde.plasma.mobile.homescreen.folio PRIVATE
Qt::Gui
Qt::Qml
Qt::Quick
Qt::DBus
Plasma::Plasma
Plasma::PlasmaQuick
KF6::I18n

View file

@ -6,6 +6,8 @@
#include <KWindowSystem>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDebug>
#include <QQmlEngine>
#include <QQmlExtensionPlugin>
@ -87,4 +89,11 @@ PageListModel *HomeScreen::pageListModel()
return m_pageListModel;
}
void HomeScreen::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 "homescreen.moc"

View file

@ -48,6 +48,8 @@ public:
void configChanged() override;
Q_INVOKABLE void triggerOverview() const;
FolioSettings *folioSettings();
HomeScreenState *homeScreenState();
WidgetsManager *widgetsManager();

View file

@ -7,9 +7,12 @@ import QtQuick.Layouts 1.1
import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.private.mobileshell.state as MobileShellState
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
import org.kde.taskmanager as TaskManager
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.kirigami as Kirigami
import QtQuick.Controls as Controls
import "./private"
import "./delegate"
@ -23,6 +26,88 @@ MouseArea {
signal delegateDragRequested(var item)
// Convergence mode: show running apps alongside favourites
readonly property bool convergenceMode: ShellSettings.Settings.convergenceModeEnabled
readonly property int totalItemCount: repeater.count + (convergenceMode ? taskRepeater.count : 0)
// In convergence mode, size icons to fit the dock bar instead of using page grid cells
readonly property real dockCellWidth: convergenceMode ? root.height : folio.HomeScreenState.pageCellWidth
readonly property real dockCellHeight: convergenceMode ? root.height : folio.HomeScreenState.pageCellHeight
// Navigation buttons width (used to offset center positioning)
readonly property real navButtonWidth: convergenceMode ? root.height : 0
// Center x for dock items (offset between nav buttons in convergence mode)
readonly property real dockCenterX: convergenceMode
? navButtonWidth + (root.width - 2 * navButtonWidth) / 2
: root.width / 2
// Home button (convergence mode, left end)
Rectangle {
id: homeButton
visible: root.convergenceMode
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: root.navButtonWidth
color: "transparent"
Kirigami.Icon {
anchors.centerIn: parent
width: Math.min(parent.width, parent.height) * 0.75
height: width
source: "start-here-kde"
}
MouseArea {
anchors.fill: parent
onClicked: MobileShellState.ShellDBusClient.openHomeScreen()
}
}
// Overview button (convergence mode, right end)
Rectangle {
id: overviewButton
visible: root.convergenceMode
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
width: root.navButtonWidth
color: "transparent"
Kirigami.Icon {
anchors.centerIn: parent
width: Math.min(parent.width, parent.height) * 0.75
height: width
source: "view-grid-symbolic"
}
MouseArea {
anchors.fill: parent
onClicked: root.folio.triggerOverview()
}
}
TaskManager.VirtualDesktopInfo {
id: virtualDesktopInfo
}
TaskManager.ActivityInfo {
id: activityInfo
}
TaskManager.TasksModel {
id: tasksModel
filterByVirtualDesktop: true
filterByActivity: true
filterNotMaximized: false
filterByScreen: true
filterHidden: true
virtualDesktop: virtualDesktopInfo.currentDesktop
activity: activityInfo.currentActivity
groupMode: TaskManager.TasksModel.GroupDisabled
}
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressAndHold: {
@ -83,21 +168,21 @@ MouseArea {
readonly property bool isLocationBottom: folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
// get the normalized index position value from the center so we can animate it
property double fromCenterValue: model.index - (repeater.count / 2)
property double fromCenterValue: model.index - (root.totalItemCount / 2)
Behavior on fromCenterValue {
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; }
}
// multiply the 'fromCenterValue' by the cell size to get the actual position
readonly property int centerPosition: (isLocationBottom ? folio.HomeScreenState.pageCellWidth : folio.HomeScreenState.pageCellHeight) * fromCenterValue
readonly property int centerPosition: (isLocationBottom ? root.dockCellWidth : root.dockCellHeight) * fromCenterValue
x: isLocationBottom ? centerPosition + parent.width / 2 : (parent.width - width) / 2
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - folio.HomeScreenState.pageCellHeight
x: isLocationBottom ? centerPosition + root.dockCenterX : (parent.width - width) / 2
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight
implicitWidth: folio.HomeScreenState.pageCellWidth
implicitHeight: folio.HomeScreenState.pageCellHeight
width: folio.HomeScreenState.pageCellWidth
height: folio.HomeScreenState.pageCellHeight
implicitWidth: root.dockCellWidth
implicitHeight: root.dockCellHeight
width: root.dockCellWidth
height: root.dockCellHeight
// Keyboard navigation to other delegates
Keys.onPressed: (event) => {
@ -170,8 +255,8 @@ MouseArea {
PlaceholderDelegate {
id: dragDropFeedback
folio: root.folio
width: folio.HomeScreenState.pageCellWidth
height: folio.HomeScreenState.pageCellHeight
width: root.dockCellWidth
height: root.dockCellHeight
}
}
@ -328,4 +413,88 @@ MouseArea {
}
}
}
// Running-app task icons (convergence mode only)
Repeater {
id: taskRepeater
model: root.convergenceMode ? tasksModel : null
delegate: Item {
id: taskDelegate
required property int index
required property var model
readonly property bool isLocationBottom: folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
// Position after all favourites
property double fromCenterValue: (repeater.count + taskDelegate.index) - (root.totalItemCount / 2)
Behavior on fromCenterValue {
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; }
}
readonly property int centerPosition: (isLocationBottom ? root.dockCellWidth : root.dockCellHeight) * fromCenterValue
x: isLocationBottom ? centerPosition + root.dockCenterX : (parent.width - width) / 2
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight
implicitWidth: root.dockCellWidth
implicitHeight: root.dockCellHeight
width: root.dockCellWidth
height: root.dockCellHeight
// Task icon
Kirigami.Icon {
anchors.centerIn: parent
width: Math.min(parent.width, parent.height) * 0.6
height: width
source: taskDelegate.model.decoration
}
// 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
}
// Click to activate
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
taskContextMenu.popup();
} else {
tasksModel.requestActivate(tasksModel.makeModelIndex(taskDelegate.index));
}
}
}
Controls.Menu {
id: taskContextMenu
Controls.MenuItem {
text: taskDelegate.model.IsMinimized ? i18n("Restore") : i18n("Minimize")
icon.name: taskDelegate.model.IsMinimized ? "window-restore" : "window-minimize"
onTriggered: tasksModel.requestToggleMinimized(tasksModel.makeModelIndex(taskDelegate.index))
}
Controls.MenuItem {
text: taskDelegate.model.IsMaximized ? i18n("Restore") : i18n("Maximize")
icon.name: taskDelegate.model.IsMaximized ? "window-restore" : "window-maximize"
onTriggered: tasksModel.requestToggleMaximized(tasksModel.makeModelIndex(taskDelegate.index))
}
Controls.MenuSeparator {}
Controls.MenuItem {
text: i18n("Close")
icon.name: "window-close"
onTriggered: tasksModel.requestClose(tasksModel.makeModelIndex(taskDelegate.index))
}
}
}
}
}

View file

@ -21,6 +21,8 @@ import org.kde.kirigami as Kirigami
MobileShell.NavigationPanel {
id: root
visible: !ShellSettings.Settings.convergenceModeEnabled
// Whether the bar background should be opaque
required property bool opaqueBar
@ -102,10 +104,11 @@ MobileShell.NavigationPanel {
}
}
// close app/keyboard button
// close app/keyboard button (hidden in convergence mode windows have title bar close buttons)
rightAction: MobileShell.NavigationPanelAction {
id: closeAppAction
visible: !ShellSettings.Settings.convergenceModeEnabled
enabled: Keyboards.KWinVirtualKeyboard.visible || WindowPlugin.WindowUtil.hasCloseableActiveWindow
iconSource: Keyboards.KWinVirtualKeyboard.visible ? "go-down-symbolic" : "mobile-close-app"
// mobile-close-app (from plasma-frameworks) seems to have fewer margins than icons from breeze-icons