1use super::{
12 FillRule, Item, ItemConsts, ItemRc, ItemRendererRef, LineCap, LineJoin, RenderingResult,
13};
14use crate::graphics::{Brush, FittedPath, PathData, PathDataIterator};
15use crate::input::{
16 FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, InternalKeyEvent,
17 KeyEventResult, MouseEvent,
18};
19use crate::item_rendering::CachedRenderingData;
20
21use crate::items::ImageFit;
22use crate::layout::{LayoutInfo, Orientation};
23use crate::lengths::{
24 LogicalBorderRadius, LogicalLength, LogicalPx, LogicalRect, LogicalSize, LogicalVector,
25 RectLengths,
26};
27#[cfg(feature = "rtti")]
28use crate::rtti::*;
29use crate::window::WindowAdapter;
30use crate::{Coord, Property};
31use alloc::boxed::Box;
32use alloc::rc::Rc;
33use const_field_offset::FieldOffsets;
34use core::cell::RefCell;
35use core::pin::Pin;
36use euclid::Point2D;
37use euclid::num::Zero;
38use i_slint_core_macros::*;
39
40#[repr(C)]
42#[derive(FieldOffsets, Default, SlintElement)]
43#[pin]
44pub struct Path {
45 pub elements: Property<PathData>,
46 pub fill: Property<Brush>,
47 pub fill_rule: Property<FillRule>,
48 pub stroke: Property<Brush>,
49 pub stroke_width: Property<LogicalLength>,
50 pub stroke_line_cap: Property<LineCap>,
51 pub stroke_line_join: Property<LineJoin>,
52 pub stroke_miter_limit: Property<f32>,
53 pub viewbox_x: Property<f32>,
54 pub viewbox_y: Property<f32>,
55 pub viewbox_width: Property<f32>,
56 pub viewbox_height: Property<f32>,
57 pub fit: Property<ImageFit>,
58 pub clip: Property<bool>,
59 pub anti_alias: Property<bool>,
60 pub cached_rendering_data: CachedRenderingData,
61 fitted_path: FittedPathBox,
62 tracker: crate::properties::PropertyTracker,
63}
64
65impl Item for Path {
66 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
67
68 fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
69
70 fn layout_info(
71 self: Pin<&Self>,
72 _orientation: Orientation,
73 _cross_axis_constraint: Coord,
74 _window_adapter: &Rc<dyn WindowAdapter>,
75 _self_rc: &ItemRc,
76 ) -> LayoutInfo {
77 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
78 }
79
80 fn input_event_filter_before_children(
81 self: Pin<&Self>,
82 _: &MouseEvent,
83 _window_adapter: &Rc<dyn WindowAdapter>,
84 _self_rc: &ItemRc,
85 _: &mut super::MouseCursorInner,
86 ) -> InputEventFilterResult {
87 InputEventFilterResult::ForwardAndIgnore
88 }
89
90 fn input_event(
91 self: Pin<&Self>,
92 _: &MouseEvent,
93 _window_adapter: &Rc<dyn WindowAdapter>,
94 _self_rc: &ItemRc,
95 _: &mut super::MouseCursorInner,
96 ) -> InputEventResult {
97 InputEventResult::EventIgnored
98 }
99
100 fn capture_key_event(
101 self: Pin<&Self>,
102 _: &InternalKeyEvent,
103 _window_adapter: &Rc<dyn WindowAdapter>,
104 _self_rc: &ItemRc,
105 ) -> KeyEventResult {
106 KeyEventResult::EventIgnored
107 }
108
109 fn key_event(
110 self: Pin<&Self>,
111 _: &InternalKeyEvent,
112 _window_adapter: &Rc<dyn WindowAdapter>,
113 _self_rc: &ItemRc,
114 ) -> KeyEventResult {
115 KeyEventResult::EventIgnored
116 }
117
118 fn focus_event(
119 self: Pin<&Self>,
120 _: &FocusEvent,
121 _window_adapter: &Rc<dyn WindowAdapter>,
122 _self_rc: &ItemRc,
123 ) -> FocusEventResult {
124 FocusEventResult::FocusIgnored
125 }
126
127 fn render(
128 self: Pin<&Self>,
129 backend: &mut ItemRendererRef,
130 self_rc: &ItemRc,
131 size: LogicalSize,
132 ) -> RenderingResult {
133 let clip = self.clip();
134 if clip {
135 (*backend).save_state();
136 (*backend).combine_clip(size.into(), LogicalBorderRadius::zero());
137 }
138 (*backend).draw_path(self, self_rc, size);
139 if clip {
140 (*backend).restore_state();
141 }
142 RenderingResult::ContinueRenderingChildren
143 }
144
145 fn bounding_rect(
146 self: core::pin::Pin<&Self>,
147 _window_adapter: &Rc<dyn WindowAdapter>,
148 _self_rc: &ItemRc,
149 geometry: LogicalRect,
150 ) -> LogicalRect {
151 geometry
152 }
153
154 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
155 false
156 }
157}
158
159impl Path {
160 pub fn fitted_path_events(
164 self: Pin<&Self>,
165 self_rc: &ItemRc,
166 ) -> Option<(LogicalVector, PathDataIterator)> {
167 let mut elements_iter = self.elements().iter()?;
168
169 let fit = self.fit();
170 if fit == ImageFit::Preserve {
171 return (LogicalVector::zero(), elements_iter).into();
172 }
173
174 let stroke_width = self.stroke_width();
175 let geometry = self_rc.geometry();
176 let bounds_width = (geometry.width_length() - stroke_width).max(LogicalLength::zero());
177 let bounds_height = (geometry.height_length() - stroke_width).max(LogicalLength::zero());
178 let offset =
179 LogicalVector::from_lengths(stroke_width / 2 as Coord, stroke_width / 2 as Coord);
180
181 let viewbox_width = self.viewbox_width();
182 let viewbox_height = self.viewbox_height();
183
184 let maybe_viewbox = if viewbox_width > 0. && viewbox_height > 0. {
185 Some(
186 euclid::rect(self.viewbox_x(), self.viewbox_y(), viewbox_width, viewbox_height)
187 .to_box2d(),
188 )
189 } else {
190 None
191 };
192
193 elements_iter.fit(bounds_width.get() as _, bounds_height.get() as _, maybe_viewbox, fit);
194 (offset, elements_iter).into()
195 }
196
197 fn sample_at(
198 self: Pin<&Self>,
199 self_rc: &ItemRc,
200 t: f32,
201 ) -> Option<(Point2D<f32, LogicalPx>, f32)> {
202 if let Some(new_path) =
203 Path::FIELD_OFFSETS.tracker().apply_pin(self).evaluate_if_dirty(|| {
204 let (offset, elements_iter) = self.fitted_path_events(self_rc)?;
205 Some(elements_iter.to_fitted_path(offset))
206 })
207 {
208 *self.fitted_path.borrow_mut() = new_path;
209 }
210 self.fitted_path.borrow().as_ref()?.sample_at(t)
211 }
212
213 pub fn point_at(self: Pin<&Self>, self_rc: &ItemRc, t: f32) -> Point2D<f32, LogicalPx> {
214 self.sample_at(self_rc, t).map(|(pos, _)| pos).unwrap_or_default()
215 }
216 pub fn angle_at(self: Pin<&Self>, self_rc: &ItemRc, t: f32) -> f32 {
217 self.sample_at(self_rc, t).map(|(_, tangent)| tangent).unwrap_or_default()
218 }
219}
220
221impl ItemConsts for Path {
222 const cached_rendering_data_offset: const_field_offset::FieldOffset<Path, CachedRenderingData> =
223 Path::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
224}
225
226struct FittedPathInner(RefCell<Option<FittedPath>>);
227
228#[repr(C)]
230pub struct FittedPathBox(core::cell::Cell<*mut FittedPathInner>);
231
232impl Default for FittedPathBox {
233 fn default() -> Self {
234 FittedPathBox(core::cell::Cell::new(core::ptr::null_mut()))
235 }
236}
237impl FittedPathBox {
238 fn get_or_init(&self) -> &FittedPathInner {
239 if self.0.get().is_null() {
240 self.0.set(Box::leak(Box::new(FittedPathInner(Default::default()))));
241 }
242 unsafe { &*self.0.get() }
244 }
245}
246impl Drop for FittedPathBox {
247 fn drop(&mut self) {
248 let ptr = self.0.get();
249 if !ptr.is_null() {
250 drop(unsafe { Box::from_raw(ptr) });
252 }
253 }
254}
255impl core::ops::Deref for FittedPathBox {
256 type Target = RefCell<Option<FittedPath>>;
257 fn deref(&self) -> &Self::Target {
258 &self.get_or_init().0
259 }
260}
261
262#[cfg(feature = "ffi")]
266#[unsafe(no_mangle)]
267pub unsafe extern "C" fn slint_path_fitted_cache_init(cache: *mut FittedPathBox) {
268 unsafe { core::ptr::write(cache, FittedPathBox::default()) };
269}
270
271#[cfg(feature = "ffi")]
274#[unsafe(no_mangle)]
275pub unsafe extern "C" fn slint_path_fitted_cache_free(cache: *mut FittedPathBox) {
276 unsafe {
277 core::ptr::drop_in_place(cache);
278 }
279}
280
281#[cfg(feature = "ffi")]
282#[unsafe(no_mangle)]
283pub unsafe extern "C" fn slint_path_point_at(
284 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
285 self_index: u32,
286 t: f32,
287) -> crate::lengths::LogicalPoint {
288 let self_rc = ItemRc::new(self_component.clone(), self_index);
289 self_rc.downcast::<Path>().unwrap().as_pin_ref().point_at(&self_rc, t)
290}
291
292#[cfg(feature = "ffi")]
293#[unsafe(no_mangle)]
294pub unsafe extern "C" fn slint_path_angle_at(
295 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
296 self_index: u32,
297 t: f32,
298) -> f32 {
299 let self_rc = ItemRc::new(self_component.clone(), self_index);
300 self_rc.downcast::<Path>().unwrap().as_pin_ref().angle_at(&self_rc, t)
301}