diff --git a/components/mobileshell/components/swipearea.h b/components/mobileshell/components/swipearea.h index 3de23451..a3bf899b 100644 --- a/components/mobileshell/components/swipearea.h +++ b/components/mobileshell/components/swipearea.h @@ -44,6 +44,7 @@ public: bool pressed() const; Q_INVOKABLE void setSkipSwipeThreshold(bool value); + Q_INVOKABLE void resetSwipe(); Q_SIGNALS: void modeChanged(); @@ -82,8 +83,6 @@ private: void handleReleaseEvent(QPointerEvent *event, QPointF point); void handleMoveEvent(QPointerEvent *event, QPointF point); - void resetSwipe(); - Mode m_mode = Mode::BothAxis; bool m_interactive = true; bool m_pressed = false; diff --git a/components/mobileshell/qml/actiondrawer/ActionDrawer.qml b/components/mobileshell/qml/actiondrawer/ActionDrawer.qml index d805b757..3427d25f 100644 --- a/components/mobileshell/qml/actiondrawer/ActionDrawer.qml +++ b/components/mobileshell/qml/actiondrawer/ActionDrawer.qml @@ -10,7 +10,6 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 -import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.private.nanoshell 2.0 as NanoShell import org.kde.plasma.private.mobileshell as MobileShell @@ -153,7 +152,7 @@ Item { function cancelAnimations() { root.state = ""; - } + } function open() { cancelAnimations(); @@ -273,35 +272,15 @@ Item { mode: MobileShell.SwipeArea.VerticalOnly anchors.fill: parent - - function startSwipeWithPoint(point) { + function startSwipe() { root.cancelAnimations(); root.dragging = true; // Immediately open action drawer if we interact with it and it's already open // This allows us to have 2 quick flicks from minimized -> expanded - if (root.intendedToBeVisible && !root.opened) { + if (root.visible && !root.opened) { root.opened = true; } - - // if the user swiped from the top left, otherwise it's from the top right - if (!root.intendedToBeVisible) { - if (point.x < root.width / 2) { - root.openToPinnedMode = ShellSettings.Settings.actionDrawerTopLeftMode == ShellSettings.Settings.Pinned; - } else { - root.openToPinnedMode = ShellSettings.Settings.actionDrawerTopRightMode == ShellSettings.Settings.Pinned; - } - - if (root.intendedToBeVisible) { - // ensure the action drawer state is consistent - root.closeImmediately(); - } - - actionDrawer.offset = 0; - actionDrawer.oldOffset = 0; - - intendedToBeVisible = true; - } } function endSwipe() { @@ -313,7 +292,7 @@ Item { root.offset += deltaY; } - onSwipeStarted: (point) => startSwipeWithPoint(point) + onSwipeStarted: startSwipe() onSwipeEnded: endSwipe() onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) @@ -325,8 +304,6 @@ Item { id: contentContainer anchors.fill: parent - opacity: root.opened || swipeArea.moving || drawerAnimation.running || root.offset > 0 || root.intendedToBeVisible ? 1 : 0 - actionDrawer: root quickSettingsModel: root.quickSettingsModel } diff --git a/components/mobileshell/qml/actiondrawer/ActionDrawerWindow.qml b/components/mobileshell/qml/actiondrawer/ActionDrawerWindow.qml index 9feaaa44..48fe6f43 100644 --- a/components/mobileshell/qml/actiondrawer/ActionDrawerWindow.qml +++ b/components/mobileshell/qml/actiondrawer/ActionDrawerWindow.qml @@ -33,28 +33,21 @@ Window { * The ActionDrawer component. */ property alias actionDrawer: drawer - property alias intendedToBeVisible: drawer.intendedToBeVisible property alias state: drawer.state - visible: true + visible: drawer.intendedToBeVisible color: "transparent" - Component.onCompleted: updateTouchArea() + // set input to transparent when closing to prevent window from taking unwanted touch inputs + onStateChanged: ShellUtil.setInputTransparent(window, state == "close") - function updateTouchArea() { - if (state != "" && state != "close") { + onVisibleChanged: { + if (visible) { window.raise(); - ShellUtil.setInputRegion(window, Qt.rect(0, 0, 0, 0)); - } else { - ShellUtil.setInputRegion(window, Qt.rect(0, 0, window.width, MobileShell.Constants.topPanelHeight)); } } - onWidthChanged: updateTouchArea() - onIntendedToBeVisibleChanged: updateTouchArea() - onStateChanged: updateTouchArea() - onActiveChanged: { if (!active) { drawer.close(); diff --git a/components/mobileshell/qml/components/ScreenEdgeDragEffect.qml b/components/mobileshell/qml/components/ScreenEdgeDragEffect.qml new file mode 100644 index 00000000..219590de --- /dev/null +++ b/components/mobileshell/qml/components/ScreenEdgeDragEffect.qml @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2024 Micah Stanley +// SPDX-License-Identifier: GPL-2.0-or-later + +import QtQuick 2.4 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.15 +import QtQuick.Shapes 1.8 + +import org.kde.kirigami 2.20 as Kirigami + +Item { + id: root + + property bool isHorizontal: false + property real length: Kirigami.Units.gridUnit * 10 + property real offsetLimit: Kirigami.Units.gridUnit * 2 + + property real startPoint: 0 + property real sidePoint: 0 + property real offsetPoint: 0 + + visible: offsetPoint != 0 + + Shape { + id: shape + + readonly property int flip: root.offsetPoint > 0 ? -1 : 1 + readonly property real position: root.startPoint - root.length / 2 + readonly property real sp: Math.max(Math.min(root.sidePoint, root.length), -Kirigami.Units.gridUnit * 10) + readonly property real op: Math.max(Math.min(-shape.calculateResistance(-root.offsetPoint, 0), 0), -root.offsetLimit + 3) + + transform: [ + Translate { + x: root.isHorizontal ? 0 : shape.position + y: root.isHorizontal ? shape.position : 0 + } + ] + + function calculateResistance(value : double, threshold : int) : double { + if (value > threshold) { + return threshold + Math.pow(value - threshold + 1, Math.max(0.8 - (value - threshold) / ((root.isHorizontal ? Screen.width : Screen.height - threshold) * 2), 0.65)); + } else { + return value; + } + } + + readonly property var shapPath: [ + Qt.point(3 * shape.flip, 0), + Qt.point(2 * shape.flip, 0), + Qt.point(0, root.length * 0.2 + shape.sp * 0.16), + Qt.point(shape.op, root.length * 0.5 + shape.sp * 0.35), + Qt.point(0, root.length * 0.8 + shape.sp * 0.16), + Qt.point(2 * shape.flip, root.length), + Qt.point(3 * shape.flip, root.length), + ] + + ShapePath { + id: shapeVertical + fillColor: "black" + strokeColor: "black" + + startX: shape.shapPath[0].x; startY: shape.shapPath[0].y + PathCurve { x: root.isHorizontal ? shape.shapPath[1].x : shape.shapPath[1].y; y: root.isHorizontal ? shape.shapPath[1].y : shape.shapPath[1].x} + PathCurve { x: root.isHorizontal ? shape.shapPath[2].x : shape.shapPath[2].y; y: root.isHorizontal ? shape.shapPath[2].y : shape.shapPath[2].x} + PathCurve { x: root.isHorizontal ? shape.shapPath[3].x : shape.shapPath[3].y; y: root.isHorizontal ? shape.shapPath[3].y : shape.shapPath[3].x} + PathCurve { x: root.isHorizontal ? shape.shapPath[4].x : shape.shapPath[4].y; y: root.isHorizontal ? shape.shapPath[4].y : shape.shapPath[4].x} + PathCurve { x: root.isHorizontal ? shape.shapPath[5].x : shape.shapPath[5].y; y: root.isHorizontal ? shape.shapPath[5].y : shape.shapPath[5].x} + PathCurve { x: root.isHorizontal ? shape.shapPath[6].x : shape.shapPath[6].y; y: root.isHorizontal ? shape.shapPath[6].y : shape.shapPath[6].x} + } + } +} diff --git a/components/mobileshell/shellutil.cpp b/components/mobileshell/shellutil.cpp index 9ebd96f3..000e4050 100644 --- a/components/mobileshell/shellutil.cpp +++ b/components/mobileshell/shellutil.cpp @@ -24,6 +24,7 @@ #include #include +#include #define FORMAT24H "HH:mm:ss" @@ -105,6 +106,13 @@ void ShellUtil::setInputTransparent(QQuickWindow *window, bool transparent) { } } +void ShellUtil::setWindowLayer(QQuickWindow *window, LayerShellQt::Window::Layer layer) { + if (window) { + auto layerShellWindow = LayerShellQt::Window::get(window); + layerShellWindow->setLayer(layer); + } +} + void ShellUtil::setInputRegion(QWindow *window, const QRect ®ion) { auto waylandWindow = dynamic_cast(window->handle()); if (!waylandWindow) { diff --git a/components/mobileshell/shellutil.h b/components/mobileshell/shellutil.h index 6b61745c..2b6f56a1 100644 --- a/components/mobileshell/shellutil.h +++ b/components/mobileshell/shellutil.h @@ -15,6 +15,7 @@ #include #include #include +#include /** * Miscellaneous class to put utility functions used in the shell. @@ -71,6 +72,11 @@ public: */ Q_INVOKABLE void setInputTransparent(QQuickWindow *window, bool transparent); + /** + * Set the window layer + */ + Q_INVOKABLE void setWindowLayer(QQuickWindow *window, LayerShellQt::Window::Layer layer); + /** * Sets a region where inputs will get registered on a window. * Inputs outside the region will pass through to the surface below. diff --git a/components/mobileshellstate/shelldbusclient.cpp b/components/mobileshellstate/shelldbusclient.cpp index d672e39c..2f33f5d8 100644 --- a/components/mobileshellstate/shelldbusclient.cpp +++ b/components/mobileshellstate/shelldbusclient.cpp @@ -29,6 +29,7 @@ ShellDBusClient::ShellDBusClient(QObject *parent) void ShellDBusClient::connectSignals() { + connect(m_interface, &OrgKdePlasmashellInterface::panelStateChanged, this, &ShellDBusClient::updatePanelState); connect(m_interface, &OrgKdePlasmashellInterface::isActionDrawerOpenChanged, this, &ShellDBusClient::updateIsActionDrawerOpen); connect(m_interface, &OrgKdePlasmashellInterface::doNotDisturbChanged, this, &ShellDBusClient::updateDoNotDisturb); connect(m_interface, &OrgKdePlasmashellInterface::isTaskSwitcherVisibleChanged, this, &ShellDBusClient::updateIsTaskSwitcherVisible); @@ -47,6 +48,16 @@ void ShellDBusClient::connectSignals() updateIsTaskSwitcherVisible(); } +QString ShellDBusClient::panelState() const +{ + return m_panelState; +} + +void ShellDBusClient::setPanelState(QString state) +{ + m_interface->setPanelState(state); +} + bool ShellDBusClient::doNotDisturb() const { return m_doNotDisturb; @@ -113,6 +124,18 @@ void ShellDBusClient::showVolumeOSD() m_interface->showVolumeOSD(); } +void ShellDBusClient::updatePanelState() +{ + auto reply = m_interface->panelState(); + auto watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](auto watcher) { + QDBusPendingReply reply = *watcher; + m_panelState = reply.argumentAt<0>(); + Q_EMIT panelStateChanged(); + }); +} + void ShellDBusClient::updateDoNotDisturb() { auto reply = m_interface->doNotDisturb(); diff --git a/components/mobileshellstate/shelldbusclient.h b/components/mobileshellstate/shelldbusclient.h index 8daa668d..32906141 100644 --- a/components/mobileshellstate/shelldbusclient.h +++ b/components/mobileshellstate/shelldbusclient.h @@ -19,6 +19,7 @@ class ShellDBusClient : public QObject Q_PROPERTY(bool doNotDisturb READ doNotDisturb WRITE setDoNotDisturb NOTIFY doNotDisturbChanged) Q_PROPERTY(bool isActionDrawerOpen READ isActionDrawerOpen WRITE setIsActionDrawerOpen NOTIFY isActionDrawerOpenChanged) Q_PROPERTY(bool isTaskSwitcherVisible READ isTaskSwitcherVisible NOTIFY isTaskSwitcherVisibleChanged) + Q_PROPERTY(QString panelState READ panelState WRITE setPanelState NOTIFY panelStateChanged) public: explicit ShellDBusClient(QObject *parent = nullptr); @@ -31,6 +32,10 @@ public: bool isTaskSwitcherVisible() const; + QString panelState() const; + void setPanelState(QString state); + + Q_INVOKABLE void openActionDrawer(); Q_INVOKABLE void closeActionDrawer(); @@ -43,6 +48,7 @@ public: Q_INVOKABLE void showVolumeOSD(); Q_SIGNALS: + void panelStateChanged(); void isActionDrawerOpenChanged(); void doNotDisturbChanged(); void isTaskSwitcherVisibleChanged(); @@ -57,6 +63,7 @@ private Q_SLOTS: void updateDoNotDisturb(); void updateIsActionDrawerOpen(); void updateIsTaskSwitcherVisible(); + void updatePanelState(); private: void connectSignals(); @@ -64,6 +71,8 @@ private: OrgKdePlasmashellInterface *m_interface; QDBusServiceWatcher *m_watcher; + QString m_panelState = "default"; + bool m_doNotDisturb = false; bool m_isActionDrawerOpen = false; bool m_isTaskSwitcherVisible = false; diff --git a/components/mobileshellstate/shelldbusobject.cpp b/components/mobileshellstate/shelldbusobject.cpp index ec1be630..8df038db 100644 --- a/components/mobileshellstate/shelldbusobject.cpp +++ b/components/mobileshellstate/shelldbusobject.cpp @@ -39,6 +39,20 @@ void ShellDBusObject::setDoNotDisturb(bool value) } } +QString ShellDBusObject::panelState() +{ + return m_panelState; +} + +void ShellDBusObject::setPanelState(QString state) +{ + if (state != m_panelState) { + m_panelState = state; + Q_EMIT panelStateChanged(); + } +} + + bool ShellDBusObject::isActionDrawerOpen() { return m_isActionDrawerOpen; diff --git a/components/mobileshellstate/shelldbusobject.h b/components/mobileshellstate/shelldbusobject.h index f51185f7..7f8cf7e0 100644 --- a/components/mobileshellstate/shelldbusobject.h +++ b/components/mobileshellstate/shelldbusobject.h @@ -28,6 +28,7 @@ public: Q_SIGNALS: Q_SCRIPTABLE void doNotDisturbChanged(); Q_SCRIPTABLE void isActionDrawerOpenChanged(); + Q_SCRIPTABLE void panelStateChanged(); Q_SCRIPTABLE void isTaskSwitcherVisibleChanged(); Q_SCRIPTABLE void openActionDrawerRequested(); Q_SCRIPTABLE void closeActionDrawerRequested(); @@ -44,6 +45,10 @@ public Q_SLOTS: Q_SCRIPTABLE bool isActionDrawerOpen(); Q_SCRIPTABLE void setIsActionDrawerOpen(bool value); + Q_SCRIPTABLE QString panelState(); + Q_SCRIPTABLE void setPanelState(QString state); + + Q_SCRIPTABLE bool isTaskSwitcherVisible(); Q_SCRIPTABLE void setIsTaskSwitcherVisible(bool value); @@ -65,5 +70,7 @@ private: bool m_isActionDrawerOpen{false}; bool m_isTaskSwitcherVisible{false}; + QString m_panelState{}; + StartupFeedbackModel *m_startupFeedbackModel{nullptr}; }; diff --git a/components/windowplugin/qml/WindowMaximizedTracker.qml b/components/windowplugin/qml/WindowMaximizedTracker.qml index 8737e646..75c44545 100644 --- a/components/windowplugin/qml/WindowMaximizedTracker.qml +++ b/components/windowplugin/qml/WindowMaximizedTracker.qml @@ -14,24 +14,39 @@ QtObject { // Set it to Plasmoid.containment.screenGeometry in a plasmoid to accomplish this. property alias screenGeometry: tasksModel.screenGeometry + property bool isCurrentWindowFullscreen: __internal.count > 0 && visibleWindowsModel.currentFullscreen && !WindowPlugin.WindowUtil.isShowingDesktop + readonly property bool showingWindow: __internal.count > 0 && !WindowPlugin.WindowUtil.isShowingDesktop readonly property int windowCount: __internal.count property var __internal: KItemModels.KSortFilterProxyModel { - id: visibleMaximizedWindowsModel - filterRoleName: 'IsMinimized' - filterString: 'false' - sourceModel: TaskManager.TasksModel { + id: visibleWindowsModel + sourceModel: taskModel + filterRowCallback: (sourceRow, sourceParent) => { + const task = sourceModel.index(sourceRow, 0, sourceParent); + let isFullScreen = sourceModel.data(task, TaskManager.AbstractTasksModel.IsFullScreen); + let isMaximized = sourceModel.data(task, TaskManager.AbstractTasksModel.IsMaximized); + if (sourceRow == 0) { + visibleWindowsModel.currentFullscreen = isFullScreen; + } + return isFullScreen || isMaximized; + } + + property bool currentFullscreen: false + + property var taskModel: TaskManager.TasksModel { id: tasksModel filterByVirtualDesktop: true filterByActivity: true - filterNotMaximized: true + filterMinimized: true filterByScreen: true filterHidden: true virtualDesktop: virtualDesktopInfo.currentDesktop activity: activityInfo.currentActivity + sortMode: TaskManager.TasksModel.SortLastActivated + groupMode: TaskManager.TasksModel.GroupDisabled } diff --git a/containments/panel/package/contents/ui/main.qml b/containments/panel/package/contents/ui/main.qml index 5897251c..c4452a27 100644 --- a/containments/panel/package/contents/ui/main.qml +++ b/containments/panel/package/contents/ui/main.qml @@ -19,34 +19,70 @@ import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin import org.kde.taskmanager as TaskManager import org.kde.notificationmanager as NotificationManager +import org.kde.layershell 1.0 as LayerShell ContainmentItem { id: root Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground Plasmoid.status: PlasmaCore.Types.PassiveStatus // ensure that the panel never takes focus away from the running app - onWidthChanged: maximizeTimer.restart() - // filled in by the shell (Panel.qml) with the plasma-workspace PanelView property var panel: null - onPanelChanged: { - if (panel) { - panel.floating = false; - } + onPanelChanged: setWindowProperties() + + MobileShell.HapticsEffect { + id: haptics } - // Ensure that panel is always the full width of the screen + readonly property real statusPanelHeight: MobileShell.Constants.topPanelHeight + readonly property real intendedWindowThickness: statusPanelHeight + + // use a timer so we don't have to maximize for every single pixel + // - improves performance if the shell is run in a window, and can be resized Timer { id: maximizeTimer running: false interval: 100 - onTriggered: root.panel.maximize() + onTriggered: root.panel.maximize() } + function setWindowProperties() { + if (root.panel) { + root.panel.floating = false; + root.panel.maximize(); // maximize first, then we can apply offsets (otherwise they are overridden) + root.panel.thickness = statusPanelHeight; + MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay) + root.updateTouchArea(); + } + } + + // update the touch area when hidden to minimize the space the panel takes for touch input + function updateTouchArea() { + const hiddenTouchAreaThickness = Kirigami.Units.gridUnit; + + if (statusPanel.state == "hidden") { + MobileShell.ShellUtil.setInputRegion(root.panel, Qt.rect(0, 0, root.panel.width, hiddenTouchAreaThickness)); + } else { + MobileShell.ShellUtil.setInputRegion(root.panel, Qt.rect(0, 0, 0, 0)); + } + } + + + Binding { + target: MobileShellState.ShellDBusClient + property: "isActionDrawerOpen" + value: drawer.visible + } + + // only opaque if there are no maximized windows on this screen readonly property bool showingStartupFeedback: MobileShellState.ShellDBusObject.startupFeedbackModel.activeWindowIsStartupFeedback && windowMaximizedTracker.windowCount === 1 readonly property bool showingApp: windowMaximizedTracker.showingWindow && !showingStartupFeedback readonly property color backgroundColor: topPanel.colorScopeColor + readonly property alias isCurrentWindowFullscreen: windowMaximizedTracker.isCurrentWindowFullscreen + onIsCurrentWindowFullscreenChanged: { + MobileShellState.ShellDBusClient.panelState = isCurrentWindowFullscreen ? "hidden" : "default"; + } WindowPlugin.WindowMaximizedTracker { id: windowMaximizedTracker @@ -89,6 +125,8 @@ ContainmentItem { //END API implementation Component.onCompleted: { + root.setWindowProperties(); + // register dbus MobileShellState.ShellDBusObject.registerObject(); @@ -108,18 +146,119 @@ ContainmentItem { fullHeight: root.height screen: Plasmoid.screen maximizedTracker: windowMaximizedTracker + + visible: !root.isCurrentWindowFullscreen } - // top panel component - MobileShell.StatusBar { - id: topPanel + Rectangle { + id: statusPanel anchors.fill: parent - Kirigami.Theme.colorSet: root.showingApp ? Kirigami.Theme.Header : Kirigami.Theme.Complementary Kirigami.Theme.inherit: false - showDropShadow: !root.showingApp - backgroundColor: !root.showingApp ? "transparent" : root.backgroundColor + color: statusPanel.state == "default" && root.showingApp ? Kirigami.Theme.backgroundColor : "transparent" + + property real offset: 0 + + // top panel component + MobileShell.StatusBar { + id: topPanel + anchors.fill: parent + + showDropShadow: !root.showingApp + backgroundColor: statusPanel.state != "default" ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95) : "transparent" + + transform: [ + Translate { + y: statusPanel.offset + } + ] + } + + state: MobileShellState.ShellDBusClient.panelState + onStateChanged: { + if (statusPanel.state != "hidden") { + root.setWindowProperties(); + hiddenTimer.restart(); + } + } + + Timer { + id: hiddenTimer + running: false + interval: 3000 + onTriggered: { + if (statusPanel.state == "visible") { + MobileShellState.ShellDBusClient.panelState = "hidden"; + } + } + } + + states: [ + State { + name: "default" + PropertyChanges { + target: statusPanel; offset: 0 + } + }, + State { + name: "visible" + PropertyChanges { + target: statusPanel; offset: 0 + } + }, + State { + name: "hidden" + PropertyChanges { + target: statusPanel; offset: -root.statusPanelHeight + } + } + ] + + transitions: Transition { + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + properties: "offset"; easing.type: statusPanel.state == "hidden" ? Easing.InExpo : Easing.OutExpo; duration: Kirigami.Units.longDuration + } + } + ScriptAction { + script: { + root.setWindowProperties(); + } + } + } + } + } + + // swiping area for swipe-down drawer + MobileShell.ActionDrawerOpenSurface { + id: swipeArea + actionDrawer: drawer.actionDrawer + anchors.fill: parent + + readonly property alias drawerVisible: drawer.visible + readonly property alias offset: drawer.actionDrawer.offset + property bool surfacePressed: false + onOffsetChanged: surfacePressed = false + + // allow tapping to bring back up the status bar when it is hidden + onPressedChanged: { + if (!pressed && surfacePressed && root.isCurrentWindowFullscreen) { + haptics.buttonVibrate(); + MobileShellState.ShellDBusClient.panelState = "visible"; + } else { + surfacePressed = true; + } + } + + // if in a fullscreen app, the panels are visible, and the action drawer is opened + // set the panels to a hidden state + onDrawerVisibleChanged: { + if (statusPanel.state == "visible") { + MobileShellState.ShellDBusClient.panelState = "hidden"; + } + } } // swipe-down drawer component diff --git a/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml b/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml index d7aff6e1..deb8c0da 100644 --- a/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml +++ b/containments/taskpanel/package/contents/ui/NavigationPanelComponent.qml @@ -20,12 +20,13 @@ import org.kde.kirigami as Kirigami MobileShell.NavigationPanel { id: root required property bool opaqueBar + required property var navbarState // background is: // - opaque if an app is shown or vkbd is shown // - translucent if the task switcher is open // - transparent if on the homescreen - backgroundColor: (Keyboards.KWinVirtualKeyboard.active || opaqueBar) ? Kirigami.Theme.backgroundColor : "transparent"; + backgroundColor: navbarState != "default" ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95) : "transparent" foregroundColorGroup: opaqueBar ? Kirigami.Theme.Window : Kirigami.Theme.Complementary shadow: !opaqueBar diff --git a/containments/taskpanel/package/contents/ui/main.qml b/containments/taskpanel/package/contents/ui/main.qml index 3d092a72..36c6f3d8 100644 --- a/containments/taskpanel/package/contents/ui/main.qml +++ b/containments/taskpanel/package/contents/ui/main.qml @@ -5,6 +5,7 @@ import QtQuick 2.4 import QtQuick.Layouts 1.1 import QtQuick.Window 2.15 +import QtQuick.Shapes 1.8 import org.kde.kirigami 2.20 as Kirigami @@ -17,6 +18,8 @@ import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin import org.kde.plasma.private.mobileshell.state as MobileShellState +import org.kde.plasma.workspace.keyboardlayout as Keyboards +import org.kde.layershell 1.0 as LayerShell ContainmentItem { id: root @@ -37,6 +40,10 @@ ContainmentItem { } } + MobileShell.HapticsEffect { + id: haptics + } + readonly property bool inLandscape: MobileShell.Constants.navigationPanelOnSide(Screen.width, Screen.height) readonly property real navigationPanelHeight: MobileShell.Constants.navigationPanelThickness @@ -73,7 +80,7 @@ ContainmentItem { interval: 100 onTriggered: { // maximize first, then we can apply offsets (otherwise they are overridden) - root.panel.maximize() + root.panel.maximize(); root.panel.offset = intendedWindowOffset; } } @@ -86,6 +93,23 @@ ContainmentItem { root.panel.offset = intendedWindowOffset; root.panel.thickness = navigationPanelHeight; root.panel.location = intendedWindowLocation; + MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay); + root.updateTouchArea(); + } + } + + // update the touch area when hidden to minimize the space the panel takes for touch input + function updateTouchArea() { + const hiddenTouchAreaThickness = Kirigami.Units.gridUnit; + + if (navigationPanel.state == "hidden") { + if (inLandscape) { + MobileShell.ShellUtil.setInputRegion(root.panel, Qt.rect(root.panel.width - hiddenTouchAreaThickness, 0, hiddenTouchAreaThickness, root.panel.height)); + } else { + MobileShell.ShellUtil.setInputRegion(root.panel, Qt.rect(0, root.panel.height - hiddenTouchAreaThickness, root.panel.width, hiddenTouchAreaThickness)); + } + } else { + MobileShell.ShellUtil.setInputRegion(root.panel, Qt.rect(0, 0, 0, 0)); } } @@ -112,7 +136,9 @@ ContainmentItem { // only opaque if there are no maximized windows on this screen readonly property bool showingStartupFeedback: MobileShellState.ShellDBusObject.startupFeedbackModel.activeWindowIsStartupFeedback && windowMaximizedTracker.windowCount === 1 - readonly property bool opaqueBar: windowMaximizedTracker.showingWindow && !showingStartupFeedback + readonly property bool opaqueBar: (windowMaximizedTracker.showingWindow || isCurrentWindowFullscreen) && !showingStartupFeedback + + readonly property alias isCurrentWindowFullscreen: windowMaximizedTracker.isCurrentWindowFullscreen WindowPlugin.WindowMaximizedTracker { id: windowMaximizedTracker @@ -128,21 +154,161 @@ ContainmentItem { fullHeight: root.height screen: Plasmoid.screen maximizedTracker: windowMaximizedTracker + + visible: !root.isCurrentWindowFullscreen } - Item { + Rectangle { + id: navigationPanel anchors.fill: parent - // contrasting colour - Kirigami.Theme.colorSet: opaqueBar ? Kirigami.Theme.Window : Kirigami.Theme.Complementary + Kirigami.Theme.colorSet: root.opaqueBar ? Kirigami.Theme.Window : Kirigami.Theme.Complementary Kirigami.Theme.inherit: false + color: navigationPanel.state == "default" && (Keyboards.KWinVirtualKeyboard.active || root.opaqueBar) ? Kirigami.Theme.backgroundColor : "transparent" + + property real offset: 0 + // load appropriate system navigation component NavigationPanelComponent { - id: navigationPanel anchors.fill: parent opaqueBar: root.opaqueBar isVertical: root.inLandscape + navbarState: navigationPanel.state + + transform: [ + Translate { + y: inLandscape ? 0 : navigationPanel.offset + x: inLandscape ? navigationPanel.offset : 0 + } + ] + } + + state: MobileShellState.ShellDBusClient.panelState + onStateChanged: { + if (navigationPanel.state != "hidden") { + root.setWindowProperties(); + } + } + + states: [ + State { + name: "default" + PropertyChanges { + target: navigationPanel; offset: 0 + } + }, + State { + name: "visible" + PropertyChanges { + target: navigationPanel; offset: 0 + } + }, + State { + name: "hidden" + PropertyChanges { + target: navigationPanel; offset: root.navigationPanelHeight + } + } + ] + + transitions: Transition { + SequentialAnimation { + ParallelAnimation { + PropertyAnimation { + properties: "offset"; easing.type: navigationPanel.state == "hidden" ? Easing.InExpo : Easing.OutExpo; duration: Kirigami.Units.longDuration + } + } + ScriptAction { + script: { + root.setWindowProperties(); + } + } + } + } + } + + MobileShell.SwipeArea { + id: swipeArea + mode: inLandscape ? MobileShell.SwipeArea.HorizontalOnly : MobileShell.SwipeArea.VerticalOnly + anchors.fill: navigationPanel + enabled: navigationPanel.state == "hidden" + + function startSwipeWithPoint(point) { + root.setWindowProperties(); + resetAn.stop(); + dragEffect.startPoint = inLandscape ? point.y - Screen.height / 2 : point.x - Screen.width / 2; + dragEffect.sidePoint = 0 + dragEffect.offsetPoint = 0; + } + + function updateOffset(offsetX, offsetY) { + dragEffect.sidePoint = inLandscape ? offsetY : offsetX; + dragEffect.offsetPoint = Math.min(0, inLandscape ? offsetX : offsetY); + if (dragEffect.offsetPoint < -Kirigami.Units.gridUnit * 5 && navigationPanel.state == "hidden") { + swipeArea.resetSwipe(); + resetAn.restart(); + haptics.buttonVibrate(); + MobileShellState.ShellDBusClient.panelState = "visible"; + } + } + + onSwipeStarted: (point) => startSwipeWithPoint(point) + onSwipeEnded: resetAn.start() + onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => updateOffset(totalDeltaX, totalDeltaY); + + onPressedChanged: { + if (!pressed && dragEffect.offsetPoint == 0) { + haptics.buttonVibrate(); + MobileShellState.ShellDBusClient.panelState = "visible"; + } + } + + NumberAnimation { + id: resetAn + running: false + target: dragEffect + property: "offsetPoint" + to: 0 + duration: Kirigami.Units.longDuration * 1.5 + easing.type: Easing.OutExpo + onRunningChanged: { + if (!running && navigationPanel.state == "hidden") { + root.setWindowProperties(); + } + } + } + + MobileShell.ScreenEdgeDragEffect { + id: dragEffect + + offsetLimit: root.inLandscape ? swipeArea.width : swipeArea.height + isHorizontal: root.inLandscape + + states: [ + State { + name: "vertical" + when: !root.inLandscape + AnchorChanges { + target: dragEffect + anchors.right: undefined + anchors.bottom: swipeArea.bottom + anchors.horizontalCenter: swipeArea.horizontalCenter + anchors.verticalCenter: undefined + } + }, + State { + name: "horizontal" + when: root.inLandscape + AnchorChanges { + target: dragEffect + anchors.right: swipeArea.right + anchors.bottom: undefined + anchors.horizontalCenter: undefined + anchors.verticalCenter: swipeArea.verticalCenter + } + } + ] } } }