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, PlotAppear, appear::track_appear, hover::track_hover};
12
13pub struct PlotElement<P>(P);
31
32impl<P: Plot + 'static> PlotElement<P> {
33 pub fn new(plot: P) -> Self {
34 Self(plot)
35 }
36
37 fn tooltip_cursor(
39 global_id: &GlobalElementId,
40 window: &mut Window,
41 ) -> Rc<Cell<Option<Point<Pixels>>>> {
42 window.with_element_state(global_id, |prev, _| {
43 let cell: Rc<Cell<Option<Point<Pixels>>>> = prev.unwrap_or_default();
44 (cell.clone(), cell)
45 })
46 }
47}
48
49impl<P: Plot + 'static> IntoElement for PlotElement<P> {
50 type Element = Self;
51
52 fn into_element(self) -> Self::Element {
53 self
54 }
55}
56
57impl<P: Plot + 'static> Element for PlotElement<P> {
58 type RequestLayoutState = ();
59 type PrepaintState = (Option<Hitbox>, Vec<AnyElement>, Option<AnyElement>);
62
63 fn id(&self) -> Option<ElementId> {
64 self.0.id()
67 }
68
69 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
70 None
71 }
72
73 fn request_layout(
74 &mut self,
75 _: Option<&GlobalElementId>,
76 _: Option<&InspectorElementId>,
77 window: &mut Window,
78 cx: &mut App,
79 ) -> (LayoutId, Self::RequestLayoutState) {
80 let style = Style {
81 size: Size::full(),
82 ..Default::default()
83 };
84 (window.request_layout(style, None, cx), ())
85 }
86
87 fn prepaint(
88 &mut self,
89 global_id: Option<&GlobalElementId>,
90 _: Option<&InspectorElementId>,
91 bounds: Bounds<Pixels>,
92 _: &mut Self::RequestLayoutState,
93 window: &mut Window,
94 cx: &mut App,
95 ) -> Self::PrepaintState {
96 let children = self.0.prepaint(bounds, window, cx);
99
100 let Some(global_id) = global_id else {
101 return (None, children, None);
102 };
103
104 let appear = match self.0.appear_generation() {
105 Some(generation) => track_appear(global_id, generation, window, cx),
106 None => PlotAppear::complete(),
107 };
108 let appearing = appear.is_appearing();
109 self.0.appear(appear, window, cx);
110
111 if !self.0.interactive() {
112 return (None, children, None);
113 }
114
115 let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
117
118 let cursor = Self::tooltip_cursor(global_id, window)
121 .get()
122 .map(|_| window.mouse_position())
123 .filter(|mouse| bounds.contains(mouse))
124 .map(|mouse| mouse - bounds.origin);
125 let live = cursor
128 .filter(|_| !appearing)
129 .and_then(|position| self.0.tooltip_state(position, bounds, cx));
130
131 let hover = track_hover(live, cursor, window, cx);
133 self.0
134 .hover(hover.as_ref().map(|(hover, _)| hover), window, cx);
135
136 let overlay = hover.and_then(|(hover, cursor)| {
137 let mut overlay = self.0.tooltip(hover.state(), cursor, bounds, window, cx)?;
138 overlay.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
139 Some(overlay)
140 });
141
142 (Some(hitbox), children, overlay)
143 }
144
145 fn paint(
146 &mut self,
147 global_id: Option<&GlobalElementId>,
148 _: Option<&InspectorElementId>,
149 bounds: Bounds<Pixels>,
150 _: &mut Self::RequestLayoutState,
151 prepaint: &mut Self::PrepaintState,
152 window: &mut Window,
153 cx: &mut App,
154 ) {
155 self.0.paint(bounds, window, cx);
156
157 let (hitbox, children, overlay) = prepaint;
158 for child in children.iter_mut() {
159 child.paint(window, cx);
160 }
161
162 if let (Some(global_id), Some(hitbox)) = (global_id, hitbox.as_ref()) {
163 let cell = Self::tooltip_cursor(global_id, window);
164 let hitbox = hitbox.clone();
165 let view = window.current_view();
168
169 if cfg!(any(target_os = "ios", target_os = "android")) {
170 window.on_mouse_event(
173 move |e: &LongPressEvent, phase, window: &mut Window, cx: &mut App| {
174 if !phase.bubble() {
175 return;
176 }
177 let next = match e.phase {
178 TouchPhase::Started => {
179 if window.default_prevented() || !hitbox.is_hovered(window) {
180 return;
181 }
182 window.prevent_default();
183 Some(e.start_position - bounds.origin)
184 }
185 TouchPhase::Moved => {
186 if cell.get().is_none() {
187 return;
188 }
189 Some(e.position - bounds.origin)
190 }
191 TouchPhase::Ended | TouchPhase::Cancelled => None,
192 };
193 if cell.get() != next {
194 cell.set(next);
195 cx.notify(view);
196 }
197 },
198 );
199 } else {
200 let next = hitbox
204 .is_hovered(window)
205 .then(|| window.mouse_position() - bounds.origin);
206 if cell.get() != next {
207 let visibility_changed = cell.get().is_some() != next.is_some();
208 cell.set(next);
209 if visibility_changed {
210 window.request_animation_frame();
211 }
212 }
213
214 window.on_mouse_event(
215 move |e: &MouseMoveEvent, _, window: &mut Window, cx: &mut App| {
216 let next = hitbox
217 .is_hovered(window)
218 .then(|| e.position - bounds.origin);
219 if cell.get() != next {
220 cell.set(next);
221 cx.notify(view);
222 }
223 },
224 );
225 }
226 }
227
228 if let Some(overlay) = overlay.as_mut() {
230 overlay.paint(window, cx);
231 }
232 }
233}