1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*!
5This module contains path related types and functions for the run-time library.
6*/
7
8use crate::debug_log;
9use crate::items::PathEvent;
10#[cfg(feature = "rtti")]
11use crate::rtti::*;
12use auto_enums::auto_enum;
13use const_field_offset::FieldOffsets;
14use i_slint_core_macros::*;
15
16#[repr(C)]
17#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
18#[pin]
19/// PathMoveTo describes the event of setting the cursor on the path to use as starting
20/// point for sub-sequent events, such as `LineTo`. Moving the cursor also implicitly closes
21/// sub-paths and therefore beings a new sub-path.
22pub struct PathMoveTo {
23 #[rtti_field]
24 /// The x coordinate where the current position should be.
25 pub x: f32,
26 #[rtti_field]
27 /// The y coordinate where the current position should be.
28 pub y: f32,
29}
30
31#[repr(C)]
32#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
33#[pin]
34/// PathLineTo describes the event of moving the cursor on the path to the specified location
35/// along a straight line.
36pub struct PathLineTo {
37 #[rtti_field]
38 /// The x coordinate where the line should go to.
39 pub x: f32,
40 #[rtti_field]
41 /// The y coordinate where the line should go to.
42 pub y: f32,
43}
44
45#[repr(C)]
46#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
47#[pin]
48/// PathArcTo describes the event of moving the cursor on the path across an arc to the specified
49/// x/y coordinates, with the specified x/y radius and additional properties.
50pub struct PathArcTo {
51 #[rtti_field]
52 /// The x coordinate where the arc should end up.
53 pub x: f32,
54 #[rtti_field]
55 /// The y coordinate where the arc should end up.
56 pub y: f32,
57 #[rtti_field]
58 /// The radius on the x-axis of the arc.
59 pub radius_x: f32,
60 #[rtti_field]
61 /// The radius on the y-axis of the arc.
62 pub radius_y: f32,
63 #[rtti_field]
64 /// The rotation along the x-axis of the arc in degrees.
65 pub x_rotation: f32,
66 #[rtti_field]
67 /// large_arc indicates whether to take the long or the shorter path to complete the arc.
68 pub large_arc: bool,
69 #[rtti_field]
70 /// sweep indicates the direction of the arc. If true, a clockwise direction is chosen,
71 /// otherwise counter-clockwise.
72 pub sweep: bool,
73}
74
75#[repr(C)]
76#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
77#[pin]
78/// PathCubicTo describes a smooth Bézier curve from the path's current position
79/// to the specified x/y location, using two control points.
80pub struct PathCubicTo {
81 #[rtti_field]
82 /// The x coordinate of the curve's end point.
83 pub x: f32,
84 #[rtti_field]
85 /// The y coordinate of the curve's end point.
86 pub y: f32,
87 #[rtti_field]
88 /// The x coordinate of the curve's first control point.
89 pub control_1_x: f32,
90 #[rtti_field]
91 /// The y coordinate of the curve's first control point.
92 pub control_1_y: f32,
93 #[rtti_field]
94 /// The x coordinate of the curve's second control point.
95 pub control_2_x: f32,
96 #[rtti_field]
97 /// The y coordinate of the curve's second control point.
98 pub control_2_y: f32,
99}
100
101#[repr(C)]
102#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
103#[pin]
104/// PathCubicTo describes a smooth Bézier curve from the path's current position
105/// to the specified x/y location, using one control points.
106pub struct PathQuadraticTo {
107 #[rtti_field]
108 /// The x coordinate of the curve's end point.
109 pub x: f32,
110 #[rtti_field]
111 /// The y coordinate of the curve's end point.
112 pub y: f32,
113 #[rtti_field]
114 /// The x coordinate of the curve's control point.
115 pub control_x: f32,
116 #[rtti_field]
117 /// The y coordinate of the curve's control point.
118 pub control_y: f32,
119}
120
121#[repr(C)]
122#[derive(Clone, Debug, PartialEq, derive_more::From)]
123/// PathElement describes a single element on a path, such as move-to, line-to, etc.
124pub enum PathElement {
125 /// The MoveTo variant sets the current position on the path.
126 MoveTo(PathMoveTo),
127 /// The LineTo variant describes a line.
128 LineTo(PathLineTo),
129 /// The PathArcTo variant describes an arc.
130 ArcTo(PathArcTo),
131 /// The CubicTo variant describes a Bézier curve with two control points.
132 CubicTo(PathCubicTo),
133 /// The QuadraticTo variant describes a Bézier curve with one control point.
134 QuadraticTo(PathQuadraticTo),
135 /// Indicates that the path should be closed now by connecting to the starting point.
136 Close,
137}
138
139struct ToLyonPathEventIterator<'a> {
140 events_it: core::slice::Iter<'a, PathEvent>,
141 coordinates_it: core::slice::Iter<'a, lyon_path::math::Point>,
142 first: Option<&'a lyon_path::math::Point>,
143 last: Option<&'a lyon_path::math::Point>,
144}
145
146impl Iterator for ToLyonPathEventIterator<'_> {
147 type Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>;
148 fn next(&mut self) -> Option<Self::Item> {
149 use lyon_path::Event;
150
151 self.events_it.next().map(|event| match event {
152 PathEvent::Begin => Event::Begin { at: *self.coordinates_it.next().unwrap() },
153 PathEvent::Line => Event::Line {
154 from: *self.coordinates_it.next().unwrap(),
155 to: *self.coordinates_it.next().unwrap(),
156 },
157 PathEvent::Quadratic => Event::Quadratic {
158 from: *self.coordinates_it.next().unwrap(),
159 ctrl: *self.coordinates_it.next().unwrap(),
160 to: *self.coordinates_it.next().unwrap(),
161 },
162 PathEvent::Cubic => Event::Cubic {
163 from: *self.coordinates_it.next().unwrap(),
164 ctrl1: *self.coordinates_it.next().unwrap(),
165 ctrl2: *self.coordinates_it.next().unwrap(),
166 to: *self.coordinates_it.next().unwrap(),
167 },
168 PathEvent::EndOpen => {
169 Event::End { first: *self.first.unwrap(), last: *self.last.unwrap(), close: false }
170 }
171 PathEvent::EndClosed => {
172 Event::End { first: *self.first.unwrap(), last: *self.last.unwrap(), close: true }
173 }
174 })
175 }
176
177 fn size_hint(&self) -> (usize, Option<usize>) {
178 self.events_it.size_hint()
179 }
180}
181
182impl ExactSizeIterator for ToLyonPathEventIterator<'_> {}
183
184struct TransformedLyonPathIterator<EventIt> {
185 it: EventIt,
186 transform: lyon_path::math::Transform,
187}
188
189impl<
190 EventIt: Iterator<Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>>,
191 > Iterator for TransformedLyonPathIterator<EventIt>
192{
193 type Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>;
194 fn next(&mut self) -> Option<Self::Item> {
195 self.it.next().map(|ev: Event, …>| ev.transformed(&self.transform))
196 }
197
198 fn size_hint(&self) -> (usize, Option<usize>) {
199 self.it.size_hint()
200 }
201}
202
203impl<
204 EventIt: Iterator<Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>>,
205 > ExactSizeIterator for TransformedLyonPathIterator<EventIt>
206{
207}
208
209/// PathDataIterator is a data structure that acts as starting point for iterating
210/// through the low-level events of a path. If the path was constructed from said
211/// events, then it is a very thin abstraction. If the path was created from higher-level
212/// elements, then an intermediate lyon path is required/built.
213pub struct PathDataIterator {
214 it: LyonPathIteratorVariant,
215 transform: lyon_path::math::Transform,
216}
217
218enum LyonPathIteratorVariant {
219 FromPath(lyon_path::Path),
220 FromEvents(crate::SharedVector<PathEvent>, crate::SharedVector<lyon_path::math::Point>),
221}
222
223impl PathDataIterator {
224 /// Create a new iterator for path traversal.
225 #[auto_enum(Iterator)]
226 pub fn iter(
227 &self,
228 ) -> impl Iterator<Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>> + '_
229 {
230 match &self.it {
231 LyonPathIteratorVariant::FromPath(path) => {
232 TransformedLyonPathIterator { it: path.iter(), transform: self.transform }
233 }
234 LyonPathIteratorVariant::FromEvents(events, coordinates) => {
235 TransformedLyonPathIterator {
236 it: ToLyonPathEventIterator {
237 events_it: events.iter(),
238 coordinates_it: coordinates.iter(),
239 first: coordinates.first(),
240 last: coordinates.last(),
241 },
242 transform: self.transform,
243 }
244 }
245 }
246 }
247
248 /// Applies a transformation on the elements this iterator provides that tries to fit everything
249 /// into the specified width/height, respecting the provided viewbox. If no viewbox is specified,
250 /// the bounding rectangle of the path is used.
251 pub fn fit(&mut self, width: f32, height: f32, viewbox: Option<lyon_path::math::Box2D>) {
252 if width > 0. || height > 0. {
253 let viewbox =
254 viewbox.unwrap_or_else(|| lyon_algorithms::aabb::bounding_box(self.iter()));
255 self.transform = lyon_algorithms::fit::fit_box(
256 &viewbox,
257 &lyon_path::math::Box2D::from_size(lyon_path::math::Size::new(width, height)),
258 lyon_algorithms::fit::FitStyle::Min,
259 );
260 }
261 }
262}
263
264#[repr(C)]
265#[derive(Clone, Debug, PartialEq)]
266/// PathData represents a path described by either high-level elements or low-level
267/// events and coordinates.
268pub enum PathData {
269 /// None is the variant when the path is empty.
270 None,
271 /// The Elements variant is used to make a Path from shared arrays of elements.
272 Elements(crate::SharedVector<PathElement>),
273 /// The Events variant describes the path as a series of low-level events and
274 /// associated coordinates.
275 Events(crate::SharedVector<PathEvent>, crate::SharedVector<lyon_path::math::Point>),
276 /// The Commands variant describes the path as a series of SVG encoded path commands.
277 Commands(crate::SharedString),
278}
279
280impl Default for PathData {
281 fn default() -> Self {
282 Self::None
283 }
284}
285
286impl PathData {
287 /// This function returns an iterator that allows traversing the path by means of lyon events.
288 pub fn iter(self) -> Option<PathDataIterator> {
289 PathDataIterator {
290 it: match self {
291 PathData::None => return None,
292 PathData::Elements(elements) => LyonPathIteratorVariant::FromPath(
293 PathData::build_path(elements.as_slice().iter()),
294 ),
295 PathData::Events(events, coordinates) => {
296 LyonPathIteratorVariant::FromEvents(events, coordinates)
297 }
298 PathData::Commands(commands) => {
299 let mut builder = lyon_path::Path::builder();
300 let mut parser = lyon_extra::parser::PathParser::new();
301 match parser.parse(
302 &lyon_extra::parser::ParserOptions::DEFAULT,
303 &mut lyon_extra::parser::Source::new(commands.chars()),
304 &mut builder,
305 ) {
306 Ok(()) => LyonPathIteratorVariant::FromPath(builder.build()),
307 Err(e) => {
308 debug_log!("Error while parsing path commands '{commands}': {e:?}");
309 LyonPathIteratorVariant::FromPath(Default::default())
310 }
311 }
312 }
313 },
314 transform: Default::default(),
315 }
316 .into()
317 }
318
319 fn build_path(element_it: core::slice::Iter<PathElement>) -> lyon_path::Path {
320 use lyon_geom::SvgArc;
321 use lyon_path::math::{Angle, Point, Vector};
322 use lyon_path::traits::SvgPathBuilder;
323 use lyon_path::ArcFlags;
324
325 let mut path_builder = lyon_path::Path::builder().with_svg();
326 for element in element_it {
327 match element {
328 PathElement::MoveTo(PathMoveTo { x, y }) => {
329 path_builder.move_to(Point::new(*x, *y));
330 }
331 PathElement::LineTo(PathLineTo { x, y }) => {
332 path_builder.line_to(Point::new(*x, *y));
333 }
334 PathElement::ArcTo(PathArcTo {
335 x,
336 y,
337 radius_x,
338 radius_y,
339 x_rotation,
340 large_arc,
341 sweep,
342 }) => {
343 let radii = Vector::new(*radius_x, *radius_y);
344 let x_rotation = Angle::degrees(*x_rotation);
345 let flags = ArcFlags { large_arc: *large_arc, sweep: *sweep };
346 let to = Point::new(*x, *y);
347
348 let svg_arc = SvgArc {
349 from: path_builder.current_position(),
350 radii,
351 x_rotation,
352 flags,
353 to,
354 };
355
356 if svg_arc.is_straight_line() {
357 path_builder.line_to(to);
358 } else {
359 path_builder.arc_to(radii, x_rotation, flags, to)
360 }
361 }
362 PathElement::CubicTo(PathCubicTo {
363 x,
364 y,
365 control_1_x,
366 control_1_y,
367 control_2_x,
368 control_2_y,
369 }) => {
370 path_builder.cubic_bezier_to(
371 Point::new(*control_1_x, *control_1_y),
372 Point::new(*control_2_x, *control_2_y),
373 Point::new(*x, *y),
374 );
375 }
376 PathElement::QuadraticTo(PathQuadraticTo { x, y, control_x, control_y }) => {
377 path_builder.quadratic_bezier_to(
378 Point::new(*control_x, *control_y),
379 Point::new(*x, *y),
380 );
381 }
382 PathElement::Close => path_builder.close(),
383 }
384 }
385
386 path_builder.build()
387 }
388}
389
390#[cfg(not(target_arch = "wasm32"))]
391pub(crate) mod ffi {
392 #![allow(unsafe_code)]
393
394 use super::super::*;
395 use super::*;
396
397 #[allow(non_camel_case_types)]
398 type c_void = ();
399
400 #[no_mangle]
401 /// This function is used for the low-level C++ interface to allocate the backing vector for a shared path element array.
402 pub unsafe extern "C" fn slint_new_path_elements(
403 out: *mut c_void,
404 first_element: *const PathElement,
405 count: usize,
406 ) {
407 let arr = crate::SharedVector::from(core::slice::from_raw_parts(first_element, count));
408 core::ptr::write(out as *mut crate::SharedVector<PathElement>, arr);
409 }
410
411 #[no_mangle]
412 /// This function is used for the low-level C++ interface to allocate the backing vector for a shared path event array.
413 pub unsafe extern "C" fn slint_new_path_events(
414 out_events: *mut c_void,
415 out_coordinates: *mut c_void,
416 first_event: *const PathEvent,
417 event_count: usize,
418 first_coordinate: *const Point,
419 coordinate_count: usize,
420 ) {
421 let events =
422 crate::SharedVector::from(core::slice::from_raw_parts(first_event, event_count));
423 core::ptr::write(out_events as *mut crate::SharedVector<PathEvent>, events);
424 let coordinates = crate::SharedVector::from(core::slice::from_raw_parts(
425 first_coordinate,
426 coordinate_count,
427 ));
428 core::ptr::write(out_coordinates as *mut crate::SharedVector<Point>, coordinates);
429 }
430}
431