1use std::rc::Rc;
6
7use inset_embedder::{
8 Matrix4, Offset, Rect, SceneBuilder, Size, SystemUiOverlayStyle, TargetPlatform, View, ViewRef,
9};
10use inset_foundation::{App, Handle};
11use inset_gestures::{HitTestEntry, HitTestResult, HitTestTarget, PointerEvent};
12use inset_services::SystemChrome;
13
14use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult};
15use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayerMixin, TransformLayer};
16use crate::object::{
17 AnyRenderObject, Constraints, RenderHandle, RenderObject, RenderObjectData, RenderObjectVTable,
18 resolve,
19};
20use crate::painting_context::PaintingContext;
21
22#[derive(Clone, Copy, Debug, PartialEq)]
24pub struct ViewConfiguration {
25 pub logical_constraints: BoxConstraints,
27 pub physical_constraints: BoxConstraints,
29 pub device_pixel_ratio: f64,
31}
32
33impl ViewConfiguration {
34 pub const fn new(
38 physical_constraints: BoxConstraints,
39 logical_constraints: BoxConstraints,
40 device_pixel_ratio: f64,
41 ) -> ViewConfiguration {
42 ViewConfiguration {
43 logical_constraints,
44 physical_constraints,
45 device_pixel_ratio,
46 }
47 }
48
49 pub fn from_view(view: &dyn View) -> ViewConfiguration {
51 let metrics = view.metrics();
52 let physical_constraints =
53 BoxConstraints::from_view_constraints(metrics.physical_constraints);
54 let device_pixel_ratio = metrics.device_pixel_ratio;
55 ViewConfiguration {
56 logical_constraints: physical_constraints / device_pixel_ratio,
57 physical_constraints,
58 device_pixel_ratio,
59 }
60 }
61
62 pub fn to_matrix(&self) -> Matrix4 {
65 Matrix4::scale(
66 self.device_pixel_ratio as f32,
67 self.device_pixel_ratio as f32,
68 )
69 }
70
71 pub fn should_update_matrix(&self, old_configuration: &ViewConfiguration) -> bool {
74 old_configuration.device_pixel_ratio != self.device_pixel_ratio
75 }
76
77 pub fn to_physical_size(&self, logical_size: Size) -> Size {
79 self.physical_constraints
80 .constrain(logical_size * self.device_pixel_ratio)
81 }
82}
83
84impl Default for ViewConfiguration {
85 fn default() -> ViewConfiguration {
86 ViewConfiguration::new(
87 BoxConstraints::new().max_width(0.0).max_height(0.0),
88 BoxConstraints::new().max_width(0.0).max_height(0.0),
89 1.0,
90 )
91 }
92}
93
94pub struct RenderView {
103 render_object: RenderObjectData,
104 child: Option<AnyRenderBox>,
105 size: Size,
106 configuration: Option<ViewConfiguration>,
107 view: ViewRef,
108 root_transform: Option<Matrix4>,
109 pub automatic_system_ui_adjustment: bool,
115}
116
117impl RenderView {
118 pub fn new(
125 app: &mut App,
126 child: Option<AnyRenderBox>,
127 configuration: Option<ViewConfiguration>,
128 view: ViewRef,
129 ) -> RenderHandle<RenderView> {
130 let this = RenderHandle::from_handle(app.create(RenderView {
131 render_object: RenderObjectData::new(),
132 child: None,
133 size: Size::ZERO,
134 configuration: None,
135 view,
136 root_transform: None,
137 automatic_system_ui_adjustment: true,
138 }));
139 let data = this.render_object_data_mut(app);
140 data.object_vtable = Some(&VTABLE);
141 data.was_repaint_boundary = true;
142 data.needs_compositing = true;
143 if let Some(configuration) = configuration {
144 this.set_configuration(app, configuration);
145 }
146 this.set_child(app, child);
147 this
148 }
149
150 pub fn as_object(self: RenderHandle<Self>) -> AnyRenderObject {
152 AnyRenderObject::from_vtable(self.id(), const { &VTABLE })
153 }
154
155 pub fn child(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderBox> {
157 self.get(app).child
158 }
159
160 pub fn set_child(self: RenderHandle<Self>, app: &mut App, value: Option<AnyRenderBox>) {
162 if let Some(old) = self.get(app).child {
163 self.as_object().drop_child(app, old.as_object());
164 }
165 self.get_mut(app).child = value;
166 if let Some(new) = value {
167 self.as_object().adopt_child(app, new.as_object());
168 }
169 }
170
171 pub fn size(self: RenderHandle<Self>, app: &App) -> Size {
173 self.get(app).size
174 }
175
176 pub fn configuration(self: RenderHandle<Self>, app: &App) -> ViewConfiguration {
188 self.get(app)
189 .configuration
190 .expect("RenderView has not been given a configuration yet")
191 }
192
193 pub fn set_configuration(self: RenderHandle<Self>, app: &mut App, value: ViewConfiguration) {
195 if self.get(app).configuration == Some(value) {
196 return;
197 }
198 let old_configuration = self.get(app).configuration;
199 self.get_mut(app).configuration = Some(value);
200 if self.get(app).root_transform.is_none() {
201 return;
203 }
204 if old_configuration.is_none_or(|old| value.should_update_matrix(&old)) {
205 let root_layer = self.update_matrices_and_create_new_root_layer(app);
206 self.as_object()
207 .replace_root_layer(app, root_layer.as_offset_layer());
208 }
209 debug_assert!(self.get(app).root_transform.is_some());
210 self.as_object().mark_needs_layout(app);
211 }
212
213 pub fn has_configuration(self: RenderHandle<Self>, app: &App) -> bool {
215 self.get(app).configuration.is_some()
216 }
217
218 pub fn constraints(self: RenderHandle<Self>, app: &App) -> BoxConstraints {
225 debug_assert!(
226 self.has_configuration(app),
227 "Constraints are not available because RenderView has not been given a configuration yet."
228 );
229 self.configuration(app).logical_constraints
230 }
231
232 pub fn flutter_view(self: RenderHandle<Self>, app: &App) -> ViewRef {
234 self.get(app).view.clone()
235 }
236
237 pub fn prepare_initial_frame(self: RenderHandle<Self>, app: &mut App) {
246 debug_assert!(
247 self.as_object().owner(app).is_some(),
248 "attach the RenderView to a PipelineOwner before calling prepare_initial_frame"
249 );
250 debug_assert!(
251 self.get(app).root_transform.is_none(),
252 "prepare_initial_frame must only be called once"
253 );
254 debug_assert!(
255 self.has_configuration(app),
256 "set a configuration before calling prepare_initial_frame"
257 );
258 self.as_object().schedule_initial_layout(app);
259 let root_layer = self.update_matrices_and_create_new_root_layer(app);
260 self.as_object()
261 .schedule_initial_paint(app, root_layer.as_container_layer());
262 debug_assert!(self.get(app).root_transform.is_some());
263 }
264
265 fn update_matrices_and_create_new_root_layer(
266 self: RenderHandle<Self>,
267 app: &mut App,
268 ) -> Handle<TransformLayer> {
269 debug_assert!(self.has_configuration(app));
270 let root_transform = self.configuration(app).to_matrix();
271 self.get_mut(app).root_transform = Some(root_transform);
272 let root_layer = TransformLayer::new(app);
273 root_layer.set_transform(app, root_transform);
274 root_layer.attach(app, self.id());
275 debug_assert!(self.get(app).root_transform.is_some());
276 root_layer
277 }
278
279 pub fn hit_test(
288 self: RenderHandle<Self>,
289 app: &mut App,
290 result: &mut HitTestResult,
291 position: Offset,
292 ) -> bool {
293 if let Some(child) = self.child(app) {
294 child.hit_test(app, &mut BoxHitTestResult::wrap(result), position);
295 }
296 result.add(HitTestEntry::new(self));
297 true
298 }
299
300 pub fn composite_frame(self: RenderHandle<Self>, app: &mut App) {
304 debug_assert!(
305 self.has_configuration(app),
306 "set the RenderView configuration before calling composite_frame"
307 );
308 debug_assert!(
309 self.get(app).root_transform.is_some(),
310 "call prepare_initial_frame before calling composite_frame"
311 );
312 let layer = self
313 .as_object()
314 .layer(app)
315 .expect("call prepare_initial_frame before calling composite_frame");
316 let scene = layer.build_scene(app, SceneBuilder::new());
317 if self.get(app).automatic_system_ui_adjustment {
318 self.update_system_chrome(app);
319 }
320 debug_assert!(
321 self.configuration(app)
322 .logical_constraints
323 .is_satisfied_by(self.size(app))
324 );
325 self.get(app).view.present(std::sync::Arc::new(scene));
326 }
327
328 fn update_system_chrome(self: RenderHandle<Self>, app: &mut App) {
354 let Some(layer) = self.as_object().layer(app) else {
355 return;
356 };
357 let bounds = self.paint_bounds(app);
358 let padding = self.get(app).view.metrics().padding;
359 let top = Offset::new(
361 bounds.center().dx(),
363 padding.top / 2.0,
366 );
367 let bottom = Offset::new(
369 bounds.center().dx(),
371 bounds.bottom - 1.0 - padding.bottom / 2.0,
377 );
378 let layer = layer.as_layer();
379 let upper = layer.find::<SystemUiOverlayStyle>(app, top);
380 let is_android = app.platform().target_platform() == TargetPlatform::Android;
382 let lower = is_android
383 .then(|| layer.find::<SystemUiOverlayStyle>(app, bottom))
384 .flatten();
385 let Some(style) = merged_overlay_style(upper, lower, is_android) else {
387 return;
388 };
389 SystemChrome::set_system_ui_overlay_style(app, &style);
390 }
391
392 pub fn paint_bounds(self: RenderHandle<Self>, app: &App) -> Rect {
395 Offset::ZERO & (self.size(app) * self.configuration(app).device_pixel_ratio)
396 }
397}
398
399fn merged_overlay_style(
408 upper: Option<Rc<SystemUiOverlayStyle>>,
409 lower: Option<Rc<SystemUiOverlayStyle>>,
410 is_android: bool,
411) -> Option<SystemUiOverlayStyle> {
412 if let (Some(upper), Some(lower)) = (&upper, &lower) {
413 return Some(SystemUiOverlayStyle {
414 status_bar_brightness: upper.status_bar_brightness,
415 status_bar_icon_brightness: upper.status_bar_icon_brightness,
416 status_bar_color: upper.status_bar_color,
417 system_status_bar_contrast_enforced: upper.system_status_bar_contrast_enforced,
418 system_navigation_bar_color: lower.system_navigation_bar_color,
419 system_navigation_bar_divider_color: lower.system_navigation_bar_divider_color,
420 system_navigation_bar_icon_brightness: lower.system_navigation_bar_icon_brightness,
421 system_navigation_bar_contrast_enforced: lower.system_navigation_bar_contrast_enforced,
422 });
423 }
424 let defined = upper.or(lower)?;
425 if !is_android {
426 return Some(SystemUiOverlayStyle {
428 status_bar_brightness: defined.status_bar_brightness,
429 status_bar_icon_brightness: defined.status_bar_icon_brightness,
430 status_bar_color: defined.status_bar_color,
431 system_status_bar_contrast_enforced: defined.system_status_bar_contrast_enforced,
432 ..SystemUiOverlayStyle::new()
433 });
434 }
435 Some(*defined)
436}
437
438impl RenderObject for RenderView {
439 fn render_object_data(self: RenderHandle<Self>, app: &App) -> &RenderObjectData {
440 &self.get(app).render_object
441 }
442
443 fn render_object_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderObjectData {
444 &mut self.get_mut(app).render_object
445 }
446
447 fn perform_resize(self: RenderHandle<Self>, _app: &mut App) {
448 panic!("RenderView is never sized by its parent");
449 }
450
451 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
452 debug_assert!(self.get(app).root_transform.is_some());
453 let constraints = self.constraints(app);
454 let sized_by_child = !constraints.is_tight();
455 let child = self.child(app);
456 if let Some(child) = child {
457 child.layout(app, constraints, sized_by_child);
458 }
459 let size = match child {
460 Some(child) if sized_by_child => child.size(app),
461 _ => constraints.smallest(),
462 };
463 self.get_mut(app).size = size;
464 debug_assert!(size.is_finite());
465 debug_assert!(constraints.is_satisfied_by(size));
466 }
467
468 fn visit_children(
469 self: RenderHandle<Self>,
470 app: &App,
471 visitor: &mut dyn FnMut(AnyRenderObject),
472 ) {
473 if let Some(child) = self.child(app) {
474 visitor(child.as_object());
475 }
476 }
477
478 fn apply_paint_transform(
479 self: RenderHandle<Self>,
480 app: &App,
481 child: AnyRenderObject,
482 transform: &mut Matrix4,
483 ) {
484 let root_transform = self
485 .get(app)
486 .root_transform
487 .as_ref()
488 .expect("the view has a configuration");
489 crate::object::multiply(transform, root_transform);
490 debug_assert!(child.parent(app).map(AnyRenderObject::id) == Some(self.id()));
491 }
492
493 fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
494 true
495 }
496
497 fn paint(
498 self: RenderHandle<Self>,
499 app: &mut App,
500 context: &mut PaintingContext,
501 offset: Offset,
502 ) {
503 if let Some(child) = self.child(app) {
504 context.paint_child(app, child.as_object(), offset);
505 }
506 }
507}
508
509impl HitTestTarget for RenderHandle<RenderView> {
511 fn handle_event(&self, _app: &mut App, _event: &PointerEvent, _entry: &HitTestEntry) {}
512}
513
514static VTABLE: RenderObjectVTable = RenderObjectVTable::of::<RenderView>(
516 |app, id, child| <RenderView as RenderObject>::setup_parent_data(resolve(id), app, child),
517 |app, id| RenderView::paint_bounds(resolve(id), app),
518 |app, id, child, transform| {
519 <RenderView as RenderObject>::apply_paint_transform(resolve(id), app, child, transform)
520 },
521 |app, id| <RenderView as RenderObject>::perform_resize(resolve(id), app),
522 |app, id| crate::object::RenderObjectBase::mark_needs_layout(resolve::<RenderView>(id), app),
523 None,
524 None,
525);
526
527#[cfg(test)]
528mod tests {
529 use inset_foundation::AppCell;
530 use std::cell::Cell;
531 use std::rc::Rc;
532
533 use inset_embedder::{Picture, ViewId, ViewMetrics};
534
535 use super::*;
536 use crate::box_::RenderBox;
537 use crate::layer::TransformLayer;
538 use crate::pipeline_owner::PipelineOwner;
539 use crate::proxy_box::RenderConstrainedBox;
540 use crate::shifted_box::RenderPadding;
541 use inset_painting::EdgeInsetsGeometry;
542
543 struct TestView {
544 metrics: ViewMetrics,
545 presented: Rc<Cell<u32>>,
546 }
547
548 impl View for TestView {
549 fn id(&self) -> ViewId {
550 ViewId(0)
551 }
552
553 fn metrics(&self) -> ViewMetrics {
554 self.metrics
555 }
556
557 fn present(&self, _picture: std::sync::Arc<Picture>) {
558 self.presented.set(self.presented.get() + 1);
559 }
560 }
561
562 fn test_view(presented: &Rc<Cell<u32>>) -> ViewRef {
563 Rc::new(TestView {
564 metrics: ViewMetrics::default(),
565 presented: Rc::clone(presented),
566 })
567 }
568
569 fn create_view_configuration(size: Size, device_pixel_ratio: f64) -> ViewConfiguration {
571 let constraints = BoxConstraints::tight(size);
572 ViewConfiguration::new(
573 constraints * device_pixel_ratio,
574 constraints,
575 device_pixel_ratio,
576 )
577 }
578
579 #[test]
581 fn view_configuration_equality() {
582 let a = create_view_configuration(Size::new(800.0, 600.0), 1.0);
583 let b = create_view_configuration(Size::new(800.0, 600.0), 1.0);
584 let c = create_view_configuration(Size::new(800.0, 600.0), 3.0);
585 assert_eq!(a, b);
586 assert_ne!(a, c);
587 }
588
589 #[test]
591 fn constraints_are_derived_from_configuration() {
592 let cell = AppCell::new();
593 let mut app = cell.borrow_mut();
594 let presented = Rc::new(Cell::new(0));
595 let view = RenderView::new(&mut app, None, None, test_view(&presented));
596 assert!(!view.has_configuration(&app));
597 let constraints = BoxConstraints::new()
598 .min_width(1.0)
599 .max_width(2.0)
600 .min_height(3.0)
601 .max_height(4.0);
602 view.set_configuration(
603 &mut app,
604 ViewConfiguration::new(constraints * 3.0, constraints, 3.0),
605 );
606 assert_eq!(view.constraints(&app), constraints);
607 }
608
609 #[test]
612 fn configuration_can_change_before_prepare_initial_frame() {
613 let cell = AppCell::new();
614 let mut app = cell.borrow_mut();
615 let presented = Rc::new(Cell::new(0));
616 let view = RenderView::new(&mut app, None, None, test_view(&presented));
617 view.set_configuration(
618 &mut app,
619 create_view_configuration(Size::new(100.0, 200.0), 3.0),
620 );
621 view.set_configuration(
622 &mut app,
623 create_view_configuration(Size::new(200.0, 300.0), 2.0),
624 );
625 let owner = PipelineOwner::new(&mut app, None);
626 owner.set_root_node(&mut app, Some(view.as_object()));
627 view.prepare_initial_frame(&mut app);
628 }
629
630 #[test]
633 fn does_not_replace_the_root_layer_unnecessarily() {
634 let cell = AppCell::new();
635 let mut app = cell.borrow_mut();
636 let presented = Rc::new(Cell::new(0));
637 let view = RenderView::new(
638 &mut app,
639 None,
640 Some(create_view_configuration(Size::new(100.0, 100.0), 1.0)),
641 test_view(&presented),
642 );
643 let owner = PipelineOwner::new(&mut app, None);
644 owner.set_root_node(&mut app, Some(view.as_object()));
645 view.prepare_initial_frame(&mut app);
646 owner.flush_layout(&mut app);
647 owner.flush_compositing_bits(&mut app);
648 owner.flush_paint(&mut app);
649 assert!(!view.as_object().debug_needs_paint(&app));
650
651 view.set_configuration(
652 &mut app,
653 create_view_configuration(Size::new(100.0, 1117.0), 1.0),
654 );
655 assert!(
656 !view.as_object().debug_needs_paint(&app),
657 "a resize keeps the root layer"
658 );
659
660 view.set_configuration(
661 &mut app,
662 create_view_configuration(Size::new(100.0, 1117.0), 5.0),
663 );
664 assert!(
665 view.as_object().debug_needs_paint(&app),
666 "a new ratio replaces it"
667 );
668 let layer = view.as_object().debug_layer(&app).expect("root layer");
669 let transform = app
670 .handle::<TransformLayer>(layer.id())
671 .expect("the root is a TransformLayer");
672 assert_eq!(transform.transform(&app), Some(Matrix4::scale(5.0, 5.0)));
673 }
674
675 #[test]
678 fn accounts_for_device_pixel_ratio_in_paint_bounds_and_presents() {
679 let cell = AppCell::new();
680 let mut app = cell.borrow_mut();
681 let presented = Rc::new(Cell::new(0));
682 let child =
683 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
684 let view = RenderView::new(
685 &mut app,
686 Some(child.as_box()),
687 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
688 test_view(&presented),
689 );
690 let owner = PipelineOwner::new(&mut app, None);
691 owner.set_root_node(&mut app, Some(view.as_object()));
692 view.prepare_initial_frame(&mut app);
693 owner.flush_layout(&mut app);
694 owner.flush_compositing_bits(&mut app);
695 owner.flush_paint(&mut app);
696 view.composite_frame(&mut app);
697
698 assert_eq!(view.size(&app), Size::new(800.0, 600.0));
699 assert_eq!(
700 view.paint_bounds(&app),
701 Offset::ZERO & Size::new(1600.0, 1200.0)
702 );
703 assert_eq!(presented.get(), 1);
704 }
705
706 #[test]
707 fn coordinates_convert_through_the_paint_transforms_below_the_view() {
708 let cell = AppCell::new();
709 let mut app = cell.borrow_mut();
710 let presented = Rc::new(Cell::new(0));
711 let child =
712 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
713 let padding = RenderPadding::new(
714 &mut app,
715 EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
716 None,
717 Some(child.as_box()),
718 );
719 let view = RenderView::new(
720 &mut app,
721 Some(padding.as_box()),
722 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
723 test_view(&presented),
724 );
725 let owner = PipelineOwner::new(&mut app, None);
726 owner.set_root_node(&mut app, Some(view.as_object()));
727 view.prepare_initial_frame(&mut app);
728 owner.flush_layout(&mut app);
729
730 let child = child.as_box();
732 assert_eq!(
733 child.local_to_global(&app, Offset::ZERO, None),
734 Offset::new(10.0, 20.0)
735 );
736 assert_eq!(
737 child.global_to_local(&app, Offset::new(15.0, 25.0), None),
738 Offset::new(5.0, 5.0)
739 );
740 assert_eq!(
741 child.local_to_global(&app, Offset::new(1.0, 1.0), Some(padding.as_object())),
742 Offset::new(11.0, 21.0)
743 );
744 assert_eq!(
745 padding.as_box().global_to_local(
746 &app,
747 Offset::new(10.0, 20.0),
748 Some(child.as_object())
749 ),
750 Offset::new(20.0, 40.0)
751 );
752 assert!(
753 child
754 .as_object()
755 .paint_bounds(&app)
756 .inflate(70.0)
757 .contains(child.global_to_local(&app, Offset::new(100.0, 100.0), None))
758 );
759 }
760
761 #[test]
762 fn a_proxy_box_adds_no_paint_transform() {
763 let cell = AppCell::new();
764 let mut app = cell.borrow_mut();
765 let presented = Rc::new(Cell::new(0));
766 let child =
767 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
768 let proxy =
769 RenderConstrainedBox::new(&mut app, BoxConstraints::new(), Some(child.as_box()));
770 let padding = RenderPadding::new(
771 &mut app,
772 EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
773 None,
774 Some(proxy.as_box()),
775 );
776 let view = RenderView::new(
777 &mut app,
778 Some(padding.as_box()),
779 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
780 test_view(&presented),
781 );
782 let owner = PipelineOwner::new(&mut app, None);
783 owner.set_root_node(&mut app, Some(view.as_object()));
784 view.prepare_initial_frame(&mut app);
785 owner.flush_layout(&mut app);
786
787 assert_eq!(
788 child.as_box().local_to_global(&app, Offset::ZERO, None),
789 Offset::new(10.0, 20.0)
790 );
791 assert_eq!(
792 child
793 .as_box()
794 .global_to_local(&app, Offset::new(15.0, 25.0), None),
795 Offset::new(5.0, 5.0)
796 );
797 }
798}