1use gpui_ce_motion::{
3 Color, MotionValue, PathCommand, PathError, PathMorphState, PathShape, StrokeReveal, Transition,
4};
5
6use gpui_kit::{
7 App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
8 Path, PathBuilder, Pixels, Style, Window, point, px, size,
9};
10
11use std::{cell::RefCell, rc::Rc, time::Instant};
12
13struct Data {
14 morph: PathMorphState,
15 epoch: Option<Instant>,
16 reveal: [MotionValue; 3],
17 error: Option<PathError>,
18}
19
20#[derive(Clone)]
23pub struct VectorState(Rc<RefCell<Data>>);
24
25impl VectorState {
26 pub fn new(shape: PathShape) -> Self {
37 Self(Rc::new(RefCell::new(Data {
38 morph: PathMorphState::new(shape),
39 epoch: None,
40 reveal: [
41 MotionValue::new(1.),
42 MotionValue::new(0.),
43 MotionValue::new(1.),
44 ],
45 error: None,
46 })))
47 }
48
49 pub fn error(&self) -> Option<PathError> {
51 self.0.borrow().error
52 }
53
54 pub fn element(
57 &self,
58 id: impl Into<ElementId>,
59 target: PathShape,
60 bounds: [f32; 2],
61 color: Color,
62 ) -> Result<VectorElement, PathError> {
63 self.0.borrow().morph.validate(&target)?;
64
65 if !bounds.iter().all(|v| v.is_finite() && *v >= 0.) {
66 return Err(PathError::Geometry);
67 }
68
69 Ok(VectorElement {
70 id: id.into(),
71 state: self.clone(),
72 target,
73 bounds,
74 color,
75 stroke: Some(2.),
76 transition: Transition::default(),
77 reveal: StrokeReveal::default(),
78 })
79 }
80}
81
82pub struct VectorElement {
85 id: ElementId,
86 state: VectorState,
87 target: PathShape,
88 bounds: [f32; 2],
89 color: Color,
90 stroke: Option<f32>,
91 transition: Transition,
92 reveal: StrokeReveal,
93}
94
95impl VectorElement {
96 pub fn transition(mut self, transition: Transition) -> Self {
98 self.transition = transition;
99
100 self
101 }
102
103 pub fn fill(mut self) -> Self {
105 self.stroke = None;
106
107 self
108 }
109
110 pub fn stroke(mut self, width: f32) -> Self {
112 assert!(width.is_finite() && width > 0.);
113 self.stroke = Some(width);
114
115 self
116 }
117
118 pub fn reveal(mut self, reveal: StrokeReveal) -> Self {
121 self.reveal = reveal;
122
123 self
124 }
125}
126
127impl IntoElement for VectorElement {
128 type Element = Self;
129
130 fn into_element(self) -> Self {
131 self
132 }
133}
134
135impl Element for VectorElement {
136 type RequestLayoutState = ();
137 type PrepaintState = Option<Path<Pixels>>;
138
139 fn id(&self) -> Option<ElementId> {
140 Some(self.id.clone())
141 }
142
143 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
144 None
145 }
146
147 fn request_layout(
148 &mut self,
149 _: Option<&GlobalElementId>,
150 _: Option<&InspectorElementId>,
151 w: &mut Window,
152 cx: &mut App,
153 ) -> (LayoutId, ()) {
154 let style = Style {
155 size: size(px(self.bounds[0]).into(), px(self.bounds[1]).into()),
156 flex_shrink: 0.,
157 ..Default::default()
158 };
159
160 (w.request_layout(style, [], cx), ())
161 }
162
163 fn prepaint(
164 &mut self,
165 _: Option<&GlobalElementId>,
166 _: Option<&InspectorElementId>,
167 bounds: Bounds<Pixels>,
168 _: &mut (),
169 w: &mut Window,
170 cx: &mut App,
171 ) -> Option<Path<Pixels>> {
172 let mut data = self.state.0.borrow_mut();
173
174 let first = data.epoch.is_none();
175
176 let epoch = *data
177 .epoch
178 .get_or_insert_with(|| cx.background_executor().now());
179
180 let now = cx
181 .background_executor()
182 .now()
183 .saturating_duration_since(epoch);
184
185 let mut reveal = [0.; 3];
186 let mut active = false;
187
188 for (i, value) in data.reveal.iter_mut().enumerate() {
189 if first {
190 value.jump(self.reveal.values()[i], now);
191 }
192
193 value.animate(self.reveal.values()[i], self.transition, now);
194 let frame = value.sample(now, cx.reduce_motion());
195
196 reveal[i] = frame.value();
197 active |= frame.is_animating();
198 }
199
200 let reveal = StrokeReveal::new(reveal[0].clamp(0., 1.), reveal[1], reveal[2].clamp(0., 1.))
202 .unwrap_or(self.reveal);
203
204 let frame = data
205 .morph
206 .sample(&self.target, self.transition, now, cx.reduce_motion())
207 .expect("validated topology");
208
209 active |= frame.is_animating();
210
211 if active {
212 w.request_animation_frame();
213 }
214
215 let point_at = |p: [f64; 2]| bounds.origin + point(px(p[0] as f32), px(p[1] as f32));
216
217 let mut builder = self
218 .stroke
219 .map_or_else(PathBuilder::fill, |width| PathBuilder::stroke(px(width)));
220
221 if self.stroke.is_some() {
222 let mut previous = None;
223
224 frame.stroke_segments(reveal, |a, b| {
225 if previous != Some(a) {
226 builder.move_to(point_at(a));
227 }
228
229 builder.line_to(point_at(b));
230 previous = Some(b);
231 });
232 } else {
233 for &command in frame.commands() {
234 match command {
235 PathCommand::Move(p) => builder.move_to(point_at(p)),
236 PathCommand::Line(p) => builder.line_to(point_at(p)),
237 PathCommand::Quadratic(control, to) => {
238 builder.curve_to(point_at(to), point_at(control))
239 }
240 PathCommand::Cubic(a, b, to) => {
241 builder.cubic_bezier_to(point_at(to), point_at(a), point_at(b))
242 }
243 PathCommand::Close => builder.close(),
244 }
245 }
246 }
247
248 match builder.build() {
249 Ok(path) => {
250 data.error = None;
251
252 Some(path)
253 }
254 Err(_) => {
255 data.error = Some(PathError::Tessellation);
256
257 None
258 }
259 }
260 }
261
262 fn paint(
263 &mut self,
264 _: Option<&GlobalElementId>,
265 _: Option<&InspectorElementId>,
266 _: Bounds<Pixels>,
267 _: &mut (),
268 path: &mut Option<Path<Pixels>>,
269 w: &mut Window,
270 _: &mut App,
271 ) {
272 if let Some(path) = path.take() {
273 let [r, g, b, a] = self.color.channels();
274
275 w.paint_path(path, gpui_kit::Rgba { r, g, b, a });
276 }
277 }
278}