| 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 | /*! |
| 5 | This module contains the builtin Path related items. |
| 6 | |
| 7 | When adding an item or a property, it needs to be kept in sync with different place. |
| 8 | Lookup the [`crate::items`] module documentation. |
| 9 | */ |
| 10 | |
| 11 | use super::{FillRule, Item, ItemConsts, ItemRc, ItemRendererRef, LineCap, RenderingResult}; |
| 12 | use crate::graphics::{Brush, PathData, PathDataIterator}; |
| 13 | use crate::input::{ |
| 14 | FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent, |
| 15 | KeyEventResult, MouseEvent, |
| 16 | }; |
| 17 | use crate::item_rendering::CachedRenderingData; |
| 18 | |
| 19 | use crate::layout::{LayoutInfo, Orientation}; |
| 20 | use crate::lengths::{ |
| 21 | LogicalBorderRadius, LogicalLength, LogicalRect, LogicalSize, LogicalVector, PointLengths, |
| 22 | RectLengths, |
| 23 | }; |
| 24 | #[cfg (feature = "rtti" )] |
| 25 | use crate::rtti::*; |
| 26 | use crate::window::WindowAdapter; |
| 27 | use crate::{Coord, Property}; |
| 28 | use alloc::rc::Rc; |
| 29 | use const_field_offset::FieldOffsets; |
| 30 | use core::pin::Pin; |
| 31 | use euclid::num::Zero; |
| 32 | use i_slint_core_macros::*; |
| 33 | |
| 34 | /// The implementation of the `Path` element |
| 35 | #[repr (C)] |
| 36 | #[derive (FieldOffsets, Default, SlintElement)] |
| 37 | #[pin] |
| 38 | pub 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 | |
| 54 | impl 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 | |
| 148 | impl 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 | |
| 182 | impl 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 | |