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