diff --git a/components/mobileshell/qml/components/Constants.qml b/components/mobileshell/qml/components/Constants.qml index c06ba6a5..eed50530 100644 --- a/components/mobileshell/qml/components/Constants.qml +++ b/components/mobileshell/qml/components/Constants.qml @@ -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: { diff --git a/components/mobileshell/qml/navigationpanel/NavigationPanel.qml b/components/mobileshell/qml/navigationpanel/NavigationPanel.qml index 68626cee..ae1fd07c 100644 --- a/components/mobileshell/qml/navigationpanel/NavigationPanel.qml +++ b/components/mobileshell/qml/navigationpanel/NavigationPanel.qml @@ -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 diff --git a/containments/homescreens/folio/CMakeLists.txt b/containments/homescreens/folio/CMakeLists.txt index 960203de..006e800a 100644 --- a/containments/homescreens/folio/CMakeLists.txt +++ b/containments/homescreens/folio/CMakeLists.txt @@ -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 diff --git a/containments/homescreens/folio/homescreen.cpp b/containments/homescreens/folio/homescreen.cpp index c7e42ff5..a7e02e3c 100644 --- a/containments/homescreens/folio/homescreen.cpp +++ b/containments/homescreens/folio/homescreen.cpp @@ -6,6 +6,8 @@ #include +#include +#include #include #include #include @@ -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" diff --git a/containments/homescreens/folio/homescreen.h b/containments/homescreens/folio/homescreen.h index 96ab7f24..e0ac8f87 100644 --- a/containments/homescreens/folio/homescreen.h +++ b/containments/homescreens/folio/homescreen.h @@ -48,6 +48,8 @@ public: void configChanged() override; + Q_INVOKABLE void triggerOverview() const; + FolioSettings *folioSettings(); HomeScreenState *homeScreenState(); WidgetsManager *widgetsManager(); diff --git a/containments/homescreens/folio/qml/FavouritesBar.qml b/containments/homescreens/folio/qml/FavouritesBar.qml index b1c2a24d..3f2e5eac 100644 --- a/containments/homescreens/folio/qml/FavouritesBar.qml +++ b/containments/homescreens/folio/qml/FavouritesBar.qml @@ -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)) + } + } + } + } } diff --git a/containments/taskpanel/qml/NavigationPanelComponent.qml b/containments/taskpanel/qml/NavigationPanelComponent.qml index 0575f875..492e976d 100644 --- a/containments/taskpanel/qml/NavigationPanelComponent.qml +++ b/containments/taskpanel/qml/NavigationPanelComponent.qml @@ -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