diff --git a/examples/graphicstest/src/main.rs b/examples/graphicstest/src/main.rs index 36df99558..3b4f61276 100644 --- a/examples/graphicstest/src/main.rs +++ b/examples/graphicstest/src/main.rs @@ -74,6 +74,8 @@ fn main() { let path_primitive = rendering_primitives_builder.create(RenderingPrimitive::Path { x: 50., y: 300., + width: 0., + height: 0., elements: PathData::Elements(SharedArray::from(TRIANGLE_PATH)), fill_color: Color::from_rgb(0, 128, 255), stroke_color: Color::BLACK, diff --git a/sixtyfps_compiler/generator/cpp.rs b/sixtyfps_compiler/generator/cpp.rs index fa7b9a6c9..a87121124 100644 --- a/sixtyfps_compiler/generator/cpp.rs +++ b/sixtyfps_compiler/generator/cpp.rs @@ -929,11 +929,19 @@ fn compute_layout(component: &Rc) -> Vec { " auto y = {};", compile_expression(&path_layout.y_reference, component) )); + res.push(format!( + " auto width = {};", + compile_expression(&path_layout.width_reference, component) + )); + res.push(format!( + " auto height = {};", + compile_expression(&path_layout.height_reference, component) + )); res.push(" sixtyfps::PathLayoutData pl { ".into()); res.push(" &path,".to_owned()); res.push(" {items, std::size(items)},".to_owned()); - res.push(" x, y".to_owned()); + res.push(" x, y, width, height".to_owned()); res.push(" };".to_owned()); res.push(" sixtyfps::solve_path_layout(&pl);".to_owned()); res.push("}".to_owned()); diff --git a/sixtyfps_compiler/generator/rust.rs b/sixtyfps_compiler/generator/rust.rs index 4818ae7a4..ebe4d8113 100644 --- a/sixtyfps_compiler/generator/rust.rs +++ b/sixtyfps_compiler/generator/rust.rs @@ -771,6 +771,8 @@ fn compute_layout(component: &Rc) -> TokenStream { let x_pos = compile_expression(&*path_layout.x_reference, &component); let y_pos = compile_expression(&*path_layout.y_reference, &component); + let width = compile_expression(&*path_layout.width_reference, &component); + let height = compile_expression(&*path_layout.width_reference, &component); layouts.push(quote! { solve_path_layout(&PathLayoutData { @@ -778,6 +780,8 @@ fn compute_layout(component: &Rc) -> TokenStream { elements: &#path, x: #x_pos, y: #y_pos, + width: #width, + height: #height, }); }); } diff --git a/sixtyfps_compiler/layout.rs b/sixtyfps_compiler/layout.rs index 2026559de..b65f50ca1 100644 --- a/sixtyfps_compiler/layout.rs +++ b/sixtyfps_compiler/layout.rs @@ -53,11 +53,15 @@ pub struct PathLayout { pub elements: Vec, pub x_reference: Box, pub y_reference: Box, + pub width_reference: Box, + pub height_reference: Box, } impl ExpressionFieldsVisitor for PathLayout { fn visit_expressions(&mut self, mut visitor: impl FnMut(&mut Expression)) { visitor(&mut self.x_reference); visitor(&mut self.y_reference); + visitor(&mut self.width_reference); + visitor(&mut self.height_reference); } } diff --git a/sixtyfps_compiler/passes/lower_layout.rs b/sixtyfps_compiler/passes/lower_layout.rs index e88522deb..07ad00164 100644 --- a/sixtyfps_compiler/passes/lower_layout.rs +++ b/sixtyfps_compiler/passes/lower_layout.rs @@ -29,17 +29,16 @@ pub fn lower_layouts(component: &Rc, diag: &mut Diagnostics) { false }; + let ref_child = child.clone(); + let prop_ref = move |name: &'static str| { + Box::new(Expression::PropertyReference(NamedReference { + element: Rc::downgrade(&ref_child), + name: name.into(), + })) + }; + let (x_reference, y_reference) = if is_grid_layout || is_path_layout { - ( - Box::new(Expression::PropertyReference(NamedReference { - element: Rc::downgrade(&child), - name: "x".into(), - })), - Box::new(Expression::PropertyReference(NamedReference { - element: Rc::downgrade(&child), - name: "y".into(), - })), - ) + (prop_ref("x"), prop_ref("y")) } else { (Box::new(Expression::Invalid), Box::new(Expression::Invalid)) }; @@ -101,6 +100,8 @@ pub fn lower_layouts(component: &Rc, diag: &mut Diagnostics) { path: path_elements_expr, x_reference, y_reference, + width_reference: prop_ref("width"), + height_reference: prop_ref("height"), }); continue; } else { diff --git a/sixtyfps_compiler/typeregister.rs b/sixtyfps_compiler/typeregister.rs index 506663f03..364ca9f56 100644 --- a/sixtyfps_compiler/typeregister.rs +++ b/sixtyfps_compiler/typeregister.rs @@ -280,6 +280,8 @@ impl TypeRegister { let mut path = BuiltinElement::new("Path"); path.properties.insert("x".to_owned(), Type::Float32); path.properties.insert("y".to_owned(), Type::Float32); + path.properties.insert("width".to_owned(), Type::Float32); + path.properties.insert("height".to_owned(), Type::Float32); path.properties.insert("fill_color".to_owned(), Type::Color); path.properties.insert("stroke_color".to_owned(), Type::Color); path.properties.insert("stroke_width".to_owned(), Type::Float32); @@ -322,6 +324,8 @@ impl TypeRegister { let mut path_layout = BuiltinElement::new("PathLayout"); path_layout.properties.insert("x".to_owned(), Type::Float32); path_layout.properties.insert("y".to_owned(), Type::Float32); + path_layout.properties.insert("width".to_owned(), Type::Float32); + path_layout.properties.insert("height".to_owned(), Type::Float32); path_layout.properties.insert("commands".to_owned(), Type::String); path_elements.iter().for_each(|elem| { path_layout diff --git a/sixtyfps_runtime/corelib/abi/datastructures.rs b/sixtyfps_runtime/corelib/abi/datastructures.rs index 303ffe50a..b70ba9ab2 100644 --- a/sixtyfps_runtime/corelib/abi/datastructures.rs +++ b/sixtyfps_runtime/corelib/abi/datastructures.rs @@ -378,13 +378,38 @@ impl<'a> Iterator for ToLyonPathEventIterator<'a> { impl<'a> ExactSizeIterator for ToLyonPathEventIterator<'a> {} +struct TransformedLyonPathIterator { + it: EventIt, + transform: lyon::math::Transform, +} + +impl>> Iterator + for TransformedLyonPathIterator +{ + type Item = lyon::path::Event; + fn next(&mut self) -> Option { + self.it.next().map(|ev| ev.transformed(&self.transform)) + } + + fn size_hint(&self) -> (usize, Option) { + self.it.size_hint() + } +} + +impl>> + ExactSizeIterator for TransformedLyonPathIterator +{ +} + /// LyonPathIterator is a data structure that acts as starting point for iterating /// through the low-level events of a path. If the path was constructed from said /// events, then it is a very thin abstraction. If the path was created from higher-level /// elements, then an intermediate lyon path is required/built. pub struct LyonPathIterator<'a> { it: LyonPathIteratorVariant<'a>, + transform: Option, } + enum LyonPathIteratorVariant<'a> { FromPath(lyon::path::Path), FromEvents(&'a crate::SharedArray, &'a crate::SharedArray), @@ -397,17 +422,42 @@ impl<'a> LyonPathIterator<'a> { ) -> Box> + 'a> { match &self.it { - LyonPathIteratorVariant::FromPath(path) => Box::new(path.iter()), + LyonPathIteratorVariant::FromPath(path) => self.apply_transform(path.iter()), LyonPathIteratorVariant::FromEvents(events, coordinates) => { - Box::new(ToLyonPathEventIterator { + Box::new(self.apply_transform(ToLyonPathEventIterator { events_it: events.iter(), coordinates_it: coordinates.iter(), first: coordinates.first(), last: coordinates.last(), - }) + })) } } } + + /// This function changes the iterator to be one that applies a transformation to make the path fit into the specified bounds. + pub fn fitted(mut self, width: f32, height: f32) -> LyonPathIterator<'a> { + if width > 0. || height > 0. { + let br = lyon::algorithms::aabb::bounding_rect(self.iter()); + self.transform = Some(lyon::algorithms::fit::fit_rectangle( + &br, + &Rect::from_size(Size::new(width, height)), + lyon::algorithms::fit::FitStyle::Min, + )); + } + + self + } + + fn apply_transform( + &'a self, + event_it: impl Iterator> + 'a, + ) -> Box> + 'a> + { + match self.transform { + Some(transform) => Box::new(TransformedLyonPathIterator { it: event_it, transform }), + None => Box::new(event_it), + } + } } #[repr(C)] @@ -443,6 +493,7 @@ impl PathData { LyonPathIteratorVariant::FromEvents(events, coordinates) } }, + transform: None, } } @@ -554,6 +605,8 @@ pub enum RenderingPrimitive { Path { x: f32, y: f32, + width: f32, + height: f32, elements: crate::PathData, fill_color: Color, stroke_color: Color, diff --git a/sixtyfps_runtime/corelib/abi/primitives.rs b/sixtyfps_runtime/corelib/abi/primitives.rs index f9534ef02..0f89b6080 100644 --- a/sixtyfps_runtime/corelib/abi/primitives.rs +++ b/sixtyfps_runtime/corelib/abi/primitives.rs @@ -268,6 +268,8 @@ pub use crate::abi::datastructures::TouchAreaVTable; pub struct Path { pub x: Property, pub y: Property, + pub width: Property, + pub height: Property, pub elements: Property, pub fill_color: Property, pub stroke_color: Property, @@ -291,6 +293,8 @@ impl Item for Path { RenderingPrimitive::Path { x: Self::field_offsets().x.apply_pin(self).get(context), y: Self::field_offsets().y.apply_pin(self).get(context), + width: Self::field_offsets().width.apply_pin(self).get(context), + height: Self::field_offsets().height.apply_pin(self).get(context), elements: Self::field_offsets().elements.apply_pin(self).get(context), fill_color: Self::field_offsets().fill_color.apply_pin(self).get(context), stroke_color: Self::field_offsets().stroke_color.apply_pin(self).get(context), diff --git a/sixtyfps_runtime/corelib/layout.rs b/sixtyfps_runtime/corelib/layout.rs index d38d5ef9c..f4b014cdc 100644 --- a/sixtyfps_runtime/corelib/layout.rs +++ b/sixtyfps_runtime/corelib/layout.rs @@ -163,6 +163,8 @@ pub struct PathLayoutData<'a> { pub items: Slice<'a, PathLayoutItemData<'a>>, pub x: Coord, pub y: Coord, + pub width: Coord, + pub height: Coord, } #[repr(C)] @@ -178,7 +180,7 @@ pub extern "C" fn solve_path_layout(data: &PathLayoutData) { use lyon::geom::*; use lyon::path::iterator::PathIterator; - let path_iter = data.elements.iter(); + let path_iter = data.elements.iter().fitted(data.width, data.height); let tolerance = 0.01; diff --git a/sixtyfps_runtime/interpreter/dynamic_component.rs b/sixtyfps_runtime/interpreter/dynamic_component.rs index d9ea6a75c..667b1a6a6 100644 --- a/sixtyfps_runtime/interpreter/dynamic_component.rs +++ b/sixtyfps_runtime/interpreter/dynamic_component.rs @@ -557,6 +557,12 @@ unsafe extern "C" fn compute_layout(component: ComponentRefPin, eval_context: &E let component_type = &*(component.get_vtable() as *const ComponentVTable as *const ComponentDescription); + let resolve_prop_ref = |prop_ref: &expression_tree::Expression| { + eval::eval_expression(&prop_ref, &component_type, eval_context) + .try_into() + .unwrap_or_default() + }; + for it in &component_type.original.layout_constraints.borrow().grids { use sixtyfps_corelib::layout::*; @@ -604,21 +610,13 @@ unsafe extern "C" fn compute_layout(component: ComponentRefPin, eval_context: &E .unwrap() }; - let x = eval::eval_expression(&it.x_reference, &component_type, eval_context) - .try_into() - .unwrap_or_default(); - - let y = eval::eval_expression(&it.y_reference, &component_type, eval_context) - .try_into() - .unwrap_or_default(); - solve_grid_layout(&GridLayoutData { row_constraint: Slice::from(row_constraint.as_slice()), col_constraint: Slice::from(col_constraint.as_slice()), width: within_prop("width"), height: within_prop("height"), - x, - y, + x: resolve_prop_ref(&it.x_reference), + y: resolve_prop_ref(&it.y_reference), cells: Slice::from(cells.as_slice()), }); } @@ -643,19 +641,13 @@ unsafe extern "C" fn compute_layout(component: ComponentRefPin, eval_context: &E let path_elements = eval::convert_path(&it.path, component_type, eval_context); - let x = eval::eval_expression(&it.x_reference, &component_type, eval_context) - .try_into() - .unwrap_or_default(); - - let y = eval::eval_expression(&it.y_reference, &component_type, eval_context) - .try_into() - .unwrap_or_default(); - solve_path_layout(&PathLayoutData { items: Slice::from(items.as_slice()), elements: &path_elements, - x, - y, + x: resolve_prop_ref(&it.x_reference), + y: resolve_prop_ref(&it.y_reference), + width: resolve_prop_ref(&it.width_reference), + height: resolve_prop_ref(&it.height_reference), }); } } diff --git a/sixtyfps_runtime/rendering_backends/gl/lib.rs b/sixtyfps_runtime/rendering_backends/gl/lib.rs index 0677d43c3..c7c5b809d 100644 --- a/sixtyfps_runtime/rendering_backends/gl/lib.rs +++ b/sixtyfps_runtime/rendering_backends/gl/lib.rs @@ -347,6 +347,8 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder { RenderingPrimitive::Path { x: _, y: _, + width, + height, elements, fill_color, stroke_color, @@ -354,7 +356,7 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder { } => { let mut primitives = SmallVec::new(); - let path_iter = elements.iter(); + let path_iter = elements.iter().fitted(*width, *height); if *fill_color != Color::TRANSPARENT { primitives.extend( diff --git a/tests/cases/path_fit.60 b/tests/cases/path_fit.60 new file mode 100644 index 000000000..131f6d658 --- /dev/null +++ b/tests/cases/path_fit.60 @@ -0,0 +1,50 @@ +TestCase := Rectangle { + + Text { + text: "The path and the items below should both fit into the red rectangle"; + color: black; + } + + fit_rect := Rectangle { + x: 0; + y: 100; + color: red; + width: 700; + height: 500; + + Path { + x: 0; + y: 0; + width: fit_rect.width; + height: fit_rect.height; + commands: "M 100 300 Q 150 50 1100 400 Q 1450 500 750 500 Q 1000 600 950 600 C 325 575 350 450 150 550 Q 0 600 100 800 C 250 850 300 600 550 850 C 800 850 850 650 2000 700 "; + stroke_color: black; + stroke_width: 2; + } + + PathLayout { + x: 0; + y: 0; + width: fit_rect.width; + height: fit_rect.height; + commands: "M 100 300 Q 150 50 1100 400 Q 1450 500 750 500 Q 1000 600 950 600 C 325 575 350 450 150 550 Q 0 600 100 800 C 250 850 300 600 550 850 C 800 850 850 650 2000 700 "; + + Text { + text: "First item"; + color: black; + } + + Text { + text: "Second item"; + color: black; + } + + Text { + text: "Third item"; + color: black; + } + + } + + } +} \ No newline at end of file