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 needs_composite_frame: bool,
110 pub automatic_system_ui_adjustment: bool,
116}
117
118impl RenderView {
119 pub fn new(
126 app: &mut App,
127 child: Option<AnyRenderBox>,
128 configuration: Option<ViewConfiguration>,
129 view: ViewRef,
130 ) -> RenderHandle<RenderView> {
131 let this = RenderHandle::from_handle(app.create(RenderView {
132 render_object: RenderObjectData::new(),
133 child: None,
134 size: Size::ZERO,
135 configuration: None,
136 view,
137 root_transform: None,
138 needs_composite_frame: false,
139 automatic_system_ui_adjustment: true,
140 }));
141 let data = this.render_object_data_mut(app);
142 data.object_vtable = Some(&VTABLE);
143 data.was_repaint_boundary = true;
144 data.needs_compositing = true;
145 if let Some(configuration) = configuration {
146 this.set_configuration(app, configuration);
147 }
148 this.set_child(app, child);
149 this
150 }
151
152 pub fn as_object(self: RenderHandle<Self>) -> AnyRenderObject {
154 AnyRenderObject::from_vtable(self.id(), const { &VTABLE })
155 }
156
157 pub fn child(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderBox> {
159 self.get(app).child
160 }
161
162 pub fn set_child(self: RenderHandle<Self>, app: &mut App, value: Option<AnyRenderBox>) {
164 if let Some(old) = self.get(app).child {
165 self.as_object().drop_child(app, old.as_object());
166 }
167 self.get_mut(app).child = value;
168 if let Some(new) = value {
169 self.as_object().adopt_child(app, new.as_object());
170 }
171 }
172
173 pub fn size(self: RenderHandle<Self>, app: &App) -> Size {
175 self.get(app).size
176 }
177
178 pub fn configuration(self: RenderHandle<Self>, app: &App) -> ViewConfiguration {
190 self.get(app)
191 .configuration
192 .expect("RenderView has not been given a configuration yet")
193 }
194
195 pub fn set_configuration(self: RenderHandle<Self>, app: &mut App, value: ViewConfiguration) {
197 if self.get(app).configuration == Some(value) {
198 return;
199 }
200 let old_configuration = self.get(app).configuration;
201 self.get_mut(app).configuration = Some(value);
202 if self.get(app).root_transform.is_none() {
203 return;
205 }
206 if old_configuration.is_none_or(|old| value.should_update_matrix(&old)) {
207 let root_layer = self.update_matrices_and_create_new_root_layer(app);
208 self.as_object()
209 .replace_root_layer(app, root_layer.as_offset_layer());
210 }
211 debug_assert!(self.get(app).root_transform.is_some());
212 self.as_object().mark_needs_layout(app);
213 }
214
215 pub fn has_configuration(self: RenderHandle<Self>, app: &App) -> bool {
217 self.get(app).configuration.is_some()
218 }
219
220 pub fn constraints(self: RenderHandle<Self>, app: &App) -> BoxConstraints {
227 debug_assert!(
228 self.has_configuration(app),
229 "Constraints are not available because RenderView has not been given a configuration yet."
230 );
231 self.configuration(app).logical_constraints
232 }
233
234 pub fn flutter_view(self: RenderHandle<Self>, app: &App) -> ViewRef {
236 self.get(app).view.clone()
237 }
238
239 pub fn prepare_initial_frame(self: RenderHandle<Self>, app: &mut App) {
248 debug_assert!(
249 self.as_object().owner(app).is_some(),
250 "attach the RenderView to a PipelineOwner before calling prepare_initial_frame"
251 );
252 debug_assert!(
253 self.get(app).root_transform.is_none(),
254 "prepare_initial_frame must only be called once"
255 );
256 debug_assert!(
257 self.has_configuration(app),
258 "set a configuration before calling prepare_initial_frame"
259 );
260 self.as_object().schedule_initial_layout(app);
261 let root_layer = self.update_matrices_and_create_new_root_layer(app);
262 self.as_object()
263 .schedule_initial_paint(app, root_layer.as_container_layer());
264 debug_assert!(self.get(app).root_transform.is_some());
265 }
266
267 pub fn needs_composite_frame(self: RenderHandle<Self>, app: &App) -> bool {
271 self.get(app).needs_composite_frame
272 }
273
274 pub fn mark_needs_composite_frame(self: RenderHandle<Self>, app: &mut App) {
281 self.get_mut(app).needs_composite_frame = true;
282 }
283
284 fn update_matrices_and_create_new_root_layer(
285 self: RenderHandle<Self>,
286 app: &mut App,
287 ) -> Handle<TransformLayer> {
288 debug_assert!(self.has_configuration(app));
289 let root_transform = self.configuration(app).to_matrix();
290 self.get_mut(app).root_transform = Some(root_transform);
291 let root_layer = TransformLayer::new(app);
292 root_layer.set_transform(app, root_transform);
293 root_layer.attach(app, self.id());
294 debug_assert!(self.get(app).root_transform.is_some());
295 root_layer
296 }
297
298 pub fn hit_test(
307 self: RenderHandle<Self>,
308 app: &mut App,
309 result: &mut HitTestResult,
310 position: Offset,
311 ) -> bool {
312 if let Some(child) = self.child(app) {
313 child.hit_test(app, &mut BoxHitTestResult::wrap(result), position);
314 }
315 result.add(HitTestEntry::new(self));
316 true
317 }
318
319 pub fn composite_frame(self: RenderHandle<Self>, app: &mut App) {
323 debug_assert!(
324 self.has_configuration(app),
325 "set the RenderView configuration before calling composite_frame"
326 );
327 debug_assert!(
328 self.get(app).root_transform.is_some(),
329 "call prepare_initial_frame before calling composite_frame"
330 );
331 let layer = self
332 .as_object()
333 .layer(app)
334 .expect("call prepare_initial_frame before calling composite_frame");
335 let scene = layer.build_scene(app, SceneBuilder::new());
336 if self.get(app).automatic_system_ui_adjustment {
337 self.update_system_chrome(app);
338 }
339 debug_assert!(
340 self.configuration(app)
341 .logical_constraints
342 .is_satisfied_by(self.size(app))
343 );
344 self.get(app).view.present(std::sync::Arc::new(scene));
345 self.get_mut(app).needs_composite_frame = false;
346 }
347
348 fn update_system_chrome(self: RenderHandle<Self>, app: &mut App) {
374 let Some(layer) = self.as_object().layer(app) else {
375 return;
376 };
377 let bounds = self.paint_bounds(app);
378 let padding = self.get(app).view.metrics().padding;
379 let top = Offset::new(
381 bounds.center().dx(),
383 padding.top / 2.0,
386 );
387 let bottom = Offset::new(
389 bounds.center().dx(),
391 bounds.bottom - 1.0 - padding.bottom / 2.0,
397 );
398 let layer = layer.as_layer();
399 let upper = layer.find::<SystemUiOverlayStyle>(app, top);
400 let is_android = app.platform().target_platform() == TargetPlatform::Android;
402 let lower = is_android
403 .then(|| layer.find::<SystemUiOverlayStyle>(app, bottom))
404 .flatten();
405 let Some(style) = merged_overlay_style(upper, lower, is_android) else {
407 return;
408 };
409 SystemChrome::set_system_ui_overlay_style(app, &style);
410 }
411
412 pub fn paint_bounds(self: RenderHandle<Self>, app: &App) -> Rect {
415 Offset::ZERO & (self.size(app) * self.configuration(app).device_pixel_ratio)
416 }
417}
418
419fn merged_overlay_style(
428 upper: Option<Rc<SystemUiOverlayStyle>>,
429 lower: Option<Rc<SystemUiOverlayStyle>>,
430 is_android: bool,
431) -> Option<SystemUiOverlayStyle> {
432 if let (Some(upper), Some(lower)) = (&upper, &lower) {
433 return Some(SystemUiOverlayStyle {
434 status_bar_brightness: upper.status_bar_brightness,
435 status_bar_icon_brightness: upper.status_bar_icon_brightness,
436 status_bar_color: upper.status_bar_color,
437 system_status_bar_contrast_enforced: upper.system_status_bar_contrast_enforced,
438 system_navigation_bar_color: lower.system_navigation_bar_color,
439 system_navigation_bar_divider_color: lower.system_navigation_bar_divider_color,
440 system_navigation_bar_icon_brightness: lower.system_navigation_bar_icon_brightness,
441 system_navigation_bar_contrast_enforced: lower.system_navigation_bar_contrast_enforced,
442 });
443 }
444 let defined = upper.or(lower)?;
445 if !is_android {
446 return Some(SystemUiOverlayStyle {
448 status_bar_brightness: defined.status_bar_brightness,
449 status_bar_icon_brightness: defined.status_bar_icon_brightness,
450 status_bar_color: defined.status_bar_color,
451 system_status_bar_contrast_enforced: defined.system_status_bar_contrast_enforced,
452 ..SystemUiOverlayStyle::new()
453 });
454 }
455 Some(*defined)
456}
457
458impl RenderObject for RenderView {
459 fn render_object_data(self: RenderHandle<Self>, app: &App) -> &RenderObjectData {
460 &self.get(app).render_object
461 }
462
463 fn render_object_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderObjectData {
464 &mut self.get_mut(app).render_object
465 }
466
467 fn perform_resize(self: RenderHandle<Self>, _app: &mut App) {
468 panic!("RenderView is never sized by its parent");
469 }
470
471 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
472 debug_assert!(self.get(app).root_transform.is_some());
473 let constraints = self.constraints(app);
474 let sized_by_child = !constraints.is_tight();
475 let child = self.child(app);
476 if let Some(child) = child {
477 child.layout(app, constraints, sized_by_child);
478 }
479 let size = match child {
480 Some(child) if sized_by_child => child.size(app),
481 _ => constraints.smallest(),
482 };
483 self.get_mut(app).size = size;
484 debug_assert!(size.is_finite());
485 debug_assert!(constraints.is_satisfied_by(size));
486 }
487
488 fn visit_children(
489 self: RenderHandle<Self>,
490 app: &App,
491 visitor: &mut dyn FnMut(AnyRenderObject),
492 ) {
493 if let Some(child) = self.child(app) {
494 visitor(child.as_object());
495 }
496 }
497
498 fn apply_paint_transform(
499 self: RenderHandle<Self>,
500 app: &App,
501 child: AnyRenderObject,
502 transform: &mut Matrix4,
503 ) {
504 let root_transform = self
505 .get(app)
506 .root_transform
507 .as_ref()
508 .expect("the view has a configuration");
509 crate::object::multiply(transform, root_transform);
510 debug_assert!(child.parent(app).map(AnyRenderObject::id) == Some(self.id()));
511 }
512
513 fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
514 true
515 }
516
517 fn paint(
518 self: RenderHandle<Self>,
519 app: &mut App,
520 context: &mut PaintingContext,
521 offset: Offset,
522 ) {
523 if let Some(child) = self.child(app) {
524 context.paint_child(app, child.as_object(), offset);
525 }
526 }
527}
528
529impl HitTestTarget for RenderHandle<RenderView> {
531 fn handle_event(&self, _app: &mut App, _event: &PointerEvent, _entry: &HitTestEntry) {}
532}
533
534static VTABLE: RenderObjectVTable = RenderObjectVTable::of::<RenderView>(
536 |app, id, child| <RenderView as RenderObject>::setup_parent_data(resolve(id), app, child),
537 |app, id| RenderView::paint_bounds(resolve(id), app),
538 |app, id, child, transform| {
539 <RenderView as RenderObject>::apply_paint_transform(resolve(id), app, child, transform)
540 },
541 |app, id| <RenderView as RenderObject>::perform_resize(resolve(id), app),
542 |app, id| crate::object::RenderObjectBase::mark_needs_layout(resolve::<RenderView>(id), app),
543 None,
544 None,
545);
546
547#[cfg(test)]
548mod tests {
549 use inset_foundation::AppCell;
550 use std::cell::Cell;
551 use std::rc::Rc;
552
553 use inset_embedder::{ViewId, ViewMetrics};
554
555 use super::*;
556 use crate::box_::RenderBox;
557 use crate::layer::TransformLayer;
558 use crate::pipeline_owner::PipelineOwner;
559 use crate::proxy_box::RenderConstrainedBox;
560 use crate::shifted_box::RenderPadding;
561 use inset_embedder::ViewHost;
562 use inset_painting::EdgeInsetsGeometry;
563 use inset_test::TestViewHost;
564
565 struct TestView {
566 metrics: ViewMetrics,
567 presented: Rc<Cell<u32>>,
568 host: TestViewHost,
569 }
570
571 impl View for TestView {
572 fn id(&self) -> ViewId {
573 ViewId(0)
574 }
575
576 fn metrics(&self) -> ViewMetrics {
577 self.metrics
578 }
579
580 fn present(&self, _scene: std::sync::Arc<inset_embedder::Scene>) {
581 self.presented.set(self.presented.get() + 1);
582 }
583
584 fn host(&self) -> &dyn ViewHost {
585 &self.host
586 }
587 }
588
589 fn test_view(presented: &Rc<Cell<u32>>) -> ViewRef {
590 Rc::new(TestView {
591 metrics: ViewMetrics::default(),
592 presented: Rc::clone(presented),
593 host: TestViewHost::default(),
594 })
595 }
596
597 fn create_view_configuration(size: Size, device_pixel_ratio: f64) -> ViewConfiguration {
599 let constraints = BoxConstraints::tight(size);
600 ViewConfiguration::new(
601 constraints * device_pixel_ratio,
602 constraints,
603 device_pixel_ratio,
604 )
605 }
606
607 #[test]
609 fn view_configuration_equality() {
610 let a = create_view_configuration(Size::new(800.0, 600.0), 1.0);
611 let b = create_view_configuration(Size::new(800.0, 600.0), 1.0);
612 let c = create_view_configuration(Size::new(800.0, 600.0), 3.0);
613 assert_eq!(a, b);
614 assert_ne!(a, c);
615 }
616
617 #[test]
619 fn constraints_are_derived_from_configuration() {
620 let cell = AppCell::new();
621 let mut app = cell.borrow_mut();
622 let presented = Rc::new(Cell::new(0));
623 let view = RenderView::new(&mut app, None, None, test_view(&presented));
624 assert!(!view.has_configuration(&app));
625 let constraints = BoxConstraints::new()
626 .min_width(1.0)
627 .max_width(2.0)
628 .min_height(3.0)
629 .max_height(4.0);
630 view.set_configuration(
631 &mut app,
632 ViewConfiguration::new(constraints * 3.0, constraints, 3.0),
633 );
634 assert_eq!(view.constraints(&app), constraints);
635 }
636
637 #[test]
640 fn configuration_can_change_before_prepare_initial_frame() {
641 let cell = AppCell::new();
642 let mut app = cell.borrow_mut();
643 let presented = Rc::new(Cell::new(0));
644 let view = RenderView::new(&mut app, None, None, test_view(&presented));
645 view.set_configuration(
646 &mut app,
647 create_view_configuration(Size::new(100.0, 200.0), 3.0),
648 );
649 view.set_configuration(
650 &mut app,
651 create_view_configuration(Size::new(200.0, 300.0), 2.0),
652 );
653 let owner = PipelineOwner::new(&mut app, None, None);
654 owner.set_root_node(&mut app, Some(view.as_object()));
655 view.prepare_initial_frame(&mut app);
656 }
657
658 #[test]
661 fn does_not_replace_the_root_layer_unnecessarily() {
662 let cell = AppCell::new();
663 let mut app = cell.borrow_mut();
664 let presented = Rc::new(Cell::new(0));
665 let view = RenderView::new(
666 &mut app,
667 None,
668 Some(create_view_configuration(Size::new(100.0, 100.0), 1.0)),
669 test_view(&presented),
670 );
671 let owner = PipelineOwner::new(&mut app, None, None);
672 owner.set_root_node(&mut app, Some(view.as_object()));
673 view.prepare_initial_frame(&mut app);
674 owner.flush_layout(&mut app);
675 owner.flush_compositing_bits(&mut app);
676 owner.flush_paint(&mut app);
677 assert!(!view.as_object().debug_needs_paint(&app));
678
679 view.set_configuration(
680 &mut app,
681 create_view_configuration(Size::new(100.0, 1117.0), 1.0),
682 );
683 assert!(
684 !view.as_object().debug_needs_paint(&app),
685 "a resize keeps the root layer"
686 );
687
688 view.set_configuration(
689 &mut app,
690 create_view_configuration(Size::new(100.0, 1117.0), 5.0),
691 );
692 assert!(
693 view.as_object().debug_needs_paint(&app),
694 "a new ratio replaces it"
695 );
696 let layer = view.as_object().debug_layer(&app).expect("root layer");
697 let transform = app
698 .handle::<TransformLayer>(layer.id())
699 .expect("the root is a TransformLayer");
700 assert_eq!(transform.transform(&app), Some(Matrix4::scale(5.0, 5.0)));
701 }
702
703 #[test]
706 fn accounts_for_device_pixel_ratio_in_paint_bounds_and_presents() {
707 let cell = AppCell::new();
708 let mut app = cell.borrow_mut();
709 let presented = Rc::new(Cell::new(0));
710 let child =
711 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
712 let view = RenderView::new(
713 &mut app,
714 Some(child.as_box()),
715 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
716 test_view(&presented),
717 );
718 let owner = PipelineOwner::new(&mut app, None, None);
719 owner.set_root_node(&mut app, Some(view.as_object()));
720 view.prepare_initial_frame(&mut app);
721 owner.flush_layout(&mut app);
722 owner.flush_compositing_bits(&mut app);
723 owner.flush_paint(&mut app);
724 view.composite_frame(&mut app);
725
726 assert_eq!(view.size(&app), Size::new(800.0, 600.0));
727 assert_eq!(
728 view.paint_bounds(&app),
729 Offset::ZERO & Size::new(1600.0, 1200.0)
730 );
731 assert_eq!(presented.get(), 1);
732 }
733
734 #[test]
735 fn coordinates_convert_through_the_paint_transforms_below_the_view() {
736 let cell = AppCell::new();
737 let mut app = cell.borrow_mut();
738 let presented = Rc::new(Cell::new(0));
739 let child =
740 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
741 let padding = RenderPadding::new(
742 &mut app,
743 EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
744 None,
745 Some(child.as_box()),
746 );
747 let view = RenderView::new(
748 &mut app,
749 Some(padding.as_box()),
750 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
751 test_view(&presented),
752 );
753 let owner = PipelineOwner::new(&mut app, None, None);
754 owner.set_root_node(&mut app, Some(view.as_object()));
755 view.prepare_initial_frame(&mut app);
756 owner.flush_layout(&mut app);
757
758 let child = child.as_box();
760 assert_eq!(
761 child.local_to_global(&app, Offset::ZERO, None),
762 Offset::new(10.0, 20.0)
763 );
764 assert_eq!(
765 child.global_to_local(&app, Offset::new(15.0, 25.0), None),
766 Offset::new(5.0, 5.0)
767 );
768 assert_eq!(
769 child.local_to_global(&app, Offset::new(1.0, 1.0), Some(padding.as_object())),
770 Offset::new(11.0, 21.0)
771 );
772 assert_eq!(
773 padding.as_box().global_to_local(
774 &app,
775 Offset::new(10.0, 20.0),
776 Some(child.as_object())
777 ),
778 Offset::new(20.0, 40.0)
779 );
780 assert!(
781 child
782 .as_object()
783 .paint_bounds(&app)
784 .inflate(70.0)
785 .contains(child.global_to_local(&app, Offset::new(100.0, 100.0), None))
786 );
787 }
788
789 #[test]
790 fn a_proxy_box_adds_no_paint_transform() {
791 let cell = AppCell::new();
792 let mut app = cell.borrow_mut();
793 let presented = Rc::new(Cell::new(0));
794 let child =
795 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
796 let proxy =
797 RenderConstrainedBox::new(&mut app, BoxConstraints::new(), Some(child.as_box()));
798 let padding = RenderPadding::new(
799 &mut app,
800 EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
801 None,
802 Some(proxy.as_box()),
803 );
804 let view = RenderView::new(
805 &mut app,
806 Some(padding.as_box()),
807 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
808 test_view(&presented),
809 );
810 let owner = PipelineOwner::new(&mut app, None, None);
811 owner.set_root_node(&mut app, Some(view.as_object()));
812 view.prepare_initial_frame(&mut app);
813 owner.flush_layout(&mut app);
814
815 assert_eq!(
816 child.as_box().local_to_global(&app, Offset::ZERO, None),
817 Offset::new(10.0, 20.0)
818 );
819 assert_eq!(
820 child
821 .as_box()
822 .global_to_local(&app, Offset::new(15.0, 25.0), None),
823 Offset::new(5.0, 5.0)
824 );
825 }
826}