shift-shell/containments/panel/qml/StatusPanel.qml
Marco Allegretti 1a70b24c06 Convergence: stabilize workspace scroll gestures
Improve touchpad scroll handling for convergence workflows while avoiding gesture conflicts.

- make SwipeArea handle angleDelta-based touchpad wheel streams and phase-less sequences safely
- wire topbar ActionDrawerOpenSurface to virtual desktop info and horizontal workspace switching logic
- add dock pager wheel switching in FavouritesBar for the vertical wheel axis environments deliver
- prevent wallpaper/home surface touchpad scroll from opening app drawer in convergence
- extend convergence invariant checks for the new gesture paths
2026-05-25 21:59:19 +02:00

236 lines
8.2 KiB
QML

// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
// SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Layouts
import QtQuick.Window
import QtQml.Models
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasmoid
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
import org.kde.plasma.private.mobileshell.state as MobileShellState
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
Item {
id: root
// The base containment item
property ContainmentItem containmentItem
//BEGIN API implementation
Connections {
target: MobileShellState.ShellDBusClient
function onOpenActionDrawerRequested() {
if (ShellSettings.Settings.convergenceModeEnabled) {
drawer.actionDrawer.openToPinnedMode = false;
drawer.actionDrawer.intendedToBeVisible = true;
}
drawer.actionDrawer.open();
}
function onCloseActionDrawerRequested() {
drawer.actionDrawer.close();
}
}
Binding {
target: MobileShellState.ShellDBusClient
property: "isActionDrawerOpen"
value: drawer.visible
}
TaskManager.VirtualDesktopInfo {
id: virtualDesktopInfo
}
//END API implementation
// Startup feedback fill animation
MobileShell.StartupFeedbackPanelFill {
id: startupFeedbackColorAnimation
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
fullHeight: containmentItem.height
screen: Plasmoid.screen
maximizedTracker: containmentItem.windowMaximizedTracker
visible: !MobileShellState.LockscreenDBusClient.lockscreenActive
&& !containmentItem.fullscreen
&& !ShellSettings.Settings.convergenceModeEnabled
}
// Status bar component
// z: 1 so system tray icon MouseAreas inside StatusBar receive events
// before ActionDrawerOpenSurface (z: default). Non-interactive areas
// pass through to the swipe surface below.
StatusBarWrapper {
id: statusBarWrapper
z: 1
visible: !ShellSettings.Settings.convergenceModeEnabled
enabled: visible
anchors.fill: parent
statusPanelHeight: MobileShell.Constants.topPanelHeight
transparentBackground: {
// If we are over the lockscreen, always have a transparent background.
if (MobileShellState.LockscreenDBusClient.lockscreenActive) {
return true;
}
// In convergence mode the status bar behaves like a desktop panel:
// always opaque so it matches the dock and doesn't let the wallpaper bleed through.
if (ShellSettings.Settings.convergenceModeEnabled) {
return false;
}
return !containmentItem.showingApp && !containmentItem.fullscreen;
}
forcedComplementary: {
if (MobileShellState.LockscreenDBusClient.lockscreenActive) {
return true;
}
// Force complementary colors (white) unless the startup feedback is showing
return transparentBackground && !startupFeedbackColorAnimation.isShowing
}
state: {
// If we are on the lockscreen, always show the status panel.
if (MobileShellState.LockscreenDBusClient.lockscreenActive) {
return "default";
}
return MobileShellState.ShellDBusClient.panelState;
}
onStateChanged: {
if (state != "hidden") {
containmentItem.setWindowProperties();
hiddenTimer.restart();
}
}
onUpdatePanelPropertiesRequested: containmentItem.setWindowProperties()
// Hide status bar panel if it is visible for 3 seconds (in forced "visible" mode).
Timer {
id: hiddenTimer
running: false
interval: 3000
onTriggered: {
if (statusBarWrapper.state == "visible") {
MobileShellState.ShellDBusClient.panelState = "hidden";
}
}
}
}
// Keyboard shortcut to toggle action drawer (convergence mode)
Shortcut {
sequence: "Meta+A"
enabled: ShellSettings.Settings.convergenceModeEnabled
context: Qt.ApplicationShortcut
onActivated: {
if (drawer.actionDrawer.intendedToBeVisible) {
drawer.actionDrawer.intendedToBeVisible = false;
drawer.actionDrawer.close();
} else {
drawer.actionDrawer.openToPinnedMode = false;
drawer.actionDrawer.intendedToBeVisible = true;
drawer.actionDrawer.open();
}
}
}
// Swiping area for swipe-down drawer
MobileShell.ActionDrawerOpenSurface {
id: swipeArea
actionDrawer: drawer.actionDrawer
virtualDesktopInfo: virtualDesktopInfo
anchors.fill: parent
readonly property alias drawerVisible: drawer.visible
readonly property alias offset: drawer.actionDrawer.offset
// if in a fullscreen app, the panels are visible, and the action drawer is opened
// set the panels to a hidden state
onDrawerVisibleChanged: {
if (statusBarWrapper.state == "visible") {
MobileShellState.ShellDBusClient.panelState = "hidden";
}
}
}
// Swipe-down drawer component
MobileShell.ActionDrawerWindow {
id: drawer
onVisibleChanged: {
if (visible && MobileShellState.LockscreenDBusClient.lockscreenActive) {
// This works as long the wayland surface is the same (no window.close(), just window.visible = false)
lockScreenOverlay.raiseOverlay();
}
}
LockscreenOverlay {
id: lockScreenOverlay
window: drawer
}
actionDrawer.restrictedPermissions: MobileShellState.LockscreenDBusClient.lockscreenActive
actionDrawer.notificationSettings: NotificationManager.Settings {
id: notificationSettings
}
actionDrawer.notificationModel: NotificationManager.Notifications {
showExpired: true
showDismissed: true
showJobs: notificationSettings.jobsInNotifications
sortMode: NotificationManager.Notifications.SortByTypeAndUrgency
groupMode: NotificationManager.Notifications.GroupApplicationsFlat
groupLimit: 2
expandUnread: true
// Strip "@other" from the blacklist: Plasma's default config blocks
// notifications from unregistered/non-configurable sources, which on
// a mobile/convergence shell silently hides anything not shipping a
// .desktop entry (e.g. third-party DBus senders). Shift surfaces all
// notifications and lets the user blacklist individual apps later.
blacklistedDesktopEntries: notificationSettings.historyBlacklistedApplications
.filter(function(e) { return e !== "@other"; })
blacklistedNotifyRcNames: notificationSettings.historyBlacklistedServices
urgencies: NotificationManager.Notifications.CriticalUrgency
| NotificationManager.Notifications.NormalUrgency
| NotificationManager.Notifications.LowUrgency
}
Connections {
target: drawer.actionDrawer
function onPermissionsRequested() {
MobileShellState.ShellDBusClient.openLockScreenKeypad();
}
}
Connections {
target: MobileShellState.LockscreenDBusClient
function onLockscreenUnlocked() {
// Run pending actions after the lockscreen gets unlocked
drawer.actionDrawer.runPendingNotificationAction();
}
}
}
}