mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
The input region Y was computed from the bottom of the screen instead of from the top, causing the touch region to be offset from the actual popup. Use openOffset directly since the popup anchors to the top. Unify the popup margin into a single property so the delegate x and the input-region regionX stay consistent.
282 lines
12 KiB
QML
282 lines
12 KiB
QML
/*
|
|
* SPDX-FileCopyrightText: 2024 Micah Stanley <stanleymicah@proton.me>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
import QtQuick 2.15
|
|
import QtQuick.Layouts
|
|
import QtQuick.Window
|
|
|
|
import org.kde.kirigami as Kirigami
|
|
import org.kde.plasma.private.mobileshell as MobileShell
|
|
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
|
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
|
|
|
import org.kde.layershell 1.0 as LayerShell
|
|
|
|
import org.kde.notificationmanager as NotificationManager
|
|
import org.kde.plasma.clock
|
|
|
|
import QtQuick.Controls as Controls
|
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
|
import org.kde.taskmanager 0.1 as TaskManager
|
|
|
|
|
|
/**
|
|
* This sets up and manages the notification popups
|
|
*/
|
|
Window {
|
|
id: notificationPopupManager
|
|
|
|
readonly property int popupWidth: Math.min(Kirigami.Units.gridUnit * 20, Screen.width - Kirigami.Units.gridUnit * 2)
|
|
readonly property real openOffset: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
|
|
readonly property int longestLength: Math.max(Screen.width, Screen.height)
|
|
readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled
|
|
// Margin between popup and screen edge in convergence mode; used in both
|
|
// the delegate x position and the input-region calculation so they stay in sync.
|
|
readonly property real convergencePopupMargin: Kirigami.Units.gridUnit * 2
|
|
property var keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
|
|
|
|
LayerShell.Window.scope: "notification"
|
|
LayerShell.Window.anchors: LayerShell.Window.AnchorTop | LayerShell.Window.AnchorHorizontalCenter
|
|
LayerShell.Window.layer: LayerShell.Window.LayerOverlay
|
|
LayerShell.Window.exclusionZone: -1
|
|
LayerShell.Window.keyboardInteractivity: keyboardInteractivity
|
|
|
|
// This toggles whether to show all the active popup notifications at ones in a list
|
|
property bool popupDrawerOpened: false
|
|
|
|
property var notificationModelType
|
|
property QtObject notificationSettings
|
|
property QtObject popupNotificationsModel
|
|
property QtObject tasksModel
|
|
property Clock clockSource
|
|
property bool inhibited
|
|
|
|
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
|
Kirigami.Theme.inherit: false
|
|
|
|
readonly property color backgroundColor: Qt.darker(Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95), 1.05)
|
|
color: popupDrawerOpened && visible ? backgroundColor : "transparent"
|
|
Behavior on color {
|
|
ColorAnimation {
|
|
duration: Kirigami.Units.veryLongDuration * 1.5
|
|
easing.type: Easing.OutExpo
|
|
}
|
|
}
|
|
|
|
width: longestLength
|
|
height: longestLength
|
|
|
|
signal timeChanged
|
|
|
|
Component.onCompleted: ShellUtil.setInputTransparent(notificationPopupManager, true)
|
|
|
|
Binding {
|
|
target: MobileShellState.ShellDBusClient
|
|
property: "isNotificationPopupDrawerOpen"
|
|
value: popupDrawerOpened
|
|
}
|
|
|
|
// hide on timeout to give time to finish animations
|
|
Timer {
|
|
id: hideTimeout
|
|
interval: Kirigami.Units.veryLongDuration * 1.5
|
|
repeat: false
|
|
onTriggered: if (notifications.count == 0) notificationPopupManager.visible = false;
|
|
}
|
|
|
|
// Update the window touch region to encapsulate the notification area or the whole screen depending on the 'popupDrawerOpened' state
|
|
function updateTouchArea() {
|
|
ShellUtil.setInputTransparent(notificationPopupManager, false);
|
|
if (popupDrawerOpened) {
|
|
ShellUtil.setInputRegion(notificationPopupManager, Qt.rect(0, 0, 0, 0));
|
|
} else {
|
|
// get the height of the popup directly to ensure we get the latest version
|
|
let popupHeight = Kirigami.Units.gridUnit * 6;
|
|
let currentPopup = notifications.objectAt(notifications.currentPopupIndex);
|
|
if (currentPopup) {
|
|
popupHeight = currentPopup.popupHeight;
|
|
} else {
|
|
console.warn("popupNotification: could not retrieve current popup height - falling back to a default value")
|
|
}
|
|
|
|
if (isConvergence) {
|
|
let regionX = notificationPopupManager.width - notificationPopupManager.popupWidth - notificationPopupManager.convergencePopupMargin;
|
|
let regionY = openOffset;
|
|
ShellUtil.setInputRegion(notificationPopupManager, Qt.rect(regionX, regionY, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit * 2, popupHeight + Kirigami.Units.gridUnit * 2));
|
|
} else {
|
|
ShellUtil.setInputRegion(notificationPopupManager, Qt.rect((notificationPopupManager.width - notificationPopupManager.popupWidth - Kirigami.Units.gridUnit) / 2, openOffset - Kirigami.Units.gridUnit / 2, notificationPopupManager.popupWidth + Kirigami.Units.gridUnit, popupHeight + Kirigami.Units.gridUnit * ((notifications.count - notifications.currentPopupIndex > 1) ? 4 : 1)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// parent the popup notifications inside a Flickable so that they can be scrollable when the drawer state is active
|
|
Flickable {
|
|
id: flickable
|
|
width: notificationPopupManager.width
|
|
height: Screen.height
|
|
contentHeight: notifications.fullHeight + notificationPopupManager.openOffset
|
|
boundsBehavior: Flickable.DragAndOvershootBounds
|
|
bottomMargin: Kirigami.Units.gridUnit * 6
|
|
|
|
interactive: notificationPopupManager.popupDrawerOpened
|
|
|
|
onDragEnded: flickable.checkDismiss();
|
|
onFlickEnded: flickable.checkDismiss();
|
|
onDragStarted: {
|
|
notifications.recalculateHeight();
|
|
atBeginning = flickable.atYBeginning;
|
|
atEnd = flickable.atYEnd;
|
|
}
|
|
onFlickStarted: {
|
|
notifications.recalculateHeight();
|
|
atBeginning = flickable.atYBeginning;
|
|
atEnd = flickable.atYEnd;
|
|
}
|
|
|
|
property bool atBeginning: false
|
|
property bool atEnd: false
|
|
|
|
function checkDismiss() {
|
|
let dismissFromTop = atBeginning && flickable.verticalOvershoot < -Kirigami.Units.gridUnit;
|
|
let dismissFromBottom = atEnd && flickable.verticalOvershoot > Kirigami.Units.gridUnit;
|
|
if (dismissFromTop || dismissFromBottom) {
|
|
flickable.dismiss();
|
|
}
|
|
}
|
|
|
|
function dismiss() {
|
|
notificationPopupManager.popupDrawerOpened = false;
|
|
notificationPopupManager.updateTouchArea();
|
|
resetContentY.running = true;
|
|
}
|
|
|
|
NumberAnimation on contentY {
|
|
id: resetContentY
|
|
running: false
|
|
to: 0
|
|
duration: Kirigami.Units.veryLongDuration * 1.5
|
|
easing.type: Easing.OutExpo
|
|
}
|
|
|
|
MouseArea {
|
|
// capture taps behind the notifications to close the drawer
|
|
id: item
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
width: notificationPopupManager.width
|
|
height: Math.max(notifications.fullHeight, Screen.height)
|
|
|
|
onReleased: flickable.dismiss();
|
|
|
|
Instantiator {
|
|
id: notifications
|
|
model: popupNotificationsModel
|
|
|
|
// get the height, drag offset, and idx of the current popup notifition and make it easily accessible by all popup notifications
|
|
property int currentPopupHeight: (count > 0 && currentPopupIndex < count && objectAt(currentPopupIndex)) ? objectAt(currentPopupIndex).popupHeight : 0;
|
|
property int currentDragOffset: 0
|
|
property int currentPopupIndex: 0
|
|
|
|
// calculate the full height of all the notifications combine for scrolling purposes
|
|
property int fullHeight: 0
|
|
onCountChanged: {
|
|
if (count == 0) {
|
|
ShellUtil.setInputTransparent(notificationPopupManager, true);
|
|
hideTimeout.restart();
|
|
notificationPopupManager.popupDrawerOpened = false;
|
|
fullHeight = 0;
|
|
return;
|
|
}
|
|
notificationPopupManager.visible = true;
|
|
notifications.recalculateHeight();
|
|
}
|
|
|
|
function recalculateHeight() {
|
|
let findHeight = 0
|
|
for (var i = 0; i < count; i++) {
|
|
findHeight += notifications.objectAt(i).popupHeight + Kirigami.Units.gridUnit;
|
|
}
|
|
fullHeight = findHeight;
|
|
}
|
|
|
|
delegate: NotificationPopup {
|
|
id: popup
|
|
|
|
x: notificationPopupManager.isConvergence
|
|
? (parent.width - width - notificationPopupManager.convergencePopupMargin)
|
|
: (parent.width - width) / 2
|
|
z: notifications.count - index
|
|
|
|
isConvergence: notificationPopupManager.isConvergence
|
|
popupWidth: notificationPopupManager.popupWidth
|
|
openOffset: notificationPopupManager.openOffset
|
|
|
|
keyboardInteractivity: notificationPopupManager.keyboardInteractivity
|
|
popupNotifications: notifications
|
|
popupIndex: index
|
|
|
|
popupDrawerOpened: notificationPopupManager.popupDrawerOpened
|
|
|
|
popupModel: model
|
|
notificationsModel: popupNotificationsModel
|
|
notificationsModelType: notificationModelType
|
|
timeDataSource: clockSource
|
|
|
|
timeout: model.timeout
|
|
|
|
onUpdateTouchArea: notificationPopupManager.updateTouchArea()
|
|
|
|
onSetInputTransparent: ShellUtil.setInputTransparent(notificationPopupManager, true)
|
|
|
|
onOpenPopupDrawer: notificationPopupManager.popupDrawerOpened = true
|
|
|
|
onSetKeyboardFocus: notificationPopupManager.keyboardInteractivity = LayerShell.Window.KeyboardInteractivityOnDemand
|
|
|
|
onRemoveKeyboardFocus: notificationPopupManager.keyboardInteractivity = LayerShell.Window.KeyboardInteractivityNone
|
|
|
|
defaultTimeout: notificationSettings.popupTimeout + (model.urls && model.urls.length > 0 ? 5000 : 0)
|
|
|
|
dismissTimeout: !notificationSettings.permanentJobPopups
|
|
&& model.type === NotificationManager.Notifications.JobType
|
|
&& model.jobState !== NotificationManager.Notifications.JobStateStopped
|
|
? defaultTimeout : 0
|
|
|
|
onDismissClicked: model.dismissed = true
|
|
|
|
onExpired: {
|
|
if (model.resident) {
|
|
// When resident, only mark it as expired so the popup disappears
|
|
// but don't actually invalidate the notification
|
|
model.expired = true;
|
|
} else {
|
|
if (notificationModelType === NotificationsModelType.WatchedNotificationsModel) {
|
|
popupNotificationsModel.expire(model.notificationId);
|
|
} else if (notificationModelType === NotificationsModelType.NotificationsModel) {
|
|
popupNotificationsModel.expire(popupNotificationsModel.index(index, 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
if (model.type === NotificationManager.Notifications.NotificationType && model.desktopEntry) {
|
|
// Register apps that were seen spawning a popup so they can be configured later
|
|
// Apps with notifyrc can already be configured anyway
|
|
if (!model.notifyRcName) {
|
|
notificationSettings.registerKnownApplication(model.desktopEntry);
|
|
notificationSettings.save();
|
|
}
|
|
}
|
|
|
|
// Tell the model that we're handling the timeout now
|
|
popupNotificationsModel.stopTimeout(popupNotificationsModel.index(index, 0));
|
|
|
|
item.children.push(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|