sidebar/notifs: better anims + misc fixes
This commit is contained in:
parent
c6b91f1299
commit
89d46c1b2f
8 changed files with 298 additions and 151 deletions
|
|
@ -84,7 +84,10 @@ ColumnLayout {
|
|||
clip: true
|
||||
|
||||
model: ScriptModel {
|
||||
values: [...new Set(Notifs.list.map(notif => notif.appName))].reverse()
|
||||
values: {
|
||||
const list = Notifs.notClosed.map(n => [n.appName, null]);
|
||||
return [...new Map(list).keys()];
|
||||
}
|
||||
}
|
||||
|
||||
delegate: NotifGroup {}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ StyledRect {
|
|||
|
||||
required property string modelData
|
||||
|
||||
readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData).reverse()
|
||||
readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData)
|
||||
readonly property string image: notifs.find(n => n.image.length > 0)?.image ?? ""
|
||||
readonly property string appIcon: notifs.find(n => n.appIcon.length > 0)?.appIcon ?? ""
|
||||
readonly property string urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? "critical" : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? "normal" : "low"
|
||||
|
|
@ -227,7 +227,7 @@ StyledRect {
|
|||
|
||||
ParallelAnimation {
|
||||
running: notif.modelData.closed
|
||||
onFinished: notif.modelData.lock(notif)
|
||||
onFinished: notif.modelData.unlock(notif)
|
||||
|
||||
Anim {
|
||||
target: notif
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ Item {
|
|||
id: list
|
||||
|
||||
model: ScriptModel {
|
||||
values: Notifs.popups.filter(n => !n.closed).reverse()
|
||||
values: Notifs.popups.filter(n => !n.closed)
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
|
|
|
|||
|
|
@ -89,133 +89,23 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
StyledListView {
|
||||
StyledFlickable {
|
||||
id: view
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const list = Notifs.list.filter(n => !n.closed).sort((a, b) => b.time - a.time).map(n => [n.appName, null]);
|
||||
return [...new Map(list).keys()];
|
||||
}
|
||||
}
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentWidth: width
|
||||
contentHeight: notifList.implicitHeight
|
||||
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: view
|
||||
}
|
||||
|
||||
delegate: MouseArea {
|
||||
id: notif
|
||||
NotifDockList {
|
||||
id: notifList
|
||||
|
||||
required property int index
|
||||
required property string modelData
|
||||
|
||||
property int startY
|
||||
|
||||
function closeAll(): void {
|
||||
for (const n of Notifs.list.filter(n => !n.closed && n.appName === modelData))
|
||||
n.close();
|
||||
}
|
||||
|
||||
implicitWidth: root.width
|
||||
implicitHeight: notifInner.implicitHeight
|
||||
|
||||
hoverEnabled: true
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : undefined
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
preventStealing: true
|
||||
|
||||
drag.target: this
|
||||
drag.axis: Drag.XAxis
|
||||
|
||||
onPressed: event => {
|
||||
if (event.button === Qt.LeftButton)
|
||||
startY = event.y;
|
||||
else if (event.button === Qt.RightButton)
|
||||
notifInner.toggleExpand();
|
||||
else if (event.button === Qt.MiddleButton)
|
||||
closeAll();
|
||||
}
|
||||
onPositionChanged: event => {
|
||||
if (pressed) {
|
||||
const diffY = event.y - startY;
|
||||
if (Math.abs(diffY) > Config.notifs.expandThreshold)
|
||||
notifInner.toggleExpand(diffY > 0);
|
||||
}
|
||||
}
|
||||
onReleased: event => {
|
||||
if (Math.abs(x) < width * Config.notifs.clearThreshold)
|
||||
x = 0;
|
||||
else
|
||||
closeAll();
|
||||
}
|
||||
|
||||
NotifGroup {
|
||||
id: notifInner
|
||||
|
||||
modelData: notif.modelData
|
||||
props: root.props
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
Anim {
|
||||
property: "scale"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
Anim {
|
||||
property: "scale"
|
||||
to: 0.6
|
||||
}
|
||||
}
|
||||
|
||||
move: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
Anim {
|
||||
property: "y"
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
Anim {
|
||||
property: "y"
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
props: root.props
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -226,8 +116,14 @@ Item {
|
|||
repeat: true
|
||||
interval: 50
|
||||
onTriggered: {
|
||||
if (root.notifCount > 0)
|
||||
Notifs.list.find(n => !n.closed).close();
|
||||
let next = null;
|
||||
for (let i = 0; i < notifList.repeater.count; i++) {
|
||||
next = notifList.repeater.itemAt(i);
|
||||
if (!next?.closed)
|
||||
break;
|
||||
}
|
||||
if (next)
|
||||
next.closeAll();
|
||||
else
|
||||
stop();
|
||||
}
|
||||
|
|
|
|||
150
modules/sidebar/NotifDockList.qml
Normal file
150
modules/sidebar/NotifDockList.qml
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.config
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Props props
|
||||
|
||||
readonly property alias repeater: repeater
|
||||
readonly property int spacing: Appearance.spacing.small
|
||||
property bool flag
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: {
|
||||
const item = repeater.itemAt(repeater.count - 1);
|
||||
return item ? item.y + item.implicitHeight : 0;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const list = Notifs.list.map(n => [n.appName, null]);
|
||||
return [...new Map(list).keys()];
|
||||
}
|
||||
onValuesChanged: root.flagChanged()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: notif
|
||||
|
||||
required property int index
|
||||
required property string modelData
|
||||
|
||||
readonly property bool closed: notifInner.notifCount === 0
|
||||
readonly property alias nonAnimHeight: notifInner.nonAnimHeight
|
||||
property int startY
|
||||
|
||||
function closeAll(): void {
|
||||
for (const n of Notifs.notClosed.filter(n => n.appName === modelData))
|
||||
n.close();
|
||||
}
|
||||
|
||||
y: {
|
||||
root.flag; // Force update
|
||||
let y = 0;
|
||||
for (let i = 0; i < index; i++) {
|
||||
const item = repeater.itemAt(i);
|
||||
if (!item.closed)
|
||||
y += item.nonAnimHeight + root.spacing;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
implicitWidth: root.width
|
||||
implicitHeight: notifInner.implicitHeight
|
||||
|
||||
hoverEnabled: true
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : undefined
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
preventStealing: true
|
||||
|
||||
drag.target: this
|
||||
drag.axis: Drag.XAxis
|
||||
|
||||
onPressed: event => {
|
||||
startY = event.y;
|
||||
if (event.button === Qt.RightButton)
|
||||
notifInner.toggleExpand(!notifInner.expanded);
|
||||
else if (event.button === Qt.MiddleButton)
|
||||
closeAll();
|
||||
}
|
||||
onPositionChanged: event => {
|
||||
if (pressed) {
|
||||
const diffY = event.y - startY;
|
||||
if (Math.abs(diffY) > Config.notifs.expandThreshold)
|
||||
notifInner.toggleExpand(diffY > 0);
|
||||
}
|
||||
}
|
||||
onReleased: event => {
|
||||
if (Math.abs(x) < width * Config.notifs.clearThreshold)
|
||||
x = 0;
|
||||
else
|
||||
closeAll();
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
running: true
|
||||
|
||||
Anim {
|
||||
target: notif
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
Anim {
|
||||
target: notif
|
||||
property: "scale"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
running: notif.closed
|
||||
|
||||
Anim {
|
||||
target: notif
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
Anim {
|
||||
target: notif
|
||||
property: "scale"
|
||||
to: 0.6
|
||||
}
|
||||
}
|
||||
|
||||
NotifGroup {
|
||||
id: notifInner
|
||||
|
||||
modelData: notif.modelData
|
||||
props: root.props
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,11 +16,17 @@ StyledRect {
|
|||
required property string modelData
|
||||
required property Props props
|
||||
|
||||
readonly property list<var> notifs: Notifs.list.filter(notif => notif.appName === modelData).reverse()
|
||||
readonly property list<var> notifs: Notifs.list.filter(n => n.appName === modelData)
|
||||
readonly property int notifCount: notifs.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
|
||||
readonly property string image: notifs.find(n => n.image.length > 0)?.image ?? ""
|
||||
readonly property string appIcon: notifs.find(n => n.appIcon.length > 0)?.appIcon ?? ""
|
||||
readonly property int urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? NotificationUrgency.Critical : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? NotificationUrgency.Normal : NotificationUrgency.Low
|
||||
|
||||
readonly property int nonAnimHeight: {
|
||||
const headerHeight = header.implicitHeight + (root.expanded ? Math.round(Appearance.spacing.small / 2) : 0);
|
||||
const columnHeight = headerHeight + notifList.nonAnimHeight + column.Layout.topMargin + column.Layout.bottomMargin;
|
||||
return Math.round(Math.max(Config.notifs.sizes.image, columnHeight) + Appearance.padding.normal * 2);
|
||||
}
|
||||
readonly property bool expanded: props.expandedNotifs.includes(modelData)
|
||||
|
||||
function toggleExpand(expand: bool): void {
|
||||
|
|
@ -32,6 +38,11 @@ StyledRect {
|
|||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (notifCount === 0 && expanded)
|
||||
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
|
||||
}
|
||||
|
||||
anchors.left: parent?.left
|
||||
anchors.right: parent?.right
|
||||
implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
|
||||
|
|
@ -133,6 +144,8 @@ StyledRect {
|
|||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
id: header
|
||||
|
||||
Layout.bottomMargin: root.expanded ? Math.round(Appearance.spacing.small / 2) : 0
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.smaller
|
||||
|
|
@ -178,7 +191,7 @@ StyledRect {
|
|||
|
||||
Layout.leftMargin: Appearance.padding.small / 2
|
||||
animate: true
|
||||
text: root.notifs.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
|
||||
text: root.notifCount
|
||||
color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
|
@ -198,6 +211,8 @@ StyledRect {
|
|||
}
|
||||
|
||||
NotifGroupList {
|
||||
id: notifList
|
||||
|
||||
props: root.props
|
||||
notifs: root.notifs
|
||||
expanded: root.expanded
|
||||
|
|
|
|||
|
|
@ -14,16 +14,23 @@ Item {
|
|||
required property list<var> notifs
|
||||
required property bool expanded
|
||||
|
||||
readonly property real nonAnimHeight: {
|
||||
let h = -root.spacing;
|
||||
for (let i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i);
|
||||
if (!item.modelData.closed)
|
||||
h += item.nonAnimHeight + root.spacing;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
readonly property int spacing: Math.round(Appearance.spacing.small / 2)
|
||||
property bool flag
|
||||
|
||||
signal requestToggleExpand(expand: bool)
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: {
|
||||
const item = repeater.itemAt(repeater.count - 1);
|
||||
return item ? item.y + item.implicitHeight : 0;
|
||||
}
|
||||
implicitHeight: nonAnimHeight
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
|
@ -145,4 +152,11 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,17 +12,23 @@ import QtQuick
|
|||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property list<Notif> list: []
|
||||
property list<Notif> list: []
|
||||
readonly property list<Notif> notClosed: list.filter(n => !n.closed)
|
||||
readonly property list<Notif> popups: list.filter(n => n.popup)
|
||||
property alias dnd: props.dnd
|
||||
|
||||
property bool loaded
|
||||
|
||||
onListChanged: {
|
||||
if (!loaded)
|
||||
return;
|
||||
if (loaded)
|
||||
saveTimer.restart();
|
||||
}
|
||||
|
||||
storage.setText(JSON.stringify(list.filter(n => !n.closed).map(n => ({
|
||||
Timer {
|
||||
id: saveTimer
|
||||
|
||||
interval: 1000
|
||||
onTriggered: storage.setText(JSON.stringify(root.notClosed.map(n => ({
|
||||
time: n.time,
|
||||
id: n.id,
|
||||
summary: n.summary,
|
||||
|
|
@ -35,7 +41,7 @@ Singleton {
|
|||
resident: n.resident,
|
||||
hasActionIcons: n.hasActionIcons,
|
||||
actions: n.actions
|
||||
}))));
|
||||
}))))
|
||||
}
|
||||
|
||||
PersistentProperties {
|
||||
|
|
@ -60,10 +66,11 @@ Singleton {
|
|||
onNotification: notif => {
|
||||
notif.tracked = true;
|
||||
|
||||
root.list.push(notifComp.createObject(root, {
|
||||
const comp = notifComp.createObject(root, {
|
||||
popup: !props.dnd && ![...Visibilities.screens.values()].some(v => v.sidebar),
|
||||
notification: notif
|
||||
}));
|
||||
});
|
||||
root.list = [comp, ...root.list];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +82,7 @@ Singleton {
|
|||
const data = JSON.parse(text());
|
||||
for (const notif of data)
|
||||
root.list.push(notifComp.createObject(root, notif));
|
||||
root.list.sort((a, b) => a.time - b.time);
|
||||
root.loaded = true;
|
||||
}
|
||||
onLoadFailed: err => {
|
||||
|
|
@ -140,21 +148,17 @@ Singleton {
|
|||
}
|
||||
|
||||
property Notification notification
|
||||
property string id: notification?.id ?? ""
|
||||
property string summary: notification?.summary ?? ""
|
||||
property string body: notification?.body ?? ""
|
||||
property string appIcon: notification?.appIcon ?? ""
|
||||
property string appName: notification?.appName ?? ""
|
||||
property string image: notification?.image ?? ""
|
||||
property real expireTimeout: notification?.expireTimeout ?? Config.notifs.defaultExpireTimeout
|
||||
property int urgency: notification?.urgency ?? NotificationUrgency.Normal
|
||||
property bool resident: notification?.resident ?? false
|
||||
property bool hasActionIcons: notification?.hasActionIcons ?? false
|
||||
property list<var> actions: notification?.actions.map(a => ({
|
||||
identifier: a.identifier,
|
||||
text: a.text,
|
||||
invoke: () => a.invoke()
|
||||
})) ?? []
|
||||
property string id
|
||||
property string summary
|
||||
property string body
|
||||
property string appIcon
|
||||
property string appName
|
||||
property string image
|
||||
property real expireTimeout: Config.notifs.defaultExpireTimeout
|
||||
property int urgency: NotificationUrgency.Normal
|
||||
property bool resident
|
||||
property bool hasActionIcons
|
||||
property list<var> actions
|
||||
|
||||
readonly property Timer timer: Timer {
|
||||
running: true
|
||||
|
|
@ -171,6 +175,50 @@ Singleton {
|
|||
function onClosed(): void {
|
||||
notif.close();
|
||||
}
|
||||
|
||||
function onSummaryChanged(): void {
|
||||
notif.summary = notif.notification.summary;
|
||||
}
|
||||
|
||||
function onBodyChanged(): void {
|
||||
notif.body = notif.notification.body;
|
||||
}
|
||||
|
||||
function onAppIconChanged(): void {
|
||||
notif.appIcon = notif.notification.appIcon;
|
||||
}
|
||||
|
||||
function onAppNameChanged(): void {
|
||||
notif.appName = notif.notification.appName;
|
||||
}
|
||||
|
||||
function onImageChanged(): void {
|
||||
notif.image = notif.notification.image;
|
||||
}
|
||||
|
||||
function onExpireTimeoutChanged(): void {
|
||||
notif.expireTimeout = notif.notification.expireTimeout;
|
||||
}
|
||||
|
||||
function onUrgencyChanged(): void {
|
||||
notif.urgency = notif.notification.urgency;
|
||||
}
|
||||
|
||||
function onResidentChanged(): void {
|
||||
notif.resident = notif.notification.resident;
|
||||
}
|
||||
|
||||
function onHasActionIconsChanged(): void {
|
||||
notif.hasActionIcons = notif.notification.hasActionIcons;
|
||||
}
|
||||
|
||||
function onActionsChanged(): void {
|
||||
notif.actions = notif.notification.actions.map(a => ({
|
||||
identifier: a.identifier,
|
||||
text: a.text,
|
||||
invoke: () => a.invoke()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function lock(item: Item): void {
|
||||
|
|
@ -195,6 +243,27 @@ Singleton {
|
|||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!notification)
|
||||
return;
|
||||
|
||||
id = notification.id;
|
||||
summary = notification.summary;
|
||||
body = notification.body;
|
||||
appIcon = notification.appIcon;
|
||||
appName = notification.appName;
|
||||
image = notification.image;
|
||||
expireTimeout = notification.expireTimeout;
|
||||
urgency = notification.urgency;
|
||||
resident = notification.resident;
|
||||
hasActionIcons = notification.hasActionIcons;
|
||||
actions = notification.actions.map(a => ({
|
||||
identifier: a.identifier,
|
||||
text: a.text,
|
||||
invoke: () => a.invoke()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue