Skip to main content

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::{
9    Color, FontRequest, Image, IntRect, adjust_rect_and_border_for_inner_drawing,
10};
11use crate::item_tree::ItemTreeRc;
12use crate::item_tree::{ItemVisitor, ItemVisitorVTable, VisitChildrenResult};
13use crate::lengths::{
14    LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
15    PhysicalBorderRadius, PhysicalPx, ScaleFactor, SizeLengths,
16};
17pub use crate::partial_renderer::CachedRenderingData;
18use crate::window::WindowAdapterRc;
19use crate::{Brush, SharedString};
20#[cfg(feature = "std")]
21use alloc::boxed::Box;
22#[cfg(feature = "std")]
23use core::cell::RefCell;
24use core::pin::Pin;
25#[cfg(feature = "std")]
26use std::collections::HashMap;
27use vtable::VRc;
28
29/// A per-item cache.
30///
31/// Cache rendering information for a given item.
32///
33/// Use [`ItemCache::get_or_update_cache_entry`] to get or update the items, the
34/// cache is automatically invalided when the property gets dirty.
35/// [`ItemCache::component_destroyed`] must be called to clear the cache for that
36/// component.
37#[cfg(feature = "std")]
38pub struct ItemCache<T> {
39    /// The pointer is a pointer to a component
40    map: RefCell<HashMap<*const vtable::Dyn, HashMap<u32, crate::graphics::CachedGraphicsData<T>>>>,
41    /// Track if the window scale factor changes; used to clear the cache if necessary.
42    window_scale_factor_tracker: Pin<Box<crate::properties::PropertyTracker>>,
43}
44
45#[cfg(feature = "std")]
46impl<T> Default for ItemCache<T> {
47    fn default() -> Self {
48        Self { map: Default::default(), window_scale_factor_tracker: Box::pin(Default::default()) }
49    }
50}
51
52#[cfg(feature = "std")]
53impl<T: Clone> ItemCache<T> {
54    /// Returns the cached value associated to the `item_rc` if it is still valid.
55    /// Otherwise call the `update_fn` to compute that value, and track property access
56    /// so it is automatically invalided when property becomes dirty.
57    pub fn get_or_update_cache_entry(&self, item_rc: &ItemRc, update_fn: impl FnOnce() -> T) -> T {
58        let component = &(**item_rc.item_tree()) as *const _;
59        let mut borrowed = self.map.borrow_mut();
60        match borrowed.entry(component).or_default().entry(item_rc.index()) {
61            std::collections::hash_map::Entry::Occupied(mut entry) => {
62                let mut tracker = entry.get_mut().dependency_tracker.take();
63                drop(borrowed);
64                let maybe_new_data = tracker
65                    .get_or_insert_with(|| Box::pin(Default::default()))
66                    .as_ref()
67                    .evaluate_if_dirty(update_fn);
68                let mut borrowed = self.map.borrow_mut();
69                let e = borrowed.get_mut(&component).unwrap().get_mut(&item_rc.index()).unwrap();
70                e.dependency_tracker = tracker;
71                if let Some(new_data) = maybe_new_data {
72                    e.data = new_data.clone();
73                    new_data
74                } else {
75                    e.data.clone()
76                }
77            }
78            std::collections::hash_map::Entry::Vacant(_) => {
79                drop(borrowed);
80                let new_entry = crate::graphics::CachedGraphicsData::new(update_fn);
81                let data = new_entry.data.clone();
82                self.map
83                    .borrow_mut()
84                    .get_mut(&component)
85                    .unwrap()
86                    .insert(item_rc.index(), new_entry);
87                data
88            }
89        }
90    }
91}
92
93#[cfg(feature = "std")]
94impl<T> ItemCache<T> {
95    /// Returns the cached value associated with the `item_rc` if it is in the cache
96    /// and still valid.
97    pub fn with_entry<U>(
98        &self,
99        item_rc: &ItemRc,
100        callback: impl FnOnce(&T) -> Option<U>,
101    ) -> Option<U> {
102        let component = &(**item_rc.item_tree()) as *const _;
103        self.map
104            .borrow()
105            .get(&component)
106            .and_then(|per_component_entries| per_component_entries.get(&item_rc.index()))
107            .and_then(|entry| callback(&entry.data))
108    }
109
110    /// Clears the cache if the window's scale factor has changed since the last call.
111    pub fn clear_cache_if_scale_factor_changed(&self, window: &crate::api::Window) {
112        if self.window_scale_factor_tracker.is_dirty() {
113            self.window_scale_factor_tracker
114                .as_ref()
115                .evaluate_as_dependency_root(|| window.scale_factor());
116            self.clear_all();
117        }
118    }
119
120    /// free the whole cache
121    pub fn clear_all(&self) {
122        self.map.borrow_mut().clear();
123    }
124
125    /// Function that must be called when a component is destroyed.
126    ///
127    /// Usually can be called from [`crate::window::WindowAdapterInternal::unregister_item_tree`]
128    pub fn component_destroyed(&self, component: crate::item_tree::ItemTreeRef) {
129        let component_ptr: *const _ =
130            crate::item_tree::ItemTreeRef::as_ptr(component).cast().as_ptr();
131        self.map.borrow_mut().remove(&component_ptr);
132    }
133
134    /// free the cache for a given item
135    pub fn release(&self, item_rc: &ItemRc) {
136        let component = &(**item_rc.item_tree()) as *const _;
137        if let Some(sub) = self.map.borrow_mut().get_mut(&component) {
138            sub.remove(&item_rc.index());
139        }
140    }
141
142    /// Returns true if there are no entries in the cache; false otherwise.
143    pub fn is_empty(&self) -> bool {
144        self.map.borrow().is_empty()
145    }
146
147    /// Keeps only the entries for which `f` returns true.
148    pub fn retain(&self, mut f: impl FnMut(&T) -> bool) {
149        self.map.borrow_mut().retain(|_, per_component| {
150            per_component.retain(|_, entry| f(&entry.data));
151            !per_component.is_empty()
152        });
153    }
154
155    /// Returns a [`RefMut`](std::cell::RefMut) referencing the cached value associated with
156    /// `item_rc`, updating the cache entry first if necessary using `update_fn`.
157    ///
158    /// Unlike [`get_or_update_cache_entry`](Self::get_or_update_cache_entry), this method does
159    /// not require `T: Clone` and returns a mutable reference into the cache, which permits
160    /// in-place modification or temporary extraction of the cached value (e.g., via
161    /// [`std::mem::take`]).
162    pub fn get_or_update_cache_entry_ref(
163        &self,
164        item_rc: &ItemRc,
165        update_fn: impl FnOnce() -> T,
166    ) -> std::cell::RefMut<'_, T> {
167        let component = &(**item_rc.item_tree()) as *const _;
168        let index = item_rc.index();
169
170        {
171            let mut borrowed = self.map.borrow_mut();
172            match borrowed.entry(component).or_default().entry(index) {
173                std::collections::hash_map::Entry::Occupied(mut entry) => {
174                    let mut tracker = entry.get_mut().dependency_tracker.take();
175                    drop(borrowed);
176                    let maybe_new_data = tracker
177                        .get_or_insert_with(|| Box::pin(Default::default()))
178                        .as_ref()
179                        .evaluate_if_dirty(update_fn);
180                    let mut borrowed = self.map.borrow_mut();
181                    let e = borrowed.get_mut(&component).unwrap().get_mut(&index).unwrap();
182                    e.dependency_tracker = tracker;
183                    if let Some(new_data) = maybe_new_data {
184                        e.data = new_data;
185                    }
186                }
187                std::collections::hash_map::Entry::Vacant(_) => {
188                    drop(borrowed);
189                    let new_entry = crate::graphics::CachedGraphicsData::new(update_fn);
190                    self.map.borrow_mut().get_mut(&component).unwrap().insert(index, new_entry);
191                }
192            }
193        }
194
195        std::cell::RefMut::map(self.map.borrow_mut(), |map| {
196            &mut map.get_mut(&component).unwrap().get_mut(&index).unwrap().data
197        })
198    }
199}
200
201/// Renders the children of the item with the specified index into the renderer.
202pub fn render_item_children(
203    renderer: &mut dyn ItemRenderer,
204    component: &ItemTreeRc,
205    index: isize,
206    window_adapter: &WindowAdapterRc,
207) {
208    let mut actual_visitor =
209        |component: &ItemTreeRc, index: u32, item: Pin<ItemRef>| -> VisitChildrenResult {
210            renderer.save_state();
211            let item_rc = ItemRc::new(component.clone(), index);
212
213            let (do_draw, item_origin, item_size) = renderer.filter_item(&item_rc, window_adapter);
214
215            renderer.translate(item_origin.to_vector());
216
217            // Don't render items that are clipped, with the exception of the Clip or Flickable since
218            // they themselves clip their content.
219            let render_result = if renderer.global_alpha_transparent() {
220                // apply_opacity only multiplies, so the whole subtree stays transparent.
221                RenderingResult::ContinueRenderingWithoutChildren
222            } else if do_draw
223               || item.as_ref().clips_children()
224               // 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
225               || ItemRef::downcast_pin::<BoxShadow>(item).is_some()
226               // Transform and Opacity should also be applied regardless if the item itself is clipped or not
227               || ItemRef::downcast_pin::<Transform>(item).is_some()
228               || ItemRef::downcast_pin::<Opacity>(item).is_some()
229               || ItemRef::downcast_pin::<Layer>(item).is_some()
230            {
231                let size = item_size.unwrap_or_else(|| {
232                    crate::properties::evaluate_no_tracking(|| item_rc.geometry()).size
233                });
234                item.as_ref().render(&mut (renderer as &mut dyn ItemRenderer), &item_rc, size)
235            } else {
236                RenderingResult::ContinueRenderingChildren
237            };
238
239            if matches!(render_result, RenderingResult::ContinueRenderingChildren) {
240                render_item_children(renderer, component, index as isize, window_adapter);
241            }
242            renderer.restore_state();
243            VisitChildrenResult::CONTINUE
244        };
245    vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
246    VRc::borrow_pin(component).as_ref().visit_children_item(
247        index,
248        crate::item_tree::TraversalOrder::BackToFront,
249        actual_visitor,
250    );
251}
252
253/// Renders the tree of items that component holds, using the specified renderer. Rendering is done
254/// relative to the specified origin.
255pub fn render_component_items(
256    component: &ItemTreeRc,
257    renderer: &mut dyn ItemRenderer,
258    origin: LogicalPoint,
259    window_adapter: &WindowAdapterRc,
260) {
261    renderer.save_state();
262    renderer.translate(origin.to_vector());
263
264    render_item_children(renderer, component, -1, window_adapter);
265
266    renderer.restore_state();
267}
268
269/// Compute the bounding rect of all children. This does /not/ include item's own bounding rect. Remember to run this
270/// via `evaluate_no_tracking`.
271pub fn item_children_bounding_rect(
272    item_rc: &ItemRc,
273    window_adapter: &WindowAdapterRc,
274) -> LogicalRect {
275    item_children_bounding_rect_transformed(item_rc, window_adapter, Default::default())
276}
277
278fn item_children_bounding_rect_transformed(
279    item_rc: &ItemRc,
280    window_adapter: &WindowAdapterRc,
281    transform: crate::lengths::ItemTransform,
282) -> LogicalRect {
283    let mut bounding_rect = LogicalRect::zero();
284
285    let mut actual_visitor =
286        |component: &ItemTreeRc, index: u32, _ref: Pin<ItemRef>| -> VisitChildrenResult {
287            let item_rc = ItemRc::new(component.clone(), index);
288            let bounds_with_children =
289                item_with_children_bounding_rect_transformed(&item_rc, window_adapter, transform);
290
291            bounding_rect = bounding_rect.union(&bounds_with_children);
292
293            VisitChildrenResult::CONTINUE
294        };
295
296    vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
297    VRc::borrow_pin(item_rc.item_tree()).as_ref().visit_children_item(
298        item_rc.index() as isize,
299        crate::item_tree::TraversalOrder::BackToFront,
300        actual_visitor,
301    );
302
303    bounding_rect
304}
305
306fn item_with_children_bounding_rect_transformed(
307    item_rc: &ItemRc,
308    window_adapter: &WindowAdapterRc,
309    transform: crate::lengths::ItemTransform,
310) -> LogicalRect {
311    let item_geom = item_rc.geometry();
312
313    if item_rc.borrow().as_ref().clips_children() {
314        transform.outer_transformed_rect(&item_geom.cast()).cast()
315    } else {
316        let bounding = item_rc.bounding_rect(&item_geom, window_adapter);
317        let bounding = transform.outer_transformed_rect(&bounding.cast());
318        let children_relative_transform = item_rc
319            .children_transform()
320            .unwrap_or_default()
321            .then_translate(item_geom.origin.to_vector().cast());
322
323        let children_absolute_transform = transform.then(&children_relative_transform);
324
325        item_children_bounding_rect_transformed(
326            item_rc,
327            window_adapter,
328            children_absolute_transform,
329        )
330        .union(&bounding.cast())
331    }
332}
333
334/// Trait for an item that represent a Rectangle to the Renderer
335#[allow(missing_docs)]
336pub trait RenderRectangle {
337    fn background(self: Pin<&Self>) -> Brush;
338}
339
340/// Trait for an item that represent a Rectangle with a border to the Renderer
341#[allow(missing_docs)]
342pub trait RenderBorderRectangle {
343    fn background(self: Pin<&Self>) -> Brush;
344    fn border_width(self: Pin<&Self>) -> LogicalLength;
345    fn border_radius(self: Pin<&Self>) -> LogicalBorderRadius;
346    fn border_color(self: Pin<&Self>) -> Brush;
347}
348
349/// The geometry for drawing a [`RenderBorderRectangle`] in the CSS box model, shared by
350/// the renderers that stroke the border centered on a path: the border is drawn entirely
351/// inside the item's geometry, the background doesn't extend under an opaque border, and
352/// brushes are resolved against the full border box.
353pub struct BorderRectLayout {
354    /// The size of the border box, for resolving the background and border brushes.
355    pub brush_size: euclid::Size2D<f32, PhysicalPx>,
356    /// The rectangle to fill with the background brush.
357    pub background_rect: euclid::Rect<f32, PhysicalPx>,
358    /// The corner radii of `background_rect`.
359    pub background_radius: PhysicalBorderRadius,
360    /// The rectangle to stroke with `border_color` when `border_width` is positive.
361    pub border_rect: euclid::Rect<f32, PhysicalPx>,
362    /// The corner radii of `border_rect`.
363    pub border_radius: PhysicalBorderRadius,
364    /// The stroke width of the border; zero for transparent borders.
365    pub border_width: euclid::Length<f32, PhysicalPx>,
366    /// The border brush.
367    pub border_color: Brush,
368}
369
370impl BorderRectLayout {
371    /// Computes the layout for a border rectangle of `size`, or `None` when the
372    /// geometry is empty.
373    pub fn new(
374        rect: Pin<&dyn RenderBorderRectangle>,
375        size: LogicalSize,
376        scale_factor: ScaleFactor,
377    ) -> Option<Self> {
378        // `cast()`: the logical Coord type can be i32, the physical geometry is f32.
379        let mut geometry = euclid::Rect::from_size(size.cast() * scale_factor);
380        if geometry.is_empty() {
381            return None;
382        }
383        let brush_size = geometry.size;
384
385        let border_color = rect.border_color();
386        let opaque_border = border_color.is_opaque();
387        let mut border_width = if border_color.is_transparent() {
388            euclid::Length::new(0.)
389        } else {
390            rect.border_width().cast() * scale_factor
391        };
392
393        // The stroke is centered on the path (50% inside, 50% outside), while in CSS the
394        // border is entirely inside the geometry. Ensure positive corner radii are at
395        // least half the border width, so that the outer edge keeps a radius at all;
396        // this is incorrect when the radius is smaller than that, but that can't be
397        // helped - better a radius a bit too big than no radius.
398        let fill_radius = (rect.border_radius().cast() * scale_factor)
399            .outer(border_width / 2. + euclid::Length::new(0.01));
400        let border_radius = fill_radius.inner(border_width / 2.);
401
402        let (background_rect, background_radius) = if opaque_border {
403            // The fill doesn't need to extend under an opaque border, so fill and
404            // stroke share the inset geometry.
405            adjust_rect_and_border_for_inner_drawing(&mut geometry, &mut border_width);
406            (geometry, border_radius)
407        } else {
408            // A (semi-)transparent border must not cover the background, so the fill
409            // covers the full rectangle.
410            let background = (geometry, fill_radius);
411            adjust_rect_and_border_for_inner_drawing(&mut geometry, &mut border_width);
412            background
413        };
414
415        Some(Self {
416            brush_size,
417            background_rect,
418            background_radius,
419            border_rect: geometry,
420            border_radius,
421            border_width,
422            border_color,
423        })
424    }
425}
426
427/// The region children are clipped to when `clip` is enabled on an element with a
428/// border: the rectangle inside the border ring, with the corner radii reduced
429/// accordingly. See <https://github.com/slint-ui/slint/issues/1988>.
430pub fn clip_content_box(
431    size: LogicalSize,
432    radius: LogicalBorderRadius,
433    border_width: LogicalLength,
434) -> (LogicalRect, LogicalBorderRadius) {
435    // A border covering half the size or more leaves no content region. Integer
436    // arithmetic, as the logical Coord type can be i32.
437    let two = 2 as crate::Coord;
438    let border_width = border_width
439        .max(LogicalLength::default())
440        .min(size.width_length() / two)
441        .min(size.height_length() / two);
442    let rect = LogicalRect::new(
443        LogicalPoint::from_lengths(border_width, border_width),
444        LogicalSize::from_lengths(
445            size.width_length() - border_width * two,
446            size.height_length() - border_width * two,
447        ),
448    );
449    (rect, radius.inner(border_width))
450}
451
452/// Trait for an item that represents an Image towards the renderer
453#[allow(missing_docs)]
454pub trait RenderImage {
455    fn target_size(self: Pin<&Self>) -> LogicalSize;
456    fn source(self: Pin<&Self>) -> Image;
457    fn source_clip(self: Pin<&Self>) -> Option<IntRect>;
458    fn image_fit(self: Pin<&Self>) -> ImageFit;
459    fn rendering(self: Pin<&Self>) -> ImageRendering;
460    fn colorize(self: Pin<&Self>) -> Brush;
461    fn alignment(self: Pin<&Self>) -> (ImageHorizontalAlignment, ImageVerticalAlignment);
462    fn tiling(self: Pin<&Self>) -> (ImageTiling, ImageTiling);
463}
464
465/// Trait for an item has font properties
466#[allow(missing_docs)]
467pub trait HasFont {
468    fn font_request(self: Pin<&Self>, self_rc: &crate::items::ItemRc) -> FontRequest;
469}
470
471#[allow(missing_docs)]
472pub enum PlainOrStyledText {
473    Plain(SharedString),
474    Styled(crate::styled_text::StyledText),
475}
476
477/// Trait for an item that represents an string towards the renderer
478#[allow(missing_docs)]
479pub trait RenderString: HasFont {
480    fn text(self: Pin<&Self>) -> PlainOrStyledText;
481    fn max_lines(self: Pin<&Self>) -> i32 {
482        0
483    }
484    /// The maximum number of lines to lay out and render, from the `max-lines` property.
485    /// Property values less than or equal to zero mean no limit.
486    fn line_limit(self: Pin<&Self>) -> Option<usize> {
487        usize::try_from(self.max_lines()).ok().filter(|max_lines| *max_lines > 0)
488    }
489    /// Stroke brush, width and style. The style is baked into the shaped glyphs, so it lives
490    /// here (rather than in `RenderText`) to keep measuring and drawing shaping-identical.
491    fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
492        Default::default()
493    }
494    /// Color of `Style::Link` spans. Like `stroke`, it's baked into the shaped glyphs.
495    fn link_color(self: Pin<&Self>) -> Color {
496        Default::default()
497    }
498}
499
500/// Trait for an item that represents an Text towards the renderer
501#[allow(missing_docs)]
502pub trait RenderText: RenderString {
503    fn target_size(self: Pin<&Self>) -> LogicalSize;
504    fn color(self: Pin<&Self>) -> Brush;
505    fn alignment(self: Pin<&Self>) -> (TextHorizontalAlignment, TextVerticalAlignment);
506    fn wrap(self: Pin<&Self>) -> TextWrap;
507    fn overflow(self: Pin<&Self>) -> TextOverflow;
508    fn is_markdown(self: Pin<&Self>) -> bool;
509}
510
511impl HasFont for (SharedString, Brush) {
512    fn font_request(self: Pin<&Self>, self_rc: &crate::items::ItemRc) -> FontRequest {
513        crate::items::WindowItem::resolved_font_request(
514            self_rc,
515            SharedString::default(),
516            0,
517            LogicalLength::default(),
518            LogicalLength::default(),
519            0.0,
520            false,
521        )
522    }
523}
524
525impl RenderString for (SharedString, Brush) {
526    fn text(self: Pin<&Self>) -> PlainOrStyledText {
527        PlainOrStyledText::Plain(self.0.clone())
528    }
529}
530
531impl RenderText for (SharedString, Brush) {
532    fn target_size(self: Pin<&Self>) -> LogicalSize {
533        LogicalSize::default()
534    }
535
536    fn color(self: Pin<&Self>) -> Brush {
537        self.1.clone()
538    }
539
540    fn alignment(
541        self: Pin<&Self>,
542    ) -> (crate::items::TextHorizontalAlignment, crate::items::TextVerticalAlignment) {
543        Default::default()
544    }
545
546    fn wrap(self: Pin<&Self>) -> crate::items::TextWrap {
547        Default::default()
548    }
549
550    fn overflow(self: Pin<&Self>) -> crate::items::TextOverflow {
551        Default::default()
552    }
553
554    fn is_markdown(self: Pin<&Self>) -> bool {
555        false
556    }
557}
558
559/// Trait used to render each items.
560///
561/// The item needs to be rendered relative to its (x,y) position. For example,
562/// draw_rectangle should draw a rectangle in `(pos.x + rect.x, pos.y + rect.y)`
563#[allow(missing_docs)]
564pub trait ItemRenderer {
565    fn draw_rectangle(
566        &mut self,
567        rect: Pin<&dyn RenderRectangle>,
568        _self_rc: &ItemRc,
569        _size: LogicalSize,
570        _cache: &CachedRenderingData,
571    );
572    fn draw_border_rectangle(
573        &mut self,
574        rect: Pin<&dyn RenderBorderRectangle>,
575        _self_rc: &ItemRc,
576        _size: LogicalSize,
577        _cache: &CachedRenderingData,
578    );
579    fn draw_window_background(
580        &mut self,
581        rect: Pin<&dyn RenderRectangle>,
582        self_rc: &ItemRc,
583        size: LogicalSize,
584        cache: &CachedRenderingData,
585    );
586    fn draw_image(
587        &mut self,
588        image: Pin<&dyn RenderImage>,
589        _self_rc: &ItemRc,
590        _size: LogicalSize,
591        _cache: &CachedRenderingData,
592    );
593    fn draw_text(
594        &mut self,
595        text: Pin<&dyn RenderText>,
596        _self_rc: &ItemRc,
597        _size: LogicalSize,
598        _cache: &CachedRenderingData,
599    );
600    fn draw_text_input(
601        &mut self,
602        text_input: Pin<&TextInput>,
603        _self_rc: &ItemRc,
604        _size: LogicalSize,
605    );
606    #[cfg(feature = "path")]
607    fn draw_path(&mut self, path: Pin<&Path>, _self_rc: &ItemRc, _size: LogicalSize);
608    fn draw_box_shadow(
609        &mut self,
610        box_shadow: Pin<&BoxShadow>,
611        _self_rc: &ItemRc,
612        _size: LogicalSize,
613    );
614    fn visit_opacity(
615        &mut self,
616        opacity_item: Pin<&Opacity>,
617        _self_rc: &ItemRc,
618        _size: LogicalSize,
619    ) -> RenderingResult {
620        self.apply_opacity(opacity_item.opacity());
621        RenderingResult::ContinueRenderingChildren
622    }
623    fn visit_layer(
624        &mut self,
625        _layer_item: Pin<&Layer>,
626        _self_rc: &ItemRc,
627        _size: LogicalSize,
628    ) -> RenderingResult {
629        // Not supported
630        RenderingResult::ContinueRenderingChildren
631    }
632
633    // Apply the bounds of the Clip element, if enabled. The default implementation calls
634    // combine_clip, but the render may choose an alternate way of implementing the clip.
635    // For example the GL backend uses a layered rendering approach.
636    fn visit_clip(
637        &mut self,
638        clip_item: Pin<&Clip>,
639        _item_rc: &ItemRc,
640        size: LogicalSize,
641    ) -> RenderingResult {
642        if clip_item.clip() {
643            let (clip_rect, clip_radius) =
644                clip_content_box(size, clip_item.logical_border_radius(), clip_item.border_width());
645            let clip_region_valid = self.combine_clip(clip_rect, clip_radius);
646
647            // If clipping is enabled but the clip element is outside the visible range, then we don't
648            // need to bother doing anything, not even rendering the children.
649            if !clip_region_valid {
650                return RenderingResult::ContinueRenderingWithoutChildren;
651            }
652        }
653        RenderingResult::ContinueRenderingChildren
654    }
655
656    /// Clip the further call until restore_state.
657    /// (FIXME: consider removing radius and have another function that take a path instead)
658    /// Returns a boolean indicating the state of the new clip region: true if the clip region covers
659    /// an area; false if the clip region is empty.
660    fn combine_clip(&mut self, rect: LogicalRect, radius: LogicalBorderRadius) -> bool;
661    /// Get the current clip bounding box in the current transformed coordinate.
662    fn get_current_clip(&self) -> LogicalRect;
663
664    fn translate(&mut self, distance: LogicalVector);
665    /// Returns the accumulated local-to-screen transform, including
666    /// translate, scale, and rotate.
667    fn current_transform(&self) -> crate::lengths::ItemTransform {
668        todo!("this renderer does not track transforms for partial rendering")
669    }
670    fn rotate(&mut self, angle_in_degrees: f32);
671    fn scale(&mut self, scale_x_factor: f32, scale_y_factor: f32);
672    /// Apply the opacity (between 0 and 1) for all following items until the next call to restore_state.
673    fn apply_opacity(&mut self, opacity: f32);
674    /// Returns true when the opacity accumulated via [`Self::apply_opacity`] is zero.
675    fn global_alpha_transparent(&self) -> bool {
676        false
677    }
678
679    fn save_state(&mut self);
680    fn restore_state(&mut self);
681
682    /// Returns the scale factor
683    fn scale_factor(&self) -> ScaleFactor;
684
685    /// Draw a pixmap in position indicated by the `pos`.
686    /// The pixmap will be taken from cache if the cache is valid, otherwise, update_fn will be called
687    /// with a callback that need to be called once with `fn (width, height, data)` where data are the
688    /// RGBA premultiplied pixel values
689    fn draw_cached_pixmap(
690        &mut self,
691        item_cache: &ItemRc,
692        update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
693    );
694
695    /// Draw the given string with the specified color at current (0, 0) with the default font. Mainly
696    /// used by the performance counter overlay.
697    fn draw_string(&mut self, string: &str, color: crate::Color);
698
699    fn draw_image_direct(&mut self, image: crate::graphics::Image);
700
701    /// This is called before it is being rendered (before the draw_* function).
702    /// Returns
703    ///  - if the item needs to be drawn (false means it is clipped or doesn't need to be drawn)
704    ///  - the origin of the item
705    ///  - the size of the item, or None if it doesn't need to be drawn and the size wasn't computed
706    fn filter_item(
707        &mut self,
708        item: &ItemRc,
709        window_adapter: &WindowAdapterRc,
710    ) -> (bool, LogicalPoint, Option<LogicalSize>) {
711        let item_geometry = item.geometry();
712        // Query bounding rect untracked, as properties that affect the bounding rect are already tracked
713        // when rendering the item.
714        let bounding_rect = crate::properties::evaluate_no_tracking(|| {
715            item.bounding_rect(&item_geometry, window_adapter)
716        });
717        (
718            self.get_current_clip().intersects(&bounding_rect),
719            item_geometry.origin,
720            Some(item_geometry.size),
721        )
722    }
723
724    fn window(&self) -> &crate::window::WindowInner;
725
726    /// Return the internal renderer
727    fn as_any(&mut self) -> Option<&mut dyn core::any::Any>;
728}
729
730/// Renderer-backend hooks for [`Layer::render`], which owns the caching and
731/// bounds computation; implementors only describe how to allocate, render
732/// into, and unwrap a layer target.
733///
734/// The `'cache` lifetime lets `layer_cache` hand out a reference that doesn't
735/// borrow `self`, so the orchestrator can call the other `&mut self` methods.
736#[cfg(feature = "std")]
737pub trait LayerRenderer<'cache>: ItemRenderer {
738    /// Per-layer render target (for example a skia `Surface` or a reused GPU texture).
739    type LayerTarget;
740    /// Cached image type produced from rendering into a [`Self::LayerTarget`].
741    type Image: Clone;
742
743    /// Access the renderer's layer cache.
744    fn layer_cache(
745        &self,
746    ) -> &'cache ItemCache<Option<(euclid::Point2D<f32, crate::lengths::PhysicalPx>, Self::Image)>>;
747
748    /// Allocate a target of the given physical size; `None` aborts rendering.
749    fn create_layer_target(
750        &mut self,
751        item_rc: &ItemRc,
752        physical_size: euclid::Size2D<f32, crate::lengths::PhysicalPx>,
753    ) -> Option<Self::LayerTarget>;
754
755    /// Redirect rendering into `target`, translate so children are positioned
756    /// relative to `bounding_rect.origin`, call `render_item_children`, then
757    /// restore. The dance is backend-specific (sub-renderer vs. render-target
758    /// swap), hence the hook.
759    fn render_into_layer(
760        &mut self,
761        target: Self::LayerTarget,
762        item_rc: &ItemRc,
763        bounding_rect: LogicalRect,
764    ) -> Self::Image;
765}
766
767/// Render the children of a [`Layer`] item through the given [`LayerRenderer`] backend.
768#[cfg(feature = "std")]
769pub fn render_layer<'cache, R>(
770    renderer: &mut R,
771    item_rc: &ItemRc,
772) -> Option<(euclid::Point2D<f32, crate::lengths::PhysicalPx>, R::Image)>
773where
774    R: LayerRenderer<'cache> + ?Sized + 'cache,
775{
776    let cache = renderer.layer_cache();
777    let scale_factor = renderer.scale_factor();
778
779    let compute_bounds = |r: &R| -> LogicalRect {
780        item_children_bounding_rect(item_rc, &r.window().window_adapter())
781            .intersection(
782                &r.get_current_clip().union(&LogicalRect::from_size(item_rc.geometry().size)),
783            )
784            .unwrap_or_default()
785    };
786
787    cache.get_or_update_cache_entry(item_rc, || {
788        // Don't track dependencies of the bounding rect here: the actual
789        // rendering below will track them as it walks the children.
790        let bounding_rect = crate::properties::evaluate_no_tracking(|| compute_bounds(renderer));
791        let physical_origin = bounding_rect.origin.cast() * scale_factor;
792        let layer_size = bounding_rect.size.cast() * scale_factor;
793
794        let Some(target) = renderer.create_layer_target(item_rc, layer_size) else {
795            // Target allocation failed (typically a zero-sized layer). The
796            // children never ran, so their dependencies weren't tracked.
797            // Re-invoke the bounds closure with tracking enabled so the
798            // layer re-renders when the size becomes non-zero.
799            let _ = compute_bounds(renderer);
800            return None;
801        };
802
803        let image = renderer.render_into_layer(target, item_rc, bounding_rect);
804        Some((physical_origin, image))
805    })
806}
807
808/// Helper trait to express the features of an item renderer.
809pub trait ItemRendererFeatures {
810    /// The renderer supports applying 2D transformations to items.
811    const SUPPORTS_TRANSFORMATIONS: bool;
812}