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, LogicalLength, LogicalPoint, LogicalPx, LogicalRect,
29 LogicalSize, LogicalVector,
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, PartialEq)]
81pub enum CachedItemBoundingBoxAndTransform {
82 RegularItem {
84 bounding_rect: LogicalRect,
86 offset: LogicalVector,
88 },
89 ItemWithTransform {
91 bounding_rect: LogicalRect,
93 transform: Box<ItemTransform>,
95 },
96 ClipItem {
98 geometry: LogicalRect,
100 },
101}
102
103impl CachedItemBoundingBoxAndTransform {
104 fn bounding_rect(&self) -> &LogicalRect {
105 match self {
106 CachedItemBoundingBoxAndTransform::RegularItem { bounding_rect, .. } => bounding_rect,
107 CachedItemBoundingBoxAndTransform::ItemWithTransform { bounding_rect, .. } => {
108 bounding_rect
109 }
110 CachedItemBoundingBoxAndTransform::ClipItem { geometry } => geometry,
111 }
112 }
113
114 fn transform(&self) -> ItemTransform {
115 match self {
116 CachedItemBoundingBoxAndTransform::RegularItem { offset, .. } => {
117 ItemTransform::translation(offset.x as f32, offset.y as f32)
118 }
119 CachedItemBoundingBoxAndTransform::ItemWithTransform { transform, .. } => **transform,
120 CachedItemBoundingBoxAndTransform::ClipItem { geometry } => {
121 ItemTransform::translation(geometry.origin.x as f32, geometry.origin.y as f32)
122 }
123 }
124 }
125
126 fn new<T: ItemRendererFeatures>(
127 item_rc: &ItemRc,
128 window_adapter: &Rc<dyn WindowAdapter>,
129 ) -> Self {
130 let geometry = item_rc.geometry();
131
132 if item_rc.borrow().as_ref().clips_children() {
133 return Self::ClipItem { geometry };
134 }
135
136 let bounding_rect = crate::properties::evaluate_no_tracking(|| {
139 item_rc.bounding_rect(&geometry, window_adapter)
140 });
141
142 if let Some(complex_child_transform) = (T::SUPPORTS_TRANSFORMATIONS
143 && window_adapter.renderer().supports_transformations())
144 .then(|| item_rc.children_transform())
145 .flatten()
146 {
147 Self::ItemWithTransform {
148 bounding_rect,
149 transform: complex_child_transform
150 .then_translate(geometry.origin.to_vector().cast())
151 .into(),
152 }
153 } else {
154 Self::RegularItem { bounding_rect, offset: geometry.origin.to_vector() }
155 }
156 }
157}
158
159struct PartialRenderingCachedData {
160 pub data: CachedItemBoundingBoxAndTransform,
162 pub tracker: Option<core::pin::Pin<Box<PropertyTracker>>>,
164}
165impl PartialRenderingCachedData {
166 fn new(data: CachedItemBoundingBoxAndTransform) -> Self {
167 Self { data, tracker: None }
168 }
169}
170
171struct PartialRendererCache {
173 slab: slab::Slab<PartialRenderingCachedData>,
174 generation: usize,
175}
176
177impl Default for PartialRendererCache {
178 fn default() -> Self {
179 Self { slab: Default::default(), generation: 1 }
180 }
181}
182
183impl PartialRendererCache {
184 pub fn generation(&self) -> usize {
187 self.generation
188 }
189
190 pub fn get_mut(&mut self, index: usize) -> Option<&mut PartialRenderingCachedData> {
192 self.slab.get_mut(index)
193 }
194
195 pub fn insert(&mut self, data: PartialRenderingCachedData) -> usize {
197 self.slab.insert(data)
198 }
199
200 pub fn remove(&mut self, index: usize) -> PartialRenderingCachedData {
202 self.slab.remove(index)
203 }
204
205 pub fn clear(&mut self) {
208 self.slab.clear();
209 self.generation += 1;
210 }
211}
212
213#[derive(Default, Clone)]
215pub struct DirtyRegion {
216 rectangles: [euclid::Box2D<Coord, LogicalPx>; Self::MAX_COUNT],
217 count: usize,
218}
219
220impl core::fmt::Debug for DirtyRegion {
221 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
222 write!(f, "{:?}", &self.rectangles[..self.count])
223 }
224}
225
226impl DirtyRegion {
227 pub const MAX_COUNT: usize = 3;
229
230 pub fn iter(&self) -> impl Iterator<Item = euclid::Box2D<Coord, LogicalPx>> + '_ {
232 (0..self.count).map(|x| self.rectangles[x])
233 }
234
235 pub fn add_rect(&mut self, rect: LogicalRect) {
239 self.add_box(rect.to_box2d());
240 }
241
242 pub fn add_box(&mut self, b: euclid::Box2D<Coord, LogicalPx>) {
246 if b.is_empty() {
247 return;
248 }
249 let mut i = 0;
250 while i < self.count {
251 let r = &self.rectangles[i];
252 if r.contains_box(&b) {
253 return;
255 } else if b.contains_box(r) {
256 self.rectangles.swap(i, self.count - 1);
257 self.count -= 1;
258 continue;
259 }
260 i += 1;
261 }
262
263 if self.count < Self::MAX_COUNT {
264 self.rectangles[self.count] = b;
265 self.count += 1;
266 } else {
267 let best_merge = (0..self.count)
268 .map(|i| (i, self.rectangles[i].union(&b).area() - self.rectangles[i].area()))
269 .min_by(|a, b| PartialOrd::partial_cmp(&a.1, &b.1).unwrap())
270 .expect("There should always be rectangles")
271 .0;
272 self.rectangles[best_merge] = self.rectangles[best_merge].union(&b);
273 }
274 }
275
276 #[must_use]
280 pub fn union(&self, other: &Self) -> Self {
281 let mut s = self.clone();
282 for o in other.iter() {
283 s.add_box(o)
284 }
285 s
286 }
287
288 #[must_use]
290 pub fn bounding_rect(&self) -> LogicalRect {
291 if self.count == 0 {
292 return Default::default();
293 }
294 let mut r = self.rectangles[0];
295 for i in 1..self.count {
296 r = r.union(&self.rectangles[i]);
297 }
298 r.to_rect()
299 }
300
301 #[must_use]
303 pub fn intersection(&self, other: LogicalRect) -> DirtyRegion {
304 let mut ret = self.clone();
305 let other = other.to_box2d();
306 let mut i = 0;
307 while i < ret.count {
308 if let Some(x) = ret.rectangles[i].intersection(&other) {
309 ret.rectangles[i] = x;
310 } else {
311 ret.count -= 1;
312 ret.rectangles.swap(i, ret.count);
313 continue;
314 }
315 i += 1;
316 }
317 ret
318 }
319
320 fn draw_intersects(&self, clipped_geom: LogicalRect) -> bool {
321 let b = clipped_geom.to_box2d();
322 self.iter().any(|r| r.intersects(&b))
323 }
324}
325
326impl From<LogicalRect> for DirtyRegion {
327 fn from(value: LogicalRect) -> Self {
328 let mut s = Self::default();
329 s.add_rect(value);
330 s
331 }
332}
333
334#[derive(PartialEq, Eq, Debug, Clone, Default, Copy)]
337pub enum RepaintBufferType {
338 #[default]
339 NewBuffer,
341 ReusedBuffer,
346
347 SwappedBuffers,
351}
352
353pub struct PartialRenderer<'a, T> {
357 cache: &'a RefCell<PartialRendererCache>,
358 pub dirty_region: DirtyRegion,
360 pub actual_renderer: T,
362 pub window_adapter: Rc<dyn WindowAdapter>,
364}
365
366impl<'a, T: ItemRenderer + ItemRendererFeatures> PartialRenderer<'a, T> {
367 fn new(
369 cache: &'a RefCell<PartialRendererCache>,
370 initial_dirty_region: DirtyRegion,
371 actual_renderer: T,
372 ) -> Self {
373 let window_adapter = actual_renderer.window().window_adapter();
374 Self { cache, dirty_region: initial_dirty_region, actual_renderer, window_adapter }
375 }
376
377 pub fn compute_dirty_regions(
379 &mut self,
380 component: &ItemTreeRc,
381 origin: LogicalPoint,
382 size: LogicalSize,
383 ) {
384 #[derive(Clone, Copy)]
385 struct ComputeDirtyRegionState {
386 transform_to_screen: ItemTransform,
387 old_transform_to_screen: ItemTransform,
388 clipped: LogicalRect,
389 must_refresh_children: bool,
390 }
391
392 impl ComputeDirtyRegionState {
393 fn adjust_transforms_for_child(
396 &mut self,
397 children_transform: &ItemTransform,
398 old_children_transform: &ItemTransform,
399 ) {
400 self.transform_to_screen = children_transform.then(&self.transform_to_screen);
401 self.old_transform_to_screen =
402 old_children_transform.then(&self.old_transform_to_screen);
403 }
404 }
405
406 crate::item_tree::visit_items(
407 component,
408 crate::item_tree::TraversalOrder::BackToFront,
409 |component, item, index, state| {
410 let mut new_state = *state;
411 let item_rc = ItemRc::new(component.clone(), index);
412 let new_geom =
413 CachedItemBoundingBoxAndTransform::new::<T>(&item_rc, &self.window_adapter);
414
415 let rendering_data = item.cached_rendering_data_offset();
416 let mut cache = self.cache.borrow_mut();
417 match rendering_data.get_entry(&mut cache) {
418 Some(PartialRenderingCachedData { data: cached_geom, tracker }) => {
419 let rendering_dirty = tracker.as_ref().is_some_and(|tr| tr.is_dirty());
420 let old_geom = cached_geom.clone();
421
422 let geometry_changed = old_geom != new_geom;
423 if ItemRef::downcast_pin::<Clip>(item).is_some()
424 || ItemRef::downcast_pin::<Opacity>(item).is_some()
425 {
426 new_state.must_refresh_children |= rendering_dirty || geometry_changed;
429
430 if rendering_dirty {
431 *tracker = None;
433 }
434 }
435
436 if geometry_changed {
437 self.mark_dirty_rect(
438 old_geom.bounding_rect(),
439 state.old_transform_to_screen,
440 &state.clipped,
441 );
442 self.mark_dirty_rect(
443 new_geom.bounding_rect(),
444 state.transform_to_screen,
445 &state.clipped,
446 );
447
448 new_state.adjust_transforms_for_child(
449 &new_geom.transform(),
450 &old_geom.transform(),
451 );
452
453 *cached_geom = new_geom;
454
455 return ItemVisitorResult::Continue(new_state);
456 }
457
458 new_state.adjust_transforms_for_child(
459 &cached_geom.transform(),
460 &cached_geom.transform(),
461 );
462
463 let moved = state.must_refresh_children
464 || new_state.transform_to_screen != new_state.old_transform_to_screen;
465
466 if rendering_dirty {
467 self.mark_dirty_rect(
468 cached_geom.bounding_rect(),
469 state.transform_to_screen,
470 &state.clipped,
471 );
472 if moved {
473 self.mark_dirty_rect(
474 cached_geom.bounding_rect(),
475 state.old_transform_to_screen,
476 &state.clipped,
477 );
478 }
479
480 ItemVisitorResult::Continue(new_state)
481 } else {
482 if moved {
483 self.mark_dirty_rect(
484 cached_geom.bounding_rect(),
485 state.old_transform_to_screen,
486 &state.clipped,
487 );
488 self.mark_dirty_rect(
489 cached_geom.bounding_rect(),
490 state.transform_to_screen,
491 &state.clipped,
492 );
493 } else if let Some(tr) = &tracker {
494 tr.as_ref().register_as_dependency_to_current_binding();
495 }
496
497 if let CachedItemBoundingBoxAndTransform::ClipItem { geometry } =
498 &cached_geom
499 {
500 new_state.clipped = new_state
501 .clipped
502 .intersection(
503 &state
504 .transform_to_screen
505 .outer_transformed_rect(&geometry.cast())
506 .cast()
507 .union(
508 &state
509 .old_transform_to_screen
510 .outer_transformed_rect(&geometry.cast())
511 .cast(),
512 ),
513 )
514 .unwrap_or_default();
515 if new_state.clipped.is_empty() {
516 return ItemVisitorResult::SkipChildren;
517 }
518 }
519 ItemVisitorResult::Continue(new_state)
520 }
521 }
522 None => {
523 let cache_entry = PartialRenderingCachedData::new(new_geom.clone());
524 rendering_data.cache_index.set(cache.insert(cache_entry));
525 rendering_data.cache_generation.set(cache.generation());
526
527 new_state.adjust_transforms_for_child(
528 &new_geom.transform(),
529 &new_geom.transform(),
530 );
531
532 if let CachedItemBoundingBoxAndTransform::ClipItem { geometry } = new_geom {
533 new_state.clipped = new_state
534 .clipped
535 .intersection(
536 &state
537 .transform_to_screen
538 .outer_transformed_rect(&geometry.cast())
539 .cast(),
540 )
541 .unwrap_or_default();
542 }
543
544 self.mark_dirty_rect(
545 new_geom.bounding_rect(),
546 state.transform_to_screen,
547 &state.clipped,
548 );
549 if new_state.clipped.is_empty() {
550 ItemVisitorResult::SkipChildren
551 } else {
552 ItemVisitorResult::Continue(new_state)
553 }
554 }
555 }
556 },
557 {
558 let initial_transform =
559 euclid::Transform2D::translation(origin.x as f32, origin.y as f32);
560 ComputeDirtyRegionState {
561 transform_to_screen: initial_transform,
562 old_transform_to_screen: initial_transform,
563 clipped: LogicalRect::from_size(size),
564 must_refresh_children: false,
565 }
566 },
567 );
568 }
569
570 fn mark_dirty_rect(
571 &mut self,
572 rect: &LogicalRect,
573 transform: ItemTransform,
574 clip_rect: &LogicalRect,
575 ) {
576 #[cfg(not(slint_int_coord))]
577 if !rect.origin.is_finite() {
578 return;
580 }
581
582 if !rect.is_empty()
583 && let Some(rect) =
584 transform.outer_transformed_rect(&rect.cast()).cast().intersection(clip_rect)
585 {
586 self.dirty_region.add_rect(rect);
587 }
588 }
589
590 fn do_rendering(
591 cache: &RefCell<PartialRendererCache>,
592 rendering_data: &CachedRenderingData,
593 item_rc: &ItemRc,
594 render_fn: impl FnOnce(),
595 ) {
596 let mut cache = cache.borrow_mut();
597 if let Some(entry) = rendering_data.get_entry(&mut cache) {
598 entry
599 .tracker
600 .get_or_insert_with(|| Box::pin(PropertyTracker::default()))
601 .as_ref()
602 .evaluate(render_fn);
603 } else {
604 item_rc.geometry();
607 render_fn();
608 }
609 }
610
611 pub fn into_inner(self) -> T {
613 self.actual_renderer
614 }
615}
616
617macro_rules! forward_rendering_call {
618 (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
619 fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize) $(-> $Ret)? {
620 let mut ret = None;
621 Self::do_rendering(&self.cache, &obj.cached_rendering_data, item_rc, || {
622 ret = Some(self.actual_renderer.$fn(obj, item_rc, size));
623 });
624 ret.unwrap_or_default()
625 }
626 };
627}
628
629macro_rules! forward_rendering_call2 {
630 (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
631 fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize, cache: &CachedRenderingData) $(-> $Ret)? {
632 let mut ret = None;
633 Self::do_rendering(&self.cache, &cache, item_rc, || {
634 ret = Some(self.actual_renderer.$fn(obj, item_rc, size, &cache));
635 });
636 ret.unwrap_or_default()
637 }
638 };
639}
640
641impl<T: ItemRenderer + ItemRendererFeatures> ItemRenderer for PartialRenderer<'_, T> {
642 fn filter_item(
643 &mut self,
644 item_rc: &ItemRc,
645 window_adapter: &Rc<dyn WindowAdapter>,
646 ) -> (bool, LogicalRect) {
647 let item = item_rc.borrow();
648
649 let item_geometry = crate::properties::evaluate_no_tracking(|| item_rc.geometry());
651
652 let rendering_data = item.cached_rendering_data_offset();
653 let mut cache = self.cache.borrow_mut();
654 let item_bounding_rect = match rendering_data.get_entry(&mut cache) {
655 Some(PartialRenderingCachedData { data, tracker: _ }) => *data.bounding_rect(),
656 None => {
657 item_rc.bounding_rect(&item_geometry, window_adapter)
659 }
660 };
661
662 let clipped_geom = self.get_current_clip().intersection(&item_bounding_rect);
663 let draw = clipped_geom.is_some_and(|clipped_geom| {
664 let screen_geom =
665 self.current_transform().outer_transformed_rect(&clipped_geom.cast()).cast();
666 self.dirty_region.draw_intersects(screen_geom)
667 });
668
669 (draw, item_geometry)
670 }
671
672 forward_rendering_call2!(fn draw_rectangle(dyn RenderRectangle));
673 forward_rendering_call2!(fn draw_border_rectangle(dyn RenderBorderRectangle));
674 forward_rendering_call2!(fn draw_window_background(dyn RenderRectangle));
675 forward_rendering_call2!(fn draw_image(dyn RenderImage));
676 forward_rendering_call2!(fn draw_text(dyn RenderText));
677 forward_rendering_call!(fn draw_text_input(TextInput));
678 #[cfg(feature = "path")]
679 forward_rendering_call!(fn draw_path(Path));
680 forward_rendering_call!(fn draw_box_shadow(BoxShadow));
681
682 forward_rendering_call!(fn visit_clip(Clip) -> RenderingResult);
683 forward_rendering_call!(fn visit_opacity(Opacity) -> RenderingResult);
684 forward_rendering_call!(fn visit_layer(Layer) -> RenderingResult);
685
686 fn combine_clip(
687 &mut self,
688 rect: LogicalRect,
689 radius: LogicalBorderRadius,
690 border_width: LogicalLength,
691 ) -> bool {
692 self.actual_renderer.combine_clip(rect, radius, border_width)
693 }
694
695 fn get_current_clip(&self) -> LogicalRect {
696 self.actual_renderer.get_current_clip()
697 }
698
699 fn translate(&mut self, distance: LogicalVector) {
700 self.actual_renderer.translate(distance)
701 }
702 fn current_transform(&self) -> ItemTransform {
703 self.actual_renderer.current_transform()
704 }
705
706 fn rotate(&mut self, angle_in_degrees: f32) {
707 self.actual_renderer.rotate(angle_in_degrees)
708 }
709
710 fn scale(&mut self, x_factor: f32, y_factor: f32) {
711 self.actual_renderer.scale(x_factor, y_factor)
712 }
713
714 fn apply_opacity(&mut self, opacity: f32) {
715 self.actual_renderer.apply_opacity(opacity)
716 }
717
718 fn save_state(&mut self) {
719 self.actual_renderer.save_state()
720 }
721
722 fn restore_state(&mut self) {
723 self.actual_renderer.restore_state()
724 }
725
726 fn scale_factor(&self) -> f32 {
727 self.actual_renderer.scale_factor()
728 }
729
730 fn draw_cached_pixmap(
731 &mut self,
732 item_rc: &ItemRc,
733 update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
734 ) {
735 self.actual_renderer.draw_cached_pixmap(item_rc, update_fn)
736 }
737
738 fn draw_string(&mut self, string: &str, color: crate::Color) {
739 self.actual_renderer.draw_string(string, color)
740 }
741
742 fn draw_image_direct(&mut self, image: crate::graphics::image::Image) {
743 self.actual_renderer.draw_image_direct(image)
744 }
745
746 fn window(&self) -> &crate::window::WindowInner {
747 self.actual_renderer.window()
748 }
749
750 fn as_any(&mut self) -> Option<&mut dyn core::any::Any> {
751 self.actual_renderer.as_any()
752 }
753}
754
755#[derive(Default)]
758pub struct PartialRenderingState {
759 partial_cache: RefCell<PartialRendererCache>,
760 force_dirty: RefCell<DirtyRegion>,
762 force_screen_refresh: Cell<bool>,
764}
765
766impl PartialRenderingState {
767 pub fn create_partial_renderer<T: ItemRenderer + ItemRendererFeatures>(
770 &self,
771 renderer: T,
772 ) -> PartialRenderer<'_, T> {
773 PartialRenderer::new(&self.partial_cache, self.force_dirty.take(), renderer)
774 }
775
776 pub fn apply_dirty_region<T: ItemRenderer + ItemRendererFeatures>(
781 &self,
782 partial_renderer: &mut PartialRenderer<'_, T>,
783 components: &[(ItemTreeWeak, LogicalPoint)],
784 logical_window_size: LogicalSize,
785 dirty_region_of_existing_buffer: Option<DirtyRegion>,
786 ) -> DirtyRegion {
787 for (component, origin) in components {
788 if let Some(component) = crate::item_tree::ItemTreeWeak::upgrade(component) {
789 partial_renderer.compute_dirty_regions(&component, *origin, logical_window_size);
790 }
791 }
792
793 let screen_region = LogicalRect::from_size(logical_window_size);
794
795 if self.force_screen_refresh.take() {
796 partial_renderer.dirty_region = screen_region.into();
797 }
798
799 let region_to_repaint = partial_renderer.dirty_region.clone();
800
801 partial_renderer.dirty_region = match dirty_region_of_existing_buffer {
802 Some(dirty_region) => partial_renderer.dirty_region.union(&dirty_region),
803 None => partial_renderer.dirty_region.clone(),
804 }
805 .intersection(screen_region);
806
807 region_to_repaint
808 }
809
810 pub fn mark_dirty_region(&self, region: DirtyRegion) {
812 self.force_dirty.replace_with(|r| r.union(®ion));
813 }
814
815 pub fn free_graphics_resources(&self, items: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>) {
818 for item in items {
819 item.cached_rendering_data_offset().release(&mut self.partial_cache.borrow_mut());
820 }
821
822 self.force_screen_refresh.set(true)
825 }
826
827 pub fn clear_cache(&self) {
829 self.partial_cache.borrow_mut().clear();
830 }
831
832 pub fn force_screen_refresh(&self) {
834 self.force_screen_refresh.set(true);
835 }
836}
837
838#[test]
839fn dirty_region_no_intersection() {
840 let mut region = DirtyRegion::default();
841 region.add_rect(LogicalRect::new(LogicalPoint::new(10., 10.), LogicalSize::new(16., 16.)));
842 region.add_rect(LogicalRect::new(LogicalPoint::new(100., 100.), LogicalSize::new(16., 16.)));
843 region.add_rect(LogicalRect::new(LogicalPoint::new(200., 100.), LogicalSize::new(16., 16.)));
844 let i = region
845 .intersection(LogicalRect::new(LogicalPoint::new(50., 50.), LogicalSize::new(10., 10.)));
846 assert_eq!(i.iter().count(), 0);
847}