1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-13 01:02:18 -04:00

Further M3 enhancements

This commit is contained in:
purian23
2026-02-14 22:48:46 -05:00
committed by bbedward
parent 949c216964
commit 0dba11d845
5 changed files with 263 additions and 123 deletions

View File

@@ -14,6 +14,8 @@ DankListView {
property real stableContentHeight: 0 property real stableContentHeight: 0
property bool cardAnimateExpansion: true property bool cardAnimateExpansion: true
property bool listInitialized: false property bool listInitialized: false
property int swipingCardIndex: -1
property real swipingCardOffset: 0
property real __pendingStableHeight: 0 property real __pendingStableHeight: 0
property real __heightUpdateThreshold: 20 property real __heightUpdateThreshold: 20
@@ -132,6 +134,11 @@ DankListView {
readonly property real dismissThreshold: width * 0.35 readonly property real dismissThreshold: width * 0.35
property bool __delegateInitialized: false property bool __delegateInitialized: false
readonly property bool isAdjacentToSwipe: listView.count >= 2 && listView.swipingCardIndex !== -1 &&
(index === listView.swipingCardIndex - 1 || index === listView.swipingCardIndex + 1)
readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? listView.swipingCardOffset * 0.10 : 0
readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(listView.swipingCardOffset) / width * 0.02 : 1.0
Component.onCompleted: { Component.onCompleted: {
Qt.callLater(() => { Qt.callLater(() => {
if (delegateRoot) if (delegateRoot)
@@ -140,13 +147,15 @@ DankListView {
} }
width: ListView.view.width width: ListView.view.width
height: isDismissing ? 0 : notificationCard.height height: notificationCard.height
clip: isDismissing || notificationCard.isAnimating clip: notificationCard.isAnimating
NotificationCard { NotificationCard {
id: notificationCard id: notificationCard
width: parent.width width: parent.width
x: delegateRoot.swipeOffset x: delegateRoot.swipeOffset + delegateRoot.adjacentSwipeInfluence
listLevelAdjacentScaleInfluence: delegateRoot.adjacentScaleInfluence
listLevelScaleAnimationsEnabled: listView.swipingCardIndex === -1 || !delegateRoot.isAdjacentToSwipe
notificationGroup: modelData notificationGroup: modelData
keyboardNavigationActive: listView.keyboardActive keyboardNavigationActive: listView.keyboardActive
animateExpansion: listView.cardAnimateExpansion && listView.listInitialized animateExpansion: listView.cardAnimateExpansion && listView.listInitialized
@@ -188,7 +197,7 @@ DankListView {
} }
Behavior on x { Behavior on x {
enabled: !swipeDragHandler.active && listView.listInitialized enabled: !swipeDragHandler.active && !delegateRoot.isDismissing && (listView.swipingCardIndex === -1 || !delegateRoot.isAdjacentToSwipe) && listView.listInitialized
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
@@ -210,12 +219,18 @@ DankListView {
xAxis.enabled: true xAxis.enabled: true
onActiveChanged: { onActiveChanged: {
if (active || delegateRoot.isDismissing) if (active) {
listView.swipingCardIndex = index;
return;
}
listView.swipingCardIndex = -1;
listView.swipingCardOffset = 0;
if (delegateRoot.isDismissing)
return; return;
if (Math.abs(delegateRoot.swipeOffset) > delegateRoot.dismissThreshold) { if (Math.abs(delegateRoot.swipeOffset) > delegateRoot.dismissThreshold) {
delegateRoot.isDismissing = true; delegateRoot.isDismissing = true;
delegateRoot.swipeOffset = delegateRoot.swipeOffset > 0 ? delegateRoot.width : -delegateRoot.width; swipeDismissAnim.to = delegateRoot.swipeOffset > 0 ? delegateRoot.width : -delegateRoot.width;
dismissTimer.start(); swipeDismissAnim.start();
} else { } else {
delegateRoot.swipeOffset = 0; delegateRoot.swipeOffset = 0;
} }
@@ -225,13 +240,18 @@ DankListView {
if (delegateRoot.isDismissing) if (delegateRoot.isDismissing)
return; return;
delegateRoot.swipeOffset = translation.x; delegateRoot.swipeOffset = translation.x;
listView.swipingCardOffset = translation.x;
} }
} }
Timer { NumberAnimation {
id: dismissTimer id: swipeDismissAnim
interval: Theme.shortDuration target: delegateRoot
onTriggered: NotificationService.dismissGroup(delegateRoot.modelData?.key || "") property: "swipeOffset"
to: 0
duration: Theme.shortDuration
easing.type: Easing.OutCubic
onStopped: NotificationService.dismissGroup(delegateRoot.modelData?.key || "")
} }
} }

View File

@@ -19,6 +19,10 @@ Rectangle {
property bool isGroupSelected: false property bool isGroupSelected: false
property int selectedNotificationIndex: -1 property int selectedNotificationIndex: -1
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
property int swipingNotificationIndex: -1
property real swipingNotificationOffset: 0
property real listLevelAdjacentScaleInfluence: 1.0
property bool listLevelScaleAnimationsEnabled: true
readonly property bool compactMode: SettingsData.notificationCompactMode readonly property bool compactMode: SettingsData.notificationCompactMode
readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding
@@ -26,13 +30,14 @@ Rectangle {
readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
readonly property real badgeSize: compactMode ? 16 : 18 readonly property real badgeSize: compactMode ? 16 : 18
readonly property real actionButtonHeight: compactMode ? 20 : 24 readonly property real actionButtonHeight: compactMode ? 20 : 24
readonly property real collapsedContentHeight: Math.max(iconSize, Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3)) readonly property real collapsedContentHeight: Math.max(iconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2))
readonly property real baseCardHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing readonly property real baseCardHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing
width: parent ? parent.width : 400 width: parent ? parent.width : 400
height: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight) height: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight) readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
radius: Theme.cornerRadius radius: Theme.cornerRadius
scale: (cardHoverHandler.hovered ? 1.01 : 1.0) * listLevelAdjacentScaleInfluence
property bool __initialized: false property bool __initialized: false
Component.onCompleted: { Component.onCompleted: {
@@ -42,6 +47,14 @@ Rectangle {
}); });
} }
Behavior on scale {
enabled: listLevelScaleAnimationsEnabled
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on border.color { Behavior on border.color {
enabled: root.__initialized enabled: root.__initialized
ColorAnimation { ColorAnimation {
@@ -115,8 +128,8 @@ Rectangle {
id: collapsedContent id: collapsedContent
readonly property real expandedTextHeight: descriptionText.contentHeight readonly property real expandedTextHeight: descriptionText.contentHeight
readonly property real collapsedLineCount: compactMode ? 1 : 3 readonly property real collapsedLineCount: compactMode ? 1 : 2
readonly property real collapsedLineHeight: descriptionText.font.pixelSize * 1.2 * collapsedLineCount readonly property real collapsedLineHeight: Theme.fontSizeSmall * 1.2 * collapsedLineCount
readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > collapsedLineHeight + 2) ? (expandedTextHeight - collapsedLineHeight) : 0 readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > collapsedLineHeight + 2) ? (expandedTextHeight - collapsedLineHeight) : 0
anchors.top: parent.top anchors.top: parent.top
@@ -146,7 +159,7 @@ Rectangle {
height: iconSize height: iconSize
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: descriptionExpanded ? Math.max(0, (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3)) / 2 - iconSize / 2) : Math.max(0, textContainer.height / 2 - iconSize / 2) anchors.topMargin: descriptionExpanded ? Math.max(0, Theme.fontSizeSmall * 1.2 + (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)) / 2 - iconSize / 2) : Math.max(0, Theme.fontSizeSmall * 1.2 + (textContainer.height - Theme.fontSizeSmall * 1.2) / 2 - iconSize / 2)
imageSource: { imageSource: {
if (hasNotificationImage) if (hasNotificationImage)
@@ -223,47 +236,51 @@ Rectangle {
spacing: Theme.notificationContentSpacing spacing: Theme.notificationContentSpacing
Row { Row {
width: parent.width - ((notificationGroup?.count || 0) > 1 ? 10 : 0) id: collapsedHeaderRow
width: parent.width
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: (collapsedTitleText.text.length > 0 || collapsedTimeText.text.length > 0) visible: (collapsedHeaderAppNameText.text.length > 0 || collapsedHeaderTimeText.text.length > 0)
readonly property real reservedTrailingWidth: collapsedSeparator.implicitWidth + Math.max(collapsedTimeText.implicitWidth, 72) + spacing
StyledText { StyledText {
id: collapsedTitleText id: collapsedHeaderAppNameText
width: Math.min(implicitWidth, Math.max(0, parent.width - parent.reservedTrailingWidth)) text: notificationGroup?.appName || ""
text: { color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
let title = notificationGroup?.latestNotification?.summary || ""; font.pixelSize: Theme.fontSizeSmall
const appName = notificationGroup?.appName || ""; font.weight: Font.Normal
const prefix = appName + " • ";
if (appName && title.toLowerCase().startsWith(prefix.toLowerCase())) {
title = title.substring(prefix.length);
}
return title;
}
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
visible: text.length > 0 width: Math.min(implicitWidth, parent.width - collapsedHeaderSeparator.implicitWidth - collapsedHeaderTimeText.implicitWidth - parent.spacing * 2)
} }
StyledText { StyledText {
id: collapsedSeparator id: collapsedHeaderSeparator
text: (collapsedTitleText.text.length > 0 && collapsedTimeText.text.length > 0) ? " • " : "" text: (collapsedHeaderAppNameText.text.length > 0 && collapsedHeaderTimeText.text.length > 0) ? " • " : ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal font.weight: Font.Normal
} }
StyledText { StyledText {
id: collapsedTimeText id: collapsedHeaderTimeText
text: notificationGroup?.latestNotification?.timeStr || "" text: notificationGroup?.latestNotification?.timeStr || ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal font.weight: Font.Normal
visible: text.length > 0
} }
} }
StyledText {
id: collapsedTitleText
width: parent.width
text: notificationGroup?.latestNotification?.summary || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
StyledText { StyledText {
id: descriptionText id: descriptionText
property string fullText: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.htmlBody) || "" property string fullText: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.htmlBody) || ""
@@ -274,7 +291,7 @@ Rectangle {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
width: parent.width width: parent.width
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 3) maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 2)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
visible: text.length > 0 visible: text.length > 0
linkColor: Theme.primary linkColor: Theme.primary
@@ -380,7 +397,7 @@ Rectangle {
id: expandedDelegateHoverHandler id: expandedDelegateHoverHandler
} }
readonly property real expandedItemPadding: compactMode ? Theme.spacingS : Theme.spacingM readonly property real expandedItemPadding: compactMode ? Theme.spacingS : Theme.spacingM
readonly property real expandedBaseHeight: expandedItemPadding * 2 + expandedIconSize + actionButtonHeight + contentSpacing * 2 readonly property real expandedBaseHeight: expandedItemPadding * 2 + Math.max(expandedIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * 2) + actionButtonHeight + contentSpacing * 2
property bool __delegateInitialized: false property bool __delegateInitialized: false
property real swipeOffset: 0 property real swipeOffset: 0
property bool isDismissing: false property bool isDismissing: false
@@ -399,16 +416,34 @@ Rectangle {
Rectangle { Rectangle {
id: delegateRect id: delegateRect
x: parent.swipeOffset
width: parent.width width: parent.width
readonly property bool isAdjacentToSwipe: root.swipingNotificationIndex !== -1 &&
(expandedDelegateWrapper.index === root.swipingNotificationIndex - 1 ||
expandedDelegateWrapper.index === root.swipingNotificationIndex + 1)
readonly property real adjacentSwipeInfluence: isAdjacentToSwipe ? root.swipingNotificationOffset * 0.10 : 0
readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(root.swipingNotificationOffset) / width * 0.02 : 1.0
x: expandedDelegateWrapper.swipeOffset + adjacentSwipeInfluence
scale: adjacentScaleInfluence
transformOrigin: Item.Center
Behavior on x { Behavior on x {
enabled: !expandedSwipeHandler.active && !parent.parent.isDismissing enabled: !expandedSwipeHandler.active && !expandedDelegateWrapper.isDismissing
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on scale {
enabled: !expandedSwipeHandler.active
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
height: { height: {
if (!messageExpanded) if (!messageExpanded)
return expandedBaseHeight; return expandedBaseHeight;
@@ -458,7 +493,7 @@ Rectangle {
height: expandedIconSize height: expandedIconSize
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: compactMode ? Theme.spacingM : Theme.spacingXL anchors.topMargin: Theme.fontSizeSmall * 1.2 + (compactMode ? Theme.spacingXS : Theme.spacingS)
imageSource: { imageSource: {
if (hasNotificationImage) if (hasNotificationImage)
@@ -504,46 +539,51 @@ Rectangle {
spacing: Theme.notificationContentSpacing spacing: Theme.notificationContentSpacing
Row { Row {
id: expandedDelegateHeaderRow
width: parent.width width: parent.width
spacing: Theme.spacingXS spacing: Theme.spacingXS
readonly property real reservedTrailingWidth: expandedDelegateSeparator.implicitWidth + Math.max(expandedDelegateTimeText.implicitWidth, 72) + spacing visible: (expandedDelegateHeaderAppNameText.text.length > 0 || expandedDelegateHeaderTimeText.text.length > 0)
StyledText { StyledText {
id: expandedDelegateTitleText id: expandedDelegateHeaderAppNameText
width: Math.min(implicitWidth, Math.max(0, parent.width - parent.reservedTrailingWidth)) text: modelData?.appName || ""
text: { color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
let title = modelData?.summary || ""; font.pixelSize: Theme.fontSizeSmall
const appName = modelData?.appName || ""; font.weight: Font.Normal
const prefix = appName + " • ";
if (appName && title.toLowerCase().startsWith(prefix.toLowerCase())) {
title = title.substring(prefix.length);
}
return title;
}
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
visible: text.length > 0 width: Math.min(implicitWidth, parent.width - expandedDelegateHeaderSeparator.implicitWidth - expandedDelegateHeaderTimeText.implicitWidth - parent.spacing * 2)
} }
StyledText { StyledText {
id: expandedDelegateSeparator id: expandedDelegateHeaderSeparator
text: (expandedDelegateTitleText.text.length > 0 && expandedDelegateTimeText.text.length > 0) ? " • " : "" text: (expandedDelegateHeaderAppNameText.text.length > 0 && expandedDelegateHeaderTimeText.text.length > 0) ? " • " : ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal font.weight: Font.Normal
} }
StyledText { StyledText {
id: expandedDelegateTimeText id: expandedDelegateHeaderTimeText
text: modelData?.timeStr || "" text: modelData?.timeStr || ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal font.weight: Font.Normal
visible: text.length > 0
} }
} }
StyledText {
id: expandedDelegateTitleText
width: parent.width
text: modelData?.summary || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
StyledText { StyledText {
id: bodyText id: bodyText
property bool hasMoreText: truncated property bool hasMoreText: truncated
@@ -685,42 +725,49 @@ Rectangle {
} }
} }
} }
}
}
DragHandler { DragHandler {
id: expandedSwipeHandler id: expandedSwipeHandler
target: null target: null
xAxis.enabled: true xAxis.enabled: true
yAxis.enabled: false yAxis.enabled: false
grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType
onActiveChanged: { onActiveChanged: {
if (active || parent.isDismissing) if (active) {
return; root.swipingNotificationIndex = expandedDelegateWrapper.index;
if (Math.abs(parent.swipeOffset) > parent.dismissThreshold) { } else {
parent.isDismissing = true; root.swipingNotificationIndex = -1;
expandedSwipeDismissAnim.start(); root.swipingNotificationOffset = 0;
} else { }
parent.swipeOffset = 0; if (active || expandedDelegateWrapper.isDismissing)
return;
if (Math.abs(expandedDelegateWrapper.swipeOffset) > expandedDelegateWrapper.dismissThreshold) {
expandedDelegateWrapper.isDismissing = true;
expandedSwipeDismissAnim.start();
} else {
expandedDelegateWrapper.swipeOffset = 0;
}
}
onTranslationChanged: {
if (expandedDelegateWrapper.isDismissing)
return;
expandedDelegateWrapper.swipeOffset = translation.x;
root.swipingNotificationOffset = translation.x;
}
}
NumberAnimation {
id: expandedSwipeDismissAnim
target: expandedDelegateWrapper
property: "swipeOffset"
to: expandedDelegateWrapper.swipeOffset > 0 ? expandedDelegateWrapper.width : -expandedDelegateWrapper.width
duration: Theme.shortDuration
easing.type: Easing.OutCubic
onStopped: NotificationService.dismissNotification(modelData)
} }
} }
onTranslationChanged: {
if (parent.isDismissing)
return;
parent.swipeOffset = translation.x;
}
}
NumberAnimation {
id: expandedSwipeDismissAnim
target: parent
property: "swipeOffset"
to: parent.swipeOffset > 0 ? parent.width : -parent.width
duration: Theme.shortDuration
easing.type: Easing.OutCubic
onStopped: NotificationService.dismissNotification(modelData)
} }
} }
} }
@@ -855,7 +902,7 @@ Rectangle {
} }
Behavior on height { Behavior on height {
enabled: root.userInitiatedExpansion && root.animateExpansion enabled: root.__initialized && root.userInitiatedExpansion && root.animateExpansion
NumberAnimation { NumberAnimation {
duration: root.expanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration duration: root.expanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline

View File

@@ -81,6 +81,8 @@ DankPopout {
} else { } else {
NotificationService.onOverlayClose(); NotificationService.onOverlayClose();
keyboardController.keyboardNavigationActive = false; keyboardController.keyboardNavigationActive = false;
NotificationService.expandedGroups = {};
NotificationService.expandedMessages = {};
} }
} }

View File

@@ -22,18 +22,25 @@ PanelWindow {
property bool _finalized: false property bool _finalized: false
readonly property string clearText: I18n.tr("Dismiss") readonly property string clearText: I18n.tr("Dismiss")
property bool descriptionExpanded: false property bool descriptionExpanded: false
onDescriptionExpandedChanged: {
popupHeightChanged();
}
onImplicitHeightChanged: {
popupHeightChanged();
}
readonly property bool compactMode: SettingsData.notificationCompactMode readonly property bool compactMode: SettingsData.notificationCompactMode
readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding
readonly property real popupIconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal readonly property real popupIconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal
readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
readonly property real actionButtonHeight: compactMode ? 20 : 24 readonly property real actionButtonHeight: compactMode ? 20 : 24
readonly property real collapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3)) readonly property real collapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2))
readonly property real basePopupHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing readonly property real basePopupHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing
signal entered signal entered
signal exitStarted signal exitStarted
signal exitFinished signal exitFinished
signal popupHeightChanged()
function startExit() { function startExit() {
if (exiting || _isDestroying) { if (exiting || _isDestroying) {
@@ -104,7 +111,7 @@ PanelWindow {
if (!descriptionExpanded) if (!descriptionExpanded)
return basePopupHeight; return basePopupHeight;
const bodyTextHeight = bodyText.contentHeight || 0; const bodyTextHeight = bodyText.contentHeight || 0;
const collapsedBodyHeight = Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3); const collapsedBodyHeight = Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2);
if (bodyTextHeight > collapsedBodyHeight + 2) if (bodyTextHeight > collapsedBodyHeight + 2)
return basePopupHeight + bodyTextHeight - collapsedBodyHeight; return basePopupHeight + bodyTextHeight - collapsedBodyHeight;
return basePopupHeight; return basePopupHeight;
@@ -232,18 +239,41 @@ PanelWindow {
width: alignedWidth width: alignedWidth
height: alignedHeight height: alignedHeight
visible: !win._finalized visible: !win._finalized
scale: cardHoverHandler.hovered ? 1.01 : 1.0
transformOrigin: Item.Center
property real swipeOffset: 0 property real swipeOffset: 0
readonly property real dismissThreshold: isCenterPosition ? height * 0.4 : width * 0.35 readonly property real dismissThreshold: isCenterPosition ? height * 0.4 : width * 0.35
readonly property bool swipeActive: swipeDragHandler.active readonly property bool swipeActive: swipeDragHandler.active
property bool swipeDismissing: false property bool swipeDismissing: false
property real shadowBlurPx: 10 property real shadowBlurPx: cardHoverHandler.hovered ? 16 : 10
property real shadowSpreadPx: 0 property real shadowSpreadPx: cardHoverHandler.hovered ? 2 : 0
property real shadowBaseAlpha: 0.60 property real shadowBaseAlpha: 0.60
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha)) readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
Behavior on shadowBlurPx {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on shadowSpreadPx {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Item { Item {
id: bgShadowLayer id: bgShadowLayer
anchors.fill: parent anchors.fill: parent
@@ -323,7 +353,7 @@ PanelWindow {
id: notificationContent id: notificationContent
readonly property real expandedTextHeight: bodyText.contentHeight || 0 readonly property real expandedTextHeight: bodyText.contentHeight || 0
readonly property real collapsedBodyHeight: Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3) readonly property real collapsedBodyHeight: Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)
readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > collapsedBodyHeight + 2) ? (expandedTextHeight - collapsedBodyHeight) : 0 readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > collapsedBodyHeight + 2) ? (expandedTextHeight - collapsedBodyHeight) : 0
anchors.top: parent.top anchors.top: parent.top
@@ -354,7 +384,7 @@ PanelWindow {
height: popupIconSize height: popupIconSize
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: descriptionExpanded ? Math.max(0, (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 3)) / 2 - popupIconSize / 2) : Math.max(0, textContainer.height / 2 - popupIconSize / 2) anchors.topMargin: descriptionExpanded ? Math.max(0, Theme.fontSizeSmall * 1.2 + (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)) / 2 - popupIconSize / 2) : Math.max(0, Theme.fontSizeSmall * 1.2 + (textContainer.height - Theme.fontSizeSmall * 1.2) / 2 - popupIconSize / 2)
imageSource: { imageSource: {
if (!notificationData) if (!notificationData)
@@ -410,6 +440,40 @@ PanelWindow {
anchors.top: parent.top anchors.top: parent.top
spacing: Theme.notificationContentSpacing spacing: Theme.notificationContentSpacing
Row {
id: headerRow
width: parent.width
spacing: Theme.spacingXS
visible: headerAppNameText.text.length > 0 || headerTimeText.text.length > 0
StyledText {
id: headerAppNameText
text: notificationData ? (notificationData.appName || "") : ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
elide: Text.ElideRight
maximumLineCount: 1
width: Math.min(implicitWidth, parent.width - headerSeparator.implicitWidth - headerTimeText.implicitWidth - parent.spacing * 2)
}
StyledText {
id: headerSeparator
text: (headerAppNameText.text.length > 0 && headerTimeText.text.length > 0) ? " • " : ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
}
StyledText {
id: headerTimeText
text: notificationData ? (notificationData.timeStr || "") : ""
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
}
}
StyledText { StyledText {
text: notificationData ? (notificationData.summary || "") : "" text: notificationData ? (notificationData.summary || "") : ""
color: Theme.surfaceText color: Theme.surfaceText
@@ -432,7 +496,7 @@ PanelWindow {
width: parent.width width: parent.width
elide: descriptionExpanded ? Text.ElideNone : Text.ElideRight elide: descriptionExpanded ? Text.ElideNone : Text.ElideRight
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 3) maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 2)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
visible: text.length > 0 visible: text.length > 0
linkColor: Theme.primary linkColor: Theme.primary
@@ -607,7 +671,9 @@ PanelWindow {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
popupContextMenu.popup(); popupContextMenu.popup();
} else if (mouse.button === Qt.LeftButton) { } else if (mouse.button === Qt.LeftButton) {
if (notificationData.actions && notificationData.actions.length > 0) { if (bodyText.hasMoreText || win.descriptionExpanded) {
win.descriptionExpanded = !win.descriptionExpanded;
} else if (notificationData.actions && notificationData.actions.length > 0) {
notificationData.actions[0].invoke(); notificationData.actions[0].invoke();
NotificationService.dismissNotification(notificationData); NotificationService.dismissNotification(notificationData);
} else { } else {

View File

@@ -13,7 +13,8 @@ QtObject {
readonly property real actionButtonHeight: compactMode ? 20 : 24 readonly property real actionButtonHeight: compactMode ? 20 : 24
readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
readonly property real popupSpacing: compactMode ? 0 : Theme.spacingXS readonly property real popupSpacing: compactMode ? 0 : Theme.spacingXS
readonly property int baseNotificationHeight: cardPadding * 2 + popupIconSize + actionButtonHeight + contentSpacing + popupSpacing readonly property real collapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2))
readonly property int baseNotificationHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing + popupSpacing
property int maxTargetNotifications: 4 property int maxTargetNotifications: 4
property var popupWindows: [] // strong refs to windows (live until exitFinished) property var popupWindows: [] // strong refs to windows (live until exitFinished)
property var destroyingWindows: new Set() property var destroyingWindows: new Set()
@@ -29,6 +30,7 @@ QtObject {
onEntered: manager._onPopupEntered(this) onEntered: manager._onPopupEntered(this)
onExitStarted: manager._onPopupExitStarted(this) onExitStarted: manager._onPopupExitStarted(this)
onExitFinished: manager._onPopupExitFinished(this) onExitFinished: manager._onPopupExitFinished(this)
onPopupHeightChanged: manager._onPopupHeightChanged(this)
} }
} }
@@ -127,10 +129,7 @@ QtObject {
} }
if (toRemove.length) { if (toRemove.length) {
popupWindows = popupWindows.filter(p => toRemove.indexOf(p) === -1); popupWindows = popupWindows.filter(p => toRemove.indexOf(p) === -1);
const survivors = _active().sort((a, b) => a.screenY - b.screenY); _repositionAllActivePopups();
for (let k = 0; k < survivors.length; ++k) {
survivors[k].screenY = topMargin + k * baseNotificationHeight;
}
} }
if (popupWindows.length === 0) { if (popupWindows.length === 0) {
sweeper.stop(); sweeper.stop();
@@ -237,13 +236,6 @@ QtObject {
function _doInsertNewestAtTop(wrapper) { function _doInsertNewestAtTop(wrapper) {
if (!wrapper) if (!wrapper)
return; return;
for (const p of popupWindows) {
if (!_isValidWindow(p))
continue;
if (p.exiting)
continue;
p.screenY = p.screenY + baseNotificationHeight;
}
const notificationId = wrapper && wrapper.notification ? wrapper.notification.id : ""; const notificationId = wrapper && wrapper.notification ? wrapper.notification.id : "";
const win = popupComponent.createObject(null, { const win = popupComponent.createObject(null, {
"notificationData": wrapper, "notificationData": wrapper,
@@ -257,7 +249,10 @@ QtObject {
win.destroy(); win.destroy();
return; return;
} }
popupWindows.push(win); popupWindows.unshift(win);
_repositionAllActivePopups();
createBusy = true; createBusy = true;
createTimer.restart(); createTimer.restart();
if (!sweeper.running) if (!sweeper.running)
@@ -283,12 +278,25 @@ QtObject {
function _onPopupEntered(p) { function _onPopupEntered(p) {
} }
function _onPopupHeightChanged(p) {
if (!p || p.exiting || p._isDestroying)
return;
_repositionAllActivePopups();
}
function _repositionAllActivePopups() {
const activeWindows = _active().sort((a, b) => a.screenY - b.screenY);
let currentY = topMargin;
for (const win of activeWindows) {
win.screenY = currentY;
currentY += win.implicitHeight + popupSpacing;
}
}
function _onPopupExitStarted(p) { function _onPopupExitStarted(p) {
if (!p) if (!p)
return; return;
const survivors = _active().sort((a, b) => a.screenY - b.screenY); _repositionAllActivePopups();
for (let k = 0; k < survivors.length; ++k)
survivors[k].screenY = topMargin + k * baseNotificationHeight;
} }
function _onPopupExitFinished(p) { function _onPopupExitFinished(p) {
@@ -310,10 +318,7 @@ QtObject {
} }
_scheduleDestroy(p); _scheduleDestroy(p);
Qt.callLater(() => destroyingWindows.delete(windowId)); Qt.callLater(() => destroyingWindows.delete(windowId));
const survivors = _active().sort((a, b) => a.screenY - b.screenY); _repositionAllActivePopups();
for (let k = 0; k < survivors.length; ++k) {
survivors[k].screenY = topMargin + k * baseNotificationHeight;
}
} }
function cleanupAllWindows() { function cleanupAllWindows() {