lock: add notifs and status
This commit is contained in:
parent
fbb58e3e1d
commit
d7d4d86da2
5 changed files with 556 additions and 0 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
|
||||||
JsonObject {
|
JsonObject {
|
||||||
|
property int maxNotifs: 5
|
||||||
|
|
||||||
property JsonObject sizes: JsonObject {
|
property JsonObject sizes: JsonObject {
|
||||||
property int border: 100
|
property int border: 100
|
||||||
property int clockWidth: 800
|
property int clockWidth: 800
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ Item {
|
||||||
required property real weatherWidth
|
required property real weatherWidth
|
||||||
required property real buttonsWidth
|
required property real buttonsWidth
|
||||||
required property real buttonsHeight
|
required property real buttonsHeight
|
||||||
|
required property real statusWidth
|
||||||
|
required property real statusHeight
|
||||||
required property bool isNormal
|
required property bool isNormal
|
||||||
required property bool isLarge
|
required property bool isLarge
|
||||||
|
|
||||||
|
|
@ -21,6 +23,8 @@ Item {
|
||||||
readonly property real weatherRight: innerMask.anchors.margins + weatherPath.width
|
readonly property real weatherRight: innerMask.anchors.margins + weatherPath.width
|
||||||
readonly property real buttonsTop: innerMask.anchors.margins + buttonsPath.height
|
readonly property real buttonsTop: innerMask.anchors.margins + buttonsPath.height
|
||||||
readonly property real buttonsLeft: innerMask.anchors.margins + buttonsPath.width
|
readonly property real buttonsLeft: innerMask.anchors.margins + buttonsPath.width
|
||||||
|
readonly property real statusBottom: innerMask.anchors.margins + statusPath.height
|
||||||
|
readonly property real statusLeft: innerMask.anchors.margins + statusPath.width
|
||||||
|
|
||||||
readonly property real mediaX: innerMask.anchors.margins + mediaPath.width
|
readonly property real mediaX: innerMask.anchors.margins + mediaPath.width
|
||||||
readonly property real mediaY: innerMask.anchors.margins + mediaPath.height
|
readonly property real mediaY: innerMask.anchors.margins + mediaPath.height
|
||||||
|
|
@ -400,6 +404,72 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
id: statusPath
|
||||||
|
|
||||||
|
property int width: root.locked ? root.statusWidth - Config.lock.sizes.border / 4 : 0
|
||||||
|
property real height: root.locked ? root.statusHeight - Config.lock.sizes.border / 4 : 0
|
||||||
|
|
||||||
|
readonly property real rounding: Appearance.rounding.large * 2
|
||||||
|
readonly property real roundingX: width < rounding * 2 ? width / 2 : rounding
|
||||||
|
readonly property real roundingY: height < rounding * 2 ? height / 2 : rounding
|
||||||
|
|
||||||
|
strokeWidth: -1
|
||||||
|
fillColor: root.isLarge ? Config.border.colour : "transparent"
|
||||||
|
|
||||||
|
startX: Math.ceil(innerMask.width)
|
||||||
|
startY: height + roundingY
|
||||||
|
|
||||||
|
PathArc {
|
||||||
|
relativeX: -statusPath.roundingX
|
||||||
|
relativeY: -statusPath.roundingY
|
||||||
|
radiusX: Math.min(statusPath.rounding, statusPath.width)
|
||||||
|
radiusY: Math.min(statusPath.rounding, statusPath.height)
|
||||||
|
direction: PathArc.Counterclockwise
|
||||||
|
}
|
||||||
|
PathLine {
|
||||||
|
relativeX: -(statusPath.width - statusPath.roundingX * 2)
|
||||||
|
relativeY: 0
|
||||||
|
}
|
||||||
|
PathArc {
|
||||||
|
relativeX: -statusPath.roundingX
|
||||||
|
relativeY: -statusPath.roundingY
|
||||||
|
radiusX: Math.min(statusPath.rounding, statusPath.width)
|
||||||
|
radiusY: Math.min(statusPath.rounding, statusPath.height)
|
||||||
|
}
|
||||||
|
PathLine {
|
||||||
|
relativeX: 0
|
||||||
|
relativeY: -(statusPath.height - statusPath.roundingY * 2)
|
||||||
|
}
|
||||||
|
PathArc {
|
||||||
|
relativeX: -statusPath.roundingX
|
||||||
|
relativeY: -statusPath.roundingY
|
||||||
|
radiusX: Math.min(statusPath.rounding, statusPath.width)
|
||||||
|
radiusY: Math.min(statusPath.rounding, statusPath.height)
|
||||||
|
direction: PathArc.Counterclockwise
|
||||||
|
}
|
||||||
|
PathLine {
|
||||||
|
relativeX: statusPath.width + statusPath.roundingX
|
||||||
|
relativeY: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on fillColor {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component Anim: NumberAnimation {
|
component Anim: NumberAnimation {
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,8 @@ WlSessionLockSurface {
|
||||||
weatherWidth: weather.implicitWidth
|
weatherWidth: weather.implicitWidth
|
||||||
buttonsWidth: buttons.item?.nonAnimWidth ?? 0
|
buttonsWidth: buttons.item?.nonAnimWidth ?? 0
|
||||||
buttonsHeight: buttons.item?.nonAnimHeight ?? 0
|
buttonsHeight: buttons.item?.nonAnimHeight ?? 0
|
||||||
|
statusWidth: status.item?.nonAnimWidth ?? 0
|
||||||
|
statusHeight: status.item?.nonAnimHeight ?? 0
|
||||||
isNormal: root.screen.width > Config.lock.sizes.smallScreenWidth
|
isNormal: root.screen.width > Config.lock.sizes.smallScreenWidth
|
||||||
isLarge: root.screen.width > Config.lock.sizes.largeScreenWidth
|
isLarge: root.screen.width > Config.lock.sizes.largeScreenWidth
|
||||||
visible: false
|
visible: false
|
||||||
|
|
@ -168,6 +170,19 @@ WlSessionLockSurface {
|
||||||
sourceComponent: Buttons {}
|
sourceComponent: Buttons {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: status
|
||||||
|
|
||||||
|
active: root.screen.width > Config.lock.sizes.largeScreenWidth
|
||||||
|
|
||||||
|
anchors.bottom: parent.top
|
||||||
|
anchors.left: parent.right
|
||||||
|
anchors.bottomMargin: -backgrounds.statusBottom
|
||||||
|
anchors.leftMargin: -backgrounds.statusLeft
|
||||||
|
|
||||||
|
sourceComponent: Status {}
|
||||||
|
}
|
||||||
|
|
||||||
component Anim: NumberAnimation {
|
component Anim: NumberAnimation {
|
||||||
duration: Appearance.anim.durations.large
|
duration: Appearance.anim.durations.large
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
|
|
|
||||||
220
modules/lock/Notification.qml
Normal file
220
modules/lock/Notification.qml
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import "root:/widgets"
|
||||||
|
import "root:/services"
|
||||||
|
import "root:/config"
|
||||||
|
import "root:/utils"
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import Quickshell.Services.Notifications
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property Notifs.Notif modelData
|
||||||
|
readonly property bool hasImage: modelData.image.length > 0
|
||||||
|
readonly property bool hasAppIcon: modelData.appIcon.length > 0
|
||||||
|
readonly property int nonAnimHeight: Math.max(image.height, details.implicitHeight) + Appearance.padding.normal * 2
|
||||||
|
|
||||||
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.palette.m3surfaceContainer
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
implicitWidth: Config.notifs.sizes.width
|
||||||
|
|
||||||
|
Component.onCompleted: implicitHeight = Qt.binding(() => nonAnimHeight)
|
||||||
|
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RetainableLock {
|
||||||
|
object: root.modelData.notification
|
||||||
|
locked: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: pressed ? Qt.ClosedHandCursor : undefined
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||||
|
preventStealing: true
|
||||||
|
|
||||||
|
onEntered: root.modelData.timer.stop()
|
||||||
|
onExited: root.modelData.timer.start()
|
||||||
|
|
||||||
|
drag.target: parent
|
||||||
|
drag.axis: Drag.XAxis
|
||||||
|
|
||||||
|
onPressed: event => {
|
||||||
|
if (event.button === Qt.MiddleButton)
|
||||||
|
root.modelData.notification.dismiss();
|
||||||
|
}
|
||||||
|
onReleased: event => {
|
||||||
|
if (Math.abs(root.x) < Config.notifs.sizes.width * Config.notifs.clearThreshold)
|
||||||
|
root.x = 0;
|
||||||
|
else
|
||||||
|
root.modelData.notification.dismiss(); // TODO: change back to popup when notif dock impled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: image
|
||||||
|
|
||||||
|
active: root.hasImage
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: Appearance.padding.normal
|
||||||
|
|
||||||
|
width: Config.notifs.sizes.image
|
||||||
|
height: Config.notifs.sizes.image
|
||||||
|
visible: root.hasImage || root.hasAppIcon
|
||||||
|
|
||||||
|
sourceComponent: ClippingRectangle {
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
implicitWidth: Config.notifs.sizes.image
|
||||||
|
implicitHeight: Config.notifs.sizes.image
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: Qt.resolvedUrl(root.modelData.image)
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
cache: false
|
||||||
|
asynchronous: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: appIcon
|
||||||
|
|
||||||
|
active: root.hasAppIcon || !root.hasImage
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
anchors.horizontalCenter: root.hasImage ? undefined : image.horizontalCenter
|
||||||
|
anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter
|
||||||
|
anchors.right: root.hasImage ? image.right : undefined
|
||||||
|
anchors.bottom: root.hasImage ? image.bottom : undefined
|
||||||
|
|
||||||
|
sourceComponent: StyledRect {
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3surfaceContainerHighest : Colours.palette.m3tertiaryContainer
|
||||||
|
implicitWidth: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
|
||||||
|
implicitHeight: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
active: root.hasAppIcon
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !root.modelData.appIcon.endsWith("symbolic")
|
||||||
|
|
||||||
|
width: Math.round(parent.width * 0.6)
|
||||||
|
height: Math.round(parent.width * 0.6)
|
||||||
|
|
||||||
|
sourceComponent: IconImage {
|
||||||
|
implicitSize: Math.round(parent.width * 0.6)
|
||||||
|
source: Quickshell.iconPath(root.modelData.appIcon)
|
||||||
|
asynchronous: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: root.modelData.appIcon.endsWith("symbolic")
|
||||||
|
asynchronous: true
|
||||||
|
anchors.fill: icon
|
||||||
|
|
||||||
|
sourceComponent: Colouriser {
|
||||||
|
source: icon
|
||||||
|
colorizationColor: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onTertiaryContainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: !root.hasAppIcon
|
||||||
|
asynchronous: true
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.horizontalCenterOffset: -Appearance.font.size.large * 0.02
|
||||||
|
anchors.verticalCenterOffset: Appearance.font.size.large * 0.02
|
||||||
|
|
||||||
|
sourceComponent: MaterialIcon {
|
||||||
|
text: Icons.getNotifIcon(root.modelData.summary.toLowerCase(), root.modelData.urgency)
|
||||||
|
|
||||||
|
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onTertiaryContainer
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
font.variableAxes: ({
|
||||||
|
opsz: Appearance.font.size.large
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: details
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: image.right
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.leftMargin: Appearance.spacing.smaller
|
||||||
|
anchors.rightMargin: Appearance.padding.larger
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: implicitWidth
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
text: root.modelData.summary
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "•"
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
animate: true
|
||||||
|
text: root.modelData.timeStr
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
text: root.modelData.body
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
249
modules/lock/Status.qml
Normal file
249
modules/lock/Status.qml
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
import "root:/widgets"
|
||||||
|
import "root:/services"
|
||||||
|
import "root:/config"
|
||||||
|
import "root:/utils"
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import Quickshell.Services.UPower
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
WrapperItem {
|
||||||
|
readonly property real nonAnimWidth: (notifs.count > 0 ? Config.notifs.sizes.width : status.implicitWidth) + margin
|
||||||
|
readonly property real nonAnimHeight: {
|
||||||
|
if (notifs.count > 0) {
|
||||||
|
const count = Math.min(notifs.count, Config.lock.maxNotifs);
|
||||||
|
let height = status.implicitHeight + Appearance.spacing.normal + Appearance.spacing.smaller * (count - 1);
|
||||||
|
for (let i = 0; i < count; i++)
|
||||||
|
height += notifs.itemAtIndex(i)?.nonAnimHeight ?? 0;
|
||||||
|
return height + margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status.implicitHeight + margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: nonAnimWidth
|
||||||
|
implicitHeight: nonAnimHeight
|
||||||
|
|
||||||
|
margin: Appearance.padding.large * 2
|
||||||
|
rightMargin: 0
|
||||||
|
topMargin: 0
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: status
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
active: UPower.displayDevice.isLaptopBattery
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
sourceComponent: StyledText {
|
||||||
|
animate: true
|
||||||
|
text: qsTr("%1%2 remaining").arg(UPower.onBattery ? "" : "(+) ").arg(UPower.displayDevice.percentage)
|
||||||
|
color: !UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? Colours.palette.m3onSurface : Colours.palette.m3error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
text: Network.active ? Icons.getNetworkIcon(Network.active.strength ?? 0) : "wifi_off"
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: item?.implicitWidth ?? 0
|
||||||
|
|
||||||
|
active: !UPower.displayDevice.isLaptopBattery
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
sourceComponent: StyledText {
|
||||||
|
animate: true
|
||||||
|
text: Network.active?.ssid ?? ""
|
||||||
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
text: Bluetooth.powered ? "bluetooth" : "bluetooth_disabled"
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
active: !UPower.displayDevice.isLaptopBattery
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
sourceComponent: StyledText {
|
||||||
|
animate: true
|
||||||
|
text: qsTr("%n device(s) connected", "", Bluetooth.devices.filter(d => d.connected).length)
|
||||||
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: notifs
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: [...Notifs.popups].reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
spacing: 0
|
||||||
|
clip: true
|
||||||
|
interactive: false
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: wrapper
|
||||||
|
|
||||||
|
required property Notifs.Notif modelData
|
||||||
|
required property int index
|
||||||
|
readonly property alias nonAnimHeight: notif.nonAnimHeight
|
||||||
|
property int idx
|
||||||
|
|
||||||
|
onIndexChanged: {
|
||||||
|
if (index !== -1)
|
||||||
|
idx = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: notif.implicitWidth
|
||||||
|
implicitHeight: notif.nonAnimHeight + (idx === 0 ? 0 : Appearance.spacing.smaller)
|
||||||
|
|
||||||
|
ListView.onRemove: removeAnim.start()
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: removeAnim
|
||||||
|
|
||||||
|
PropertyAction {
|
||||||
|
target: wrapper
|
||||||
|
property: "ListView.delayRemove"
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: wrapper
|
||||||
|
property: "enabled"
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: wrapper
|
||||||
|
property: "implicitHeight"
|
||||||
|
value: 0
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: wrapper
|
||||||
|
property: "z"
|
||||||
|
value: 1
|
||||||
|
}
|
||||||
|
Anim {
|
||||||
|
target: notif
|
||||||
|
property: "x"
|
||||||
|
to: (notif.x >= 0 ? Config.notifs.sizes.width : -Config.notifs.sizes.width) * 2
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: wrapper
|
||||||
|
property: "ListView.delayRemove"
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClippingRectangle {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: wrapper.idx === 0 ? 0 : Appearance.spacing.smaller
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
radius: notif.radius
|
||||||
|
implicitWidth: notif.implicitWidth
|
||||||
|
implicitHeight: notif.nonAnimHeight
|
||||||
|
|
||||||
|
Notification {
|
||||||
|
id: notif
|
||||||
|
|
||||||
|
modelData: wrapper.modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move: Transition {
|
||||||
|
Anim {
|
||||||
|
property: "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
displaced: Transition {
|
||||||
|
Anim {
|
||||||
|
property: "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Appearance.padding.normal
|
||||||
|
|
||||||
|
color: Colours.palette.m3tertiaryContainer
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
|
implicitWidth: count.implicitWidth + Appearance.padding.normal * 2
|
||||||
|
implicitHeight: count.implicitHeight + Appearance.padding.small * 2
|
||||||
|
|
||||||
|
scale: Notifs.popups.length > Config.lock.maxNotifs ? 1 : 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: count
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: qsTr("+%1").arg(Notifs.popups.length - Config.lock.maxNotifs)
|
||||||
|
color: Colours.palette.m3onTertiaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.expressiveFastSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Anim: NumberAnimation {
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue