mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 12:54:45 +00:00
Allow for fitting paths into a given bounding rectangle
... by applying a transformation. This allows designing a path in some other path design tool and then make it fit using bindings.
This commit is contained in:
parent
79ba943882
commit
992f990fa8
12 changed files with 162 additions and 36 deletions
|
@ -74,6 +74,8 @@ fn main() {
|
||||||
let path_primitive = rendering_primitives_builder.create(RenderingPrimitive::Path {
|
let path_primitive = rendering_primitives_builder.create(RenderingPrimitive::Path {
|
||||||
x: 50.,
|
x: 50.,
|
||||||
y: 300.,
|
y: 300.,
|
||||||
|
width: 0.,
|
||||||
|
height: 0.,
|
||||||
elements: PathData::Elements(SharedArray::from(TRIANGLE_PATH)),
|
elements: PathData::Elements(SharedArray::from(TRIANGLE_PATH)),
|
||||||
fill_color: Color::from_rgb(0, 128, 255),
|
fill_color: Color::from_rgb(0, 128, 255),
|
||||||
stroke_color: Color::BLACK,
|
stroke_color: Color::BLACK,
|
||||||
|
|
|
@ -929,11 +929,19 @@ fn compute_layout(component: &Rc<Component>) -> Vec<String> {
|
||||||
" auto y = {};",
|
" auto y = {};",
|
||||||
compile_expression(&path_layout.y_reference, component)
|
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(" sixtyfps::PathLayoutData pl { ".into());
|
||||||
res.push(" &path,".to_owned());
|
res.push(" &path,".to_owned());
|
||||||
res.push(" {items, std::size(items)},".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(" };".to_owned());
|
||||||
res.push(" sixtyfps::solve_path_layout(&pl);".to_owned());
|
res.push(" sixtyfps::solve_path_layout(&pl);".to_owned());
|
||||||
res.push("}".to_owned());
|
res.push("}".to_owned());
|
||||||
|
|
|
@ -771,6 +771,8 @@ fn compute_layout(component: &Rc<Component>) -> TokenStream {
|
||||||
|
|
||||||
let x_pos = compile_expression(&*path_layout.x_reference, &component);
|
let x_pos = compile_expression(&*path_layout.x_reference, &component);
|
||||||
let y_pos = compile_expression(&*path_layout.y_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! {
|
layouts.push(quote! {
|
||||||
solve_path_layout(&PathLayoutData {
|
solve_path_layout(&PathLayoutData {
|
||||||
|
@ -778,6 +780,8 @@ fn compute_layout(component: &Rc<Component>) -> TokenStream {
|
||||||
elements: &#path,
|
elements: &#path,
|
||||||
x: #x_pos,
|
x: #x_pos,
|
||||||
y: #y_pos,
|
y: #y_pos,
|
||||||
|
width: #width,
|
||||||
|
height: #height,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,11 +53,15 @@ pub struct PathLayout {
|
||||||
pub elements: Vec<ElementRc>,
|
pub elements: Vec<ElementRc>,
|
||||||
pub x_reference: Box<Expression>,
|
pub x_reference: Box<Expression>,
|
||||||
pub y_reference: Box<Expression>,
|
pub y_reference: Box<Expression>,
|
||||||
|
pub width_reference: Box<Expression>,
|
||||||
|
pub height_reference: Box<Expression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExpressionFieldsVisitor for PathLayout {
|
impl ExpressionFieldsVisitor for PathLayout {
|
||||||
fn visit_expressions(&mut self, mut visitor: impl FnMut(&mut Expression)) {
|
fn visit_expressions(&mut self, mut visitor: impl FnMut(&mut Expression)) {
|
||||||
visitor(&mut self.x_reference);
|
visitor(&mut self.x_reference);
|
||||||
visitor(&mut self.y_reference);
|
visitor(&mut self.y_reference);
|
||||||
|
visitor(&mut self.width_reference);
|
||||||
|
visitor(&mut self.height_reference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,17 +29,16 @@ pub fn lower_layouts(component: &Rc<Component>, diag: &mut Diagnostics) {
|
||||||
false
|
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 {
|
let (x_reference, y_reference) = if is_grid_layout || is_path_layout {
|
||||||
(
|
(prop_ref("x"), prop_ref("y"))
|
||||||
Box::new(Expression::PropertyReference(NamedReference {
|
|
||||||
element: Rc::downgrade(&child),
|
|
||||||
name: "x".into(),
|
|
||||||
})),
|
|
||||||
Box::new(Expression::PropertyReference(NamedReference {
|
|
||||||
element: Rc::downgrade(&child),
|
|
||||||
name: "y".into(),
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(Box::new(Expression::Invalid), Box::new(Expression::Invalid))
|
(Box::new(Expression::Invalid), Box::new(Expression::Invalid))
|
||||||
};
|
};
|
||||||
|
@ -101,6 +100,8 @@ pub fn lower_layouts(component: &Rc<Component>, diag: &mut Diagnostics) {
|
||||||
path: path_elements_expr,
|
path: path_elements_expr,
|
||||||
x_reference,
|
x_reference,
|
||||||
y_reference,
|
y_reference,
|
||||||
|
width_reference: prop_ref("width"),
|
||||||
|
height_reference: prop_ref("height"),
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -280,6 +280,8 @@ impl TypeRegister {
|
||||||
let mut path = BuiltinElement::new("Path");
|
let mut path = BuiltinElement::new("Path");
|
||||||
path.properties.insert("x".to_owned(), Type::Float32);
|
path.properties.insert("x".to_owned(), Type::Float32);
|
||||||
path.properties.insert("y".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("fill_color".to_owned(), Type::Color);
|
||||||
path.properties.insert("stroke_color".to_owned(), Type::Color);
|
path.properties.insert("stroke_color".to_owned(), Type::Color);
|
||||||
path.properties.insert("stroke_width".to_owned(), Type::Float32);
|
path.properties.insert("stroke_width".to_owned(), Type::Float32);
|
||||||
|
@ -322,6 +324,8 @@ impl TypeRegister {
|
||||||
let mut path_layout = BuiltinElement::new("PathLayout");
|
let mut path_layout = BuiltinElement::new("PathLayout");
|
||||||
path_layout.properties.insert("x".to_owned(), Type::Float32);
|
path_layout.properties.insert("x".to_owned(), Type::Float32);
|
||||||
path_layout.properties.insert("y".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_layout.properties.insert("commands".to_owned(), Type::String);
|
||||||
path_elements.iter().for_each(|elem| {
|
path_elements.iter().for_each(|elem| {
|
||||||
path_layout
|
path_layout
|
||||||
|
|
|
@ -378,13 +378,38 @@ impl<'a> Iterator for ToLyonPathEventIterator<'a> {
|
||||||
|
|
||||||
impl<'a> ExactSizeIterator for ToLyonPathEventIterator<'a> {}
|
impl<'a> ExactSizeIterator for ToLyonPathEventIterator<'a> {}
|
||||||
|
|
||||||
|
struct TransformedLyonPathIterator<EventIt> {
|
||||||
|
it: EventIt,
|
||||||
|
transform: lyon::math::Transform,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<EventIt: Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>>> Iterator
|
||||||
|
for TransformedLyonPathIterator<EventIt>
|
||||||
|
{
|
||||||
|
type Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.it.next().map(|ev| ev.transformed(&self.transform))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.it.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<EventIt: Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>>>
|
||||||
|
ExactSizeIterator for TransformedLyonPathIterator<EventIt>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// LyonPathIterator is a data structure that acts as starting point for iterating
|
/// 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
|
/// 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
|
/// 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.
|
/// elements, then an intermediate lyon path is required/built.
|
||||||
pub struct LyonPathIterator<'a> {
|
pub struct LyonPathIterator<'a> {
|
||||||
it: LyonPathIteratorVariant<'a>,
|
it: LyonPathIteratorVariant<'a>,
|
||||||
|
transform: Option<lyon::math::Transform>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LyonPathIteratorVariant<'a> {
|
enum LyonPathIteratorVariant<'a> {
|
||||||
FromPath(lyon::path::Path),
|
FromPath(lyon::path::Path),
|
||||||
FromEvents(&'a crate::SharedArray<PathEvent>, &'a crate::SharedArray<Point>),
|
FromEvents(&'a crate::SharedArray<PathEvent>, &'a crate::SharedArray<Point>),
|
||||||
|
@ -397,17 +422,42 @@ impl<'a> LyonPathIterator<'a> {
|
||||||
) -> Box<dyn Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a>
|
) -> Box<dyn Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a>
|
||||||
{
|
{
|
||||||
match &self.it {
|
match &self.it {
|
||||||
LyonPathIteratorVariant::FromPath(path) => Box::new(path.iter()),
|
LyonPathIteratorVariant::FromPath(path) => self.apply_transform(path.iter()),
|
||||||
LyonPathIteratorVariant::FromEvents(events, coordinates) => {
|
LyonPathIteratorVariant::FromEvents(events, coordinates) => {
|
||||||
Box::new(ToLyonPathEventIterator {
|
Box::new(self.apply_transform(ToLyonPathEventIterator {
|
||||||
events_it: events.iter(),
|
events_it: events.iter(),
|
||||||
coordinates_it: coordinates.iter(),
|
coordinates_it: coordinates.iter(),
|
||||||
first: coordinates.first(),
|
first: coordinates.first(),
|
||||||
last: coordinates.last(),
|
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<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a,
|
||||||
|
) -> Box<dyn Iterator<Item = lyon::path::Event<lyon::math::Point, lyon::math::Point>> + 'a>
|
||||||
|
{
|
||||||
|
match self.transform {
|
||||||
|
Some(transform) => Box::new(TransformedLyonPathIterator { it: event_it, transform }),
|
||||||
|
None => Box::new(event_it),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -443,6 +493,7 @@ impl PathData {
|
||||||
LyonPathIteratorVariant::FromEvents(events, coordinates)
|
LyonPathIteratorVariant::FromEvents(events, coordinates)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
transform: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,6 +605,8 @@ pub enum RenderingPrimitive {
|
||||||
Path {
|
Path {
|
||||||
x: f32,
|
x: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
elements: crate::PathData,
|
elements: crate::PathData,
|
||||||
fill_color: Color,
|
fill_color: Color,
|
||||||
stroke_color: Color,
|
stroke_color: Color,
|
||||||
|
|
|
@ -268,6 +268,8 @@ pub use crate::abi::datastructures::TouchAreaVTable;
|
||||||
pub struct Path {
|
pub struct Path {
|
||||||
pub x: Property<f32>,
|
pub x: Property<f32>,
|
||||||
pub y: Property<f32>,
|
pub y: Property<f32>,
|
||||||
|
pub width: Property<f32>,
|
||||||
|
pub height: Property<f32>,
|
||||||
pub elements: Property<PathData>,
|
pub elements: Property<PathData>,
|
||||||
pub fill_color: Property<Color>,
|
pub fill_color: Property<Color>,
|
||||||
pub stroke_color: Property<Color>,
|
pub stroke_color: Property<Color>,
|
||||||
|
@ -291,6 +293,8 @@ impl Item for Path {
|
||||||
RenderingPrimitive::Path {
|
RenderingPrimitive::Path {
|
||||||
x: Self::field_offsets().x.apply_pin(self).get(context),
|
x: Self::field_offsets().x.apply_pin(self).get(context),
|
||||||
y: Self::field_offsets().y.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),
|
elements: Self::field_offsets().elements.apply_pin(self).get(context),
|
||||||
fill_color: Self::field_offsets().fill_color.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),
|
stroke_color: Self::field_offsets().stroke_color.apply_pin(self).get(context),
|
||||||
|
|
|
@ -163,6 +163,8 @@ pub struct PathLayoutData<'a> {
|
||||||
pub items: Slice<'a, PathLayoutItemData<'a>>,
|
pub items: Slice<'a, PathLayoutItemData<'a>>,
|
||||||
pub x: Coord,
|
pub x: Coord,
|
||||||
pub y: Coord,
|
pub y: Coord,
|
||||||
|
pub width: Coord,
|
||||||
|
pub height: Coord,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -178,7 +180,7 @@ pub extern "C" fn solve_path_layout(data: &PathLayoutData) {
|
||||||
use lyon::geom::*;
|
use lyon::geom::*;
|
||||||
use lyon::path::iterator::PathIterator;
|
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;
|
let tolerance = 0.01;
|
||||||
|
|
||||||
|
|
|
@ -557,6 +557,12 @@ unsafe extern "C" fn compute_layout(component: ComponentRefPin, eval_context: &E
|
||||||
let component_type =
|
let component_type =
|
||||||
&*(component.get_vtable() as *const ComponentVTable as *const ComponentDescription);
|
&*(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 {
|
for it in &component_type.original.layout_constraints.borrow().grids {
|
||||||
use sixtyfps_corelib::layout::*;
|
use sixtyfps_corelib::layout::*;
|
||||||
|
|
||||||
|
@ -604,21 +610,13 @@ unsafe extern "C" fn compute_layout(component: ComponentRefPin, eval_context: &E
|
||||||
.unwrap()
|
.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 {
|
solve_grid_layout(&GridLayoutData {
|
||||||
row_constraint: Slice::from(row_constraint.as_slice()),
|
row_constraint: Slice::from(row_constraint.as_slice()),
|
||||||
col_constraint: Slice::from(col_constraint.as_slice()),
|
col_constraint: Slice::from(col_constraint.as_slice()),
|
||||||
width: within_prop("width"),
|
width: within_prop("width"),
|
||||||
height: within_prop("height"),
|
height: within_prop("height"),
|
||||||
x,
|
x: resolve_prop_ref(&it.x_reference),
|
||||||
y,
|
y: resolve_prop_ref(&it.y_reference),
|
||||||
cells: Slice::from(cells.as_slice()),
|
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 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 {
|
solve_path_layout(&PathLayoutData {
|
||||||
items: Slice::from(items.as_slice()),
|
items: Slice::from(items.as_slice()),
|
||||||
elements: &path_elements,
|
elements: &path_elements,
|
||||||
x,
|
x: resolve_prop_ref(&it.x_reference),
|
||||||
y,
|
y: resolve_prop_ref(&it.y_reference),
|
||||||
|
width: resolve_prop_ref(&it.width_reference),
|
||||||
|
height: resolve_prop_ref(&it.height_reference),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -347,6 +347,8 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
|
||||||
RenderingPrimitive::Path {
|
RenderingPrimitive::Path {
|
||||||
x: _,
|
x: _,
|
||||||
y: _,
|
y: _,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
elements,
|
elements,
|
||||||
fill_color,
|
fill_color,
|
||||||
stroke_color,
|
stroke_color,
|
||||||
|
@ -354,7 +356,7 @@ impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder {
|
||||||
} => {
|
} => {
|
||||||
let mut primitives = SmallVec::new();
|
let mut primitives = SmallVec::new();
|
||||||
|
|
||||||
let path_iter = elements.iter();
|
let path_iter = elements.iter().fitted(*width, *height);
|
||||||
|
|
||||||
if *fill_color != Color::TRANSPARENT {
|
if *fill_color != Color::TRANSPARENT {
|
||||||
primitives.extend(
|
primitives.extend(
|
||||||
|
|
50
tests/cases/path_fit.60
Normal file
50
tests/cases/path_fit.60
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue