use gpui::{App, Pixels, Point, Window};
use crate::{
Spring, Theme,
motion::{TransitionId, spring, transition},
};
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct TooltipState {
pub index: usize,
pub cross_line: Point<Pixels>,
pub dots: Vec<Point<Pixels>>,
}
impl TooltipState {
pub fn new(index: usize, cross_line: Point<Pixels>, dots: Vec<Point<Pixels>>) -> Self {
Self {
index,
cross_line,
dots,
}
}
}
#[derive(Clone)]
pub struct PlotHover {
state: TooltipState,
progress: f32,
hovered: bool,
}
impl PlotHover {
pub fn state(&self) -> &TooltipState {
&self.state
}
pub fn progress(&self) -> f32 {
self.progress
}
#[deprecated(since = "0.7.0", note = "use `progress`")]
pub fn focus(&self) -> f32 {
self.progress()
}
pub fn is_hovered(&self) -> bool {
self.hovered
}
pub fn is_entering(&self) -> bool {
self.hovered && self.progress == 0.
}
pub fn glide(
&self,
id: impl Into<TransitionId>,
target: Pixels,
window: &mut Window,
cx: &mut App,
) -> Pixels {
let policy = pointer_spring(cx).with_travel(!self.is_entering());
spring(id, target, policy, window, cx)
}
}
pub fn pointer_spring(cx: &App) -> Spring {
Theme::global(cx).plot.motion().pointer()
}
struct HoverMemory {
state: Option<TooltipState>,
cursor: Point<Pixels>,
progress: f32,
entering: bool,
}
impl Default for HoverMemory {
fn default() -> Self {
Self {
state: None,
cursor: Point::default(),
progress: 1.,
entering: false,
}
}
}
const HOVER_MEMORY: &str = "__plot-hover";
pub(super) fn track_hover(
live: Option<TooltipState>,
cursor: Option<Point<Pixels>>,
window: &mut Window,
cx: &mut App,
) -> Option<(PlotHover, Point<Pixels>)> {
let hovered = live.is_some();
let memory = window.use_keyed_state(HOVER_MEMORY, cx, |_, _| HoverMemory::default());
let theme = Theme::global(cx);
let motion = theme.plot.motion();
let policy = if hovered {
motion.enter().clone()
} else {
motion.exit().clone()
};
let progress = transition(
(HOVER_MEMORY, "progress"),
if hovered { 1. } else { 0. },
policy,
window,
cx,
);
memory.update(cx, |memory, _| {
if let (Some(live), Some(cursor)) = (live, cursor) {
memory.state = Some(live);
memory.cursor = cursor;
}
memory.progress = progress;
memory.entering = hovered && progress == 0.;
if !hovered && progress <= 0. {
memory.state = None;
}
});
let memory = memory.read(cx);
let state = memory.state.clone()?;
Some((
PlotHover {
state,
progress,
hovered,
},
memory.cursor,
))
}
pub fn hover_progress(window: &mut Window, cx: &mut App) -> f32 {
window
.use_keyed_state(HOVER_MEMORY, cx, |_, _| HoverMemory::default())
.read(cx)
.progress
}
pub fn is_hover_entering(window: &mut Window, cx: &mut App) -> bool {
window
.use_keyed_state(HOVER_MEMORY, cx, |_, _| HoverMemory::default())
.read(cx)
.entering
}
#[cfg(test)]
mod tests {
use gpui::{point, px};
use super::*;
#[test]
fn test_plot_hover_readers() {
let state = TooltipState::new(2, point(px(10.), px(20.)), vec![]);
let hover = PlotHover {
state,
progress: 1.,
hovered: true,
};
assert_eq!(hover.state().index, 2);
assert!(hover.is_hovered());
assert!(!hover.is_entering());
let entering = PlotHover {
progress: 0.,
..hover.clone()
};
assert!(entering.is_entering());
let lingering = PlotHover {
progress: 0.4,
hovered: false,
..hover
};
assert!(!lingering.is_hovered());
assert!(!lingering.is_entering());
}
}