Skip to main content

i_slint_core/items/
path.rs

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