diff --git a/api/sixtyfps-cpp/include/sixtyfps_pathelements.h b/api/sixtyfps-cpp/include/sixtyfps_pathelements.h index 20cebfc15..dae124b2e 100644 --- a/api/sixtyfps-cpp/include/sixtyfps_pathelements.h +++ b/api/sixtyfps-cpp/include/sixtyfps_pathelements.h @@ -5,6 +5,7 @@ namespace sixtyfps { +using internal::types::PathArcTo; using internal::types::PathElement; using internal::types::PathLineTo; diff --git a/api/sixtyfps-node/native/lib.rs b/api/sixtyfps-node/native/lib.rs index 1480e1239..aaae36294 100644 --- a/api/sixtyfps-node/native/lib.rs +++ b/api/sixtyfps-node/native/lib.rs @@ -1,7 +1,7 @@ use core::cell::RefCell; use neon::prelude::*; use sixtyfps_compilerlib::typeregister::Type; -use sixtyfps_corelib::abi::datastructures::{PathElement, PathLineTo, Resource}; +use sixtyfps_corelib::abi::datastructures::{PathArcTo, PathElement, PathLineTo, Resource}; use sixtyfps_corelib::{ComponentRefPin, EvaluationContext}; use std::rc::Rc; @@ -122,6 +122,30 @@ fn to_eval_value<'cx>( } } +fn set_float_property<'cx>( + cx: &mut impl Context<'cx>, + object: &Handle, + name: &str, + value: f32, +) -> NeonResult<()> { + let value = JsNumber::new(cx, value); + let value = value.as_value(cx); + object.set(cx, name, value)?; + Ok(()) +} + +fn set_bool_property<'cx>( + cx: &mut impl Context<'cx>, + object: &Handle, + name: &str, + value: bool, +) -> NeonResult<()> { + let value = JsBoolean::new(cx, value); + let value = value.as_value(cx); + object.set(cx, name, value)?; + Ok(()) +} + fn to_js_value<'cx>( val: sixtyfps_interpreter::Value, cx: &mut impl Context<'cx>, @@ -162,13 +186,28 @@ fn to_js_value<'cx>( match element { PathElement::LineTo(PathLineTo { x, y }) => { - let x = JsNumber::new(cx, *x); - let x = x.as_value(cx); - element_object.set(cx, "x", x)?; + set_float_property(cx, &element_object, "x", *x)?; + set_float_property(cx, &element_object, "y", *y)?; + } + PathElement::ArcTo(PathArcTo { + x, + y, + radius_x, + radius_y, + x_rotation, + large_arc, + sweep, + }) => { + set_float_property(cx, &element_object, "x", *x)?; + set_float_property(cx, &element_object, "y", *y)?; - let y = JsNumber::new(cx, *y); - let y = y.as_value(cx); - element_object.set(cx, "y", y)?; + set_float_property(cx, &element_object, "radius_x", *radius_x)?; + set_float_property(cx, &element_object, "radius_y", *radius_y)?; + + set_float_property(cx, &element_object, "x_rotation", *x_rotation)?; + + set_bool_property(cx, &element_object, "large_arc", *large_arc)?; + set_bool_property(cx, &element_object, "sweep", *sweep)?; } } diff --git a/sixtyfps_compiler/tests/basic/path.60 b/sixtyfps_compiler/tests/basic/path.60 index d51250968..c811e5e62 100644 --- a/sixtyfps_compiler/tests/basic/path.60 +++ b/sixtyfps_compiler/tests/basic/path.60 @@ -6,7 +6,7 @@ TestCase := Rectangle { LineTo { x: 100; y: 0; } LineTo { x: 100; y: 0; } Rectangle {} -// ^error{Rectangle is not allowed within Path. Only LineTo are valid children} +// ^error{Rectangle is not allowed within Path. Only LineTo ArcTo are valid children} } LineTo { x: 100; y: 0; } diff --git a/sixtyfps_compiler/typeregister.rs b/sixtyfps_compiler/typeregister.rs index d4e4315a5..1cba88ac4 100644 --- a/sixtyfps_compiler/typeregister.rs +++ b/sixtyfps_compiler/typeregister.rs @@ -289,6 +289,20 @@ impl TypeRegister { path.additional_accepted_child_types .insert("LineTo".to_owned(), Type::Builtin(Rc::new(line_to))); + let mut arc_to = BuiltinElement::new("ArcTo"); + arc_to.properties.insert("x".to_owned(), Type::Float32); + arc_to.properties.insert("y".to_owned(), Type::Float32); + arc_to.properties.insert("radius_x".to_owned(), Type::Float32); + arc_to.properties.insert("radius_y".to_owned(), Type::Float32); + arc_to.properties.insert("x_rotation".to_owned(), Type::Float32); + arc_to.properties.insert("large_arc".to_owned(), Type::Bool); + arc_to.properties.insert("sweep".to_owned(), Type::Bool); + arc_to.rust_type_constructor = + Some("sixtyfps::re_exports::PathElement::ArcTo(PathArcTo{{}})".into()); + arc_to.cpp_type = Some("sixtyfps::PathArcTo".into()); + path.additional_accepted_child_types + .insert("ArcTo".to_owned(), Type::Builtin(Rc::new(arc_to))); + r.types.insert("Path".to_owned(), Type::Builtin(Rc::new(path))); let mut property_animation = diff --git a/sixtyfps_runtime/corelib/abi/datastructures.rs b/sixtyfps_runtime/corelib/abi/datastructures.rs index 459251564..e790ea403 100644 --- a/sixtyfps_runtime/corelib/abi/datastructures.rs +++ b/sixtyfps_runtime/corelib/abi/datastructures.rs @@ -266,12 +266,44 @@ pub struct PathLineTo { pub y: f32, } +#[repr(C)] +#[derive(FieldOffsets, Default, BuiltinItem, Clone, Debug, PartialEq)] +#[pin] +/// PathArcTo describes the event of moving the cursor on the path across an arc to the specified +/// x/y coordinates, with the specified x/y radius and additional properties. +pub struct PathArcTo { + #[rtti_field] + /// The x coordinate where the arc should end up. + pub x: f32, + #[rtti_field] + /// The y coordinate where the arc should end up. + pub y: f32, + #[rtti_field] + /// The radius on the x-axis of the arc. + pub radius_x: f32, + #[rtti_field] + /// The radius on the y-axis of the arc. + pub radius_y: f32, + #[rtti_field] + /// The rotation along the x-axis of the arc in degress. + pub x_rotation: f32, + #[rtti_field] + /// large_arc indicates whether to take the long or the shorter path to complete the arc. + pub large_arc: bool, + #[rtti_field] + /// sweep indicates the direction of the arc. If true, a clockwise direction is chosen, + /// otherwise counter-clockwise. + pub sweep: bool, +} + #[repr(C)] #[derive(Clone, Debug, PartialEq)] /// PathElement describes a single element on a path, such as move-to, line-to, etc. pub enum PathElement { /// The LineTo variant describes a line. LineTo(PathLineTo), + /// The PathArcTo variant describes an arc. + ArcTo(PathArcTo), } #[repr(C)] diff --git a/sixtyfps_runtime/interpreter/eval.rs b/sixtyfps_runtime/interpreter/eval.rs index 03229a501..9174c8030 100644 --- a/sixtyfps_runtime/interpreter/eval.rs +++ b/sixtyfps_runtime/interpreter/eval.rs @@ -377,6 +377,9 @@ fn convert_path_element( "LineTo" => sixtyfps_corelib::abi::datastructures::PathElement::LineTo( new_struct_with_bindings(&expr_element.bindings, component_type, eval_context), ), + "ArcTo" => sixtyfps_corelib::abi::datastructures::PathElement::ArcTo( + new_struct_with_bindings(&expr_element.bindings, component_type, eval_context), + ), _ => panic!( "Cannot create unsupported path element {}", expr_element.element_type.class_name diff --git a/sixtyfps_runtime/rendering_backends/gl/lib.rs b/sixtyfps_runtime/rendering_backends/gl/lib.rs index e86432555..9af22c36c 100644 --- a/sixtyfps_runtime/rendering_backends/gl/lib.rs +++ b/sixtyfps_runtime/rendering_backends/gl/lib.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use lyon::tessellation::geometry_builder::{BuffersBuilder, VertexBuffers}; use lyon::tessellation::{FillAttributes, FillOptions, FillTessellator}; use sixtyfps_corelib::abi::datastructures::{ - Color, ComponentWindow, ComponentWindowOpaque, PathElement, PathLineTo, Point, Rect, + Color, ComponentWindow, ComponentWindowOpaque, PathArcTo, PathElement, PathLineTo, Point, Rect, RenderingPrimitive, Resource, Size, }; use sixtyfps_corelib::graphics::{ @@ -336,8 +336,11 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder { Some(self.create_glyph_runs(text, font_family, pixel_size, *color)) } RenderingPrimitive::Path { x: _, y: _, elements, fill_color } => { - use lyon::math::Point; - use lyon::path::builder::{Build, FlatPathBuilder}; + use lyon::math::{Angle, Point, Vector}; + use lyon::path::{ + builder::{Build, FlatPathBuilder, SvgBuilder}, + ArcFlags, + }; let mut path_builder = lyon::path::Path::builder().with_svg(); for element in elements.iter() { @@ -345,6 +348,20 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder { PathElement::LineTo(PathLineTo { x, y }) => { path_builder.line_to(Point::new(*x, *y)) } + PathElement::ArcTo(PathArcTo { + x, + y, + radius_x, + radius_y, + x_rotation, + large_arc, + sweep, + }) => path_builder.arc_to( + Vector::new(*radius_x, *radius_y), + Angle::degrees(*x_rotation), + ArcFlags { large_arc: *large_arc, sweep: *sweep }, + Point::new(*x, *y), + ), } } diff --git a/tests/cases/hello.60 b/tests/cases/hello.60 index 507f04d14..42da70773 100644 --- a/tests/cases/hello.60 +++ b/tests/cases/hello.60 @@ -114,6 +114,13 @@ Hello := Rectangle { x: 0; y: 100; } + + ArcTo { + x: 0; + y: 0; + radius_x: 10; + radius_y: 10; + } } }