C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Failing after 27s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Failing after 1m26s
Rust / fmt (pull_request) Successful in 46s
Rust / clippy (pull_request) Successful in 1m6s
202 lines
4.6 KiB
QML
202 lines
4.6 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Shapes
|
|
import Quickshell
|
|
import ZShell
|
|
import M3Shapes
|
|
import ZShell.Config
|
|
import qs.Components
|
|
import qs.Helpers
|
|
import qs.Services
|
|
import Quickshell.Services.Notifications
|
|
|
|
Item {
|
|
id: root
|
|
|
|
required property var owner
|
|
required property string image
|
|
required property string icon
|
|
required property int urgency
|
|
required property string summary
|
|
required property color lowFillColour
|
|
property real progress: 0
|
|
property string shownImage
|
|
property string shownIcon
|
|
|
|
readonly property color fillColour: urgency === NotificationUrgency.Critical ? Colors.palette.m3primary : urgency === NotificationUrgency.Low ? lowFillColour : Colors.palette.m3secondaryContainer
|
|
readonly property color iconColour: urgency === NotificationUrgency.Critical ? Colors.palette.m3onPrimary : urgency === NotificationUrgency.Low ? Colors.palette.m3onSurfaceVariant : Colors.palette.m3onSecondaryContainer
|
|
|
|
implicitWidth: Config.notifs.sizes.image
|
|
implicitHeight: Config.notifs.sizes.image
|
|
|
|
// Keeping the previously shown url avoids replaying the fade when a row
|
|
// swaps between its raw source and the cached blob.
|
|
onImageChanged: {
|
|
if (image && !owner.isSameImage(shownImage))
|
|
shownImage = image;
|
|
}
|
|
onIconChanged: {
|
|
if (icon && !owner.isSameAppIcon(shownIcon))
|
|
shownIcon = icon;
|
|
}
|
|
Component.onCompleted: {
|
|
shownImage = image;
|
|
shownIcon = icon;
|
|
}
|
|
|
|
Component {
|
|
id: imageComp
|
|
|
|
FadeImage {
|
|
source: Qt.resolvedUrl(root.shownImage)
|
|
width: Config.notifs.sizes.image
|
|
height: Config.notifs.sizes.image
|
|
fadeInLargeAnim: Anim.DefaultEffects
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: appIconComp
|
|
|
|
FadeIcon {
|
|
size: Math.round(Config.notifs.sizes.image * 0.6)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: materialIconComp
|
|
|
|
MaterialIcon {
|
|
animate: true
|
|
text: Icons.getNotifIcon(root.summary, root.urgency)
|
|
color: root.iconColour
|
|
fontStyle: Tokens.font.icon.medium
|
|
}
|
|
}
|
|
|
|
MaterialShape {
|
|
id: bgShape
|
|
|
|
anchors.fill: parent
|
|
shape: contentLoader.sourceComponent === materialIconComp && root.urgency === NotificationUrgency.Critical ? MaterialShape.SoftBurst : MaterialShape.Circle
|
|
color: Qt.alpha(root.fillColour, 1)
|
|
opacity: root.fillColour.a
|
|
|
|
Behavior on color {
|
|
CAnim {}
|
|
}
|
|
|
|
Behavior on opacity {
|
|
Anim {
|
|
type: Anim.SlowEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
anchors.fill: parent
|
|
|
|
Loader {
|
|
id: contentLoader
|
|
|
|
anchors.centerIn: parent
|
|
anchors.verticalCenterOffset: sourceComponent === materialIconComp ? 1 : 0
|
|
asynchronous: true
|
|
sourceComponent: root.image ? imageComp : root.icon ? appIconComp : materialIconComp
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
asynchronous: true
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
active: opacity > 0
|
|
opacity: root.icon && root.image ? 1 : 0
|
|
|
|
sourceComponent: CustomRect {
|
|
implicitWidth: Config.notifs.sizes.badge
|
|
implicitHeight: Config.notifs.sizes.badge
|
|
|
|
color: root.fillColour
|
|
radius: Tokens.rounding.full
|
|
|
|
FadeIcon {
|
|
anchors.centerIn: parent
|
|
size: Math.round(Config.notifs.sizes.badge * 0.6)
|
|
}
|
|
}
|
|
|
|
Behavior on opacity {
|
|
Anim {
|
|
type: root.icon && root.image ? Anim.DefaultEffects : Anim.FastEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
asynchronous: true
|
|
anchors.fill: parent
|
|
active: root.progress > 0
|
|
|
|
sourceComponent: Shape {
|
|
preferredRendererType: Shape.CurveRenderer
|
|
|
|
ShapePath {
|
|
id: progressShape
|
|
|
|
property real sweep: root.progress * 360
|
|
property real spacing: root.urgency === NotificationUrgency.Critical ? Tokens.spacing.extraSmall : 0
|
|
|
|
capStyle: ShapePath.RoundCap
|
|
fillColor: "transparent"
|
|
strokeWidth: 2
|
|
strokeColor: Colors.palette.m3primary
|
|
|
|
PathPolyline {
|
|
function pointAt(angle: real): point {
|
|
const dist = bgShape.distanceAtAngle(angle) + progressShape.strokeWidth / 2 + progressShape.spacing;
|
|
const rad = angle * Math.PI / 180;
|
|
return Qt.point(root.width / 2 + dist * Math.sin(rad), root.height / 2 - dist * Math.cos(rad));
|
|
}
|
|
|
|
path: {
|
|
bgShape.morphProgress; // mapToItem-free dependency: re-run when the shape morphs
|
|
const points = [];
|
|
for (let angle = 0; angle < progressShape.sweep; angle += 3)
|
|
points.push(pointAt(angle));
|
|
points.push(pointAt(progressShape.sweep));
|
|
return points;
|
|
}
|
|
}
|
|
|
|
Behavior on sweep {
|
|
Anim {
|
|
easing: Tokens.anim.emphasizedDecel
|
|
}
|
|
}
|
|
|
|
Behavior on spacing {
|
|
Anim {}
|
|
}
|
|
|
|
Behavior on strokeColor {
|
|
CAnim {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component FadeIcon: FadeImage {
|
|
id: fadeIcon
|
|
|
|
required property int size
|
|
|
|
width: size
|
|
height: size
|
|
source: Quickshell.iconPath(root.shownIcon)
|
|
fillMode: Image.PreserveAspectFit
|
|
fadeInLargeAnim: Anim.DefaultEffects
|
|
}
|
|
}
|