ferroui_base/rendering/composition/server/dirty_rects/
region_dirty_rect_tracker.rs1use super::i_dirty_rect_tracker::{
2 pixel_rect_from_rect_unscaled, pixel_rect_to_ltrb_rect_unscaled, VisualizeRandom,
3};
4use super::{IDirtyRectCollector, IDirtyRectTracker};
5use crate::media::immutable::ImmutableSolidColorBrush;
6use crate::media::Color;
7use crate::platform::{
8 IDrawingContextImpl, IPlatformRenderInterface, IPlatformRenderInterfaceRegion, LtrbPixelRect, LtrbRect,
9};
10use crate::Thickness;
11use std::cell::{Cell, RefCell};
12use std::rc::Rc;
13
14pub struct RegionDirtyRectTracker {
17 region: Rc<dyn IPlatformRenderInterfaceRegion>,
18 rects: RefCell<Vec<LtrbRect>>,
19 random: VisualizeRandom,
20 combined_rect: Cell<LtrbRect>,
21}
22
23impl RegionDirtyRectTracker {
24 pub fn new(platform_render: &dyn IPlatformRenderInterface) -> Self {
25 Self {
26 region: platform_render.create_region(),
27 rects: RefCell::new(Vec::new()),
28 random: VisualizeRandom::new(),
29 combined_rect: Cell::new(LtrbRect::default()),
30 }
31 }
32
33 fn get_inflated_pixel_rect(rc: LtrbRect) -> LtrbPixelRect {
34 let inflated = rc.inflate(Thickness::uniform(1.0)).intersect_or_empty(rc);
37 pixel_rect_from_rect_unscaled(inflated)
38 }
39}
40
41impl IDirtyRectCollector for RegionDirtyRectTracker {
42 fn add_rect(&self, rect: LtrbRect) {
43 self.rects.borrow_mut().push(rect);
44 }
45}
46
47impl IDirtyRectTracker for RegionDirtyRectTracker {
48 fn finalize_frame(&self, _bounds: LtrbRect) {
49 self.region.reset();
50 for rc in self.rects.borrow().iter() {
51 self.region.add_rect(Self::get_inflated_pixel_rect(*rc));
52 }
53 self.combined_rect.set(pixel_rect_to_ltrb_rect_unscaled(self.region.bounds()));
54 }
55
56 fn begin_draw(&self, ctx: &mut dyn IDrawingContextImpl) {
57 ctx.push_clip_region(&*self.region);
58 }
59
60 fn end_draw(&self, ctx: &mut dyn IDrawingContextImpl) {
61 ctx.pop_clip();
62 }
63
64 fn is_empty(&self) -> bool {
65 self.rects.borrow().is_empty()
66 }
67
68 fn intersects(&self, rect: LtrbRect) -> bool {
69 self.region.intersects(rect)
70 }
71
72 fn initialize(&self, _bounds: LtrbRect) {
73 self.rects.borrow_mut().clear();
74 }
75
76 fn visualize(&self, context: &mut dyn IDrawingContextImpl) {
77 let brush = ImmutableSolidColorBrush::new(Color::new(
78 150,
79 self.random.next(255) as u8,
80 self.random.next(255) as u8,
81 self.random.next(255) as u8,
82 ));
83 context.draw_region(Some(&brush), None, &*self.region);
84 }
85
86 fn combined_rect(&self) -> LtrbRect {
87 self.combined_rect.get()
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::super::i_dirty_rect_tracker::tests::{MockDrawingContext, MockRenderInterface};
94 use super::*;
95
96 const BOUNDS: LtrbRect = LtrbRect::new(0.0, 0.0, 100.0, 100.0);
97
98 #[test]
99 fn collects_rects_into_the_platform_region_on_finalize() {
100 let platform = MockRenderInterface::default();
101 let tracker = RegionDirtyRectTracker::new(&platform);
102 let region = platform.regions.borrow()[0].clone();
103 assert!(tracker.is_empty());
104
105 tracker.add_rect(LtrbRect::new(10.2, 10.7, 20.1, 20.0));
106 tracker.add_rect(LtrbRect::new(50.0, 60.0, 70.0, 80.0));
107 assert!(!tracker.is_empty());
108 assert!(region.rects.borrow().is_empty());
109
110 tracker.finalize_frame(BOUNDS);
111 assert_eq!(
114 vec![LtrbPixelRect::new(10, 10, 21, 20), LtrbPixelRect::new(50, 60, 70, 80)],
115 *region.rects.borrow()
116 );
117 assert_eq!(LtrbRect::new(10.0, 10.0, 70.0, 80.0), tracker.combined_rect());
118 assert!(tracker.intersects(LtrbRect::new(15.0, 15.0, 16.0, 16.0)));
119 assert!(!tracker.intersects(LtrbRect::new(30.0, 30.0, 40.0, 40.0)));
120 }
121
122 #[test]
123 fn finalize_rebuilds_the_region_and_initialize_clears_the_rects() {
124 let platform = MockRenderInterface::default();
125 let tracker = RegionDirtyRectTracker::new(&platform);
126 let region = platform.regions.borrow()[0].clone();
127 tracker.add_rect(LtrbRect::new(1.0, 1.0, 2.0, 2.0));
128 tracker.finalize_frame(BOUNDS);
129 tracker.finalize_frame(BOUNDS);
130 assert_eq!(1, region.rects.borrow().len());
131
132 tracker.initialize(BOUNDS);
133 assert!(tracker.is_empty());
134 assert_eq!(1, region.rects.borrow().len());
136 tracker.finalize_frame(BOUNDS);
137 assert!(region.rects.borrow().is_empty());
138 assert_eq!(LtrbRect::default(), tracker.combined_rect());
139 }
140
141 #[test]
142 fn draw_uses_the_region_clip() {
143 let platform = MockRenderInterface::default();
144 let tracker = RegionDirtyRectTracker::new(&platform);
145 tracker.add_rect(LtrbRect::new(1.0, 1.0, 2.0, 2.0));
146 tracker.finalize_frame(BOUNDS);
147 let mut ctx = MockDrawingContext::default();
148 tracker.begin_draw(&mut ctx);
149 tracker.end_draw(&mut ctx);
150 tracker.visualize(&mut ctx);
151 assert_eq!(
152 vec![
153 "push_clip_region [LtrbPixelRect { left: 1, top: 1, right: 2, bottom: 2 }]".to_string(),
154 "pop_clip".to_string(),
155 "region alpha 150 rects 1".to_string()
156 ],
157 ctx.log
158 );
159 }
160}