1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-16 10:42:06 -04:00

(frame): QOL Control Center & Notification updates

This commit is contained in:
purian23
2026-04-16 00:06:34 -04:00
parent 59160ce5ac
commit c11069d502
10 changed files with 637 additions and 463 deletions

View File

@@ -16,8 +16,7 @@ DankListView {
property bool listInitialized: false
property int swipingCardIndex: -1
property real swipingCardOffset: 0
property real __pendingStableHeight: 0
property real __heightUpdateThreshold: 20
property bool _stableHeightUpdatePending: false
readonly property real shadowBlurPx: Theme.elevationEnabled ? ((Theme.elevationLevel1 && Theme.elevationLevel1.blurPx !== undefined) ? Theme.elevationLevel1.blurPx : 4) : 0
readonly property real shadowHorizontalGutter: Theme.snap(Math.max(Theme.spacingS, Math.min(32, shadowBlurPx * 1.5 + 6)), 1)
readonly property real shadowVerticalGutter: Theme.snap(Math.max(Theme.spacingXS, 6), 1)
@@ -27,51 +26,52 @@ DankListView {
Qt.callLater(() => {
if (listView) {
listView.listInitialized = true;
listView.stableContentHeight = listView.contentHeight;
listView.syncStableContentHeight(false);
}
});
}
Timer {
id: heightUpdateDebounce
interval: Theme.mediumDuration + 20
repeat: false
onTriggered: {
if (!listView.isAnimatingExpansion && Math.abs(listView.__pendingStableHeight - listView.stableContentHeight) > listView.__heightUpdateThreshold) {
listView.stableContentHeight = listView.__pendingStableHeight;
}
function targetContentHeight() {
if (count <= 0)
return contentHeight;
let total = topMargin + bottomMargin + Math.max(0, count - 1) * spacing;
for (let i = 0; i < count; i++) {
const item = itemAtIndex(i);
if (!item || item.nonAnimHeight === undefined)
return contentHeight;
total += item.nonAnimHeight;
}
return Math.max(0, total);
}
function syncStableContentHeight(useTarget) {
const nextHeight = useTarget ? targetContentHeight() : contentHeight;
if (Math.abs(nextHeight - stableContentHeight) <= 0.5)
return;
stableContentHeight = nextHeight;
}
function queueStableContentHeightUpdate(useTarget) {
if (_stableHeightUpdatePending)
return;
_stableHeightUpdatePending = true;
Qt.callLater(() => {
_stableHeightUpdatePending = false;
syncStableContentHeight(useTarget || isAnimatingExpansion);
});
}
onContentHeightChanged: {
if (!isAnimatingExpansion) {
__pendingStableHeight = contentHeight;
if (Math.abs(contentHeight - stableContentHeight) > __heightUpdateThreshold) {
heightUpdateDebounce.restart();
} else {
stableContentHeight = contentHeight;
}
}
if (!isAnimatingExpansion)
queueStableContentHeightUpdate(false);
}
onIsAnimatingExpansionChanged: {
if (isAnimatingExpansion) {
heightUpdateDebounce.stop();
let delta = 0;
for (let i = 0; i < count; i++) {
const item = itemAtIndex(i);
if (item && item.children[0] && item.children[0].isAnimating) {
const targetDelegateHeight = item.children[0].targetHeight + listView.delegateShadowGutter;
delta += targetDelegateHeight - item.height;
}
}
const targetHeight = contentHeight + delta;
// During expansion, always update immediately without threshold check
stableContentHeight = targetHeight;
syncStableContentHeight(true);
} else {
__pendingStableHeight = contentHeight;
heightUpdateDebounce.stop();
stableContentHeight = __pendingStableHeight;
queueStableContentHeightUpdate(false);
}
}
@@ -148,11 +148,14 @@ DankListView {
readonly property real adjacentScaleInfluence: isAdjacentToSwipe ? 1.0 - Math.abs(listView.swipingCardOffset) / width * 0.02 : 1.0
readonly property real swipeFadeStartOffset: width * 0.75
readonly property real swipeFadeDistance: Math.max(1, width - swipeFadeStartOffset)
readonly property real nonAnimHeight: notificationCard.targetHeight + listView.delegateShadowGutter
Component.onCompleted: {
Qt.callLater(() => {
if (delegateRoot)
if (delegateRoot) {
delegateRoot.__delegateInitialized = true;
listView.queueStableContentHeightUpdate(listView.isAnimatingExpansion);
}
});
}
@@ -180,6 +183,7 @@ DankListView {
onIsAnimatingChanged: {
if (isAnimating) {
listView.isAnimatingExpansion = true;
listView.syncStableContentHeight(true);
} else {
Qt.callLater(() => {
if (!notificationCard || !listView)
@@ -197,6 +201,13 @@ DankListView {
}
}
onTargetHeightChanged: {
if (isAnimating || listView.isAnimatingExpansion)
listView.syncStableContentHeight(true);
else
listView.queueStableContentHeightUpdate(false);
}
isGroupSelected: {
if (!keyboardController || !keyboardController.keyboardNavigationActive || !listView.keyboardActive)
return false;

View File

@@ -16,6 +16,12 @@ Rectangle {
property bool userInitiatedExpansion: false
property bool isAnimating: false
property bool animateExpansion: true
property bool _retainedExpandedContent: false
property bool _clipAnimatedContent: false
property real expandedContentOpacity: expanded ? 1 : 0
property real collapsedContentOpacity: expanded ? 0 : 1
readonly property bool renderExpandedContent: expanded || _retainedExpandedContent
readonly property bool renderCollapsedContent: !expanded
property bool isGroupSelected: false
property int selectedNotificationIndex: -1
@@ -57,6 +63,14 @@ Rectangle {
});
}
function expansionMotionDuration() {
return root.connectedFrameMode ? Theme.variantDuration(Theme.popoutAnimationDuration, root.expanded) : (root.expanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration);
}
function expansionMotionCurve() {
return root.connectedFrameMode ? (root.expanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : Theme.expressiveCurves.emphasized;
}
Behavior on scale {
enabled: listLevelScaleAnimationsEnabled
NumberAnimation {
@@ -66,6 +80,7 @@ Rectangle {
}
Behavior on shadowBlurPx {
enabled: !root.connectedFrameMode
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
@@ -73,6 +88,7 @@ Rectangle {
}
Behavior on shadowOffsetXPx {
enabled: !root.connectedFrameMode
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
@@ -80,6 +96,7 @@ Rectangle {
}
Behavior on shadowOffsetYPx {
enabled: !root.connectedFrameMode
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
@@ -94,6 +111,24 @@ Rectangle {
}
}
Behavior on expandedContentOpacity {
enabled: root.__initialized && root.userInitiatedExpansion && root.animateExpansion
NumberAnimation {
duration: root.expansionMotionDuration()
easing.type: Easing.BezierSpline
easing.bezierCurve: root.expansionMotionCurve()
}
}
Behavior on collapsedContentOpacity {
enabled: root.__initialized && root.userInitiatedExpansion && root.animateExpansion
NumberAnimation {
duration: root.expansionMotionDuration()
easing.type: Easing.BezierSpline
easing.bezierCurve: root.expansionMotionCurve()
}
}
color: {
if (isGroupSelected && keyboardNavigationActive) {
return Theme.primaryPressed;
@@ -129,7 +164,31 @@ Rectangle {
}
return 0;
}
clip: false
clip: connectedFrameMode && _clipAnimatedContent
onExpandedChanged: {
if (connectedFrameMode && __initialized && userInitiatedExpansion && animateExpansion)
_clipAnimatedContent = true;
if (expanded) {
_retainedExpandedContent = false;
return;
}
if (connectedFrameMode && __initialized && userInitiatedExpansion && animateExpansion)
_retainedExpandedContent = true;
}
onHeightChanged: {
if (Math.abs(height - targetHeight) > 0.5)
return;
_clipAnimatedContent = false;
if (!expanded && _retainedExpandedContent)
_retainedExpandedContent = false;
}
onExpandedContentOpacityChanged: {
if (!expanded && _retainedExpandedContent && expandedContentOpacity <= 0.01)
_retainedExpandedContent = false;
}
HoverHandler {
id: cardHoverHandler
@@ -149,7 +208,7 @@ Rectangle {
shadowOffsetX: root.shadowOffsetXPx
shadowOffsetY: root.shadowOffsetYPx
shadowColor: root.shadowElevation ? Theme.elevationShadowColor(root.shadowElevation) : "transparent"
shadowEnabled: root.shadowsAllowed
shadowEnabled: root.shadowsAllowed && !root.connectedFrameMode
}
Rectangle {
@@ -189,7 +248,8 @@ Rectangle {
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL + Theme.notificationHoverRevealMargin
height: collapsedContentHeight + extraHeight
visible: !expanded
visible: renderCollapsedContent
opacity: root.collapsedContentOpacity
DankCircularImage {
id: iconContainer
@@ -388,7 +448,8 @@ Rectangle {
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL
spacing: compactMode ? Theme.spacingXS : Theme.spacingS
visible: expanded
visible: renderExpandedContent
opacity: root.expandedContentOpacity
Item {
width: parent.width
@@ -831,7 +892,8 @@ Rectangle {
}
Row {
visible: !expanded
visible: renderCollapsedContent
opacity: root.collapsedContentOpacity
anchors.right: clearButton.visible ? clearButton.left : parent.right
anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
anchors.top: collapsedContent.bottom
@@ -887,7 +949,8 @@ Rectangle {
property bool isHovered: false
readonly property int actionCount: (notificationGroup?.latestNotification?.actions || []).length
visible: !expanded && actionCount < 3
visible: renderCollapsedContent && actionCount < 3
opacity: root.collapsedContentOpacity
anchors.right: parent.right
anchors.rightMargin: Theme.spacingL
anchors.top: collapsedContent.bottom
@@ -918,7 +981,7 @@ Rectangle {
MouseArea {
anchors.fill: parent
visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
visible: renderCollapsedContent && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
cursorShape: Qt.PointingHandCursor
onClicked: {
root.userInitiatedExpansion = true;
@@ -962,15 +1025,17 @@ Rectangle {
Behavior on height {
enabled: root.__initialized && root.userInitiatedExpansion && root.animateExpansion
NumberAnimation {
duration: root.connectedFrameMode ? Theme.variantDuration(Theme.popoutAnimationDuration, root.expanded) : (root.expanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration)
duration: root.expansionMotionDuration()
easing.type: Easing.BezierSpline
easing.bezierCurve: root.connectedFrameMode ? (root.expanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : Theme.expressiveCurves.emphasized
easing.bezierCurve: root.expansionMotionCurve()
onRunningChanged: {
if (running) {
root.isAnimating = true;
} else {
root.isAnimating = false;
root.userInitiatedExpansion = false;
root._retainedExpandedContent = false;
root._clipAnimatedContent = false;
}
}
}

View File

@@ -14,6 +14,7 @@ DankPopout {
property real stablePopupHeight: 400
property real _lastAlignedContentHeight: -1
property bool _pendingSizedOpen: false
property bool _heightUpdatePending: false
function updateStablePopupHeight() {
const item = contentLoader.item;
@@ -30,6 +31,16 @@ DankPopout {
stablePopupHeight = target;
}
function queueStablePopupHeightUpdate() {
if (_heightUpdatePending)
return;
_heightUpdatePending = true;
Qt.callLater(() => {
_heightUpdatePending = false;
updateStablePopupHeight();
});
}
NotificationKeyboardController {
id: keyboardController
listView: null
@@ -128,7 +139,7 @@ DankPopout {
Connections {
target: contentLoader.item
function onImplicitHeightChanged() {
root.updateStablePopupHeight();
root.queueStablePopupHeightUpdate();
}
}

View File

@@ -537,21 +537,21 @@ PanelWindow {
Behavior on shadowBlurPx {
NumberAnimation {
duration: Theme.shortDuration
duration: win.descriptionExpanded ? Theme.notificationExpandDuration : Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on shadowOffsetX {
NumberAnimation {
duration: Theme.shortDuration
duration: win.descriptionExpanded ? Theme.notificationExpandDuration : Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on shadowOffsetY {
NumberAnimation {
duration: Theme.shortDuration
duration: win.descriptionExpanded ? Theme.notificationExpandDuration : Theme.shortDuration
easing.type: Theme.standardEasing
}
}
@@ -566,7 +566,7 @@ PanelWindow {
shadowOffsetX: content.shadowOffsetX
shadowOffsetY: content.shadowOffsetY
shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent"
shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed
shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed && !win.connectedFrameMode
layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically
@@ -578,39 +578,39 @@ PanelWindow {
sourceRect.radius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
sourceRect.color: win.connectedFrameMode ? Theme.popupLayerColor(Theme.surfaceContainer) : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
sourceRect.antialiasing: true
sourceRect.layer.enabled: win.connectedFrameMode
sourceRect.layer.smooth: true
sourceRect.layer.textureSize: win.connectedFrameMode && win.dpr > 1 ? Qt.size(Math.ceil(sourceRect.width * win.dpr), Math.ceil(sourceRect.height * win.dpr)) : Qt.size(0, 0)
sourceRect.layer.enabled: false
sourceRect.layer.textureSize: Qt.size(0, 0)
sourceRect.border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08)
sourceRect.border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0
}
Rectangle {
x: bgShadowLayer.sourceRect.x
y: bgShadowLayer.sourceRect.y
width: bgShadowLayer.sourceRect.width
height: bgShadowLayer.sourceRect.height
radius: bgShadowLayer.sourceRect.radius
visible: notificationData && notificationData.urgency === NotificationUrgency.Critical
opacity: 1
clip: true
// Keep critical accent outside shadow rendering so connected mode still shows it.
Rectangle {
x: content.cardInset
y: content.cardInset
width: Math.max(0, content.width - content.cardInset * 2)
height: Math.max(0, content.height - content.cardInset * 2)
radius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
visible: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical
opacity: 1
clip: true
gradient: Gradient {
orientation: Gradient.Horizontal
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0
color: Theme.primary
}
GradientStop {
position: 0
color: Theme.primary
}
GradientStop {
position: 0.02
color: Theme.primary
}
GradientStop {
position: 0.02
color: Theme.primary
}
GradientStop {
position: 0.021
color: "transparent"
}
GradientStop {
position: 0.021
color: "transparent"
}
}
}

View File

@@ -36,6 +36,7 @@ QtObject {
property var pendingDestroys: []
property int destroyDelayMs: 100
property bool _chromeSyncPending: false
property bool _syncingVisibleNotifications: false
readonly property real chromeOpenProgressThreshold: 0.10
readonly property real chromeReleaseTailStart: 0.90
readonly property real chromeReleaseDropProgress: 0.995
@@ -160,6 +161,7 @@ QtObject {
function _sync(newWrappers) {
let needsReposition = false;
_syncingVisibleNotifications = true;
for (const p of popupWindows.slice()) {
if (!_isValidWindow(p) || p.exiting)
continue;
@@ -171,10 +173,10 @@ QtObject {
}
for (const w of newWrappers) {
if (w && !_hasWindowFor(w) && _isFocusedScreen()) {
_insertAtTop(w);
needsReposition = false;
needsReposition = _insertAtTop(w, true) || needsReposition;
}
}
_syncingVisibleNotifications = false;
if (needsReposition)
_repositionAll();
}
@@ -183,9 +185,9 @@ QtObject {
return (p.alignedHeight || p.implicitHeight || (baseNotificationHeight - popupSpacing)) + popupSpacing;
}
function _insertAtTop(wrapper) {
function _insertAtTop(wrapper, deferReposition) {
if (!wrapper)
return;
return false;
const notificationId = wrapper?.notification ? wrapper.notification.id : "";
const win = popupComponent.createObject(null, {
"notificationData": wrapper,
@@ -194,15 +196,17 @@ QtObject {
"screen": manager.modelData
});
if (!win)
return;
return false;
if (!win.hasValidData) {
win.destroy();
return;
return false;
}
popupWindows.unshift(win);
_repositionAll();
if (!deferReposition)
_repositionAll();
if (!sweeper.running)
sweeper.start();
return true;
}
function _repositionAll() {
@@ -321,6 +325,10 @@ QtObject {
if (!rect || p !== trailing || !p.popupChromeReleaseProgress)
return rect;
// Keep maxed-stack chrome anchored while a replacement tail exits.
if (p.exiting && p.notificationData?.removedByLimit && _layoutWindows().length > 0)
return rect;
const progress = _chromeReleaseTailProgress(p.popupChromeReleaseProgress());
if (progress <= 0)
return rect;
@@ -489,17 +497,34 @@ QtObject {
_scheduleNotificationChromeSync();
}
// Coalesce resize repositioning; exit-path moves remain immediate.
property bool _repositionPending: false
function _queueReposition() {
if (_repositionPending)
return;
_repositionPending = true;
Qt.callLater(_flushReposition);
}
function _flushReposition() {
_repositionPending = false;
_repositionAll();
}
function _onPopupHeightChanged(p) {
if (!p || p.exiting || p._isDestroying)
return;
if (popupWindows.indexOf(p) === -1)
return;
_repositionAll();
_queueReposition();
}
function _onPopupExitStarted(p) {
if (!p || popupWindows.indexOf(p) === -1)
return;
if (_syncingVisibleNotifications)
return;
_repositionAll();
}