From 42d5acb736b8fde23e69b35bc4921edb790a9afc Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 26 Aug 2026 11:40:52 +0800 Subject: [PATCH] workspaces: stop the trail pill growing when switches are spammed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Interrupting an in-flight stretch fed the pill's CURRENT geometry back in as the next animation's starting point. Mid-stretch that geometry deliberately spans both the old and new buttons, and ease_overshoot (c = 1.4) pushes it wider still past the target — so each interrupted switch began wider than the last and the pill grew until it hit MAX_CHIP_W. That cap was masking the compounding rather than preventing it, which is why the bug survived an earlier fix that only tightened the cap. from_geom now takes POSITION from the live geometry, so rapid switches still read as one continuous movement, and WIDTH from the source button's natural size, so width can no longer compound across interruptions. --- src/bar/workspaces.rs | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/bar/workspaces.rs b/src/bar/workspaces.rs index 86d8268..8bdf799 100644 --- a/src/bar/workspaces.rs +++ b/src/bar/workspaces.rs @@ -413,13 +413,40 @@ impl WorkspaceTrail { fn from_geom(&self, from: Option<>k4::Button>) -> Option { let st = self.inner.borrow(); - // A leftover mid-stretch can be as wide as the whole row — never - // treat that as the start of the next animation. - if self.pill.is_visible() && st.geom.w > 0.5 && st.geom.w <= MAX_CHIP_W { - return Some(st.geom); - } + let live = if self.pill.is_visible() && st.geom.w > 0.5 { + Some(st.geom) + } else { + None + }; drop(st); - from.and_then(|b| button_geom(b, &self.host).map(inset_pill)) + + let natural = from.and_then(|b| button_geom(b, &self.host).map(inset_pill)); + + // Continuity without accumulation. + // + // Interrupting an in-flight stretch should carry the pill's CURRENT + // POSITION into the next animation, so a rapid sequence of switches + // reads as one continuous movement. It must not carry the current + // WIDTH: mid-stretch the pill deliberately spans both the old and new + // buttons, and `ease_overshoot` (c = 1.4) pushes it wider still past + // the target. Feeding that span back in as the next `from` made each + // interrupted switch start wider than the last, so spamming workspace + // switches grew the pill until it hit MAX_CHIP_W — which only capped + // the runaway, it never stopped the compounding. + // + // Taking position from the live geometry and width from the source + // button's natural size keeps the motion continuous while making width + // a pure function of which button we started from. + match (live, natural) { + (Some(live), Some(natural)) => Some(Geom { + x: live.x, + y: live.y, + w: natural.w, + h: natural.h, + }), + (Some(live), None) if live.w <= MAX_CHIP_W => Some(live), + (_, natural) => natural, + } } }