nosignal-shell/modules/sidebar/Notif.qml
2 * r + 2 * t 2efe1a93a6 fix: layout delegates after configurable delay
Fixes glitches due to delegate sizes changing on creation
Also use the loader state pattern to ensure size is set on same frame as
active
2026-04-10 00:19:32 +10:00

205 lines
5.7 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.components
import qs.services
import qs.config
StyledRect {
id: root
required property NotifData modelData
required property Props props
required property bool expanded
required property DrawerVisibilities visibilities
readonly property StyledText body: (expandedContent.item as ExpandedBody)?.body ?? null
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Appearance.padding.normal * 2 : summaryHeightMetrics.height
implicitHeight: nonAnimHeight
radius: Appearance.rounding.small
color: {
const c = root.modelData?.urgency === "critical" ? Colours.palette.m3secondaryContainer : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2);
return expanded ? c : Qt.alpha(c, 0);
}
state: expanded ? "expanded" : ""
states: State {
name: "expanded"
PropertyChanges {
summary.anchors.margins: Appearance.padding.normal
dummySummary.anchors.margins: Appearance.padding.normal
compactBody.anchors.margins: Appearance.padding.normal
timeStr.anchors.margins: Appearance.padding.normal
expandedContent.anchors.margins: Appearance.padding.normal
summary.width: root.width - Appearance.padding.normal * 2 - timeStr.implicitWidth - Appearance.spacing.small
summary.maximumLineCount: Number.MAX_SAFE_INTEGER
}
}
transitions: Transition {
Anim {
properties: "margins,width,maximumLineCount"
}
}
TextMetrics {
id: summaryHeightMetrics
font: summary.font
text: " " // Use this height to prevent weird characters from changing the line height
}
StyledText {
id: summary
anchors.top: parent.top
anchors.left: parent.left
width: parent.width
text: root.modelData?.summary ?? ""
color: root.modelData?.urgency === "critical" ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 1
}
StyledText {
id: dummySummary
anchors.top: parent.top
anchors.left: parent.left
visible: false
text: root.modelData?.summary ?? ""
}
WrappedLoader {
id: compactBody
shouldBeActive: !root.expanded
anchors.top: parent.top
anchors.left: dummySummary.right
anchors.right: parent.right
anchors.leftMargin: Appearance.spacing.small
sourceComponent: StyledText {
text: String(root.modelData?.body ?? "").replace(/\n/g, " ")
color: root.modelData?.urgency === "critical" ? Colours.palette.m3secondary : Colours.palette.m3outline
elide: Text.ElideRight
}
}
WrappedLoader {
id: timeStr
shouldBeActive: root.expanded
anchors.top: parent.top
anchors.right: parent.right
sourceComponent: StyledText {
animate: true
text: root.modelData?.timeStr ?? ""
color: Colours.palette.m3outline
font.pointSize: Appearance.font.size.small
}
}
WrappedLoader {
id: expandedContent
shouldBeActive: root.expanded
anchors.top: summary.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: Appearance.spacing.small / 2
sourceComponent: ExpandedBody {}
}
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
component ExpandedBody: ColumnLayout {
readonly property alias body: bodyText
spacing: Appearance.spacing.smaller
StyledText {
id: bodyText
Layout.fillWidth: true
textFormat: Text.MarkdownText
text: String(root.modelData?.body ?? "").replace(/(.)\n(?!\n)/g, "$1\n\n") || qsTr("No body here! :/")
color: root.modelData?.urgency === "critical" ? Colours.palette.m3secondary : Colours.palette.m3outline
wrapMode: Text.WordWrap
onLinkActivated: link => {
Quickshell.execDetached(["app2unit", "-O", "--", link]);
root.visibilities.sidebar = false;
}
}
NotifActionList {
notif: root.modelData
}
}
component WrappedLoader: Loader {
id: comp
required property bool shouldBeActive
active: false
opacity: 0
// Makes the loader load on the same frame shouldBeActive becomes true, which ensures size is set
states: State {
name: "active"
when: comp.shouldBeActive
PropertyChanges {
comp.opacity: 1
comp.active: true
}
}
transitions: [
Transition {
from: ""
to: "active"
SequentialAnimation {
PropertyAction {
property: "active"
}
Anim {
property: "opacity"
}
}
},
Transition {
from: "active"
to: ""
SequentialAnimation {
Anim {
property: "opacity"
}
PropertyAction {
property: "active"
}
}
}
]
}
}