1#![warn(missing_docs)]
5use 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#[derive(Default, Debug)]
30#[repr(C)]
31pub struct CachedRenderingData {
32 pub(crate) cache_index: Cell<usize>,
34 pub(crate) cache_generation: Cell<usize>,
38}
39
40impl CachedRenderingData {
41 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 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#[cfg(feature = "std")]
77pub struct ItemCache<T> {
78 map: RefCell<HashMap<*const vtable::Dyn, HashMap<u32, CachedGraphicsData<T>>>>,
80 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 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 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 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 pub fn clear_all(&self) {
158 self.map.borrow_mut().clear();
159 }
160
161 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 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 pub fn is_empty(&self) -> bool {
180 self.map.borrow().is_empty()
181 }
182}
183
184pub 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 let render_result = if do_draw
204 || item.as_ref().clips_children()
205 || 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
231pub 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
247pub 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#[allow(missing_docs)]
287pub trait RenderRectangle {
288 fn background(self: Pin<&Self>) -> Brush;
289}
290
291#[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#[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#[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#[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 RenderingResult::ContinueRenderingChildren
425 }
426
427 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 !clip_region_valid {
448 return RenderingResult::ContinueRenderingWithoutChildren;
449 }
450 }
451 RenderingResult::ContinueRenderingChildren
452 }
453
454 fn combine_clip(
460 &mut self,
461 rect: LogicalRect,
462 radius: LogicalBorderRadius,
463 border_width: LogicalLength,
464 ) -> bool;
465 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 fn apply_opacity(&mut self, opacity: f32);
475
476 fn save_state(&mut self);
477 fn restore_state(&mut self);
478
479 fn scale_factor(&self) -> f32;
481
482 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 fn draw_string(&mut self, string: &str, color: crate::Color);
495
496 fn draw_image_direct(&mut self, image: crate::graphics::Image);
497
498 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 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 fn as_any(&mut self) -> Option<&mut dyn core::any::Any>;
520
521 fn metrics(&self) -> crate::graphics::rendering_metrics_collector::RenderingMetrics {
524 Default::default()
525 }
526}
527
528pub trait ItemRendererFeatures {
530 const SUPPORTS_TRANSFORMATIONS: bool;
532}
533
534#[derive(Clone)]
537
538pub enum CachedItemBoundingBoxAndTransform {
539 RegularItem {
541 bounding_rect: LogicalRect,
543 offset: LogicalVector,
545 },
546 ItemWithTransform {
548 bounding_rect: LogicalRect,
550 transform: Box<ItemTransform>,
552 },
553 ClipItem {
555 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 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
614pub type PartialRenderingCache = RenderingCache<CachedItemBoundingBoxAndTransform>;
616
617#[derive(Default, Clone, Debug)]
619pub struct DirtyRegion {
620 rectangles: [euclid::Box2D<Coord, LogicalPx>; Self::MAX_COUNT],
621 count: usize,
622}
623
624impl DirtyRegion {
625 pub(crate) const MAX_COUNT: usize = 3;
627
628 pub fn iter(&self) -> impl Iterator<Item = euclid::Box2D<Coord, LogicalPx>> + '_ {
630 (0..self.count).map(|x| self.rectangles[x])
631 }
632
633 pub fn add_rect(&mut self, rect: LogicalRect) {
637 self.add_box(rect.to_box2d());
638 }
639
640 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 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 #[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 #[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 #[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#[derive(PartialEq, Eq, Debug, Clone, Default, Copy)]
735pub enum RepaintBufferType {
736 #[default]
737 NewBuffer,
739 ReusedBuffer,
744
745 SwappedBuffers,
749}
750
751pub struct PartialRenderer<'a, T> {
753 cache: &'a RefCell<PartialRenderingCache>,
754 pub dirty_region: DirtyRegion,
756 pub actual_renderer: T,
758 pub window_adapter: Rc<dyn WindowAdapter>,
760}
761
762impl<'a, T: ItemRenderer + ItemRendererFeatures> PartialRenderer<'a, T> {
763 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 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 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 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 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 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 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#[derive(Default)]
1132pub struct PartialRenderingState {
1133 partial_cache: RefCell<PartialRenderingCache>,
1134 force_dirty: RefCell<DirtyRegion>,
1136 force_screen_refresh: Cell<bool>,
1138}
1139
1140impl PartialRenderingState {
1141 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 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 pub fn mark_dirty_region(&self, region: DirtyRegion) {
1184 self.force_dirty.replace_with(|r| r.union(®ion));
1185 }
1186
1187 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 self.force_screen_refresh.set(true)
1197 }
1198
1199 pub fn clear_cache(&self) {
1201 self.partial_cache.borrow_mut().clear();
1202 }
1203
1204 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}