mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-07 14:05:38 -05:00
notifications: improve resource usage
This commit is contained in:
@@ -1,115 +1,171 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
QtObject {
|
||||
id: manager
|
||||
|
||||
property list<var> popupInstances: []
|
||||
property int baseNotificationHeight: 130 // Height of a single notification + margins
|
||||
property var popupLoaders: []
|
||||
property int maxTargetNotifications: 3
|
||||
property int baseNotificationHeight: 132
|
||||
property bool dismissalInProgress: false
|
||||
property int maxTargetNotifications: 3 // Target number of notifications to maintain
|
||||
|
||||
Timer {
|
||||
id: dismissalTimer
|
||||
interval: 500 // Half second delay between dismissals
|
||||
|
||||
property Timer dismissalTimer: Timer {
|
||||
interval: 200
|
||||
repeat: false
|
||||
onTriggered: dismissNextOldest()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: dismissalDelayTimer
|
||||
interval: 1 // Start immediately
|
||||
onTriggered: startSequentialDismissal()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: popupComponent
|
||||
NotificationPopup {}
|
||||
}
|
||||
property Component popupLoaderComponent: Component {
|
||||
Loader {
|
||||
id: popupLoader
|
||||
|
||||
Connections {
|
||||
target: NotificationService
|
||||
function onNotificationQueueChanged() {
|
||||
syncPopupsWithQueue();
|
||||
repositionAll();
|
||||
property var notifWrapper
|
||||
|
||||
active: false
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: NotificationPopup {
|
||||
id: popup
|
||||
|
||||
notificationData: popupLoader.notifWrapper
|
||||
notificationId: popupLoader.notifWrapper ? popupLoader.notifWrapper.notification.id : ""
|
||||
onEntered: manager._onPopupEntered(popupLoader)
|
||||
onSlideOutChanged: {
|
||||
if (slideOut) {
|
||||
manager._onPopupExitStarted(popupLoader);
|
||||
}
|
||||
}
|
||||
onExitFinished: manager._onPopupExitFinished(popupLoader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Connections notificationConnections: Connections {
|
||||
function onVisibleNotificationsChanged() {
|
||||
repositionAll();
|
||||
syncPopupsWithQueue(NotificationService.visibleNotifications);
|
||||
}
|
||||
|
||||
target: NotificationService
|
||||
}
|
||||
|
||||
|
||||
function syncPopupsWithQueue() {
|
||||
const queue = NotificationService.notificationQueue.filter(n => n && n.popup);
|
||||
|
||||
// Clean up destroyed popups first
|
||||
popupInstances = popupInstances.filter(p => p && p.notificationId);
|
||||
|
||||
// DON'T aggressively destroy popups - let them handle their own lifecycle
|
||||
// Only remove popups that are actually destroyed/invalid
|
||||
// The popup will destroy itself when notificationData.popup becomes false
|
||||
|
||||
// Only create NEW notifications, don't touch existing ones AT ALL
|
||||
for (const notif of queue) {
|
||||
const existingPopup = popupInstances.find(p => p.notificationId === notif.notification.id);
|
||||
if (existingPopup) {
|
||||
// CRITICAL: Do absolutely NOTHING to existing popups
|
||||
// Don't change their verticalOffset, don't touch any properties
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate position for NEW notification only - at the bottom of ACTIVE stack
|
||||
const currentActive = popupInstances.filter(p => p && p.notificationData && p.notificationData.popup).length;
|
||||
const popup = popupComponent.createObject(root, {
|
||||
notificationData: notif,
|
||||
notificationId: notif.notification.id,
|
||||
verticalOffset: currentActive * baseNotificationHeight // ✅ bottom of active stack
|
||||
});
|
||||
|
||||
if (popup) {
|
||||
popupInstances.push(popup);
|
||||
|
||||
// Pin it until entrance finishes, then maybe start overflow
|
||||
popup.entered.connect(function() {
|
||||
repositionAll(); // it's now "stable"; allow vertical compaction
|
||||
maybeStartOverflow(); // defer overflow until after slot N is fully occupied
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Overflow dismissal handled in Connections now
|
||||
function _createPopupLoader(notifWrapper) {
|
||||
const L = popupLoaderComponent.createObject(manager, {
|
||||
"notifWrapper": notifWrapper
|
||||
});
|
||||
popupLoaders.push(L);
|
||||
return L;
|
||||
}
|
||||
|
||||
|
||||
function _destroyPopupLoader(L) {
|
||||
const i = popupLoaders.indexOf(L);
|
||||
if (i !== -1) {
|
||||
popupLoaders.splice(i, 1);
|
||||
popupLoaders = popupLoaders.slice();
|
||||
}
|
||||
L.active = false;
|
||||
L.sourceComponent = null;
|
||||
}
|
||||
|
||||
function _activeItems() {
|
||||
return popupLoaders.filter((L) => {
|
||||
return L.item && L.item.notificationData && L.item.notificationData.popup;
|
||||
});
|
||||
}
|
||||
|
||||
function _stableItems() {
|
||||
return _activeItems().filter((L) => {
|
||||
return !L.item.entering;
|
||||
});
|
||||
}
|
||||
|
||||
function repositionAll() {
|
||||
// Only compact stable (non-entering) active popups
|
||||
const stable = popupInstances.filter(p => p && p.notificationData && p.notificationData.popup && !p.entering);
|
||||
for (let i = 0; i < stable.length; ++i)
|
||||
stable[i].verticalOffset = i * baseNotificationHeight;
|
||||
const stable = _stableItems();
|
||||
for (let i = 0; i < stable.length; ++i) {
|
||||
const it = stable[i].item;
|
||||
if (it)
|
||||
it.verticalOffset = i * baseNotificationHeight;
|
||||
|
||||
// Newcomers keep their creation-time offset (slot N) until `entered()`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function syncPopupsWithQueue(newWrappers) {
|
||||
for (let w of newWrappers) {
|
||||
if (!popupLoaders.some((L) => {
|
||||
return L.notifWrapper === w;
|
||||
})) {
|
||||
const L = _createPopupLoader(w);
|
||||
const actives = _activeItems().length;
|
||||
w.initialOffset = actives * baseNotificationHeight;
|
||||
L.active = true;
|
||||
}
|
||||
}
|
||||
for (let L of popupLoaders.slice()) {
|
||||
if (newWrappers.indexOf(L.notifWrapper) === -1)
|
||||
_destroyPopupLoader(L);
|
||||
|
||||
}
|
||||
repositionAll();
|
||||
}
|
||||
|
||||
function _onPopupEntered(L) {
|
||||
repositionAll();
|
||||
maybeStartOverflow();
|
||||
}
|
||||
|
||||
function _onPopupExitStarted(L) {
|
||||
const it = L.item;
|
||||
if (!it)
|
||||
return ;
|
||||
|
||||
if (it.shadowLayers) {
|
||||
for (let layer of it.shadowLayers) {
|
||||
if (layer)
|
||||
layer.visible = false;
|
||||
|
||||
}
|
||||
}
|
||||
if (it.iconContainer && it.iconContainer.iconImage) {
|
||||
it.iconContainer.iconImage.source = "";
|
||||
}
|
||||
}
|
||||
|
||||
function _onPopupExitFinished(L) {
|
||||
NotificationService.releaseWrapper(L.notifWrapper);
|
||||
_destroyPopupLoader(L);
|
||||
repositionAll();
|
||||
maybeStartOverflow();
|
||||
}
|
||||
|
||||
function maybeStartOverflow() {
|
||||
const active = popupInstances.filter(p => p && p.notificationData && p.notificationData.popup);
|
||||
if (active.length > maxTargetNotifications && !dismissalInProgress)
|
||||
const active = _activeItems();
|
||||
if (dismissalInProgress)
|
||||
return ;
|
||||
|
||||
if (active.length > maxTargetNotifications)
|
||||
startSequentialDismissal();
|
||||
|
||||
}
|
||||
|
||||
|
||||
function startSequentialDismissal() {
|
||||
if (dismissalInProgress) return; // Don't start multiple dismissals
|
||||
|
||||
dismissalInProgress = true;
|
||||
dismissNextOldest();
|
||||
}
|
||||
|
||||
|
||||
function dismissNextOldest() {
|
||||
const active = popupInstances.filter(p => p && p.notificationData.popup);
|
||||
if (active.length <= maxTargetNotifications) { dismissalInProgress = false; return; }
|
||||
const oldest = active[0];
|
||||
const active = _activeItems();
|
||||
if (active.length <= maxTargetNotifications) {
|
||||
dismissalInProgress = false;
|
||||
return ;
|
||||
}
|
||||
const oldest = active[0].item;
|
||||
if (oldest) {
|
||||
oldest.notificationData.removedByLimit = true;
|
||||
oldest.notificationData.popup = false; // triggers slide-out in popup
|
||||
dismissalTimer.restart(); // your existing 500ms is fine
|
||||
oldest.notificationData.popup = false;
|
||||
dismissalTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user