1use crate::Coord;
19use crate::item_rendering::{
20 ItemRenderer, ItemRendererFeatures, RenderBorderRectangle, RenderImage, RenderRectangle,
21 RenderText,
22};
23use crate::item_tree::{ItemTreeRc, ItemTreeWeak, ItemVisitorResult};
24#[cfg(feature = "path")]
25use crate::items::Path;
26use crate::items::{BoxShadow, Clip, ItemRc, ItemRef, Layer, Opacity, RenderingResult, TextInput};
27use crate::lengths::{
28 ItemTransform, LogicalBorderRadius, LogicalPoint, LogicalPx, LogicalRect, LogicalSize,
29 LogicalVector, ScaleFactor,
30};
31use crate::properties::PropertyTracker;
32use crate::window::WindowAdapter;
33use alloc::boxed::Box;
34use alloc::rc::Rc;
35use core::cell::{Cell, RefCell};
36use core::pin::Pin;
37
38#[derive(Default, Debug)]
41#[repr(C)]
42pub struct CachedRenderingData {
43 pub(crate) cache_index: Cell<usize>,
45 pub(crate) cache_generation: Cell<usize>,
49}
50
51impl CachedRenderingData {
52 fn release(
56 &self,
57 cache: &mut PartialRendererCache,
58 ) -> Option<CachedItemBoundingBoxAndTransform> {
59 if self.cache_generation.get() == cache.generation() {
60 let index = self.cache_index.get();
61 self.cache_generation.set(0);
62 Some(cache.remove(index).data)
63 } else {
64 None
65 }
66 }
67
68 fn get_entry<'a>(
70 &self,
71 cache: &'a mut PartialRendererCache,
72 ) -> Option<&'a mut PartialRenderingCachedData> {
73 let index = self.cache_index.get();
74 if self.cache_generation.get() == cache.generation() { cache.get_mut(index) } else { None }
75 }
76}
77
78#[derive(Clone)]
87pub enum CachedItemBoundingBoxAndTransform {
88 RegularItem {
90 bounding_rect: LogicalRect,
92 offset: LogicalVector,
94 sibling_index: u16,
95 },
96 ItemWithTransform {
98 bounding_rect: LogicalRect,
100 transform: Box<ItemTransform>,
102 sibling_index: u16,
103 },
104 ClipItem {
106 geometry: LogicalRect,
108 sibling_index: u16,
109 },
110}
111
112impl CachedItemBoundingBoxAndTransform {
113 fn bounding_rect(&self) -> &LogicalRect {
114 match self {
115 CachedItemBoundingBoxAndTransform::RegularItem { bounding_rect, .. } => bounding_rect,
116 CachedItemBoundingBoxAndTransform::ItemWithTransform { bounding_rect, .. } => {
117 bounding_rect
118 }
119 CachedItemBoundingBoxAndTransform::ClipItem { geometry, .. } => geometry,
120 }
121 }
122
123 fn transform(&self) -> ItemTransform {
124 match self {
125 CachedItemBoundingBoxAndTransform::RegularItem { offset, .. } => {
126 ItemTransform::translation(offset.x as f32, offset.y as f32)
127 }
128 CachedItemBoundingBoxAndTransform::ItemWithTransform { transform, .. } => **transform,
129 CachedItemBoundingBoxAndTransform::ClipItem { geometry, .. } => {
130 ItemTransform::translation(geometry.origin.x as f32, geometry.origin.y as f32)
131 }
132 }
133 }
134
135 fn sibling_index(&mut self) -> &mut u16 {
136 match self {
137 CachedItemBoundingBoxAndTransform::RegularItem { sibling_index, .. }
138 | CachedItemBoundingBoxAndTransform::ItemWithTransform { sibling_index, .. }
139 | CachedItemBoundingBoxAndTransform::ClipItem { sibling_index, .. } => sibling_index,
140 }
141 }
142
143 fn same_geometry(&self, other: &Self) -> bool {
145 use CachedItemBoundingBoxAndTransform::*;
146 match (self, other) {
147 (
148 RegularItem { bounding_rect: a, offset: oa, .. },
149 RegularItem { bounding_rect: b, offset: ob, .. },
150 ) => a == b && oa == ob,
151 (
152 ItemWithTransform { bounding_rect: a, transform: ta, .. },
153 ItemWithTransform { bounding_rect: b, transform: tb, .. },
154 ) => a == b && ta == tb,
155 (ClipItem { geometry: a, .. }, ClipItem { geometry: b, .. }) => a == b,
156 _ => false,
157 }
158 }
159
160 fn new<T: ItemRendererFeatures>(
161 item_rc: &ItemRc,
162 window_adapter: &Rc<dyn WindowAdapter>,
163 sibling_index: u16,
164 ) -> Self {
165 let geometry = item_rc.geometry();
166
167 if item_rc.borrow().as_ref().clips_children() {
168 return Self::ClipItem { geometry, sibling_index };
169 }
170
171 let bounding_rect = crate::properties::evaluate_no_tracking(|| {
174 item_rc.bounding_rect(&geometry, window_adapter)
175 });
176
177 if let Some(complex_child_transform) = (T::SUPPORTS_TRANSFORMATIONS
178 && window_adapter.renderer().supports_transformations())
179 .then(|| item_rc.children_transform())
180 .flatten()
181 {
182 Self::ItemWithTransform {
183 bounding_rect,
184 transform: complex_child_transform
185 .then_translate(geometry.origin.to_vector().cast())
186 .into(),
187 sibling_index,
188 }
189 } else {
190 Self::RegularItem { bounding_rect, offset: geometry.origin.to_vector(), sibling_index }
191 }
192 }
193}
194
195struct PartialRenderingCachedData {
196 pub data: CachedItemBoundingBoxAndTransform,
198 pub tracker: Option<core::pin::Pin<Box<PropertyTracker>>>,
200}
201impl PartialRenderingCachedData {
202 fn new(data: CachedItemBoundingBoxAndTransform) -> Self {
203 Self { data, tracker: None }
204 }
205}
206
207struct PartialRendererCache {
209 slab: slab::Slab<PartialRenderingCachedData>,
210 generation: usize,
211 tree_screen_rects: alloc::collections::BTreeMap<usize, LogicalRect>,
217}
218
219impl Default for PartialRendererCache {
220 fn default() -> Self {
221 Self { slab: Default::default(), generation: 1, tree_screen_rects: Default::default() }
222 }
223}
224
225impl PartialRendererCache {
226 pub fn generation(&self) -> usize {
229 self.generation
230 }
231
232 pub fn get_mut(&mut self, index: usize) -> Option<&mut PartialRenderingCachedData> {
234 self.slab.get_mut(index)
235 }
236
237 pub fn insert(&mut self, data: PartialRenderingCachedData) -> usize {
239 self.slab.insert(data)
240 }
241
242 pub fn remove(&mut self, index: usize) -> PartialRenderingCachedData {
244 self.slab.remove(index)
245 }
246
247 pub fn clear(&mut self) {
250 self.slab.clear();
251 self.generation += 1;
252 self.tree_screen_rects.clear();
253 }
254}
255
256#[derive(Default, Clone)]
258pub struct DirtyRegion {
259 rectangles: [euclid::Box2D<Coord, LogicalPx>; Self::MAX_COUNT],
260 count: usize,
261}
262
263impl core::fmt::Debug for DirtyRegion {
264 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
265 write!(f, "{:?}", &self.rectangles[..self.count])
266 }
267}
268
269impl DirtyRegion {
270 pub const MAX_COUNT: usize = 3;
272
273 pub fn iter(&self) -> impl Iterator<Item = euclid::Box2D<Coord, LogicalPx>> + '_ {
275 (0..self.count).map(|x| self.rectangles[x])
276 }
277
278 pub fn add_rect(&mut self, rect: LogicalRect) {
282 self.add_box(rect.to_box2d());
283 }
284
285 pub fn add_box(&mut self, b: euclid::Box2D<Coord, LogicalPx>) {
289 if b.is_empty() {
290 return;
291 }
292 let mut i = 0;
293 while i < self.count {
294 let r = &self.rectangles[i];
295 if r.contains_box(&b) {
296 return;
298 } else if b.contains_box(r) {
299 self.rectangles.swap(i, self.count - 1);
300 self.count -= 1;
301 continue;
302 }
303 i += 1;
304 }
305
306 if self.count < Self::MAX_COUNT {
307 self.rectangles[self.count] = b;
308 self.count += 1;
309 } else {
310 let best_merge = (0..self.count)
311 .map(|i| (i, self.rectangles[i].union(&b).area() - self.rectangles[i].area()))
312 .min_by(|a, b| PartialOrd::partial_cmp(&a.1, &b.1).unwrap())
313 .expect("There should always be rectangles")
314 .0;
315 self.rectangles[best_merge] = self.rectangles[best_merge].union(&b);
316 }
317 }
318
319 #[must_use]
323 pub fn union(&self, other: &Self) -> Self {
324 let mut s = self.clone();
325 for o in other.iter() {
326 s.add_box(o)
327 }
328 s
329 }
330
331 #[must_use]
333 pub fn bounding_rect(&self) -> LogicalRect {
334 if self.count == 0 {
335 return Default::default();
336 }
337 let mut r = self.rectangles[0];
338 for i in 1..self.count {
339 r = r.union(&self.rectangles[i]);
340 }
341 r.to_rect()
342 }
343
344 #[must_use]
346 pub fn intersection(&self, other: LogicalRect) -> DirtyRegion {
347 let mut ret = self.clone();
348 let other = other.to_box2d();
349 let mut i = 0;
350 while i < ret.count {
351 if let Some(x) = ret.rectangles[i].intersection(&other) {
352 ret.rectangles[i] = x;
353 } else {
354 ret.count -= 1;
355 ret.rectangles.swap(i, ret.count);
356 continue;
357 }
358 i += 1;
359 }
360 ret
361 }
362
363 fn draw_intersects(&self, clipped_geom: LogicalRect) -> bool {
364 let b = clipped_geom.to_box2d();
365 self.iter().any(|r| r.intersects(&b))
366 }
367}
368
369impl From<LogicalRect> for DirtyRegion {
370 fn from(value: LogicalRect) -> Self {
371 let mut s = Self::default();
372 s.add_rect(value);
373 s
374 }
375}
376
377#[derive(PartialEq, Eq, Debug, Clone, Default, Copy)]
380pub enum RepaintBufferType {
381 #[default]
382 NewBuffer,
384 ReusedBuffer,
389
390 SwappedBuffers,
394}
395
396fn clipped_screen_rect(
399 rect: &LogicalRect,
400 transform: &ItemTransform,
401 clip_rect: &LogicalRect,
402) -> Option<LogicalRect> {
403 #[cfg(not(slint_int_coord))]
404 if !rect.origin.is_finite() {
405 return None;
407 }
408
409 if rect.is_empty() {
410 return None;
411 }
412 let rect = rect.cast();
413 let transformed =
416 if (transform.m11, transform.m12, transform.m21, transform.m22) == (1., 0., 0., 1.) {
417 rect.translate(euclid::vec2(transform.m31, transform.m32))
418 } else {
419 transform.outer_transformed_rect(&rect)
420 };
421 transformed.cast().intersection(clip_rect)
422}
423
424pub struct PartialRenderer<'a, T> {
428 cache: &'a RefCell<PartialRendererCache>,
429 pub dirty_region: DirtyRegion,
431 pub actual_renderer: T,
433 pub window_adapter: Rc<dyn WindowAdapter>,
435}
436
437impl<'a, T: ItemRenderer + ItemRendererFeatures> PartialRenderer<'a, T> {
438 fn new(
440 cache: &'a RefCell<PartialRendererCache>,
441 initial_dirty_region: DirtyRegion,
442 actual_renderer: T,
443 ) -> Self {
444 let window_adapter = actual_renderer.window().window_adapter();
445 Self { cache, dirty_region: initial_dirty_region, actual_renderer, window_adapter }
446 }
447
448 pub fn compute_dirty_regions(
450 &mut self,
451 component: &ItemTreeRc,
452 origin: LogicalPoint,
453 size: LogicalSize,
454 ) {
455 #[derive(Clone, Copy)]
456 struct ComputeDirtyRegionState {
457 transform_to_screen: ItemTransform,
458 old_transform_to_screen: ItemTransform,
459 clipped: LogicalRect,
460 must_refresh_children: bool,
461 depth: usize,
463 }
464
465 let sibling_counters = RefCell::new(alloc::vec::Vec::<(u16, u16)>::new());
474
475 impl ComputeDirtyRegionState {
476 fn adjust_transforms_for_child(
479 &mut self,
480 children_transform: &ItemTransform,
481 old_children_transform: &ItemTransform,
482 ) {
483 self.transform_to_screen = children_transform.then(&self.transform_to_screen);
484 self.old_transform_to_screen =
485 old_children_transform.then(&self.old_transform_to_screen);
486 }
487 }
488
489 crate::item_tree::visit_items(
490 component,
491 crate::item_tree::TraversalOrder::BackToFront,
492 |component, item, index, state| {
493 let mut new_state = *state;
494 let item_rc = ItemRc::new(component.clone(), index);
495
496 let my_sibling_index = {
497 let depth = state.depth;
498 let mut counters = sibling_counters.borrow_mut();
499 if counters.len() <= depth + 1 {
500 counters.resize(depth + 2, (0, 0));
501 }
502 counters[depth + 1] = (0, 0); let idx = counters[depth].0;
504 counters[depth].0 = idx.saturating_add(1);
505 idx
506 };
507 new_state.depth = state.depth + 1;
508
509 let new_geom = CachedItemBoundingBoxAndTransform::new::<T>(
510 &item_rc,
511 &self.window_adapter,
512 my_sibling_index,
513 );
514
515 let new_screen_rect = clipped_screen_rect(
521 new_geom.bounding_rect(),
522 &state.transform_to_screen,
523 &state.clipped,
524 )
525 .unwrap_or_default();
526
527 let rendering_data = item.cached_rendering_data_offset();
528 let mut cache = self.cache.borrow_mut();
529
530 let tree_key = vtable::VRef::as_ptr(crate::item_tree::ItemTreeRc::borrow(component))
531 .as_ptr() as usize;
532 if index == 0
533 && let Some(acc) = cache.tree_screen_rects.get_mut(&tree_key)
534 {
535 *acc = LogicalRect::default();
537 }
538 if !new_screen_rect.is_empty() {
539 let acc = cache.tree_screen_rects.entry(tree_key).or_default();
540 *acc = acc.union(&new_screen_rect);
541 }
542
543 match rendering_data.get_entry(&mut cache) {
544 Some(PartialRenderingCachedData { data: cached_geom, tracker }) => {
545 let rendering_dirty = tracker.as_ref().is_some_and(|tr| tr.is_dirty());
546
547 let comparison_sibling_index = {
555 let mut counters = sibling_counters.borrow_mut();
556 let idx = counters[state.depth].1;
557 counters[state.depth].1 = idx.saturating_add(1);
558 idx
559 };
560 let old_sibling_index =
561 core::mem::replace(cached_geom.sibling_index(), my_sibling_index);
562 let sibling_index_changed = my_sibling_index == u16::MAX
563 || comparison_sibling_index != old_sibling_index;
564 new_state.must_refresh_children |= sibling_index_changed;
565
566 let geometry_changed = !cached_geom.same_geometry(&new_geom);
567 if ItemRef::downcast_pin::<Clip>(item).is_some()
568 || ItemRef::downcast_pin::<Opacity>(item).is_some()
569 {
570 new_state.must_refresh_children |= rendering_dirty || geometry_changed;
573
574 if rendering_dirty {
575 *tracker = None;
577 }
578 }
579
580 if geometry_changed {
581 let old_transform = cached_geom.transform();
582 self.mark_dirty_rect(
583 cached_geom.bounding_rect(),
584 state.old_transform_to_screen,
585 &state.clipped,
586 );
587 self.dirty_region.add_rect(new_screen_rect);
588
589 new_state
590 .adjust_transforms_for_child(&new_geom.transform(), &old_transform);
591
592 *cached_geom = new_geom;
593
594 return ItemVisitorResult::Continue(new_state);
595 }
596
597 new_state.adjust_transforms_for_child(
598 &cached_geom.transform(),
599 &cached_geom.transform(),
600 );
601
602 let moved = state.must_refresh_children
603 || sibling_index_changed
604 || new_state.transform_to_screen != new_state.old_transform_to_screen;
605
606 if rendering_dirty {
607 self.dirty_region.add_rect(new_screen_rect);
608 if moved {
609 self.mark_dirty_rect(
610 cached_geom.bounding_rect(),
611 state.old_transform_to_screen,
612 &state.clipped,
613 );
614 }
615
616 ItemVisitorResult::Continue(new_state)
617 } else {
618 if moved {
619 self.mark_dirty_rect(
620 cached_geom.bounding_rect(),
621 state.old_transform_to_screen,
622 &state.clipped,
623 );
624 self.dirty_region.add_rect(new_screen_rect);
625 } else if let Some(tr) = &tracker {
626 tr.as_ref().register_as_dependency_to_current_binding();
627 }
628
629 if let CachedItemBoundingBoxAndTransform::ClipItem {
630 geometry, ..
631 } = &cached_geom
632 {
633 new_state.clipped = new_state
634 .clipped
635 .intersection(
636 &state
637 .transform_to_screen
638 .outer_transformed_rect(&geometry.cast())
639 .cast()
640 .union(
641 &state
642 .old_transform_to_screen
643 .outer_transformed_rect(&geometry.cast())
644 .cast(),
645 ),
646 )
647 .unwrap_or_default();
648 if new_state.clipped.is_empty() {
649 return ItemVisitorResult::SkipChildren;
650 }
651 }
652 ItemVisitorResult::Continue(new_state)
653 }
654 }
655 None => {
656 let cache_entry = PartialRenderingCachedData::new(new_geom.clone());
657 rendering_data.cache_index.set(cache.insert(cache_entry));
658 rendering_data.cache_generation.set(cache.generation());
659
660 new_state.adjust_transforms_for_child(
661 &new_geom.transform(),
662 &new_geom.transform(),
663 );
664
665 if let CachedItemBoundingBoxAndTransform::ClipItem { geometry, .. } =
666 new_geom
667 {
668 new_state.clipped = new_state
669 .clipped
670 .intersection(
671 &state
672 .transform_to_screen
673 .outer_transformed_rect(&geometry.cast())
674 .cast(),
675 )
676 .unwrap_or_default();
677 }
678
679 self.dirty_region.add_rect(new_screen_rect);
680 if new_state.clipped.is_empty() {
681 ItemVisitorResult::SkipChildren
682 } else {
683 ItemVisitorResult::Continue(new_state)
684 }
685 }
686 }
687 },
688 {
689 let initial_transform =
690 euclid::Transform2D::translation(origin.x as f32, origin.y as f32);
691 ComputeDirtyRegionState {
692 transform_to_screen: initial_transform,
693 old_transform_to_screen: initial_transform,
694 clipped: LogicalRect::from_size(size),
695 must_refresh_children: false,
696 depth: 0,
697 }
698 },
699 );
700 }
701
702 fn mark_dirty_rect(
703 &mut self,
704 rect: &LogicalRect,
705 transform: ItemTransform,
706 clip_rect: &LogicalRect,
707 ) {
708 if let Some(rect) = clipped_screen_rect(rect, &transform, clip_rect) {
709 self.dirty_region.add_rect(rect);
710 }
711 }
712
713 fn do_rendering(
714 cache: &RefCell<PartialRendererCache>,
715 rendering_data: &CachedRenderingData,
716 item_rc: &ItemRc,
717 render_fn: impl FnOnce(),
718 ) {
719 let mut cache = cache.borrow_mut();
720 if let Some(entry) = rendering_data.get_entry(&mut cache) {
721 entry
722 .tracker
723 .get_or_insert_with(|| Box::pin(PropertyTracker::default()))
724 .as_ref()
725 .evaluate(render_fn);
726 } else {
727 item_rc.geometry();
730 render_fn();
731 }
732 }
733
734 pub fn into_inner(self) -> T {
736 self.actual_renderer
737 }
738
739 fn item_is_drawn(&self, item_bounding_rect: &LogicalRect) -> bool {
741 self.get_current_clip().intersection(item_bounding_rect).is_some_and(|clipped_geom| {
742 let screen_geom =
743 self.current_transform().outer_transformed_rect(&clipped_geom.cast()).cast();
744 self.dirty_region.draw_intersects(screen_geom)
745 })
746 }
747}
748
749macro_rules! forward_rendering_call {
750 (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
751 fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize) $(-> $Ret)? {
752 let mut ret = None;
753 Self::do_rendering(&self.cache, &obj.cached_rendering_data, item_rc, || {
754 ret = Some(self.actual_renderer.$fn(obj, item_rc, size));
755 });
756 ret.unwrap_or_default()
757 }
758 };
759}
760
761macro_rules! forward_rendering_call2 {
762 (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
763 fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize, cache: &CachedRenderingData) $(-> $Ret)? {
764 let mut ret = None;
765 Self::do_rendering(&self.cache, &cache, item_rc, || {
766 ret = Some(self.actual_renderer.$fn(obj, item_rc, size, &cache));
767 });
768 ret.unwrap_or_default()
769 }
770 };
771}
772
773impl<T: ItemRenderer + ItemRendererFeatures> ItemRenderer for PartialRenderer<'_, T> {
774 fn filter_item(
775 &mut self,
776 item_rc: &ItemRc,
777 window_adapter: &Rc<dyn WindowAdapter>,
778 ) -> (bool, LogicalPoint, Option<LogicalSize>) {
779 let item = item_rc.borrow();
780 let rendering_data = item.cached_rendering_data_offset();
781
782 let cached = {
784 let mut cache = self.cache.borrow_mut();
785 rendering_data.get_entry(&mut cache).map(|e| {
786 let draw = self.item_is_drawn(e.data.bounding_rect());
787 let offset = match &e.data {
788 CachedItemBoundingBoxAndTransform::RegularItem { offset, .. } => Some(*offset),
789 _ => None,
790 };
791 (draw, offset)
792 })
793 };
794
795 if let Some((false, Some(offset))) = cached {
797 return (false, offset.to_point(), None);
798 }
799
800 let item_geometry = crate::properties::evaluate_no_tracking(|| item_rc.geometry());
802 let draw = cached.map(|(draw, _)| draw).unwrap_or_else(|| {
803 self.item_is_drawn(&item_rc.bounding_rect(&item_geometry, window_adapter))
806 });
807
808 (draw, item_geometry.origin, Some(item_geometry.size))
809 }
810
811 forward_rendering_call2!(fn draw_rectangle(dyn RenderRectangle));
812 forward_rendering_call2!(fn draw_border_rectangle(dyn RenderBorderRectangle));
813 forward_rendering_call2!(fn draw_window_background(dyn RenderRectangle));
814 forward_rendering_call2!(fn draw_image(dyn RenderImage));
815 forward_rendering_call2!(fn draw_text(dyn RenderText));
816 forward_rendering_call!(fn draw_text_input(TextInput));
817 #[cfg(feature = "path")]
818 forward_rendering_call!(fn draw_path(Path));
819 forward_rendering_call!(fn draw_box_shadow(BoxShadow));
820
821 forward_rendering_call!(fn visit_clip(Clip) -> RenderingResult);
822 forward_rendering_call!(fn visit_opacity(Opacity) -> RenderingResult);
823 forward_rendering_call!(fn visit_layer(Layer) -> RenderingResult);
824
825 fn combine_clip(&mut self, rect: LogicalRect, radius: LogicalBorderRadius) -> bool {
826 self.actual_renderer.combine_clip(rect, radius)
827 }
828
829 fn get_current_clip(&self) -> LogicalRect {
830 self.actual_renderer.get_current_clip()
831 }
832
833 fn translate(&mut self, distance: LogicalVector) {
834 self.actual_renderer.translate(distance)
835 }
836 fn current_transform(&self) -> ItemTransform {
837 self.actual_renderer.current_transform()
838 }
839
840 fn rotate(&mut self, angle_in_degrees: f32) {
841 self.actual_renderer.rotate(angle_in_degrees)
842 }
843
844 fn scale(&mut self, x_factor: f32, y_factor: f32) {
845 self.actual_renderer.scale(x_factor, y_factor)
846 }
847
848 fn apply_opacity(&mut self, opacity: f32) {
849 self.actual_renderer.apply_opacity(opacity)
850 }
851
852 fn global_alpha_transparent(&self) -> bool {
853 self.actual_renderer.global_alpha_transparent()
854 }
855
856 fn save_state(&mut self) {
857 self.actual_renderer.save_state()
858 }
859
860 fn restore_state(&mut self) {
861 self.actual_renderer.restore_state()
862 }
863
864 fn scale_factor(&self) -> ScaleFactor {
865 self.actual_renderer.scale_factor()
866 }
867
868 fn draw_cached_pixmap(
869 &mut self,
870 item_rc: &ItemRc,
871 update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
872 ) {
873 self.actual_renderer.draw_cached_pixmap(item_rc, update_fn)
874 }
875
876 fn draw_string(&mut self, string: &str, color: crate::Color) {
877 self.actual_renderer.draw_string(string, color)
878 }
879
880 fn draw_image_direct(&mut self, image: crate::graphics::image::Image) {
881 self.actual_renderer.draw_image_direct(image)
882 }
883
884 fn window(&self) -> &crate::window::WindowInner {
885 self.actual_renderer.window()
886 }
887
888 fn as_any(&mut self) -> Option<&mut dyn core::any::Any> {
889 self.actual_renderer.as_any()
890 }
891}
892
893#[derive(Default)]
896pub struct PartialRenderingState {
897 partial_cache: RefCell<PartialRendererCache>,
898 force_dirty: RefCell<DirtyRegion>,
900 force_screen_refresh: Cell<bool>,
902}
903
904impl PartialRenderingState {
905 pub fn create_partial_renderer<T: ItemRenderer + ItemRendererFeatures>(
908 &self,
909 renderer: T,
910 ) -> PartialRenderer<'_, T> {
911 PartialRenderer::new(&self.partial_cache, DirtyRegion::default(), renderer)
912 }
913
914 pub fn apply_dirty_region<T: ItemRenderer + ItemRendererFeatures>(
919 &self,
920 partial_renderer: &mut PartialRenderer<'_, T>,
921 components: &[(ItemTreeWeak, LogicalPoint)],
922 logical_window_size: LogicalSize,
923 dirty_region_of_existing_buffer: Option<DirtyRegion>,
924 ) -> DirtyRegion {
925 for (component, origin) in components {
926 if let Some(component) = crate::item_tree::ItemTreeWeak::upgrade(component) {
927 partial_renderer.compute_dirty_regions(&component, *origin, logical_window_size);
928 }
929 }
930
931 let screen_region = LogicalRect::from_size(logical_window_size);
932
933 partial_renderer.dirty_region =
938 partial_renderer.dirty_region.union(&self.force_dirty.take());
939
940 if self.force_screen_refresh.take() {
941 partial_renderer.dirty_region = screen_region.into();
942 }
943
944 let region_to_repaint = partial_renderer.dirty_region.clone();
945
946 partial_renderer.dirty_region = match dirty_region_of_existing_buffer {
947 Some(dirty_region) => partial_renderer.dirty_region.union(&dirty_region),
948 None => partial_renderer.dirty_region.clone(),
949 }
950 .intersection(screen_region);
951
952 region_to_repaint
953 }
954
955 pub fn mark_dirty_region(&self, region: DirtyRegion) {
957 self.force_dirty.replace_with(|r| r.union(®ion));
958 }
959
960 pub fn free_graphics_resources(
964 &self,
965 component: crate::item_tree::ItemTreeRef,
966 items: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>,
967 ) {
968 let mut cache = self.partial_cache.borrow_mut();
969
970 let tree_key = vtable::VRef::as_ptr(component).as_ptr() as usize;
971 if let Some(rect) = cache.tree_screen_rects.remove(&tree_key) {
972 self.force_dirty.borrow_mut().add_rect(rect);
973 }
974
975 for item in items {
976 item.cached_rendering_data_offset().release(&mut cache);
977 }
978 }
979
980 pub fn clear_cache(&self) {
982 self.partial_cache.borrow_mut().clear();
983 }
984
985 pub fn force_screen_refresh(&self) {
987 self.force_screen_refresh.set(true);
988 }
989}
990
991#[test]
992fn dirty_region_ignores_empty_rects() {
993 let mut region = DirtyRegion::default();
996 region.add_rect(LogicalRect::default());
997 assert_eq!(region.iter().count(), 0);
998 let real = LogicalRect::new(LogicalPoint::new(10., 10.), LogicalSize::new(16., 16.));
999 region.add_rect(real);
1000 region.add_rect(LogicalRect::default());
1001 assert_eq!(region.bounding_rect(), real);
1002}
1003
1004#[test]
1005fn dirty_region_no_intersection() {
1006 let mut region = DirtyRegion::default();
1007 region.add_rect(LogicalRect::new(LogicalPoint::new(10., 10.), LogicalSize::new(16., 16.)));
1008 region.add_rect(LogicalRect::new(LogicalPoint::new(100., 100.), LogicalSize::new(16., 16.)));
1009 region.add_rect(LogicalRect::new(LogicalPoint::new(200., 100.), LogicalSize::new(16., 16.)));
1010 let i = region
1011 .intersection(LogicalRect::new(LogicalPoint::new(50., 50.), LogicalSize::new(10., 10.)));
1012 assert_eq!(i.iter().count(), 0);
1013}