use crate::{
Color, MotionValue, PathCommand, PathError, PathMorphState, PathShape, StrokeReveal, Transition,
};
use gpui::{
App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
Path, PathBuilder, Pixels, Style, Window, point, px, size,
};
use std::{cell::RefCell, rc::Rc, time::Instant};
struct Data {
morph: PathMorphState,
epoch: Option<Instant>,
reveal: [MotionValue; 3],
error: Option<PathError>,
}
#[derive(Clone)]
pub struct VectorState(Rc<RefCell<Data>>);
impl VectorState {
pub fn new(shape: PathShape) -> Self {
Self(Rc::new(RefCell::new(Data {
morph: PathMorphState::new(shape),
epoch: None,
reveal: [
MotionValue::new(1.),
MotionValue::new(0.),
MotionValue::new(1.),
],
error: None,
})))
}
pub fn error(&self) -> Option<PathError> {
self.0.borrow().error
}
pub fn element(
&self,
id: impl Into<ElementId>,
target: PathShape,
bounds: [f32; 2],
color: Color,
) -> Result<VectorElement, PathError> {
self.0.borrow().morph.validate(&target)?;
if !bounds.iter().all(|v| v.is_finite() && *v >= 0.) {
return Err(PathError::Geometry);
}
Ok(VectorElement {
id: id.into(),
state: self.clone(),
target,
bounds,
color,
stroke: Some(2.),
transition: Transition::default(),
reveal: StrokeReveal::default(),
})
}
}
pub struct VectorElement {
id: ElementId,
state: VectorState,
target: PathShape,
bounds: [f32; 2],
color: Color,
stroke: Option<f32>,
transition: Transition,
reveal: StrokeReveal,
}
impl VectorElement {
pub fn transition(mut self, transition: Transition) -> Self {
self.transition = transition;
self
}
pub fn fill(mut self) -> Self {
self.stroke = None;
self
}
pub fn stroke(mut self, width: f32) -> Self {
assert!(width.is_finite() && width > 0.);
self.stroke = Some(width);
self
}
pub fn reveal(mut self, reveal: StrokeReveal) -> Self {
self.reveal = reveal;
self
}
}
impl IntoElement for VectorElement {
type Element = Self;
fn into_element(self) -> Self {
self
}
}
impl Element for VectorElement {
type RequestLayoutState = ();
type PrepaintState = Option<Path<Pixels>>;
fn id(&self) -> Option<ElementId> {
Some(self.id.clone())
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
w: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) {
let style = Style {
size: size(px(self.bounds[0]).into(), px(self.bounds[1]).into()),
flex_shrink: 0.,
..Default::default()
};
(w.request_layout(style, [], cx), ())
}
fn prepaint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut (),
w: &mut Window,
cx: &mut App,
) -> Option<Path<Pixels>> {
let mut data = self.state.0.borrow_mut();
let epoch = *data
.epoch
.get_or_insert_with(|| cx.background_executor().now());
let now = cx
.background_executor()
.now()
.saturating_duration_since(epoch);
let mut reveal = [0.; 3];
let mut active = false;
for (i, value) in data.reveal.iter_mut().enumerate() {
value.animate(self.reveal.values()[i], self.transition, now);
let frame = value.sample(now, cx.reduce_motion());
reveal[i] = frame.value();
active |= frame.is_animating();
}
let reveal = StrokeReveal::new(reveal[0].clamp(0., 1.), reveal[1], reveal[2].clamp(0., 1.))
.unwrap_or(self.reveal);
let frame = data
.morph
.sample(&self.target, self.transition, now, cx.reduce_motion())
.expect("validated topology");
active |= frame.is_animating();
if active {
w.request_animation_frame();
}
let point_at = |p: [f64; 2]| bounds.origin + point(px(p[0] as f32), px(p[1] as f32));
let mut builder = self
.stroke
.map_or_else(PathBuilder::fill, |width| PathBuilder::stroke(px(width)));
if self.stroke.is_some() {
let mut previous = None;
frame.stroke_segments(reveal, |a, b| {
if previous != Some(a) {
builder.move_to(point_at(a));
}
builder.line_to(point_at(b));
previous = Some(b);
});
} else {
for &command in frame.commands() {
match command {
PathCommand::Move(p) => builder.move_to(point_at(p)),
PathCommand::Line(p) => builder.line_to(point_at(p)),
PathCommand::Quadratic(control, to) => {
builder.curve_to(point_at(to), point_at(control))
}
PathCommand::Cubic(a, b, to) => {
builder.cubic_bezier_to(point_at(to), point_at(a), point_at(b))
}
PathCommand::Close => builder.close(),
}
}
}
match builder.build() {
Ok(path) => {
data.error = None;
Some(path)
}
Err(_) => {
data.error = Some(PathError::Tessellation);
None
}
}
}
fn paint(
&mut self,
_: Option<&GlobalElementId>,
_: Option<&InspectorElementId>,
_: Bounds<Pixels>,
_: &mut (),
path: &mut Option<Path<Pixels>>,
w: &mut Window,
_: &mut App,
) {
if let Some(path) = path.take() {
w.paint_path(path, self.color.to_rgba());
}
}
}