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 the builtin Path related items.
6
7When adding an item or a property, it needs to be kept in sync with different place.
8Lookup the [`crate::items`] module documentation.
9*/
10
11use super::{FillRule, Item, ItemConsts, ItemRc, ItemRendererRef, LineCap, RenderingResult};
12use crate::graphics::{Brush, PathData, PathDataIterator};
13use crate::input::{
14 FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent,
15 KeyEventResult, MouseEvent,
16};
17use crate::item_rendering::CachedRenderingData;
18
19use crate::layout::{LayoutInfo, Orientation};
20use crate::lengths::{
21 LogicalBorderRadius, LogicalLength, LogicalRect, LogicalSize, LogicalVector, PointLengths,
22 RectLengths,
23};
24#[cfg(feature = "rtti")]
25use crate::rtti::*;
26use crate::window::WindowAdapter;
27use crate::{Coord, Property};
28use alloc::rc::Rc;
29use const_field_offset::FieldOffsets;
30use core::pin::Pin;
31use euclid::num::Zero;
32use i_slint_core_macros::*;
33
34/// The implementation of the `Path` element
35#[repr(C)]
36#[derive(FieldOffsets, Default, SlintElement)]
37#[pin]
38pub struct Path {
39 pub elements: Property<PathData>,
40 pub fill: Property<Brush>,
41 pub fill_rule: Property<FillRule>,
42 pub stroke: Property<Brush>,
43 pub stroke_width: Property<LogicalLength>,
44 pub stroke_line_cap: Property<LineCap>,
45 pub viewbox_x: Property<f32>,
46 pub viewbox_y: Property<f32>,
47 pub viewbox_width: Property<f32>,
48 pub viewbox_height: Property<f32>,
49 pub clip: Property<bool>,
50 pub anti_alias: Property<bool>,
51 pub cached_rendering_data: CachedRenderingData,
52}
53
54impl Item for Path {
55 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
56
57 fn layout_info(
58 self: Pin<&Self>,
59 _orientation: Orientation,
60 _window_adapter: &Rc<dyn WindowAdapter>,
61 ) -> LayoutInfo {
62 LayoutInfo { stretch: 1., ..LayoutInfo::default() }
63 }
64
65 fn input_event_filter_before_children(
66 self: Pin<&Self>,
67 event: MouseEvent,
68 _window_adapter: &Rc<dyn WindowAdapter>,
69 self_rc: &ItemRc,
70 ) -> InputEventFilterResult {
71 if let Some(pos) = event.position() {
72 let geometry = self_rc.geometry();
73 if self.clip()
74 && (pos.x < 0 as _
75 || pos.y < 0 as _
76 || pos.x_length() > geometry.width_length()
77 || pos.y_length() > geometry.height_length())
78 {
79 return InputEventFilterResult::Intercept;
80 }
81 }
82 InputEventFilterResult::ForwardAndIgnore
83 }
84
85 fn input_event(
86 self: Pin<&Self>,
87 _: MouseEvent,
88 _window_adapter: &Rc<dyn WindowAdapter>,
89 _self_rc: &ItemRc,
90 ) -> InputEventResult {
91 InputEventResult::EventIgnored
92 }
93
94 fn key_event(
95 self: Pin<&Self>,
96 _: &KeyEvent,
97 _window_adapter: &Rc<dyn WindowAdapter>,
98 _self_rc: &ItemRc,
99 ) -> KeyEventResult {
100 KeyEventResult::EventIgnored
101 }
102
103 fn focus_event(
104 self: Pin<&Self>,
105 _: &FocusEvent,
106 _window_adapter: &Rc<dyn WindowAdapter>,
107 _self_rc: &ItemRc,
108 ) -> FocusEventResult {
109 FocusEventResult::FocusIgnored
110 }
111
112 fn render(
113 self: Pin<&Self>,
114 backend: &mut ItemRendererRef,
115 self_rc: &ItemRc,
116 size: LogicalSize,
117 ) -> RenderingResult {
118 let clip = self.clip();
119 if clip {
120 (*backend).save_state();
121 (*backend).combine_clip(
122 size.into(),
123 LogicalBorderRadius::zero(),
124 LogicalLength::zero(),
125 );
126 }
127 (*backend).draw_path(self, self_rc, size);
128 if clip {
129 (*backend).restore_state();
130 }
131 RenderingResult::ContinueRenderingChildren
132 }
133
134 fn bounding_rect(
135 self: core::pin::Pin<&Self>,
136 _window_adapter: &Rc<dyn WindowAdapter>,
137 _self_rc: &ItemRc,
138 geometry: LogicalRect,
139 ) -> LogicalRect {
140 geometry
141 }
142
143 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
144 false
145 }
146}
147
148impl Path {
149 /// Returns an iterator of the events of the path and an offset, so that the
150 /// shape fits into the width/height of the path while respecting the stroke
151 /// width.
152 pub fn fitted_path_events(
153 self: Pin<&Self>,
154 self_rc: &ItemRc,
155 ) -> Option<(LogicalVector, PathDataIterator)> {
156 let mut elements_iter = self.elements().iter()?;
157
158 let stroke_width = self.stroke_width();
159 let geometry = self_rc.geometry();
160 let bounds_width = (geometry.width_length() - stroke_width).max(LogicalLength::zero());
161 let bounds_height = (geometry.height_length() - stroke_width).max(LogicalLength::zero());
162 let offset =
163 LogicalVector::from_lengths(stroke_width / 2 as Coord, stroke_width / 2 as Coord);
164
165 let viewbox_width = self.viewbox_width();
166 let viewbox_height = self.viewbox_height();
167
168 let maybe_viewbox = if viewbox_width > 0. && viewbox_height > 0. {
169 Some(
170 euclid::rect(self.viewbox_x(), self.viewbox_y(), viewbox_width, viewbox_height)
171 .to_box2d(),
172 )
173 } else {
174 None
175 };
176
177 elements_iter.fit(bounds_width.get() as _, bounds_height.get() as _, maybe_viewbox);
178 (offset, elements_iter).into()
179 }
180}
181
182impl ItemConsts for Path {
183 const cached_rendering_data_offset: const_field_offset::FieldOffset<Path, CachedRenderingData> =
184 Path::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
185}
186