1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-17 11:12:06 -04:00

(Notifications): Update body card expansions

This commit is contained in:
purian23
2026-04-16 16:00:33 -04:00
parent 62049ee242
commit 745570bc8b
5 changed files with 191 additions and 31 deletions

View File

@@ -236,12 +236,35 @@ Singleton {
}; };
} }
function _sameNotificationGeometry(a, b) {
if (!a || !b)
return false;
return Math.abs(Number(a.bodyX) - Number(b.bodyX)) < 0.5
&& Math.abs(Number(a.bodyY) - Number(b.bodyY)) < 0.5
&& Math.abs(Number(a.bodyW) - Number(b.bodyW)) < 0.5
&& Math.abs(Number(a.bodyH) - Number(b.bodyH)) < 0.5;
}
function _sameNotificationState(a, b) {
if (!a || !b)
return false;
return a.visible === b.visible
&& a.barSide === b.barSide
&& a.omitStartConnector === b.omitStartConnector
&& a.omitEndConnector === b.omitEndConnector
&& _sameNotificationGeometry(a, b);
}
function setNotificationState(screenName, state) { function setNotificationState(screenName, state) {
if (!screenName || !state) if (!screenName || !state)
return false; return false;
const normalized = _normalizeNotificationState(state);
if (_sameNotificationState(notificationStates[screenName], normalized))
return true;
const next = _cloneNotificationStates(); const next = _cloneNotificationStates();
next[screenName] = _normalizeNotificationState(state); next[screenName] = normalized;
notificationStates = next; notificationStates = next;
return true; return true;
} }

View File

@@ -1069,6 +1069,9 @@ Singleton {
return base === 0 ? 0 : Math.round(base * 0.85); return base === 0 ? 0 : Math.round(base * 0.85);
} }
readonly property int notificationInlineExpandDuration: notificationAnimationBaseDuration === 0 ? 0 : 185
readonly property int notificationInlineCollapseDuration: notificationAnimationBaseDuration === 0 ? 0 : 150
readonly property real notificationIconSizeNormal: 56 readonly property real notificationIconSizeNormal: 56
readonly property real notificationIconSizeCompact: 48 readonly property real notificationIconSizeCompact: 48
readonly property real notificationExpandedIconSizeNormal: 48 readonly property real notificationExpandedIconSizeNormal: 48

View File

@@ -16,6 +16,7 @@ Rectangle {
property bool userInitiatedExpansion: false property bool userInitiatedExpansion: false
property bool isAnimating: false property bool isAnimating: false
property bool animateExpansion: true property bool animateExpansion: true
property bool isDescriptionToggleAnimation: false
property bool _retainedExpandedContent: false property bool _retainedExpandedContent: false
property bool _clipAnimatedContent: false property bool _clipAnimatedContent: false
property real expandedContentOpacity: expanded ? 1 : 0 property real expandedContentOpacity: expanded ? 1 : 0
@@ -64,6 +65,8 @@ Rectangle {
} }
function expansionMotionDuration() { function expansionMotionDuration() {
if (isDescriptionToggleAnimation)
return descriptionExpanded ? Theme.notificationInlineExpandDuration : Theme.notificationInlineCollapseDuration;
return root.connectedFrameMode ? Theme.variantDuration(Theme.popoutAnimationDuration, root.expanded) : (root.expanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration); return root.connectedFrameMode ? Theme.variantDuration(Theme.popoutAnimationDuration, root.expanded) : (root.expanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration);
} }
@@ -414,6 +417,7 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) { if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
root.userInitiatedExpansion = true; root.userInitiatedExpansion = true;
root.isDescriptionToggleAnimation = true;
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : ""; const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "";
NotificationService.toggleMessageExpansion(messageId); NotificationService.toggleMessageExpansion(messageId);
Qt.callLater(() => { Qt.callLater(() => {
@@ -423,7 +427,7 @@ Rectangle {
} }
} }
propagateComposedEvents: true propagateComposedEvents: false
onPressed: mouse => { onPressed: mouse => {
if (parent.hoveredLink) if (parent.hoveredLink)
mouse.accepted = false; mouse.accepted = false;
@@ -580,7 +584,12 @@ Rectangle {
} }
Behavior on height { Behavior on height {
enabled: false enabled: expandedDelegateWrapper.__delegateInitialized && root.animateExpansion && root.userInitiatedExpansion
NumberAnimation {
duration: root.expansionMotionDuration()
easing.type: Easing.BezierSpline
easing.bezierCurve: root.expansionMotionCurve()
}
} }
Item { Item {
@@ -719,6 +728,7 @@ Rectangle {
onClicked: mouse => { onClicked: mouse => {
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) { if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
root.userInitiatedExpansion = true; root.userInitiatedExpansion = true;
root.isDescriptionToggleAnimation = true;
NotificationService.toggleMessageExpansion(modelData?.notification?.id || ""); NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
Qt.callLater(() => { Qt.callLater(() => {
if (root && !root.isAnimating) if (root && !root.isAnimating)
@@ -727,7 +737,7 @@ Rectangle {
} }
} }
propagateComposedEvents: true propagateComposedEvents: false
onPressed: mouse => { onPressed: mouse => {
if (parent.hoveredLink) { if (parent.hoveredLink) {
mouse.accepted = false; mouse.accepted = false;
@@ -985,6 +995,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.userInitiatedExpansion = true; root.userInitiatedExpansion = true;
root.isDescriptionToggleAnimation = false;
NotificationService.toggleGroupExpansion(notificationGroup?.key || ""); NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
} }
z: -1 z: -1
@@ -1008,6 +1019,7 @@ Rectangle {
buttonSize: compactMode ? 24 : 28 buttonSize: compactMode ? 24 : 28
onClicked: { onClicked: {
root.userInitiatedExpansion = true; root.userInitiatedExpansion = true;
root.isDescriptionToggleAnimation = false;
NotificationService.toggleGroupExpansion(notificationGroup?.key || ""); NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
} }
} }
@@ -1034,6 +1046,7 @@ Rectangle {
} else { } else {
root.isAnimating = false; root.isAnimating = false;
root.userInitiatedExpansion = false; root.userInitiatedExpansion = false;
root.isDescriptionToggleAnimation = false;
root._retainedExpandedContent = false; root._retainedExpandedContent = false;
root._clipAnimatedContent = false; root._clipAnimatedContent = false;
} }

View File

@@ -25,6 +25,9 @@ PanelWindow {
default: return "top"; default: return "top";
} }
} }
readonly property int inlineExpandDuration: Theme.notificationInlineExpandDuration
readonly property int inlineCollapseDuration: Theme.notificationInlineCollapseDuration
property bool inlineHeightAnimating: false
WindowBlur { WindowBlur {
targetWindow: win targetWindow: win
@@ -57,6 +60,7 @@ PanelWindow {
property real _lastReportedAlignedHeight: -1 property real _lastReportedAlignedHeight: -1
property real _storedTopMargin: 0 property real _storedTopMargin: 0
property real _storedBottomMargin: 0 property real _storedBottomMargin: 0
property bool _inlineGeometryReady: false
readonly property bool directionalEffect: Theme.isDirectionalEffect readonly property bool directionalEffect: Theme.isDirectionalEffect
readonly property bool depthEffect: Theme.isDepthEffect readonly property bool depthEffect: Theme.isDepthEffect
readonly property real entryTravel: { readonly property real entryTravel: {
@@ -84,14 +88,8 @@ PanelWindow {
property bool descriptionExpanded: false property bool descriptionExpanded: false
readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0 readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
onDescriptionExpandedChanged: { onDescriptionExpandedChanged: {
popupHeightChanged(); if (connectedFrameMode)
} popupChromeGeometryChanged();
onImplicitHeightChanged: {
const aligned = Theme.px(implicitHeight, dpr);
if (Math.abs(aligned - _lastReportedAlignedHeight) < 0.5)
return;
_lastReportedAlignedHeight = aligned;
popupHeightChanged();
} }
readonly property bool compactMode: SettingsData.notificationCompactMode readonly property bool compactMode: SettingsData.notificationCompactMode
@@ -182,23 +180,86 @@ PanelWindow {
return basePopupHeightPrivacy; return basePopupHeightPrivacy;
if (!descriptionExpanded) if (!descriptionExpanded)
return basePopupHeight; return basePopupHeight;
const bodyTextHeight = bodyText.contentHeight || 0; const bodyTextHeight = expandedBodyMeasure.contentHeight || bodyText.contentHeight || 0;
const collapsedBodyHeight = Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2); 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;
} }
readonly property real targetAlignedHeight: Theme.px(Math.max(0, contentImplicitHeight), dpr)
property real renderedAlignedHeight: targetAlignedHeight
property real allocatedAlignedHeight: targetAlignedHeight
readonly property bool inlineGeometryGrowing: targetAlignedHeight >= renderedAlignedHeight
readonly property bool contentAnchorsTop: isTopCenter
|| SettingsData.notificationPopupPosition === SettingsData.Position.Top
|| SettingsData.notificationPopupPosition === SettingsData.Position.Left
readonly property real renderedContentOffsetY: contentAnchorsTop ? 0 : Math.max(0, allocatedAlignedHeight - renderedAlignedHeight)
implicitWidth: contentImplicitWidth + (windowShadowPad * 2) implicitWidth: contentImplicitWidth + (windowShadowPad * 2)
implicitHeight: contentImplicitHeight + (windowShadowPad * 2) implicitHeight: allocatedAlignedHeight + (windowShadowPad * 2)
Behavior on implicitHeight { function inlineMotionDuration(growing) {
return growing ? inlineExpandDuration : inlineCollapseDuration;
}
function syncInlineTargetHeight() {
const target = Math.max(0, Number(targetAlignedHeight));
if (isNaN(target))
return;
if (!_inlineGeometryReady) {
renderedHeightAnim.stop();
renderedAlignedHeight = target;
allocatedAlignedHeight = target;
_lastReportedAlignedHeight = target;
return;
}
const currentRendered = Math.max(0, Number(renderedAlignedHeight));
const nextAllocation = Math.max(target, currentRendered, allocatedAlignedHeight);
if (Math.abs(nextAllocation - allocatedAlignedHeight) >= 0.5)
allocatedAlignedHeight = nextAllocation;
if (Math.abs(target - renderedAlignedHeight) < 0.5) {
finishInlineHeightAnimation();
return;
}
renderedAlignedHeight = target;
if (connectedFrameMode)
popupChromeGeometryChanged();
if (inlineMotionDuration(target >= currentRendered) <= 0)
Qt.callLater(() => finishInlineHeightAnimation());
}
function finishInlineHeightAnimation() {
const target = Math.max(0, Number(targetAlignedHeight));
if (isNaN(target))
return;
if (Math.abs(renderedAlignedHeight - target) >= 0.5)
renderedAlignedHeight = target;
if (Math.abs(allocatedAlignedHeight - target) >= 0.5)
allocatedAlignedHeight = target;
_lastReportedAlignedHeight = renderedAlignedHeight;
popupHeightChanged();
if (connectedFrameMode)
popupChromeGeometryChanged();
}
onTargetAlignedHeightChanged: syncInlineTargetHeight()
onAllocatedAlignedHeightChanged: {
if (connectedFrameMode)
popupChromeGeometryChanged();
}
Behavior on renderedAlignedHeight {
enabled: !exiting && !_isDestroying enabled: !exiting && !_isDestroying
NumberAnimation { NumberAnimation {
id: implicitHeightAnim id: renderedHeightAnim
duration: descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration duration: win.inlineMotionDuration(win.inlineGeometryGrowing)
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: descriptionExpanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve easing.bezierCurve: win.inlineGeometryGrowing ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
onFinished: win.popupHeightChanged() onRunningChanged: win.inlineHeightAnimating = running
onFinished: win.finishInlineHeightAnimation()
} }
} }
@@ -208,7 +269,11 @@ PanelWindow {
} }
} }
Component.onCompleted: { Component.onCompleted: {
_lastReportedAlignedHeight = Theme.px(implicitHeight, dpr); renderedHeightAnim.stop();
renderedAlignedHeight = targetAlignedHeight;
allocatedAlignedHeight = targetAlignedHeight;
_inlineGeometryReady = true;
_lastReportedAlignedHeight = renderedAlignedHeight;
_storedTopMargin = getTopMargin(); _storedTopMargin = getTopMargin();
_storedBottomMargin = getBottomMargin(); _storedBottomMargin = getBottomMargin();
if (SettingsData.notificationPopupPrivacyMode) if (SettingsData.notificationPopupPrivacyMode)
@@ -379,7 +444,7 @@ PanelWindow {
return Theme.snap(screen.width - alignedWidth - barRight, dpr); return Theme.snap(screen.width - alignedWidth - barRight, dpr);
} }
function getContentY() { function getAllocatedContentY() {
if (!screen) if (!screen)
return 0; return 0;
@@ -389,7 +454,11 @@ PanelWindow {
const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left; const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left;
if (isTop) if (isTop)
return Theme.snap(barTop, dpr); return Theme.snap(barTop, dpr);
return Theme.snap(screen.height - alignedHeight - barBottom, dpr); return Theme.snap(screen.height - allocatedAlignedHeight - barBottom, dpr);
}
function getContentY() {
return Theme.snap(getAllocatedContentY() + renderedContentOffsetY, dpr);
} }
function getWindowLeftMargin() { function getWindowLeftMargin() {
@@ -401,7 +470,7 @@ PanelWindow {
function getWindowTopMargin() { function getWindowTopMargin() {
if (!screen) if (!screen)
return 0; return 0;
return Theme.snap(getContentY() - windowShadowPad, dpr); return Theme.snap(getAllocatedContentY() - windowShadowPad, dpr);
} }
function _swipeDismissTarget() { function _swipeDismissTarget() {
@@ -481,7 +550,7 @@ PanelWindow {
readonly property bool screenValid: win.screen && !_isDestroying readonly property bool screenValid: win.screen && !_isDestroying
readonly property real dpr: screenValid ? CompositorService.getScreenScale(win.screen) : 1 readonly property real dpr: screenValid ? CompositorService.getScreenScale(win.screen) : 1
readonly property real alignedWidth: Theme.px(Math.max(0, implicitWidth - (windowShadowPad * 2)), dpr) readonly property real alignedWidth: Theme.px(Math.max(0, implicitWidth - (windowShadowPad * 2)), dpr)
readonly property real alignedHeight: Theme.px(Math.max(0, implicitHeight - (windowShadowPad * 2)), dpr) readonly property real alignedHeight: renderedAlignedHeight
onScreenYChanged: popupChromeGeometryChanged() onScreenYChanged: popupChromeGeometryChanged()
onScreenChanged: popupChromeGeometryChanged() onScreenChanged: popupChromeGeometryChanged()
onConnectedFrameModeChanged: popupChromeGeometryChanged() onConnectedFrameModeChanged: popupChromeGeometryChanged()
@@ -492,11 +561,11 @@ PanelWindow {
id: content id: content
x: Theme.snap(windowShadowPad, dpr) x: Theme.snap(windowShadowPad, dpr)
y: Theme.snap(windowShadowPad, dpr) y: Theme.snap(windowShadowPad + renderedContentOffsetY, dpr)
width: alignedWidth width: alignedWidth
height: alignedHeight height: alignedHeight
visible: !win._finalized && !chromeOnlyExit visible: !win._finalized && !chromeOnlyExit
scale: cardHoverHandler.hovered ? 1.01 : 1.0 scale: (!win.inlineHeightAnimating && cardHoverHandler.hovered) ? 1.01 : 1.0
transformOrigin: Item.Center transformOrigin: Item.Center
Behavior on scale { Behavior on scale {
@@ -537,21 +606,21 @@ PanelWindow {
Behavior on shadowBlurPx { Behavior on shadowBlurPx {
NumberAnimation { NumberAnimation {
duration: win.descriptionExpanded ? Theme.notificationExpandDuration : Theme.shortDuration duration: win.inlineHeightAnimating ? win.inlineExpandDuration : Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on shadowOffsetX { Behavior on shadowOffsetX {
NumberAnimation { NumberAnimation {
duration: win.descriptionExpanded ? Theme.notificationExpandDuration : Theme.shortDuration duration: win.inlineHeightAnimating ? win.inlineExpandDuration : Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
Behavior on shadowOffsetY { Behavior on shadowOffsetY {
NumberAnimation { NumberAnimation {
duration: win.descriptionExpanded ? Theme.notificationExpandDuration : Theme.shortDuration duration: win.inlineHeightAnimating ? win.inlineExpandDuration : Theme.shortDuration
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
} }
@@ -652,10 +721,23 @@ PanelWindow {
LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true LayoutMirroring.childrenInherit: true
StyledText {
id: expandedBodyMeasure
visible: false
width: Math.max(0, backgroundContainer.width - Theme.spacingL - (Theme.spacingL + Theme.notificationHoverRevealMargin) - popupIconSize - Theme.spacingM)
text: notificationData ? (notificationData.htmlBody || "") : ""
font.pixelSize: Theme.fontSizeSmall
elide: Text.ElideNone
horizontalAlignment: Text.AlignLeft
maximumLineCount: -1
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
Item { Item {
id: notificationContent id: notificationContent
readonly property real expandedTextHeight: bodyText.contentHeight || 0 readonly property real expandedTextHeight: expandedBodyMeasure.contentHeight || bodyText.contentHeight || 0
readonly property real collapsedBodyHeight: Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2) readonly property real collapsedBodyHeight: Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)
readonly property real effectiveCollapsedHeight: (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded) ? win.privacyCollapsedContentHeight : win.collapsedContentHeight readonly property real effectiveCollapsedHeight: (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded) ? win.privacyCollapsedContentHeight : win.collapsedContentHeight
readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > collapsedBodyHeight + 2) ? (expandedTextHeight - collapsedBodyHeight) : 0 readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > collapsedBodyHeight + 2) ? (expandedTextHeight - collapsedBodyHeight) : 0
@@ -825,7 +907,7 @@ PanelWindow {
win.descriptionExpanded = !win.descriptionExpanded; win.descriptionExpanded = !win.descriptionExpanded;
} }
propagateComposedEvents: true propagateComposedEvents: false
onPressed: mouse => { onPressed: mouse => {
if (parent.hoveredLink) if (parent.hoveredLink)
mouse.accepted = false; mouse.accepted = false;

View File

@@ -141,6 +141,8 @@ QtObject {
return false; return false;
if (!p.notificationData?.popup && !p.exiting) if (!p.notificationData?.popup && !p.exiting)
return false; return false;
if (p.exiting && p.notificationData?.removedByLimit && _layoutWindows().length > 0)
return true;
if (!p.exiting && p.popupChromeOpenProgress && p.popupChromeOpenProgress() < chromeOpenProgressThreshold) if (!p.exiting && p.popupChromeOpenProgress && p.popupChromeOpenProgress() < chromeOpenProgressThreshold)
return false; return false;
// Keep the connected shell until the card is almost fully closed. // Keep the connected shell until the card is almost fully closed.
@@ -408,6 +410,33 @@ QtObject {
}; };
} }
function _filledMaxStackChromeEdge(candidates, stackEdge) {
const layoutWindows = _layoutWindows();
if (layoutWindows.length < NotificationService.maxVisibleNotifications)
return null;
const anchorsTop = _stackAnchorsTop();
const layoutAnchorEdge = _stackAnchoredChromeEdge(layoutWindows);
const anchorEdge = layoutAnchorEdge !== null ? layoutAnchorEdge : (stackEdge !== null ? stackEdge : _stackAnchoredChromeEdge(candidates));
if (anchorEdge === null)
return null;
let span = 0;
for (const p of layoutWindows) {
const rect = _popupChromeRect(p, false);
if (!rect)
continue;
span += Math.max(0, rect.bottom - rect.y);
}
if (span <= 0)
return null;
if (layoutWindows.length > 1)
span += popupSpacing * (layoutWindows.length - 1);
return {
anchorsTop: anchorsTop,
startEdge: anchorEdge.edge,
edge: anchorsTop ? anchorEdge.edge + span : anchorEdge.edge - span
};
}
function _syncNotificationChromeState() { function _syncNotificationChromeState() {
const screenName = manager.modelData?.name || ""; const screenName = manager.modelData?.name || "";
if (!screenName) if (!screenName)
@@ -455,6 +484,16 @@ QtObject {
if (!stackEdge.anchorsTop && stackEdge.edge > maxYEnd) if (!stackEdge.anchorsTop && stackEdge.edge > maxYEnd)
maxYEnd = stackEdge.edge; maxYEnd = stackEdge.edge;
} }
const filledMaxStackEdge = _filledMaxStackChromeEdge(chromeCandidates, stackEdge);
if (filledMaxStackEdge !== null) {
if (filledMaxStackEdge.anchorsTop) {
minY = filledMaxStackEdge.startEdge;
maxYEnd = filledMaxStackEdge.edge;
} else {
minY = filledMaxStackEdge.edge;
maxYEnd = filledMaxStackEdge.startEdge;
}
}
const anchorsTop = stackEdge !== null ? stackEdge.anchorsTop : _stackAnchorsTop(); const anchorsTop = stackEdge !== null ? stackEdge.anchorsTop : _stackAnchorsTop();
const closeGapAnchorEdge = _closeGapChromeAnchorEdge(anchorsTop); const closeGapAnchorEdge = _closeGapChromeAnchorEdge(anchorsTop);
if (closeGapAnchorEdge !== null) { if (closeGapAnchorEdge !== null) {