From 1b523eab632d552ba71b8e30e4a9dd53b6ac476b Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 26 Aug 2026 11:45:18 +0800 Subject: [PATCH] workspaces: derive trail width only from a live button; wire the launcher hotkey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes. The trail pill still grew when spamming between workspace 1 and an empty 6. Switching to an empty workspace makes Hyprland create and destroy it, which rebuilds the button row mid-animation and leaves the source button detached — button_geom then returned None and the fallback handed the wide mid-stretch span straight back in, reintroducing exactly the accumulation the previous commit removed. Width now falls back to the destination button, which is always live, and no path returns the live width any more. Separately, AppInput::OpenLauncher's local arm only called grab_focus(). Focus stopped opening the capsule when connect_enter's open_fn() call was removed (that call was what opened the capsule during window construction), so the keybind focused the entry and left the drawer shut. It now invokes launcher_open_fn, which was added for this and never read — the dead-code warning on that field is what surfaced it. --- src/bar/workspaces.rs | 24 +++++++++++++++++++----- src/main.rs | 7 +++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/bar/workspaces.rs b/src/bar/workspaces.rs index 8bdf799..ef2d833 100644 --- a/src/bar/workspaces.rs +++ b/src/bar/workspaces.rs @@ -369,7 +369,7 @@ impl WorkspaceTrail { pub fn stretch(&self, from: Option<>k4::Button>, to: >k4::Button) { self.cancel(); - let Some(from_g) = self.from_geom(from) else { + let Some(from_g) = self.from_geom(from, to) else { self.place(to); return; }; @@ -411,7 +411,7 @@ impl WorkspaceTrail { self.inner.borrow_mut().tick = Some(id); } - fn from_geom(&self, from: Option<>k4::Button>) -> Option { + fn from_geom(&self, from: Option<>k4::Button>, to: >k4::Button) -> Option { let st = self.inner.borrow(); let live = if self.pill.is_visible() && st.geom.w > 0.5 { Some(st.geom) @@ -420,7 +420,17 @@ impl WorkspaceTrail { }; drop(st); - let natural = from.and_then(|b| button_geom(b, &self.host).map(inset_pill)); + // Width must always come from a button that is CURRENTLY IN THE ROW. + // Switching to an empty workspace makes Hyprland create and destroy it, + // which rebuilds the button row mid-animation and can leave `from` + // detached — `button_geom` then returns None. Falling back to the live + // geometry there handed the wide mid-stretch span straight back in, + // which is exactly the accumulation this function exists to prevent + // (reproduced by spamming between workspace 1 and an empty 6). The + // destination button is always live, so it is the correct fallback. + let natural = from + .and_then(|b| button_geom(b, &self.host).map(inset_pill)) + .or_else(|| button_geom(to, &self.host).map(inset_pill)); // Continuity without accumulation. // @@ -437,6 +447,10 @@ impl WorkspaceTrail { // 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. + // `natural` is None only if BOTH buttons are detached, in which case + // there is no sane width to animate from and the caller falls back to + // an instant `place()`. There is deliberately no path that returns the + // live width. match (live, natural) { (Some(live), Some(natural)) => Some(Geom { x: live.x, @@ -444,8 +458,8 @@ impl WorkspaceTrail { w: natural.w, h: natural.h, }), - (Some(live), None) if live.w <= MAX_CHIP_W => Some(live), - (_, natural) => natural, + (None, natural) => natural, + (Some(_), None) => None, } } } diff --git a/src/main.rs b/src/main.rs index e78ff10..1e4f77a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1915,6 +1915,13 @@ impl SimpleComponent for App { // below is a silent no-op. self.launcher_entry.set_can_focus(true); self.launcher_entry.grab_focus(); + // ...and then actually OPEN it. Focus alone no longer + // opens the capsule: `connect_enter` used to call + // `open_fn()`, which is precisely what made the capsule + // open itself during window construction, so that path + // was deliberately removed. Without this call the + // keybind focuses the entry and leaves the drawer shut. + (self.launcher_open_fn)(); } } }