//! Hover color transitions are independent of tab layout and selected state. use super::{Point, Tab, Visual}; use std::{collections::BTreeMap, time::Instant}; #[derive(Default)] struct Fade { from: f32, target: f32, started: Option, } impl Fade { fn sample(&mut self, hovered: bool, now: Instant) -> (f32, bool) { let t = self.started.map_or(1.0, |start| { (now.duration_since(start).as_secs_f32() / 0.16).min(1.0) }); let value = self.from + (self.target - self.from) * (1.0 - (1.0 - t).powi(3)); let target = if hovered { 1.0 } else { 0.0 }; if target != self.target { // Reversing mid-fade starts at the currently visible color. self.from = value; self.target = target; self.started = Some(now); (value, true) } else { (value, t < 1.0) } } } #[derive(Default)] pub(super) struct Hover { tabs: BTreeMap, plus: Fade, } impl Hover { pub fn sample( &mut self, visual: &mut Visual, tabs: &[Tab], pointer: Option, now: Instant, ) -> bool { let hit = pointer.and_then(|point| visual.hit(point)); let (plus, mut animating) = self.plus.sample( pointer.is_some_and(|point| visual.plus.contains(point)), now, ); visual.plus_hover = plus; self.tabs .retain(|id, _| tabs.iter().any(|tab| tab.id == *id)); visual.hovers.clear(); for tab in tabs { let (body, close) = self.tabs.entry(tab.id).or_default(); let (body, body_animating) = body.sample(hit.is_some_and(|hit| hit.0 == tab.id), now); let (close, close_animating) = close.sample(hit == Some((tab.id, true)), now); visual.hovers.push((tab.id, body, close)); animating |= body_animating || close_animating; } animating } }