// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 /*! This module contains the builtin Path related items. When adding an item or a property, it needs to be kept in sync with different place. Lookup the [`crate::items`] module documentation. */ use super::{ FillRule, Item, ItemConsts, ItemRc, ItemRendererRef, LineCap, LineJoin, RenderingResult, }; use crate::graphics::{Brush, FittedPath, PathData, PathDataIterator}; use crate::input::{ FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, InternalKeyEvent, KeyEventResult, MouseEvent, }; use crate::item_rendering::CachedRenderingData; use crate::items::ImageFit; use crate::layout::{LayoutInfo, Orientation}; use crate::lengths::{ LogicalBorderRadius, LogicalLength, LogicalPx, LogicalRect, LogicalSize, LogicalVector, RectLengths, }; #[cfg(feature = "rtti")] use crate::rtti::*; use crate::window::WindowAdapter; use crate::{Coord, Property}; use alloc::boxed::Box; use alloc::rc::Rc; use const_field_offset::FieldOffsets; use core::cell::RefCell; use core::pin::Pin; use euclid::Point2D; use euclid::num::Zero; use i_slint_core_macros::*; /// The implementation of the `Path` element #[repr(C)] #[derive(FieldOffsets, Default, SlintElement)] #[pin] pub struct Path { pub elements: Property, pub fill: Property, pub fill_rule: Property, pub stroke: Property, pub stroke_width: Property, pub stroke_line_cap: Property, pub stroke_line_join: Property, pub stroke_miter_limit: Property, pub viewbox_x: Property, pub viewbox_y: Property, pub viewbox_width: Property, pub viewbox_height: Property, pub fit: Property, pub clip: Property, pub anti_alias: Property, pub cached_rendering_data: CachedRenderingData, fitted_path: FittedPathBox, tracker: crate::properties::PropertyTracker, } impl Item for Path { fn init(self: Pin<&Self>, _self_rc: &ItemRc) {} fn deinit(self: Pin<&Self>, _window_adapter: &Rc) {} fn layout_info( self: Pin<&Self>, _orientation: Orientation, _cross_axis_constraint: Coord, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> LayoutInfo { LayoutInfo { stretch: 1., ..LayoutInfo::default() } } fn input_event_filter_before_children( self: Pin<&Self>, _: &MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut super::MouseCursorInner, ) -> InputEventFilterResult { InputEventFilterResult::ForwardAndIgnore } fn input_event( self: Pin<&Self>, _: &MouseEvent, _window_adapter: &Rc, _self_rc: &ItemRc, _: &mut super::MouseCursorInner, ) -> InputEventResult { InputEventResult::EventIgnored } fn capture_key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn key_event( self: Pin<&Self>, _: &InternalKeyEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event( self: Pin<&Self>, _: &FocusEvent, _window_adapter: &Rc, _self_rc: &ItemRc, ) -> FocusEventResult { FocusEventResult::FocusIgnored } fn render( self: Pin<&Self>, backend: &mut ItemRendererRef, self_rc: &ItemRc, size: LogicalSize, ) -> RenderingResult { let clip = self.clip(); if clip { (*backend).save_state(); (*backend).combine_clip(size.into(), LogicalBorderRadius::zero()); } (*backend).draw_path(self, self_rc, size); if clip { (*backend).restore_state(); } RenderingResult::ContinueRenderingChildren } fn bounding_rect( self: core::pin::Pin<&Self>, _window_adapter: &Rc, _self_rc: &ItemRc, geometry: LogicalRect, ) -> LogicalRect { geometry } fn clips_children(self: core::pin::Pin<&Self>) -> bool { false } } impl Path { /// Returns an iterator of the events of the path and an offset, so that the /// shape fits into the width/height of the path while respecting the stroke /// width. pub fn fitted_path_events( self: Pin<&Self>, self_rc: &ItemRc, ) -> Option<(LogicalVector, PathDataIterator)> { let mut elements_iter = self.elements().iter()?; let fit = self.fit(); if fit == ImageFit::Preserve { return (LogicalVector::zero(), elements_iter).into(); } let stroke_width = self.stroke_width(); let geometry = self_rc.geometry(); let bounds_width = (geometry.width_length() - stroke_width).max(LogicalLength::zero()); let bounds_height = (geometry.height_length() - stroke_width).max(LogicalLength::zero()); let offset = LogicalVector::from_lengths(stroke_width / 2 as Coord, stroke_width / 2 as Coord); let viewbox_width = self.viewbox_width(); let viewbox_height = self.viewbox_height(); let maybe_viewbox = if viewbox_width > 0. && viewbox_height > 0. { Some( euclid::rect(self.viewbox_x(), self.viewbox_y(), viewbox_width, viewbox_height) .to_box2d(), ) } else { None }; elements_iter.fit(bounds_width.get() as _, bounds_height.get() as _, maybe_viewbox, fit); (offset, elements_iter).into() } fn sample_at( self: Pin<&Self>, self_rc: &ItemRc, t: f32, ) -> Option<(Point2D, f32)> { if let Some(new_path) = Path::FIELD_OFFSETS.tracker().apply_pin(self).evaluate_if_dirty(|| { let (offset, elements_iter) = self.fitted_path_events(self_rc)?; Some(elements_iter.to_fitted_path(offset)) }) { *self.fitted_path.borrow_mut() = new_path; } self.fitted_path.borrow().as_ref()?.sample_at(t) } pub fn point_at(self: Pin<&Self>, self_rc: &ItemRc, t: f32) -> Point2D { self.sample_at(self_rc, t).map(|(pos, _)| pos).unwrap_or_default() } pub fn angle_at(self: Pin<&Self>, self_rc: &ItemRc, t: f32) -> f32 { self.sample_at(self_rc, t).map(|(_, tangent)| tangent).unwrap_or_default() } } impl ItemConsts for Path { const cached_rendering_data_offset: const_field_offset::FieldOffset = Path::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection(); } struct FittedPathInner(RefCell>); /// Opaque box holding the FittedPath, allocated lazily on first use #[repr(C)] pub struct FittedPathBox(core::cell::Cell<*mut FittedPathInner>); impl Default for FittedPathBox { fn default() -> Self { FittedPathBox(core::cell::Cell::new(core::ptr::null_mut())) } } impl FittedPathBox { fn get_or_init(&self) -> &FittedPathInner { if self.0.get().is_null() { self.0.set(Box::leak(Box::new(FittedPathInner(Default::default())))); } // Safety: the pointer is guaranteed non-null above, and was created from a Box::leak unsafe { &*self.0.get() } } } impl Drop for FittedPathBox { fn drop(&mut self) { let ptr = self.0.get(); if !ptr.is_null() { // Safety: ptr was constructed from a Box::leak in get_or_init drop(unsafe { Box::from_raw(ptr) }); } } } impl core::ops::Deref for FittedPathBox { type Target = RefCell>; fn deref(&self) -> &Self::Target { &self.get_or_init().0 } } /// # Safety /// This must be called using a non-null pointer pointing to a chunk of memory big enough to /// hold a FittedPathBox #[cfg(feature = "ffi")] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_path_fitted_cache_init(cache: *mut FittedPathBox) { unsafe { core::ptr::write(cache, FittedPathBox::default()) }; } /// # Safety /// This must be called using a non-null pointer pointing to an initialized FittedPathBox #[cfg(feature = "ffi")] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_path_fitted_cache_free(cache: *mut FittedPathBox) { unsafe { core::ptr::drop_in_place(cache); } } #[cfg(feature = "ffi")] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_path_point_at( self_component: &vtable::VRc, self_index: u32, t: f32, ) -> crate::lengths::LogicalPoint { let self_rc = ItemRc::new(self_component.clone(), self_index); self_rc.downcast::().unwrap().as_pin_ref().point_at(&self_rc, t) } #[cfg(feature = "ffi")] #[unsafe(no_mangle)] pub unsafe extern "C" fn slint_path_angle_at( self_component: &vtable::VRc, self_index: u32, t: f32, ) -> f32 { let self_rc = ItemRc::new(self_component.clone(), self_index); self_rc.downcast::().unwrap().as_pin_ref().angle_at(&self_rc, t) }