| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::{ |
| component_dirt::ComponentDirt, |
| core::{Core, CoreContext, Object, ObjectRef, OnAdded, Property}, |
| dyn_vec::DynVec, |
| math::{self, Mat}, |
| node::Node, |
| option_cell::OptionCell, |
| shapes::{ |
| command_path::CommandPathBuilder, CommandPath, CubicVertex, PathVertex, PointsPath, Shape, |
| StraightVertex, |
| }, |
| status_code::StatusCode, |
| Component, TransformComponent, |
| }; |
| |
| #[derive(Debug, Default)] |
| pub struct Path { |
| node: Node, |
| path_flags: Property<u64>, |
| shape: OptionCell<Object<Shape>>, |
| pub(crate) vertices: DynVec<Object<PathVertex>>, |
| command_path: OptionCell<CommandPath>, |
| } |
| |
| impl ObjectRef<'_, Path> { |
| pub fn path_flags(&self) -> u64 { |
| self.path_flags.get() |
| } |
| |
| pub fn set_path_flags(&self, path_flags: u64) { |
| self.path_flags.set(path_flags); |
| } |
| } |
| |
| impl ObjectRef<'_, Path> { |
| pub fn shape(&self) -> Option<Object<Shape>> { |
| self.shape.get() |
| } |
| |
| pub fn transform(&self) -> Mat { |
| if let Some(points_path) = self.try_cast::<PointsPath>() { |
| return points_path.transform(); |
| } |
| |
| self.cast::<TransformComponent>().world_transform() |
| } |
| |
| pub fn push_vertex(&self, path_vertex: Object<PathVertex>) { |
| self.vertices.push(path_vertex); |
| } |
| |
| pub(crate) fn vertices(&self) -> impl Iterator<Item = Object<PathVertex>> + '_ { |
| self.vertices.iter() |
| } |
| |
| pub(crate) fn with_command_path(&self, f: impl FnMut(Option<&CommandPath>)) { |
| self.command_path.with(f); |
| } |
| |
| pub fn mark_path_dirty(&self) { |
| if let Some(points_path) = self.try_cast::<PointsPath>() { |
| points_path.mark_path_dirty(); |
| } |
| |
| self.cast::<Component>().add_dirt(ComponentDirt::PATH, false); |
| |
| if let Some(shape) = self.shape() { |
| shape.as_ref().path_changed(); |
| } |
| } |
| |
| pub fn is_path_closed(&self) -> bool { |
| if let Some(points_path) = self.try_cast::<PointsPath>() { |
| return points_path.is_path_closed(); |
| } |
| |
| true |
| } |
| |
| pub fn build_dependencies(&self) { |
| self.cast::<TransformComponent>().build_dependencies(); |
| } |
| |
| pub fn on_dirty(&self, dirt: ComponentDirt) { |
| if let Some(shape) = self.shape() { |
| if Component::value_has_dirt(dirt, ComponentDirt::WORLD_TRANSFORM) { |
| shape.as_ref().path_changed(); |
| } |
| } |
| } |
| |
| pub fn update(&self, value: ComponentDirt) { |
| self.cast::<TransformComponent>().update(value); |
| |
| if Component::value_has_dirt(value, ComponentDirt::PATH) { |
| self.command_path.set(Some(self.build_path())); |
| } |
| } |
| |
| fn build_path(&self) -> CommandPath { |
| let mut builder = CommandPathBuilder::new(); |
| |
| if self.vertices.len() < 2 { |
| return builder.build(); |
| } |
| |
| enum Dir { |
| In, |
| Out, |
| } |
| |
| fn path_vertex_get_or_translation( |
| path_vertex: ObjectRef<'_, PathVertex>, |
| dir: Dir, |
| ) -> math::Vec { |
| path_vertex |
| .try_cast::<CubicVertex>() |
| .map(|cubic| match dir { |
| Dir::In => cubic.render_in(), |
| Dir::Out => cubic.render_out(), |
| }) |
| .unwrap_or_else(|| path_vertex.render_translation()) |
| } |
| |
| let first_point = self.vertices.index(0); |
| |
| let mut out; |
| let mut prev_is_cubic; |
| |
| let start; |
| let start_in; |
| let start_is_cubic; |
| |
| if let Some(cubic) = first_point.try_cast::<CubicVertex>() { |
| let cubic = cubic.as_ref(); |
| prev_is_cubic = true; |
| start_is_cubic = true; |
| |
| start_in = cubic.render_in(); |
| out = cubic.render_out(); |
| start = cubic.cast::<PathVertex>().render_translation(); |
| |
| builder.move_to(start); |
| } else { |
| prev_is_cubic = false; |
| start_is_cubic = false; |
| |
| let point = first_point.cast::<StraightVertex>(); |
| let point = point.as_ref(); |
| let radius = point.radius(); |
| |
| if radius > 0.0 { |
| let prev = self.vertices.index(self.vertices.len() - 1); |
| |
| let pos = point.cast::<PathVertex>().render_translation(); |
| let to_prev = path_vertex_get_or_translation(prev.as_ref(), Dir::Out) - pos; |
| let to_prev_length = to_prev.length(); |
| let to_prev = to_prev * to_prev_length.recip(); |
| |
| let next = self.vertices.index(1); |
| let to_next = path_vertex_get_or_translation(next.as_ref(), Dir::In) - pos; |
| let to_next_length = to_next.length(); |
| let to_next = to_next * to_next_length.recip(); |
| |
| let render_radius = to_prev_length.min(to_next_length.min(radius)); |
| |
| let translation = pos + to_prev * render_radius; |
| |
| start = translation; |
| start_in = translation; |
| |
| builder.move_to(translation); |
| |
| let out_point = pos + to_prev * (1.0 - math::CIRCLE_CONSTANT) * render_radius; |
| let in_point = pos + to_next * (1.0 - math::CIRCLE_CONSTANT) * render_radius; |
| |
| out = pos + to_next * render_radius; |
| |
| builder.cubic_to(out_point, in_point, out); |
| } else { |
| let translation = point.cast::<PathVertex>().render_translation(); |
| |
| start = translation; |
| start_in = translation; |
| out = translation; |
| |
| builder.move_to(translation); |
| } |
| } |
| |
| for (i, vertex) in self.vertices.iter().enumerate().skip(1) { |
| let vertex = vertex.as_ref(); |
| |
| if let Some(cubic) = vertex.try_cast::<CubicVertex>() { |
| let in_point = cubic.render_in(); |
| let translation = vertex.render_translation(); |
| |
| builder.cubic_to(out, in_point, translation); |
| |
| prev_is_cubic = true; |
| out = cubic.render_out(); |
| } else { |
| let point = vertex.cast::<StraightVertex>(); |
| let pos = vertex.render_translation(); |
| let radius = point.radius(); |
| |
| if radius > 0.0 { |
| let to_prev = out - pos; |
| let to_prev_length = to_prev.length(); |
| let to_prev = to_prev * to_prev_length.recip(); |
| |
| let next = self.vertices.index((i + 1) % self.vertices.len()); |
| let to_next = path_vertex_get_or_translation(next.as_ref(), Dir::In) - pos; |
| let to_next_length = to_next.length(); |
| let to_next = to_next * to_next_length.recip(); |
| |
| let render_radius = to_prev_length.min(to_next_length.min(radius)); |
| |
| let translation = pos + to_prev * render_radius; |
| |
| if prev_is_cubic { |
| builder.cubic_to(out, translation, translation); |
| } else { |
| builder.line_to(translation); |
| } |
| |
| let out_point = pos + to_prev * (1.0 - math::CIRCLE_CONSTANT) * render_radius; |
| let in_point = pos + to_next * (1.0 - math::CIRCLE_CONSTANT) * render_radius; |
| |
| out = pos + to_next * render_radius; |
| |
| builder.cubic_to(out_point, in_point, out); |
| |
| prev_is_cubic = false; |
| } else if prev_is_cubic { |
| builder.cubic_to(out, pos, pos); |
| |
| prev_is_cubic = false; |
| out = pos; |
| } else { |
| builder.line_to(pos); |
| |
| out = pos; |
| } |
| } |
| } |
| |
| if self.is_path_closed() { |
| if prev_is_cubic || start_is_cubic { |
| builder.cubic_to(out, start_in, start); |
| } |
| |
| builder.close(); |
| } |
| |
| builder.build() |
| } |
| } |
| |
| impl Core for Path { |
| parent_types![(node, Node)]; |
| |
| properties![(128, path_flags, set_path_flags), node]; |
| } |
| |
| impl OnAdded for ObjectRef<'_, Path> { |
| on_added!([on_added_dirty, import], Node); |
| |
| fn on_added_clean(&self, context: &dyn CoreContext) -> StatusCode { |
| let code = self.cast::<Node>().on_added_clean(context); |
| if code != StatusCode::Ok { |
| return code; |
| } |
| |
| for parent in self.cast::<Component>().parents() { |
| if let Some(shape) = parent.try_cast::<Shape>() { |
| self.shape.set(Some(shape.clone())); |
| shape.as_ref().push_path(self.as_object()); |
| return StatusCode::Ok; |
| } |
| } |
| |
| StatusCode::MissingObject |
| } |
| } |