taskpanel/panel: make navbar and statusbar accessible from within fullscreen windows

These changes make the navigation and status bar accessible from within fullscreen applications by allowing the user to tap on the top or tap or drag on the bottom of the screen.

Also, since the top panel is now over top of all fullscreen applications, this moves the action drawer swipe area back into the status bar.

![navandstatusbar](/uploads/29bce14baf957059669689345c909896/navandstatusbar.gif)
This commit is contained in:
Micah Stanley 2024-11-25 17:30:47 +00:00 committed by Devin Lin
parent 6aa5a2e0e0
commit 48dedcd546
14 changed files with 495 additions and 67 deletions

View file

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

View file

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

View file

@ -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();

View file

@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2024 Micah Stanley <stanleymicah@proton.me>
// 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}
}
}
}

View file

@ -24,6 +24,7 @@
#include <QTextDocumentFragment>
#include <QtWaylandClient/private/qwaylandwindow_p.h>
#include <LayerShellQt/Window>
#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 &region) {
auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
if (!waylandWindow) {

View file

@ -15,6 +15,7 @@
#include <KConfigWatcher>
#include <KIO/ApplicationLauncherJob>
#include <KSharedConfig>
#include <LayerShellQt/Window>
/**
* 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.

View file

@ -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<QString> reply = *watcher;
m_panelState = reply.argumentAt<0>();
Q_EMIT panelStateChanged();
});
}
void ShellDBusClient::updateDoNotDisturb()
{
auto reply = m_interface->doNotDisturb();

View file

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

View file

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

View file

@ -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};
};

View file

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

View file

@ -19,23 +19,26 @@ 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
@ -43,10 +46,43 @@ ContainmentItem {
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
}
Rectangle {
id: statusPanel
anchors.fill: parent
Kirigami.Theme.colorSet: root.showingApp ? Kirigami.Theme.Header : Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false
color: statusPanel.state == "default" && root.showingApp ? Kirigami.Theme.backgroundColor : "transparent"
property real offset: 0
// top panel component
MobileShell.StatusBar {
id: topPanel
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
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

View file

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

View file

@ -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
}
}
]
}
}
}