notif actions rework
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
ButtonBase {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property alias label: label
|
||||||
|
property alias text: label.text
|
||||||
|
|
||||||
|
activeColor: type === TextButton.Filled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary
|
||||||
|
activeOnColor: {
|
||||||
|
if (type === TextButton.Text)
|
||||||
|
return DynamicColors.palette.m3primary;
|
||||||
|
return type === TextButton.Filled ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondary;
|
||||||
|
}
|
||||||
|
horizontalPadding: Appearance.padding.normal
|
||||||
|
implicitHeight: label.implicitHeight + verticalPadding * 2
|
||||||
|
implicitWidth: label.implicitWidth + horizontalPadding * 2
|
||||||
|
inactiveColor: {
|
||||||
|
if (!isToggle && type === TextButton.Filled)
|
||||||
|
return DynamicColors.palette.m3primary;
|
||||||
|
return type === TextButton.Filled ? DynamicColors.tPalette.m3surfaceContainer : DynamicColors.palette.m3secondaryContainer;
|
||||||
|
}
|
||||||
|
inactiveOnColor: {
|
||||||
|
if (!isToggle && type === TextButton.Filled)
|
||||||
|
return DynamicColors.palette.m3onPrimary;
|
||||||
|
if (type === TextButton.Text)
|
||||||
|
return DynamicColors.palette.m3primary;
|
||||||
|
return type === TextButton.Filled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer;
|
||||||
|
}
|
||||||
|
verticalPadding: Appearance.padding.small
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: label
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: root.onColor
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -215,6 +215,10 @@ Singleton {
|
|||||||
notif.hasActionIcons = notif.notification.hasActionIcons;
|
notif.hasActionIcons = notif.notification.hasActionIcons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onHintsChanged(): void {
|
||||||
|
notif.hints = notif.notification.hints;
|
||||||
|
}
|
||||||
|
|
||||||
function onImageChanged(): void {
|
function onImageChanged(): void {
|
||||||
notif.imageSource = notif.notification.image || "";
|
notif.imageSource = notif.notification.image || "";
|
||||||
notif.image = notif.imageSource;
|
notif.image = notif.imageSource;
|
||||||
@@ -237,6 +241,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
property real expireTimeout: 5
|
property real expireTimeout: 5
|
||||||
property bool hasActionIcons
|
property bool hasActionIcons
|
||||||
|
property var hints
|
||||||
property string image
|
property string image
|
||||||
property string imageSource
|
property string imageSource
|
||||||
property var locks: new Set()
|
property var locks: new Set()
|
||||||
@@ -333,6 +338,7 @@ Singleton {
|
|||||||
appName = notification.appName;
|
appName = notification.appName;
|
||||||
imageSource = notification.image || "";
|
imageSource = notification.image || "";
|
||||||
image = imageSource;
|
image = imageSource;
|
||||||
|
hints = notification.hints;
|
||||||
expireTimeout = notification.expireTimeout;
|
expireTimeout = notification.expireTimeout;
|
||||||
urgency = notification.urgency;
|
urgency = notification.urgency;
|
||||||
resident = notification.resident;
|
resident = notification.resident;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Config
|
import qs.Config
|
||||||
import qs.Daemons
|
import qs.Daemons
|
||||||
@@ -149,11 +151,12 @@ Item {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
implicitHeight: notif.implicitHeight
|
implicitHeight: notif.implicitHeight
|
||||||
implicitWidth: notif.implicitWidth
|
implicitWidth: notif.implicitWidth
|
||||||
radius: Appearance.rounding.smallest / 2
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
Notification {
|
Notification {
|
||||||
id: notif
|
id: notif
|
||||||
|
|
||||||
|
implicitWidth: root.implicitWidth - root.padding * 2
|
||||||
modelData: wrapper.modelData
|
modelData: wrapper.modelData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,33 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import qs.Components
|
import QtQuick
|
||||||
import qs.Config
|
import QtQuick.Shapes
|
||||||
import qs.Modules
|
import Quickshell
|
||||||
|
import Quickshell.Services.Notifications
|
||||||
|
import ZShell.Components
|
||||||
import qs.Daemons
|
import qs.Daemons
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import Quickshell
|
import qs.Config
|
||||||
import Quickshell.Widgets
|
import qs.Components
|
||||||
import Quickshell.Services.Notifications
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property int bodyTextFormat: /[<*_`#\[\]]/.test(modelData.body) ? Text.MarkdownText : Text.PlainText
|
||||||
property bool expanded: Config.notifs.openExpanded
|
property bool expanded: Config.notifs.openExpanded
|
||||||
readonly property bool hasAppIcon: modelData.appIcon.length > 0
|
readonly property bool hasAppIcon: modelData.appIcon.length > 0
|
||||||
readonly property bool hasImage: modelData.image.length > 0
|
readonly property bool hasImage: modelData.image.length > 0
|
||||||
required property NotifServer.Notif modelData
|
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
|
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
|
||||||
implicitHeight: inner.implicitHeight
|
implicitHeight: inner.implicitHeight
|
||||||
implicitWidth: Config.notifs.sizes.width
|
radius: Appearance.rounding.small
|
||||||
radius: 6
|
x: implicitWidth
|
||||||
x: Config.notifs.sizes.width
|
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
Anim {
|
Anim {
|
||||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +53,7 @@ CustomRect {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const actions = root.modelData.actions;
|
const actions = root.modelData.actions;
|
||||||
if (actions?.length === 1)
|
if (actions.length === 1)
|
||||||
actions[0].invoke();
|
actions[0].invoke();
|
||||||
}
|
}
|
||||||
onEntered: root.modelData.timer.stop()
|
onEntered: root.modelData.timer.stop()
|
||||||
@@ -80,7 +78,7 @@ CustomRect {
|
|||||||
if (!containsMouse)
|
if (!containsMouse)
|
||||||
root.modelData.timer.start();
|
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;
|
root.x = 0;
|
||||||
else
|
else
|
||||||
root.modelData.popup = false;
|
root.modelData.popup = false;
|
||||||
@@ -90,7 +88,7 @@ CustomRect {
|
|||||||
id: inner
|
id: inner
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.margins: 8
|
anchors.margins: Appearance.padding.normal
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
implicitHeight: root.nonAnimHeight
|
implicitHeight: root.nonAnimHeight
|
||||||
@@ -111,7 +109,8 @@ CustomRect {
|
|||||||
visible: root.hasImage || root.hasAppIcon
|
visible: root.hasImage || root.hasAppIcon
|
||||||
width: Config.notifs.sizes.image
|
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
|
implicitHeight: Config.notifs.sizes.image
|
||||||
implicitWidth: Config.notifs.sizes.image
|
implicitWidth: Config.notifs.sizes.image
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
@@ -121,8 +120,11 @@ CustomRect {
|
|||||||
asynchronous: true
|
asynchronous: true
|
||||||
cache: false
|
cache: false
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
mipmap: true
|
|
||||||
source: Qt.resolvedUrl(root.modelData.image)
|
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)
|
height: Math.round(parent.width * 0.6)
|
||||||
width: Math.round(parent.width * 0.6)
|
width: Math.round(parent.width * 0.6)
|
||||||
|
|
||||||
sourceComponent: CustomIcon {
|
sourceComponent: ColoredIcon {
|
||||||
anchors.fill: parent
|
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")
|
layer.enabled: root.modelData.appIcon.endsWith("symbolic")
|
||||||
source: Quickshell.iconPath(root.modelData.appIcon)
|
source: Quickshell.iconPath(root.modelData.appIcon)
|
||||||
}
|
}
|
||||||
@@ -162,34 +165,67 @@ CustomRect {
|
|||||||
Loader {
|
Loader {
|
||||||
active: !root.hasAppIcon
|
active: !root.hasAppIcon
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.horizontalCenterOffset: -18 * 0.02
|
anchors.verticalCenterOffset: 1
|
||||||
anchors.verticalCenterOffset: 18 * 0.02
|
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
sourceComponent: MaterialIcon {
|
sourceComponent: MaterialIcon {
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
|
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)
|
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 {
|
CustomText {
|
||||||
id: appName
|
id: appName
|
||||||
|
|
||||||
anchors.left: image.right
|
anchors.left: image.right
|
||||||
anchors.leftMargin: 10
|
anchors.leftMargin: Appearance.spacing.small
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
animate: true
|
animate: true
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
font.pointSize: 10
|
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
opacity: root.expanded ? 1 : 0
|
opacity: root.expanded ? 1 : 0
|
||||||
text: appNameMetrics.elidedText
|
text: appNameMetrics.elidedText
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,9 +234,8 @@ CustomRect {
|
|||||||
id: appNameMetrics
|
id: appNameMetrics
|
||||||
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3
|
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - Appearance.spacing.small * 3
|
||||||
font.family: appName.font.family
|
font: appName.font
|
||||||
font.pointSize: appName.font.pointSize
|
|
||||||
text: root.modelData.appName
|
text: root.modelData.appName
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +243,7 @@ CustomRect {
|
|||||||
id: summary
|
id: summary
|
||||||
|
|
||||||
anchors.left: image.right
|
anchors.left: image.right
|
||||||
anchors.leftMargin: 10
|
anchors.leftMargin: Appearance.spacing.small
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
animate: true
|
animate: true
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
@@ -224,6 +259,9 @@ CustomRect {
|
|||||||
when: root.expanded
|
when: root.expanded
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
|
body.anchors.topMargin: Appearance.spacing.extraSmall
|
||||||
|
bodyPreview.anchors.topMargin: Appearance.spacing.extraSmall
|
||||||
|
summary.anchors.topMargin: Appearance.spacing.extraSmall
|
||||||
summary.maximumLineCount: undefined
|
summary.maximumLineCount: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,10 +276,11 @@ CustomRect {
|
|||||||
target: summary
|
target: summary
|
||||||
}
|
}
|
||||||
|
|
||||||
AnchorAnimation {
|
Anim {
|
||||||
duration: MaterialEasing.expressiveEffectsTime
|
property: "topMargin"
|
||||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
}
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
|
AnchorAnim {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,9 +289,8 @@ CustomRect {
|
|||||||
id: summaryMetrics
|
id: summaryMetrics
|
||||||
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3
|
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - Appearance.spacing.small * 3
|
||||||
font.family: summary.font.family
|
font: summary.font
|
||||||
font.pointSize: summary.font.pointSize
|
|
||||||
text: root.modelData.summary
|
text: root.modelData.summary
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,10 +298,9 @@ CustomRect {
|
|||||||
id: timeSep
|
id: timeSep
|
||||||
|
|
||||||
anchors.left: summary.right
|
anchors.left: summary.right
|
||||||
anchors.leftMargin: 7
|
anchors.leftMargin: Appearance.spacing.small
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
font.pointSize: 10
|
|
||||||
text: "•"
|
text: "•"
|
||||||
|
|
||||||
states: State {
|
states: State {
|
||||||
@@ -276,10 +313,7 @@ CustomRect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
transitions: Transition {
|
transitions: Transition {
|
||||||
AnchorAnimation {
|
AnchorAnim {
|
||||||
duration: MaterialEasing.expressiveEffectsTime
|
|
||||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
|
||||||
easing.type: Easing.BezierSpline
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,11 +322,10 @@ CustomRect {
|
|||||||
id: time
|
id: time
|
||||||
|
|
||||||
anchors.left: timeSep.right
|
anchors.left: timeSep.right
|
||||||
anchors.leftMargin: 7
|
anchors.leftMargin: Appearance.spacing.small
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
animate: true
|
animate: true
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
font.pointSize: 10
|
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
text: root.modelData.timeStr
|
text: root.modelData.timeStr
|
||||||
}
|
}
|
||||||
@@ -302,25 +335,33 @@ CustomRect {
|
|||||||
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
implicitHeight: expandIcon.height
|
anchors.topMargin: -Appearance.padding.extraSmall
|
||||||
implicitWidth: expandIcon.height
|
implicitHeight: expandIcon.implicitHeight
|
||||||
|
implicitWidth: expandIcon.implicitHeight
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
|
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
|
||||||
radius: Appearance.rounding.full
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
onClicked: {
|
onClicked: root.expanded = !root.expanded
|
||||||
root.expanded = !root.expanded;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
id: expandIcon
|
id: expandIcon
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
animate: true
|
anchors.verticalCenterOffset: root.expanded ? -1 : 1
|
||||||
font.pointSize: 13
|
rotation: root.expanded ? 180 : 0
|
||||||
text: root.expanded ? "expand_less" : "expand_more"
|
text: "expand_more"
|
||||||
|
|
||||||
|
Behavior on anchors.verticalCenterOffset {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on rotation {
|
||||||
|
Anim {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,17 +370,17 @@ CustomRect {
|
|||||||
|
|
||||||
anchors.left: summary.left
|
anchors.left: summary.left
|
||||||
anchors.right: expandBtn.left
|
anchors.right: expandBtn.left
|
||||||
anchors.rightMargin: 7
|
anchors.rightMargin: Appearance.spacing.small
|
||||||
anchors.top: summary.bottom
|
anchors.top: summary.bottom
|
||||||
animate: true
|
animate: true
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
font.pointSize: 10
|
|
||||||
opacity: root.expanded ? 0 : 1
|
opacity: root.expanded ? 0 : 1
|
||||||
text: bodyPreviewMetrics.elidedText
|
text: bodyPreviewMetrics.elidedText
|
||||||
textFormat: Text.MarkdownText
|
textFormat: root.bodyTextFormat
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,8 +390,7 @@ CustomRect {
|
|||||||
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
elideWidth: bodyPreview.width
|
elideWidth: bodyPreview.width
|
||||||
font.family: bodyPreview.font.family
|
font: bodyPreview.font
|
||||||
font.pointSize: bodyPreview.font.pointSize
|
|
||||||
text: root.modelData.body
|
text: root.modelData.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,19 +399,19 @@ CustomRect {
|
|||||||
|
|
||||||
anchors.left: summary.left
|
anchors.left: summary.left
|
||||||
anchors.right: expandBtn.left
|
anchors.right: expandBtn.left
|
||||||
anchors.rightMargin: 7
|
anchors.rightMargin: Appearance.spacing.small
|
||||||
anchors.top: summary.bottom
|
anchors.top: summary.bottom
|
||||||
animate: true
|
animate: true
|
||||||
color: DynamicColors.palette.m3onSurfaceVariant
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
font.pointSize: 10
|
|
||||||
height: text ? implicitHeight : 0
|
height: text ? implicitHeight : 0
|
||||||
opacity: root.expanded ? 1 : 0
|
opacity: root.expanded ? 1 : 0
|
||||||
text: root.modelData.body
|
text: root.modelData.body
|
||||||
textFormat: Text.MarkdownText
|
textFormat: root.bodyTextFormat
|
||||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,84 +424,80 @@ CustomRect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
ButtonRow {
|
||||||
id: actions
|
id: actions
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.left: body.left
|
||||||
|
anchors.right: body.right
|
||||||
anchors.top: body.bottom
|
anchors.top: body.bottom
|
||||||
anchors.topMargin: 7
|
anchors.topMargin: Appearance.spacing.small
|
||||||
opacity: root.expanded ? 1 : 0
|
opacity: root.expanded ? 1 : 0
|
||||||
spacing: 10
|
spacing: Appearance.spacing.extraSmall
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Action {
|
IconButton {
|
||||||
modelData: QtObject {
|
fillWidth: root.modelData.actions.length === 0
|
||||||
readonly property string text: qsTr("Close")
|
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 {
|
onClicked: root.modelData.close()
|
||||||
root.modelData.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.modelData.actions
|
model: root.modelData.actions
|
||||||
|
|
||||||
delegate: Component {
|
TextButton {
|
||||||
Action {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ qml_module(ZShell-components
|
|||||||
SOURCES
|
SOURCES
|
||||||
lazylistview.hpp lazylistview.cpp
|
lazylistview.hpp lazylistview.cpp
|
||||||
wavyline.hpp wavyline.cpp
|
wavyline.hpp wavyline.cpp
|
||||||
|
buttonrow.hpp buttonrow.cpp
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
#include "buttonrow.hpp"
|
||||||
|
|
||||||
|
namespace ZShell::components {
|
||||||
|
|
||||||
|
ButtonRow::ButtonRow(QQuickItem* parent)
|
||||||
|
: QQuickItem(parent)
|
||||||
|
, m_dirty(false)
|
||||||
|
, m_spacing(0.0) {
|
||||||
|
setFlag(QQuickItem::ItemHasContents, true);
|
||||||
|
QObject::connect(this, &ButtonRow::widthChanged, this, &ButtonRow::invalidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal ButtonRow::spacing() const {
|
||||||
|
return m_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonRow::setSpacing(qreal spacing) {
|
||||||
|
if (qFuzzyCompare(m_spacing + 1.0, spacing + 1.0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_spacing = spacing;
|
||||||
|
emit spacingChanged();
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonRow::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) {
|
||||||
|
if (change == QQuickItem::ItemChildAddedChange) {
|
||||||
|
auto* const child = data.item;
|
||||||
|
QObject::connect(child, &QQuickItem::implicitWidthChanged, this, &ButtonRow::invalidate);
|
||||||
|
QObject::connect(child, &QQuickItem::implicitHeightChanged, this, &ButtonRow::invalidate);
|
||||||
|
QObject::connect(child, &QQuickItem::visibleChanged, this, &ButtonRow::invalidate);
|
||||||
|
|
||||||
|
const auto* childMeta = child->metaObject();
|
||||||
|
const auto morphSignalIdx = childMeta->indexOfSignal("shapeMorphExpansionChanged()");
|
||||||
|
if (morphSignalIdx != -1)
|
||||||
|
QObject::connect(child, childMeta->method(morphSignalIdx), this,
|
||||||
|
metaObject()->method(metaObject()->indexOfSlot("invalidate()")));
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
} else if (change == QQuickItem::ItemChildRemovedChange) {
|
||||||
|
QObject::disconnect(data.item, nullptr, this, nullptr);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickItem::itemChange(change, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonRow::updatePolish() {
|
||||||
|
if (m_dirty) {
|
||||||
|
relayout();
|
||||||
|
m_dirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonRow::invalidate() {
|
||||||
|
m_dirty = true;
|
||||||
|
polish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonRow::relayout() {
|
||||||
|
const auto allChildren = childItems();
|
||||||
|
|
||||||
|
QList<QQuickItem*> validChildren;
|
||||||
|
for (auto* const child : allChildren) {
|
||||||
|
if (child->isVisible() && !child->inherits("QQuickRepeater"))
|
||||||
|
validChildren.append(child);
|
||||||
|
}
|
||||||
|
const auto nChildren = validChildren.size();
|
||||||
|
const auto totalSpacing = static_cast<qreal>(nChildren - 1) * m_spacing;
|
||||||
|
|
||||||
|
qreal reservedWidth = 0;
|
||||||
|
qreal unreservedWidth = 0;
|
||||||
|
int fillWidthCount = 0;
|
||||||
|
qreal maxHeight = 0;
|
||||||
|
for (auto* const child : validChildren) {
|
||||||
|
maxHeight = qMax(maxHeight, child->implicitHeight());
|
||||||
|
|
||||||
|
const auto prop = child->property("fillWidth");
|
||||||
|
if (!prop.isValid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (prop.toBool()) {
|
||||||
|
fillWidthCount++;
|
||||||
|
unreservedWidth += child->implicitWidth();
|
||||||
|
} else {
|
||||||
|
reservedWidth += child->implicitWidth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillWidthCount == 0)
|
||||||
|
fillWidthCount = 1; // Avoid divide by 0
|
||||||
|
|
||||||
|
const auto widthPerItem = (width() - totalSpacing - reservedWidth) / static_cast<qreal>(fillWidthCount);
|
||||||
|
|
||||||
|
QList<qreal> baseWidths;
|
||||||
|
baseWidths.reserve(nChildren);
|
||||||
|
for (auto* const child : validChildren)
|
||||||
|
baseWidths.append(child->property("fillWidth").toBool() ? widthPerItem : child->implicitWidth());
|
||||||
|
|
||||||
|
qreal accX = 0;
|
||||||
|
for (int i = 0; i < nChildren; ++i) {
|
||||||
|
auto* const child = validChildren[i];
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
auto prevExtraWidth = i > 0 ? getMorphExpansion(validChildren[i - 1]) : 0.0;
|
||||||
|
auto nextExtraWidth = i < nChildren - 1 ? getMorphExpansion(validChildren[i + 1]) : 0.0;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Items at edges push by full amount, items in middle push by half
|
||||||
|
if (i > 1)
|
||||||
|
prevExtraWidth /= 2;
|
||||||
|
if (i < nChildren - 2)
|
||||||
|
nextExtraWidth /= 2;
|
||||||
|
|
||||||
|
child->setWidth(baseWidths[i] + getMorphExpansion(child) - prevExtraWidth - nextExtraWidth);
|
||||||
|
child->setHeight(maxHeight);
|
||||||
|
|
||||||
|
child->setX(accX);
|
||||||
|
child->setY(0);
|
||||||
|
accX += child->width() + m_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImplicitWidth(reservedWidth + unreservedWidth + totalSpacing);
|
||||||
|
setImplicitHeight(maxHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal ButtonRow::getMorphExpansion(const QQuickItem* item) {
|
||||||
|
return item->property("shapeMorphExpansion").toReal();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZShell::components
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qquickitem.h>
|
||||||
|
|
||||||
|
namespace ZShell::components {
|
||||||
|
|
||||||
|
class ButtonRow : public QQuickItem {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ButtonRow(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal spacing() const;
|
||||||
|
void setSpacing(qreal spacing);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void spacingChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) override;
|
||||||
|
void updatePolish() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void invalidate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void relayout();
|
||||||
|
static qreal getMorphExpansion(const QQuickItem* item);
|
||||||
|
|
||||||
|
bool m_dirty;
|
||||||
|
qreal m_spacing;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZShell::components
|
||||||
Reference in New Issue
Block a user