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