fancy notifs
This commit is contained in:
@@ -1,392 +1,396 @@
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property NotificationTheme theme
|
||||
property int notificationId: -1
|
||||
property string notifTitle: ""
|
||||
property string notifBody: ""
|
||||
property string notifUrgency: "Normal"
|
||||
property string notifAppName: ""
|
||||
property string notifAppIcon: ""
|
||||
property string notifImage: ""
|
||||
property int notifTimeoutMs: 5000
|
||||
property bool notifCritical: false
|
||||
property var notifHints: ({})
|
||||
property var notifObject: null
|
||||
property int notifVersion: 0
|
||||
property real createdAtMs: Date.now()
|
||||
|
||||
property bool interactive: true
|
||||
property bool forceExpanded: false
|
||||
property bool peekMode: false
|
||||
|
||||
signal closeFinished(int notificationId, string reason)
|
||||
|
||||
property bool highlighted: hoverHandler.hovered
|
||||
property bool closing: false
|
||||
property string closeReason: ""
|
||||
property real borderAlpha: 1
|
||||
property real textAlpha: 1
|
||||
property bool contextOpen: false
|
||||
property real collapseFactor: 1
|
||||
|
||||
readonly property color normalInk: "#ffffff"
|
||||
readonly property color highlightInk: "#ffc90e"
|
||||
readonly property color activeInk: highlighted ? highlightInk : normalInk
|
||||
readonly property int padding: 14
|
||||
readonly property int iconBox: 28
|
||||
readonly property int appIconSize: 24
|
||||
readonly property string appIconSource: {
|
||||
if (notifImage && notifImage.length > 0)
|
||||
return notifImage;
|
||||
readonly property string notifTitle: notifObject ? String(notifObject.summary || "") : ""
|
||||
readonly property string notifBody: notifObject ? String(notifObject.body || "") : ""
|
||||
readonly property string notifAppName: notifObject ? String(notifObject.appName || "") : ""
|
||||
readonly property string notifAppIcon: notifObject ? String(notifObject.appIcon || "") : ""
|
||||
readonly property string notifImage: notifObject ? String(notifObject.image || "") : ""
|
||||
readonly property bool isExpanded: forceExpanded || contextOpen
|
||||
|
||||
readonly property string appIconSource: {
|
||||
if (!notifAppIcon || notifAppIcon.length === 0)
|
||||
return "";
|
||||
|
||||
if (notifAppIcon.indexOf("/") >= 0 || notifAppIcon.indexOf("file://") === 0)
|
||||
return notifAppIcon;
|
||||
|
||||
return Quickshell.iconPath(notifAppIcon);
|
||||
}
|
||||
|
||||
function hintString(name) {
|
||||
if (!notifHints || typeof notifHints !== "object")
|
||||
return "";
|
||||
const value = notifHints[name];
|
||||
return value === undefined || value === null ? "" : String(value);
|
||||
readonly property string customImageSource: notifImage
|
||||
readonly property string primaryIconSource: customImageSource.length > 0 ? customImageSource : appIconSource
|
||||
readonly property bool showBadgeIcon: customImageSource.length > 0 && appIconSource.length > 0
|
||||
readonly property int iconBoxSize: (theme ? theme.iconBox : 28) + 12
|
||||
|
||||
readonly property var notifActions: {
|
||||
if (!notifObject || !notifObject.actions)
|
||||
return [];
|
||||
|
||||
const normalized = [];
|
||||
for (let i = 0; i < notifObject.actions.length; i++) {
|
||||
const actionObject = notifObject.actions[i];
|
||||
if (!actionObject)
|
||||
continue;
|
||||
const actionText = String(actionObject.text || "").trim();
|
||||
if (actionText.length === 0)
|
||||
continue;
|
||||
normalized.push({
|
||||
actionObject: actionObject,
|
||||
actionText: actionText
|
||||
});
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function textBlob() {
|
||||
return (notifAppName + " " + notifTitle + " " + notifBody + " " + hintString("category")).toLowerCase();
|
||||
readonly property int actionCount: notifActions.length
|
||||
readonly property var contextItems: {
|
||||
const items = [];
|
||||
for (let i = 0; i < notifActions.length; i++)
|
||||
items.push(notifActions[i]);
|
||||
items.push({
|
||||
actionObject: null,
|
||||
actionText: "Dismiss"
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
function parseVolumePercent() {
|
||||
const source = (notifTitle + " " + notifBody).toUpperCase();
|
||||
const match = source.match(/VOLUME\s*(\d{1,3})%/);
|
||||
if (!match || match.length < 2)
|
||||
return -1;
|
||||
function urgencyTimeoutMs() {
|
||||
if (!notifObject)
|
||||
return theme.fallbackNormalTimeoutMs;
|
||||
|
||||
const parsed = Number(match[1]);
|
||||
if (!Number.isFinite(parsed))
|
||||
return -1;
|
||||
const rawSeconds = Number(notifObject.expireTimeout);
|
||||
if (Number.isFinite(rawSeconds) && rawSeconds > 0)
|
||||
return Math.max(1000, Math.round(rawSeconds * 1000));
|
||||
|
||||
return Math.max(0, Math.min(100, parsed));
|
||||
const urgency = notifObject.urgency;
|
||||
if (urgency === NotificationUrgency.Critical)
|
||||
return theme.fallbackCriticalTimeoutMs;
|
||||
if (urgency === NotificationUrgency.Low)
|
||||
return theme.fallbackLowTimeoutMs;
|
||||
return theme.fallbackNormalTimeoutMs;
|
||||
}
|
||||
|
||||
readonly property int volumePercent: parseVolumePercent()
|
||||
readonly property bool isVolumeLayout: volumePercent >= 0
|
||||
|
||||
function soulColor() {
|
||||
const full = textBlob();
|
||||
const urgencyText = String(notifUrgency || "").toLowerCase();
|
||||
|
||||
if (urgencyText === "critical")
|
||||
return "#fff27a";
|
||||
|
||||
if (full.indexOf("network") >= 0 || full.indexOf("wifi") >= 0 || full.indexOf("ethernet") >= 0 || full.indexOf("bluetooth") >= 0)
|
||||
return "#4ca4ff";
|
||||
|
||||
if (full.indexOf("success") >= 0 || full.indexOf("completed") >= 0 || full.indexOf("saved") >= 0 || full.indexOf("done") >= 0)
|
||||
return "#47d66b";
|
||||
|
||||
return "#ff2a2a";
|
||||
function relativeTime() {
|
||||
const elapsedSeconds = Math.max(0, Math.floor((Date.now() - createdAtMs) / 1000));
|
||||
if (elapsedSeconds < 60)
|
||||
return "now";
|
||||
if (elapsedSeconds < 3600)
|
||||
return Math.floor(elapsedSeconds / 60) + "m";
|
||||
return Math.floor(elapsedSeconds / 3600) + "h";
|
||||
}
|
||||
|
||||
readonly property color soulInk: soulColor()
|
||||
|
||||
function beginClose(reason) {
|
||||
if (closing)
|
||||
return;
|
||||
|
||||
closing = true;
|
||||
closeReason = reason;
|
||||
timeoutTimer.stop();
|
||||
|
||||
if (reason === "click") {
|
||||
heartFlash.restart();
|
||||
clickClose.start();
|
||||
} else {
|
||||
timeoutClose.start();
|
||||
}
|
||||
closeReason = reason;
|
||||
closeAnimation.start();
|
||||
}
|
||||
|
||||
implicitWidth: 420
|
||||
implicitHeight: Math.max(1, contentColumn.implicitHeight * collapseFactor + padding * 2)
|
||||
function openContextPanel() {
|
||||
if (!interactive || closing || peekMode)
|
||||
return;
|
||||
contextOpen = !contextOpen;
|
||||
restartTimeout();
|
||||
}
|
||||
|
||||
function invokeAction(actionObject) {
|
||||
if (closing)
|
||||
return;
|
||||
if (actionObject)
|
||||
actionObject.invoke();
|
||||
beginClose(actionObject ? "click" : "dismiss");
|
||||
}
|
||||
|
||||
function restartTimeout() {
|
||||
if (closing || isExpanded || peekMode)
|
||||
return;
|
||||
timeoutTimer.interval = urgencyTimeoutMs();
|
||||
timeoutTimer.restart();
|
||||
}
|
||||
|
||||
property string closeReason: ""
|
||||
implicitWidth: theme ? theme.stackWidth : 420
|
||||
implicitHeight: peekMode ? (theme ? theme.stackPeekHeight : 36) : Math.max(1, Math.round((contentColumn.implicitHeight + ((theme ? theme.cardPadding : 14) * 2)) * collapseFactor))
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
transformOrigin: Item.TopRight
|
||||
opacity: 1
|
||||
|
||||
SequentialAnimation {
|
||||
id: entryAnimation
|
||||
running: true
|
||||
|
||||
PropertyAction {
|
||||
target: root
|
||||
property: "opacity"
|
||||
value: 0
|
||||
}
|
||||
PropertyAction {
|
||||
target: root
|
||||
property: "scale"
|
||||
value: 0.95
|
||||
}
|
||||
id: enterAnimation
|
||||
running: !peekMode
|
||||
|
||||
PropertyAction { target: root; property: "opacity"; value: 0 }
|
||||
PropertyAction { target: root; property: "x"; value: 20 }
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 0.45
|
||||
duration: 55
|
||||
}
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "scale"
|
||||
to: 0.97
|
||||
duration: 55
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 0.75
|
||||
duration: 55
|
||||
}
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "scale"
|
||||
to: 0.99
|
||||
duration: 55
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 1
|
||||
duration: 70
|
||||
}
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "scale"
|
||||
to: 1
|
||||
duration: 70
|
||||
}
|
||||
NumberAnimation { target: root; property: "opacity"; to: 1; duration: theme ? theme.enterMs : 170; easing.type: Easing.OutCubic }
|
||||
NumberAnimation { target: root; property: "x"; to: 0; duration: theme ? theme.enterMs : 170; easing.type: Easing.OutCubic }
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: heartFlash
|
||||
NumberAnimation {
|
||||
target: soulFlashOverlay
|
||||
property: "opacity"
|
||||
to: 1
|
||||
duration: 35
|
||||
}
|
||||
NumberAnimation {
|
||||
target: soulFlashOverlay
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 75
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: clickClose
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "y"
|
||||
to: -8
|
||||
duration: 160
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 160
|
||||
}
|
||||
}
|
||||
ScriptAction {
|
||||
script: root.closeFinished(root.notificationId, "click")
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: timeoutClose
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "borderAlpha"
|
||||
to: 0.35
|
||||
duration: 90
|
||||
}
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "textAlpha"
|
||||
to: 0
|
||||
duration: 110
|
||||
}
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "collapseFactor"
|
||||
to: 0
|
||||
duration: 150
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
ScriptAction {
|
||||
script: root.closeFinished(root.notificationId, "timeout")
|
||||
}
|
||||
ParallelAnimation {
|
||||
id: closeAnimation
|
||||
NumberAnimation { target: root; property: "opacity"; to: 0; duration: theme ? theme.fadeMs : 120; easing.type: Easing.InCubic }
|
||||
NumberAnimation { target: root; property: "x"; to: width; duration: theme ? theme.exitMs : 140; easing.type: Easing.InCubic }
|
||||
onFinished: root.closeFinished(root.notificationId, root.closeReason)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timeoutTimer
|
||||
interval: root.notifTimeoutMs
|
||||
interval: root.urgencyTimeoutMs()
|
||||
repeat: false
|
||||
running: !root.notifCritical && root.notifTimeoutMs > 0
|
||||
running: !root.peekMode
|
||||
onTriggered: root.beginClose("timeout")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#000000"
|
||||
border.width: 3
|
||||
border.color: Qt.rgba(root.activeInk.r, root.activeInk.g, root.activeInk.b, root.borderAlpha)
|
||||
color: theme ? theme.panelBackground : "#000000"
|
||||
border.width: theme ? theme.borderWidth : 3
|
||||
border.color: theme ? theme.panelBorder : "#ffffff"
|
||||
radius: 0
|
||||
antialiasing: false
|
||||
visible: !peekMode
|
||||
}
|
||||
|
||||
Item {
|
||||
id: soulContainer
|
||||
x: {
|
||||
if (!root.isVolumeLayout)
|
||||
return root.padding;
|
||||
|
||||
const left = root.padding;
|
||||
const right = Math.max(left, root.width - root.padding - root.iconBox);
|
||||
return left + (right - left) * (root.volumePercent / 100);
|
||||
}
|
||||
y: root.padding + 2
|
||||
width: root.iconBox
|
||||
height: root.iconBox
|
||||
scale: 1
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: root.appIconSource.length > 0
|
||||
anchors.centerIn: parent
|
||||
implicitSize: root.appIconSize
|
||||
source: root.appIconSource
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: root.appIconSource.length === 0
|
||||
anchors.fill: parent
|
||||
|
||||
Image {
|
||||
id: soulImage
|
||||
anchors.fill: parent
|
||||
source: "../Topbar/topbar/soul_small.png"
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: soulImage
|
||||
source: soulImage
|
||||
color: root.soulInk
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: soulFlashOverlay
|
||||
anchors.fill: parent
|
||||
color: "#ffffff"
|
||||
opacity: 0
|
||||
radius: 0
|
||||
antialiasing: false
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: theme ? theme.panelBackground : "#000000"
|
||||
border.width: theme ? theme.borderWidth : 3
|
||||
border.color: theme ? theme.panelBorder : "#ffffff"
|
||||
radius: 0
|
||||
antialiasing: false
|
||||
visible: peekMode
|
||||
opacity: 0.95
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
x: root.padding + root.iconBox + 10
|
||||
y: root.padding - 1
|
||||
width: root.width - x - root.padding
|
||||
spacing: 2
|
||||
visible: !peekMode
|
||||
x: (theme ? theme.cardPadding : 14)
|
||||
y: (theme ? theme.cardPadding : 14)
|
||||
width: root.width - ((theme ? theme.cardPadding : 14) * 2)
|
||||
spacing: theme ? theme.lineGap : 2
|
||||
|
||||
Text {
|
||||
visible: !root.isVolumeLayout
|
||||
text: String(root.notifTitle || "")
|
||||
color: Qt.rgba(root.activeInk.r, root.activeInk.g, root.activeInk.b, root.textAlpha)
|
||||
font.family: "8bitoperator JVE"
|
||||
font.pixelSize: 28
|
||||
font.letterSpacing: 1
|
||||
wrapMode: Text.NoWrap
|
||||
elide: Text.ElideRight
|
||||
renderType: Text.NativeRendering
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
Row {
|
||||
id: mainRow
|
||||
width: parent.width
|
||||
spacing: theme ? theme.contentGap : 10
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
Item {
|
||||
width: root.iconBoxSize
|
||||
height: root.iconBoxSize
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: root.primaryIconSource
|
||||
visible: root.primaryIconSource.length > 0
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: root.primaryIconSource.length === 0
|
||||
color: "#000000"
|
||||
border.width: 1
|
||||
border.color: theme ? theme.panelBorder : "#ffffff"
|
||||
radius: 0
|
||||
antialiasing: false
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 2
|
||||
height: parent.height - 2
|
||||
source: "../Topbar/topbar/soul_small.png"
|
||||
visible: root.primaryIconSource.length === 0
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: root.showBadgeIcon
|
||||
width: Math.max(12, Math.round(parent.width * 0.42))
|
||||
height: Math.max(12, Math.round(parent.height * 0.42))
|
||||
source: root.appIconSource
|
||||
x: parent.width - width
|
||||
y: parent.height - height
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: textColumn
|
||||
width: parent.width - root.iconBoxSize - (theme ? theme.contentGap : 10)
|
||||
spacing: theme ? theme.lineGap : 2
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
Text {
|
||||
width: parent.width - timeLabel.implicitWidth - 6
|
||||
text: root.notifTitle
|
||||
color: theme ? theme.panelText : "#ffffff"
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
font.family: theme ? theme.fontFamily : "8bitoperator JVE"
|
||||
font.pixelSize: 24
|
||||
font.letterSpacing: theme ? theme.fontLetterSpacing : 1
|
||||
renderType: Text.NativeRendering
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
}
|
||||
|
||||
Text {
|
||||
id: timeLabel
|
||||
text: root.relativeTime()
|
||||
color: theme ? theme.panelText : "#ffffff"
|
||||
font.family: theme ? theme.fontFamily : "8bitoperator JVE"
|
||||
font.pixelSize: 16
|
||||
font.letterSpacing: theme ? theme.fontLetterSpacing : 1
|
||||
renderType: Text.NativeRendering
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.notifBody
|
||||
color: theme ? theme.panelText : "#ffffff"
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: root.isExpanded ? (theme ? theme.expandedBodyLines : 8) : (theme ? theme.collapsedBodyLines : 2)
|
||||
elide: Text.ElideRight
|
||||
font.family: theme ? theme.fontFamily : "8bitoperator JVE"
|
||||
font.pixelSize: 22
|
||||
font.letterSpacing: theme ? theme.fontLetterSpacing : 1
|
||||
renderType: Text.NativeRendering
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !root.isVolumeLayout
|
||||
text: String(root.notifBody || "")
|
||||
color: Qt.rgba(root.activeInk.r, root.activeInk.g, root.activeInk.b, root.textAlpha)
|
||||
font.family: "8bitoperator JVE"
|
||||
font.pixelSize: 24
|
||||
font.letterSpacing: 1
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 4
|
||||
elide: Text.ElideRight
|
||||
textFormat: Text.PlainText
|
||||
renderType: Text.NativeRendering
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
Column {
|
||||
visible: root.contextOpen
|
||||
width: parent.width
|
||||
}
|
||||
spacing: theme ? theme.actionGap : 8
|
||||
|
||||
Text {
|
||||
visible: root.isVolumeLayout
|
||||
text: "VOLUME " + root.volumePercent + "%"
|
||||
color: Qt.rgba(root.activeInk.r, root.activeInk.g, root.activeInk.b, root.textAlpha)
|
||||
font.family: "8bitoperator JVE"
|
||||
font.pixelSize: 30
|
||||
font.letterSpacing: 1
|
||||
wrapMode: Text.NoWrap
|
||||
elide: Text.ElideRight
|
||||
renderType: Text.NativeRendering
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
width: parent.width
|
||||
}
|
||||
Repeater {
|
||||
model: root.contextItems
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: theme ? theme.actionHeight : 34
|
||||
color: actionHover.hovered ? (theme ? theme.hoverInvertBackground : "#ffffff") : (theme ? theme.panelBackground : "#000000")
|
||||
border.width: theme ? theme.actionBorderWidth : 2
|
||||
border.color: theme ? theme.panelBorder : "#ffffff"
|
||||
radius: 0
|
||||
antialiasing: false
|
||||
|
||||
HoverHandler { id: actionHover }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.actionText
|
||||
color: actionHover.hovered ? (theme ? theme.hoverInvertText : "#000000") : (theme ? theme.panelText : "#ffffff")
|
||||
font.family: theme ? theme.fontFamily : "8bitoperator JVE"
|
||||
font.pixelSize: 18
|
||||
font.letterSpacing: theme ? theme.fontLetterSpacing : 1
|
||||
renderType: Text.NativeRendering
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
enabled: root.interactive && !root.closing
|
||||
onTapped: root.invokeAction(modelData.actionObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
DragHandler {
|
||||
id: swipeHandler
|
||||
enabled: root.interactive && !root.peekMode && !root.closing
|
||||
xAxis.enabled: true
|
||||
yAxis.enabled: false
|
||||
|
||||
onTranslationChanged: {
|
||||
root.x = Math.max(0, translation.x);
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active)
|
||||
return;
|
||||
|
||||
const threshold = theme ? theme.dragDismissThreshold : 140;
|
||||
if (root.x >= threshold) {
|
||||
root.beginClose("drag");
|
||||
return;
|
||||
}
|
||||
snapBackAnimation.restart();
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: snapBackAnimation
|
||||
target: root
|
||||
property: "x"
|
||||
to: 0
|
||||
duration: theme ? theme.expandMs : 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
enabled: root.highlighted && !root.closing
|
||||
onTapped: root.beginClose("click")
|
||||
enabled: root.interactive && !root.peekMode && !root.closing
|
||||
onTapped: root.openContextPanel()
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
enabled: root.interactive && !root.peekMode && !root.closing
|
||||
onTapped: root.beginClose("dismiss")
|
||||
}
|
||||
|
||||
onNotifObjectChanged: restartTimeout()
|
||||
onNotifVersionChanged: restartTimeout()
|
||||
onContextOpenChanged: {
|
||||
if (!contextOpen)
|
||||
restartTimeout();
|
||||
else
|
||||
timeoutTimer.stop();
|
||||
}
|
||||
Component.onCompleted: restartTimeout()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user