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; 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 defaultGesturePanelThickness: Kirigami.Units.gridUnit
readonly property real navigationPanelThickness: { readonly property real navigationPanelThickness: {

View file

@ -141,9 +141,10 @@ Item {
} }
// Running-app task strip for convergence (desktop) mode // Running-app task strip for convergence (desktop) mode
// NOTE: Disabled running apps now shown in FavouritesBar dock
ListView { ListView {
id: taskStrip id: taskStrip
visible: root.convergenceMode && root.taskModel !== null && root.taskModel.count > 0 visible: false
orientation: root.isVertical ? ListView.Vertical : ListView.Horizontal orientation: root.isVertical ? ListView.Vertical : ListView.Horizontal
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
clip: true clip: true
@ -334,6 +335,79 @@ Item {
top: rightCornerButton.bottom 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 { }, State {
name: "horizontal" name: "horizontal"
when: !root.isVertical when: !root.isVertical

View file

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

View file

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

View file

@ -48,6 +48,8 @@ public:
void configChanged() override; void configChanged() override;
Q_INVOKABLE void triggerOverview() const;
FolioSettings *folioSettings(); FolioSettings *folioSettings();
HomeScreenState *homeScreenState(); HomeScreenState *homeScreenState();
WidgetsManager *widgetsManager(); 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.components 3.0 as PC3
import org.kde.plasma.private.mobileshell.state as MobileShellState 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 plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import QtQuick.Controls as Controls
import "./private" import "./private"
import "./delegate" import "./delegate"
@ -23,6 +26,88 @@ MouseArea {
signal delegateDragRequested(var item) 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 acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressAndHold: { onPressAndHold: {
@ -83,21 +168,21 @@ MouseArea {
readonly property bool isLocationBottom: folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom readonly property bool isLocationBottom: folio.HomeScreenState.favouritesBarLocation === Folio.HomeScreenState.Bottom
// get the normalized index position value from the center so we can animate it // 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 { Behavior on fromCenterValue {
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; } NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; }
} }
// multiply the 'fromCenterValue' by the cell size to get the actual position // 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 x: isLocationBottom ? centerPosition + root.dockCenterX : (parent.width - width) / 2
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - folio.HomeScreenState.pageCellHeight y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight
implicitWidth: folio.HomeScreenState.pageCellWidth implicitWidth: root.dockCellWidth
implicitHeight: folio.HomeScreenState.pageCellHeight implicitHeight: root.dockCellHeight
width: folio.HomeScreenState.pageCellWidth width: root.dockCellWidth
height: folio.HomeScreenState.pageCellHeight height: root.dockCellHeight
// Keyboard navigation to other delegates // Keyboard navigation to other delegates
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
@ -170,8 +255,8 @@ MouseArea {
PlaceholderDelegate { PlaceholderDelegate {
id: dragDropFeedback id: dragDropFeedback
folio: root.folio folio: root.folio
width: folio.HomeScreenState.pageCellWidth width: root.dockCellWidth
height: folio.HomeScreenState.pageCellHeight 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 { MobileShell.NavigationPanel {
id: root id: root
visible: !ShellSettings.Settings.convergenceModeEnabled
// Whether the bar background should be opaque // Whether the bar background should be opaque
required property bool opaqueBar 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 { rightAction: MobileShell.NavigationPanelAction {
id: closeAppAction id: closeAppAction
visible: !ShellSettings.Settings.convergenceModeEnabled
enabled: Keyboards.KWinVirtualKeyboard.visible || WindowPlugin.WindowUtil.hasCloseableActiveWindow enabled: Keyboards.KWinVirtualKeyboard.visible || WindowPlugin.WindowUtil.hasCloseableActiveWindow
iconSource: Keyboards.KWinVirtualKeyboard.visible ? "go-down-symbolic" : "mobile-close-app" 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 // mobile-close-app (from plasma-frameworks) seems to have fewer margins than icons from breeze-icons