notif actions rework
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
import qs.Daemons
|
||||
@@ -149,11 +151,12 @@ Item {
|
||||
color: "transparent"
|
||||
implicitHeight: notif.implicitHeight
|
||||
implicitWidth: notif.implicitWidth
|
||||
radius: Appearance.rounding.smallest / 2
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
Notification {
|
||||
id: notif
|
||||
|
||||
implicitWidth: root.implicitWidth - root.padding * 2
|
||||
modelData: wrapper.modelData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
import qs.Modules
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import ZShell.Components
|
||||
import qs.Daemons
|
||||
import qs.Helpers
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Services.Notifications
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Config
|
||||
import qs.Components
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
readonly property int bodyTextFormat: /[<*_`#\[\]]/.test(modelData.body) ? Text.MarkdownText : Text.PlainText
|
||||
property bool expanded: Config.notifs.openExpanded
|
||||
readonly property bool hasAppIcon: modelData.appIcon.length > 0
|
||||
readonly property bool hasImage: modelData.image.length > 0
|
||||
required property NotifServer.Notif modelData
|
||||
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
|
||||
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? Appearance.spacing.extraSmall * 2 + appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
|
||||
|
||||
clip: true
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitHeight: inner.implicitHeight
|
||||
implicitWidth: Config.notifs.sizes.width
|
||||
radius: 6
|
||||
x: Config.notifs.sizes.width
|
||||
radius: Appearance.rounding.small
|
||||
x: implicitWidth
|
||||
|
||||
Behavior on x {
|
||||
Anim {
|
||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +53,7 @@ CustomRect {
|
||||
return;
|
||||
|
||||
const actions = root.modelData.actions;
|
||||
if (actions?.length === 1)
|
||||
if (actions.length === 1)
|
||||
actions[0].invoke();
|
||||
}
|
||||
onEntered: root.modelData.timer.stop()
|
||||
@@ -80,7 +78,7 @@ CustomRect {
|
||||
if (!containsMouse)
|
||||
root.modelData.timer.start();
|
||||
|
||||
if (Math.abs(root.x) < Config.notifs.sizes.width * Config.notifs.clearThreshold)
|
||||
if (Math.abs(root.x) < root.implicitWidth * Config.notifs.clearThreshold)
|
||||
root.x = 0;
|
||||
else
|
||||
root.modelData.popup = false;
|
||||
@@ -90,7 +88,7 @@ CustomRect {
|
||||
id: inner
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 8
|
||||
anchors.margins: Appearance.padding.normal
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitHeight: root.nonAnimHeight
|
||||
@@ -111,7 +109,8 @@ CustomRect {
|
||||
visible: root.hasImage || root.hasAppIcon
|
||||
width: Config.notifs.sizes.image
|
||||
|
||||
sourceComponent: ClippingRectangle {
|
||||
sourceComponent: CustomClippingRect {
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) : DynamicColors.palette.m3secondaryContainer
|
||||
implicitHeight: Config.notifs.sizes.image
|
||||
implicitWidth: Config.notifs.sizes.image
|
||||
radius: Appearance.rounding.full
|
||||
@@ -121,8 +120,11 @@ CustomRect {
|
||||
asynchronous: true
|
||||
cache: false
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
mipmap: true
|
||||
source: Qt.resolvedUrl(root.modelData.image)
|
||||
sourceSize: {
|
||||
const size = Config.notifs.sizes.image * ((QsWindow.window as QsWindow)?.devicePixelRatio ?? 1);
|
||||
return Qt.size(size, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,8 +154,9 @@ CustomRect {
|
||||
height: Math.round(parent.width * 0.6)
|
||||
width: Math.round(parent.width * 0.6)
|
||||
|
||||
sourceComponent: CustomIcon {
|
||||
sourceComponent: ColoredIcon {
|
||||
anchors.fill: parent
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
|
||||
layer.enabled: root.modelData.appIcon.endsWith("symbolic")
|
||||
source: Quickshell.iconPath(root.modelData.appIcon)
|
||||
}
|
||||
@@ -162,34 +165,67 @@ CustomRect {
|
||||
Loader {
|
||||
active: !root.hasAppIcon
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -18 * 0.02
|
||||
anchors.verticalCenterOffset: 18 * 0.02
|
||||
anchors.verticalCenterOffset: 1
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
|
||||
font.pointSize: 18
|
||||
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: progressIndicator
|
||||
|
||||
anchors.centerIn: appIcon
|
||||
height: appIcon.implicitHeight + progressShape.strokeWidth * 2
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
width: appIcon.implicitWidth + progressShape.strokeWidth * 2
|
||||
|
||||
ShapePath {
|
||||
id: progressShape
|
||||
|
||||
capStyle: ShapePath.RoundCap
|
||||
fillColor: "transparent"
|
||||
strokeColor: DynamicColors.palette.m3primary
|
||||
strokeWidth: 2
|
||||
|
||||
PathAngleArc {
|
||||
id: progressArc
|
||||
|
||||
centerX: progressIndicator.width / 2
|
||||
centerY: progressIndicator.height / 2
|
||||
radiusX: progressIndicator.width / 2 - Appearance.padding.extraSmall / 2
|
||||
radiusY: progressIndicator.height / 2 - Appearance.padding.extraSmall / 2
|
||||
startAngle: -90
|
||||
sweepAngle: ((root.modelData.hints.value ?? 0) / 100) * 360
|
||||
|
||||
Behavior on sweepAngle {
|
||||
Anim {
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: appName
|
||||
|
||||
anchors.left: image.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.leftMargin: Appearance.spacing.small
|
||||
anchors.top: parent.top
|
||||
animate: true
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
font.pointSize: 10
|
||||
maximumLineCount: 1
|
||||
opacity: root.expanded ? 1 : 0
|
||||
text: appNameMetrics.elidedText
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,9 +234,8 @@ CustomRect {
|
||||
id: appNameMetrics
|
||||
|
||||
elide: Text.ElideRight
|
||||
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3
|
||||
font.family: appName.font.family
|
||||
font.pointSize: appName.font.pointSize
|
||||
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - Appearance.spacing.small * 3
|
||||
font: appName.font
|
||||
text: root.modelData.appName
|
||||
}
|
||||
|
||||
@@ -208,7 +243,7 @@ CustomRect {
|
||||
id: summary
|
||||
|
||||
anchors.left: image.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.leftMargin: Appearance.spacing.small
|
||||
anchors.top: parent.top
|
||||
animate: true
|
||||
height: implicitHeight
|
||||
@@ -224,6 +259,9 @@ CustomRect {
|
||||
when: root.expanded
|
||||
|
||||
PropertyChanges {
|
||||
body.anchors.topMargin: Appearance.spacing.extraSmall
|
||||
bodyPreview.anchors.topMargin: Appearance.spacing.extraSmall
|
||||
summary.anchors.topMargin: Appearance.spacing.extraSmall
|
||||
summary.maximumLineCount: undefined
|
||||
}
|
||||
|
||||
@@ -238,10 +276,11 @@ CustomRect {
|
||||
target: summary
|
||||
}
|
||||
|
||||
AnchorAnimation {
|
||||
duration: MaterialEasing.expressiveEffectsTime
|
||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
easing.type: Easing.BezierSpline
|
||||
Anim {
|
||||
property: "topMargin"
|
||||
}
|
||||
|
||||
AnchorAnim {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,9 +289,8 @@ CustomRect {
|
||||
id: summaryMetrics
|
||||
|
||||
elide: Text.ElideRight
|
||||
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3
|
||||
font.family: summary.font.family
|
||||
font.pointSize: summary.font.pointSize
|
||||
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - Appearance.spacing.small * 3
|
||||
font: summary.font
|
||||
text: root.modelData.summary
|
||||
}
|
||||
|
||||
@@ -260,10 +298,9 @@ CustomRect {
|
||||
id: timeSep
|
||||
|
||||
anchors.left: summary.right
|
||||
anchors.leftMargin: 7
|
||||
anchors.leftMargin: Appearance.spacing.small
|
||||
anchors.top: parent.top
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
font.pointSize: 10
|
||||
text: "•"
|
||||
|
||||
states: State {
|
||||
@@ -276,10 +313,7 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
transitions: Transition {
|
||||
AnchorAnimation {
|
||||
duration: MaterialEasing.expressiveEffectsTime
|
||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
easing.type: Easing.BezierSpline
|
||||
AnchorAnim {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,11 +322,10 @@ CustomRect {
|
||||
id: time
|
||||
|
||||
anchors.left: timeSep.right
|
||||
anchors.leftMargin: 7
|
||||
anchors.leftMargin: Appearance.spacing.small
|
||||
anchors.top: parent.top
|
||||
animate: true
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
font.pointSize: 10
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
text: root.modelData.timeStr
|
||||
}
|
||||
@@ -302,25 +335,33 @@ CustomRect {
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
implicitHeight: expandIcon.height
|
||||
implicitWidth: expandIcon.height
|
||||
anchors.topMargin: -Appearance.padding.extraSmall
|
||||
implicitHeight: expandIcon.implicitHeight
|
||||
implicitWidth: expandIcon.implicitHeight
|
||||
|
||||
StateLayer {
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
onClicked: {
|
||||
root.expanded = !root.expanded;
|
||||
}
|
||||
onClicked: root.expanded = !root.expanded
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: expandIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
font.pointSize: 13
|
||||
text: root.expanded ? "expand_less" : "expand_more"
|
||||
anchors.verticalCenterOffset: root.expanded ? -1 : 1
|
||||
rotation: root.expanded ? 180 : 0
|
||||
text: "expand_more"
|
||||
|
||||
Behavior on anchors.verticalCenterOffset {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on rotation {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,17 +370,17 @@ CustomRect {
|
||||
|
||||
anchors.left: summary.left
|
||||
anchors.right: expandBtn.left
|
||||
anchors.rightMargin: 7
|
||||
anchors.rightMargin: Appearance.spacing.small
|
||||
anchors.top: summary.bottom
|
||||
animate: true
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
font.pointSize: 10
|
||||
opacity: root.expanded ? 0 : 1
|
||||
text: bodyPreviewMetrics.elidedText
|
||||
textFormat: Text.MarkdownText
|
||||
textFormat: root.bodyTextFormat
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,8 +390,7 @@ CustomRect {
|
||||
|
||||
elide: Text.ElideRight
|
||||
elideWidth: bodyPreview.width
|
||||
font.family: bodyPreview.font.family
|
||||
font.pointSize: bodyPreview.font.pointSize
|
||||
font: bodyPreview.font
|
||||
text: root.modelData.body
|
||||
}
|
||||
|
||||
@@ -359,19 +399,19 @@ CustomRect {
|
||||
|
||||
anchors.left: summary.left
|
||||
anchors.right: expandBtn.left
|
||||
anchors.rightMargin: 7
|
||||
anchors.rightMargin: Appearance.spacing.small
|
||||
anchors.top: summary.bottom
|
||||
animate: true
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
font.pointSize: 10
|
||||
height: text ? implicitHeight : 0
|
||||
opacity: root.expanded ? 1 : 0
|
||||
text: root.modelData.body
|
||||
textFormat: Text.MarkdownText
|
||||
textFormat: root.bodyTextFormat
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,84 +424,80 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ButtonRow {
|
||||
id: actions
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.left: body.left
|
||||
anchors.right: body.right
|
||||
anchors.top: body.bottom
|
||||
anchors.topMargin: 7
|
||||
anchors.topMargin: Appearance.spacing.small
|
||||
opacity: root.expanded ? 1 : 0
|
||||
spacing: 10
|
||||
spacing: Appearance.spacing.extraSmall
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
modelData: QtObject {
|
||||
readonly property string text: qsTr("Close")
|
||||
IconButton {
|
||||
fillWidth: root.modelData.actions.length === 0
|
||||
icon: "close"
|
||||
inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
||||
inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
|
||||
isRound: true
|
||||
padding: Appearance.padding.extraSmall
|
||||
shapeMorph: true
|
||||
|
||||
function invoke(): void {
|
||||
root.modelData.close();
|
||||
}
|
||||
}
|
||||
onClicked: root.modelData.close()
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.modelData.actions
|
||||
|
||||
delegate: Component {
|
||||
Action {
|
||||
}
|
||||
TextButton {
|
||||
required property var modelData
|
||||
|
||||
fillWidth: true
|
||||
inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
||||
inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
|
||||
isRound: true
|
||||
label.anchors.centerIn: undefined
|
||||
label.anchors.left: left
|
||||
label.anchors.margins: Appearance.padding.normal
|
||||
label.anchors.right: right
|
||||
label.anchors.verticalCenter: verticalCenter
|
||||
label.elide: Text.ElideRight
|
||||
label.horizontalAlignment: Text.AlignHCenter
|
||||
shapeMorph: true
|
||||
text: modelData.text
|
||||
|
||||
onClicked: modelData.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
fillWidth: root.modelData.actions.length === 0
|
||||
icon: copyTimer.running ? "inventory" : "content_copy"
|
||||
inactiveColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
||||
inactiveOnColor: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
|
||||
isRound: true
|
||||
label.animate: true
|
||||
padding: Appearance.padding.extraSmall
|
||||
shapeMorph: true
|
||||
|
||||
onClicked: {
|
||||
Quickshell.clipboardText = root.modelData.body;
|
||||
copyTimer.restart();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: copyTimer
|
||||
|
||||
interval: 3000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Action: CustomRect {
|
||||
id: action
|
||||
|
||||
required property var modelData
|
||||
|
||||
Layout.preferredHeight: actionText.height + 4 * 2
|
||||
Layout.preferredWidth: actionText.width + 8 * 2
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
||||
implicitHeight: actionText.height + 4 * 2
|
||||
implicitWidth: actionText.width + 8 * 2
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
StateLayer {
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurface
|
||||
enabled: root.expanded
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
onClicked: {
|
||||
action.modelData.invoke();
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: actionText
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
|
||||
font.pointSize: 10
|
||||
text: actionTextMetrics.elidedText
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: actionTextMetrics
|
||||
|
||||
elide: Text.ElideRight
|
||||
elideWidth: {
|
||||
const numActions = root.modelData.actions.length + 1;
|
||||
return (inner.width - actions.spacing * (numActions - 1)) / numActions - 8 * 2;
|
||||
}
|
||||
font.family: actionText.font.family
|
||||
font.pointSize: actionText.font.pointSize
|
||||
text: action.modelData.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user