1use crate::debug_log;
9use crate::items::{ImageFit, PathEvent};
10use crate::lengths::{LogicalPx, LogicalVector};
11#[cfg(feature = "rtti")]
12use crate::rtti::*;
13use const_field_offset::FieldOffsets;
14use euclid::Point2D;
15use i_slint_core_macros::*;
16
17#[repr(C)]
18#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
19#[pin]
20pub struct PathMoveTo {
24 #[rtti_field]
25 pub x: f32,
27 #[rtti_field]
28 pub y: f32,
30}
31
32#[repr(C)]
33#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
34#[pin]
35pub struct PathLineTo {
38 #[rtti_field]
39 pub x: f32,
41 #[rtti_field]
42 pub y: f32,
44}
45
46#[repr(C)]
47#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
48#[pin]
49pub struct PathArcTo {
52 #[rtti_field]
53 pub x: f32,
55 #[rtti_field]
56 pub y: f32,
58 #[rtti_field]
59 pub radius_x: f32,
61 #[rtti_field]
62 pub radius_y: f32,
64 #[rtti_field]
65 pub x_rotation: f32,
67 #[rtti_field]
68 pub large_arc: bool,
70 #[rtti_field]
71 pub sweep: bool,
74}
75
76#[repr(C)]
77#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
78#[pin]
79pub struct PathCubicTo {
82 #[rtti_field]
83 pub x: f32,
85 #[rtti_field]
86 pub y: f32,
88 #[rtti_field]
89 pub control_1_x: f32,
91 #[rtti_field]
92 pub control_1_y: f32,
94 #[rtti_field]
95 pub control_2_x: f32,
97 #[rtti_field]
98 pub control_2_y: f32,
100}
101
102#[repr(C)]
103#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
104#[pin]
105pub struct PathQuadraticTo {
108 #[rtti_field]
109 pub x: f32,
111 #[rtti_field]
112 pub y: f32,
114 #[rtti_field]
115 pub control_x: f32,
117 #[rtti_field]
118 pub control_y: f32,
120}
121
122#[repr(C)]
123#[derive(Clone, Debug, PartialEq, derive_more::From)]
124pub enum PathElement {
126 MoveTo(PathMoveTo),
128 LineTo(PathLineTo),
130 ArcTo(PathArcTo),
132 CubicTo(PathCubicTo),
134 QuadraticTo(PathQuadraticTo),
136 Close,
138}
139
140struct ToLyonPathEventIterator<'a> {
141 events_it: core::slice::Iter<'a, PathEvent>,
142 coordinates_it: core::slice::Iter<'a, lyon_path::math::Point>,
143 first: Option<&'a lyon_path::math::Point>,
144 last: Option<&'a lyon_path::math::Point>,
145}
146
147impl Iterator for ToLyonPathEventIterator<'_> {
148 type Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>;
149 fn next(&mut self) -> Option<Self::Item> {
150 use lyon_path::Event;
151
152 self.events_it.next().map(|event| match event {
153 PathEvent::Begin => Event::Begin { at: *self.coordinates_it.next().unwrap() },
154 PathEvent::Line => Event::Line {
155 from: *self.coordinates_it.next().unwrap(),
156 to: *self.coordinates_it.next().unwrap(),
157 },
158 PathEvent::Quadratic => Event::Quadratic {
159 from: *self.coordinates_it.next().unwrap(),
160 ctrl: *self.coordinates_it.next().unwrap(),
161 to: *self.coordinates_it.next().unwrap(),
162 },
163 PathEvent::Cubic => Event::Cubic {
164 from: *self.coordinates_it.next().unwrap(),
165 ctrl1: *self.coordinates_it.next().unwrap(),
166 ctrl2: *self.coordinates_it.next().unwrap(),
167 to: *self.coordinates_it.next().unwrap(),
168 },
169 PathEvent::EndOpen => {
170 Event::End { first: *self.first.unwrap(), last: *self.last.unwrap(), close: false }
171 }
172 PathEvent::EndClosed => {
173 Event::End { first: *self.first.unwrap(), last: *self.last.unwrap(), close: true }
174 }
175 })
176 }
177
178 fn size_hint(&self) -> (usize, Option<usize>) {
179 self.events_it.size_hint()
180 }
181}
182
183impl ExactSizeIterator for ToLyonPathEventIterator<'_> {}
184
185struct TransformedLyonPathIterator<EventIt> {
186 it: EventIt,
187 transform: lyon_path::math::Transform,
188}
189
190impl<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| ev.transformed(&self.transform))
196 }
197
198 fn size_hint(&self) -> (usize, Option<usize>) {
199 self.it.size_hint()
200 }
201}
202
203impl<EventIt: Iterator<Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>>>
204 ExactSizeIterator for TransformedLyonPathIterator<EventIt>
205{
206}
207
208enum LyonPathEventIterator<'a> {
211 FromPath(lyon_path::path::Iter<'a>),
212 FromEvents(ToLyonPathEventIterator<'a>),
213}
214
215impl Iterator for LyonPathEventIterator<'_> {
216 type Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>;
217 fn next(&mut self) -> Option<Self::Item> {
218 match self {
219 Self::FromPath(it) => it.next(),
220 Self::FromEvents(it) => it.next(),
221 }
222 }
223
224 fn size_hint(&self) -> (usize, Option<usize>) {
225 match self {
226 Self::FromPath(it) => it.size_hint(),
227 Self::FromEvents(it) => it.size_hint(),
228 }
229 }
230}
231
232pub struct PathDataIterator {
237 it: LyonPathIteratorVariant,
238 transform: lyon_path::math::Transform,
239}
240
241enum LyonPathIteratorVariant {
242 FromPath(lyon_path::Path),
243 FromEvents(crate::SharedVector<PathEvent>, crate::SharedVector<lyon_path::math::Point>),
244}
245
246impl PathDataIterator {
247 pub fn iter(
249 &self,
250 ) -> impl Iterator<Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>> + '_
251 {
252 TransformedLyonPathIterator {
253 it: match &self.it {
254 LyonPathIteratorVariant::FromPath(path) => {
255 LyonPathEventIterator::FromPath(path.iter())
256 }
257 LyonPathIteratorVariant::FromEvents(events, coordinates) => {
258 LyonPathEventIterator::FromEvents(ToLyonPathEventIterator {
259 events_it: events.iter(),
260 coordinates_it: coordinates.iter(),
261 first: coordinates.first(),
262 last: coordinates.last(),
263 })
264 }
265 },
266 transform: self.transform,
267 }
268 }
269
270 pub fn fit(
274 &mut self,
275 width: f32,
276 height: f32,
277 viewbox: Option<lyon_path::math::Box2D>,
278 style: ImageFit,
279 ) {
280 if width > 0. || height > 0. {
281 let fit_style = match style {
282 ImageFit::Contain => lyon_algorithms::fit::FitStyle::Min,
283 ImageFit::Cover => lyon_algorithms::fit::FitStyle::Max,
284 ImageFit::Fill => lyon_algorithms::fit::FitStyle::Stretch,
285 ImageFit::Preserve => return,
286 };
287 let viewbox =
288 viewbox.unwrap_or_else(|| lyon_algorithms::aabb::bounding_box(self.iter()));
289 self.transform = lyon_algorithms::fit::fit_box(
290 &viewbox,
291 &lyon_path::math::Box2D::from_size(lyon_path::math::Size::new(width, height)),
292 fit_style,
293 );
294 }
295 }
296
297 fn to_lyon_path(&self) -> lyon_path::Path {
298 match &self.it {
299 LyonPathIteratorVariant::FromPath(path) => path.clone().transformed(&self.transform),
300 LyonPathIteratorVariant::FromEvents(..) => self.iter().collect(),
301 }
302 }
303
304 pub fn to_fitted_path(&self, offset: LogicalVector) -> FittedPath {
306 use lyon_algorithms::measure::PathMeasurements;
307
308 let path = self.to_lyon_path();
309 let measurements = PathMeasurements::from_path(&path, 1e-3);
313 FittedPath { path, offset, measurements }
314 }
315}
316
317pub struct FittedPath {
319 path: lyon_path::Path,
320 offset: LogicalVector,
321 measurements: lyon_algorithms::measure::PathMeasurements,
322}
323
324impl FittedPath {
325 pub fn sample_at(&self, t: f32) -> Option<(Point2D<f32, LogicalPx>, f32)> {
327 use lyon_algorithms::measure::SampleType;
328
329 let mut rem = t.rem_euclid(1.);
330 if rem == 0.0 && t != 0.0 {
331 rem = 1.0;
333 }
334 if self.measurements.length() <= 0. {
335 return None;
336 }
337 let mut sampler = self.measurements.create_sampler(&self.path, SampleType::Normalized);
338 let sample = sampler.sample(rem);
339 let pos = Point2D::new(sample.position().x, sample.position().y);
340 let angle = sample.tangent().angle_from_x_axis().to_degrees();
341 Some((pos + self.offset, angle))
342 }
343}
344
345#[repr(C)]
346#[derive(Clone, Debug, PartialEq)]
347#[derive(Default)]
350pub enum PathData {
351 #[default]
353 None,
354 Elements(crate::SharedVector<PathElement>),
356 Events(crate::SharedVector<PathEvent>, crate::SharedVector<lyon_path::math::Point>),
359 Commands(crate::SharedString),
361}
362
363impl PathData {
364 pub fn iter(self) -> Option<PathDataIterator> {
366 PathDataIterator {
367 it: match self {
368 PathData::None => return None,
369 PathData::Elements(elements) => LyonPathIteratorVariant::FromPath(
370 PathData::build_path(elements.as_slice().iter()),
371 ),
372 PathData::Events(events, coordinates) => {
373 LyonPathIteratorVariant::FromEvents(events, coordinates)
374 }
375 PathData::Commands(commands) => {
376 let mut builder = lyon_path::Path::builder();
377 let mut parser = lyon_extra::parser::PathParser::new();
378 match parser.parse(
379 &lyon_extra::parser::ParserOptions::DEFAULT,
380 &mut lyon_extra::parser::Source::new(commands.chars()),
381 &mut builder,
382 ) {
383 Ok(()) => LyonPathIteratorVariant::FromPath(builder.build()),
384 Err(e) => {
385 debug_log!("Error while parsing path commands '{commands}': {e:?}");
386 LyonPathIteratorVariant::FromPath(Default::default())
387 }
388 }
389 }
390 },
391 transform: Default::default(),
392 }
393 .into()
394 }
395
396 fn build_path(element_it: core::slice::Iter<PathElement>) -> lyon_path::Path {
397 use lyon_geom::SvgArc;
398 use lyon_path::ArcFlags;
399 use lyon_path::math::{Angle, Point, Vector};
400 use lyon_path::traits::SvgPathBuilder;
401
402 let mut path_builder = lyon_path::Path::builder().with_svg();
403 for element in element_it {
404 match element {
405 PathElement::MoveTo(PathMoveTo { x, y }) => {
406 path_builder.move_to(Point::new(*x, *y));
407 }
408 PathElement::LineTo(PathLineTo { x, y }) => {
409 path_builder.line_to(Point::new(*x, *y));
410 }
411 PathElement::ArcTo(PathArcTo {
412 x,
413 y,
414 radius_x,
415 radius_y,
416 x_rotation,
417 large_arc,
418 sweep,
419 }) => {
420 let radii = Vector::new(*radius_x, *radius_y);
421 let x_rotation = Angle::degrees(*x_rotation);
422 let flags = ArcFlags { large_arc: *large_arc, sweep: *sweep };
423 let to = Point::new(*x, *y);
424
425 let svg_arc = SvgArc {
426 from: path_builder.current_position(),
427 radii,
428 x_rotation,
429 flags,
430 to,
431 };
432
433 if svg_arc.is_straight_line() {
434 path_builder.line_to(to);
435 } else {
436 path_builder.arc_to(radii, x_rotation, flags, to)
437 }
438 }
439 PathElement::CubicTo(PathCubicTo {
440 x,
441 y,
442 control_1_x,
443 control_1_y,
444 control_2_x,
445 control_2_y,
446 }) => {
447 path_builder.cubic_bezier_to(
448 Point::new(*control_1_x, *control_1_y),
449 Point::new(*control_2_x, *control_2_y),
450 Point::new(*x, *y),
451 );
452 }
453 PathElement::QuadraticTo(PathQuadraticTo { x, y, control_x, control_y }) => {
454 path_builder.quadratic_bezier_to(
455 Point::new(*control_x, *control_y),
456 Point::new(*x, *y),
457 );
458 }
459 PathElement::Close => path_builder.close(),
460 }
461 }
462
463 path_builder.build()
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470 use crate::lengths::LogicalLength;
471 use alloc::vec;
472 use alloc::vec::Vec;
473 use lyon_path::math::{Point, Transform};
474
475 fn elements_path() -> PathData {
480 PathData::Elements(
481 [
482 PathElement::MoveTo(PathMoveTo { x: 0., y: 0. }),
483 PathElement::LineTo(PathLineTo { x: 10., y: 0. }),
484 PathElement::LineTo(PathLineTo { x: 10., y: 10. }),
485 ]
486 .as_slice()
487 .into(),
488 )
489 }
490
491 fn events_path() -> PathData {
492 let events: crate::SharedVector<PathEvent> =
493 [PathEvent::Begin, PathEvent::Line, PathEvent::Line, PathEvent::EndOpen]
494 .as_slice()
495 .into();
496 let coordinates: crate::SharedVector<Point> = [
497 Point::new(0., 0.),
498 Point::new(0., 0.),
499 Point::new(10., 0.),
500 Point::new(10., 0.),
501 Point::new(10., 10.),
502 ]
503 .as_slice()
504 .into();
505 PathData::Events(events, coordinates)
506 }
507
508 fn line_path() -> PathData {
509 PathData::Elements(
510 [
511 PathElement::MoveTo(PathMoveTo { x: 0., y: 0. }),
512 PathElement::LineTo(PathLineTo { x: 10., y: 0. }),
513 ]
514 .as_slice()
515 .into(),
516 )
517 }
518
519 fn points_of(path: &lyon_path::Path) -> Vec<Point> {
520 path.iter()
521 .flat_map(|ev| match ev {
522 lyon_path::Event::Begin { at } => vec![at],
523 lyon_path::Event::Line { from, to } => vec![from, to],
524 lyon_path::Event::Quadratic { from, ctrl, to } => vec![from, ctrl, to],
525 lyon_path::Event::Cubic { from, ctrl1, ctrl2, to } => vec![from, ctrl1, ctrl2, to],
526 lyon_path::Event::End { .. } => vec![],
527 })
528 .collect()
529 }
530
531 #[test]
532 fn to_lyon_path_applies_transform_for_elements_variant() {
533 let mut it = elements_path().iter().unwrap();
534 it.transform = Transform::translation(5., 7.);
535 assert_eq!(
536 points_of(&it.to_lyon_path()),
537 vec![
538 Point::new(5., 7.),
539 Point::new(5., 7.),
540 Point::new(15., 7.),
541 Point::new(15., 7.),
542 Point::new(15., 17.),
543 ]
544 );
545 }
546
547 #[test]
548 fn to_lyon_path_applies_transform_for_events_variant() {
549 let mut it = events_path().iter().unwrap();
550 it.transform = Transform::translation(5., 7.);
551 assert_eq!(
552 points_of(&it.to_lyon_path()),
553 vec![
554 Point::new(5., 7.),
555 Point::new(5., 7.),
556 Point::new(15., 7.),
557 Point::new(15., 7.),
558 Point::new(15., 17.),
559 ]
560 );
561 }
562
563 #[test]
564 fn to_lyon_path_transform_stays_consistent_across_variants_after_fit() {
565 let mut elements_it = elements_path().iter().unwrap();
566 let mut events_it = events_path().iter().unwrap();
567
568 elements_it.fit(100., 50., None, ImageFit::Contain);
571 events_it.fit(100., 50., None, ImageFit::Contain);
572
573 assert_ne!(elements_it.transform, Transform::identity());
574 assert_eq!(elements_it.transform, events_it.transform);
575 assert_eq!(points_of(&elements_it.to_lyon_path()), points_of(&events_it.to_lyon_path()));
576 }
577
578 #[test]
579 fn fitted_path_sample_at_adds_offset_to_position() {
580 let it = line_path().iter().unwrap();
581 let offset = LogicalVector::from_lengths(LogicalLength::new(3.), LogicalLength::new(4.));
582 let fitted = it.to_fitted_path(offset);
583
584 let (start, _) = fitted.sample_at(0.0).unwrap();
585 assert_eq!(start, Point2D::new(3., 4.));
586
587 let (end, _) = fitted.sample_at(1.0).unwrap();
588 assert_eq!(end, Point2D::new(13., 4.));
589 }
590
591 #[test]
592 fn fitted_path_sample_at_wraps_percent_for_open_path() {
593 let it = line_path().iter().unwrap();
594 let fitted = it.to_fitted_path(LogicalVector::default());
595
596 let (end, _) = fitted.sample_at(1.0).unwrap();
599 assert_eq!(end, Point2D::new(10., 0.));
600
601 let (mid, _) = fitted.sample_at(0.5).unwrap();
602 assert_eq!(mid, Point2D::new(5., 0.));
603
604 let (wrapped_up, _) = fitted.sample_at(1.5).unwrap();
606 assert_eq!(wrapped_up, mid);
607 let (wrapped_down, _) = fitted.sample_at(-0.5).unwrap();
608 assert_eq!(wrapped_down, mid);
609 }
610
611 #[test]
612 fn fitted_path_sample_at_returns_none_for_zero_length_path() {
613 let elements = PathData::Elements(
614 [PathElement::MoveTo(PathMoveTo { x: 0., y: 0. })].as_slice().into(),
615 );
616 let it = elements.iter().unwrap();
617 let fitted = it.to_fitted_path(LogicalVector::default());
618
619 assert!(fitted.sample_at(0.0).is_none());
620 assert!(fitted.sample_at(0.5).is_none());
621 }
622}
623
624#[cfg(not(target_arch = "wasm32"))]
625pub(crate) mod ffi {
626 #![allow(unsafe_code)]
627
628 use super::super::*;
629 use super::*;
630 use core::ffi::c_void;
631
632 #[unsafe(no_mangle)]
633 pub unsafe extern "C" fn slint_new_path_elements(
635 out: *mut c_void,
636 first_element: *const PathElement,
637 count: usize,
638 ) {
639 let arr =
640 crate::SharedVector::from(unsafe { core::slice::from_raw_parts(first_element, count) });
641 unsafe { core::ptr::write(out as *mut crate::SharedVector<PathElement>, arr) };
642 }
643
644 #[unsafe(no_mangle)]
645 pub unsafe extern "C" fn slint_new_path_events(
647 out_events: *mut c_void,
648 out_coordinates: *mut c_void,
649 first_event: *const PathEvent,
650 event_count: usize,
651 first_coordinate: *const Point,
652 coordinate_count: usize,
653 ) {
654 let events = crate::SharedVector::from(unsafe {
655 core::slice::from_raw_parts(first_event, event_count)
656 });
657 unsafe { core::ptr::write(out_events as *mut crate::SharedVector<PathEvent>, events) };
658 let coordinates = crate::SharedVector::from(unsafe {
659 core::slice::from_raw_parts(first_coordinate, coordinate_count)
660 });
661 unsafe {
662 core::ptr::write(out_coordinates as *mut crate::SharedVector<Point>, coordinates)
663 };
664 }
665}