diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-09-21 17:34:55 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-21 17:34:55 +1000 |
| commit | 37b61cb0d923bc8ab988a0ba4174cd55de458bdc (patch) | |
| tree | ee4808009110c6e85540b4b6d0f0b947b3f80d25 /modules/utilities/toasts | |
| parent | sidebar/notifs: add link support (diff) | |
| parent | feat: add battery warnings (diff) | |
| download | caelestia-shell-37b61cb0d923bc8ab988a0ba4174cd55de458bdc.tar.gz caelestia-shell-37b61cb0d923bc8ab988a0ba4174cd55de458bdc.tar.bz2 caelestia-shell-37b61cb0d923bc8ab988a0ba4174cd55de458bdc.zip | |
Merge pull request #662 from caelestia-dots/toasts
utilities: add toasts & battery warnings
Diffstat (limited to 'modules/utilities/toasts')
| -rw-r--r-- | modules/utilities/toasts/ToastItem.qml | 135 | ||||
| -rw-r--r-- | modules/utilities/toasts/Toasts.qml | 142 |
2 files changed, 277 insertions, 0 deletions
diff --git a/modules/utilities/toasts/ToastItem.qml b/modules/utilities/toasts/ToastItem.qml new file mode 100644 index 0000000..481b831 --- /dev/null +++ b/modules/utilities/toasts/ToastItem.qml @@ -0,0 +1,135 @@ +import qs.components +import qs.components.effects +import qs.services +import qs.config +import Caelestia +import QtQuick +import QtQuick.Layouts + +StyledRect { + id: root + + required property Toast modelData + + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: layout.implicitHeight + Appearance.padding.smaller * 2 + + radius: Appearance.rounding.normal + color: { + if (root.modelData.type === Toast.Success) + return Colours.palette.m3successContainer; + if (root.modelData.type === Toast.Warning) + return Colours.palette.m3secondary; + if (root.modelData.type === Toast.Error) + return Colours.palette.m3errorContainer; + return Colours.palette.m3surface; + } + + border.width: 1 + border.color: { + let colour = Colours.palette.m3outlineVariant; + if (root.modelData.type === Toast.Success) + colour = Colours.palette.m3success; + if (root.modelData.type === Toast.Warning) + colour = Colours.palette.m3secondaryContainer; + if (root.modelData.type === Toast.Error) + colour = Colours.palette.m3error; + return Qt.alpha(colour, 0.3); + } + + Elevation { + anchors.fill: parent + radius: parent.radius + opacity: parent.opacity + z: -1 + level: 3 + } + + RowLayout { + id: layout + + anchors.fill: parent + anchors.margins: Appearance.padding.smaller + anchors.leftMargin: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.normal + spacing: Appearance.spacing.normal + + StyledRect { + radius: Appearance.rounding.normal + color: { + if (root.modelData.type === Toast.Success) + return Colours.palette.m3success; + if (root.modelData.type === Toast.Warning) + return Colours.palette.m3secondaryContainer; + if (root.modelData.type === Toast.Error) + return Colours.palette.m3error; + return Colours.palette.m3surfaceContainerHigh; + } + + implicitWidth: implicitHeight + implicitHeight: icon.implicitHeight + Appearance.padding.normal * 2 + + MaterialIcon { + id: icon + + anchors.centerIn: parent + text: root.modelData.icon + color: { + if (root.modelData.type === Toast.Success) + return Colours.palette.m3onSuccess; + if (root.modelData.type === Toast.Warning) + return Colours.palette.m3onSecondaryContainer; + if (root.modelData.type === Toast.Error) + return Colours.palette.m3onError; + return Colours.palette.m3onSurfaceVariant; + } + font.pointSize: Appearance.font.size.large + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + StyledText { + id: title + + Layout.fillWidth: true + text: root.modelData.title + color: { + if (root.modelData.type === Toast.Success) + return Colours.palette.m3onSuccessContainer; + if (root.modelData.type === Toast.Warning) + return Colours.palette.m3onSecondary; + if (root.modelData.type === Toast.Error) + return Colours.palette.m3onErrorContainer; + return Colours.palette.m3onSurface; + } + font.pointSize: Appearance.font.size.normal + elide: Text.ElideRight + } + + StyledText { + Layout.fillWidth: true + textFormat: Text.StyledText + text: root.modelData.message + color: { + if (root.modelData.type === Toast.Success) + return Colours.palette.m3onSuccessContainer; + if (root.modelData.type === Toast.Warning) + return Colours.palette.m3onSecondary; + if (root.modelData.type === Toast.Error) + return Colours.palette.m3onErrorContainer; + return Colours.palette.m3onSurface; + } + opacity: 0.8 + elide: Text.ElideRight + } + } + } + + Behavior on border.color { + CAnim {} + } +} diff --git a/modules/utilities/toasts/Toasts.qml b/modules/utilities/toasts/Toasts.qml new file mode 100644 index 0000000..c9a8d4d --- /dev/null +++ b/modules/utilities/toasts/Toasts.qml @@ -0,0 +1,142 @@ +pragma ComponentBehavior: Bound + +import qs.components +import qs.config +import Caelestia +import Quickshell +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + readonly property int spacing: Appearance.spacing.small + property bool flag + + implicitWidth: Config.utilities.sizes.width - Appearance.padding.normal * 2 + implicitHeight: { + let h = -spacing; + for (let i = 0; i < repeater.count; i++) { + const item = repeater.itemAt(i); + if (!item.modelData.closed && !item.previewHidden) + h += item.implicitHeight + spacing; + } + return h; + } + + Repeater { + id: repeater + + model: ScriptModel { + values: { + const toasts = []; + let count = 0; + for (const toast of Toaster.toasts) { + toasts.push(toast); + if (!toast.closed) { + count++; + if (count > Config.utilities.maxToasts) + break; + } + } + return toasts; + } + onValuesChanged: root.flagChanged() + } + + MouseArea { + id: toast + + required property int index + required property Toast modelData + + readonly property bool previewHidden: { + let extraHidden = 0; + for (let i = 0; i < index; i++) + if (Toaster.toasts[i].closed) + extraHidden++; + return index >= Config.utilities.maxToasts + extraHidden; + } + + onPreviewHiddenChanged: { + if (initAnim.running && previewHidden) + initAnim.stop(); + } + + opacity: modelData.closed || previewHidden ? 0 : 1 + scale: modelData.closed || previewHidden ? 0.7 : 1 + + anchors.bottomMargin: { + root.flag; // Force update + let y = 0; + for (let i = 0; i < index; i++) { + const item = repeater.itemAt(i); + if (item && !item.modelData.closed && !item.previewHidden) + y += item.implicitHeight + root.spacing; + } + return y; + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + implicitHeight: toastInner.implicitHeight + + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton + onClicked: modelData.close() + + Component.onCompleted: modelData.lock(this) + + Anim { + id: initAnim + + Component.onCompleted: running = !toast.previewHidden + + target: toast + properties: "opacity,scale" + from: 0 + to: 1 + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + + ParallelAnimation { + running: toast.modelData.closed + onStarted: toast.anchors.bottomMargin = toast.anchors.bottomMargin + onFinished: toast.modelData.unlock(toast) + + Anim { + target: toast + property: "opacity" + to: 0 + } + Anim { + target: toast + property: "scale" + to: 0.7 + } + } + + ToastItem { + id: toastInner + + modelData: toast.modelData + } + + Behavior on opacity { + Anim {} + } + + Behavior on scale { + Anim {} + } + + Behavior on anchors.bottomMargin { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + } + } +} |