From c7d9880f198395a7531941727973182542c151c1 Mon Sep 17 00:00:00 2001 From: Ben Leppke Date: Wed, 28 Oct 2026 09:46:48 -0500 Subject: [PATCH] fix: don't detach top bar items when only reordering them The workspace dots are animated in with a 500ms scale transition when the number of workspaces changes, e.g. when the second workspace is created at login. `#orderTopBarItems()` used to call `remove_child()` on every top bar item, including the "activities" container that owns those dots. Clutter's default remove-child flags include `REMOVE_CHILD_STOP_TRANSITIONS`, which stops all running transitions on the removed actor and its children. That froze the new dot at scale 0 or at a partial scale. Since `Panel._addToPanelBox` is overridden and triggers reordering whenever an indicator is added, this raced with the startup dot animation. Only detach an item when it actually changes boxes. Reorder items that are already in the target box with `set_child_at_index()`, which changes the child order without changing actor state or stopping transitions. Closes #17 --- src/extension.ts | 54 +++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 026de7e..9b064b0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -125,8 +125,8 @@ export default class TopBarOrganizerExtension extends Extension { /// Go through the items of the validBoxOrder and order the GNOME Shell /// top bar box accordingly. - for (let i = 0; i < validBoxOrder.length; i++) { - const item = validBoxOrder[i]; + const indicatorContainers: St.Bin[] = []; + for (const item of validBoxOrder) { // Get the indicator container associated with the current role. const associatedIndicatorContainer = (Main.panel.statusArea as any)[item.role]?.container; if (!(associatedIndicatorContainer instanceof St.Bin)) { @@ -134,29 +134,23 @@ export default class TopBarOrganizerExtension extends Extension { continue; } + indicatorContainers.push(associatedIndicatorContainer); + // Save whether or not the indicator container is visible. const isVisible = associatedIndicatorContainer.visible; - const parent = associatedIndicatorContainer.get_parent(); - if (parent !== null) { - parent.remove_child(associatedIndicatorContainer); - } - if (box === "right") { - // If the target panel box is the right panel box, insert the - // indicator container at index `-1`, which just adds it to the - // end (correct order is ensured, since `validBoxOrder` is - // sorted correctly and we're looping over it in order). - // This way unaccounted-for indicator containers will be at the - // left, which is preferred, since the box is logically - // right-to-left. - // The same applies for indicator containers, which are just - // temporarily unaccounted for (like for indicator containers of - // not yet ready app indicators), since them being at the right - // for a probably temporary stay causes all the indicator - // containers to shift. + // Only remove the indicator container from its current parent if + // it actually changes boxes. `remove_child()` stops all running + // transitions on the removed actor and its children (the default + // remove-child flags include `REMOVE_CHILD_STOP_TRANSITIONS`). + // Detaching e.g. the "activities" indicator container while GNOME + // Shell animates in a new workspace dot stops that animation, + // leaving the dot invisible or incorrectly sized until the next + // workspace change. + if (associatedIndicatorContainer.get_parent() !== panelBox) { + const parent = associatedIndicatorContainer.get_parent(); + parent?.remove_child(associatedIndicatorContainer); panelBox.insert_child_at_index(associatedIndicatorContainer, -1); - } else { - panelBox.insert_child_at_index(associatedIndicatorContainer, i); } // Hide the indicator container... @@ -172,6 +166,24 @@ export default class TopBarOrganizerExtension extends Extension { associatedIndicatorContainer.hide(); } } + + // Order the indicator containers by changing their indexes in place. + // `set_child_at_index()` is logically equivalent to removing and + // re-inserting an actor, but unlike `remove_child()` it does not change + // the actor's state or stop its transitions. + // + // For the right box, the first indicator container has to be placed at + // the beginning of the block of accounted-for indicator containers at + // the end of the box. This way unaccounted-for indicator containers + // (e.g. not yet ready app indicators) will be at the left, which is + // preferred, since the box is logically right-to-left. For the left and + // center box, the block starts at index 0. + const firstIndex = box === "right" + ? panelBox.get_n_children() - indicatorContainers.length + : 0; + for (let i = 0; i < indicatorContainers.length; i++) { + panelBox.set_child_at_index(indicatorContainers[i], firstIndex + i); + } // To handle the case, where the box order got set to a permutation // of an outdated box order, it would be wise, if the caller updated the // box order now to include the items present in the top bar.