diff --git a/pax-compiler/files/interfaces/web/src/classes/occlusion-context.ts b/pax-compiler/files/interfaces/web/src/classes/occlusion-context.ts index 2ee8f3bfa..468e4bd55 100644 --- a/pax-compiler/files/interfaces/web/src/classes/occlusion-context.ts +++ b/pax-compiler/files/interfaces/web/src/classes/occlusion-context.ts @@ -144,6 +144,7 @@ export class OcclusionLayerManager { throw new Error(`tried to remove container width id ${id} while children still present`); } parent!.removeChild(elem); + this.objectManager.returnToPool(DIV, elem); }) let var_name = containerCssClipPathVar(id); document.documentElement.style.removeProperty(var_name); diff --git a/pax-designer/src/glass/control_point.pax b/pax-designer/src/glass/control_point.pax index 1ff6f06dd..fd57b2ab5 100644 --- a/pax-designer/src/glass/control_point.pax +++ b/pax-designer/src/glass/control_point.pax @@ -2,13 +2,15 @@ @mouse_down=self.mouse_down @double_click=self.double_click > - if !self.data.styling.round { + if !self.style.round { } - if self.data.styling.round { + if self.style.round { } + // for debugging: + // @settings { @@ -20,21 +22,20 @@ anchor_y: {(self.data.anchor_y*100.0)%}, x: {(self.data.point.x)px}, y: {(self.data.point.y)px}, - width: {(self.data.styling.width + self.data.styling.hit_padding)px} - height: {(self.data.styling.height + self.data.styling.hit_padding)px} rotate: {self.applied_rotation} + + width: {(self.style.width + self.style.hit_padding)px} + height: {(self.style.height + self.style.hit_padding)px} } .control_point { stroke: { - color: {self.data.styling.stroke_color} - width: {(self.data.styling.stroke_width_pixels)px} + color: {self.style.stroke_color} + width: {(self.style.stroke_width_pixels)px} } x: 50%, y: 50%, - fill: { self.data.styling.fill_color } - width: {(self.data.styling.width)px} - height: {(self.data.styling.height)px} - anchor_x: {(100.0 - self.data.anchor_x*100.0)%}, - anchor_y: {(100.0 - self.data.anchor_y*100.0)%}, + fill: {self.style.fill_color} + width: {(self.style.width)px} + height: {(self.style.height)px} } } diff --git a/pax-designer/src/glass/control_point.rs b/pax-designer/src/glass/control_point.rs index e452846af..c72d9c507 100644 --- a/pax-designer/src/glass/control_point.rs +++ b/pax-designer/src/glass/control_point.rs @@ -35,12 +35,15 @@ use crate::model::input::Dir; pub struct ControlPoint { pub data: Property, pub ind: Property, + pub styles: Property>, // the transform of the currently selected object pub object_rotation: Property, // private // the transform to be applied to this control point pub applied_rotation: Property, + // derived from styling lookup ind in controlpointdef on mount. + pub style: Property, } #[derive(Clone)] @@ -153,13 +156,23 @@ impl ToolBehavior for ControlPointTool { impl ControlPoint { pub fn on_mount(&mut self, _ctx: &NodeContext) { - let data = self.data.clone(); + // NOTE: styling only applied once at mount, not reactive + let style = self + .styles + .read(|styles| styles[self.data.read(|data| data.styling_lookup_ind)].clone()); + let affected_by_transform = style.affected_by_transform.clone(); + self.style.replace_with(Property::new(style)); + let object_transform = self.object_rotation.clone(); + let data = self.data.clone(); let deps = [data.untyped(), object_transform.untyped()]; self.applied_rotation.replace_with(Property::computed( move || { - if data.get().styling.affected_by_transform { - object_transform.get() + if affected_by_transform { + Rotation::Degrees( + (object_transform.get().get_as_degrees() + data.read(|data| data.rotation)) + .into(), + ) } else { Default::default() } @@ -204,14 +217,16 @@ impl ControlPoint { } pub fn mouse_over(&mut self, ctx: &NodeContext, _event: Event) { - self.data.read(|data| { - model::perform_action( - &SetCursor(DesignerCursor { - cursor_type: data.styling.cursor_type, - rotation_degrees: data.node_local_rotation_degrees, - }), - ctx, - ); + self.style.read(|style| { + self.data.read(|data| { + model::perform_action( + &SetCursor(DesignerCursor { + cursor_type: style.cursor_type, + rotation_degrees: data.cursor_rotation, + }), + ctx, + ); + }) }) } pub fn mouse_out(&mut self, ctx: &NodeContext, _event: Event) { @@ -227,8 +242,11 @@ pub struct ControlPointDef { pub anchor_x: f64, /// 0.0-1.0 pub anchor_y: f64, - pub node_local_rotation_degrees: f64, - pub styling: ControlPointStyling, + /// Node-local rotation in degrees + pub rotation: f64, + // Node-local rotation in degrees + pub cursor_rotation: f64, + pub styling_lookup_ind: usize, } #[pax] diff --git a/pax-designer/src/glass/wireframe_editor.pax b/pax-designer/src/glass/wireframe_editor.pax index dab5ce640..f0f274ccc 100644 --- a/pax-designer/src/glass/wireframe_editor.pax +++ b/pax-designer/src/glass/wireframe_editor.pax @@ -3,6 +3,7 @@ for (data, i) in self.control_points { diff --git a/pax-designer/src/glass/wireframe_editor.rs b/pax-designer/src/glass/wireframe_editor.rs index ff76008aa..4982341e0 100644 --- a/pax-designer/src/glass/wireframe_editor.rs +++ b/pax-designer/src/glass/wireframe_editor.rs @@ -30,6 +30,7 @@ use self::editor_generation::Editor; pub struct WireframeEditor { pub control_points: Property>, pub bounding_segments: Property>, + pub styles: Property>, pub on_selection_changed: Property, pub object_rotation: Property, } @@ -52,6 +53,7 @@ impl WireframeEditor { let control_points = self.control_points.clone(); let bounding_segments = self.bounding_segments.clone(); + let styles = self.styles.clone(); // This is doing _hierarchical_ binding: // whenever the selection ID changes, the transform and bounds of // the editor (among other things) are re-bound to the engine node @@ -64,7 +66,12 @@ impl WireframeEditor { if selected.items.len() > 0 { Self::bind_object_transform(object_rotation.clone(), &selected); let editor = Editor::new(ctx.clone(), selected); - Self::bind_editor(control_points.clone(), bounding_segments.clone(), editor); + Self::bind_editor( + control_points.clone(), + bounding_segments.clone(), + styles.clone(), + editor, + ); } else { control_points.replace_with(Property::new(vec![])); bounding_segments.replace_with(Property::new(vec![])); @@ -84,39 +91,41 @@ impl WireframeEditor { fn bind_editor( control_points: Property>, bounding_segments: Property>, - editor: Property, + styles: Property>, + editor: Editor, ) { - let editorcp = editor.clone(); - let deps = [editor.untyped()]; - + let deps: Vec<_> = editor + .controls + .iter() + .map(|set| set.points.untyped()) + .collect(); + let controls: Vec<_> = editor + .controls + .iter() + .map(|set| set.points.clone()) + .enumerate() + .collect(); + let new_styles: Vec<_> = editor.controls.into_iter().map(|set| set.styling).collect(); + styles.set(new_styles); control_points.replace_with(Property::computed( move || { - let mut control_points = vec![]; - let mut behaviors = vec![]; - for control_set in editorcp.get().controls { - let (control_points_set, behavior_set): (Vec<_>, Vec<_>) = control_set - .points - .into_iter() - .map(|c_point| { - ( - (c_point.point, c_point.rotation, c_point.anchor), - c_point.behavior, - ) - }) - .unzip(); - let control_points_from_set: Vec = control_points_set - .into_iter() - .map(|(p, rot, anchor)| ControlPointDef { - point: p.into(), - node_local_rotation_degrees: rot, - anchor_x: anchor.x, - anchor_y: anchor.y, - styling: control_set.styling.clone(), - }) - .collect(); - control_points.extend(control_points_from_set); - behaviors.extend(behavior_set); - } + let (control_points, behaviors) = controls + .iter() + .flat_map(|(style_index, c)| c.get().into_iter().map(|p| (*style_index, p))) + .map(|(style_index, p)| { + ( + ControlPointDef { + point: p.point.into(), + anchor_x: p.anchor.x, + anchor_y: p.anchor.y, + rotation: p.rotation, + cursor_rotation: p.cursor_rotation, + styling_lookup_ind: style_index, + }, + p.behavior, + ) + }) + .unzip(); CONTROL_POINT_FUNCS.with_borrow_mut(|funcs| { *funcs = Some(behaviors); @@ -126,8 +135,10 @@ impl WireframeEditor { &deps, )); + let segments = editor.segments.clone(); + let deps = [segments.untyped()]; bounding_segments.replace_with(Property::computed( - move || editor.get().segments.into_iter().map(Into::into).collect(), + move || segments.get().into_iter().map(Into::into).collect(), &deps, )); } diff --git a/pax-designer/src/glass/wireframe_editor/editor_generation.rs b/pax-designer/src/glass/wireframe_editor/editor_generation.rs index 687552e66..c8a086b3c 100644 --- a/pax-designer/src/glass/wireframe_editor/editor_generation.rs +++ b/pax-designer/src/glass/wireframe_editor/editor_generation.rs @@ -33,11 +33,11 @@ pub mod stacker_control; #[derive(Clone, Default)] pub struct Editor { pub controls: Vec, - pub segments: Vec<(Point2, Point2)>, + pub segments: Property, Point2)>>, } impl Editor { - pub fn new(ctx: NodeContext, selection: SelectionState) -> Property { + pub fn new(ctx: NodeContext, selection: SelectionState) -> Self { let total_bounds = selection.total_bounds.clone(); let deps = [total_bounds.untyped()]; let total_bounds_cp = total_bounds.clone(); @@ -60,21 +60,16 @@ impl Editor { &deps, ); - let mut deps: Vec<_> = control_point_sets.iter().map(|p| p.untyped()).collect(); - deps.push(bounding_segments.untyped()); - Property::computed( - move || Self { - controls: control_point_sets.iter().map(|set| set.get()).collect(), - segments: bounding_segments.get(), - }, - &deps, - ) + Self { + controls: control_point_sets, + segments: bounding_segments, + } } fn object_specific_control_point_sets( ctx: NodeContext, selection: SelectionState, - ) -> Vec> { + ) -> Vec { if selection.items.len() != 1 { return Vec::default(); } @@ -90,9 +85,9 @@ impl Editor { match import_path.as_ref().map(|v| v.as_str()) { Some("pax_std::layout::stacker::Stacker") => { vec![ + stacker_control::stacker_divider_control_set(ctx.clone(), item.clone()), // add slot control generally for all slot components? slot_control::slot_dot_control_set(ctx.clone(), item.clone()), - stacker_control::stacker_divider_control_set(ctx.clone(), item.clone()), ] } _ => return Vec::default(), @@ -108,8 +103,10 @@ pub struct CPoint { pub point: Point2, /// Node-local rotation in degrees pub rotation: f64, + /// Node-local cursor rotation in degrees + pub cursor_rotation: f64, /// Anchor of control point (x/y 0.0-1.0, 0.5 is center) - pub anchor: Point2, + pub anchor: Point2, /// behavior on click/double click pub behavior: ControlPointToolFactory, } @@ -119,7 +116,10 @@ impl Default for CPoint { Self { point: Default::default(), rotation: 0.0, + cursor_rotation: 0.0, anchor: Point2::new(0.5, 0.5), + // TODO we want this to be required most likely, + // make builder instead of derive of Default? behavior: ControlPointToolFactory { tool_factory: Rc::new(|_, _| { Rc::new(RefCell::new({ @@ -179,6 +179,6 @@ impl Interpolatable for ControlPointSet {} #[derive(Clone, Default)] pub struct ControlPointSet { - pub points: Vec, + pub points: Property>, pub styling: ControlPointStyling, } diff --git a/pax-designer/src/glass/wireframe_editor/editor_generation/anchor_control.rs b/pax-designer/src/glass/wireframe_editor/editor_generation/anchor_control.rs index 28f1ab3a7..d902dce40 100644 --- a/pax-designer/src/glass/wireframe_editor/editor_generation/anchor_control.rs +++ b/pax-designer/src/glass/wireframe_editor/editor_generation/anchor_control.rs @@ -22,7 +22,7 @@ use crate::{ use super::ControlPointSet; -pub fn anchor_control_point_set(anchor: Property>) -> Property { +pub fn anchor_control_point_set(anchor: Property>) -> ControlPointSet { let anchor_control_point_styling = ControlPointStyling { round: true, stroke_color: Color::BLUE, @@ -36,22 +36,22 @@ pub fn anchor_control_point_set(anchor: Property>) -> Property ControlPointToolFactory { diff --git a/pax-designer/src/glass/wireframe_editor/editor_generation/resize_control.rs b/pax-designer/src/glass/wireframe_editor/editor_generation/resize_control.rs index c80fea0fb..c0fe5f213 100644 --- a/pax-designer/src/glass/wireframe_editor/editor_generation/resize_control.rs +++ b/pax-designer/src/glass/wireframe_editor/editor_generation/resize_control.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, f64::consts::PI, rc::Rc}; use pax_engine::{ api::Color, - math::{Point2, TransformParts}, + math::{Point2, TransformParts, Vector2}, pax_runtime::TransformAndBounds, NodeLocal, Property, }; @@ -29,7 +29,38 @@ use super::ControlPointSet; pub fn resize_control_points_set( total_selection_bounds: Property>, -) -> Property { +) -> ControlPointSet { + let deps = [total_selection_bounds.untyped()]; + // resize points + let resize_control_points = Property::computed( + move || { + let total_selection_bounds = total_selection_bounds.get().as_transform(); + let c_point = |x: f64, y: f64| { + let point = Point2::new(x, y); + let cursor_rotation = Vector2::new(1.0, 0.0) + .angle_to(point - Point2::new(0.5, 0.5)) + .get_as_degrees(); + CPoint { + point: total_selection_bounds * point, + cursor_rotation, + behavior: resize_factory(point), + ..Default::default() + } + }; + vec![ + c_point(0.0, 0.0), + c_point(0.5, 0.0), + c_point(1.0, 0.0), + c_point(1.0, 0.5), + c_point(1.0, 1.0), + c_point(0.5, 1.0), + c_point(0.0, 1.0), + c_point(0.0, 0.5), + ] + }, + &deps, + ); + let resize_control_point_styling = ControlPointStyling { round: false, stroke_color: Color::BLUE, @@ -42,75 +73,14 @@ pub fn resize_control_points_set( hit_padding: 10.0, }; - let deps = [total_selection_bounds.untyped()]; - Property::computed( - move || { - let total_bounds = total_selection_bounds.get(); - // clockwise from top left: - let [p1, p4, p3, p2] = total_bounds.corners(); - - // resize points - let resize_control_points = vec![ - CPoint { - point: p1, - behavior: resize_factory(Point2::new(1.0, 1.0)), - rotation: 225.0, - ..Default::default() - }, - CPoint { - point: p1.midpoint_towards(p2), - behavior: resize_factory(Point2::new(0.5, 1.0)), - rotation: 270.0, - ..Default::default() - }, - CPoint { - point: p2, - behavior: resize_factory(Point2::new(0.0, 1.0)), - rotation: 315.0, - ..Default::default() - }, - CPoint { - point: p2.midpoint_towards(p3), - behavior: resize_factory(Point2::new(0.0, 0.5)), - rotation: 0.0, - ..Default::default() - }, - CPoint { - point: p3, - behavior: resize_factory(Point2::new(0.0, 0.0)), - rotation: 45.0, - ..Default::default() - }, - CPoint { - point: p3.midpoint_towards(p4), - behavior: resize_factory(Point2::new(0.5, 0.0)), - rotation: 90.0, - ..Default::default() - }, - CPoint { - point: p4, - behavior: resize_factory(Point2::new(1.0, 0.0)), - rotation: 135.0, - ..Default::default() - }, - CPoint { - point: p4.midpoint_towards(p1), - behavior: resize_factory(Point2::new(1.0, 0.5)), - rotation: 180.0, - ..Default::default() - }, - ]; - - ControlPointSet { - points: resize_control_points, - styling: resize_control_point_styling.clone(), - } - }, - &deps, - ) + ControlPointSet { + points: resize_control_points, + styling: resize_control_point_styling, + } } -fn resize_factory(anchor: Point2) -> ControlPointToolFactory { +fn resize_factory(position: Point2) -> ControlPointToolFactory { + let anchor = (Point2::new(1.0, 1.0) - position).to_point(); ControlPointToolFactory { tool_factory: Rc::new(move |ac, _p| { let initial_selection: SelectionStateSnapshot = @@ -145,7 +115,7 @@ fn resize_factory(anchor: Point2) -> ControlPointToolFactory { } struct ResizeBehavior { - attachment_point: Point2, + attachment_point: Point2, initial_selection: SelectionStateSnapshot, } diff --git a/pax-designer/src/glass/wireframe_editor/editor_generation/rotate_control.rs b/pax-designer/src/glass/wireframe_editor/editor_generation/rotate_control.rs index dd4951afd..823fd1022 100644 --- a/pax-designer/src/glass/wireframe_editor/editor_generation/rotate_control.rs +++ b/pax-designer/src/glass/wireframe_editor/editor_generation/rotate_control.rs @@ -1,6 +1,11 @@ use std::{cell::RefCell, rc::Rc}; -use pax_engine::{api::Color, math::Point2, pax_runtime::TransformAndBounds, Property}; +use pax_engine::{ + api::Color, + math::{Point2, Vector2}, + pax_runtime::TransformAndBounds, + Property, +}; use crate::{ glass::{ @@ -21,9 +26,9 @@ use super::ControlPointSet; pub fn rotate_control_points_set( total_selection_bounds: Property>, -) -> Property { +) -> ControlPointSet { let rotate_control_point_styling = ControlPointStyling { - round: true, + round: false, stroke_color: Color::TRANSPARENT, fill_color: Color::TRANSPARENT, stroke_width_pixels: 0.0, @@ -35,101 +40,45 @@ pub fn rotate_control_points_set( }; let deps = [total_selection_bounds.untyped()]; - Property::computed( + let rotate_control_points = Property::computed( move || { - let total_bounds = total_selection_bounds.get(); - // clockwise from top left: - let [p1, p4, p3, p2] = total_bounds.corners(); - let rotate_control_points = vec![ - // each section of three sits in one corner, and fills out an - // "L" shape that "fits" the selected object. first CPoint in - // each section is the one pointing diagonally outwards from - // the selection. - - // -------- section 1 -------- - CPoint { - point: p1, - behavior: rotate_factory(), - rotation: 225.0, - anchor: Point2::new(1.0, 1.0), - }, - CPoint { - point: p1, - behavior: rotate_factory(), - rotation: 225.0, - anchor: Point2::new(0.0, 1.0), - }, - CPoint { - point: p1, - behavior: rotate_factory(), - rotation: 225.0, - anchor: Point2::new(1.0, 0.0), - }, - // -------- section 2 -------- - CPoint { - point: p2, - behavior: rotate_factory(), - rotation: 315.0, - anchor: Point2::new(0.0, 1.0), - }, - CPoint { - point: p2, - behavior: rotate_factory(), - rotation: 315.0, - anchor: Point2::new(1.0, 1.0), - }, - CPoint { - point: p2, - behavior: rotate_factory(), - rotation: 315.0, - anchor: Point2::new(0.0, 0.0), - }, - // -------- section 3 -------- - CPoint { - point: p3, - behavior: rotate_factory(), - rotation: 55.0, - anchor: Point2::new(0.0, 0.0), - }, - CPoint { - point: p3, - behavior: rotate_factory(), - rotation: 55.0, - anchor: Point2::new(1.0, 0.0), - }, - CPoint { - point: p3, - behavior: rotate_factory(), - rotation: 55.0, - anchor: Point2::new(0.0, 1.0), - }, - // -------- section 4 -------- - CPoint { - point: p4, - behavior: rotate_factory(), - rotation: 145.0, - anchor: Point2::new(1.0, 0.0), - }, - CPoint { - point: p4, - behavior: rotate_factory(), - rotation: 145.0, - anchor: Point2::new(0.0, 0.0), - }, - CPoint { - point: p4, - behavior: rotate_factory(), - rotation: 145.0, - anchor: Point2::new(1.0, 1.0), - }, - ]; - ControlPointSet { - points: rotate_control_points, - styling: rotate_control_point_styling.clone(), - } + let total_selection_bounds = total_selection_bounds.get().as_transform(); + let rotate_factory = rotate_factory(); + let c_point = |x: f64, y: f64| { + let local_point = Point2::new(x, y); + let cursor_rotation = Vector2::new(1.0, 0.0) + .angle_to(local_point - Point2::new(0.5, 0.5)) + .get_as_degrees(); + // Create three control points per corner in an "L" shape + [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0)] + .into_iter() + .map(|(x, y)| Point2::new(x, y)) + .filter(|p| p != &local_point) + .map(|anchor| CPoint { + point: total_selection_bounds * local_point, + cursor_rotation, + anchor: anchor.cast_space(), + behavior: rotate_factory.clone(), + ..Default::default() + }) + .collect::>() + }; + [ + c_point(1.0, 1.0), + c_point(0.0, 1.0), + c_point(1.0, 0.0), + c_point(0.0, 0.0), + ] + .into_iter() + .flatten() + .collect() }, &deps, - ) + ); + ControlPointSet { + points: rotate_control_points, + styling: rotate_control_point_styling, + } } fn rotate_factory() -> ControlPointToolFactory { diff --git a/pax-designer/src/glass/wireframe_editor/editor_generation/slot_control.rs b/pax-designer/src/glass/wireframe_editor/editor_generation/slot_control.rs index d817259c5..a699ffd43 100644 --- a/pax-designer/src/glass/wireframe_editor/editor_generation/slot_control.rs +++ b/pax-designer/src/glass/wireframe_editor/editor_generation/slot_control.rs @@ -44,15 +44,7 @@ use super::ControlPointSet; // TODO: decide if this should stay as separate logic (if we want different behavior at times) // or if slot_dot dragging should always behave as normal movement. If so, make this use the // MovingTool with a pre-defined target. -pub fn slot_dot_control_set(ctx: NodeContext, item: GlassNode) -> Property { - struct SlotBehavior { - initial_node: GlassNodeSnapshot, - pickup_point: Point2, - drop_intent_handler: DropIntentHandler, - vis: Property, - transaction: model::action::Transaction, - } - +pub fn slot_dot_control_set(ctx: NodeContext, item: GlassNode) -> ControlPointSet { let to_glass_transform = model::read_app_state_with_derived(|_, derived| derived.to_glass_transform.get()); @@ -64,134 +56,9 @@ pub fn slot_dot_control_set(ctx: NodeContext, item: GlassNode) -> Property, - _ctx: &mut ActionContext, - ) -> ControlFlow<()> { - ControlFlow::Break(()) - } - - fn pointer_move( - &mut self, - point: Point2, - ctx: &mut ActionContext, - ) -> ControlFlow<()> { - let translation = point - self.pickup_point; - - let curr_node = ctx - .engine_context - .get_nodes_by_global_id(self.initial_node.id.clone()) - .into_iter() - .next() - .unwrap(); - - let move_translation = TransformAndBounds { - transform: Transform2::translate(translation), - bounds: (1.0, 1.0), - }; - - let glass_curr_node = GlassNode::new(&curr_node, &ctx.glass_transform()); - let _ = self.transaction.run(|| { - SetNodeLayout { - id: &self.initial_node.id, - node_layout: &NodeLayoutSettings::KeepScreenBounds { - node_transform_and_bounds: &(move_translation - * self.initial_node.transform_and_bounds), - parent_transform_and_bounds: &glass_curr_node - .parent_transform_and_bounds - .get(), - node_decomposition_config: &self - .initial_node - .layout_properties - .into_decomposition_config(), - }, - } - .perform(ctx) - }); - - self.drop_intent_handler.update(ctx, point); - ControlFlow::Continue(()) - } - - fn pointer_up( - &mut self, - _point: Point2, - ctx: &mut ActionContext, - ) -> ControlFlow<()> { - if let Err(e) = self.transaction.run(|| { - self.drop_intent_handler.handle_drop(ctx); - Ok(()) - }) { - log::warn!("failed slot dot movement: {e}"); - }; - ControlFlow::Break(()) - } - - fn finish(&mut self, _ctx: &mut ActionContext) -> anyhow::Result<()> { - Ok(()) - } - - fn keyboard( - &mut self, - _event: InputEvent, - _dir: crate::model::input::Dir, - _ctx: &mut ActionContext, - ) -> ControlFlow<()> { - ControlFlow::Break(()) - } - - fn get_visual(&self) -> Property { - let vis = self.vis.clone(); - let deps = [vis.untyped()]; - Property::computed(move || vis.get(), &deps) - } - } - - fn slot_dot_control_factory(slot_child: GlassNode) -> ControlPointToolFactory { - ControlPointToolFactory { - tool_factory: Rc::new(move |ctx, p| { - let drop_intent_handler = - DropIntentHandler::new(&[slot_child.raw_node_interface.clone()]); - let intent_areas = drop_intent_handler.get_intent_areas_prop(); - let deps = [intent_areas.untyped()]; - let transaction = ctx.transaction("slot dot move"); - - let vis = Property::computed( - move || ToolVisualizationState { - intent_areas: intent_areas.get(), - ..Default::default() - }, - &deps, - ); - Rc::new(RefCell::new(SlotBehavior { - initial_node: (&slot_child).into(), - pickup_point: p, - vis, - drop_intent_handler, - transaction, - })) - }), - double_click_behavior: Rc::new(|_| ()), - } - } - - let slot_dot_point_styling = ControlPointStyling { - round: true, - stroke_color: Color::RED, - fill_color: Color::rgba(255.into(), 255.into(), 255.into(), 150.into()), - stroke_width_pixels: 1.0, - affected_by_transform: false, - width: 14.0, - height: 14.0, - cursor_type: DesignerCursorType::Move, - hit_padding: 10.0, - }; - let t_and_b = slot_parent_node.transform_and_bounds(); let deps = [t_and_b.untyped(), slot_count.untyped()]; - Property::computed( + let slot_dot_control_points = Property::computed( move || { let mut slots = vec![]; let mut search_set: Vec = slot_parent_node.children(); @@ -205,25 +72,144 @@ pub fn slot_dot_control_set(ctx: NodeContext, item: GlassNode) -> Property, + drop_intent_handler: DropIntentHandler, + vis: Property, + transaction: model::action::Transaction, +} +impl ToolBehavior for SlotBehavior { + fn pointer_down(&mut self, _point: Point2, _ctx: &mut ActionContext) -> ControlFlow<()> { + ControlFlow::Break(()) + } + + fn pointer_move(&mut self, point: Point2, ctx: &mut ActionContext) -> ControlFlow<()> { + let translation = point - self.pickup_point; + + let curr_node = ctx + .engine_context + .get_nodes_by_global_id(self.initial_node.id.clone()) + .into_iter() + .next() + .unwrap(); + + let move_translation = TransformAndBounds { + transform: Transform2::translate(translation), + bounds: (1.0, 1.0), + }; + + let glass_curr_node = GlassNode::new(&curr_node, &ctx.glass_transform()); + let _ = self.transaction.run(|| { + SetNodeLayout { + id: &self.initial_node.id, + node_layout: &NodeLayoutSettings::KeepScreenBounds { + node_transform_and_bounds: &(move_translation + * self.initial_node.transform_and_bounds), + parent_transform_and_bounds: &glass_curr_node.parent_transform_and_bounds.get(), + node_decomposition_config: &self + .initial_node + .layout_properties + .into_decomposition_config(), + }, + } + .perform(ctx) + }); + + self.drop_intent_handler.update(ctx, point); + ControlFlow::Continue(()) + } + + fn pointer_up(&mut self, _point: Point2, ctx: &mut ActionContext) -> ControlFlow<()> { + if let Err(e) = self.transaction.run(|| { + self.drop_intent_handler.handle_drop(ctx); + Ok(()) + }) { + log::warn!("failed slot dot movement: {e}"); + }; + ControlFlow::Break(()) + } + + fn finish(&mut self, _ctx: &mut ActionContext) -> anyhow::Result<()> { + Ok(()) + } + + fn keyboard( + &mut self, + _event: InputEvent, + _dir: crate::model::input::Dir, + _ctx: &mut ActionContext, + ) -> ControlFlow<()> { + ControlFlow::Break(()) + } + + fn get_visual(&self) -> Property { + let vis = self.vis.clone(); + let deps = [vis.untyped()]; + Property::computed(move || vis.get(), &deps) + } +} + +fn slot_dot_control_factory(slot_child: GlassNode) -> ControlPointToolFactory { + ControlPointToolFactory { + tool_factory: Rc::new(move |ctx, p| { + let drop_intent_handler = + DropIntentHandler::new(&[slot_child.raw_node_interface.clone()]); + let intent_areas = drop_intent_handler.get_intent_areas_prop(); + let deps = [intent_areas.untyped()]; + let transaction = ctx.transaction("slot dot move"); + + let vis = Property::computed( + move || ToolVisualizationState { + intent_areas: intent_areas.get(), + ..Default::default() + }, + &deps, + ); + Rc::new(RefCell::new(SlotBehavior { + initial_node: (&slot_child).into(), + pickup_point: p, + vis, + drop_intent_handler, + transaction, + })) + }), + double_click_behavior: Rc::new(|_| ()), + } } diff --git a/pax-designer/src/glass/wireframe_editor/editor_generation/stacker_control.rs b/pax-designer/src/glass/wireframe_editor/editor_generation/stacker_control.rs index 73ed72e99..56e85f7d9 100644 --- a/pax-designer/src/glass/wireframe_editor/editor_generation/stacker_control.rs +++ b/pax-designer/src/glass/wireframe_editor/editor_generation/stacker_control.rs @@ -24,145 +24,8 @@ use crate::{ use super::{CPoint, ControlPointSet}; -pub fn stacker_divider_control_set(ctx: NodeContext, item: GlassNode) -> Property { - struct StackerDividerControlBehavior { - stacker_node: GlassNodeSnapshot, - resize_ind: usize, - start_sizes: Vec>, - boundaries: Vec<(f64, f64)>, - dir: StackerDirection, - } - - impl ControlPointBehavior for StackerDividerControlBehavior { - fn step(&self, ctx: &mut ActionContext, point: Point2) -> anyhow::Result<()> { - let t = ctx.world_transform() * self.stacker_node.transform_and_bounds.as_transform(); - let point = ctx.world_transform() * point; - let (_, u, v) = t.decompose(); - let (x_l, y_l) = (u.length(), v.length()); - let box_point = t.inverse() * point; - let (ratio, total) = match self.dir { - StackerDirection::Vertical => (box_point.y, y_l), - StackerDirection::Horizontal => (box_point.x, x_l), - }; - - let mut new_sizes = self.start_sizes.clone(); - while new_sizes.len() < self.boundaries.len() { - new_sizes.push(None); - } - let mut positions: Vec = self.boundaries.iter().map(|v| v.0).collect(); - if let Some((p, w)) = self.boundaries.last() { - positions.push(p + w); - } - let above = positions[self.resize_ind] / total; - let above_unit = new_sizes[self.resize_ind].unit(); - let new_val_ratio = (ratio - above).max(0.0); - - new_sizes[self.resize_ind] = Some(match above_unit { - crate::math::SizeUnit::Pixels => { - Size::Pixels(round_2_dec(new_val_ratio * total).into()) - } - crate::math::SizeUnit::Percent => { - Size::Percent(round_2_dec(new_val_ratio * 100.0).into()) - } - }); - - let sizes_str = sizes_to_string(&new_sizes); - - let mut dt = borrow_mut!(ctx.engine_context.designtime); - let mut builder = dt - .get_orm_mut() - .get_node_builder( - self.stacker_node.id.clone(), - ctx.app_state - .modifiers - .get() - .contains(&ModifierKey::Control), - ) - .unwrap(); - - builder.set_property("sizes", &sizes_str)?; - - builder - .save() - .map_err(|e| anyhow!("could not save: {}", e))?; - Ok(()) - } - } - - fn stacker_divider_control_factory( - resize_ind: usize, - boundaries: Vec<(f64, f64)>, - start_sizes: Vec>, - item: GlassNode, - dir: StackerDirection, - ) -> ControlPointToolFactory { - let stacker_id = item.id.clone(); - let stacker_id_2 = item.id.clone(); - ControlPointToolFactory { - tool_factory: Rc::new(move |ac, _p| { - Rc::new(RefCell::new(ControlPointTool::new( - ac, - "resizing stacker cells", - Some(IntentSnapper::new_from_scene(ac, &[stacker_id.clone()])), - StackerDividerControlBehavior { - stacker_node: (&item).into(), - resize_ind, - boundaries: boundaries.clone(), - start_sizes: start_sizes.clone(), - dir: dir.clone(), - }, - ))) - }), - double_click_behavior: Rc::new(move |ctx| { - let stacker_node = ctx - .engine_context - .get_nodes_by_global_id(stacker_id_2.clone()) - .into_iter() - .next() - .unwrap(); - let mut sizes = stacker_node - .with_properties(|stacker: &mut Stacker| stacker.sizes.get()) - .unwrap(); - if let Some(val) = sizes.get_mut(resize_ind) { - *val = None - } - - let sizes_str = sizes_to_string(&sizes); - - let mut dt = borrow_mut!(ctx.engine_context.designtime); - let mut builder = dt - .get_orm_mut() - .get_node_builder( - stacker_id_2.clone(), - ctx.app_state - .modifiers - .get() - .contains(&ModifierKey::Control), - ) - .unwrap(); - - builder.set_property("sizes", &sizes_str).unwrap(); - - builder - .save() - .map_err(|e| anyhow!("could not save: {}", e)) - .unwrap(); - }), - } - } - +pub fn stacker_divider_control_set(ctx: NodeContext, item: GlassNode) -> ControlPointSet { // slot-divider resizer style - let control_point_styling = ControlPointStyling { - affected_by_transform: true, - round: false, - stroke_color: Color::RED, - fill_color: Color::rgba(255.into(), 255.into(), 255.into(), 150.into()), - stroke_width_pixels: 1.0, - width: -1.0, - height: -1.0, - cursor_type: DesignerCursorType::Resize, - hit_padding: 10.0, - }; let to_glass_transform = model::read_app_state_with_derived(|_, derived| derived.to_glass_transform.get()); let manifest_changed_notifier = ctx @@ -176,7 +39,7 @@ pub fn stacker_divider_control_set(ctx: NodeContext, item: GlassNode) -> Propert let deps = [object_transform.untyped(), manifest_changed_notifier]; let item_id = item.id; let ctx = ctx.clone(); - Property::computed( + let stacker_divider_control_points = Property::computed( move || { let item = ctx .clone() @@ -205,7 +68,11 @@ pub fn stacker_divider_control_set(ctx: NodeContext, item: GlassNode) -> Propert }) .collect(); - let stacker_divider_control_points = boundaries + let rotation = match dir { + StackerDirection::Vertical => 90.0, + StackerDirection::Horizontal => 0.0, + }; + boundaries .iter() .enumerate() .map(|(i, &c)| CPoint { @@ -220,39 +87,157 @@ pub fn stacker_divider_control_set(ctx: NodeContext, item: GlassNode) -> Propert item.clone(), dir.clone(), ), - rotation: match dir { - StackerDirection::Vertical => 90.0, - StackerDirection::Horizontal => 0.0, - }, + rotation, + cursor_rotation: rotation, ..Default::default() }) - .collect(); + .collect() + }, + &deps, + ); - let mut cp_style = control_point_styling.clone(); + let control_point_styling = ControlPointStyling { + affected_by_transform: true, + round: false, + stroke_color: Color::RED, + fill_color: Color::rgba(255.into(), 255.into(), 255.into(), 150.into()), + stroke_width_pixels: 1.0, + width: 6.0, + height: 24.0, + cursor_type: DesignerCursorType::Resize, + hit_padding: 10.0, + }; - //width / height of slot-divider resizer's major axis - const MAJOR: f64 = 24.0; + ControlPointSet { + points: stacker_divider_control_points, + styling: control_point_styling, + } +} - //width / height of slot-divider resizer's minor axis - const MINOR: f64 = 6.0; - match dir { - StackerDirection::Vertical => { - cp_style.width = MAJOR; - cp_style.height = MINOR; - } - StackerDirection::Horizontal => { - cp_style.width = MINOR; - cp_style.height = MAJOR; - } +struct StackerDividerControlBehavior { + stacker_node: GlassNodeSnapshot, + resize_ind: usize, + start_sizes: Vec>, + boundaries: Vec<(f64, f64)>, + dir: StackerDirection, +} + +impl ControlPointBehavior for StackerDividerControlBehavior { + fn step(&self, ctx: &mut ActionContext, point: Point2) -> anyhow::Result<()> { + let t = ctx.world_transform() * self.stacker_node.transform_and_bounds.as_transform(); + let point = ctx.world_transform() * point; + let (_, u, v) = t.decompose(); + let (x_l, y_l) = (u.length(), v.length()); + let box_point = t.inverse() * point; + let (ratio, total) = match self.dir { + StackerDirection::Vertical => (box_point.y, y_l), + StackerDirection::Horizontal => (box_point.x, x_l), + }; + + let mut new_sizes = self.start_sizes.clone(); + while new_sizes.len() < self.boundaries.len() { + new_sizes.push(None); + } + let mut positions: Vec = self.boundaries.iter().map(|v| v.0).collect(); + if let Some((p, w)) = self.boundaries.last() { + positions.push(p + w); + } + let above = positions[self.resize_ind] / total; + let above_unit = new_sizes[self.resize_ind].unit(); + let new_val_ratio = (ratio - above).max(0.0); + + new_sizes[self.resize_ind] = Some(match above_unit { + crate::math::SizeUnit::Pixels => { + Size::Pixels(round_2_dec(new_val_ratio * total).into()) } + crate::math::SizeUnit::Percent => { + Size::Percent(round_2_dec(new_val_ratio * 100.0).into()) + } + }); + + let sizes_str = sizes_to_string(&new_sizes); + + let mut dt = borrow_mut!(ctx.engine_context.designtime); + let mut builder = dt + .get_orm_mut() + .get_node_builder( + self.stacker_node.id.clone(), + ctx.app_state + .modifiers + .get() + .contains(&ModifierKey::Control), + ) + .unwrap(); - ControlPointSet { - points: stacker_divider_control_points, - styling: cp_style, + builder.set_property("sizes", &sizes_str)?; + + builder + .save() + .map_err(|e| anyhow!("could not save: {}", e))?; + Ok(()) + } +} + +fn stacker_divider_control_factory( + resize_ind: usize, + boundaries: Vec<(f64, f64)>, + start_sizes: Vec>, + item: GlassNode, + dir: StackerDirection, +) -> ControlPointToolFactory { + let stacker_id = item.id.clone(); + let stacker_id_2 = item.id.clone(); + ControlPointToolFactory { + tool_factory: Rc::new(move |ac, _p| { + Rc::new(RefCell::new(ControlPointTool::new( + ac, + "resizing stacker cells", + Some(IntentSnapper::new_from_scene(ac, &[stacker_id.clone()])), + StackerDividerControlBehavior { + stacker_node: (&item).into(), + resize_ind, + boundaries: boundaries.clone(), + start_sizes: start_sizes.clone(), + dir: dir.clone(), + }, + ))) + }), + double_click_behavior: Rc::new(move |ctx| { + let stacker_node = ctx + .engine_context + .get_nodes_by_global_id(stacker_id_2.clone()) + .into_iter() + .next() + .unwrap(); + let mut sizes = stacker_node + .with_properties(|stacker: &mut Stacker| stacker.sizes.get()) + .unwrap(); + if let Some(val) = sizes.get_mut(resize_ind) { + *val = None } - }, - &deps, - ) + + let sizes_str = sizes_to_string(&sizes); + + let mut dt = borrow_mut!(ctx.engine_context.designtime); + let mut builder = dt + .get_orm_mut() + .get_node_builder( + stacker_id_2.clone(), + ctx.app_state + .modifiers + .get() + .contains(&ModifierKey::Control), + ) + .unwrap(); + + builder.set_property("sizes", &sizes_str).unwrap(); + + builder + .save() + .map_err(|e| anyhow!("could not save: {}", e)) + .unwrap(); + }), + } } fn round_2_dec(v: f64) -> f64 { diff --git a/pax-designer/src/lib.rs b/pax-designer/src/lib.rs index 46c06758a..9b418bfc0 100644 --- a/pax-designer/src/lib.rs +++ b/pax-designer/src/lib.rs @@ -91,9 +91,10 @@ impl PaxDesigner { self.bind_stage_outline_width_property(&app_state); }); self.bind_is_manifest_loaded_from_server(ctx); + self.bind_cull_console(); } - pub fn toggle_console(&mut self, ctx: &NodeContext, args: Event) { + pub fn toggle_console(&mut self, _ctx: &NodeContext, _args: Event) { let current_console_status = self.console_status.get(); let new_console_status = !current_console_status; self.console_status.set(new_console_status); @@ -118,10 +119,18 @@ impl PaxDesigner { // hasn't been written to engine yet - this is the place they get // flushed model::action::meta::flush_sheduled_actions(ctx); + } - let should_cull_console = self.console_height.get() >= CLOSED_CONSOLE_HEIGHT - f64::EPSILON - && self.console_height.get() <= CLOSED_CONSOLE_HEIGHT + f64::EPSILON; - self.cull_console.set(should_cull_console); + pub fn bind_cull_console(&mut self) { + let console_height = self.console_height.clone(); + let deps = [console_height.untyped()]; + self.cull_console.replace_with(Property::computed( + move || { + console_height.get() >= CLOSED_CONSOLE_HEIGHT - f64::EPSILON + && console_height.get() <= CLOSED_CONSOLE_HEIGHT + f64::EPSILON + }, + &deps, + )); } fn bind_stage_property(&mut self, app_state: &model::AppState) { diff --git a/pax-designer/src/model/action/orm/space_movement.rs b/pax-designer/src/model/action/orm/space_movement.rs index f0e976637..388907311 100644 --- a/pax-designer/src/model/action/orm/space_movement.rs +++ b/pax-designer/src/model/action/orm/space_movement.rs @@ -57,7 +57,7 @@ impl Action for SetAnchor<'_> { } pub struct ResizeFromSnapshot<'a> { - pub fixed_point: Point2, + pub fixed_point: Point2, pub new_point: Point2, pub initial_selection: &'a SelectionStateSnapshot, } diff --git a/pax-designer/src/model/tools/paintbrush_tool.rs b/pax-designer/src/model/tools/paintbrush_tool.rs index 78f685a48..5e206c736 100644 --- a/pax-designer/src/model/tools/paintbrush_tool.rs +++ b/pax-designer/src/model/tools/paintbrush_tool.rs @@ -201,9 +201,9 @@ impl ToolBehavior for PaintbrushTool { &PathElement::Point(x, y) => points.push((x, y)), PathElement::Line => (), &PathElement::Quadratic(x, y) => points.push((x, y)), - PathElement::Cubic(sizes) => { - points.push((sizes.0, sizes.1)); - points.push((sizes.2, sizes.3)); + &PathElement::Cubic(v1, v2, v3, v4) => { + points.push((v1, v2)); + points.push((v3, v4)); } PathElement::Close => (), PathElement::Empty => (), @@ -272,12 +272,9 @@ impl ToolBehavior for PaintbrushTool { PathElement::Point(x, y) => PathElement::Point(conv_x(x), conv_y(y)), PathElement::Line => PathElement::Line, PathElement::Quadratic(x, y) => PathElement::Quadratic(conv_x(x), conv_y(y)), - PathElement::Cubic(sizes) => PathElement::Cubic(Box::new(( - conv_x(sizes.0), - conv_y(sizes.1), - conv_x(sizes.2), - conv_y(sizes.3), - ))), + PathElement::Cubic(v1, v2, v3, v4) => { + PathElement::Cubic(conv_x(v1), conv_y(v2), conv_x(v3), conv_y(v4)) + } PathElement::Close => PathElement::Close, }) .collect(); @@ -411,12 +408,12 @@ fn to_pax_path(path: &CompoundPath, bounds: (f64, f64)) -> Vec { handle_start: ctrl1, handle_end: ctrl2, } => { - pax_segs.push(PathElement::Cubic(Box::new(( + pax_segs.push(PathElement::Cubic( Size::Percent((100.0 * ctrl1.x / bounds.0).into()), Size::Percent((100.0 * ctrl1.y / bounds.1).into()), Size::Percent((100.0 * ctrl2.x / bounds.0).into()), Size::Percent((100.0 * ctrl2.y / bounds.1).into()), - )))); + )); pax_segs.push(PathElement::Point( Size::Percent((100.0 * seg.end.x / bounds.0).into()), Size::Percent((100.0 * seg.end.y / bounds.1).into()), diff --git a/pax-lang/src/deserializer/helpers.rs b/pax-lang/src/deserializer/helpers.rs index 2afa63fb2..72ba323ac 100644 --- a/pax-lang/src/deserializer/helpers.rs +++ b/pax-lang/src/deserializer/helpers.rs @@ -173,26 +173,25 @@ impl<'de> VariantAccess<'de> for PaxEnum<'de> { T: DeserializeSeed<'de>, { let ast = self.args.unwrap(); - seed.deserialize(PaxDeserializer::from(ast)) - } - - fn tuple_variant(self, _len: usize, visitor: V) -> Result - where - V: Visitor<'de>, - { if self.is_pax_value && self.variant == "Enum" { - let mut enum_pairs = self.args.unwrap().into_inner(); + let mut enum_pairs = ast.into_inner(); while enum_pairs.len() > 3 { enum_pairs.next(); } if enum_pairs.len() == 2 { let name = enum_pairs.next().unwrap().as_str(); let variant = enum_pairs.next().unwrap().as_str(); - return visitor.visit_seq(UnitVariant::new(name, variant)); + return seed.deserialize(UnitVariant::new(name, variant)); } - return visitor.visit_seq(PaxSeq::new(enum_pairs)); + return seed.deserialize(PaxSeq::new(enum_pairs)); } + seed.deserialize(PaxDeserializer::from(ast)) + } + fn tuple_variant(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { if let Some(args) = self.args { visitor.visit_seq(PaxSeq::new(args.into_inner())) } else { @@ -215,6 +214,23 @@ pub struct PaxSeq<'de> { index: usize, } +impl<'de> Deserializer<'de> for PaxSeq<'de> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> std::result::Result + where + V: Visitor<'de>, + { + visitor.visit_seq(self) + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq tuple identifier + tuple_struct map struct enum ignored_any + } +} + impl<'de> PaxSeq<'de> { pub fn new(elements: Pairs<'de, Rule>) -> Self { PaxSeq { elements, index: 0 } @@ -446,6 +462,23 @@ impl<'de> UnitVariant<'de> { } } +impl<'de> Deserializer<'de> for UnitVariant<'de> { + type Error = Error; + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct seq tuple identifier + tuple_struct map struct enum ignored_any + } + + fn deserialize_any(self, visitor: V) -> std::result::Result + where + V: Visitor<'de>, + { + visitor.visit_seq(self) + } +} + impl<'de> SeqAccess<'de> for UnitVariant<'de> { type Error = Error; diff --git a/pax-lang/src/deserializer/tests.rs b/pax-lang/src/deserializer/tests.rs index 9ea9918f3..ced7ca036 100644 --- a/pax-lang/src/deserializer/tests.rs +++ b/pax-lang/src/deserializer/tests.rs @@ -100,7 +100,7 @@ fn test_vec() { #[test] fn test_enum() { let enum_pax = "Test::Enum(10, 20, 30)".to_string(); - let expected = PaxValue::Enum( + let expected = PaxValue::Enum(Box::new(( "Test".to_string(), "Enum".to_string(), vec![ @@ -108,7 +108,7 @@ fn test_enum() { PaxValue::Numeric(Numeric::I64(20)), PaxValue::Numeric(Numeric::I64(30)), ], - ); + ))); let v = from_pax(&enum_pax).unwrap(); assert_eq!(expected, v); } @@ -148,7 +148,7 @@ fn test_complex_vec() { #[test] fn test_complex_enum() { let enum_pax = "Test::Enum(10, 20, 30, [40, 50], { a: 60 })".to_string(); - let expected = PaxValue::Enum( + let expected = PaxValue::Enum(Box::new(( "Test".to_string(), "Enum".to_string(), vec![ @@ -165,7 +165,7 @@ fn test_complex_enum() { .collect(), ), ], - ); + ))); let v = from_pax(&enum_pax).unwrap(); assert_eq!(expected, v); } diff --git a/pax-lang/src/interpreter/computable.rs b/pax-lang/src/interpreter/computable.rs index 0bd2d8ee7..c04936237 100644 --- a/pax-lang/src/interpreter/computable.rs +++ b/pax-lang/src/interpreter/computable.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, rc::Rc}; +use std::rc::Rc; use pax_runtime_api::{ functions::call_function, CoercionRules, Functions, Numeric, PaxValue, Percent, Rotation, Size, @@ -29,52 +29,58 @@ impl Computable for PaxPrimary { fn compute(&self, idr: Rc) -> Result { match self { PaxPrimary::Literal(v) => Ok(v.clone()), - PaxPrimary::Identifier(i, accessors) => { - let mut value = i.compute(idr.clone())?; - for accessor in accessors { - match accessor { - PaxAccessor::Tuple(index) => { - if let PaxValue::Vec(v) = value { - value = v.into_iter().nth(*index).ok_or_else(|| { - format!( - "paxel interpreter: tuple index out of bounds: {:?}", - index - ) - })?; - } else { - return Err("Tuple access must be performed on a tuple".to_string()); + PaxPrimary::Identifier(i, accessors) => idr + .resolve(i.name.clone())? + .read_pax_value_ref(|mut value| { + for accessor in accessors { + match accessor { + PaxAccessor::Tuple(index) => { + if let PaxValue::Vec(v) = value { + value = v.into_iter().nth(*index).ok_or_else(|| { + format!( + "paxel interpreter: tuple index out of bounds: {:?}", + index + ) + })?; + } else { + return Err( + "Tuple access must be performed on a tuple".to_string() + ); + } } - } - PaxAccessor::List(index) => { - if let PaxValue::Vec(v) = value { - let index = Numeric::try_coerce(index.compute(idr.clone())?)? - .to_int() as usize; - value = v.into_iter().nth(index).ok_or_else(|| { - format!( - "paxel interpreter: list index out of bounds: {:?}", - index - ) - })?; - } else { - return Err("List access must be performed on a list".to_string()); + PaxAccessor::List(index) => { + if let PaxValue::Vec(v) = value { + let index = Numeric::try_coerce(index.compute(idr.clone())?)? + .to_int() + as usize; + value = v.into_iter().nth(index).ok_or_else(|| { + format!( + "paxel interpreter: list index out of bounds: {:?}", + index + ) + })?; + } else { + return Err( + "List access must be performed on a list".to_string() + ); + } } - } - PaxAccessor::Struct(field) => { - if let PaxValue::Object(obj) = value { - value = obj - .into_iter() - .find_map(|(n, v)| (&n == field).then_some(v)) - .ok_or(format!("Field not found: {}", field))?; - } else { - return Err( - "Struct access must be performed on an object".to_string() - ); + PaxAccessor::Struct(field) => { + if let PaxValue::Object(obj) = value { + value = obj + .into_iter() + .find_map(|(n, v)| (n == field).then_some(v)) + .ok_or(format!("Field not found: {}", field))?; + } else { + return Err( + "Struct access must be performed on an object".to_string() + ); + } } } } - } - Ok(value) - } + Ok(value.clone()) + }), PaxPrimary::Object(o) => Ok(PaxValue::Object( o.into_iter() .map(|(k, v)| Result::<_, String>::Ok((k.clone(), v.compute(idr.clone())?))) @@ -127,7 +133,11 @@ impl Computable for PaxPrimary { return call_function(scope.clone(), name_or_variant.clone(), args); } - Ok(PaxValue::Enum(scope.clone(), name_or_variant.clone(), args)) + Ok(PaxValue::Enum(Box::new(( + scope.clone(), + name_or_variant.clone(), + args, + )))) } } } @@ -160,6 +170,6 @@ impl Computable for PaxInfix { impl Computable for PaxIdentifier { fn compute(&self, idr: Rc) -> Result { - idr.resolve(self.name.clone()) + Ok(idr.resolve(self.name.clone())?.get_as_pax_value()) } } diff --git a/pax-lang/src/interpreter/property_resolution.rs b/pax-lang/src/interpreter/property_resolution.rs index 17b7dcf2e..ee963f907 100644 --- a/pax-lang/src/interpreter/property_resolution.rs +++ b/pax-lang/src/interpreter/property_resolution.rs @@ -1,13 +1,13 @@ use std::collections::{hash_set, HashMap}; -use pax_runtime_api::PaxValue; +use pax_runtime_api::{PaxValue, Property, Variable}; use super::{PaxExpression, PaxInfix, PaxPostfix, PaxPrefix, PaxPrimary}; /// Trait for resolving identifiers to values /// This is implemented by RuntimePropertyStackFrame pub trait IdentifierResolver { - fn resolve(&self, name: String) -> Result; + fn resolve(&self, name: String) -> Result; } pub trait DependencyCollector { @@ -72,9 +72,9 @@ impl DependencyCollector for PaxPostfix { } impl IdentifierResolver for HashMap { - fn resolve(&self, name: String) -> Result { + fn resolve(&self, name: String) -> Result { self.get(&name) - .map(|v| v.clone()) + .map(|v| Variable::new_from_typed_property(Property::new(v.clone()))) .ok_or(format!("Identifier not found: {}", name)) } } diff --git a/pax-macro/templates/derive_pax.stpl b/pax-macro/templates/derive_pax.stpl index fb9d535c9..714878d1e 100644 --- a/pax-macro/templates/derive_pax.stpl +++ b/pax-macro/templates/derive_pax.stpl @@ -308,7 +308,8 @@ impl <%= engine_import_path %>::api::CoercionRules for <%= pascal_identifier %> fn try_coerce(value: <%= engine_import_path %>::PaxValue) -> Result { <% if let InternalDefinitions::Enum(variant_definitions) = &internal_definitions { %> match value { - <%= engine_import_path %>::PaxValue::Enum(_, variant_name, values) => { + <%= engine_import_path %>::PaxValue::Enum(contents) => { + let (_, variant_name, values) = *contents; match variant_name.as_str() { <% for vd in variant_definitions { %> "<%= vd.variant_name %>" => { @@ -367,7 +368,7 @@ impl <%= engine_import_path %>::api::ToPaxValue for <%= pascal_identifier %> { <% } %> ) <% } %> => { - <%= engine_import_path %>::PaxValue::Enum( + <%= engine_import_path %>::PaxValue::Enum(Box::new(( "<%= pascal_identifier %>".to_string(), "<%= vd.variant_name %>".to_string(), vec![ @@ -375,7 +376,7 @@ impl <%= engine_import_path %>::api::ToPaxValue for <%= pascal_identifier %> { <<%=spd.root_scoped_resolvable_type%> as <%= engine_import_path %>::api::ToPaxValue>::to_pax_value(<%= spd.field_name %>), <% } %> ].into_iter().collect() - ) + ))) }, <% } %> } diff --git a/pax-manifest/src/lib.rs b/pax-manifest/src/lib.rs index d41c6aa03..308247b30 100644 --- a/pax-manifest/src/lib.rs +++ b/pax-manifest/src/lib.rs @@ -276,45 +276,48 @@ impl HelperFunctions for PaxType {} impl CoercionRules for PaxType { fn try_coerce(value: PaxValue) -> Result { match value { - PaxValue::Enum(_name, variant, args) => match variant.as_str() { - "If" => Ok(PaxType::If), - "Slot" => Ok(PaxType::Slot), - "Repeat" => Ok(PaxType::Repeat), - "Comment" => Ok(PaxType::Comment), - "BlankComponent" => { - let pascal_identifier = String::try_coerce(args[0].clone())?; - Ok(PaxType::BlankComponent { pascal_identifier }) - } - "Primitive" => { - let pascal_identifier = String::try_coerce(args[0].clone())?; - Ok(PaxType::Primitive { pascal_identifier }) - } - "Singleton" => { - let pascal_identifier = String::try_coerce(args[0].clone())?; - Ok(PaxType::Singleton { pascal_identifier }) - } - "Range" => { - let identifier = String::try_coerce(args[0].clone())?; - Ok(PaxType::Range { identifier }) - } - "Option" => { - let identifier = String::try_coerce(args[0].clone())?; - Ok(PaxType::Option { identifier }) - } - "Vector" => { - let elem_identifier = String::try_coerce(args[0].clone())?; - Ok(PaxType::Vector { elem_identifier }) - } - "Map" => { - let key_identifier = String::try_coerce(args[0].clone())?; - let value_identifier = String::try_coerce(args[1].clone())?; - Ok(PaxType::Map { - key_identifier, - value_identifier, - }) + PaxValue::Enum(contents) => { + let (_, variant, args) = *contents; + match variant.as_str() { + "If" => Ok(PaxType::If), + "Slot" => Ok(PaxType::Slot), + "Repeat" => Ok(PaxType::Repeat), + "Comment" => Ok(PaxType::Comment), + "BlankComponent" => { + let pascal_identifier = String::try_coerce(args[0].clone())?; + Ok(PaxType::BlankComponent { pascal_identifier }) + } + "Primitive" => { + let pascal_identifier = String::try_coerce(args[0].clone())?; + Ok(PaxType::Primitive { pascal_identifier }) + } + "Singleton" => { + let pascal_identifier = String::try_coerce(args[0].clone())?; + Ok(PaxType::Singleton { pascal_identifier }) + } + "Range" => { + let identifier = String::try_coerce(args[0].clone())?; + Ok(PaxType::Range { identifier }) + } + "Option" => { + let identifier = String::try_coerce(args[0].clone())?; + Ok(PaxType::Option { identifier }) + } + "Vector" => { + let elem_identifier = String::try_coerce(args[0].clone())?; + Ok(PaxType::Vector { elem_identifier }) + } + "Map" => { + let key_identifier = String::try_coerce(args[0].clone())?; + let value_identifier = String::try_coerce(args[1].clone())?; + Ok(PaxType::Map { + key_identifier, + value_identifier, + }) + } + _ => Ok(PaxType::Unknown), } - _ => Ok(PaxType::Unknown), - }, + } _ => Err("Cannot coerce PaxValue into PaxType".to_string()), } } @@ -323,56 +326,70 @@ impl CoercionRules for PaxType { impl ToPaxValue for PaxType { fn to_pax_value(self) -> PaxValue { match self { - PaxType::If => PaxValue::Enum("PaxType".to_string(), "If".to_string(), vec![]), - PaxType::Slot => PaxValue::Enum("PaxType".to_string(), "Slot".to_string(), vec![]), - PaxType::Repeat => PaxValue::Enum("PaxType".to_string(), "Repeat".to_string(), vec![]), - PaxType::Comment => { - PaxValue::Enum("PaxType".to_string(), "Comment".to_string(), vec![]) + PaxType::If => { + PaxValue::Enum(Box::new(("PaxType".to_string(), "If".to_string(), vec![]))) } - PaxType::BlankComponent { pascal_identifier } => PaxValue::Enum( + PaxType::Slot => PaxValue::Enum(Box::new(( + "PaxType".to_string(), + "Slot".to_string(), + vec![], + ))), + PaxType::Repeat => PaxValue::Enum(Box::new(( + "PaxType".to_string(), + "Repeat".to_string(), + vec![], + ))), + PaxType::Comment => PaxValue::Enum(Box::new(( + "PaxType".to_string(), + "Comment".to_string(), + vec![], + ))), + PaxType::BlankComponent { pascal_identifier } => PaxValue::Enum(Box::new(( "PaxType".to_string(), "BlankComponent".to_string(), vec![pascal_identifier.to_pax_value()], - ), - PaxType::Primitive { pascal_identifier } => PaxValue::Enum( + ))), + PaxType::Primitive { pascal_identifier } => PaxValue::Enum(Box::new(( "PaxType".to_string(), "Primitive".to_string(), vec![pascal_identifier.to_pax_value()], - ), - PaxType::Singleton { pascal_identifier } => PaxValue::Enum( + ))), + PaxType::Singleton { pascal_identifier } => PaxValue::Enum(Box::new(( "PaxType".to_string(), "Singleton".to_string(), vec![pascal_identifier.to_pax_value()], - ), - PaxType::Range { identifier } => PaxValue::Enum( + ))), + PaxType::Range { identifier } => PaxValue::Enum(Box::new(( "PaxType".to_string(), "Range".to_string(), vec![identifier.to_pax_value()], - ), - PaxType::Option { identifier } => PaxValue::Enum( + ))), + PaxType::Option { identifier } => PaxValue::Enum(Box::new(( "PaxType".to_string(), "Option".to_string(), vec![identifier.to_pax_value()], - ), - PaxType::Vector { elem_identifier } => PaxValue::Enum( + ))), + PaxType::Vector { elem_identifier } => PaxValue::Enum(Box::new(( "PaxType".to_string(), "Vector".to_string(), vec![elem_identifier.to_pax_value()], - ), + ))), PaxType::Map { key_identifier, value_identifier, - } => PaxValue::Enum( + } => PaxValue::Enum(Box::new(( "PaxType".to_string(), "Map".to_string(), vec![ key_identifier.to_pax_value(), value_identifier.to_pax_value(), ], - ), - PaxType::Unknown => { - PaxValue::Enum("PaxType".to_string(), "Unknown".to_string(), vec![]) - } + ))), + PaxType::Unknown => PaxValue::Enum(Box::new(( + "PaxType".to_string(), + "Unknown".to_string(), + vec![], + ))), } } } diff --git a/pax-manifest/src/server.rs b/pax-manifest/src/server.rs index d56e0ed1e..5e68c2daf 100644 --- a/pax-manifest/src/server.rs +++ b/pax-manifest/src/server.rs @@ -35,59 +35,62 @@ impl Interpolatable for ResponseError {} impl ToPaxValue for PublishResponse { fn to_pax_value(self) -> PaxValue { match self { - PublishResponse::Success(success) => PaxValue::Enum( + PublishResponse::Success(success) => PaxValue::Enum(Box::new(( "PublishResponse".to_string(), "Success".to_string(), vec![success.clone().to_pax_value()], - ), - PublishResponse::Error(error) => PaxValue::Enum( + ))), + PublishResponse::Error(error) => PaxValue::Enum(Box::new(( "PublishResponse".to_string(), "Error".to_string(), vec![error.clone().to_pax_value()], - ), - PublishResponse::Undefined => PaxValue::Enum( + ))), + PublishResponse::Undefined => PaxValue::Enum(Box::new(( "PublishResponse".to_string(), "Undefined".to_string(), vec![], - ), + ))), } } } impl ToPaxValue for PublishResponseSuccess { fn to_pax_value(self) -> PaxValue { - PaxValue::Enum( + PaxValue::Enum(Box::new(( "".to_string(), "PublishResponseSuccess".to_string(), vec![self.pull_request_url.clone().to_pax_value()], - ) + ))) } } impl ToPaxValue for ResponseError { fn to_pax_value(self) -> PaxValue { - PaxValue::Enum( + PaxValue::Enum(Box::new(( "".to_string(), "ResponseError".to_string(), vec![self.message.clone().to_pax_value()], - ) + ))) } } impl CoercionRules for PublishResponse { fn try_coerce(value: PaxValue) -> Result { match value { - PaxValue::Enum(_, variant, values) => match variant.as_str() { - "Success" => { - let success = PublishResponseSuccess::try_coerce(values[0].clone())?; - Ok(PublishResponse::Success(success)) - } - "Error" => { - let error = ResponseError::try_coerce(values[0].clone())?; - Ok(PublishResponse::Error(error)) + PaxValue::Enum(contents) => { + let (_, variant, values) = *contents; + match variant.as_str() { + "Success" => { + let success = PublishResponseSuccess::try_coerce(values[0].clone())?; + Ok(PublishResponse::Success(success)) + } + "Error" => { + let error = ResponseError::try_coerce(values[0].clone())?; + Ok(PublishResponse::Error(error)) + } + _ => Err(format!("Invalid variant: {}", variant)), } - _ => Err(format!("Invalid variant: {}", variant)), - }, + } _ => Err("Invalid PaxValue".to_string()), } } @@ -96,13 +99,16 @@ impl CoercionRules for PublishResponse { impl CoercionRules for PublishResponseSuccess { fn try_coerce(value: PaxValue) -> Result { match value { - PaxValue::Enum(_, variant, values) => match variant.as_str() { - "PublishResponseSuccess" => { - let pull_request_url = String::try_coerce(values[0].clone())?; - Ok(PublishResponseSuccess { pull_request_url }) + PaxValue::Enum(contents) => { + let (_, variant, values) = *contents; + match variant.as_str() { + "PublishResponseSuccess" => { + let pull_request_url = String::try_coerce(values[0].clone())?; + Ok(PublishResponseSuccess { pull_request_url }) + } + _ => Err(format!("Invalid variant: {}", variant)), } - _ => Err(format!("Invalid variant: {}", variant)), - }, + } _ => Err("Invalid PaxValue".to_string()), } } @@ -111,13 +117,16 @@ impl CoercionRules for PublishResponseSuccess { impl CoercionRules for ResponseError { fn try_coerce(value: PaxValue) -> Result { match value { - PaxValue::Enum(_, variant, values) => match variant.as_str() { - "ResponseError" => { - let message = String::try_coerce(values[0].clone())?; - Ok(ResponseError { message }) + PaxValue::Enum(contents) => { + let (_, variant, values) = *contents; + match variant.as_str() { + "ResponseError" => { + let message = String::try_coerce(values[0].clone())?; + Ok(ResponseError { message }) + } + _ => Err(format!("Invalid variant: {}", variant)), } - _ => Err(format!("Invalid variant: {}", variant)), - }, + } _ => Err("Invalid PaxValue".to_string()), } } diff --git a/pax-runtime-api/src/lib.rs b/pax-runtime-api/src/lib.rs index 4594b2b98..f2038ea5b 100644 --- a/pax-runtime-api/src/lib.rs +++ b/pax-runtime-api/src/lib.rs @@ -1687,7 +1687,7 @@ pub enum PathElement { Point(Size, Size), Line, Quadratic(Size, Size), - Cubic(Box<(Size, Size, Size, Size)>), + Cubic(Size, Size, Size, Size), Close, } @@ -2017,39 +2017,33 @@ impl Transform2D { #[derive(Clone)] pub struct Variable { untyped_property: UntypedProperty, - convert_to_pax_value: Rc PaxValue>, + converted_to_pax_value: Property, } impl Variable { pub fn new(untyped_property: UntypedProperty) -> Self { - let closure = |untyped_property: UntypedProperty| { - let property: Property = Property::new_from_untyped(untyped_property.clone()); - property.get().to_pax_value() - }; - - Variable { - untyped_property, - convert_to_pax_value: Rc::new(closure), - } + Self::new_from_typed_property(Property::::new_from_untyped(untyped_property.clone())) } pub fn new_from_typed_property(property: Property) -> Self { let untyped_property = property.untyped(); - let closure = |untyped_property: UntypedProperty| { - let property: Property = Property::new_from_untyped(untyped_property.clone()); - property.get().to_pax_value() - }; - + let deps = [untyped_property.clone()]; + let pax_value_prop = Property::computed(move || property.get().to_pax_value(), &deps); Variable { untyped_property, - convert_to_pax_value: Rc::new(closure), + converted_to_pax_value: pax_value_prop, } } pub fn get_untyped_property(&self) -> &UntypedProperty { &self.untyped_property } + pub fn get_as_pax_value(&self) -> PaxValue { - (self.convert_to_pax_value)(self.untyped_property.clone()) + self.converted_to_pax_value.get() + } + + pub fn read_pax_value_ref(&self, f: impl FnOnce(&PaxValue) -> V) -> V { + self.converted_to_pax_value.read(f) } } diff --git a/pax-runtime-api/src/pax_value/coercion_impls.rs b/pax-runtime-api/src/pax_value/coercion_impls.rs index 149d91b0c..9bffc0a4e 100644 --- a/pax-runtime-api/src/pax_value/coercion_impls.rs +++ b/pax-runtime-api/src/pax_value/coercion_impls.rs @@ -91,14 +91,14 @@ impl CoercionRules for Color { impl CoercionRules for PathElement { fn try_coerce(value: PaxValue) -> Result { - let err = format!("{:?} can't be coerced into a PathElement", value); match value { - PaxValue::PathElement(path_elem) => Ok(path_elem), + PaxValue::PathElement(path_elem) => Ok(*path_elem), // Why is this needed? should never deserialize a path into this - PaxValue::Enum(enum_name, enum_variant, values) => { - if enum_name == "PathElement" { + PaxValue::Enum(contents) => { + let (name, variant, values) = *contents; + if name == "PathElement" { let mut values_itr = values.into_iter(); - match enum_variant.as_str() { + match variant.as_str() { "Line" => Ok(PathElement::Line), "Close" => Ok(PathElement::Close), "Empty" => Ok(PathElement::Empty), @@ -110,19 +110,26 @@ impl CoercionRules for PathElement { Size::try_coerce(values_itr.next().unwrap())?, Size::try_coerce(values_itr.next().unwrap())?, )), - "Cubic" => Ok(PathElement::Cubic(Box::new(( + "Cubic" => Ok(PathElement::Cubic( Size::try_coerce(values_itr.next().unwrap())?, Size::try_coerce(values_itr.next().unwrap())?, Size::try_coerce(values_itr.next().unwrap())?, Size::try_coerce(values_itr.next().unwrap())?, - )))), - _ => return Err(err), + )), + _ => { + return Err(format!( + "failed to coerce PathElement: unknown enum variant: {:?}", + variant + )) + } } } else { - return Err(err); + return Err(format!( + "failed to coerce PathElement: enum name doesn't match" + )); } } - _ => return Err(err), + _ => return Err(format!("failed to coerce PathElement: PaxValue not a path")), } } } @@ -132,28 +139,38 @@ impl CoercionRules for PathElement { // an error if it contains any other type) impl CoercionRules for Fill { fn try_coerce(pax_value: PaxValue) -> Result { - Ok(match pax_value.clone() { + Ok(match pax_value { PaxValue::Color(color) => Fill::Solid(*color), - PaxValue::Enum(_, variant, args) => match variant.as_str() { - "Solid" => { - let color = Color::try_coerce(args[0].clone())?; - Fill::Solid(color) - } - "LinearGradient" => { - let gradient = LinearGradient::try_coerce(args[0].clone())?; - Fill::LinearGradient(gradient) - } - "RadialGradient" => { - let gradient = RadialGradient::try_coerce(args[0].clone())?; - Fill::RadialGradient(gradient) + PaxValue::Enum(contents) => { + let (_, variant, args) = *contents; + match variant.as_str() { + "Solid" => { + let color = Color::try_coerce(args.into_iter().next().unwrap())?; + Fill::Solid(color) + } + "LinearGradient" => { + let gradient = + LinearGradient::try_coerce(args.into_iter().next().unwrap())?; + Fill::LinearGradient(gradient) + } + "RadialGradient" => { + let gradient = + RadialGradient::try_coerce(args.into_iter().next().unwrap())?; + Fill::RadialGradient(gradient) + } + _ => { + return Err(format!( + "failed to coerce Fill: unknown enum variant {:?}", + variant + )) + } } - _ => return Err(format!("{:?} can't be coerced into a Fill", pax_value)), - }, - PaxValue::Option(mut o) => { - if let Some(o) = o.take() { + } + PaxValue::Option(o) => { + if let Some(o) = *o { Fill::try_coerce(o)? } else { - return Err(format!("{:?} can't be coerced into a Fill", pax_value)); + return Err(format!("failed to coerce Fill: can't coerce None")); } } _ => return Err(format!("{:?} can't be coerced into a Fill", pax_value)), @@ -163,65 +180,41 @@ impl CoercionRules for Fill { impl CoercionRules for LinearGradient { fn try_coerce(pax_value: PaxValue) -> Result { - Ok(match pax_value.clone() { + Ok(match pax_value { PaxValue::Object(map) => { - let start = map - .iter() - .find_map(|(n, v)| (n == "start").then_some(v)) - .unwrap() - .clone(); + let [start, end, stops] = extract_options(["start", "end", "stops"], map) + .map_err(|e| format!("failed to convert to LinearGradient: {e}"))?; let (s1, s2) = match start { PaxValue::Vec(vec) => { - let s1 = Size::try_coerce(vec[0].clone())?; - let s2 = Size::try_coerce(vec[1].clone())?; + let mut itr = vec.into_iter(); + let s1 = Size::try_coerce(itr.next().unwrap())?; + let s2 = Size::try_coerce(itr.next().unwrap())?; (s1, s2) } - _ => { - return Err(format!( - "{:?} can't be coerced into a LinearGradient", - pax_value - )) - } + _ => return Err(format!("failed to coerce LinearGradient")), }; - let end = map - .iter() - .find_map(|(n, v)| (n == "end").then_some(v)) - .unwrap() - .clone(); let (e1, e2) = match end { PaxValue::Vec(vec) => { - let e1 = Size::try_coerce(vec[0].clone())?; - let e2 = Size::try_coerce(vec[1].clone())?; + let mut itr = vec.into_iter(); + let e1 = Size::try_coerce(itr.next().unwrap())?; + let e2 = Size::try_coerce(itr.next().unwrap())?; (e1, e2) } - _ => { - return Err(format!( - "{:?} can't be coerced into a LinearGradient", - pax_value - )) - } + _ => return Err(format!("failed to coerce LinearGradient")), }; - let stops = Vec::::try_coerce( - map.iter() - .find_map(|(n, v)| (n == "stops").then_some(v)) - .unwrap() - .clone(), - )?; + let stops = Vec::::try_coerce(stops)?; LinearGradient { start: (s1, s2), end: (e1, e2), stops, } } - PaxValue::Option(mut o) => { - if let Some(o) = o.take() { + PaxValue::Option(o) => { + if let Some(o) = *o { LinearGradient::try_coerce(o)? } else { - return Err(format!( - "{:?} can't be coerced into a LinearGradient", - pax_value - )); + return Err(format!("failed to coerce LinearGradient")); } } _ => { @@ -236,65 +229,41 @@ impl CoercionRules for LinearGradient { impl CoercionRules for RadialGradient { fn try_coerce(pax_value: PaxValue) -> Result { - Ok(match pax_value.clone() { + Ok(match pax_value { PaxValue::Object(map) => { - let start = map - .iter() - .find_map(|(n, v)| (n == "start").then_some(v)) - .unwrap() - .clone(); + let [start, end, stops, radius] = + extract_options(["start", "end", "stops", "radius"], map) + .map_err(|e| format!("failed to convert to RadialGradient: {e}"))?; let (s1, s2) = match start { PaxValue::Vec(vec) => { - let s1 = Size::try_coerce(vec[0].clone())?; - let s2 = Size::try_coerce(vec[1].clone())?; + let mut itr = vec.into_iter(); + let s1 = Size::try_coerce(itr.next().unwrap())?; + let s2 = Size::try_coerce(itr.next().unwrap())?; (s1, s2) } _ => { - return Err(format!( - "{:?} can't be coerced into a RadialGradient", - pax_value - )) + return Err(format!("failed to coerce RadialGradient")); } }; - let end = map - .iter() - .find_map(|(n, v)| (n == "end").then_some(v)) - .unwrap() - .clone(); let (e1, e2) = match end { PaxValue::Vec(vec) => { - let e1 = Size::try_coerce(vec[0].clone())?; - let e2 = Size::try_coerce(vec[1].clone())?; + let mut itr = vec.into_iter(); + let e1 = Size::try_coerce(itr.next().unwrap())?; + let e2 = Size::try_coerce(itr.next().unwrap())?; (e1, e2) } _ => { - return Err(format!( - "{:?} can't be coerced into a RadialGradient", - pax_value - )) + return Err(format!("failed to coerce RadialGradient")); } }; - let radius = match map - .iter() - .find_map(|(n, v)| (n == "radius").then_some(v)) - .unwrap() - .clone() - { + let radius = match radius { PaxValue::Numeric(n) => n.to_float(), _ => { - return Err(format!( - "{:?} can't be coerced into a RadialGradient", - pax_value - )) + return Err(format!("failed to coerce RadialGradient")); } }; - let stops = Vec::::try_coerce( - map.iter() - .find_map(|(n, v)| (n == "stops").then_some(v)) - .unwrap() - .clone(), - )?; + let stops = Vec::::try_coerce(stops)?; RadialGradient { start: (s1, s2), end: (e1, e2), @@ -302,21 +271,15 @@ impl CoercionRules for RadialGradient { stops, } } - PaxValue::Option(mut o) => { - if let Some(o) = o.take() { + PaxValue::Option(o) => { + if let Some(o) = *o { RadialGradient::try_coerce(o)? } else { - return Err(format!( - "{:?} can't be coerced into a RadialGradient", - pax_value - )); + return Err(format!("failed to coerce RadialGradient")); } } _ => { - return Err(format!( - "{:?} can't be coerced into a RadialGradient", - pax_value - )) + return Err(format!("failed to coerce RadialGradient")); } }) } @@ -326,32 +289,21 @@ impl CoercionRules for GradientStop { fn try_coerce(pax_value: PaxValue) -> Result { Ok(match pax_value { PaxValue::Object(map) => { - let position = Size::try_coerce( - map.iter() - .find_map(|(n, v)| (n == "position").then_some(v)) - .unwrap() - .clone(), - )?; - let color = Color::try_coerce( - map.iter() - .find_map(|(n, v)| (n == "color").then_some(v)) - .unwrap() - .clone(), - )?; + let [position, color] = extract_options(["position", "color"], map) + .map_err(|e| format!("failed to convert to GradientStop: {e}"))?; + let position = Size::try_coerce(position)?; + let color = Color::try_coerce(color)?; GradientStop { position, color } } - PaxValue::Option(mut o) => { - if let Some(o) = o.take() { + PaxValue::Option(o) => { + if let Some(o) = *o { GradientStop::try_coerce(o)? } else { - return Err(format!("None can't be coerced into a GradientStop")); + return Err(format!("failed to convert to GradientStop")); } } _ => { - return Err(format!( - "{:?} can't be coerced into a GradientStop", - pax_value - )) + return Err(format!("failed to convert to GradientStop")); } }) } @@ -365,61 +317,61 @@ impl CoercionRules for Stroke { width: Property::new(Size::Pixels(1.into())), }, PaxValue::Object(map) => { - let color = Property::new(Color::try_coerce( - map.iter() - .find_map(|(n, v)| (n == "color").then_some(v)) - .cloned() - .unwrap_or_default(), - )?); - let width = Property::new(Size::try_coerce( - map.iter() - .find_map(|(n, v)| (n == "width").then_some(v)) - .cloned() - .unwrap_or(PaxValue::Size(Size::Pixels(1.into()))), - )?); + let [color, width] = extract_options(["color", "width"], map) + .map_err(|e| format!("failed to convert to Stroke: {e}"))?; + let color = Property::new(Color::try_coerce(color)?); + let width = Property::new(Size::try_coerce(width)?); Stroke { color, width } } - PaxValue::Option(mut o) => { - if let Some(o) = o.take() { + PaxValue::Option(o) => { + if let Some(o) = *o { Stroke::try_coerce(o)? } else { - return Err(format!("None can't be coerced into a Stroke")); + return Err(format!("failed to convert to Stroke")); } } - _ => return Err(format!("{:?} can't be coerced into a Stroke", pax_value)), + _ => return Err(format!("failed to convert to Stroke")), }) } } impl CoercionRules for ColorChannel { fn try_coerce(value: PaxValue) -> Result { - Ok(match value.clone() { + Ok(match value { PaxValue::Rotation(rot) => ColorChannel::Rotation(rot), PaxValue::Percent(perc) => ColorChannel::Percent(perc.0), PaxValue::Numeric(num) => ColorChannel::Integer(num.to_int().clamp(0, 255) as u8), - PaxValue::Enum(_, variant, args) => match variant.as_str() { - "Rotation" => { - let rot = Rotation::try_coerce(args[0].clone())?; - ColorChannel::Rotation(rot) - } - "Integer" => { - let num = Numeric::try_coerce(args[0].clone())?; - ColorChannel::Integer(num.to_int().clamp(0, 255) as u8) - } - "Percent" => { - let num = Numeric::try_coerce(args[0].clone())?; - ColorChannel::Percent(num) + PaxValue::Enum(contents) => { + let (_, variant, args) = *contents; + match variant.as_str() { + "Rotation" => { + let rot = Rotation::try_coerce(args.into_iter().next().unwrap())?; + ColorChannel::Rotation(rot) + } + "Integer" => { + let num = Numeric::try_coerce(args.into_iter().next().unwrap())?; + ColorChannel::Integer(num.to_int().clamp(0, 255) as u8) + } + "Percent" => { + let num = Numeric::try_coerce(args.into_iter().next().unwrap())?; + ColorChannel::Percent(num) + } + _ => { + return Err(format!( + "failed to convert to ColorChannel: unknown variant {:?}", + variant + )) + } } - _ => return Err(format!("{:?} can't be coerced into a ColorChannel", value)), - }, - PaxValue::Option(mut o) => { - if let Some(o) = o.take() { + } + PaxValue::Option(o) => { + if let Some(o) = *o { ColorChannel::try_coerce(o)? } else { - return Err(format!("{:?} can't be coerced into a ColorChannel", value)); + return Err(format!("failed to convert to ColorChannel")); } } - _ => return Err(format!("{:?} can't be coerced into a ColorChannel", value)), + _ => return Err(format!("failed to convert to ColorChannel")), }) } } @@ -545,12 +497,13 @@ impl CoercionRules for (T1, T2) { fn try_coerce(value: PaxValue) -> Result { match value { PaxValue::Vec(vec) => { - let res: Result = T1::try_coerce(vec[0].clone()); - let res2: Result = T2::try_coerce(vec[1].clone()); + let mut itr = vec.into_iter(); + let res: Result = T1::try_coerce(itr.next().unwrap()); + let res2: Result = T2::try_coerce(itr.next().unwrap()); res.and_then(|v1| res2.map(|v2| (v1, v2))) } - PaxValue::Option(mut opt) => { - if let Some(p) = opt.take() { + PaxValue::Option(opt) => { + if let Some(p) = *opt { <(T1, T2)>::try_coerce(p) } else { return Err(format!("None can't be coerced into a Vec")); @@ -616,124 +569,88 @@ impl CoercionRules for Box { impl CoercionRules for Transform2D { fn try_coerce(value: PaxValue) -> Result { Ok(match value { - PaxValue::Option(mut opt) => { - if let Some(t) = opt.take() { + PaxValue::Option(opt) => { + if let Some(t) = *opt { Transform2D::try_coerce(t)? } else { return Err(format!("None can't be coerced into a Transform2D")); } } PaxValue::Object(map) => { - let previous = match map.iter().find_map(|(n, v)| (n == "previous").then_some(v)) { - Some(p) => Option::>::try_coerce(p.clone())?, - None => None, - }; - let rotate = match map.iter().find_map(|(n, v)| (n == "rotate").then_some(v)) { - Some(r) => Option::::try_coerce(r.clone())?, - None => None, - }; - let translate = match map - .iter() - .find_map(|(n, v)| (n == "translate").then_some(v)) - { - Some(t) => match t.clone() { - PaxValue::Option(mut opt) => { - if let Some(t) = opt.take() { - let t = Vec::::try_coerce(t.clone())?; - if t.len() != 2 { - return Err(format!( - "expected 2 elements in translate, got {:?}", - t.len() - )); - } - Some([t[0], t[1]]) - } else { - None + let [previous, rotate, translate, anchor, scale, skew] = extract_options( + ["previous", "rotate", "translate", "anchor", "scale", "skew"], + map, + ) + .map_err(|e| format!("failed to convert to Transform2D: {e}"))?; + let previous = Option::>::try_coerce(previous)?; + let rotate = Option::::try_coerce(rotate)?; + let translate = match translate { + PaxValue::Option(opt) => { + if let Some(t) = *opt { + let t = Vec::::try_coerce(t)?; + if t.len() != 2 { + return Err(format!( + "expected 2 elements in translate, got {:?}", + t.len() + )); } + Some([t[0], t[1]]) + } else { + None } - _ => { - return Err(format!( - "translate of {:?} can't be coerced into a Transform2D", - t - )) - } - }, - None => None, + } + _ => return Err(format!("translate can't be coerced into a Transform2D",)), }; - let anchor = match map.iter().find_map(|(n, v)| (n == "anchor").then_some(v)) { - Some(a) => match a.clone() { - PaxValue::Option(mut opt) => { - if let Some(a) = opt.take() { - let a = Vec::::try_coerce(a.clone())?; - if a.len() != 2 { - return Err(format!( - "expected 2 elements in anchor, got {:?}", - a.len() - )); - } - Some([a[0], a[1]]) - } else { - None + let anchor = match anchor { + PaxValue::Option(opt) => { + if let Some(a) = *opt { + let a = Vec::::try_coerce(a)?; + if a.len() != 2 { + return Err(format!( + "expected 2 elements in anchor, got {:?}", + a.len() + )); } + Some([a[0], a[1]]) + } else { + None } - _ => { - return Err(format!( - "anchor of {:?} can't be coerced into a Transform2D", - a - )) - } - }, - None => None, + } + _ => return Err(format!("anchor can't be coerced into a Transform2D",)), }; - let scale = match map.iter().find_map(|(n, v)| (n == "scale").then_some(v)) { - Some(s) => match s.clone() { - PaxValue::Option(mut opt) => { - if let Some(s) = opt.take() { - let s = Vec::::try_coerce(s.clone())?; - if s.len() != 2 { - return Err(format!( - "expected 2 elements in scale, got {:?}", - s.len() - )); - } - Some([s[0], s[1]]) - } else { - None + let scale = match scale { + PaxValue::Option(opt) => { + if let Some(s) = *opt { + let s = Vec::::try_coerce(s)?; + if s.len() != 2 { + return Err(format!( + "expected 2 elements in scale, got {:?}", + s.len() + )); } + Some([s[0], s[1]]) + } else { + None } - _ => { - return Err(format!( - "scale of {:?} can't be coerced into a Transform2D", - s - )) - } - }, - None => None, + } + _ => return Err(format!("scale can't be coerced into a Transform2D",)), }; - let skew = match map.iter().find_map(|(n, v)| (n == "skew").then_some(v)) { - Some(s) => match s.clone() { - PaxValue::Option(mut opt) => { - if let Some(s) = opt.take() { - let s = Vec::::try_coerce(s.clone())?; - if s.len() != 2 { - return Err(format!( - "expected 2 elements in skew, got {:?}", - s.len() - )); - } - Some([s[0], s[1]]) - } else { - None + let skew = match skew { + PaxValue::Option(opt) => { + if let Some(s) = *opt { + let s = Vec::::try_coerce(s)?; + if s.len() != 2 { + return Err(format!( + "expected 2 elements in skew, got {:?}", + s.len() + )); } + Some([s[0], s[1]]) + } else { + None } - _ => { - return Err(format!( - "skew of {:?} can't be coerced into a Transform2D", - s - )) - } - }, - None => None, + } + _ => return Err(format!("skew can't be coerced into a Transform2D",)), }; Transform2D { previous, @@ -761,10 +678,9 @@ impl CoercionRules for Transform2 { } PaxValue::Object(map) => { let m = Vec::::try_coerce( - map.iter() + map.into_iter() .find_map(|(n, v)| (n == "m").then_some(v)) - .unwrap() - .clone(), + .unwrap(), )?; if m.len() != 6 { return Err(format!("expected 6 elements in coeffs, got {:?}", m.len())); @@ -787,21 +703,39 @@ impl CoercionRules for Vector2 { } } PaxValue::Object(map) => { - let x = f64::try_coerce( - map.iter() - .find_map(|(n, v)| (n == "x").then_some(v)) - .unwrap() - .clone(), - )?; - let y = f64::try_coerce( - map.iter() - .find_map(|(n, v)| (n == "y").then_some(v)) - .unwrap() - .clone(), - )?; + let [x, y] = extract_options(["x", "y"], map) + .map_err(|e| format!("failed to convert to Vector2: {e}"))?; + let x = f64::try_coerce(x)?; + let y = f64::try_coerce(y)?; Vector2::new(x, y) } _ => return Err(format!("{:?} can't be coerced into a Vector2", value)), }) } } + +pub fn extract_options( + keys: [&'static str; N], + vec: Vec<(String, T)>, +) -> Result<[T; N], String> { + // First create array of Options + let mut intermediate: [Option; N] = std::array::from_fn(|_| None); + + // Fill in the values we find + for (k, v) in vec { + if let Some(pos) = keys.iter().position(|&key| k == key) { + intermediate[pos] = Some(v); + } + } + + // Convert to Result, ensuring all values were found + let result: Result<[T; N], String> = intermediate + .into_iter() + .enumerate() + .map(|(i, opt)| opt.ok_or_else(|| format!("missing field: {}", keys[i]))) + .collect::, _>>()? + .try_into() + .map_err(|_| "Internal error converting vec to array".to_string()); + + result +} diff --git a/pax-runtime-api/src/pax_value/functions.rs b/pax-runtime-api/src/pax_value/functions.rs index 77b5260e0..41a8312be 100644 --- a/pax-runtime-api/src/pax_value/functions.rs +++ b/pax-runtime-api/src/pax_value/functions.rs @@ -50,14 +50,16 @@ fn add(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function add".to_string()); } - Ok(args[0].clone() + args[1].clone()) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap() + itr.next().unwrap()) } fn sub_or_neg(args: Vec) -> Result { if args.len() == 1 { - Ok(-args[0].clone()) + Ok(-args.into_iter().next().unwrap()) } else if args.len() == 2 { - Ok(args[0].clone() - args[1].clone()) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap() - itr.next().unwrap()) } else { Err("Expected 1 or 2 arguments for function sub_or_neg".to_string()) } @@ -67,28 +69,32 @@ fn mul(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function mul".to_string()); } - Ok(args[0].clone() * args[1].clone()) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap() * itr.next().unwrap()) } fn div(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function div".to_string()); } - Ok(args[0].clone() / args[1].clone()) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap() / itr.next().unwrap()) } fn exp(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function exp".to_string()); } - Ok(args[0].clone().pow(args[1].clone())) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap().pow(itr.next().unwrap())) } fn mod_(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function mod_".to_string()); } - Ok(args[0].clone() % args[1].clone()) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap() % itr.next().unwrap()) } fn rel_eq(args: Vec) -> Result { @@ -137,35 +143,39 @@ fn bool_and(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function bool_and".to_string()); } - Ok(args[0].clone().op_and(args[1].clone())) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap().op_and(itr.next().unwrap())) } fn bool_or(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function bool_or".to_string()); } - Ok(args[0].clone().op_or(args[1].clone())) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap().op_or(itr.next().unwrap())) } fn bool_not(args: Vec) -> Result { if args.len() != 1 { return Err("Expected 1 argument for function bool_not".to_string()); } - Ok(args[0].clone().op_not()) + Ok(args.into_iter().next().unwrap().op_not()) } fn min(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function min".to_string()); } - Ok(args[0].clone().min(args[1].clone())) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap().min(itr.next().unwrap())) } fn max(args: Vec) -> Result { if args.len() != 2 { return Err("Expected 2 arguments for function max".to_string()); } - Ok(args[0].clone().max(args[1].clone())) + let mut itr = args.into_iter(); + Ok(itr.next().unwrap().max(itr.next().unwrap())) } fn len(args: Vec) -> Result { @@ -182,9 +192,10 @@ fn rgb(args: Vec) -> Result { if args.len() != 3 { return Err("Expected 3 arguments for function rgb".to_string()); } - let r = ColorChannel::try_coerce(args[0].clone())?; - let g = ColorChannel::try_coerce(args[1].clone())?; - let b = ColorChannel::try_coerce(args[2].clone())?; + let mut itr = args.into_iter(); + let r = ColorChannel::try_coerce(itr.next().unwrap())?; + let g = ColorChannel::try_coerce(itr.next().unwrap())?; + let b = ColorChannel::try_coerce(itr.next().unwrap())?; Ok(Color::rgb(r, g, b).to_pax_value()) } @@ -192,10 +203,11 @@ fn rgba(args: Vec) -> Result { if args.len() != 4 { return Err("Expected 4 arguments for function rgba".to_string()); } - let r = ColorChannel::try_coerce(args[0].clone())?; - let g = ColorChannel::try_coerce(args[1].clone())?; - let b = ColorChannel::try_coerce(args[2].clone())?; - let a = ColorChannel::try_coerce(args[3].clone())?; + let mut itr = args.into_iter(); + let r = ColorChannel::try_coerce(itr.next().unwrap())?; + let g = ColorChannel::try_coerce(itr.next().unwrap())?; + let b = ColorChannel::try_coerce(itr.next().unwrap())?; + let a = ColorChannel::try_coerce(itr.next().unwrap())?; Ok(Color::rgba(r, g, b, a).to_pax_value()) } @@ -203,9 +215,10 @@ fn hsl(args: Vec) -> Result { if args.len() != 3 { return Err("Expected 3 arguments for function hsl".to_string()); } - let h = Rotation::try_coerce(args[0].clone())?; - let s = ColorChannel::try_coerce(args[1].clone())?; - let l = ColorChannel::try_coerce(args[2].clone())?; + let mut itr = args.into_iter(); + let h = Rotation::try_coerce(itr.next().unwrap())?; + let s = ColorChannel::try_coerce(itr.next().unwrap())?; + let l = ColorChannel::try_coerce(itr.next().unwrap())?; Ok(Color::hsl(h, s, l).to_pax_value()) } @@ -213,10 +226,11 @@ fn hsla(args: Vec) -> Result { if args.len() != 4 { return Err("Expected 4 arguments for function hsla".to_string()); } - let h = Rotation::try_coerce(args[0].clone())?; - let s = ColorChannel::try_coerce(args[1].clone())?; - let l = ColorChannel::try_coerce(args[2].clone())?; - let a = ColorChannel::try_coerce(args[3].clone())?; + let mut itr = args.into_iter(); + let h = Rotation::try_coerce(itr.next().unwrap())?; + let s = ColorChannel::try_coerce(itr.next().unwrap())?; + let l = ColorChannel::try_coerce(itr.next().unwrap())?; + let a = ColorChannel::try_coerce(itr.next().unwrap())?; Ok(Color::hsla(h, s, l, a).to_pax_value()) } @@ -224,7 +238,7 @@ fn hex(args: Vec) -> Result { if args.len() != 1 { return Err("Expected 1 argument for function hex".to_string()); } - let hex = String::try_coerce(args[0].clone())?; + let hex = String::try_coerce(args.into_iter().next().unwrap())?; Ok(Color::from_hex(&hex).to_pax_value()) } @@ -324,8 +338,9 @@ impl HelperFunctions for crate::Transform2D { if args.len() != 2 { return Err("Expected 2 arguments for function scale".to_string()); } - let x = crate::Size::try_coerce(args[0].clone())?; - let y = crate::Size::try_coerce(args[1].clone())?; + let mut itr = args.into_iter(); + let x = crate::Size::try_coerce(itr.next().unwrap())?; + let y = crate::Size::try_coerce(itr.next().unwrap())?; Ok(crate::Transform2D::scale(x, y).to_pax_value()) }), ); @@ -336,7 +351,7 @@ impl HelperFunctions for crate::Transform2D { if args.len() != 1 { return Err("Expected 1 argument for function rotate".to_string()); } - let z = crate::Rotation::try_coerce(args[0].clone())?; + let z = crate::Rotation::try_coerce(args.into_iter().next().unwrap())?; Ok(crate::Transform2D::rotate(z).to_pax_value()) }), ); @@ -347,8 +362,9 @@ impl HelperFunctions for crate::Transform2D { if args.len() != 2 { return Err("Expected 2 arguments for function translate".to_string()); } - let x = crate::Size::try_coerce(args[0].clone())?; - let y = crate::Size::try_coerce(args[1].clone())?; + let mut itr = args.into_iter(); + let x = crate::Size::try_coerce(itr.next().unwrap())?; + let y = crate::Size::try_coerce(itr.next().unwrap())?; Ok(crate::Transform2D::translate(x, y).to_pax_value()) }), ); @@ -359,8 +375,9 @@ impl HelperFunctions for crate::Transform2D { if args.len() != 2 { return Err("Expected 2 arguments for function anchor".to_string()); } - let x = crate::Size::try_coerce(args[0].clone())?; - let y = crate::Size::try_coerce(args[1].clone())?; + let mut itr = args.into_iter(); + let x = crate::Size::try_coerce(itr.next().unwrap())?; + let y = crate::Size::try_coerce(itr.next().unwrap())?; Ok(crate::Transform2D::anchor(x, y).to_pax_value()) }), ); @@ -386,7 +403,7 @@ impl HelperFunctions for Transform2 { if args.len() != 1 { return Err("Expected 1 argument for function scale".to_string()); } - let s = f64::try_coerce(args[0].clone())?; + let s = f64::try_coerce(args.into_iter().next().unwrap())?; Ok(Transform2::scale(s).to_pax_value()) }), ); @@ -398,7 +415,7 @@ impl HelperFunctions for Transform2 { if args.len() != 1 { return Err("Expected 1 argument for function scale".to_string()); } - let s = Vector2::try_coerce(args[0].clone())?; + let s = Vector2::try_coerce(args.into_iter().next().unwrap())?; Ok(Transform2::translate(s).to_pax_value()) }), ); @@ -410,7 +427,7 @@ impl HelperFunctions for Transform2 { if args.len() != 1 { return Err("Expected 1 argument for function rotate".to_string()); } - let s = f64::try_coerce(args[0].clone())?; + let s = f64::try_coerce(args.into_iter().next().unwrap())?; Ok(Transform2::rotate(s).to_pax_value()) }), ); @@ -421,7 +438,7 @@ impl HelperFunctions for Transform2 { if args.len() != 1 { return Err("Expected 1 argument for function skew".to_string()); } - let s = Vector2::try_coerce(args[0].clone())?; + let s = Vector2::try_coerce(args.into_iter().next().unwrap())?; Ok(Transform2::skew(s).to_pax_value()) }), ); diff --git a/pax-runtime-api/src/pax_value/macros.rs b/pax-runtime-api/src/pax_value/macros.rs index d2d559745..60aaed677 100644 --- a/pax-runtime-api/src/pax_value/macros.rs +++ b/pax-runtime-api/src/pax_value/macros.rs @@ -20,7 +20,7 @@ macro_rules! impl_default_coercion_rule { // This macro implements from and to #[macro_export] -macro_rules! impl_to_from_pax_value { +macro_rules! impl_to_pax_value { // For a single variant path ($Type:ty, $Variant:path) => { impl ToPaxValue for $Type { diff --git a/pax-runtime-api/src/pax_value/mod.rs b/pax-runtime-api/src/pax_value/mod.rs index 96fdef855..9161d864b 100644 --- a/pax-runtime-api/src/pax_value/mod.rs +++ b/pax-runtime-api/src/pax_value/mod.rs @@ -1,5 +1,5 @@ use crate::{Color, Interpolatable, PathElement, Percent, Rotation, Size}; -use std::{any::Any, fmt::Display}; +use std::{any::Any, fmt::Display, rc::Rc}; use self::numeric::Numeric; pub use coercion_impls::CoercionRules; @@ -12,13 +12,14 @@ mod macros; pub mod numeric; mod to_from_impls; +pub type RcPaxValue = Rc; /// Container for all internal pax types /// Two important traits are related to this type: /// ToFromPaxValue - responsible for converting to and from specific types (u8, /// String, Color, etc) /// CoercionRules - responsible for coercing a PaxValue to a specific type /// (possibly from multiple different variants) -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(crate = "crate::serde")] pub enum PaxValue { Bool(bool), @@ -28,12 +29,35 @@ pub enum PaxValue { Percent(Percent), Color(Box), Rotation(Rotation), - PathElement(PathElement), + PathElement(Box), Option(Box>), Vec(Vec), Range(Box, Box), Object(Vec<(String, PaxValue)>), - Enum(String, String, Vec), + Enum(Box<(String, String, Vec)>), +} + +impl Clone for PaxValue { + fn clone(&self) -> Self { + match self { + PaxValue::Bool(b) => PaxValue::Bool(*b), + PaxValue::Numeric(n) => PaxValue::Numeric(n.clone()), + PaxValue::String(s) => PaxValue::String(s.clone()), + PaxValue::Size(s) => PaxValue::Size(s.clone()), + PaxValue::Percent(p) => PaxValue::Percent(p.clone()), + PaxValue::Color(c) => PaxValue::Color(c.clone()), + PaxValue::Rotation(r) => PaxValue::Rotation(r.clone()), + PaxValue::PathElement(pe) => PaxValue::PathElement(pe.clone()), + PaxValue::Option(opt) => PaxValue::Option(opt.clone()), + PaxValue::Vec(v) => PaxValue::Vec(v.clone()), + PaxValue::Range(start, end) => PaxValue::Range(start.clone(), end.clone()), + PaxValue::Object(pairs) => PaxValue::Object(pairs.clone()), + PaxValue::Enum(contents) => { + let (name, variant, values) = contents.as_ref(); + PaxValue::Enum(Box::new((name.clone(), variant.clone(), values.clone()))) + } + } + } } // Make sure Enum looks like an enum and vec looks like a vec @@ -49,13 +73,13 @@ impl Display for PaxValue { PaxValue::Rotation(r) => write!(f, "{}", r), PaxValue::PathElement(path_elem) => { write!(f, "PathElement::")?; - match path_elem { + match path_elem.as_ref() { PathElement::Empty => write!(f, "Empty"), PathElement::Point(s1, s2) => write!(f, "Point({}, {})", s1, s2), PathElement::Line => write!(f, "Line"), PathElement::Quadratic(s1, s2) => write!(f, "Quadratic({}, {})", s1, s2), - PathElement::Cubic(vals) => { - write!(f, "Cubic({}, {}, {}, {})", vals.0, vals.1, vals.2, vals.3) + PathElement::Cubic(v1, v2, v3, v4) => { + write!(f, "Cubic({}, {}, {}, {})", v1, v2, v3, v4) } PathElement::Close => write!(f, "Close"), }?; @@ -86,7 +110,8 @@ impl Display for PaxValue { } write!(f, "}}") } - PaxValue::Enum(name, variant, values) => { + PaxValue::Enum(contents) => { + let (name, variant, values) = contents.as_ref(); if name == "Color" { write!(f, "{}", variant)?; } else { diff --git a/pax-runtime-api/src/pax_value/to_from_impls.rs b/pax-runtime-api/src/pax_value/to_from_impls.rs index 264fb9270..3bb153153 100644 --- a/pax-runtime-api/src/pax_value/to_from_impls.rs +++ b/pax-runtime-api/src/pax_value/to_from_impls.rs @@ -7,7 +7,7 @@ use super::ImplToFromPaxAny; use super::Numeric; use super::PaxValue; use super::ToPaxValue; -use crate::impl_to_from_pax_value; +use crate::impl_to_pax_value; use crate::math::Space; use crate::math::Transform2; use crate::math::Vector2; @@ -27,23 +27,23 @@ use crate::Stroke; use crate::Transform2D; // Primitive types -impl_to_from_pax_value!(bool, PaxValue::Bool); +impl_to_pax_value!(bool, PaxValue::Bool); -impl_to_from_pax_value!(u8, PaxValue::Numeric, Numeric::U8); -impl_to_from_pax_value!(u16, PaxValue::Numeric, Numeric::U16); -impl_to_from_pax_value!(u32, PaxValue::Numeric, Numeric::U32); -impl_to_from_pax_value!(u64, PaxValue::Numeric, Numeric::U64); +impl_to_pax_value!(u8, PaxValue::Numeric, Numeric::U8); +impl_to_pax_value!(u16, PaxValue::Numeric, Numeric::U16); +impl_to_pax_value!(u32, PaxValue::Numeric, Numeric::U32); +impl_to_pax_value!(u64, PaxValue::Numeric, Numeric::U64); -impl_to_from_pax_value!(i8, PaxValue::Numeric, Numeric::I8); -impl_to_from_pax_value!(i16, PaxValue::Numeric, Numeric::I16); -impl_to_from_pax_value!(i32, PaxValue::Numeric, Numeric::I32); -impl_to_from_pax_value!(i64, PaxValue::Numeric, Numeric::I64); +impl_to_pax_value!(i8, PaxValue::Numeric, Numeric::I8); +impl_to_pax_value!(i16, PaxValue::Numeric, Numeric::I16); +impl_to_pax_value!(i32, PaxValue::Numeric, Numeric::I32); +impl_to_pax_value!(i64, PaxValue::Numeric, Numeric::I64); -impl_to_from_pax_value!(f32, PaxValue::Numeric, Numeric::F32); -impl_to_from_pax_value!(f64, PaxValue::Numeric, Numeric::F64); +impl_to_pax_value!(f32, PaxValue::Numeric, Numeric::F32); +impl_to_pax_value!(f64, PaxValue::Numeric, Numeric::F64); -impl_to_from_pax_value!(isize, PaxValue::Numeric, Numeric::ISize); -impl_to_from_pax_value!(usize, PaxValue::Numeric, Numeric::USize); +impl_to_pax_value!(isize, PaxValue::Numeric, Numeric::ISize); +impl_to_pax_value!(usize, PaxValue::Numeric, Numeric::USize); // don't allow to be serialized/deserialized successfully, just store it as a dyn Any impl ImplToFromPaxAny for () {} @@ -51,14 +51,19 @@ impl ImplToFromPaxAny for () {} // for now. TBD how to handle this when we join Transform2D with Transform2 at some point impl ImplToFromPaxAny for Transform2 {} -impl_to_from_pax_value!(String, PaxValue::String); +impl_to_pax_value!(String, PaxValue::String); // Pax internal types -impl_to_from_pax_value!(Numeric, PaxValue::Numeric); -impl_to_from_pax_value!(Size, PaxValue::Size); -impl_to_from_pax_value!(Rotation, PaxValue::Rotation); -impl_to_from_pax_value!(Percent, PaxValue::Percent); -impl_to_from_pax_value!(PathElement, PaxValue::PathElement); +impl_to_pax_value!(Numeric, PaxValue::Numeric); +impl_to_pax_value!(Size, PaxValue::Size); +impl_to_pax_value!(Rotation, PaxValue::Rotation); +impl_to_pax_value!(Percent, PaxValue::Percent); + +impl ToPaxValue for PathElement { + fn to_pax_value(self) -> PaxValue { + PaxValue::PathElement(Box::new(self)) + } +} impl ToPaxValue for Color { fn to_pax_value(self) -> PaxValue { @@ -116,21 +121,21 @@ impl ToPaxValue for Property { impl ToPaxValue for Fill { fn to_pax_value(self) -> PaxValue { match self { - Fill::Solid(color) => PaxValue::Enum( + Fill::Solid(color) => PaxValue::Enum(Box::new(( "Fill".to_string(), "Solid".to_string(), vec![color.to_pax_value()], - ), - Fill::LinearGradient(gradient) => PaxValue::Enum( + ))), + Fill::LinearGradient(gradient) => PaxValue::Enum(Box::new(( "Fill".to_string(), "LinearGradient".to_string(), vec![gradient.to_pax_value()], - ), - Fill::RadialGradient(gradient) => PaxValue::Enum( + ))), + Fill::RadialGradient(gradient) => PaxValue::Enum(Box::new(( "Fill".to_string(), "RadialGradient".to_string(), vec![gradient.to_pax_value()], - ), + ))), } } } @@ -138,21 +143,21 @@ impl ToPaxValue for Fill { impl ToPaxValue for ColorChannel { fn to_pax_value(self) -> PaxValue { match self { - ColorChannel::Rotation(rot) => PaxValue::Enum( + ColorChannel::Rotation(rot) => PaxValue::Enum(Box::new(( "ColorChannel".to_string(), "Rotation".to_string(), vec![rot.to_pax_value()], - ), - ColorChannel::Percent(perc) => PaxValue::Enum( + ))), + ColorChannel::Percent(perc) => PaxValue::Enum(Box::new(( "ColorChannel".to_string(), "Percent".to_string(), vec![perc.to_pax_value()], - ), - ColorChannel::Integer(num) => PaxValue::Enum( + ))), + ColorChannel::Integer(num) => PaxValue::Enum(Box::new(( "ColorChannel".to_string(), "Integer".to_string(), vec![num.to_pax_value()], - ), + ))), } } } diff --git a/pax-runtime/src/cartridge.rs b/pax-runtime/src/cartridge.rs index debe8f07a..8489b2327 100644 --- a/pax-runtime/src/cartridge.rs +++ b/pax-runtime/src/cartridge.rs @@ -174,6 +174,7 @@ pub trait DefinitionToInstanceTraverser { let rc = Rc::clone(&outer_ref); let mut inner_ref = (*rc).borrow_mut(); let cp = ConditionalProperties::mut_from_pax_any(&mut inner_ref).unwrap(); + let name = format!("conditional (if) expr ({})", expr_ast); cp.boolean_expression .replace_with(Property::computed_with_name( move || { @@ -194,13 +195,14 @@ pub trait DefinitionToInstanceTraverser { coerced }, &dependencies, - "conditional (if) expr", + &name, )); return None; } Some(std::rc::Rc::new(RefCell::new({ let mut properties = crate::ConditionalProperties::default(); + let name = format!("conditional (if) expr ({})", expr_ast); properties.boolean_expression = Property::computed_with_name( move || { let new_value = expr_ast.compute(cloned_stack.clone()).unwrap(); @@ -211,7 +213,7 @@ pub trait DefinitionToInstanceTraverser { coerced }, &dependencies, - "conditional (if) expr", + &name, ); properties.to_pax_any() }))) diff --git a/pax-runtime/src/properties.rs b/pax-runtime/src/properties.rs index 25b301ed1..e8e6a883d 100644 --- a/pax-runtime/src/properties.rs +++ b/pax-runtime/src/properties.rs @@ -500,12 +500,11 @@ impl RuntimePropertiesStackFrame { } } - pub fn resolve_symbol_as_pax_value(&self, symbol: &str) -> Option { - if let Some(e) = self.symbols_within_frame.get(&clean_symbol(symbol)) { - Some(e.get_as_pax_value()) - } else { - self.parent.upgrade()?.resolve_symbol_as_pax_value(symbol) - } + pub fn resolve_symbol(&self, symbol: &str) -> Option { + self.symbols_within_frame + .get(&clean_symbol(symbol)) + .cloned() + .or_else(|| self.parent.upgrade()?.resolve_symbol(symbol)) } } @@ -514,8 +513,8 @@ fn clean_symbol(symbol: &str) -> String { } impl IdentifierResolver for RuntimePropertiesStackFrame { - fn resolve(&self, name: String) -> Result { - self.resolve_symbol_as_pax_value(&name) + fn resolve(&self, name: String) -> Result { + self.resolve_symbol(&name) .ok_or_else(|| format!("Could not resolve symbol {}", name)) } } diff --git a/pax-runtime/src/repeat.rs b/pax-runtime/src/repeat.rs index d8f52508f..5ac11ff7b 100644 --- a/pax-runtime/src/repeat.rs +++ b/pax-runtime/src/repeat.rs @@ -164,15 +164,19 @@ impl RepeatInstance { let Some(cloned_expanded_node) = weak_ref_self.upgrade() else { panic!("ran evaluator after expanded node dropped (repeat elem)") }; - let source = source_expression.get(); - let source_len = if let PaxValue::Range(start, end) = source { - (isize::try_coerce(*end).unwrap() - isize::try_coerce(*start).unwrap()) as usize - } else if let PaxValue::Vec(v) = source { - v.len() - } else { - log::warn!("source is not a vec"); - 0 - }; + let source_len = source_expression.read(|source| { + let source_len = if let PaxValue::Range(start, end) = source { + (isize::try_coerce(*end.clone()).unwrap() + - isize::try_coerce(*start.clone()).unwrap()) + as usize + } else if let PaxValue::Vec(v) = source { + v.len() + } else { + log::warn!("source is not a vec"); + 0 + }; + source_len + }); if source_len == *borrow!(last_length) && i_symbol.read(|i| i == &*borrow!(last_i_sym)) && elem_symbol.read(|e| e == &*borrow!(last_elem_sym)) @@ -192,17 +196,18 @@ impl RepeatInstance { let cp_source_expression = source_expression.clone(); let property_elem = Property::computed_with_name( move || { - let source = cp_source_expression.get(); - if let PaxValue::Range(start, _) = source { - let start = isize::try_coerce(*start).unwrap(); - let elem = (start + i as isize).to_pax_value(); - elem - } else if let PaxValue::Vec(v) = source { - v[i].clone() - } else { - log::warn!("source is not a vec"); - Default::default() - } + cp_source_expression.read(|source| { + if let PaxValue::Range(start, _) = source { + let start = isize::try_coerce(*start.clone()).unwrap(); + let elem = (start + i as isize).to_pax_value(); + elem + } else if let PaxValue::Vec(v) = source { + v[i].clone() + } else { + log::warn!("source is not a vec"); + Default::default() + } + }) }, &[source_expression.untyped()], "repeat elem", diff --git a/pax-std/src/drawing/path.rs b/pax-std/src/drawing/path.rs index ef3cb76e2..ca33e0f14 100644 --- a/pax-std/src/drawing/path.rs +++ b/pax-std/src/drawing/path.rs @@ -188,22 +188,14 @@ impl InstanceNode for PathInstance { Point { x, y }.to_kurbo_point(bounds), ); } - PathElement::Cubic(vals) => { + &PathElement::Cubic(v1, v2, v3, v4) => { let Some(&PathElement::Point(x, y)) = itr_elems.next() else { log::warn!("curve expects to be followed by a point"); return; }; bez_path.curve_to( - Point { - x: vals.0, - y: vals.1, - } - .to_kurbo_point(bounds), - Point { - x: vals.2, - y: vals.3, - } - .to_kurbo_point(bounds), + Point { x: v1, y: v2 }.to_kurbo_point(bounds), + Point { x: v3, y: v4 }.to_kurbo_point(bounds), Point { x, y }.to_kurbo_point(bounds), ); }