1use std::{cell::Cell, rc::Rc};
4
5use gpui::{
6 AnyElement, App, Bounds, Element, ElementId, GlobalElementId, Hitbox, HitboxBehavior,
7 InspectorElementId, IntoElement, LayoutId, LongPressEvent, MouseMoveEvent, Pixels, Point, Size,
8 Style, TouchPhase, Window,
9};
10
11use super::{Plot, hover::track_hover};
12
13pub struct PlotElement<P>(P);
30
31impl<P: Plot + 'static> PlotElement<P> {
32 pub fn new(plot: P) -> Self {
33 Self(plot)
34 }
35
36 fn tooltip_cursor(
38 global_id: &GlobalElementId,
39 window: &mut Window,
40 ) -> Rc<Cell<Option<Point<Pixels>>>> {
41 window.with_element_state(global_id, |prev, _| {
42 let cell: Rc<Cell<Option<Point<Pixels>>>> = prev.unwrap_or_default();
43 (cell.clone(), cell)
44 })
45 }
46}
47
48impl<P: Plot + 'static> IntoElement for PlotElement<P> {
49 type Element = Self;
50
51 fn into_element(self) -> Self::Element {
52 self
53 }
54}
55
56impl<P: Plot + 'static> Element for PlotElement<P> {
57 type RequestLayoutState = ();
58 type PrepaintState = (Option<Hitbox>, Vec<AnyElement>, Option<AnyElement>);
61
62 fn id(&self) -> Option<ElementId> {
63 self.0.id()
65 }
66
67 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
68 None
69 }
70
71 fn request_layout(
72 &mut self,
73 _: Option<&GlobalElementId>,
74 _: Option<&InspectorElementId>,
75 window: &mut Window,
76 cx: &mut App,
77 ) -> (LayoutId, Self::RequestLayoutState) {
78 let style = Style {
79 size: Size::full(),
80 ..Default::default()
81 };
82 (window.request_layout(style, None, cx), ())
83 }
84
85 fn prepaint(
86 &mut self,
87 global_id: Option<&GlobalElementId>,
88 _: Option<&InspectorElementId>,
89 bounds: Bounds<Pixels>,
90 _: &mut Self::RequestLayoutState,
91 window: &mut Window,
92 cx: &mut App,
93 ) -> Self::PrepaintState {
94 let children = self.0.prepaint(bounds, window, cx);
97
98 let Some(global_id) = global_id else {
99 return (None, children, None);
100 };
101
102 let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
104
105 let cursor = Self::tooltip_cursor(global_id, window)
108 .get()
109 .map(|_| window.mouse_position())
110 .filter(|mouse| bounds.contains(mouse))
111 .map(|mouse| mouse - bounds.origin);
112 let live = cursor.and_then(|position| self.0.tooltip_state(position, bounds, cx));
113
114 let hover = track_hover(live, cursor, window, cx);
116 self.0
117 .hover(hover.as_ref().map(|(hover, _)| hover), window, cx);
118
119 let overlay = hover.and_then(|(hover, cursor)| {
120 let mut overlay = self.0.tooltip(hover.state(), cursor, bounds, window, cx)?;
121 overlay.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
122 Some(overlay)
123 });
124
125 (Some(hitbox), children, overlay)
126 }
127
128 fn paint(
129 &mut self,
130 global_id: Option<&GlobalElementId>,
131 _: Option<&InspectorElementId>,
132 bounds: Bounds<Pixels>,
133 _: &mut Self::RequestLayoutState,
134 prepaint: &mut Self::PrepaintState,
135 window: &mut Window,
136 cx: &mut App,
137 ) {
138 self.0.paint(bounds, window, cx);
139
140 let (hitbox, children, overlay) = prepaint;
141 for child in children.iter_mut() {
142 child.paint(window, cx);
143 }
144
145 if let (Some(global_id), Some(hitbox)) = (global_id, hitbox.as_ref()) {
146 let cell = Self::tooltip_cursor(global_id, window);
147 let hitbox = hitbox.clone();
148 let view = window.current_view();
151
152 if cfg!(any(target_os = "ios", target_os = "android")) {
153 window.on_mouse_event(
156 move |e: &LongPressEvent, phase, window: &mut Window, cx: &mut App| {
157 if !phase.bubble() {
158 return;
159 }
160 let next = match e.phase {
161 TouchPhase::Started => {
162 if window.default_prevented() || !hitbox.is_hovered(window) {
163 return;
164 }
165 window.prevent_default();
166 Some(e.start_position - bounds.origin)
167 }
168 TouchPhase::Moved => {
169 if cell.get().is_none() {
170 return;
171 }
172 Some(e.position - bounds.origin)
173 }
174 TouchPhase::Ended | TouchPhase::Cancelled => None,
175 };
176 if cell.get() != next {
177 cell.set(next);
178 cx.notify(view);
179 }
180 },
181 );
182 } else {
183 let next = hitbox
187 .is_hovered(window)
188 .then(|| window.mouse_position() - bounds.origin);
189 if cell.get() != next {
190 let visibility_changed = cell.get().is_some() != next.is_some();
191 cell.set(next);
192 if visibility_changed {
193 window.request_animation_frame();
194 }
195 }
196
197 window.on_mouse_event(
198 move |e: &MouseMoveEvent, _, window: &mut Window, cx: &mut App| {
199 let next = hitbox
200 .is_hovered(window)
201 .then(|| e.position - bounds.origin);
202 if cell.get() != next {
203 cell.set(next);
204 cx.notify(view);
205 }
206 },
207 );
208 }
209 }
210
211 if let Some(overlay) = overlay.as_mut() {
213 overlay.paint(window, cx);
214 }
215 }
216}