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; bool pressed() const;
Q_INVOKABLE void setSkipSwipeThreshold(bool value); Q_INVOKABLE void setSkipSwipeThreshold(bool value);
Q_INVOKABLE void resetSwipe();
Q_SIGNALS: Q_SIGNALS:
void modeChanged(); void modeChanged();
@ -82,8 +83,6 @@ private:
void handleReleaseEvent(QPointerEvent *event, QPointF point); void handleReleaseEvent(QPointerEvent *event, QPointF point);
void handleMoveEvent(QPointerEvent *event, QPointF point); void handleMoveEvent(QPointerEvent *event, QPointF point);
void resetSwipe();
Mode m_mode = Mode::BothAxis; Mode m_mode = Mode::BothAxis;
bool m_interactive = true; bool m_interactive = true;
bool m_pressed = false; bool m_pressed = false;

View file

@ -10,7 +10,6 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Window 2.2 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.components 3.0 as PlasmaComponents
import org.kde.plasma.private.nanoshell 2.0 as NanoShell import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell as MobileShell
@ -153,7 +152,7 @@ Item {
function cancelAnimations() { function cancelAnimations() {
root.state = ""; root.state = "";
} }
function open() { function open() {
cancelAnimations(); cancelAnimations();
@ -273,35 +272,15 @@ Item {
mode: MobileShell.SwipeArea.VerticalOnly mode: MobileShell.SwipeArea.VerticalOnly
anchors.fill: parent anchors.fill: parent
function startSwipe() {
function startSwipeWithPoint(point) {
root.cancelAnimations(); root.cancelAnimations();
root.dragging = true; root.dragging = true;
// Immediately open action drawer if we interact with it and it's already open // 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 // This allows us to have 2 quick flicks from minimized -> expanded
if (root.intendedToBeVisible && !root.opened) { if (root.visible && !root.opened) {
root.opened = true; 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() { function endSwipe() {
@ -313,7 +292,7 @@ Item {
root.offset += deltaY; root.offset += deltaY;
} }
onSwipeStarted: (point) => startSwipeWithPoint(point) onSwipeStarted: startSwipe()
onSwipeEnded: endSwipe() onSwipeEnded: endSwipe()
onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY)
@ -325,8 +304,6 @@ Item {
id: contentContainer id: contentContainer
anchors.fill: parent anchors.fill: parent
opacity: root.opened || swipeArea.moving || drawerAnimation.running || root.offset > 0 || root.intendedToBeVisible ? 1 : 0
actionDrawer: root actionDrawer: root
quickSettingsModel: root.quickSettingsModel quickSettingsModel: root.quickSettingsModel
} }

View file

@ -33,28 +33,21 @@ Window {
* The ActionDrawer component. * The ActionDrawer component.
*/ */
property alias actionDrawer: drawer property alias actionDrawer: drawer
property alias intendedToBeVisible: drawer.intendedToBeVisible
property alias state: drawer.state property alias state: drawer.state
visible: true visible: drawer.intendedToBeVisible
color: "transparent" 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() { onVisibleChanged: {
if (state != "" && state != "close") { if (visible) {
window.raise(); 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: { onActiveChanged: {
if (!active) { if (!active) {
drawer.close(); 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 <QTextDocumentFragment>
#include <QtWaylandClient/private/qwaylandwindow_p.h> #include <QtWaylandClient/private/qwaylandwindow_p.h>
#include <LayerShellQt/Window>
#define FORMAT24H "HH:mm:ss" #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) { void ShellUtil::setInputRegion(QWindow *window, const QRect &region) {
auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle()); auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
if (!waylandWindow) { if (!waylandWindow) {

View file

@ -15,6 +15,7 @@
#include <KConfigWatcher> #include <KConfigWatcher>
#include <KIO/ApplicationLauncherJob> #include <KIO/ApplicationLauncherJob>
#include <KSharedConfig> #include <KSharedConfig>
#include <LayerShellQt/Window>
/** /**
* Miscellaneous class to put utility functions used in the shell. * Miscellaneous class to put utility functions used in the shell.
@ -71,6 +72,11 @@ public:
*/ */
Q_INVOKABLE void setInputTransparent(QQuickWindow *window, bool transparent); 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. * Sets a region where inputs will get registered on a window.
* Inputs outside the region will pass through to the surface below. * Inputs outside the region will pass through to the surface below.

View file

@ -29,6 +29,7 @@ ShellDBusClient::ShellDBusClient(QObject *parent)
void ShellDBusClient::connectSignals() void ShellDBusClient::connectSignals()
{ {
connect(m_interface, &OrgKdePlasmashellInterface::panelStateChanged, this, &ShellDBusClient::updatePanelState);
connect(m_interface, &OrgKdePlasmashellInterface::isActionDrawerOpenChanged, this, &ShellDBusClient::updateIsActionDrawerOpen); connect(m_interface, &OrgKdePlasmashellInterface::isActionDrawerOpenChanged, this, &ShellDBusClient::updateIsActionDrawerOpen);
connect(m_interface, &OrgKdePlasmashellInterface::doNotDisturbChanged, this, &ShellDBusClient::updateDoNotDisturb); connect(m_interface, &OrgKdePlasmashellInterface::doNotDisturbChanged, this, &ShellDBusClient::updateDoNotDisturb);
connect(m_interface, &OrgKdePlasmashellInterface::isTaskSwitcherVisibleChanged, this, &ShellDBusClient::updateIsTaskSwitcherVisible); connect(m_interface, &OrgKdePlasmashellInterface::isTaskSwitcherVisibleChanged, this, &ShellDBusClient::updateIsTaskSwitcherVisible);
@ -47,6 +48,16 @@ void ShellDBusClient::connectSignals()
updateIsTaskSwitcherVisible(); updateIsTaskSwitcherVisible();
} }
QString ShellDBusClient::panelState() const
{
return m_panelState;
}
void ShellDBusClient::setPanelState(QString state)
{
m_interface->setPanelState(state);
}
bool ShellDBusClient::doNotDisturb() const bool ShellDBusClient::doNotDisturb() const
{ {
return m_doNotDisturb; return m_doNotDisturb;
@ -113,6 +124,18 @@ void ShellDBusClient::showVolumeOSD()
m_interface->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() void ShellDBusClient::updateDoNotDisturb()
{ {
auto reply = m_interface->doNotDisturb(); 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 doNotDisturb READ doNotDisturb WRITE setDoNotDisturb NOTIFY doNotDisturbChanged)
Q_PROPERTY(bool isActionDrawerOpen READ isActionDrawerOpen WRITE setIsActionDrawerOpen NOTIFY isActionDrawerOpenChanged) Q_PROPERTY(bool isActionDrawerOpen READ isActionDrawerOpen WRITE setIsActionDrawerOpen NOTIFY isActionDrawerOpenChanged)
Q_PROPERTY(bool isTaskSwitcherVisible READ isTaskSwitcherVisible NOTIFY isTaskSwitcherVisibleChanged) Q_PROPERTY(bool isTaskSwitcherVisible READ isTaskSwitcherVisible NOTIFY isTaskSwitcherVisibleChanged)
Q_PROPERTY(QString panelState READ panelState WRITE setPanelState NOTIFY panelStateChanged)
public: public:
explicit ShellDBusClient(QObject *parent = nullptr); explicit ShellDBusClient(QObject *parent = nullptr);
@ -31,6 +32,10 @@ public:
bool isTaskSwitcherVisible() const; bool isTaskSwitcherVisible() const;
QString panelState() const;
void setPanelState(QString state);
Q_INVOKABLE void openActionDrawer(); Q_INVOKABLE void openActionDrawer();
Q_INVOKABLE void closeActionDrawer(); Q_INVOKABLE void closeActionDrawer();
@ -43,6 +48,7 @@ public:
Q_INVOKABLE void showVolumeOSD(); Q_INVOKABLE void showVolumeOSD();
Q_SIGNALS: Q_SIGNALS:
void panelStateChanged();
void isActionDrawerOpenChanged(); void isActionDrawerOpenChanged();
void doNotDisturbChanged(); void doNotDisturbChanged();
void isTaskSwitcherVisibleChanged(); void isTaskSwitcherVisibleChanged();
@ -57,6 +63,7 @@ private Q_SLOTS:
void updateDoNotDisturb(); void updateDoNotDisturb();
void updateIsActionDrawerOpen(); void updateIsActionDrawerOpen();
void updateIsTaskSwitcherVisible(); void updateIsTaskSwitcherVisible();
void updatePanelState();
private: private:
void connectSignals(); void connectSignals();
@ -64,6 +71,8 @@ private:
OrgKdePlasmashellInterface *m_interface; OrgKdePlasmashellInterface *m_interface;
QDBusServiceWatcher *m_watcher; QDBusServiceWatcher *m_watcher;
QString m_panelState = "default";
bool m_doNotDisturb = false; bool m_doNotDisturb = false;
bool m_isActionDrawerOpen = false; bool m_isActionDrawerOpen = false;
bool m_isTaskSwitcherVisible = 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() bool ShellDBusObject::isActionDrawerOpen()
{ {
return m_isActionDrawerOpen; return m_isActionDrawerOpen;

View file

@ -28,6 +28,7 @@ public:
Q_SIGNALS: Q_SIGNALS:
Q_SCRIPTABLE void doNotDisturbChanged(); Q_SCRIPTABLE void doNotDisturbChanged();
Q_SCRIPTABLE void isActionDrawerOpenChanged(); Q_SCRIPTABLE void isActionDrawerOpenChanged();
Q_SCRIPTABLE void panelStateChanged();
Q_SCRIPTABLE void isTaskSwitcherVisibleChanged(); Q_SCRIPTABLE void isTaskSwitcherVisibleChanged();
Q_SCRIPTABLE void openActionDrawerRequested(); Q_SCRIPTABLE void openActionDrawerRequested();
Q_SCRIPTABLE void closeActionDrawerRequested(); Q_SCRIPTABLE void closeActionDrawerRequested();
@ -44,6 +45,10 @@ public Q_SLOTS:
Q_SCRIPTABLE bool isActionDrawerOpen(); Q_SCRIPTABLE bool isActionDrawerOpen();
Q_SCRIPTABLE void setIsActionDrawerOpen(bool value); Q_SCRIPTABLE void setIsActionDrawerOpen(bool value);
Q_SCRIPTABLE QString panelState();
Q_SCRIPTABLE void setPanelState(QString state);
Q_SCRIPTABLE bool isTaskSwitcherVisible(); Q_SCRIPTABLE bool isTaskSwitcherVisible();
Q_SCRIPTABLE void setIsTaskSwitcherVisible(bool value); Q_SCRIPTABLE void setIsTaskSwitcherVisible(bool value);
@ -65,5 +70,7 @@ private:
bool m_isActionDrawerOpen{false}; bool m_isActionDrawerOpen{false};
bool m_isTaskSwitcherVisible{false}; bool m_isTaskSwitcherVisible{false};
QString m_panelState{};
StartupFeedbackModel *m_startupFeedbackModel{nullptr}; StartupFeedbackModel *m_startupFeedbackModel{nullptr};
}; };

View file

@ -14,24 +14,39 @@ QtObject {
// Set it to Plasmoid.containment.screenGeometry in a plasmoid to accomplish this. // Set it to Plasmoid.containment.screenGeometry in a plasmoid to accomplish this.
property alias screenGeometry: tasksModel.screenGeometry 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 bool showingWindow: __internal.count > 0 && !WindowPlugin.WindowUtil.isShowingDesktop
readonly property int windowCount: __internal.count readonly property int windowCount: __internal.count
property var __internal: KItemModels.KSortFilterProxyModel { property var __internal: KItemModels.KSortFilterProxyModel {
id: visibleMaximizedWindowsModel id: visibleWindowsModel
filterRoleName: 'IsMinimized' sourceModel: taskModel
filterString: 'false' filterRowCallback: (sourceRow, sourceParent) => {
sourceModel: TaskManager.TasksModel { 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 id: tasksModel
filterByVirtualDesktop: true filterByVirtualDesktop: true
filterByActivity: true filterByActivity: true
filterNotMaximized: true filterMinimized: true
filterByScreen: true filterByScreen: true
filterHidden: true filterHidden: true
virtualDesktop: virtualDesktopInfo.currentDesktop virtualDesktop: virtualDesktopInfo.currentDesktop
activity: activityInfo.currentActivity activity: activityInfo.currentActivity
sortMode: TaskManager.TasksModel.SortLastActivated
groupMode: TaskManager.TasksModel.GroupDisabled groupMode: TaskManager.TasksModel.GroupDisabled
} }

View file

@ -19,34 +19,70 @@ import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
import org.kde.taskmanager as TaskManager import org.kde.taskmanager as TaskManager
import org.kde.notificationmanager as NotificationManager import org.kde.notificationmanager as NotificationManager
import org.kde.layershell 1.0 as LayerShell
ContainmentItem { ContainmentItem {
id: root id: root
Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground
Plasmoid.status: PlasmaCore.Types.PassiveStatus // ensure that the panel never takes focus away from the running app 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 // filled in by the shell (Panel.qml) with the plasma-workspace PanelView
property var panel: null property var panel: null
onPanelChanged: { onPanelChanged: setWindowProperties()
if (panel) {
panel.floating = false; 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 { Timer {
id: maximizeTimer id: maximizeTimer
running: false running: false
interval: 100 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 // 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 showingStartupFeedback: MobileShellState.ShellDBusObject.startupFeedbackModel.activeWindowIsStartupFeedback && windowMaximizedTracker.windowCount === 1
readonly property bool showingApp: windowMaximizedTracker.showingWindow && !showingStartupFeedback readonly property bool showingApp: windowMaximizedTracker.showingWindow && !showingStartupFeedback
readonly property color backgroundColor: topPanel.colorScopeColor readonly property color backgroundColor: topPanel.colorScopeColor
readonly property alias isCurrentWindowFullscreen: windowMaximizedTracker.isCurrentWindowFullscreen
onIsCurrentWindowFullscreenChanged: {
MobileShellState.ShellDBusClient.panelState = isCurrentWindowFullscreen ? "hidden" : "default";
}
WindowPlugin.WindowMaximizedTracker { WindowPlugin.WindowMaximizedTracker {
id: windowMaximizedTracker id: windowMaximizedTracker
@ -89,6 +125,8 @@ ContainmentItem {
//END API implementation //END API implementation
Component.onCompleted: { Component.onCompleted: {
root.setWindowProperties();
// register dbus // register dbus
MobileShellState.ShellDBusObject.registerObject(); MobileShellState.ShellDBusObject.registerObject();
@ -108,18 +146,119 @@ ContainmentItem {
fullHeight: root.height fullHeight: root.height
screen: Plasmoid.screen screen: Plasmoid.screen
maximizedTracker: windowMaximizedTracker maximizedTracker: windowMaximizedTracker
visible: !root.isCurrentWindowFullscreen
} }
// top panel component Rectangle {
MobileShell.StatusBar { id: statusPanel
id: topPanel
anchors.fill: parent anchors.fill: parent
Kirigami.Theme.colorSet: root.showingApp ? Kirigami.Theme.Header : Kirigami.Theme.Complementary Kirigami.Theme.colorSet: root.showingApp ? Kirigami.Theme.Header : Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
showDropShadow: !root.showingApp color: statusPanel.state == "default" && root.showingApp ? Kirigami.Theme.backgroundColor : "transparent"
backgroundColor: !root.showingApp ? "transparent" : root.backgroundColor
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 // swipe-down drawer component

View file

@ -20,12 +20,13 @@ import org.kde.kirigami as Kirigami
MobileShell.NavigationPanel { MobileShell.NavigationPanel {
id: root id: root
required property bool opaqueBar required property bool opaqueBar
required property var navbarState
// background is: // background is:
// - opaque if an app is shown or vkbd is shown // - opaque if an app is shown or vkbd is shown
// - translucent if the task switcher is open // - translucent if the task switcher is open
// - transparent if on the homescreen // - 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 foregroundColorGroup: opaqueBar ? Kirigami.Theme.Window : Kirigami.Theme.Complementary
shadow: !opaqueBar shadow: !opaqueBar

View file

@ -5,6 +5,7 @@
import QtQuick 2.4 import QtQuick 2.4
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import QtQuick.Shapes 1.8
import org.kde.kirigami 2.20 as Kirigami 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.shellsettingsplugin as ShellSettings
import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
import org.kde.plasma.private.mobileshell.state as MobileShellState 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 { ContainmentItem {
id: root id: root
@ -37,6 +40,10 @@ ContainmentItem {
} }
} }
MobileShell.HapticsEffect {
id: haptics
}
readonly property bool inLandscape: MobileShell.Constants.navigationPanelOnSide(Screen.width, Screen.height) readonly property bool inLandscape: MobileShell.Constants.navigationPanelOnSide(Screen.width, Screen.height)
readonly property real navigationPanelHeight: MobileShell.Constants.navigationPanelThickness readonly property real navigationPanelHeight: MobileShell.Constants.navigationPanelThickness
@ -73,7 +80,7 @@ ContainmentItem {
interval: 100 interval: 100
onTriggered: { onTriggered: {
// maximize first, then we can apply offsets (otherwise they are overridden) // maximize first, then we can apply offsets (otherwise they are overridden)
root.panel.maximize() root.panel.maximize();
root.panel.offset = intendedWindowOffset; root.panel.offset = intendedWindowOffset;
} }
} }
@ -86,6 +93,23 @@ ContainmentItem {
root.panel.offset = intendedWindowOffset; root.panel.offset = intendedWindowOffset;
root.panel.thickness = navigationPanelHeight; root.panel.thickness = navigationPanelHeight;
root.panel.location = intendedWindowLocation; 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 // 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 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 { WindowPlugin.WindowMaximizedTracker {
id: windowMaximizedTracker id: windowMaximizedTracker
@ -128,21 +154,161 @@ ContainmentItem {
fullHeight: root.height fullHeight: root.height
screen: Plasmoid.screen screen: Plasmoid.screen
maximizedTracker: windowMaximizedTracker maximizedTracker: windowMaximizedTracker
visible: !root.isCurrentWindowFullscreen
} }
Item { Rectangle {
id: navigationPanel
anchors.fill: parent anchors.fill: parent
// contrasting colour // 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 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 // load appropriate system navigation component
NavigationPanelComponent { NavigationPanelComponent {
id: navigationPanel
anchors.fill: parent anchors.fill: parent
opaqueBar: root.opaqueBar opaqueBar: root.opaqueBar
isVertical: root.inLandscape 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
}
}
]
} }
} }
} }