i_slint_core/
item_rendering.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#![warn(missing_docs)]
5//! module for rendering the tree of items
6
7use super::items::*;
8use crate::graphics::{FontRequest, Image, IntRect};
9use crate::item_tree::ItemTreeRc;
10use crate::item_tree::{ItemVisitor, ItemVisitorVTable, VisitChildrenResult};
11use crate::lengths::{
12    LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
13};
14pub use crate::partial_renderer::CachedRenderingData;
15use crate::window::WindowAdapter;
16use crate::{Brush, SharedString};
17#[cfg(feature = "std")]
18use alloc::boxed::Box;
19use alloc::rc::Rc;
20#[cfg(feature = "std")]
21use core::cell::RefCell;
22use core::pin::Pin;
23#[cfg(feature = "std")]
24use std::collections::HashMap;
25use vtable::VRc;
26
27/// A per-item cache.
28///
29/// Cache rendering information for a given item.
30///
31/// Use [`ItemCache::get_or_update_cache_entry`] to get or update the items, the
32/// cache is automatically invalided when the property gets dirty.
33/// [`ItemCache::component_destroyed`] must be called to clear the cache for that
34/// component.
35#[cfg(feature = "std")]
36pub struct ItemCache<T> {
37    /// The pointer is a pointer to a component
38    map: RefCell<HashMap<*const vtable::Dyn, HashMap<u32, crate::graphics::CachedGraphicsData<T>>>>,
39    /// Track if the window scale factor changes; used to clear the cache if necessary.
40    window_scale_factor_tracker: Pin<Box<crate::properties::PropertyTracker>>,
41}
42
43#[cfg(feature = "std")]
44impl<T> Default for ItemCache<T> {
45    fn default() -> Self {
46        Self { map: Default::default(), window_scale_factor_tracker: Box::pin(Default::default()) }
47    }
48}
49
50#[cfg(feature = "std")]
51impl<T: Clone> ItemCache<T> {
52    /// Returns the cached value associated to the `item_rc` if it is still valid.
53    /// Otherwise call the `update_fn` to compute that value, and track property access
54    /// so it is automatically invalided when property becomes dirty.
55    pub fn get_or_update_cache_entry(&self, item_rc: &ItemRc, update_fn: impl FnOnce() -> T) -> T {
56        let component = &(**item_rc.item_tree()) as *const _;
57        let mut borrowed = self.map.borrow_mut();
58        match borrowed.entry(component).or_default().entry(item_rc.index()) {
59            std::collections::hash_map::Entry::Occupied(mut entry) => {
60                let mut tracker = entry.get_mut().dependency_tracker.take();
61                drop(borrowed);
62                let maybe_new_data = tracker
63                    .get_or_insert_with(|| Box::pin(Default::default()))
64                    .as_ref()
65                    .evaluate_if_dirty(update_fn);
66                let mut borrowed = self.map.borrow_mut();
67                let e = borrowed.get_mut(&component).unwrap().get_mut(&item_rc.index()).unwrap();
68                e.dependency_tracker = tracker;
69                if let Some(new_data) = maybe_new_data {
70                    e.data = new_data.clone();
71                    new_data
72                } else {
73                    e.data.clone()
74                }
75            }
76            std::collections::hash_map::Entry::Vacant(_) => {
77                drop(borrowed);
78                let new_entry = crate::graphics::CachedGraphicsData::new(update_fn);
79                let data = new_entry.data.clone();
80                self.map
81                    .borrow_mut()
82                    .get_mut(&component)
83                    .unwrap()
84                    .insert(item_rc.index(), new_entry);
85                data
86            }
87        }
88    }
89
90    /// Returns the cached value associated with the `item_rc` if it is in the cache
91    /// and still valid.
92    pub fn with_entry<U>(
93        &self,
94        item_rc: &ItemRc,
95        callback: impl FnOnce(&T) -> Option<U>,
96    ) -> Option<U> {
97        let component = &(**item_rc.item_tree()) as *const _;
98        self.map
99            .borrow()
100            .get(&component)
101            .and_then(|per_component_entries| per_component_entries.get(&item_rc.index()))
102            .and_then(|entry| callback(&entry.data))
103    }
104
105    /// Clears the cache if the window's scale factor has changed since the last call.
106    pub fn clear_cache_if_scale_factor_changed(&self, window: &crate::api::Window) {
107        if self.window_scale_factor_tracker.is_dirty() {
108            self.window_scale_factor_tracker
109                .as_ref()
110                .evaluate_as_dependency_root(|| window.scale_factor());
111            self.clear_all();
112        }
113    }
114
115    /// free the whole cache
116    pub fn clear_all(&self) {
117        self.map.borrow_mut().clear();
118    }
119
120    /// Function that must be called when a component is destroyed.
121    ///
122    /// Usually can be called from [`crate::window::WindowAdapterInternal::unregister_item_tree`]
123    pub fn component_destroyed(&self, component: crate::item_tree::ItemTreeRef) {
124        let component_ptr: *const _ =
125            crate::item_tree::ItemTreeRef::as_ptr(component).cast().as_ptr();
126        self.map.borrow_mut().remove(&component_ptr);
127    }
128
129    /// free the cache for a given item
130    pub fn release(&self, item_rc: &ItemRc) {
131        let component = &(**item_rc.item_tree()) as *const _;
132        if let Some(sub) = self.map.borrow_mut().get_mut(&component) {
133            sub.remove(&item_rc.index());
134        }
135    }
136
137    /// Returns true if there are no entries in the cache; false otherwise.
138    pub fn is_empty(&self) -> bool {
139        self.map.borrow().is_empty()
140    }
141}
142
143/// Renders the children of the item with the specified index into the renderer.
144pub fn render_item_children(
145    renderer: &mut dyn ItemRenderer,
146    component: &ItemTreeRc,
147    index: isize,
148    window_adapter: &Rc<dyn WindowAdapter>,
149) {
150    let mut actual_visitor =
151        |component: &ItemTreeRc, index: u32, item: Pin<ItemRef>| -> VisitChildrenResult {
152            renderer.save_state();
153            let item_rc = ItemRc::new(component.clone(), index);
154
155            let (do_draw, item_geometry) = renderer.filter_item(&item_rc, window_adapter);
156
157            let item_origin = item_geometry.origin;
158            renderer.translate(item_origin.to_vector());
159
160            // Don't render items that are clipped, with the exception of the Clip or Flickable since
161            // they themselves clip their content.
162            let render_result = if do_draw
163               || item.as_ref().clips_children()
164               // HACK, the geometry of the box shadow does not include the shadow, because when the shadow is the root for repeated elements it would translate the children
165               || ItemRef::downcast_pin::<BoxShadow>(item).is_some()
166            {
167                item.as_ref().render(
168                    &mut (renderer as &mut dyn ItemRenderer),
169                    &item_rc,
170                    item_geometry.size,
171                )
172            } else {
173                RenderingResult::ContinueRenderingChildren
174            };
175
176            if matches!(render_result, RenderingResult::ContinueRenderingChildren) {
177                render_item_children(renderer, component, index as isize, window_adapter);
178            }
179            renderer.restore_state();
180            VisitChildrenResult::CONTINUE
181        };
182    vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
183    VRc::borrow_pin(component).as_ref().visit_children_item(
184        index,
185        crate::item_tree::TraversalOrder::BackToFront,
186        actual_visitor,
187    );
188}
189
190/// Renders the tree of items that component holds, using the specified renderer. Rendering is done
191/// relative to the specified origin.
192pub fn render_component_items(
193    component: &ItemTreeRc,
194    renderer: &mut dyn ItemRenderer,
195    origin: LogicalPoint,
196    window_adapter: &Rc<dyn WindowAdapter>,
197) {
198    renderer.save_state();
199    renderer.translate(origin.to_vector());
200
201    render_item_children(renderer, component, -1, window_adapter);
202
203    renderer.restore_state();
204}
205
206/// Compute the bounding rect of all children. This does /not/ include item's own bounding rect. Remember to run this
207/// via `evaluate_no_tracking`.
208pub fn item_children_bounding_rect(
209    component: &ItemTreeRc,
210    index: isize,
211    clip_rect: &LogicalRect,
212) -> LogicalRect {
213    item_children_bounding_rect_inner(component, index, clip_rect, Default::default())
214}
215
216fn item_children_bounding_rect_inner(
217    component: &ItemTreeRc,
218    index: isize,
219    clip_rect: &LogicalRect,
220    transform: crate::lengths::ItemTransform,
221) -> LogicalRect {
222    let mut bounding_rect = LogicalRect::zero();
223
224    let mut actual_visitor =
225        |component: &ItemTreeRc, index: u32, item: Pin<ItemRef>| -> VisitChildrenResult {
226            let item_geometry = transform.outer_transformed_rect(
227                &ItemTreeRc::borrow_pin(component).as_ref().item_geometry(index).cast(),
228            );
229            let children_transform = ItemRc::new(component.clone(), index)
230                .children_transform()
231                .unwrap_or_default()
232                .then_translate(item_geometry.origin.to_vector());
233
234            let offset: LogicalPoint = item_geometry.origin.cast();
235            let local_clip_rect = clip_rect.translate(-offset.to_vector());
236
237            if let Some(clipped_item_geometry) = item_geometry.intersection(&clip_rect.cast()) {
238                bounding_rect = bounding_rect.union(&clipped_item_geometry.cast());
239            }
240
241            if !item.as_ref().clips_children() {
242                bounding_rect = bounding_rect.union(&item_children_bounding_rect_inner(
243                    component,
244                    index as isize,
245                    &local_clip_rect,
246                    transform.then(&children_transform),
247                ));
248            }
249            VisitChildrenResult::CONTINUE
250        };
251    vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
252    VRc::borrow_pin(component).as_ref().visit_children_item(
253        index,
254        crate::item_tree::TraversalOrder::BackToFront,
255        actual_visitor,
256    );
257
258    bounding_rect
259}
260
261/// Trait for an item that represent a Rectangle to the Renderer
262#[allow(missing_docs)]
263pub trait RenderRectangle {
264    fn background(self: Pin<&Self>) -> Brush;
265}
266
267/// Trait for an item that represent a Rectangle with a border to the Renderer
268#[allow(missing_docs)]
269pub trait RenderBorderRectangle {
270    fn background(self: Pin<&Self>) -> Brush;
271    fn border_width(self: Pin<&Self>) -> LogicalLength;
272    fn border_radius(self: Pin<&Self>) -> LogicalBorderRadius;
273    fn border_color(self: Pin<&Self>) -> Brush;
274}
275
276/// Trait for an item that represents an Image towards the renderer
277#[allow(missing_docs)]
278pub trait RenderImage {
279    fn target_size(self: Pin<&Self>) -> LogicalSize;
280    fn source(self: Pin<&Self>) -> Image;
281    fn source_clip(self: Pin<&Self>) -> Option<IntRect>;
282    fn image_fit(self: Pin<&Self>) -> ImageFit;
283    fn rendering(self: Pin<&Self>) -> ImageRendering;
284    fn colorize(self: Pin<&Self>) -> Brush;
285    fn alignment(self: Pin<&Self>) -> (ImageHorizontalAlignment, ImageVerticalAlignment);
286    fn tiling(self: Pin<&Self>) -> (ImageTiling, ImageTiling);
287}
288
289/// Trait for an item that represents an Text towards the renderer
290#[allow(missing_docs)]
291pub trait RenderText {
292    fn target_size(self: Pin<&Self>) -> LogicalSize;
293    fn text(self: Pin<&Self>) -> SharedString;
294    fn font_request(self: Pin<&Self>, self_rc: &ItemRc) -> FontRequest;
295    fn color(self: Pin<&Self>) -> Brush;
296    fn alignment(self: Pin<&Self>) -> (TextHorizontalAlignment, TextVerticalAlignment);
297    fn wrap(self: Pin<&Self>) -> TextWrap;
298    fn overflow(self: Pin<&Self>) -> TextOverflow;
299    fn letter_spacing(self: Pin<&Self>) -> LogicalLength;
300    fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle);
301    fn is_markdown(self: Pin<&Self>) -> bool;
302}
303
304impl RenderText for (SharedString, Brush) {
305    fn target_size(self: Pin<&Self>) -> LogicalSize {
306        LogicalSize::default()
307    }
308
309    fn text(self: Pin<&Self>) -> SharedString {
310        self.0.clone()
311    }
312
313    fn font_request(self: Pin<&Self>, _self_rc: &ItemRc) -> crate::graphics::FontRequest {
314        Default::default()
315    }
316
317    fn color(self: Pin<&Self>) -> Brush {
318        self.1.clone()
319    }
320
321    fn alignment(
322        self: Pin<&Self>,
323    ) -> (crate::items::TextHorizontalAlignment, crate::items::TextVerticalAlignment) {
324        Default::default()
325    }
326
327    fn wrap(self: Pin<&Self>) -> crate::items::TextWrap {
328        Default::default()
329    }
330
331    fn overflow(self: Pin<&Self>) -> crate::items::TextOverflow {
332        Default::default()
333    }
334
335    fn letter_spacing(self: Pin<&Self>) -> LogicalLength {
336        LogicalLength::default()
337    }
338
339    fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
340        Default::default()
341    }
342
343    fn is_markdown(self: Pin<&Self>) -> bool {
344        false
345    }
346}
347
348/// Trait used to render each items.
349///
350/// The item needs to be rendered relative to its (x,y) position. For example,
351/// draw_rectangle should draw a rectangle in `(pos.x + rect.x, pos.y + rect.y)`
352#[allow(missing_docs)]
353pub trait ItemRenderer {
354    fn draw_rectangle(
355        &mut self,
356        rect: Pin<&dyn RenderRectangle>,
357        _self_rc: &ItemRc,
358        _size: LogicalSize,
359        _cache: &CachedRenderingData,
360    );
361    fn draw_border_rectangle(
362        &mut self,
363        rect: Pin<&dyn RenderBorderRectangle>,
364        _self_rc: &ItemRc,
365        _size: LogicalSize,
366        _cache: &CachedRenderingData,
367    );
368    fn draw_window_background(
369        &mut self,
370        rect: Pin<&dyn RenderRectangle>,
371        self_rc: &ItemRc,
372        size: LogicalSize,
373        cache: &CachedRenderingData,
374    );
375    fn draw_image(
376        &mut self,
377        image: Pin<&dyn RenderImage>,
378        _self_rc: &ItemRc,
379        _size: LogicalSize,
380        _cache: &CachedRenderingData,
381    );
382    fn draw_text(
383        &mut self,
384        text: Pin<&dyn RenderText>,
385        _self_rc: &ItemRc,
386        _size: LogicalSize,
387        _cache: &CachedRenderingData,
388    );
389    fn draw_text_input(
390        &mut self,
391        text_input: Pin<&TextInput>,
392        _self_rc: &ItemRc,
393        _size: LogicalSize,
394    );
395    #[cfg(feature = "std")]
396    fn draw_path(&mut self, path: Pin<&Path>, _self_rc: &ItemRc, _size: LogicalSize);
397    fn draw_box_shadow(
398        &mut self,
399        box_shadow: Pin<&BoxShadow>,
400        _self_rc: &ItemRc,
401        _size: LogicalSize,
402    );
403    fn visit_opacity(
404        &mut self,
405        opacity_item: Pin<&Opacity>,
406        _self_rc: &ItemRc,
407        _size: LogicalSize,
408    ) -> RenderingResult {
409        self.apply_opacity(opacity_item.opacity());
410        RenderingResult::ContinueRenderingChildren
411    }
412    fn visit_layer(
413        &mut self,
414        _layer_item: Pin<&Layer>,
415        _self_rc: &ItemRc,
416        _size: LogicalSize,
417    ) -> RenderingResult {
418        // Not supported
419        RenderingResult::ContinueRenderingChildren
420    }
421
422    // Apply the bounds of the Clip element, if enabled. The default implementation calls
423    // combine_clip, but the render may choose an alternate way of implementing the clip.
424    // For example the GL backend uses a layered rendering approach.
425    fn visit_clip(
426        &mut self,
427        clip_item: Pin<&Clip>,
428        _item_rc: &ItemRc,
429        size: LogicalSize,
430    ) -> RenderingResult {
431        if clip_item.clip() {
432            let clip_region_valid = self.combine_clip(
433                LogicalRect::new(LogicalPoint::default(), size),
434                clip_item.logical_border_radius(),
435                clip_item.border_width(),
436            );
437
438            // If clipping is enabled but the clip element is outside the visible range, then we don't
439            // need to bother doing anything, not even rendering the children.
440            if !clip_region_valid {
441                return RenderingResult::ContinueRenderingWithoutChildren;
442            }
443        }
444        RenderingResult::ContinueRenderingChildren
445    }
446
447    /// Clip the further call until restore_state.
448    /// radius/border_width can be used for border rectangle clip.
449    /// (FIXME: consider removing radius/border_width and have another  function that take a path instead)
450    /// Returns a boolean indicating the state of the new clip region: true if the clip region covers
451    /// an area; false if the clip region is empty.
452    fn combine_clip(
453        &mut self,
454        rect: LogicalRect,
455        radius: LogicalBorderRadius,
456        border_width: LogicalLength,
457    ) -> bool;
458    /// Get the current clip bounding box in the current transformed coordinate.
459    fn get_current_clip(&self) -> LogicalRect;
460
461    fn translate(&mut self, distance: LogicalVector);
462    fn translation(&self) -> LogicalVector {
463        unimplemented!()
464    }
465    fn rotate(&mut self, angle_in_degrees: f32);
466    fn scale(&mut self, scale_x_factor: f32, scale_y_factor: f32);
467    /// Apply the opacity (between 0 and 1) for all following items until the next call to restore_state.
468    fn apply_opacity(&mut self, opacity: f32);
469
470    fn save_state(&mut self);
471    fn restore_state(&mut self);
472
473    /// Returns the scale factor
474    fn scale_factor(&self) -> f32;
475
476    /// Draw a pixmap in position indicated by the `pos`.
477    /// The pixmap will be taken from cache if the cache is valid, otherwise, update_fn will be called
478    /// with a callback that need to be called once with `fn (width, height, data)` where data are the
479    /// RGBA premultiplied pixel values
480    fn draw_cached_pixmap(
481        &mut self,
482        item_cache: &ItemRc,
483        update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
484    );
485
486    /// Draw the given string with the specified color at current (0, 0) with the default font. Mainly
487    /// used by the performance counter overlay.
488    fn draw_string(&mut self, string: &str, color: crate::Color);
489
490    fn draw_image_direct(&mut self, image: crate::graphics::Image);
491
492    /// This is called before it is being rendered (before the draw_* function).
493    /// Returns
494    ///  - if the item needs to be drawn (false means it is clipped or doesn't need to be drawn)
495    ///  - the geometry of the item
496    fn filter_item(
497        &mut self,
498        item: &ItemRc,
499        window_adapter: &Rc<dyn WindowAdapter>,
500    ) -> (bool, LogicalRect) {
501        let item_geometry = item.geometry();
502        // Query bounding rect untracked, as properties that affect the bounding rect are already tracked
503        // when rendering the item.
504        let bounding_rect = crate::properties::evaluate_no_tracking(|| {
505            item.bounding_rect(&item_geometry, window_adapter)
506        });
507        (self.get_current_clip().intersects(&bounding_rect), item_geometry)
508    }
509
510    fn window(&self) -> &crate::window::WindowInner;
511
512    /// Return the internal renderer
513    fn as_any(&mut self) -> Option<&mut dyn core::any::Any>;
514}
515
516/// Helper trait to express the features of an item renderer.
517pub trait ItemRendererFeatures {
518    /// The renderer supports applying 2D transformations to items.
519    const SUPPORTS_TRANSFORMATIONS: bool;
520}