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::graphics::RenderingCache;
8use super::items::*;
9use crate::graphics::{CachedGraphicsData, FontRequest, Image, IntRect};
10use crate::item_tree::ItemTreeRc;
11use crate::item_tree::{ItemVisitor, ItemVisitorResult, ItemVisitorVTable, VisitChildrenResult};
12use crate::lengths::{
13    ItemTransform, LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalPx, LogicalRect,
14    LogicalSize, LogicalVector, SizeLengths,
15};
16use crate::properties::PropertyTracker;
17use crate::window::{WindowAdapter, WindowInner};
18use crate::{Brush, Coord, SharedString};
19use alloc::boxed::Box;
20use alloc::rc::Rc;
21use core::cell::{Cell, RefCell};
22use core::pin::Pin;
23#[cfg(feature = "std")]
24use std::collections::HashMap;
25use vtable::VRc;
26
27/// This structure must be present in items that are Rendered and contains information.
28/// Used by the backend.
29#[derive(Default, Debug)]
30#[repr(C)]
31pub struct CachedRenderingData {
32    /// Used and modified by the backend, should be initialized to 0 by the user code
33    pub(crate) cache_index: Cell<usize>,
34    /// Used and modified by the backend, should be initialized to 0 by the user code.
35    /// The backend compares this generation against the one of the cache to verify
36    /// the validity of the cache_index field.
37    pub(crate) cache_generation: Cell<usize>,
38}
39
40impl CachedRenderingData {
41    /// This function can be used to remove an entry from the rendering cache for a given item, if it
42    /// exists, i.e. if any data was ever cached. This is typically called by the graphics backend's
43    /// implementation of the release_item_graphics_cache function.
44    pub fn release<T>(&self, cache: &mut RenderingCache<T>) -> Option<T> {
45        if self.cache_generation.get() == cache.generation() {
46            let index = self.cache_index.get();
47            self.cache_generation.set(0);
48            Some(cache.remove(index).data)
49        } else {
50            None
51        }
52    }
53
54    /// Return the value if it is in the cache
55    pub fn get_entry<'a, T>(
56        &self,
57        cache: &'a mut RenderingCache<T>,
58    ) -> Option<&'a mut crate::graphics::CachedGraphicsData<T>> {
59        let index = self.cache_index.get();
60        if self.cache_generation.get() == cache.generation() {
61            cache.get_mut(index)
62        } else {
63            None
64        }
65    }
66}
67
68/// A per-item cache.
69///
70/// Cache rendering information for a given item.
71///
72/// Use [`ItemCache::get_or_update_cache_entry`] to get or update the items, the
73/// cache is automatically invalided when the property gets dirty.
74/// [`ItemCache::component_destroyed`] must be called to clear the cache for that
75/// component.
76#[cfg(feature = "std")]
77pub struct ItemCache<T> {
78    /// The pointer is a pointer to a component
79    map: RefCell<HashMap<*const vtable::Dyn, HashMap<u32, CachedGraphicsData<T>>>>,
80    /// Track if the window scale factor changes; used to clear the cache if necessary.
81    window_scale_factor_tracker: Pin<Box<PropertyTracker>>,
82}
83
84#[cfg(feature = "std")]
85impl<T> Default for ItemCache<T> {
86    fn default() -> Self {
87        Self { map: Default::default(), window_scale_factor_tracker: Box::pin(Default::default()) }
88    }
89}
90
91#[cfg(feature = "std")]
92impl<T: Clone> ItemCache<T> {
93    /// Returns the cached value associated to the `item_rc` if it is still valid.
94    /// Otherwise call the `update_fn` to compute that value, and track property access
95    /// so it is automatically invalided when property becomes dirty.
96    pub fn get_or_update_cache_entry(&self, item_rc: &ItemRc, update_fn: impl FnOnce() -> T) -> T {
97        let component = &(**item_rc.item_tree()) as *const _;
98        let mut borrowed = self.map.borrow_mut();
99        match borrowed.entry(component).or_default().entry(item_rc.index()) {
100            std::collections::hash_map::Entry::Occupied(mut entry) => {
101                let mut tracker = entry.get_mut().dependency_tracker.take();
102                drop(borrowed);
103                let maybe_new_data = tracker
104                    .get_or_insert_with(|| Box::pin(Default::default()))
105                    .as_ref()
106                    .evaluate_if_dirty(update_fn);
107                let mut borrowed = self.map.borrow_mut();
108                let e = borrowed.get_mut(&component).unwrap().get_mut(&item_rc.index()).unwrap();
109                e.dependency_tracker = tracker;
110                if let Some(new_data) = maybe_new_data {
111                    e.data = new_data.clone();
112                    new_data
113                } else {
114                    e.data.clone()
115                }
116            }
117            std::collections::hash_map::Entry::Vacant(_) => {
118                drop(borrowed);
119                let new_entry = CachedGraphicsData::new(update_fn);
120                let data = new_entry.data.clone();
121                self.map
122                    .borrow_mut()
123                    .get_mut(&component)
124                    .unwrap()
125                    .insert(item_rc.index(), new_entry);
126                data
127            }
128        }
129    }
130
131    /// Returns the cached value associated with the `item_rc` if it is in the cache
132    /// and still valid.
133    pub fn with_entry<U>(
134        &self,
135        item_rc: &ItemRc,
136        callback: impl FnOnce(&T) -> Option<U>,
137    ) -> Option<U> {
138        let component = &(**item_rc.item_tree()) as *const _;
139        self.map
140            .borrow()
141            .get(&component)
142            .and_then(|per_component_entries| per_component_entries.get(&item_rc.index()))
143            .and_then(|entry| callback(&entry.data))
144    }
145
146    /// Clears the cache if the window's scale factor has changed since the last call.
147    pub fn clear_cache_if_scale_factor_changed(&self, window: &crate::api::Window) {
148        if self.window_scale_factor_tracker.is_dirty() {
149            self.window_scale_factor_tracker
150                .as_ref()
151                .evaluate_as_dependency_root(|| window.scale_factor());
152            self.clear_all();
153        }
154    }
155
156    /// free the whole cache
157    pub fn clear_all(&self) {
158        self.map.borrow_mut().clear();
159    }
160
161    /// Function that must be called when a component is destroyed.
162    ///
163    /// Usually can be called from [`crate::window::WindowAdapterInternal::unregister_item_tree`]
164    pub fn component_destroyed(&self, component: crate::item_tree::ItemTreeRef) {
165        let component_ptr: *const _ =
166            crate::item_tree::ItemTreeRef::as_ptr(component).cast().as_ptr();
167        self.map.borrow_mut().remove(&component_ptr);
168    }
169
170    /// free the cache for a given item
171    pub fn release(&self, item_rc: &ItemRc) {
172        let component = &(**item_rc.item_tree()) as *const _;
173        if let Some(sub) = self.map.borrow_mut().get_mut(&component) {
174            sub.remove(&item_rc.index());
175        }
176    }
177
178    /// Returns true if there are no entries in the cache; false otherwise.
179    pub fn is_empty(&self) -> bool {
180        self.map.borrow().is_empty()
181    }
182}
183
184/// Renders the children of the item with the specified index into the renderer.
185pub fn render_item_children(
186    renderer: &mut dyn ItemRenderer,
187    component: &ItemTreeRc,
188    index: isize,
189    window_adapter: &Rc<dyn WindowAdapter>,
190) {
191    let mut actual_visitor =
192        |component: &ItemTreeRc, index: u32, item: Pin<ItemRef>| -> VisitChildrenResult {
193            renderer.save_state();
194            let item_rc = ItemRc::new(component.clone(), index);
195
196            let (do_draw, item_geometry) = renderer.filter_item(&item_rc, window_adapter);
197
198            let item_origin = item_geometry.origin;
199            renderer.translate(item_origin.to_vector());
200
201            // Don't render items that are clipped, with the exception of the Clip or Flickable since
202            // they themselves clip their content.
203            let render_result = if do_draw
204               || item.as_ref().clips_children()
205               // 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
206               || ItemRef::downcast_pin::<BoxShadow>(item).is_some()
207            {
208                item.as_ref().render(
209                    &mut (renderer as &mut dyn ItemRenderer),
210                    &item_rc,
211                    item_geometry.size,
212                )
213            } else {
214                RenderingResult::ContinueRenderingChildren
215            };
216
217            if matches!(render_result, RenderingResult::ContinueRenderingChildren) {
218                render_item_children(renderer, component, index as isize, window_adapter);
219            }
220            renderer.restore_state();
221            VisitChildrenResult::CONTINUE
222        };
223    vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
224    VRc::borrow_pin(component).as_ref().visit_children_item(
225        index,
226        crate::item_tree::TraversalOrder::BackToFront,
227        actual_visitor,
228    );
229}
230
231/// Renders the tree of items that component holds, using the specified renderer. Rendering is done
232/// relative to the specified origin.
233pub fn render_component_items(
234    component: &ItemTreeRc,
235    renderer: &mut dyn ItemRenderer,
236    origin: LogicalPoint,
237    window_adapter: &Rc<dyn WindowAdapter>,
238) {
239    renderer.save_state();
240    renderer.translate(origin.to_vector());
241
242    render_item_children(renderer, component, -1, window_adapter);
243
244    renderer.restore_state();
245}
246
247/// Compute the bounding rect of all children. This does /not/ include item's own bounding rect. Remember to run this
248/// via `evaluate_no_tracking`.
249pub fn item_children_bounding_rect(
250    component: &ItemTreeRc,
251    index: isize,
252    clip_rect: &LogicalRect,
253) -> LogicalRect {
254    let mut bounding_rect = LogicalRect::zero();
255
256    let mut actual_visitor =
257        |component: &ItemTreeRc, index: u32, item: Pin<ItemRef>| -> VisitChildrenResult {
258            let item_geometry = ItemTreeRc::borrow_pin(component).as_ref().item_geometry(index);
259
260            let local_clip_rect = clip_rect.translate(-item_geometry.origin.to_vector());
261
262            if let Some(clipped_item_geometry) = item_geometry.intersection(clip_rect) {
263                bounding_rect = bounding_rect.union(&clipped_item_geometry);
264            }
265
266            if !item.as_ref().clips_children() {
267                bounding_rect = bounding_rect.union(&item_children_bounding_rect(
268                    component,
269                    index as isize,
270                    &local_clip_rect,
271                ));
272            }
273            VisitChildrenResult::CONTINUE
274        };
275    vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
276    VRc::borrow_pin(component).as_ref().visit_children_item(
277        index,
278        crate::item_tree::TraversalOrder::BackToFront,
279        actual_visitor,
280    );
281
282    bounding_rect
283}
284
285/// Trait for an item that represent a Rectangle to the Renderer
286#[allow(missing_docs)]
287pub trait RenderRectangle {
288    fn background(self: Pin<&Self>) -> Brush;
289}
290
291/// Trait for an item that represent a Rectangle with a border to the Renderer
292#[allow(missing_docs)]
293pub trait RenderBorderRectangle {
294    fn background(self: Pin<&Self>) -> Brush;
295    fn border_width(self: Pin<&Self>) -> LogicalLength;
296    fn border_radius(self: Pin<&Self>) -> LogicalBorderRadius;
297    fn border_color(self: Pin<&Self>) -> Brush;
298}
299
300/// Trait for an item that represents an Image towards the renderer
301#[allow(missing_docs)]
302pub trait RenderImage {
303    fn target_size(self: Pin<&Self>) -> LogicalSize;
304    fn source(self: Pin<&Self>) -> Image;
305    fn source_clip(self: Pin<&Self>) -> Option<IntRect>;
306    fn image_fit(self: Pin<&Self>) -> ImageFit;
307    fn rendering(self: Pin<&Self>) -> ImageRendering;
308    fn colorize(self: Pin<&Self>) -> Brush;
309    fn alignment(self: Pin<&Self>) -> (ImageHorizontalAlignment, ImageVerticalAlignment);
310    fn tiling(self: Pin<&Self>) -> (ImageTiling, ImageTiling);
311}
312
313/// Trait for an item that represents an Text towards the renderer
314#[allow(missing_docs)]
315pub trait RenderText {
316    fn target_size(self: Pin<&Self>) -> LogicalSize;
317    fn text(self: Pin<&Self>) -> SharedString;
318    fn font_request(self: Pin<&Self>, self_rc: &ItemRc) -> FontRequest;
319    fn color(self: Pin<&Self>) -> Brush;
320    fn alignment(self: Pin<&Self>) -> (TextHorizontalAlignment, TextVerticalAlignment);
321    fn wrap(self: Pin<&Self>) -> TextWrap;
322    fn overflow(self: Pin<&Self>) -> TextOverflow;
323    fn letter_spacing(self: Pin<&Self>) -> LogicalLength;
324    fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle);
325
326    fn text_bounding_rect(
327        self: Pin<&Self>,
328        self_rc: &ItemRc,
329        window_adapter: &Rc<dyn WindowAdapter>,
330        mut geometry: euclid::Rect<f32, crate::lengths::LogicalPx>,
331    ) -> euclid::Rect<f32, crate::lengths::LogicalPx> {
332        let window_inner = WindowInner::from_pub(window_adapter.window());
333        let text_string = self.text();
334        let font_request = self.font_request(self_rc);
335        let scale_factor = crate::lengths::ScaleFactor::new(window_inner.scale_factor());
336        let max_width = geometry.size.width_length();
337        geometry.size = geometry.size.max(
338            window_adapter
339                .renderer()
340                .text_size(
341                    font_request.clone(),
342                    text_string.as_str(),
343                    Some(max_width.cast()),
344                    scale_factor,
345                    self.wrap(),
346                )
347                .cast(),
348        );
349        geometry
350    }
351}
352
353/// Trait used to render each items.
354///
355/// The item needs to be rendered relative to its (x,y) position. For example,
356/// draw_rectangle should draw a rectangle in `(pos.x + rect.x, pos.y + rect.y)`
357#[allow(missing_docs)]
358pub trait ItemRenderer {
359    fn draw_rectangle(
360        &mut self,
361        rect: Pin<&dyn RenderRectangle>,
362        _self_rc: &ItemRc,
363        _size: LogicalSize,
364        _cache: &CachedRenderingData,
365    );
366    fn draw_border_rectangle(
367        &mut self,
368        rect: Pin<&dyn RenderBorderRectangle>,
369        _self_rc: &ItemRc,
370        _size: LogicalSize,
371        _cache: &CachedRenderingData,
372    );
373    fn draw_window_background(
374        &mut self,
375        rect: Pin<&dyn RenderRectangle>,
376        self_rc: &ItemRc,
377        size: LogicalSize,
378        cache: &CachedRenderingData,
379    );
380    fn draw_image(
381        &mut self,
382        image: Pin<&dyn RenderImage>,
383        _self_rc: &ItemRc,
384        _size: LogicalSize,
385        _cache: &CachedRenderingData,
386    );
387    fn draw_text(
388        &mut self,
389        text: Pin<&dyn RenderText>,
390        _self_rc: &ItemRc,
391        _size: LogicalSize,
392        _cache: &CachedRenderingData,
393    );
394    fn draw_text_input(
395        &mut self,
396        text_input: Pin<&TextInput>,
397        _self_rc: &ItemRc,
398        _size: LogicalSize,
399    );
400    #[cfg(feature = "std")]
401    fn draw_path(&mut self, path: Pin<&Path>, _self_rc: &ItemRc, _size: LogicalSize);
402    fn draw_box_shadow(
403        &mut self,
404        box_shadow: Pin<&BoxShadow>,
405        _self_rc: &ItemRc,
406        _size: LogicalSize,
407    );
408    fn visit_opacity(
409        &mut self,
410        opacity_item: Pin<&Opacity>,
411        _self_rc: &ItemRc,
412        _size: LogicalSize,
413    ) -> RenderingResult {
414        self.apply_opacity(opacity_item.opacity());
415        RenderingResult::ContinueRenderingChildren
416    }
417    fn visit_layer(
418        &mut self,
419        _layer_item: Pin<&Layer>,
420        _self_rc: &ItemRc,
421        _size: LogicalSize,
422    ) -> RenderingResult {
423        // Not supported
424        RenderingResult::ContinueRenderingChildren
425    }
426
427    // Apply the bounds of the Clip element, if enabled. The default implementation calls
428    // combine_clip, but the render may choose an alternate way of implementing the clip.
429    // For example the GL backend uses a layered rendering approach.
430    fn visit_clip(
431        &mut self,
432        clip_item: Pin<&Clip>,
433        item_rc: &ItemRc,
434        _size: LogicalSize,
435    ) -> RenderingResult {
436        if clip_item.clip() {
437            let geometry = item_rc.geometry();
438
439            let clip_region_valid = self.combine_clip(
440                LogicalRect::new(LogicalPoint::default(), geometry.size),
441                clip_item.logical_border_radius(),
442                clip_item.border_width(),
443            );
444
445            // If clipping is enabled but the clip element is outside the visible range, then we don't
446            // need to bother doing anything, not even rendering the children.
447            if !clip_region_valid {
448                return RenderingResult::ContinueRenderingWithoutChildren;
449            }
450        }
451        RenderingResult::ContinueRenderingChildren
452    }
453
454    /// Clip the further call until restore_state.
455    /// radius/border_width can be used for border rectangle clip.
456    /// (FIXME: consider removing radius/border_width and have another  function that take a path instead)
457    /// Returns a boolean indicating the state of the new clip region: true if the clip region covers
458    /// an area; false if the clip region is empty.
459    fn combine_clip(
460        &mut self,
461        rect: LogicalRect,
462        radius: LogicalBorderRadius,
463        border_width: LogicalLength,
464    ) -> bool;
465    /// Get the current clip bounding box in the current transformed coordinate.
466    fn get_current_clip(&self) -> LogicalRect;
467
468    fn translate(&mut self, distance: LogicalVector);
469    fn translation(&self) -> LogicalVector {
470        unimplemented!()
471    }
472    fn rotate(&mut self, angle_in_degrees: f32);
473    /// Apply the opacity (between 0 and 1) for all following items until the next call to restore_state.
474    fn apply_opacity(&mut self, opacity: f32);
475
476    fn save_state(&mut self);
477    fn restore_state(&mut self);
478
479    /// Returns the scale factor
480    fn scale_factor(&self) -> f32;
481
482    /// Draw a pixmap in position indicated by the `pos`.
483    /// The pixmap will be taken from cache if the cache is valid, otherwise, update_fn will be called
484    /// with a callback that need to be called once with `fn (width, height, data)` where data are the
485    /// RGBA premultiplied pixel values
486    fn draw_cached_pixmap(
487        &mut self,
488        item_cache: &ItemRc,
489        update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
490    );
491
492    /// Draw the given string with the specified color at current (0, 0) with the default font. Mainly
493    /// used by the performance counter overlay.
494    fn draw_string(&mut self, string: &str, color: crate::Color);
495
496    fn draw_image_direct(&mut self, image: crate::graphics::Image);
497
498    /// This is called before it is being rendered (before the draw_* function).
499    /// Returns
500    ///  - if the item needs to be drawn (false means it is clipped or doesn't need to be drawn)
501    ///  - the geometry of the item
502    fn filter_item(
503        &mut self,
504        item: &ItemRc,
505        window_adapter: &Rc<dyn WindowAdapter>,
506    ) -> (bool, LogicalRect) {
507        let item_geometry = item.geometry();
508        // Query bounding rect untracked, as properties that affect the bounding rect are already tracked
509        // when rendering the item.
510        let bounding_rect = crate::properties::evaluate_no_tracking(|| {
511            item.bounding_rect(&item_geometry, window_adapter)
512        });
513        (self.get_current_clip().intersects(&bounding_rect), item_geometry)
514    }
515
516    fn window(&self) -> &crate::window::WindowInner;
517
518    /// Return the internal renderer
519    fn as_any(&mut self) -> Option<&mut dyn core::any::Any>;
520
521    /// Returns any rendering metrics collecting since the creation of the renderer (typically
522    /// per frame)
523    fn metrics(&self) -> crate::graphics::rendering_metrics_collector::RenderingMetrics {
524        Default::default()
525    }
526}
527
528/// Helper trait to express the features of an item renderer.
529pub trait ItemRendererFeatures {
530    /// The renderer supports applying 2D transformations to items.
531    const SUPPORTS_TRANSFORMATIONS: bool;
532}
533
534/// After rendering an item, we cache the geometry and the transform it applies to
535/// children.
536#[derive(Clone)]
537
538pub enum CachedItemBoundingBoxAndTransform {
539    /// A regular item with a translation
540    RegularItem {
541        /// The item's bounding rect relative to its parent.
542        bounding_rect: LogicalRect,
543        /// The item's offset relative to its parent.
544        offset: LogicalVector,
545    },
546    /// An item such as Rotate that defines an additional transformation
547    ItemWithTransform {
548        /// The item's bounding rect relative to its parent.
549        bounding_rect: LogicalRect,
550        /// The item's transform to apply to children.
551        transform: Box<ItemTransform>,
552    },
553    /// A clip item.
554    ClipItem {
555        /// The item's geometry relative to its parent.
556        geometry: LogicalRect,
557    },
558}
559
560impl CachedItemBoundingBoxAndTransform {
561    fn bounding_rect(&self) -> &LogicalRect {
562        match self {
563            CachedItemBoundingBoxAndTransform::RegularItem { bounding_rect, .. } => bounding_rect,
564            CachedItemBoundingBoxAndTransform::ItemWithTransform { bounding_rect, .. } => {
565                bounding_rect
566            }
567            CachedItemBoundingBoxAndTransform::ClipItem { geometry } => geometry,
568        }
569    }
570
571    fn transform(&self) -> ItemTransform {
572        match self {
573            CachedItemBoundingBoxAndTransform::RegularItem { offset, .. } => {
574                ItemTransform::translation(offset.x as f32, offset.y as f32)
575            }
576            CachedItemBoundingBoxAndTransform::ItemWithTransform { transform, .. } => **transform,
577            CachedItemBoundingBoxAndTransform::ClipItem { geometry } => {
578                ItemTransform::translation(geometry.origin.x as f32, geometry.origin.y as f32)
579            }
580        }
581    }
582
583    fn new<T: ItemRendererFeatures>(
584        item_rc: &ItemRc,
585        window_adapter: &Rc<dyn WindowAdapter>,
586    ) -> Self {
587        let geometry = item_rc.geometry();
588
589        if item_rc.borrow().as_ref().clips_children() {
590            return Self::ClipItem { geometry };
591        }
592
593        // Evaluate the bounding rect untracked, as properties that affect the bounding rect are already tracked
594        // at rendering time.
595        let bounding_rect = crate::properties::evaluate_no_tracking(|| {
596            item_rc.bounding_rect(&geometry, window_adapter)
597        });
598
599        if let Some(complex_child_transform) =
600            T::SUPPORTS_TRANSFORMATIONS.then(|| item_rc.children_transform()).flatten()
601        {
602            Self::ItemWithTransform {
603                bounding_rect,
604                transform: complex_child_transform
605                    .then_translate(geometry.origin.to_vector().cast())
606                    .into(),
607            }
608        } else {
609            Self::RegularItem { bounding_rect, offset: geometry.origin.to_vector() }
610        }
611    }
612}
613
614/// The cache that needs to be held by the Window for the partial rendering
615pub type PartialRenderingCache = RenderingCache<CachedItemBoundingBoxAndTransform>;
616
617/// A region composed of a few rectangles that need to be redrawn.
618#[derive(Default, Clone, Debug)]
619pub struct DirtyRegion {
620    rectangles: [euclid::Box2D<Coord, LogicalPx>; Self::MAX_COUNT],
621    count: usize,
622}
623
624impl DirtyRegion {
625    /// The maximum number of rectangles that can be stored in a DirtyRegion
626    pub(crate) const MAX_COUNT: usize = 3;
627
628    /// An iterator over the part of the region (they can overlap)
629    pub fn iter(&self) -> impl Iterator<Item = euclid::Box2D<Coord, LogicalPx>> + '_ {
630        (0..self.count).map(|x| self.rectangles[x])
631    }
632
633    /// Add a rectangle to the region.
634    ///
635    /// Note that if the region becomes too complex, it might be simplified by being bigger than the actual union.
636    pub fn add_rect(&mut self, rect: LogicalRect) {
637        self.add_box(rect.to_box2d());
638    }
639
640    /// Add a box to the region
641    ///
642    /// Note that if the region becomes too complex, it might be simplified by being bigger than the actual union.
643    pub fn add_box(&mut self, b: euclid::Box2D<Coord, LogicalPx>) {
644        if b.is_empty() {
645            return;
646        }
647        let mut i = 0;
648        while i < self.count {
649            let r = &self.rectangles[i];
650            if r.contains_box(&b) {
651                // the rectangle is already in the union
652                return;
653            } else if b.contains_box(r) {
654                self.rectangles.swap(i, self.count - 1);
655                self.count -= 1;
656                continue;
657            }
658            i += 1;
659        }
660
661        if self.count < Self::MAX_COUNT {
662            self.rectangles[self.count] = b;
663            self.count += 1;
664        } else {
665            let best_merge = (0..self.count)
666                .map(|i| (i, self.rectangles[i].union(&b).area() - self.rectangles[i].area()))
667                .min_by(|a, b| PartialOrd::partial_cmp(&a.1, &b.1).unwrap())
668                .expect("There should always be rectangles")
669                .0;
670            self.rectangles[best_merge] = self.rectangles[best_merge].union(&b);
671        }
672    }
673
674    /// Make an union of two regions.
675    ///
676    /// Note that if the region becomes too complex, it might be simplified by being bigger than the actual union
677    #[must_use]
678    pub fn union(&self, other: &Self) -> Self {
679        let mut s = self.clone();
680        for o in other.iter() {
681            s.add_box(o)
682        }
683        s
684    }
685
686    /// Bounding rectangle of the region.
687    #[must_use]
688    pub fn bounding_rect(&self) -> LogicalRect {
689        if self.count == 0 {
690            return Default::default();
691        }
692        let mut r = self.rectangles[0];
693        for i in 1..self.count {
694            r = r.union(&self.rectangles[i]);
695        }
696        r.to_rect()
697    }
698
699    /// Intersection of a region and a rectangle.
700    #[must_use]
701    pub fn intersection(&self, other: LogicalRect) -> DirtyRegion {
702        let mut ret = self.clone();
703        let other = other.to_box2d();
704        let mut i = 0;
705        while i < ret.count {
706            if let Some(x) = ret.rectangles[i].intersection(&other) {
707                ret.rectangles[i] = x;
708            } else {
709                ret.count -= 1;
710                ret.rectangles.swap(i, ret.count);
711                continue;
712            }
713            i += 1;
714        }
715        ret
716    }
717
718    fn draw_intersects(&self, clipped_geom: LogicalRect) -> bool {
719        let b = clipped_geom.to_box2d();
720        self.iter().any(|r| r.intersects(&b))
721    }
722}
723
724impl From<LogicalRect> for DirtyRegion {
725    fn from(value: LogicalRect) -> Self {
726        let mut s = Self::default();
727        s.add_rect(value);
728        s
729    }
730}
731
732/// This enum describes which parts of the buffer passed to the [`SoftwareRenderer`](crate::software_renderer::SoftwareRenderer) may be re-used to speed up painting.
733// FIXME: #[non_exhaustive] #3023
734#[derive(PartialEq, Eq, Debug, Clone, Default, Copy)]
735pub enum RepaintBufferType {
736    #[default]
737    /// The full window is always redrawn. No attempt at partial rendering will be made.
738    NewBuffer,
739    /// Only redraw the parts that have changed since the previous call to render().
740    ///
741    /// This variant assumes that the same buffer is passed on every call to render() and
742    /// that it still contains the previously rendered frame.
743    ReusedBuffer,
744
745    /// Redraw the part that have changed since the last two frames were drawn.
746    ///
747    /// This is used when using double buffering and swapping of the buffers.
748    SwappedBuffers,
749}
750
751/// Put this structure in the renderer to help with partial rendering
752pub struct PartialRenderer<'a, T> {
753    cache: &'a RefCell<PartialRenderingCache>,
754    /// The region of the screen which is considered dirty and that should be repainted
755    pub dirty_region: DirtyRegion,
756    /// The actual renderer which the drawing call will be forwarded to
757    pub actual_renderer: T,
758    /// The window adapter the renderer is rendering into.
759    pub window_adapter: Rc<dyn WindowAdapter>,
760}
761
762impl<'a, T: ItemRenderer + ItemRendererFeatures> PartialRenderer<'a, T> {
763    /// Create a new PartialRenderer
764    pub fn new(
765        cache: &'a RefCell<PartialRenderingCache>,
766        initial_dirty_region: DirtyRegion,
767        actual_renderer: T,
768    ) -> Self {
769        let window_adapter = actual_renderer.window().window_adapter();
770        Self { cache, dirty_region: initial_dirty_region, actual_renderer, window_adapter }
771    }
772
773    /// Visit the tree of item and compute what are the dirty regions
774    pub fn compute_dirty_regions(
775        &mut self,
776        component: &ItemTreeRc,
777        origin: LogicalPoint,
778        size: LogicalSize,
779    ) {
780        #[derive(Clone, Copy)]
781        struct ComputeDirtyRegionState {
782            transform_to_screen: ItemTransform,
783            old_transform_to_screen: ItemTransform,
784            clipped: LogicalRect,
785            must_refresh_children: bool,
786        }
787
788        impl ComputeDirtyRegionState {
789            /// Adjust transform_to_screen and old_transform_to_screen to map from item coordinates
790            /// to the screen when using it on a child, specified by its children transform.
791            fn adjust_transforms_for_child(
792                &mut self,
793                children_transform: &ItemTransform,
794                old_children_transform: &ItemTransform,
795            ) {
796                self.transform_to_screen = children_transform.then(&self.transform_to_screen);
797                self.old_transform_to_screen =
798                    old_children_transform.then(&self.old_transform_to_screen);
799            }
800        }
801
802        crate::item_tree::visit_items(
803            component,
804            crate::item_tree::TraversalOrder::BackToFront,
805            |component, item, index, state| {
806                let mut new_state = *state;
807                let mut borrowed = self.cache.borrow_mut();
808                let item_rc = ItemRc::new(component.clone(), index);
809
810                match item.cached_rendering_data_offset().get_entry(&mut borrowed) {
811                    Some(CachedGraphicsData {
812                        data: cached_geom,
813                        dependency_tracker: Some(tr),
814                    }) => {
815                        if tr.is_dirty() {
816                            let old_geom = cached_geom.clone();
817                            drop(borrowed);
818                            let new_geom = crate::properties::evaluate_no_tracking(|| {
819                                CachedItemBoundingBoxAndTransform::new::<T>(
820                                    &item_rc,
821                                    &self.window_adapter,
822                                )
823                            });
824
825                            self.mark_dirty_rect(
826                                old_geom.bounding_rect(),
827                                state.old_transform_to_screen,
828                                &state.clipped,
829                            );
830                            self.mark_dirty_rect(
831                                new_geom.bounding_rect(),
832                                state.transform_to_screen,
833                                &state.clipped,
834                            );
835
836                            new_state.adjust_transforms_for_child(
837                                &new_geom.transform(),
838                                &old_geom.transform(),
839                            );
840
841                            if ItemRef::downcast_pin::<Clip>(item).is_some()
842                                || ItemRef::downcast_pin::<Opacity>(item).is_some()
843                            {
844                                // When the opacity or the clip change, this will impact all the children, including
845                                // the ones outside the element, regardless if they are themselves dirty or not.
846                                new_state.must_refresh_children = true;
847                            }
848
849                            ItemVisitorResult::Continue(new_state)
850                        } else {
851                            tr.as_ref().register_as_dependency_to_current_binding();
852
853                            if state.must_refresh_children
854                                || new_state.transform_to_screen
855                                    != new_state.old_transform_to_screen
856                            {
857                                self.mark_dirty_rect(
858                                    cached_geom.bounding_rect(),
859                                    state.old_transform_to_screen,
860                                    &state.clipped,
861                                );
862                                self.mark_dirty_rect(
863                                    cached_geom.bounding_rect(),
864                                    state.transform_to_screen,
865                                    &state.clipped,
866                                );
867                            }
868
869                            new_state.adjust_transforms_for_child(
870                                &cached_geom.transform(),
871                                &cached_geom.transform(),
872                            );
873
874                            if let CachedItemBoundingBoxAndTransform::ClipItem { geometry } =
875                                &cached_geom
876                            {
877                                new_state.clipped = new_state
878                                    .clipped
879                                    .intersection(
880                                        &state
881                                            .transform_to_screen
882                                            .outer_transformed_rect(&geometry.cast())
883                                            .cast()
884                                            .union(
885                                                &state
886                                                    .old_transform_to_screen
887                                                    .outer_transformed_rect(&geometry.cast())
888                                                    .cast(),
889                                            ),
890                                    )
891                                    .unwrap_or_default();
892                            }
893                            ItemVisitorResult::Continue(new_state)
894                        }
895                    }
896                    _ => {
897                        drop(borrowed);
898                        let bounding_rect = crate::properties::evaluate_no_tracking(|| {
899                            let geom = CachedItemBoundingBoxAndTransform::new::<T>(
900                                &item_rc,
901                                &self.window_adapter,
902                            );
903
904                            new_state
905                                .adjust_transforms_for_child(&geom.transform(), &geom.transform());
906
907                            if let CachedItemBoundingBoxAndTransform::ClipItem { geometry } = geom {
908                                new_state.clipped = new_state
909                                    .clipped
910                                    .intersection(
911                                        &state
912                                            .transform_to_screen
913                                            .outer_transformed_rect(&geometry.cast())
914                                            .cast(),
915                                    )
916                                    .unwrap_or_default();
917                            }
918                            *geom.bounding_rect()
919                        });
920                        self.mark_dirty_rect(
921                            &bounding_rect,
922                            state.transform_to_screen,
923                            &state.clipped,
924                        );
925                        ItemVisitorResult::Continue(new_state)
926                    }
927                }
928            },
929            {
930                let initial_transform =
931                    euclid::Transform2D::translation(origin.x as f32, origin.y as f32);
932                ComputeDirtyRegionState {
933                    transform_to_screen: initial_transform,
934                    old_transform_to_screen: initial_transform,
935                    clipped: LogicalRect::from_size(size),
936                    must_refresh_children: false,
937                }
938            },
939        );
940    }
941
942    fn mark_dirty_rect(
943        &mut self,
944        rect: &LogicalRect,
945        transform: ItemTransform,
946        clip_rect: &LogicalRect,
947    ) {
948        if !rect.is_empty() {
949            if let Some(rect) =
950                transform.outer_transformed_rect(&rect.cast()).cast().intersection(clip_rect)
951            {
952                self.dirty_region.add_rect(rect);
953            }
954        }
955    }
956
957    fn do_rendering(
958        cache: &RefCell<PartialRenderingCache>,
959        rendering_data: &CachedRenderingData,
960        render_fn: impl FnOnce() -> CachedItemBoundingBoxAndTransform,
961    ) {
962        let mut cache = cache.borrow_mut();
963        if let Some(entry) = rendering_data.get_entry(&mut cache) {
964            entry
965                .dependency_tracker
966                .get_or_insert_with(|| Box::pin(PropertyTracker::default()))
967                .as_ref()
968                .evaluate(render_fn);
969        } else {
970            let cache_entry = crate::graphics::CachedGraphicsData::new(render_fn);
971            rendering_data.cache_index.set(cache.insert(cache_entry));
972            rendering_data.cache_generation.set(cache.generation());
973        }
974    }
975
976    /// Move the actual renderer
977    pub fn into_inner(self) -> T {
978        self.actual_renderer
979    }
980}
981
982macro_rules! forward_rendering_call {
983    (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
984        fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize) $(-> $Ret)? {
985            let mut ret = None;
986            Self::do_rendering(&self.cache, &obj.cached_rendering_data, || {
987                ret = Some(self.actual_renderer.$fn(obj, item_rc, size));
988                CachedItemBoundingBoxAndTransform::new::<T>(&item_rc, &self.window_adapter)
989            });
990            ret.unwrap_or_default()
991        }
992    };
993}
994
995macro_rules! forward_rendering_call2 {
996    (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
997        fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize, cache: &CachedRenderingData) $(-> $Ret)? {
998            let mut ret = None;
999            Self::do_rendering(&self.cache, &cache, || {
1000                ret = Some(self.actual_renderer.$fn(obj, item_rc, size, &cache));
1001                CachedItemBoundingBoxAndTransform::new::<T>(&item_rc, &self.window_adapter)
1002            });
1003            ret.unwrap_or_default()
1004        }
1005    };
1006}
1007
1008impl<T: ItemRenderer + ItemRendererFeatures> ItemRenderer for PartialRenderer<'_, T> {
1009    fn filter_item(
1010        &mut self,
1011        item_rc: &ItemRc,
1012        window_adapter: &Rc<dyn WindowAdapter>,
1013    ) -> (bool, LogicalRect) {
1014        let item = item_rc.borrow();
1015        let eval = || {
1016            // registers dependencies on the geometry and clip properties.
1017            CachedItemBoundingBoxAndTransform::new::<T>(item_rc, window_adapter)
1018        };
1019
1020        let rendering_data = item.cached_rendering_data_offset();
1021        let mut cache = self.cache.borrow_mut();
1022        let item_bounding_rect = match rendering_data.get_entry(&mut cache) {
1023            Some(CachedGraphicsData { data, dependency_tracker }) => {
1024                dependency_tracker
1025                    .get_or_insert_with(|| Box::pin(PropertyTracker::default()))
1026                    .as_ref()
1027                    .evaluate_if_dirty(|| *data = eval());
1028                *data.bounding_rect()
1029            }
1030            None => {
1031                let cache_entry = crate::graphics::CachedGraphicsData::new(eval);
1032                let geom = cache_entry.data.clone();
1033                rendering_data.cache_index.set(cache.insert(cache_entry));
1034                rendering_data.cache_generation.set(cache.generation());
1035                *geom.bounding_rect()
1036            }
1037        };
1038
1039        let clipped_geom = self.get_current_clip().intersection(&item_bounding_rect);
1040        let draw = clipped_geom.is_some_and(|clipped_geom| {
1041            let clipped_geom = clipped_geom.translate(self.translation());
1042            self.dirty_region.draw_intersects(clipped_geom)
1043        });
1044
1045        // Query untracked, as the bounding rect calculation already registers a dependency on the geometry.
1046        let item_geometry = crate::properties::evaluate_no_tracking(|| item_rc.geometry());
1047
1048        (draw, item_geometry)
1049    }
1050
1051    forward_rendering_call2!(fn draw_rectangle(dyn RenderRectangle));
1052    forward_rendering_call2!(fn draw_border_rectangle(dyn RenderBorderRectangle));
1053    forward_rendering_call2!(fn draw_window_background(dyn RenderRectangle));
1054    forward_rendering_call2!(fn draw_image(dyn RenderImage));
1055    forward_rendering_call2!(fn draw_text(dyn RenderText));
1056    forward_rendering_call!(fn draw_text_input(TextInput));
1057    #[cfg(feature = "std")]
1058    forward_rendering_call!(fn draw_path(Path));
1059    forward_rendering_call!(fn draw_box_shadow(BoxShadow));
1060
1061    forward_rendering_call!(fn visit_clip(Clip) -> RenderingResult);
1062    forward_rendering_call!(fn visit_opacity(Opacity) -> RenderingResult);
1063
1064    fn combine_clip(
1065        &mut self,
1066        rect: LogicalRect,
1067        radius: LogicalBorderRadius,
1068        border_width: LogicalLength,
1069    ) -> bool {
1070        self.actual_renderer.combine_clip(rect, radius, border_width)
1071    }
1072
1073    fn get_current_clip(&self) -> LogicalRect {
1074        self.actual_renderer.get_current_clip()
1075    }
1076
1077    fn translate(&mut self, distance: LogicalVector) {
1078        self.actual_renderer.translate(distance)
1079    }
1080    fn translation(&self) -> LogicalVector {
1081        self.actual_renderer.translation()
1082    }
1083
1084    fn rotate(&mut self, angle_in_degrees: f32) {
1085        self.actual_renderer.rotate(angle_in_degrees)
1086    }
1087
1088    fn apply_opacity(&mut self, opacity: f32) {
1089        self.actual_renderer.apply_opacity(opacity)
1090    }
1091
1092    fn save_state(&mut self) {
1093        self.actual_renderer.save_state()
1094    }
1095
1096    fn restore_state(&mut self) {
1097        self.actual_renderer.restore_state()
1098    }
1099
1100    fn scale_factor(&self) -> f32 {
1101        self.actual_renderer.scale_factor()
1102    }
1103
1104    fn draw_cached_pixmap(
1105        &mut self,
1106        item_rc: &ItemRc,
1107        update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
1108    ) {
1109        self.actual_renderer.draw_cached_pixmap(item_rc, update_fn)
1110    }
1111
1112    fn draw_string(&mut self, string: &str, color: crate::Color) {
1113        self.actual_renderer.draw_string(string, color)
1114    }
1115
1116    fn draw_image_direct(&mut self, image: crate::graphics::image::Image) {
1117        self.actual_renderer.draw_image_direct(image)
1118    }
1119
1120    fn window(&self) -> &crate::window::WindowInner {
1121        self.actual_renderer.window()
1122    }
1123
1124    fn as_any(&mut self) -> Option<&mut dyn core::any::Any> {
1125        self.actual_renderer.as_any()
1126    }
1127}
1128
1129/// This struct holds the state of the partial renderer between different frames, in particular the cache of the bounding rect
1130/// of each item. This permits a more fine-grained computation of the region that needs to be repainted.
1131#[derive(Default)]
1132pub struct PartialRenderingState {
1133    partial_cache: RefCell<PartialRenderingCache>,
1134    /// This is the area which we are going to redraw in the next frame, no matter if the items are dirty or not
1135    force_dirty: RefCell<DirtyRegion>,
1136    /// Force a redraw in the next frame, no matter what's dirty. Use only as a last resort.
1137    force_screen_refresh: Cell<bool>,
1138}
1139
1140impl PartialRenderingState {
1141    /// Creates a partial renderer that's initialized with the partial rendering caches maintained in this state structure.
1142    /// Call [`Self::apply_dirty_region`] after this function to compute the correct partial rendering region.
1143    pub fn create_partial_renderer<T: ItemRenderer + ItemRendererFeatures>(
1144        &self,
1145        renderer: T,
1146    ) -> PartialRenderer<'_, T> {
1147        PartialRenderer::new(&self.partial_cache, self.force_dirty.take(), renderer)
1148    }
1149
1150    /// Compute the correct partial rendering region based on the components to be drawn, the bounding rectangles of
1151    /// changes items within, and the current repaint buffer type. Returns the computed dirty region just for this frame.
1152    /// The provided buffer_dirty_region specifies which area of the buffer is known to *additionally* require repainting,
1153    /// where `None` means that buffer is not known to be dirty beyond what applies to this frame (reused buffer).
1154    pub fn apply_dirty_region<T: ItemRenderer + ItemRendererFeatures>(
1155        &self,
1156        partial_renderer: &mut PartialRenderer<'_, T>,
1157        components: &[(&ItemTreeRc, LogicalPoint)],
1158        logical_window_size: LogicalSize,
1159        dirty_region_of_existing_buffer: Option<DirtyRegion>,
1160    ) -> DirtyRegion {
1161        for (component, origin) in components {
1162            partial_renderer.compute_dirty_regions(component, *origin, logical_window_size);
1163        }
1164
1165        let screen_region = LogicalRect::from_size(logical_window_size);
1166
1167        if self.force_screen_refresh.take() {
1168            partial_renderer.dirty_region = screen_region.into();
1169        }
1170
1171        let region_to_repaint = partial_renderer.dirty_region.clone();
1172
1173        partial_renderer.dirty_region = match dirty_region_of_existing_buffer {
1174            Some(dirty_region) => partial_renderer.dirty_region.union(&dirty_region),
1175            None => partial_renderer.dirty_region.clone(),
1176        }
1177        .intersection(screen_region);
1178
1179        region_to_repaint
1180    }
1181
1182    /// Add the specified region to the list of regions to include in the next rendering.
1183    pub fn mark_dirty_region(&self, region: DirtyRegion) {
1184        self.force_dirty.replace_with(|r| r.union(&region));
1185    }
1186
1187    /// Call this from your renderer's `free_graphics_resources` function to ensure that the cached item geometries
1188    /// are cleared for the destroyed items in the item tree.
1189    pub fn free_graphics_resources(&self, items: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>) {
1190        for item in items {
1191            item.cached_rendering_data_offset().release(&mut self.partial_cache.borrow_mut());
1192        }
1193
1194        // We don't have a way to determine the screen region of the delete items, what's in the cache is relative. So
1195        // as a last resort, refresh everything.
1196        self.force_screen_refresh.set(true)
1197    }
1198
1199    /// Clears the partial rendering cache. Use this for example when the entire undering window surface changes.
1200    pub fn clear_cache(&self) {
1201        self.partial_cache.borrow_mut().clear();
1202    }
1203
1204    /// Force re-rendering of the entire window region the next time a partial renderer is created.
1205    pub fn force_screen_refresh(&self) {
1206        self.force_screen_refresh.set(true);
1207    }
1208}
1209
1210#[test]
1211fn dirty_region_no_intersection() {
1212    let mut region = DirtyRegion::default();
1213    region.add_rect(LogicalRect::new(LogicalPoint::new(10., 10.), LogicalSize::new(16., 16.)));
1214    region.add_rect(LogicalRect::new(LogicalPoint::new(100., 100.), LogicalSize::new(16., 16.)));
1215    region.add_rect(LogicalRect::new(LogicalPoint::new(200., 100.), LogicalSize::new(16., 16.)));
1216    let i = region
1217        .intersection(LogicalRect::new(LogicalPoint::new(50., 50.), LogicalSize::new(10., 10.)));
1218    assert_eq!(i.iter().count(), 0);
1219}