use std::{cell::Cell, rc::Rc};
use gpui::{
AnyElement, App, Bounds, Element, ElementId, GlobalElementId, Hitbox, HitboxBehavior,
InspectorElementId, IntoElement, LayoutId, LongPressEvent, MouseMoveEvent, Pixels, Point, Size,
Style, TouchPhase, Window,
};
use super::{Plot, hover::track_hover};
pub struct PlotElement<P>(P);
impl<P: Plot + 'static> PlotElement<P> {
pub fn new(plot: P) -> Self {
Self(plot)
}
fn tooltip_cursor(
global_id: &GlobalElementId,
window: &mut Window,
) -> Rc<Cell<Option<Point<Pixels>>>> {
window.with_element_state(global_id, |prev, _| {
let cell: Rc<Cell<Option<Point<Pixels>>>> = prev.unwrap_or_default();
(cell.clone(), cell)
})
}
}
impl<P: Plot + 'static> IntoElement for PlotElement<P> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl<P: Plot + 'static> Element for PlotElement<P> {
type RequestLayoutState = ();
type PrepaintState = (Option<Hitbox>, Vec<AnyElement>, Option<AnyElement>);
fn id(&self) -> Option<ElementId> {
self.0.id()
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
let style = Style {
size: Size::full(),
..Default::default()
};
(window.request_layout(style, None, cx), ())
}
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState {
let children = self.0.prepaint(bounds, window, cx);
let Some(global_id) = global_id else {
return (None, children, None);
};
let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
let cursor = Self::tooltip_cursor(global_id, window)
.get()
.map(|_| window.mouse_position())
.filter(|mouse| bounds.contains(mouse))
.map(|mouse| mouse - bounds.origin);
let live = cursor.and_then(|position| self.0.tooltip_state(position, bounds, cx));
let hover = track_hover(live, cursor, window, cx);
self.0
.hover(hover.as_ref().map(|(hover, _)| hover), window, cx);
let overlay = hover.and_then(|(hover, cursor)| {
let mut overlay = self.0.tooltip(hover.state(), cursor, bounds, window, cx)?;
overlay.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
Some(overlay)
});
(Some(hitbox), children, overlay)
}
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
self.0.paint(bounds, window, cx);
let (hitbox, children, overlay) = prepaint;
for child in children.iter_mut() {
child.paint(window, cx);
}
if let (Some(global_id), Some(hitbox)) = (global_id, hitbox.as_ref()) {
let cell = Self::tooltip_cursor(global_id, window);
let hitbox = hitbox.clone();
let view = window.current_view();
if cfg!(any(target_os = "ios", target_os = "android")) {
window.on_mouse_event(
move |e: &LongPressEvent, phase, window: &mut Window, cx: &mut App| {
if !phase.bubble() {
return;
}
let next = match e.phase {
TouchPhase::Started => {
if window.default_prevented() || !hitbox.is_hovered(window) {
return;
}
window.prevent_default();
Some(e.start_position - bounds.origin)
}
TouchPhase::Moved => {
if cell.get().is_none() {
return;
}
Some(e.position - bounds.origin)
}
TouchPhase::Ended | TouchPhase::Cancelled => None,
};
if cell.get() != next {
cell.set(next);
cx.notify(view);
}
},
);
} else {
let next = hitbox
.is_hovered(window)
.then(|| window.mouse_position() - bounds.origin);
if cell.get() != next {
let visibility_changed = cell.get().is_some() != next.is_some();
cell.set(next);
if visibility_changed {
window.request_animation_frame();
}
}
window.on_mouse_event(
move |e: &MouseMoveEvent, _, window: &mut Window, cx: &mut App| {
let next = hitbox
.is_hovered(window)
.then(|| e.position - bounds.origin);
if cell.get() != next {
cell.set(next);
cx.notify(view);
}
},
);
}
}
if let Some(overlay) = overlay.as_mut() {
overlay.paint(window, cx);
}
}
}