navigationpanel: Add ability to toggle between gesture only and shown navigation panel modes

Addresses: https://invent.kde.org/plasma/plasma-phone-components/-/issues/140
This commit is contained in:
Devin Lin 2022-02-13 04:23:57 +00:00
parent 6d6b38ac46
commit 8f9f722ca7
27 changed files with 690 additions and 151 deletions

View file

@ -69,6 +69,7 @@ add_subdirectory(bin)
add_subdirectory(containments) add_subdirectory(containments)
add_subdirectory(components) add_subdirectory(components)
add_subdirectory(quicksettings) add_subdirectory(quicksettings)
add_subdirectory(kcms)
find_program(PlasmaOpenSettings plasma-open-settings) find_program(PlasmaOpenSettings plasma-open-settings)
set_package_properties(PlasmaOpenSettings PROPERTIES set_package_properties(PlasmaOpenSettings PROPERTIES

21
LICENSES/MIT.txt Normal file
View file

@ -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.

View file

@ -33,8 +33,8 @@ Item {
appDrawerFlickable: appDrawer.flickable appDrawerFlickable: appDrawer.flickable
availableScreenHeight: height - (MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0) availableScreenHeight: height - MobileShell.Shell.bottomMargin
availableScreenWidth: width - (MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth) availableScreenWidth: width - MobileShell.Shell.leftMargin - MobileShell.Shell.rightMargin
appDrawerBottomOffset: favoriteStrip.height appDrawerBottomOffset: favoriteStrip.height
} }
@ -73,9 +73,10 @@ Item {
// account for panels // account for panels
anchors.fill: parent anchors.fill: parent
anchors.topMargin: MobileShell.TopPanelControls.panelHeight anchors.topMargin: MobileShell.Shell.topMargin
anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth anchors.bottomMargin: MobileShell.Shell.bottomMargin
anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 anchors.leftMargin: MobileShell.Shell.leftMargin
anchors.rightMargin: MobileShell.Shell.rightMargin
// animation when app drawer is being shown // animation when app drawer is being shown
opacity: root.appDrawer ? 1 - root.appDrawer.openFactor : 1 opacity: root.appDrawer ? 1 - root.appDrawer.openFactor : 1
@ -129,9 +130,10 @@ Item {
homeScreenState: root.homeScreenState homeScreenState: root.homeScreenState
// account for panels // account for panels
topPadding: MobileShell.TopPanelControls.panelHeight topPadding: MobileShell.Shell.topMargin
rightPadding: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth bottomPadding: MobileShell.Shell.bottomMargin
bottomPadding: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 leftPadding: MobileShell.Shell.leftMargin
rightPadding: MobileShell.Shell.rightMargin
} }
} }
} }

View file

@ -9,6 +9,7 @@ set(mobileshellplugin_SRCS
mobileshellplugin.cpp mobileshellplugin.cpp
shellutil.cpp shellutil.cpp
quicksettingsmodel.cpp quicksettingsmodel.cpp
mobileshellsettings.cpp
vkbdinterface.cpp vkbdinterface.cpp
displaysmodel.cpp displaysmodel.cpp

View file

@ -10,6 +10,7 @@
#include <QQuickItem> #include <QQuickItem>
#include "displaysmodel.h" #include "displaysmodel.h"
#include "mobileshellsettings.h"
#include "notifications/notificationfilemenu.h" #include "notifications/notificationfilemenu.h"
#include "notifications/notificationthumbnailer.h" #include "notifications/notificationthumbnailer.h"
#include "quicksettingsmodel.h" #include "quicksettingsmodel.h"
@ -25,6 +26,10 @@ void MobileShellPlugin::registerTypes(const char *uri)
return ShellUtil::instance(); return ShellUtil::instance();
}); });
qmlRegisterSingletonType<MobileShellSettings>(uri, 1, 0, "MobileShellSettings", [](QQmlEngine *, QJSEngine *) -> QObject * {
return MobileShellSettings::self();
});
qmlRegisterType<QuickSetting>(uri, 1, 0, "QuickSetting"); qmlRegisterType<QuickSetting>(uri, 1, 0, "QuickSetting");
qmlRegisterType<QuickSettingsModel>(uri, 1, 0, "QuickSettingsModel"); qmlRegisterType<QuickSettingsModel>(uri, 1, 0, "QuickSettingsModel");

View file

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
*
* 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);
}

View file

@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <KConfigGroup>
#include <KConfigWatcher>
#include <KSharedConfig>
#include <QObject>
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;
};

View file

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
*
* 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
}
}

View file

@ -11,6 +11,7 @@ pragma Singleton
/** /**
* Provides access to the taskpanel plasmoid containment within the shell. * Provides access to the taskpanel plasmoid containment within the shell.
* Properties are updated by the taskpanel containment.
*/ */
QtObject { QtObject {
id: root id: root

View file

@ -0,0 +1,74 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
*
* 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();
}
}
}
}

View file

@ -20,6 +20,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell
Item { Item {
id: root id: root
property bool shadow: false
property color backgroundColor property color backgroundColor
property var foregroundColorGroup property var foregroundColorGroup
@ -32,7 +33,7 @@ Item {
DropShadow { DropShadow {
anchors.fill: mouseArea anchors.fill: mouseArea
visible: !showingApp visible: shadow
cached: true cached: true
horizontalOffset: 0 horizontalOffset: 0
verticalOffset: 1 verticalOffset: 1
@ -166,7 +167,7 @@ Item {
states: [ states: [
State { State {
name: "landscape" name: "landscape"
when: Screen.width > Screen.height when: root.width < root.height
PropertyChanges { PropertyChanges {
target: icons target: icons
buttonLength: icons.height * 0.8 / 3 buttonLength: icons.height * 0.8 / 3
@ -204,7 +205,7 @@ Item {
} }
}, State { }, State {
name: "portrait" name: "portrait"
when: Screen.width <= Screen.height when: root.width >= root.height
PropertyChanges { PropertyChanges {
target: icons target: icons
buttonLength: icons.width * 0.8 / 3 buttonLength: icons.width * 0.8 / 3

View file

@ -23,6 +23,7 @@ singleton VolumeProvider 1.0 dataproviders/VolumeProvider.qml
singleton WifiProvider 1.0 dataproviders/WifiProvider.qml singleton WifiProvider 1.0 dataproviders/WifiProvider.qml
# /navigationpanel # /navigationpanel
NavigationGestureArea 1.0 navigationpanel/NavigationGestureArea.qml
NavigationPanel 1.0 navigationpanel/NavigationPanel.qml NavigationPanel 1.0 navigationpanel/NavigationPanel.qml
NavigationPanelAction 1.0 navigationpanel/NavigationPanelAction.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 HomeScreenControls 1.0 HomeScreenControls.qml
singleton Shell 1.0 Shell.qml
singleton TaskPanelControls 1.0 TaskPanelControls.qml singleton TaskPanelControls 1.0 TaskPanelControls.qml
singleton TopPanelControls 1.0 TopPanelControls.qml singleton TopPanelControls 1.0 TopPanelControls.qml

View file

@ -71,8 +71,8 @@ Item {
// account for system header and footer offset (center the preview image) // account for system header and footer offset (center the preview image)
y: { y: {
let headerHeight = MobileShell.TopPanelControls.panelHeight; let headerHeight = MobileShell.Shell.topMargin;
let footerHeight = MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0; let footerHeight = MobileShell.Shell.bottomMargin;
let diff = headerHeight - footerHeight; let diff = headerHeight - footerHeight;
let baseY = (taskSwitcher.height / 2) - (height / 2) - (taskSwitcherState.taskHeaderHeight / 2) let baseY = (taskSwitcher.height / 2) - (height / 2) - (taskSwitcherState.taskHeaderHeight / 2)

View file

@ -166,9 +166,10 @@ Item {
// provide shell margins // provide shell margins
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth anchors.leftMargin: MobileShell.Shell.leftMargin
anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 anchors.rightMargin: MobileShell.Shell.rightMargin
anchors.topMargin: MobileShell.TopPanelControls.panelHeight anchors.bottomMargin: MobileShell.Shell.bottomMargin
anchors.topMargin: MobileShell.Shell.topMargin
FlickContainer { FlickContainer {
id: flickable id: flickable

View file

@ -70,8 +70,8 @@ QtObject {
// ~~ measurement constants ~~ // ~~ measurement constants ~~
// dimensions of a real window on the screen // 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 windowHeight: taskSwitcher.height - MobileShell.Shell.topMargin - MobileShell.Shell.bottomMargin
readonly property real windowWidth: taskSwitcher.width - (MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth) readonly property real windowWidth: taskSwitcher.width - MobileShell.Shell.leftMargin - MobileShell.Shell.rightMargin
// dimensions of the task previews // dimensions of the task previews
readonly property real previewHeight: windowHeight * scalingFactor readonly property real previewHeight: windowHeight * scalingFactor

View file

@ -75,9 +75,10 @@ Item {
id: flickable id: flickable
anchors.fill: parent anchors.fill: parent
anchors.topMargin: MobileShell.TopPanelControls.panelHeight anchors.topMargin: MobileShell.Shell.topMargin
anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 anchors.bottomMargin: MobileShell.Shell.bottomMargin
anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth anchors.leftMargin: MobileShell.Shell.leftMargin
anchors.rightMargin: MobileShell.Shell.rightMargin
contentHeight: flickable.height + root.closedContentY + 999999 contentHeight: flickable.height + root.closedContentY + 999999
contentY: root.closedContentY contentY: root.closedContentY

View file

@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Devin Lin <devin@kde.org>
*
* 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);
}
}
}
}
}

View file

@ -7,7 +7,7 @@
import QtQuick 2.4 import QtQuick 2.4
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Window 2.2 import QtQuick.Window 2.15
import QtGraphicalEffects 1.12 import QtGraphicalEffects 1.12
import org.kde.taskmanager 0.1 as TaskManager import org.kde.taskmanager 0.1 as TaskManager
@ -21,16 +21,31 @@ import org.kde.plasma.phone.taskpanel 1.0 as TaskPanel
PlasmaCore.ColorScope { PlasmaCore.ColorScope {
id: root id: root
colorGroup: showingApp ? PlasmaCore.Theme.HeaderColorGroup : PlasmaCore.Theme.ComplementaryColorGroup width: 360
// contrasting colour
colorGroup: !plasmoid.nativeInterface.allMinimized ? PlasmaCore.Theme.HeaderColorGroup : PlasmaCore.Theme.ComplementaryColorGroup
readonly property color backgroundColor: PlasmaCore.ColorScope.backgroundColor
Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground
readonly property color backgroundColor: PlasmaCore.ColorScope.backgroundColor // toggle visibility of navigation bar (show, or use gestures only)
readonly property bool showingApp: !plasmoid.nativeInterface.allMinimized Binding {
target: plasmoid.Window.window // assumed to be plasma-workspace "PanelView" component
readonly property bool hasTasks: tasksModel.count > 0 property: "visibilityMode"
// 0 - VisibilityMode.NormalPanel
property var taskSwitcher: MobileShell.HomeScreenControls.taskSwitcher // 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 //BEGIN API implementation
@ -42,16 +57,14 @@ PlasmaCore.ColorScope {
Binding { Binding {
target: MobileShell.TaskPanelControls target: MobileShell.TaskPanelControls
property: "panelHeight" property: "panelHeight"
value: root.height value: MobileShell.MobileShellSettings.navigationPanelEnabled ? root.height : 0
} }
Binding { Binding {
target: MobileShell.TaskPanelControls target: MobileShell.TaskPanelControls
property: "panelWidth" property: "panelWidth"
value: root.width value: MobileShell.MobileShellSettings.navigationPanelEnabled ? root.width : 0
} }
//END API implementation
Connections { Connections {
target: plasmoid.nativeInterface target: plasmoid.nativeInterface
function onAllMinimizedChanged() { function onAllMinimizedChanged() {
@ -59,143 +72,54 @@ PlasmaCore.ColorScope {
} }
} }
TaskManager.TasksModel { //END API implementation
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
}
Window.onWindowChanged: { Window.onWindowChanged: {
if (!Window.window) if (!Window.window)
return; return;
// ensure that Plasma sets the correct offset
Window.window.offset = Qt.binding(() => { 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 // bottom navigation panel component
MobileShell.NavigationPanelAction { Component {
id: taskSwitcherAction id: navigationPanel
NavigationPanelComponent {
enabled: hasTasks || taskSwitcher.visible taskSwitcher: MobileShell.HomeScreenControls.taskSwitcher
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();
}
}
} }
} }
MobileShell.NavigationPanelAction { // bottom navigation gesture area component
id: homeAction Component {
id: navigationGesture
enabled: true MobileShell.NavigationGestureArea {
iconSource: "start-here-kde" taskSwitcher: MobileShell.HomeScreenControls.taskSwitcher
iconSizeFactor: 1
onTriggered: {
MobileShell.HomeScreenControls.openHomeScreen();
plasmoid.nativeInterface.allMinimizedChanged();
} }
} }
MobileShell.NavigationPanelAction { // load appropriate system navigation component
id: closeAppAction Loader {
id: navigationLoader
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
anchors.fill: parent anchors.fill: parent
taskSwitcher: root.taskSwitcher sourceComponent: MobileShell.MobileShellSettings.navigationPanelEnabled ? navigationPanel : navigationGesture
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
} }
// landscape vs. portrait orientation of panel
states: [ states: [
State { State {
name: "landscape" name: "landscape"
when: Screen.width > Screen.height when: MobileShell.Shell.orientation === MobileShell.Shell.Landscape
PropertyChanges { PropertyChanges {
target: plasmoid.nativeInterface target: plasmoid.nativeInterface
location: PlasmaCore.Types.RightEdge // only show on right edge if gestures are not enabled
} location: MobileShell.MobileShellSettings.navigationPanelEnabled ? PlasmaCore.Types.RightEdge : PlasmaCore.Types.BottomEdge
PropertyChanges {
target: plasmoid
width: PlasmaCore.Units.gridUnit
height: PlasmaCore.Units.gridUnit
} }
}, State { }, State {
name: "portrait" name: "portrait"
when: Screen.width <= Screen.height when: MobileShell.Shell.orientation === MobileShell.Shell.Portrait
PropertyChanges {
target: plasmoid
height: PlasmaCore.Units.gridUnit
}
PropertyChanges { PropertyChanges {
target: plasmoid.nativeInterface target: plasmoid.nativeInterface
location: PlasmaCore.Types.BottomEdge location: PlasmaCore.Types.BottomEdge

View file

@ -76,7 +76,7 @@ void TaskPanel::initWayland()
return; return;
} }
m_showingDesktop = showing; m_showingDesktop = showing;
emit showingDesktopChanged(m_showingDesktop); Q_EMIT showingDesktopChanged(m_showingDesktop);
}); });
connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::activeWindowChanged, m_activeTimer, qOverload<>(&QTimer::start)); connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::activeWindowChanged, m_activeTimer, qOverload<>(&QTimer::start));
@ -115,10 +115,17 @@ void TaskPanel::setPanel(QWindow *panel)
} }
m_panel = panel; m_panel = panel;
connect(m_panel, &QWindow::visibilityChanged, this, &TaskPanel::updatePanelVisibility, Qt::QueuedConnection); connect(m_panel, &QWindow::visibilityChanged, this, &TaskPanel::updatePanelVisibility, Qt::QueuedConnection);
emit panelChanged(); Q_EMIT panelChanged();
updatePanelVisibility(); updatePanelVisibility();
} }
void TaskPanel::setPanelHeight(qreal height)
{
if (m_panel) {
m_panel->setHeight(height);
}
}
void TaskPanel::updatePanelVisibility() void TaskPanel::updatePanelVisibility()
{ {
using namespace KWayland::Client; using namespace KWayland::Client;
@ -163,10 +170,10 @@ void TaskPanel::updateActiveWindow()
} }
if (newAllMinimized != m_allMinimized) { if (newAllMinimized != m_allMinimized) {
m_allMinimized = newAllMinimized; m_allMinimized = newAllMinimized;
emit allMinimizedChanged(); Q_EMIT allMinimizedChanged();
} }
// TODO: connect to closeableChanged, not needed right now as KWin doesn't provide this changeable // TODO: connect to closeableChanged, not needed right now as KWin doesn't provide this changeable
emit hasCloseableActiveWindowChanged(); Q_EMIT hasCloseableActiveWindowChanged();
} }
bool TaskPanel::hasCloseableActiveWindow() const bool TaskPanel::hasCloseableActiveWindow() const
@ -181,7 +188,7 @@ void TaskPanel::forgetActiveWindow()
disconnect(m_activeWindow.data(), &KWayland::Client::PlasmaWindow::unmapped, this, &TaskPanel::forgetActiveWindow); disconnect(m_activeWindow.data(), &KWayland::Client::PlasmaWindow::unmapped, this, &TaskPanel::forgetActiveWindow);
} }
m_activeWindow.clear(); m_activeWindow.clear();
emit hasCloseableActiveWindowChanged(); Q_EMIT hasCloseableActiveWindowChanged();
} }
void TaskPanel::closeActiveWindow() void TaskPanel::closeActiveWindow()

View file

@ -41,6 +41,8 @@ public:
QWindow *panel(); QWindow *panel();
void setPanel(QWindow *panel); void setPanel(QWindow *panel);
Q_INVOKABLE void setPanelHeight(qreal height);
Q_INVOKABLE void closeActiveWindow(); Q_INVOKABLE void closeActiveWindow();
bool isShowingDesktop() const bool isShowingDesktop() const

4
kcms/CMakeLists.txt Normal file
View file

@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later
add_subdirectory(mobileshell)

View file

@ -0,0 +1,88 @@
---
# SPDX-FileCopyrightText: 2019 Christoph Cullmann <cullmann@kde.org>
# SPDX-FileCopyrightText: 2019 Gernot Gebhard <gebhard@absint.com>
#
# 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<std::vector<lala>>
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

View file

@ -0,0 +1,50 @@
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
# 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.

35
kcms/mobileshell/kcm.cpp Normal file
View file

@ -0,0 +1,35 @@
/**
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kcm.h"
#include <KPluginFactory>
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"

29
kcms/mobileshell/kcm.h Normal file
View file

@ -0,0 +1,29 @@
/**
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <KConfigGroup>
#include <KQuickAddons/ManagedConfigModule>
#include <KSharedConfig>
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;
};

View file

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
* 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;
}
}
}
}
}

View file

@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
# 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