From 8fc11581ad3c8ebf2cb7cb51fd1b1e73d19fb61c Mon Sep 17 00:00:00 2001 From: leyoda Date: Fri, 24 Apr 2026 13:13:52 +0200 Subject: [PATCH] yoda: fisheye magnification for dock hover (phase B v2 / c) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the binary 1.3× hover with a true gaussian bell curve — the hovered icon still peaks at ~1.35×, but the ±1 neighbours also bulge noticeably, ±2 a bit, and ±3+ relax to 1.0×. Footprint ~5 icons wide, matching the macOS Dock fisheye feel. Implementation in fn icon_scale_for(id): - Reads the hovered icon's and the current icon's bounds from self.rectangles (already populated by the existing RectangleTracker subscription — no new plumbing). - Distance = |this_center - hovered_center| along the panel's long axis (horizontal for Top/Bottom anchors, vertical for Left/Right). - sigma = hovered_extent * 1.4 so the bell's half-width matches one icon width (neighbors clearly pulled, far icons untouched). - scale = 1.0 + PEAK * exp(-(d/sigma)²) with PEAK = 0.35. - Falls back to binary 1.35×/1.0× when rectangle data isn't populated yet (first render / resize) — visibly responsive even before the tracker catches up. No widget signature changes vs v1, just a smarter formula. All five as_icon call sites already pass the result of icon_scale_for so this update propagates everywhere. Still on the TODO list: smooth animation (b). Right now icon→icon transitions snap instantly; a smoothed_hover_center + tick subscription would lerp it. Deferred to a follow-up commit. --- cosmic-app-list/src/app.rs | 59 ++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/cosmic-app-list/src/app.rs b/cosmic-app-list/src/app.rs index a6b967f7..7fd4337f 100755 --- a/cosmic-app-list/src/app.rs +++ b/cosmic-app-list/src/app.rs @@ -688,15 +688,62 @@ impl CosmicAppList { .collect::>(); } - /// Yoda: macOS-Tahoe-style hover magnification. Returns the icon - /// size multiplier for a given dock item — 1.3× when the pointer - /// is over it, 1.0× otherwise. Called from the view() icon builders. + /// Yoda: macOS-Tahoe fisheye-style magnification. Returns the + /// per-icon size multiplier based on the distance (in pixels) from + /// the currently hovered icon's center to this icon's center. + /// + /// Uses a gaussian bell curve so the hovered icon peaks at + /// 1.0 + PEAK, immediate neighbors still bulge noticeably, and icons + /// further away relax back to 1.0× — that's the smooth neighbour + /// deformation people associate with the macOS Dock. + /// + /// Falls back to binary 1.3×/1.0× when the rectangle tracker hasn't + /// populated yet (first render, or just after layout changes). fn icon_scale_for(&self, id: &DockItemId) -> f32 { - if self.hovered_dock_item.as_ref() == Some(id) { - 1.3 + const PEAK: f32 = 0.35; + // sigma expressed in multiples of the hovered icon's size — + // 1.4 means the ±1 neighbors sit ~0.7σ away and still bulge + // visibly, while ±3+ has collapsed to ~1.0× (fisheye footprint + // close to 5 icons wide, Tahoe-ish). + const SIGMA_FACTOR: f32 = 1.4; + + let Some(hovered_id) = self.hovered_dock_item.as_ref() else { + return 1.0; + }; + + // Without tracker data we can't compute distance — still peak on + // the hovered one so something visibly responds immediately. + let (Some(hovered_rect), Some(this_rect)) = + (self.rectangles.get(hovered_id), self.rectangles.get(id)) + else { + return if id == hovered_id { 1.0 + PEAK } else { 1.0 }; + }; + + let is_horizontal = matches!( + self.core.applet.anchor, + PanelAnchor::Top | PanelAnchor::Bottom + ); + let hovered_center = if is_horizontal { + hovered_rect.x + hovered_rect.width / 2.0 } else { - 1.0 + hovered_rect.y + hovered_rect.height / 2.0 + }; + let this_center = if is_horizontal { + this_rect.x + this_rect.width / 2.0 + } else { + this_rect.y + this_rect.height / 2.0 + }; + let distance = (this_center - hovered_center).abs(); + let icon_extent = if is_horizontal { + hovered_rect.width + } else { + hovered_rect.height } + .max(1.0); + let sigma = icon_extent * SIGMA_FACTOR; + // exp(-t²) bell curve, t = distance / sigma + let t = distance / sigma; + 1.0 + PEAK * (-t * t).exp() } fn is_on_current_monitor_and_workspace(&self, toplevel_info: &ToplevelInfo) -> bool {