1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-15 10:12:07 -04:00

notifications: cleanup popup display logic

This commit is contained in:
bbedward
2026-02-15 13:24:31 -05:00
parent fd05768059
commit 00d3829999
3 changed files with 95 additions and 197 deletions

View File

@@ -16,6 +16,7 @@ PanelWindow {
required property var notificationData required property var notificationData
required property string notificationId required property string notificationId
readonly property bool hasValidData: notificationData && notificationData.notification readonly property bool hasValidData: notificationData && notificationData.notification
readonly property alias hovered: cardHoverHandler.hovered
property int screenY: 0 property int screenY: 0
property bool exiting: false property bool exiting: false
property bool _isDestroying: false property bool _isDestroying: false
@@ -48,7 +49,7 @@ PanelWindow {
signal entered signal entered
signal exitStarted signal exitStarted
signal exitFinished signal exitFinished
signal popupHeightChanged() signal popupHeightChanged
function startExit() { function startExit() {
if (exiting || _isDestroying) { if (exiting || _isDestroying) {
@@ -428,14 +429,14 @@ PanelWindow {
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: { anchors.topMargin: {
if (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded) { if (SettingsData.notificationPopupPrivacyMode && !descriptionExpanded) {
const headerSummary = Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2; const headerSummary = Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2;
return Math.max(0, headerSummary / 2 - popupIconSize / 2); return Math.max(0, headerSummary / 2 - popupIconSize / 2);
}
if (descriptionExpanded)
return Math.max(0, Theme.fontSizeSmall * 1.2 + (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)) / 2 - popupIconSize / 2);
return Math.max(0, Theme.fontSizeSmall * 1.2 + (textContainer.height - Theme.fontSizeSmall * 1.2) / 2 - popupIconSize / 2);
} }
if (descriptionExpanded)
return Math.max(0, Theme.fontSizeSmall * 1.2 + (Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2)) / 2 - popupIconSize / 2);
return Math.max(0, Theme.fontSizeSmall * 1.2 + (textContainer.height - Theme.fontSizeSmall * 1.2) / 2 - popupIconSize / 2);
}
imageSource: { imageSource: {
if (!notificationData) if (!notificationData)

View File

@@ -15,20 +15,14 @@ QtObject {
readonly property real popupSpacing: compactMode ? 0 : Theme.spacingXS readonly property real popupSpacing: compactMode ? 0 : Theme.spacingXS
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 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 readonly property int baseNotificationHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing + popupSpacing
property int maxTargetNotifications: 4 property var popupWindows: []
property var popupWindows: [] // strong refs to windows (live until exitFinished)
property var destroyingWindows: new Set() property var destroyingWindows: new Set()
property var pendingDestroys: [] property var pendingDestroys: []
property int destroyDelayMs: 100 property int destroyDelayMs: 100
property var pendingCreates: []
property int createDelayMs: 50
property bool createBusy: false
property Component popupComponent property Component popupComponent
popupComponent: Component { popupComponent: Component {
NotificationPopup { NotificationPopup {
onEntered: manager._onPopupEntered(this)
onExitStarted: manager._onPopupExitStarted(this)
onExitFinished: manager._onPopupExitFinished(this) onExitFinished: manager._onPopupExitFinished(this)
onPopupHeightChanged: manager._onPopupHeightChanged(this) onPopupHeightChanged: manager._onPopupHeightChanged(this)
} }
@@ -74,36 +68,6 @@ QtObject {
destroyTimer.restart(); destroyTimer.restart();
} }
property Timer createTimer: Timer {
interval: createDelayMs
running: false
repeat: false
onTriggered: manager._processCreateQueue()
}
function _processCreateQueue() {
createBusy = false;
if (pendingCreates.length === 0)
return;
const wrapper = pendingCreates.shift();
if (wrapper)
_doInsertNewestAtTop(wrapper);
if (pendingCreates.length > 0) {
createBusy = true;
createTimer.restart();
}
}
function _scheduleCreate(wrapper) {
if (!wrapper)
return;
pendingCreates.push(wrapper);
if (!createBusy) {
createBusy = true;
createTimer.restart();
}
}
sweeper: Timer { sweeper: Timer {
interval: 500 interval: 500
running: false running: false
@@ -129,11 +93,10 @@ QtObject {
} }
if (toRemove.length) { if (toRemove.length) {
popupWindows = popupWindows.filter(p => toRemove.indexOf(p) === -1); popupWindows = popupWindows.filter(p => toRemove.indexOf(p) === -1);
_repositionAllActivePopups(); _repositionAll();
} }
if (popupWindows.length === 0) { if (popupWindows.length === 0)
sweeper.stop(); sweeper.stop();
}
} }
} }
@@ -145,98 +108,29 @@ QtObject {
return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData; return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
} }
function _canMakeRoomFor(wrapper) {
const activeWindows = _active();
if (activeWindows.length < maxTargetNotifications) {
return true;
}
if (!wrapper || !wrapper.notification) {
return false;
}
const incomingUrgency = wrapper.urgency || 0;
for (const p of activeWindows) {
if (!p.notificationData || !p.notificationData.notification) {
continue;
}
const existingUrgency = p.notificationData.urgency || 0;
if (existingUrgency < incomingUrgency) {
return true;
}
if (existingUrgency === incomingUrgency) {
const timer = p.notificationData.timer;
if (timer && !timer.running) {
return true;
}
}
}
return false;
}
function _makeRoomForNew(wrapper) {
const activeWindows = _active();
if (activeWindows.length < maxTargetNotifications) {
return;
}
const toRemove = _selectPopupToRemove(activeWindows, wrapper);
if (toRemove && !toRemove.exiting) {
toRemove.notificationData.removedByLimit = true;
toRemove.notificationData.popup = false;
if (toRemove.notificationData.timer) {
toRemove.notificationData.timer.stop();
}
}
}
function _selectPopupToRemove(activeWindows, incomingWrapper) {
const sortedWindows = activeWindows.slice().sort((a, b) => {
const aUrgency = (a.notificationData) ? a.notificationData.urgency || 0 : 0;
const bUrgency = (b.notificationData) ? b.notificationData.urgency || 0 : 0;
if (aUrgency !== bUrgency) {
return aUrgency - bUrgency;
}
const aTimer = a.notificationData && a.notificationData.timer;
const bTimer = b.notificationData && b.notificationData.timer;
const aRunning = aTimer && aTimer.running;
const bRunning = bTimer && bTimer.running;
if (aRunning !== bRunning) {
return aRunning ? 1 : -1;
}
return b.screenY - a.screenY;
});
return sortedWindows[0];
}
function _sync(newWrappers) { function _sync(newWrappers) {
for (const w of newWrappers) {
if (w && !_hasWindowFor(w)) {
insertNewestAtTop(w);
}
}
for (const p of popupWindows.slice()) { for (const p of popupWindows.slice()) {
if (!_isValidWindow(p)) { if (!_isValidWindow(p) || p.exiting)
continue; continue;
} if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1) {
if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1 && !p.exiting) {
p.notificationData.removedByLimit = true; p.notificationData.removedByLimit = true;
p.notificationData.popup = false; p.notificationData.popup = false;
} }
} }
} for (const w of newWrappers) {
if (w && !_hasWindowFor(w))
function insertNewestAtTop(wrapper) { _insertAtTop(w);
if (!wrapper)
return;
if (createBusy || pendingCreates.length > 0) {
_scheduleCreate(wrapper);
return;
} }
_doInsertNewestAtTop(wrapper);
} }
function _doInsertNewestAtTop(wrapper) { function _popupHeight(p) {
return (p.alignedHeight || p.implicitHeight || (baseNotificationHeight - popupSpacing)) + popupSpacing;
}
function _insertAtTop(wrapper) {
if (!wrapper) if (!wrapper)
return; return;
const notificationId = wrapper && wrapper.notification ? wrapper.notification.id : ""; const notificationId = wrapper?.notification ? wrapper.notification.id : "";
const win = popupComponent.createObject(null, { const win = popupComponent.createObject(null, {
"notificationData": wrapper, "notificationData": wrapper,
"notificationId": notificationId, "notificationId": notificationId,
@@ -250,85 +144,69 @@ QtObject {
return; return;
} }
popupWindows.unshift(win); popupWindows.unshift(win);
_repositionAll();
_repositionAllActivePopups();
createBusy = true;
createTimer.restart();
if (!sweeper.running) if (!sweeper.running)
sweeper.start(); sweeper.start();
} }
function _active() { function _repositionAll() {
return popupWindows.filter(p => _isValidWindow(p) && p.notificationData && p.notificationData.popup && !p.exiting); const active = popupWindows.filter(p => _isValidWindow(p) && p.notificationData?.popup && !p.exiting);
}
function _bottom() { const pinnedSlots = [];
let b = null; for (const p of active) {
let maxY = -1; if (!p.hovered)
for (const p of _active()) { continue;
if (p.screenY > maxY) { pinnedSlots.push({
maxY = p.screenY; y: p.screenY,
b = p; end: p.screenY + _popupHeight(p)
} });
} }
return b; pinnedSlots.sort((a, b) => a.y - b.y);
}
function _onPopupEntered(p) { let currentY = topMargin;
for (const win of active) {
if (win.hovered)
continue;
for (const slot of pinnedSlots) {
if (currentY >= slot.y - 1 && currentY < slot.end)
currentY = slot.end;
}
win.screenY = currentY;
currentY += _popupHeight(win);
}
} }
function _onPopupHeightChanged(p) { function _onPopupHeightChanged(p) {
if (!p || p.exiting || p._isDestroying) if (!p || p.exiting || p._isDestroying)
return; return;
_repositionAllActivePopups(); if (popupWindows.indexOf(p) === -1)
}
function _repositionAllActivePopups() {
const activeWindows = _active().sort((a, b) => a.screenY - b.screenY);
let currentY = topMargin;
for (const win of activeWindows) {
win.screenY = currentY;
const popupHeight = win.alignedHeight || win.implicitHeight;
currentY += popupHeight + popupSpacing;
}
}
function _onPopupExitStarted(p) {
if (!p)
return; return;
_repositionAllActivePopups(); _repositionAll();
} }
function _onPopupExitFinished(p) { function _onPopupExitFinished(p) {
if (!p) { if (!p)
return; return;
}
const windowId = p.toString(); const windowId = p.toString();
if (destroyingWindows.has(windowId)) { if (destroyingWindows.has(windowId))
return; return;
}
destroyingWindows.add(windowId); destroyingWindows.add(windowId);
const i = popupWindows.indexOf(p); const i = popupWindows.indexOf(p);
if (i !== -1) { if (i !== -1) {
popupWindows.splice(i, 1); popupWindows.splice(i, 1);
popupWindows = popupWindows.slice(); popupWindows = popupWindows.slice();
} }
if (NotificationService.releaseWrapper && p.notificationData) { if (NotificationService.releaseWrapper && p.notificationData)
NotificationService.releaseWrapper(p.notificationData); NotificationService.releaseWrapper(p.notificationData);
}
_scheduleDestroy(p); _scheduleDestroy(p);
Qt.callLater(() => destroyingWindows.delete(windowId)); Qt.callLater(() => destroyingWindows.delete(windowId));
_repositionAllActivePopups(); _repositionAll();
} }
function cleanupAllWindows() { function cleanupAllWindows() {
sweeper.stop(); sweeper.stop();
destroyTimer.stop(); destroyTimer.stop();
createTimer.stop();
pendingDestroys = []; pendingDestroys = [];
pendingCreates = [];
createBusy = false;
for (const p of popupWindows.slice()) { for (const p of popupWindows.slice()) {
if (p) { if (p) {
try { try {

View File

@@ -22,7 +22,7 @@ Singleton {
property list<NotifWrapper> notificationQueue: [] property list<NotifWrapper> notificationQueue: []
property list<NotifWrapper> visibleNotifications: [] property list<NotifWrapper> visibleNotifications: []
property int maxVisibleNotifications: 3 property int maxVisibleNotifications: 4
property bool addGateBusy: false property bool addGateBusy: false
property int enterAnimMs: 400 property int enterAnimMs: 400
property int seqCounter: 0 property int seqCounter: 0
@@ -253,7 +253,9 @@ Singleton {
return timeStr; return timeStr;
try { try {
const localeName = (typeof Qt !== "undefined" && Qt.locale) ? Qt.locale().name : "en-US"; const localeName = (typeof Qt !== "undefined" && Qt.locale) ? Qt.locale().name : "en-US";
const weekday = date.toLocaleDateString(localeName, { weekday: "long" }); const weekday = date.toLocaleDateString(localeName, {
weekday: "long"
});
return weekday + ", " + timeStr; return weekday + ", " + timeStr;
} catch (e) { } catch (e) {
return timeStr; return timeStr;
@@ -488,7 +490,7 @@ Singleton {
Timer { Timer {
id: addGate id: addGate
interval: enterAnimMs + 50 interval: 80
running: false running: false
repeat: false repeat: false
onTriggered: { onTriggered: {
@@ -694,7 +696,9 @@ Singleton {
try { try {
const localeName = (typeof Qt !== "undefined" && Qt.locale) ? Qt.locale().name : "en-US"; const localeName = (typeof Qt !== "undefined" && Qt.locale) ? Qt.locale().name : "en-US";
const weekday = time.toLocaleDateString(localeName, { weekday: "long" }); const weekday = time.toLocaleDateString(localeName, {
weekday: "long"
});
return `${weekday}, ${formatTime(time)}`; return `${weekday}, ${formatTime(time)}`;
} catch (e) { } catch (e) {
return formatTime(time); return formatTime(time);
@@ -843,39 +847,54 @@ Singleton {
} }
} }
function processQueue() { property bool _processingQueue: false
if (addGateBusy) {
return;
}
if (popupsDisabled) {
return;
}
if (SessionData.doNotDisturb) {
return;
}
if (notificationQueue.length === 0) {
return;
}
const activePopupCount = visibleNotifications.filter(n => n && n.popup).length; function processQueue() {
if (activePopupCount >= maxVisibleNotifications) { if (addGateBusy || _processingQueue)
return; return;
} if (popupsDisabled)
return;
if (SessionData.doNotDisturb)
return;
if (notificationQueue.length === 0)
return;
_processingQueue = true;
const next = notificationQueue.shift(); const next = notificationQueue.shift();
if (!next) if (!next) {
_processingQueue = false;
return; return;
}
next.seq = ++seqCounter; next.seq = ++seqCounter;
visibleNotifications = [...visibleNotifications, next];
const activePopups = visibleNotifications.filter(n => n && n.popup);
let evicted = null;
if (activePopups.length >= maxVisibleNotifications) {
const unhovered = activePopups.filter(n => n.timer?.running);
const pool = unhovered.length > 0 ? unhovered : activePopups;
evicted = pool.reduce((min, n) => (n.seq < min.seq) ? n : min, pool[0]);
if (evicted)
evicted.removedByLimit = true;
}
if (evicted) {
visibleNotifications = [...visibleNotifications.filter(n => n !== evicted), next];
} else {
visibleNotifications = [...visibleNotifications, next];
}
if (evicted)
evicted.popup = false;
next.popup = true; next.popup = true;
if (next.timer.interval > 0) { if (next.timer.interval > 0)
next.timer.start(); next.timer.start();
}
addGateBusy = true; addGateBusy = true;
addGate.restart(); addGate.restart();
_processingQueue = false;
} }
function removeFromVisibleNotifications(wrapper) { function removeFromVisibleNotifications(wrapper) {