yoda: fisheye magnification for dock hover (phase B v2 / c)

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.
This commit is contained in:
Lionel DARNIS 2026-04-24 13:13:52 +02:00 committed by Lionel DARNIS
parent d090e60370
commit 8fc11581ad

View file

@ -688,15 +688,62 @@ impl CosmicAppList {
.collect::<Vec<_>>();
}
/// 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 {