mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-17 00:34:01 -04:00
fix: stabilize tray item key to prevent position jumps on property changes (#3386)
* fix: keep tray order stable when tooltipTitle changes dynamically
Revert the earlier simplistic key approach: keep getTrayItemKey()
unchanged (id::tooltipTitle) so duplicate-id disambiguation (#1542)
is preserved and persisted id::tooltipTitle entries keep matching.
Instead make persisted-key lookups tolerant of dynamic tooltipTitle:
resolveOrderIndex() in sortByPreferredOrder() does exact match first,
falling back to prefix matching an id::* entry only when the id has a
single instance. findSavedTrayKey()/isTrayIdHidden() replace direct
SessionData.isHiddenTrayId() calls and showTrayId() now receives the
actual saved key.
Prefix fallback is gated by trayItemCountForId() to single-instance
ids only, so multiple items sharing an id always match exactly and
hiding/ordering one never affects the others.
* fix: bail on ambiguous saved tray keys in fallback
* tray: use occurrence index as stable key, add one-time session migration
* tray: pin stable key per item instance so it never flips when siblings come and go
* tray: assign unique per-id sequential keys to avoid collision after sibling exit
* tray: reuse free key slots so a restarted singleton keeps its plain id
(cherry picked from commit 60342b7416)
This commit is contained in:
@@ -14,7 +14,7 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("SessionData")
|
||||
|
||||
readonly property int sessionConfigVersion: 4
|
||||
readonly property int sessionConfigVersion: 5
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
|
||||
@@ -83,5 +83,29 @@ function migrateToVersion(obj, targetVersion, settingsData) {
|
||||
session.configVersion = 4;
|
||||
}
|
||||
|
||||
if (currentVersion < 5) {
|
||||
console.info("SessionData: Migrating session to version 5");
|
||||
console.info("SessionData: Tray keys now use a stable id (or id::instance) instead of id::tooltipTitle");
|
||||
|
||||
function stripTrayKeys(list) {
|
||||
if (!list || list.constructor !== Array) return list;
|
||||
var out = [];
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var raw = list[i];
|
||||
if (typeof raw !== "string") continue;
|
||||
var key = raw.includes("::") ? raw.split("::")[0] : raw;
|
||||
var hasDup = false;
|
||||
for (var j = 0; j < out.length; j++) {
|
||||
if (out[j] === key) { hasDup = true; break; }
|
||||
}
|
||||
if (key && !hasDup) out.push(key);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
if (session.hiddenTrayIds !== undefined) session.hiddenTrayIds = stripTrayKeys(session.hiddenTrayIds);
|
||||
if (session.trayItemOrder !== undefined) session.trayItemOrder = stripTrayKeys(session.trayItemOrder);
|
||||
session.configVersion = 5;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -40,13 +40,33 @@ BasePill {
|
||||
return !hiddenTrayIds.includes(itemId.toLowerCase());
|
||||
});
|
||||
}
|
||||
property var _trayKeyByItem: new Map()
|
||||
function getTrayItemKey(item) {
|
||||
const id = item?.id || "";
|
||||
const tooltipTitle = item?.tooltipTitle || "";
|
||||
if (!tooltipTitle || tooltipTitle === id) {
|
||||
return id;
|
||||
if (!id)
|
||||
return "";
|
||||
if (root._trayKeyByItem.has(item))
|
||||
return root._trayKeyByItem.get(item);
|
||||
const taken = new Set();
|
||||
for (const live of root.allTrayItems) {
|
||||
if (live !== item && (live?.id || "") === id && root._trayKeyByItem.has(live))
|
||||
taken.add(root._trayKeyByItem.get(live));
|
||||
}
|
||||
return `${id}::${tooltipTitle}`;
|
||||
let key = id;
|
||||
for (let n = 1; taken.has(key); n++)
|
||||
key = `${id}::${n}`;
|
||||
root._trayKeyByItem.set(item, key);
|
||||
return key;
|
||||
}
|
||||
|
||||
function resolveOrderIndex(key, orderMap) {
|
||||
if (orderMap.has(key))
|
||||
return orderMap.get(key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
function isTrayIdHidden(key) {
|
||||
return SessionData.isHiddenTrayId(key);
|
||||
}
|
||||
|
||||
function trayIconSourceFor(trayItem) {
|
||||
@@ -150,6 +170,13 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SystemTray
|
||||
function onItemUnregistered(item) {
|
||||
root._trayKeyByItem.delete(item);
|
||||
}
|
||||
}
|
||||
|
||||
function sortByPreferredOrder(items, trigger) {
|
||||
void trigger;
|
||||
const savedOrder = SessionData.trayItemOrder || [];
|
||||
@@ -159,15 +186,17 @@ BasePill {
|
||||
return [...items].sort((a, b) => {
|
||||
const keyA = getTrayItemKey(a);
|
||||
const keyB = getTrayItemKey(b);
|
||||
const orderA = orderMap.has(keyA) ? orderMap.get(keyA) : 10000 + items.indexOf(a);
|
||||
const orderB = orderMap.has(keyB) ? orderMap.get(keyB) : 10000 + items.indexOf(b);
|
||||
return orderA - orderB;
|
||||
const orderA = resolveOrderIndex(keyA, orderMap);
|
||||
const orderB = resolveOrderIndex(keyB, orderMap);
|
||||
const posA = orderA >= 0 ? orderA : 10000 + items.indexOf(a);
|
||||
const posB = orderB >= 0 ? orderB : 10000 + items.indexOf(b);
|
||||
return posA - posB;
|
||||
});
|
||||
}
|
||||
|
||||
readonly property var allSortedTrayItems: sortByPreferredOrder(allTrayItems, _trayOrderTrigger)
|
||||
readonly property var allSortedTrayItemKeys: allSortedTrayItems.map(item => getTrayItemKey(item))
|
||||
readonly property var visibleSortedTrayItems: allSortedTrayItems.filter(item => !SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
|
||||
readonly property var visibleSortedTrayItems: allSortedTrayItems.filter(item => !root.isTrayIdHidden(root.getTrayItemKey(item)))
|
||||
readonly property int automaticVisibleItemLimit: {
|
||||
if (!root.useAutomaticOverflow)
|
||||
return root.visibleSortedTrayItems.length;
|
||||
@@ -190,7 +219,7 @@ BasePill {
|
||||
item: item
|
||||
}))
|
||||
readonly property var autoOverflowBarItems: visibleSortedTrayItems.slice(automaticVisibleItemLimit)
|
||||
readonly property var manualHiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
|
||||
readonly property var manualHiddenBarItems: allSortedTrayItems.filter(item => root.isTrayIdHidden(root.getTrayItemKey(item)))
|
||||
readonly property var hiddenBarItemKeys: manualHiddenBarItems.concat(autoOverflowBarItems).map(item => root.getTrayItemKey(item))
|
||||
readonly property var hiddenBarItems: allSortedTrayItems.filter(item => hiddenBarItemKeys.indexOf(root.getTrayItemKey(item)) !== -1)
|
||||
readonly property string trayIconTintMode: {
|
||||
@@ -298,7 +327,7 @@ BasePill {
|
||||
}
|
||||
|
||||
function isManualHiddenTrayItem(item) {
|
||||
return SessionData.isHiddenTrayId(getTrayItemKey(item));
|
||||
return root.isTrayIdHidden(root.getTrayItemKey(item));
|
||||
}
|
||||
|
||||
function isAutoOverflowTrayItem(item) {
|
||||
|
||||
Reference in New Issue
Block a user