VolumeOSD/NotificationPopup: Animation Adjustments

This merge request adjust animations in the VolumeOSD and NotificationPopup to bring consistency and to make it feel better when swiping up a notification.
This commit is contained in:
Micah Stanley 2025-04-30 03:50:22 +00:00
parent 0f1c0d86e7
commit d39bdbedb8
4 changed files with 130 additions and 61 deletions

View file

@ -33,6 +33,9 @@ Item {
property real aboveNotificationFullOffset: 0
property int aboveNotificationHeight: 0
// set to true when notification is swiped up by user
property bool closedWithSwipe: false
// the drag offset on the current popup notification - used to position notification when stacked underneath
property real currentDragOffset: {
let current = popupNotifications.currentPopupIndex == notificationPopup.popupIndex;
@ -257,7 +260,7 @@ Item {
if (notificationPopup.popupDrawerOpened && notificationItem.state != "inDrawerClosed" && notificationItem.state != "open") {
notificationItem.offset = openOffset;
notificationItem.scale = 0.75;
notificationItem.opacity = 0.0;
notificationItem.popupOpacity = 0.0;
}
notificationItem.state = "inDrawerClosed";
notificationPopup.removeKeyboardFocus();
@ -268,7 +271,7 @@ Item {
if (notificationPopup.popupDrawerOpened && notificationItem.state != "open" && notificationItem.state != "inDrawerClosed") {
notificationItem.offset = openOffset;
notificationItem.scale = 0.75;
notificationItem.opacity = 0.0;
notificationItem.popupOpacity = 0.0;
}
notificationItem.state = "open";
notificationPopup.removeKeyboardFocus();
@ -353,29 +356,53 @@ Item {
property real offset: closedOffset
property real scale: 1.0
property real drawerScale: 1 - Math.max(Math.min(notificationPopup.popupIndex - popupNotifications.currentPopupIndex, 3), 1) * 0.075
property real popupOpacity: 1.0 // controls the opacity of the notification popup when outside the popup drawer
property real drawerScale: {
if (notificationPopup.popupDrawerOpened) {
return 0; // when popup drawer is opened, reset scale to 0
}
let index = notificationPopup.popupIndex - popupNotifications.currentPopupIndex;
// clamp the index value to avoid scaling too much with animations
let indexClamped = Math.max(Math.min(index, 2), 0);
return indexClamped * 0.075;
}
property real drawerAddedOffset: {
if (notificationPopup.popupDrawerOpened) {
return 0; // when popup drawer is opened, reset any added height to 0
}
let index = notificationPopup.popupIndex - popupNotifications.currentPopupIndex;
// clamp the index value to avoid moving too much with animations
let indexClamped = Math.max(Math.min(index, 2), -1);
return Kirigami.Units.gridUnit * 0.5 * indexClamped;
}
property real drawerOpacity: {
let index = notificationPopup.popupIndex - popupNotifications.currentPopupIndex;
if (index > 2 && !notificationPopup.popupDrawerOpened) {
return 0; // make this popup invisible if it is below 3 other popups
} else {
return 1; // when popup drawer is opened, reset opacity to 1
}
}
Behavior on drawerScale {
NumberAnimation {
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutExpo
duration: Kirigami.Units.veryLongDuration * 1.25
easing.type: Easing.OutQuint
}
}
property real drawerAddedOffset: Kirigami.Units.gridUnit * 0.5 * Math.max(Math.min(notificationPopup.popupIndex - popupNotifications.currentPopupIndex, 3), 1)
Behavior on drawerAddedOffset {
NumberAnimation {
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutExpo
duration: Kirigami.Units.veryLongDuration * 1.25
easing.type: Easing.OutQuint
}
}
property real drawerOpacity: (Math.max(Math.min(notificationPopup.popupIndex - popupNotifications.currentPopupIndex, 3), 1) > 2) ? 0 : 1
Behavior on drawerOpacity {
NumberAnimation {
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutExpo
duration: Kirigami.Units.veryLongDuration * 1.25
easing.type: Easing.OutQuint
}
}
opacity: 1.0
opacity: Math.min(popupOpacity, drawerOpacity)
state: ""
@ -389,7 +416,7 @@ Item {
target: notificationItem; scale: 1.0
}
PropertyChanges {
target: notificationItem; opacity: 1.0
target: notificationItem; popupOpacity: 1.0
}
},
State {
@ -401,7 +428,7 @@ Item {
target: notificationItem; scale: 1.0
}
PropertyChanges {
target: notificationItem; opacity: 1.0
target: notificationItem; popupOpacity: 1.0
}
},
State {
@ -413,47 +440,88 @@ Item {
target: notificationItem; scale: 0.75
}
PropertyChanges {
target: notificationItem; opacity: 0.0
target: notificationItem; popupOpacity: 0.0
}
},
State {
name: "inDrawerClosed"
PropertyChanges {
target: notificationItem; offset: notificationPopup.openOffset + (notificationPopup.popupDrawerOpened ? 0 : drawerAddedOffset)
target: notificationItem; offset: notificationPopup.openOffset
}
PropertyChanges {
target: notificationItem; scale: notificationPopup.popupDrawerOpened ? 1 : drawerScale
target: notificationItem; scale: 1
}
PropertyChanges {
target: notificationItem; opacity: notificationPopup.popupDrawerOpened ? 1 : drawerOpacity
target: notificationItem; popupOpacity: 1
}
}
]
readonly property int notificationEasing: {
// check whether the popup is the current one or above it
let topPopup = popupNotifications.currentPopupIndex >= notificationPopup.popupIndex;
// check whether the popup has any popups below it
let popupBelow = notificationPopup.popupCount - notificationPopup.popupIndex > 1;
let popupOpening = notificationItem.state == "open" || notificationItem.state == "inDrawerClosed";
let popupClosing = notificationItem.state == "closeWithMove" || notificationItem.state == "closeWithScale"
if (notificationPopup.closedWithSwipe || (topPopup && popupClosing && popupBelow)) {
// set the easing type to linear when closed with a swipe or if a popup is below when closing
// as to make sure the popup feels like it is keeping it's momentum
return Easing.Linear;
} else if (popupOpening) {
// set the easing type to 'Out' when opening so the popup will have a gentle landing
return Easing.OutQuint;
} else {
// if above conditions fail, set the easing type to 'In' so the popup will build up speed for it's exit
return Easing.InQuint;
}
}
readonly property real notificationDuration: {
// check whether the popup is the current one or above it
let topPopup = popupNotifications.currentPopupIndex >= notificationPopup.popupIndex;
// check whether the popup has any popups below it
let popupBelow = notificationPopup.popupCount - notificationPopup.popupIndex > 1;
let popupClosing = notificationItem.state == "closeWithMove" || notificationItem.state == "closeWithScale"
if (notificationPopup.closedWithSwipe || (topPopup && popupClosing && popupBelow)) {
// make sure the speed it faster when closed with a swipe or if there is a popup below when closing
// as to make sure the speed feels comparable with the easing type is set to linear
return Kirigami.Units.veryLongDuration * 0.5;
} else {
return Kirigami.Units.veryLongDuration * 1.25;
}
}
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
PropertyAnimation {
properties: "offset"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.5
properties: "offset"
easing.type: notificationItem.notificationEasing
duration: notificationItem.notificationDuration
}
PropertyAnimation {
properties: "scale"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.5
properties: "scale"
easing.type: notificationItem.notificationEasing
duration: notificationItem.notificationDuration
}
PropertyAnimation {
properties: "opacity"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.5
properties: "popupOpacity"
easing.type: notificationItem.notificationEasing
duration: notificationItem.notificationDuration
}
}
ScriptAction {
script: {
if (notificationItem.state == "open") {
preventDismissTimeout = false;
notificationPopup.preventDismissTimeout = false;
notificationPopup.updateTouchArea();
} else if (notificationItem.state == "closeWithMove" || notificationItem.state == "closeWithScale") {
preventDismissTimeout = true;
if (dismissTimeout) {
notificationPopup.preventDismissTimeout = true;
if (notificationPopup.dismissTimeout) {
notificationPopup.dismissClicked();
} else if (!isActionDrawerOpen) {
notificationPopup.expired();
} else if (!notificationPopup.isActionDrawerOpen) {
notificationPopup.expired();
}
}
}
@ -465,15 +533,15 @@ Item {
Scale {
origin.x: Math.round(notificationPopup.popupWidth / 2)
origin.y: notificationPopup.scaleOriginY
xScale: notificationItem.scale
yScale: notificationItem.scale
xScale: notificationItem.scale - notificationItem.drawerScale
yScale: notificationItem.scale - notificationItem.drawerScale
}
]
}
transform: [
Translate {
y: notificationItem.offset + notificationPopup.fullOffsetAn + notificationPopup.dragOffset + notificationPopup.currentDragOffset
y: notificationItem.offset + notificationPopup.fullOffsetAn + notificationPopup.dragOffset + notificationPopup.currentDragOffset + notificationItem.drawerAddedOffset
}
]
@ -506,12 +574,14 @@ Item {
startActive = active;
notificationPopup.preventDismissTimeout = true;
if (!active && !(notificationItem.state == "closeWithScale" || notificationItem.state == "closeWithMove")) {
dragOffsetAn.running = true;
if ((lastOffset - notificationPopup.dragOffset > 1.0 && notificationPopup.dragOffset < 0) || (-(notificationPopup.openOffset - notificationPopup.closedOffset) / 4 > notificationPopup.dragOffset)) {
// this code is called when the notifition is swiped or draged to the top.
notificationPopup.closedWithSwipe = true;
notificationPopup.closePopup(popupIndex);
return;
} else if (notificationPopup.dragOffset - lastOffset > 1.0 || Kirigami.Units.gridUnit * 3 < notificationPopup.dragOffset) {
}
dragOffsetAn.running = true;
if (notificationPopup.dragOffset - lastOffset > 1.0 || Kirigami.Units.gridUnit * 3 < notificationPopup.dragOffset) {
// this code is called when the notifition is swiped or draged down.
}
notificationPopup.preventDismissTimeout = (keyboardInteractivity == LayerShell.Window.KeyboardInteractivityOnDemand);

View file

@ -74,6 +74,14 @@ Window {
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);
@ -166,7 +174,7 @@ Window {
onCountChanged: {
if (count == 0) {
ShellUtil.setInputTransparent(notificationPopupManager, true);
notificationPopupManager.visible = false;
hideTimeout.restart();
notificationPopupManager.popupDrawerOpened = false;
fullHeight = 0;
return;

View file

@ -23,17 +23,16 @@ Window {
id: window
width: osd.width + 6
height: cards.implicitHeight + 6
height: cards.implicitHeight + 6 + cards.openOffset
onWidthChanged: if (visible) window.updateTouchRegion()
visible: false
readonly property real offsetMargins: Math.max(cards.offset, 0)
LayerShell.Window.scope: "overlay"
LayerShell.Window.anchors: LayerShell.Window.AnchorTop
LayerShell.Window.layer: LayerShell.Window.LayerOverlay
LayerShell.Window.exclusionZone: -1
LayerShell.Window.margins.top: offsetMargins
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
Kirigami.Theme.colorSet: Kirigami.Theme.View
@ -51,10 +50,11 @@ Window {
}
function open() {
// set window input transparency to allow touches to pass through while the opening animation is playing
ShellUtil.setInputTransparent(window, true);
window.visible = true;
cards.state = "open";
// set window input transparency to accept touches
ShellUtil.setInputTransparent(window, false);
}
function close() {
@ -63,12 +63,16 @@ Window {
ShellUtil.setInputTransparent(window, true);
}
function updateTouchRegion() {
ShellUtil.setInputRegion(window, Qt.rect(0, cards.openOffset, window.width, cards.implicitHeight + 6));
}
Timer {
id: hideTimer
interval: 2000
running: false
onTriggered: {
window.close();
window.close();
}
}
@ -87,7 +91,6 @@ Window {
readonly property real closedOffset: -(cards.implicitHeight + Kirigami.Units.smallSpacing)
readonly property real openOffset: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
property real offset: closedOffset
property real scale: 0.95
state: "closed"
@ -97,18 +100,12 @@ Window {
PropertyChanges {
target: cards; offset: openOffset
}
PropertyChanges {
target: cards; scale: 1.0
}
},
State {
name: "closed"
PropertyChanges {
target: cards; offset: closedOffset
}
PropertyChanges {
target: cards; scale: 0.95
}
}
]
@ -116,16 +113,16 @@ Window {
SequentialAnimation {
ParallelAnimation {
PropertyAnimation {
properties: "offset"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.25
}
PropertyAnimation {
properties: "scale"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.25
properties: "offset"; easing.type: cards.state == "open" ? Easing.OutQuint : Easing.InQuint; duration: Kirigami.Units.veryLongDuration * 1.25
}
}
ScriptAction {
script: {
if (cards.state == "open") {
hideTimer.restart();
// set window input transparency to accept touches
ShellUtil.setInputTransparent(window, false);
window.updateTouchRegion();
} else {
hideTimer.stop();
window.visible = false;
@ -142,13 +139,7 @@ Window {
transform: [
Translate {
y: cards.offset - window.offsetMargins + 1
},
Scale {
origin.x: Math.round(width / 2)
origin.y: Math.round(height / 2)
xScale: cards.scale
yScale: cards.scale
y: cards.offset + 1
}
]

View file

@ -121,16 +121,16 @@ Window {
SequentialAnimation {
ParallelAnimation {
PropertyAnimation {
properties: "offset"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.25
properties: "offset"; easing.type: Easing.OutQuint; duration: Kirigami.Units.veryLongDuration * 1.25
}
PropertyAnimation {
properties: "scale"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.25
properties: "scale"; easing.type: Easing.OutQuint; duration: Kirigami.Units.veryLongDuration * 1.25
}
PropertyAnimation {
properties: "opacity"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.25
properties: "opacity"; easing.type: Easing.OutQuint; duration: Kirigami.Units.veryLongDuration * 1.25
}
PropertyAnimation {
properties: "color"; easing.type: Easing.OutExpo; duration: Kirigami.Units.veryLongDuration * 1.25
properties: "color"; easing.type: Easing.OutQuint; duration: Kirigami.Units.veryLongDuration * 1.25
}
}
ScriptAction {