From 8f9f722ca7fbfbc3023db40d6b8accff83bea29f Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Sun, 13 Feb 2022 04:23:57 +0000 Subject: [PATCH] navigationpanel: Add ability to toggle between gesture only and shown navigation panel modes Addresses: https://invent.kde.org/plasma/plasma-phone-components/-/issues/140 --- CMakeLists.txt | 1 + LICENSES/MIT.txt | 21 +++ .../qml/HomeScreen.qml | 18 +- components/mobileshell/CMakeLists.txt | 1 + components/mobileshell/mobileshellplugin.cpp | 5 + .../mobileshell/mobileshellsettings.cpp | 35 ++++ components/mobileshell/mobileshellsettings.h | 32 ++++ components/mobileshell/qml/Shell.qml | 49 +++++ .../mobileshell/qml/TaskPanelControls.qml | 1 + .../navigationpanel/NavigationGestureArea.qml | 74 ++++++++ .../qml/navigationpanel/NavigationPanel.qml | 7 +- components/mobileshell/qml/qmldir | 2 + .../mobileshell/qml/taskswitcher/TaskList.qml | 4 +- .../qml/taskswitcher/TaskSwitcher.qml | 7 +- .../qml/taskswitcher/TaskSwitcherState.qml | 4 +- .../qml/widgets/krunner/KRunnerWidget.qml | 7 +- .../contents/ui/NavigationPanelComponent.qml | 106 +++++++++++ .../taskpanel/package/contents/ui/main.qml | 174 +++++------------- containments/taskpanel/taskpanel.cpp | 17 +- containments/taskpanel/taskpanel.h | 2 + kcms/CMakeLists.txt | 4 + kcms/mobileshell/.clang-format | 88 +++++++++ kcms/mobileshell/CMakeLists.txt | 50 +++++ kcms/mobileshell/kcm.cpp | 35 ++++ kcms/mobileshell/kcm.h | 29 +++ kcms/mobileshell/package/contents/ui/main.qml | 43 +++++ kcms/mobileshell/package/metadata.desktop | 25 +++ 27 files changed, 690 insertions(+), 151 deletions(-) create mode 100644 LICENSES/MIT.txt create mode 100644 components/mobileshell/mobileshellsettings.cpp create mode 100644 components/mobileshell/mobileshellsettings.h create mode 100644 components/mobileshell/qml/Shell.qml create mode 100644 components/mobileshell/qml/navigationpanel/NavigationGestureArea.qml create mode 100644 containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml create mode 100644 kcms/CMakeLists.txt create mode 100644 kcms/mobileshell/.clang-format create mode 100644 kcms/mobileshell/CMakeLists.txt create mode 100644 kcms/mobileshell/kcm.cpp create mode 100644 kcms/mobileshell/kcm.h create mode 100644 kcms/mobileshell/package/contents/ui/main.qml create mode 100644 kcms/mobileshell/package/metadata.desktop diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b01e766..d69cbe54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,7 @@ add_subdirectory(bin) add_subdirectory(containments) add_subdirectory(components) add_subdirectory(quicksettings) +add_subdirectory(kcms) find_program(PlasmaOpenSettings plasma-open-settings) set_package_properties(PlasmaOpenSettings PROPERTIES diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 00000000..8aa26455 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/components/mobilehomescreencomponents/qml/HomeScreen.qml b/components/mobilehomescreencomponents/qml/HomeScreen.qml index aae300fb..1ecc88bf 100644 --- a/components/mobilehomescreencomponents/qml/HomeScreen.qml +++ b/components/mobilehomescreencomponents/qml/HomeScreen.qml @@ -33,8 +33,8 @@ Item { appDrawerFlickable: appDrawer.flickable - availableScreenHeight: height - (MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0) - availableScreenWidth: width - (MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth) + availableScreenHeight: height - MobileShell.Shell.bottomMargin + availableScreenWidth: width - MobileShell.Shell.leftMargin - MobileShell.Shell.rightMargin appDrawerBottomOffset: favoriteStrip.height } @@ -73,9 +73,10 @@ Item { // account for panels anchors.fill: parent - anchors.topMargin: MobileShell.TopPanelControls.panelHeight - anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth - anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 + anchors.topMargin: MobileShell.Shell.topMargin + anchors.bottomMargin: MobileShell.Shell.bottomMargin + anchors.leftMargin: MobileShell.Shell.leftMargin + anchors.rightMargin: MobileShell.Shell.rightMargin // animation when app drawer is being shown opacity: root.appDrawer ? 1 - root.appDrawer.openFactor : 1 @@ -129,9 +130,10 @@ Item { homeScreenState: root.homeScreenState // account for panels - topPadding: MobileShell.TopPanelControls.panelHeight - rightPadding: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth - bottomPadding: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 + topPadding: MobileShell.Shell.topMargin + bottomPadding: MobileShell.Shell.bottomMargin + leftPadding: MobileShell.Shell.leftMargin + rightPadding: MobileShell.Shell.rightMargin } } } diff --git a/components/mobileshell/CMakeLists.txt b/components/mobileshell/CMakeLists.txt index c41aa147..5d91f657 100644 --- a/components/mobileshell/CMakeLists.txt +++ b/components/mobileshell/CMakeLists.txt @@ -9,6 +9,7 @@ set(mobileshellplugin_SRCS mobileshellplugin.cpp shellutil.cpp quicksettingsmodel.cpp + mobileshellsettings.cpp vkbdinterface.cpp displaysmodel.cpp diff --git a/components/mobileshell/mobileshellplugin.cpp b/components/mobileshell/mobileshellplugin.cpp index 63627fe0..8f81041b 100644 --- a/components/mobileshell/mobileshellplugin.cpp +++ b/components/mobileshell/mobileshellplugin.cpp @@ -10,6 +10,7 @@ #include #include "displaysmodel.h" +#include "mobileshellsettings.h" #include "notifications/notificationfilemenu.h" #include "notifications/notificationthumbnailer.h" #include "quicksettingsmodel.h" @@ -25,6 +26,10 @@ void MobileShellPlugin::registerTypes(const char *uri) return ShellUtil::instance(); }); + qmlRegisterSingletonType(uri, 1, 0, "MobileShellSettings", [](QQmlEngine *, QJSEngine *) -> QObject * { + return MobileShellSettings::self(); + }); + qmlRegisterType(uri, 1, 0, "QuickSetting"); qmlRegisterType(uri, 1, 0, "QuickSettingsModel"); diff --git a/components/mobileshell/mobileshellsettings.cpp b/components/mobileshell/mobileshellsettings.cpp new file mode 100644 index 00000000..6ebc0a6b --- /dev/null +++ b/components/mobileshell/mobileshellsettings.cpp @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "mobileshellsettings.h" + +const QString CONFIG_FILE = QStringLiteral("plasmamobilerc"); +const QString GENERAL_CONFIG_GROUP = QStringLiteral("General"); + +MobileShellSettings *MobileShellSettings::self() +{ + static MobileShellSettings *singleton = new MobileShellSettings(); + return singleton; +} + +MobileShellSettings::MobileShellSettings(QObject *parent) + : QObject{parent} +{ + m_config = KSharedConfig::openConfig(CONFIG_FILE, KConfig::SimpleConfig); + m_configWatcher = KConfigWatcher::create(m_config); + + connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void { + if (group.name() == GENERAL_CONFIG_GROUP) { + Q_EMIT navigationPanelEnabledChanged(); + } + }); +} + +bool MobileShellSettings::navigationPanelEnabled() const +{ + auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; + return group.readEntry("navigationPanelEnabled", true); +} diff --git a/components/mobileshell/mobileshellsettings.h b/components/mobileshell/mobileshellsettings.h new file mode 100644 index 00000000..15e6e320 --- /dev/null +++ b/components/mobileshell/mobileshellsettings.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include +#include +#include + +class MobileShellSettings : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool navigationPanelEnabled READ navigationPanelEnabled NOTIFY navigationPanelEnabledChanged) + +public: + static MobileShellSettings *self(); + + MobileShellSettings(QObject *parent = nullptr); + + bool navigationPanelEnabled() const; + +Q_SIGNALS: + void navigationPanelEnabledChanged(); + +private: + KConfigWatcher::Ptr m_configWatcher; + KSharedConfig::Ptr m_config; +}; diff --git a/components/mobileshell/qml/Shell.qml b/components/mobileshell/qml/Shell.qml new file mode 100644 index 00000000..522e56f0 --- /dev/null +++ b/components/mobileshell/qml/Shell.qml @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2022 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Window 2.15 + +import org.kde.plasma.private.mobileshell 1.0 as MobileShell + +pragma Singleton + +/** + * Provides access to the homescreen plasmoid containment within the shell. + */ +QtObject { + id: delegate + + /** + * Top margin from the screen edge where application windows would display. + */ + readonly property real topMargin: TopPanelControls.panelHeight + + /** + * Bottom margin from the screen edge where application windows would display. + */ + readonly property real bottomMargin: TaskPanelControls.isPortrait ? TaskPanelControls.panelHeight : 0 + + /** + * Left margin from the screen edge where application windows would display. + */ + readonly property real leftMargin: 0 + + /** + * Right margin from the screen edge where application windows would display. + */ + readonly property real rightMargin: !TaskPanelControls.isPortrait ? TaskPanelControls.panelWidth : 0 + + /** + * Orientation of the mobile device. + */ + readonly property int orientation: TaskPanelControls.isPortrait ? Shell.Portrait : Shell.Landscape + + enum Orientation { + Landscape, + Portrait + } +} diff --git a/components/mobileshell/qml/TaskPanelControls.qml b/components/mobileshell/qml/TaskPanelControls.qml index e2503c85..c7a8ea7d 100644 --- a/components/mobileshell/qml/TaskPanelControls.qml +++ b/components/mobileshell/qml/TaskPanelControls.qml @@ -11,6 +11,7 @@ pragma Singleton /** * Provides access to the taskpanel plasmoid containment within the shell. + * Properties are updated by the taskpanel containment. */ QtObject { id: root diff --git a/components/mobileshell/qml/navigationpanel/NavigationGestureArea.qml b/components/mobileshell/qml/navigationpanel/NavigationGestureArea.qml new file mode 100644 index 00000000..094d771f --- /dev/null +++ b/components/mobileshell/qml/navigationpanel/NavigationGestureArea.qml @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2022 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.4 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.2 +import QtGraphicalEffects 1.12 + +import org.kde.taskmanager 0.1 as TaskManager +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.kquickcontrolsaddons 2.0 + +import org.kde.plasma.private.nanoshell 2.0 as NanoShell +import org.kde.plasma.private.mobileshell 1.0 as MobileShell + +Item { + id: root + + property var taskSwitcher + + MouseArea { + id: mouseArea + anchors.fill: parent + drag.filterChildren: true + + // drag gesture + property int oldMouseY: 0 + property int startMouseY: 0 + property int oldMouseX: 0 + property int startMouseX: 0 + property bool opening: false + + onPressed: { + startMouseX = oldMouseX = mouse.y; + startMouseY = oldMouseY = mouse.y; + } + + onPositionChanged: { + if (!opening && Math.abs(startMouseY - oldMouseY) < root.height) { + oldMouseY = mouse.y; + return; + } else if (mouseArea.pressed) { + opening = true; + } + + if (root.taskSwitcher.visible) { + // update task switcher drag + let offsetY = (mouse.y - oldMouseY) * 0.5; // we want to make the gesture take a longer swipe than it being pixel perfect + let offsetX = (mouse.x - oldMouseX) * 0.7; // we want to make the gesture not too hard to swipe, but not too easy + taskSwitcher.taskSwitcherState.yPosition = Math.max(0, taskSwitcher.taskSwitcherState.yPosition - offsetY); + taskSwitcher.taskSwitcherState.xPosition -= offsetX; + } + + if (!root.taskSwitcher.visible && Math.abs(startMouseY - mouse.y) > PlasmaCore.Units.gridUnit && taskSwitcher.tasksCount) { + // start task switcher gesture + root.taskSwitcher.show(false); + } + + oldMouseY = mouse.y; + oldMouseX = mouse.x; + } + + onReleased: { + if (taskSwitcher.taskSwitcherState.currentlyBeingOpened) { + taskSwitcher.taskSwitcherState.updateState(); + } + } + } +} + diff --git a/components/mobileshell/qml/navigationpanel/NavigationPanel.qml b/components/mobileshell/qml/navigationpanel/NavigationPanel.qml index 4938c388..74e52590 100644 --- a/components/mobileshell/qml/navigationpanel/NavigationPanel.qml +++ b/components/mobileshell/qml/navigationpanel/NavigationPanel.qml @@ -20,6 +20,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell Item { id: root + property bool shadow: false property color backgroundColor property var foregroundColorGroup @@ -32,7 +33,7 @@ Item { DropShadow { anchors.fill: mouseArea - visible: !showingApp + visible: shadow cached: true horizontalOffset: 0 verticalOffset: 1 @@ -166,7 +167,7 @@ Item { states: [ State { name: "landscape" - when: Screen.width > Screen.height + when: root.width < root.height PropertyChanges { target: icons buttonLength: icons.height * 0.8 / 3 @@ -204,7 +205,7 @@ Item { } }, State { name: "portrait" - when: Screen.width <= Screen.height + when: root.width >= root.height PropertyChanges { target: icons buttonLength: icons.width * 0.8 / 3 diff --git a/components/mobileshell/qml/qmldir b/components/mobileshell/qml/qmldir index 2be22e28..faf58dc4 100644 --- a/components/mobileshell/qml/qmldir +++ b/components/mobileshell/qml/qmldir @@ -23,6 +23,7 @@ singleton VolumeProvider 1.0 dataproviders/VolumeProvider.qml singleton WifiProvider 1.0 dataproviders/WifiProvider.qml # /navigationpanel +NavigationGestureArea 1.0 navigationpanel/NavigationGestureArea.qml NavigationPanel 1.0 navigationpanel/NavigationPanel.qml NavigationPanelAction 1.0 navigationpanel/NavigationPanelAction.qml @@ -40,5 +41,6 @@ NotificationsModelType 1.0 widgets/notifications/NotificationsModelType.qml # / singleton HomeScreenControls 1.0 HomeScreenControls.qml +singleton Shell 1.0 Shell.qml singleton TaskPanelControls 1.0 TaskPanelControls.qml singleton TopPanelControls 1.0 TopPanelControls.qml diff --git a/components/mobileshell/qml/taskswitcher/TaskList.qml b/components/mobileshell/qml/taskswitcher/TaskList.qml index c6748912..61ed88fb 100644 --- a/components/mobileshell/qml/taskswitcher/TaskList.qml +++ b/components/mobileshell/qml/taskswitcher/TaskList.qml @@ -71,8 +71,8 @@ Item { // account for system header and footer offset (center the preview image) y: { - let headerHeight = MobileShell.TopPanelControls.panelHeight; - let footerHeight = MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0; + let headerHeight = MobileShell.Shell.topMargin; + let footerHeight = MobileShell.Shell.bottomMargin; let diff = headerHeight - footerHeight; let baseY = (taskSwitcher.height / 2) - (height / 2) - (taskSwitcherState.taskHeaderHeight / 2) diff --git a/components/mobileshell/qml/taskswitcher/TaskSwitcher.qml b/components/mobileshell/qml/taskswitcher/TaskSwitcher.qml index 78baa97c..9955985b 100644 --- a/components/mobileshell/qml/taskswitcher/TaskSwitcher.qml +++ b/components/mobileshell/qml/taskswitcher/TaskSwitcher.qml @@ -166,9 +166,10 @@ Item { // provide shell margins anchors.fill: parent - anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth - anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 - anchors.topMargin: MobileShell.TopPanelControls.panelHeight + anchors.leftMargin: MobileShell.Shell.leftMargin + anchors.rightMargin: MobileShell.Shell.rightMargin + anchors.bottomMargin: MobileShell.Shell.bottomMargin + anchors.topMargin: MobileShell.Shell.topMargin FlickContainer { id: flickable diff --git a/components/mobileshell/qml/taskswitcher/TaskSwitcherState.qml b/components/mobileshell/qml/taskswitcher/TaskSwitcherState.qml index 0a68a582..09abf304 100644 --- a/components/mobileshell/qml/taskswitcher/TaskSwitcherState.qml +++ b/components/mobileshell/qml/taskswitcher/TaskSwitcherState.qml @@ -70,8 +70,8 @@ QtObject { // ~~ measurement constants ~~ // dimensions of a real window on the screen - readonly property real windowHeight: taskSwitcher.height - (MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0) - MobileShell.TopPanelControls.panelHeight - readonly property real windowWidth: taskSwitcher.width - (MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth) + readonly property real windowHeight: taskSwitcher.height - MobileShell.Shell.topMargin - MobileShell.Shell.bottomMargin + readonly property real windowWidth: taskSwitcher.width - MobileShell.Shell.leftMargin - MobileShell.Shell.rightMargin // dimensions of the task previews readonly property real previewHeight: windowHeight * scalingFactor diff --git a/components/mobileshell/qml/widgets/krunner/KRunnerWidget.qml b/components/mobileshell/qml/widgets/krunner/KRunnerWidget.qml index 91180198..f5130395 100644 --- a/components/mobileshell/qml/widgets/krunner/KRunnerWidget.qml +++ b/components/mobileshell/qml/widgets/krunner/KRunnerWidget.qml @@ -75,9 +75,10 @@ Item { id: flickable anchors.fill: parent - anchors.topMargin: MobileShell.TopPanelControls.panelHeight - anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 - anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth + anchors.topMargin: MobileShell.Shell.topMargin + anchors.bottomMargin: MobileShell.Shell.bottomMargin + anchors.leftMargin: MobileShell.Shell.leftMargin + anchors.rightMargin: MobileShell.Shell.rightMargin contentHeight: flickable.height + root.closedContentY + 999999 contentY: root.closedContentY diff --git a/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml b/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml new file mode 100644 index 00000000..0bcebcf4 --- /dev/null +++ b/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.4 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.15 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore + +import org.kde.plasma.private.mobileshell 1.0 as MobileShell + +MobileShell.NavigationPanel { + id: root + property bool appIsShown: !plasmoid.nativeInterface.allMinimized + + // background is: + // - opaque if an app is shown + // - translucent if the task switcher is open + // - transparent if on the homescreen + backgroundColor: { + if (root.taskSwitcher.visible) { + return Qt.rgba(0, 0, 0, 0.1); + } else { + return appIsShown ? PlasmaCore.ColorScope.backgroundColor : "transparent"; + } + } + foregroundColorGroup: (!root.taskSwitcher.visible && appIsShown) ? PlasmaCore.Theme.NormalColorGroup : PlasmaCore.Theme.ComplementaryColorGroup + shadow: !appIsShown + + // do not enable drag gesture when task switcher is already open + // also don't disable drag gesture mid-drag + dragGestureEnabled: !root.taskSwitcher.visible || root.taskSwitcher.taskSwitcherState.currentlyBeingOpened + + // ~~~~ + // navigation panel actions + + // toggle task switcher button + leftAction: MobileShell.NavigationPanelAction { + id: taskSwitcherAction + + enabled: (root.taskSwitcher.tasksCount > 0) || root.taskSwitcher.visible + iconSource: "mobile-task-switcher" + iconSizeFactor: 0.75 + + onTriggered: { + plasmoid.nativeInterface.showDesktop = false; + + if (!root.taskSwitcher.visible) { + root.taskSwitcher.show(true); + } else { + // when task switcher is open + if (root.taskSwitcher.taskSwitcherState.wasInActiveTask) { + // restore active window + root.taskSwitcher.activateWindow(taskSwitcher.taskSwitcherState.currentTaskIndex); + } else { + root.taskSwitcher.hide(); + } + } + } + } + + // home button + middleAction: MobileShell.NavigationPanelAction { + id: homeAction + + enabled: true + iconSource: "start-here-kde" + iconSizeFactor: 1 + + onTriggered: { + MobileShell.HomeScreenControls.openHomeScreen(); + plasmoid.nativeInterface.allMinimizedChanged(); + } + } + + // close app/keyboard button + rightAction: MobileShell.NavigationPanelAction { + id: closeAppAction + + enabled: MobileShell.KWinVirtualKeyboard.visible || root.taskSwitcher.visible || plasmoid.nativeInterface.hasCloseableActiveWindow + iconSource: MobileShell.KWinVirtualKeyboard.visible ? "go-down-symbolic" : "mobile-close-app" + // mobile-close-app (from plasma-frameworks) seems to have less margins than icons from breeze-icons + iconSizeFactor: MobileShell.KWinVirtualKeyboard.visible ? 1 : 0.75 + + onTriggered: { + if (MobileShell.KWinVirtualKeyboard.active) { + // close keyboard if it is open + MobileShell.KWinVirtualKeyboard.active = false; + } else if (taskSwitcher.visible) { + // if task switcher is open, close the current window shown + let indexToClose = root.taskSwitcher.tasksModel.index(root.taskSwitcher.currentTaskIndex, 0); + root.taskSwitcher.tasksModel.requestClose(indexToClose); + + } else if (plasmoid.nativeInterface.hasCloseableActiveWindow) { + // if task switcher is closed, but there is an active window + if (root.taskSwitcher.tasksModel.activeTask !== 0) { + root.taskSwitcher.tasksModel.requestClose(root.taskSwitcher.tasksModel.activeTask); + } + } + } + } +} diff --git a/containments/taskpanel/package/contents/ui/main.qml b/containments/taskpanel/package/contents/ui/main.qml index 1cf7dd8d..db3dd0e5 100644 --- a/containments/taskpanel/package/contents/ui/main.qml +++ b/containments/taskpanel/package/contents/ui/main.qml @@ -7,7 +7,7 @@ import QtQuick 2.4 import QtQuick.Layouts 1.1 -import QtQuick.Window 2.2 +import QtQuick.Window 2.15 import QtGraphicalEffects 1.12 import org.kde.taskmanager 0.1 as TaskManager @@ -21,17 +21,32 @@ import org.kde.plasma.phone.taskpanel 1.0 as TaskPanel PlasmaCore.ColorScope { id: root - colorGroup: showingApp ? PlasmaCore.Theme.HeaderColorGroup : PlasmaCore.Theme.ComplementaryColorGroup + width: 360 - Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground + // contrasting colour + colorGroup: !plasmoid.nativeInterface.allMinimized ? PlasmaCore.Theme.HeaderColorGroup : PlasmaCore.Theme.ComplementaryColorGroup readonly property color backgroundColor: PlasmaCore.ColorScope.backgroundColor - readonly property bool showingApp: !plasmoid.nativeInterface.allMinimized - - readonly property bool hasTasks: tasksModel.count > 0 - - property var taskSwitcher: MobileShell.HomeScreenControls.taskSwitcher + Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground + + // toggle visibility of navigation bar (show, or use gestures only) + Binding { + target: plasmoid.Window.window // assumed to be plasma-workspace "PanelView" component + property: "visibilityMode" + // 0 - VisibilityMode.NormalPanel + // 3 - VisibilityMode.WindowsGoBelow + value: MobileShell.MobileShellSettings.navigationPanelEnabled ? 0 : 3 + } + Binding { + target: plasmoid.Window.window // assumed to be plasma-workspace "PanelView" component + property: "thickness" + // height of panel: + // - if navigation panel is enabled: PlasmaCore.Units.gridUnit * 2 + // - if gestures only is enabled: 8 (just large enough for touch swipe to register, without blocking app content) + value: MobileShell.MobileShellSettings.navigationPanelEnabled ? PlasmaCore.Units.gridUnit * 2 : 8 + } + //BEGIN API implementation Binding { @@ -42,160 +57,69 @@ PlasmaCore.ColorScope { Binding { target: MobileShell.TaskPanelControls property: "panelHeight" - value: root.height + value: MobileShell.MobileShellSettings.navigationPanelEnabled ? root.height : 0 } Binding { target: MobileShell.TaskPanelControls property: "panelWidth" - value: root.width + value: MobileShell.MobileShellSettings.navigationPanelEnabled ? root.width : 0 } -//END API implementation - Connections { target: plasmoid.nativeInterface function onAllMinimizedChanged() { MobileShell.HomeScreenControls.homeScreenVisible = plasmoid.nativeInterface.allMinimized } } - - TaskManager.TasksModel { - id: tasksModel - groupMode: TaskManager.TasksModel.GroupDisabled - - screenGeometry: plasmoid.screenGeometry - sortMode: TaskManager.TasksModel.SortAlpha - - virtualDesktop: virtualDesktopInfo.currentDesktop - activity: activityInfo.currentActivity - } - - TaskManager.VirtualDesktopInfo { - id: virtualDesktopInfo - } - - TaskManager.ActivityInfo { - id: activityInfo - } + +//END API implementation Window.onWindowChanged: { if (!Window.window) return; + // ensure that Plasma sets the correct offset Window.window.offset = Qt.binding(() => { - return plasmoid.formFactor === PlasmaCore.Types.Vertical ? MobileShell.TopPanelControls.panelHeight : 0 + return (plasmoid.formFactor === PlasmaCore.Types.Vertical) ? MobileShell.TopPanelControls.panelHeight : MobileShell.TopPanelControls.panelWidth }); } - - // navigation panel actions - MobileShell.NavigationPanelAction { - id: taskSwitcherAction - - enabled: hasTasks || taskSwitcher.visible - iconSource: "mobile-task-switcher" - iconSizeFactor: 0.75 - - onTriggered: { - plasmoid.nativeInterface.showDesktop = false; - - if (!taskSwitcher.visible) { - taskSwitcher.show(true); - } else { - // when task switcher is open - if (taskSwitcher.taskSwitcherState.wasInActiveTask) { - // restore active window - taskSwitcher.activateWindow(taskSwitcher.taskSwitcherState.currentTaskIndex); - } else { - taskSwitcher.hide(); - } - } + + // bottom navigation panel component + Component { + id: navigationPanel + NavigationPanelComponent { + taskSwitcher: MobileShell.HomeScreenControls.taskSwitcher } } - MobileShell.NavigationPanelAction { - id: homeAction - - enabled: true - iconSource: "start-here-kde" - iconSizeFactor: 1 - onTriggered: { - MobileShell.HomeScreenControls.openHomeScreen(); - plasmoid.nativeInterface.allMinimizedChanged(); + // bottom navigation gesture area component + Component { + id: navigationGesture + MobileShell.NavigationGestureArea { + taskSwitcher: MobileShell.HomeScreenControls.taskSwitcher } } - MobileShell.NavigationPanelAction { - id: closeAppAction - - enabled: MobileShell.KWinVirtualKeyboard.visible || taskSwitcher.visible || plasmoid.nativeInterface.hasCloseableActiveWindow - // mobile-close-app (from plasma-frameworks) seems to have less margins than icons from breeze-icons - iconSizeFactor: MobileShell.KWinVirtualKeyboard.visible ? 1 : 0.75 - iconSource: MobileShell.KWinVirtualKeyboard.visible ? "go-down-symbolic" : "mobile-close-app" - - onTriggered: { - if (MobileShell.KWinVirtualKeyboard.active) { - // close keyboard if it is open - MobileShell.KWinVirtualKeyboard.active = false; - } else if (taskSwitcher.visible) { - - // if task switcher is open, close the current window shown - taskSwitcher.tasksModel.requestClose(taskSwitcher.tasksModel.index(taskSwitcher.currentTaskIndex, 0)); - - } else if (plasmoid.nativeInterface.hasCloseableActiveWindow) { - - // if task switcher is closed, but there is an active window - var index = taskSwitcher.tasksModel.activeTask; - if (index) { - taskSwitcher.tasksModel.requestClose(index); - } - } - } - } - - // bottom navigation panel - MobileShell.NavigationPanel { - id: panel + // load appropriate system navigation component + Loader { + id: navigationLoader anchors.fill: parent - taskSwitcher: root.taskSwitcher - - backgroundColor: { - if (taskSwitcher.visible) { - return Qt.rgba(0, 0, 0, 0.1); - } else { - return root.showingApp ? root.backgroundColor : "transparent"; - } - } - foregroundColorGroup: (!taskSwitcher.visible && root.showingApp) ? PlasmaCore.Theme.NormalColorGroup : PlasmaCore.Theme.ComplementaryColorGroup - - // do not enable drag gesture when task switcher is already open - // also don't disable drag gesture mid-drag - dragGestureEnabled: !taskSwitcher.visible || taskSwitcher.taskSwitcherState.currentlyBeingOpened - - leftAction: taskSwitcherAction - middleAction: homeAction - rightAction: closeAppAction + sourceComponent: MobileShell.MobileShellSettings.navigationPanelEnabled ? navigationPanel : navigationGesture } + // landscape vs. portrait orientation of panel states: [ State { name: "landscape" - when: Screen.width > Screen.height + when: MobileShell.Shell.orientation === MobileShell.Shell.Landscape PropertyChanges { target: plasmoid.nativeInterface - location: PlasmaCore.Types.RightEdge - } - PropertyChanges { - target: plasmoid - width: PlasmaCore.Units.gridUnit - height: PlasmaCore.Units.gridUnit + // only show on right edge if gestures are not enabled + location: MobileShell.MobileShellSettings.navigationPanelEnabled ? PlasmaCore.Types.RightEdge : PlasmaCore.Types.BottomEdge } }, State { name: "portrait" - when: Screen.width <= Screen.height - PropertyChanges { - target: plasmoid - height: PlasmaCore.Units.gridUnit - } + when: MobileShell.Shell.orientation === MobileShell.Shell.Portrait PropertyChanges { target: plasmoid.nativeInterface location: PlasmaCore.Types.BottomEdge diff --git a/containments/taskpanel/taskpanel.cpp b/containments/taskpanel/taskpanel.cpp index 31a8f895..9dc27b1a 100644 --- a/containments/taskpanel/taskpanel.cpp +++ b/containments/taskpanel/taskpanel.cpp @@ -76,7 +76,7 @@ void TaskPanel::initWayland() return; } m_showingDesktop = showing; - emit showingDesktopChanged(m_showingDesktop); + Q_EMIT showingDesktopChanged(m_showingDesktop); }); connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::activeWindowChanged, m_activeTimer, qOverload<>(&QTimer::start)); @@ -115,10 +115,17 @@ void TaskPanel::setPanel(QWindow *panel) } m_panel = panel; connect(m_panel, &QWindow::visibilityChanged, this, &TaskPanel::updatePanelVisibility, Qt::QueuedConnection); - emit panelChanged(); + Q_EMIT panelChanged(); updatePanelVisibility(); } +void TaskPanel::setPanelHeight(qreal height) +{ + if (m_panel) { + m_panel->setHeight(height); + } +} + void TaskPanel::updatePanelVisibility() { using namespace KWayland::Client; @@ -163,10 +170,10 @@ void TaskPanel::updateActiveWindow() } if (newAllMinimized != m_allMinimized) { m_allMinimized = newAllMinimized; - emit allMinimizedChanged(); + Q_EMIT allMinimizedChanged(); } // TODO: connect to closeableChanged, not needed right now as KWin doesn't provide this changeable - emit hasCloseableActiveWindowChanged(); + Q_EMIT hasCloseableActiveWindowChanged(); } bool TaskPanel::hasCloseableActiveWindow() const @@ -181,7 +188,7 @@ void TaskPanel::forgetActiveWindow() disconnect(m_activeWindow.data(), &KWayland::Client::PlasmaWindow::unmapped, this, &TaskPanel::forgetActiveWindow); } m_activeWindow.clear(); - emit hasCloseableActiveWindowChanged(); + Q_EMIT hasCloseableActiveWindowChanged(); } void TaskPanel::closeActiveWindow() diff --git a/containments/taskpanel/taskpanel.h b/containments/taskpanel/taskpanel.h index e94c9151..085f4c93 100644 --- a/containments/taskpanel/taskpanel.h +++ b/containments/taskpanel/taskpanel.h @@ -41,6 +41,8 @@ public: QWindow *panel(); void setPanel(QWindow *panel); + Q_INVOKABLE void setPanelHeight(qreal height); + Q_INVOKABLE void closeActiveWindow(); bool isShowingDesktop() const diff --git a/kcms/CMakeLists.txt b/kcms/CMakeLists.txt new file mode 100644 index 00000000..82bc4723 --- /dev/null +++ b/kcms/CMakeLists.txt @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2022 Devin Lin +# SPDX-License-Identifier: GPL-2.0-or-later + +add_subdirectory(mobileshell) diff --git a/kcms/mobileshell/.clang-format b/kcms/mobileshell/.clang-format new file mode 100644 index 00000000..cf5ff2c0 --- /dev/null +++ b/kcms/mobileshell/.clang-format @@ -0,0 +1,88 @@ +--- +# SPDX-FileCopyrightText: 2019 Christoph Cullmann +# SPDX-FileCopyrightText: 2019 Gernot Gebhard +# +# SPDX-License-Identifier: MIT + +# This file got automatically created by ECM, do not edit +# See https://clang.llvm.org/docs/ClangFormatStyleOptions.html for the config options +# and https://community.kde.org/Policies/Frameworks_Coding_Style#Clang-format_automatic_code_formatting +# for clang-format tips & tricks +--- +Language: JavaScript +DisableFormat: true +--- + +# Style for C++ +Language: Cpp + +# base is WebKit coding style: https://webkit.org/code-style-guidelines/ +# below are only things set that diverge from this style! +BasedOnStyle: WebKit + +# enforce C++11 (e.g. for std::vector> +Standard: Cpp11 + +# 4 spaces indent +TabWidth: 4 + +# 2 * 80 wide lines +ColumnLimit: 160 + +# sort includes inside line separated groups +SortIncludes: true + +# break before braces on function, namespace and class definitions. +BreakBeforeBraces: Linux + +# CrlInstruction *a; +PointerAlignment: Right + +# horizontally aligns arguments after an open bracket. +AlignAfterOpenBracket: Align + +# don't move all parameters to new line +AllowAllParametersOfDeclarationOnNextLine: false + +# no single line functions +AllowShortFunctionsOnASingleLine: None + +# always break before you encounter multi line strings +AlwaysBreakBeforeMultilineStrings: true + +# don't move arguments to own lines if they are not all on the same +BinPackArguments: false + +# don't move parameters to own lines if they are not all on the same +BinPackParameters: false + +# In case we have an if statement with multiple lines the operator should be at the beginning of the line +# but we do not want to break assignments +BreakBeforeBinaryOperators: NonAssignment + +# format C++11 braced lists like function calls +Cpp11BracedListStyle: true + +# do not put a space before C++11 braced lists +SpaceBeforeCpp11BracedList: false + +# remove empty lines +KeepEmptyLinesAtTheStartOfBlocks: false + +# no namespace indentation to keep indent level low +NamespaceIndentation: None + +# we use template< without space. +SpaceAfterTemplateKeyword: false + +# Always break after template declaration +AlwaysBreakTemplateDeclarations: true + +# macros for which the opening brace stays attached. +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE , wl_resource_for_each, wl_resource_for_each_safe ] + +# keep lambda formatting multi-line if not empty +AllowShortLambdasOnASingleLine: Empty + +# We do not want clang-format to put all arguments on a new line +AllowAllArgumentsOnNextLine: false diff --git a/kcms/mobileshell/CMakeLists.txt b/kcms/mobileshell/CMakeLists.txt new file mode 100644 index 00000000..20d9a079 --- /dev/null +++ b/kcms/mobileshell/CMakeLists.txt @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2022 Devin Lin +# SPDX-License-Identifier: GPL-2.0-or-later + +cmake_minimum_required(VERSION 3.0) + +project(mobileshellkcm) + +set(QT_MIN_VERSION "5.15.0") +set(KF5_MIN_VERSION "5.86.0") + +find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) + +include(KDEInstallDirs) +include(KDECMakeSettings) +include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) + +find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS + Quick + Svg +) + +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS + I18n + KCMUtils + Declarative + Config +) + +set(mobileshellsettings_SRCS + kcm.cpp +) + +add_library(kcm_mobileshell MODULE ${mobileshellsettings_SRCS}) + +target_link_libraries(kcm_mobileshell + Qt5::Core + KF5::CoreAddons + KF5::KCMUtils + KF5::I18n + KF5::QuickAddons +) + +kcoreaddons_desktop_to_json(kcm_mobileshell "package/metadata.desktop") + +install(TARGETS kcm_mobileshell DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) +install(FILES package/metadata.desktop RENAME kcm_mobileshell.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) # Install the desktop file + + +kpackage_install_package(package kcm_mobileshell kcms) # Install our QML kpackage. diff --git a/kcms/mobileshell/kcm.cpp b/kcms/mobileshell/kcm.cpp new file mode 100644 index 00000000..6e169deb --- /dev/null +++ b/kcms/mobileshell/kcm.cpp @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: 2022 Devin Lin + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "kcm.h" + +#include + +K_PLUGIN_CLASS_WITH_JSON(KCMMobileShell, "metadata.json") + +const QString CONFIG_FILE = QStringLiteral("plasmamobilerc"); +const QString GENERAL_CONFIG_GROUP = QStringLiteral("General"); + +KCMMobileShell::KCMMobileShell(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : KQuickAddons::ManagedConfigModule(parent, data, args) + , m_config{KSharedConfig::openConfig("plasmamobilerc", KConfig::SimpleConfig)} +{ + setButtons(0); +} + +bool KCMMobileShell::navigationPanelEnabled() const +{ + auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; + return group.readEntry("navigationPanelEnabled", true); +} + +void KCMMobileShell::setNavigationPanelEnabled(bool navigationPanelEnabled) +{ + auto group = KConfigGroup{m_config, GENERAL_CONFIG_GROUP}; + group.writeEntry("navigationPanelEnabled", navigationPanelEnabled, KConfigGroup::Notify); + m_config->sync(); +} + +#include "kcm.moc" diff --git a/kcms/mobileshell/kcm.h b/kcms/mobileshell/kcm.h new file mode 100644 index 00000000..e0d84970 --- /dev/null +++ b/kcms/mobileshell/kcm.h @@ -0,0 +1,29 @@ +/** + * SPDX-FileCopyrightText: 2022 Devin Lin + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include +#include + +class KCMMobileShell : public KQuickAddons::ManagedConfigModule +{ + Q_OBJECT + Q_PROPERTY(bool navigationPanelEnabled READ navigationPanelEnabled WRITE setNavigationPanelEnabled NOTIFY navigationPanelEnabledChanged) + +public: + KCMMobileShell(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + virtual ~KCMMobileShell() override = default; + + bool navigationPanelEnabled() const; + void setNavigationPanelEnabled(bool navigationPanelEnabled); + +Q_SIGNALS: + void navigationPanelEnabledChanged(); + +private: + KSharedConfig::Ptr m_config; +}; diff --git a/kcms/mobileshell/package/contents/ui/main.qml b/kcms/mobileshell/package/contents/ui/main.qml new file mode 100644 index 00000000..227270c6 --- /dev/null +++ b/kcms/mobileshell/package/contents/ui/main.qml @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2022 Devin Lin + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 as QQC2 + +import org.kde.kirigami 2.19 as Kirigami +import org.kde.kcm 1.3 as KCM + +KCM.SimpleKCM { + id: root + + title: i18n("Shell") + + leftPadding: Kirigami.Units.largeSpacing + rightPadding: Kirigami.Units.largeSpacing + + Kirigami.FormLayout { + id: form + wideMode: false + + Item { + Layout.fillWidth: true + Kirigami.FormData.label: i18n("Navigation Panel") + Kirigami.FormData.isSection: true + } + + QQC2.CheckBox { + Kirigami.FormData.label: i18n("Remove panel (only use gestures):") + Layout.maximumWidth: form.width + text: checked ? i18n("On") : i18n("Off") + checked: !kcm.navigationPanelEnabled + onCheckStateChanged: { + if (checked != !kcm.navigationPanelEnabled) { + kcm.navigationPanelEnabled = !checked; + } + } + } + } +} diff --git a/kcms/mobileshell/package/metadata.desktop b/kcms/mobileshell/package/metadata.desktop new file mode 100644 index 00000000..37b176a9 --- /dev/null +++ b/kcms/mobileshell/package/metadata.desktop @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2022 Devin Lin +# SPDX-License-Identifier: GPL-2.0-or-later + +[Desktop Entry] +Name=Shell +Comment=Configure the system shell +Encoding=UTF-8 +Type=Service +Icon=preferences-desktop-plasma +X-KDE-Library=kcm_mobileshell +X-KDE-ServiceTypes=KCModule +X-KDE-FormFactors=desktop,handset,tablet,mediacenter +X-Plasma-MainScript=ui/main.qml +X-KDE-System-Settings-Parent-Category=personalization +X-KDE-PluginInfo-Author=Devin Lin +X-KDE-PluginInfo-Email=espidev@gmail.com +X-KDE-PluginInfo-Name=kcm_cellular_network +X-KDE-PluginInfo-Version=0.1 +X-KDE-PluginInfo-Website=https://plasma-mobile.org/ +X-KDE-PluginInfo-Category=System Information +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true +X-KDE-Keywords=system,shell,panel +X-KDE-ParentApp=kcontrol +X-KDE-PluginInfo-Name=kcm_mobileshell