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_painting::EdgeInsetsGeometry;
562
563 struct TestView {
564 metrics: ViewMetrics,
565 presented: Rc<Cell<u32>>,
566 }
567
568 impl View for TestView {
569 fn id(&self) -> ViewId {
570 ViewId(0)
571 }
572
573 fn metrics(&self) -> ViewMetrics {
574 self.metrics
575 }
576
577 fn present(&self, _scene: std::sync::Arc<inset_embedder::Scene>) {
578 self.presented.set(self.presented.get() + 1);
579 }
580 }
581
582 fn test_view(presented: &Rc<Cell<u32>>) -> ViewRef {
583 Rc::new(TestView {
584 metrics: ViewMetrics::default(),
585 presented: Rc::clone(presented),
586 })
587 }
588
589 fn create_view_configuration(size: Size, device_pixel_ratio: f64) -> ViewConfiguration {
591 let constraints = BoxConstraints::tight(size);
592 ViewConfiguration::new(
593 constraints * device_pixel_ratio,
594 constraints,
595 device_pixel_ratio,
596 )
597 }
598
599 #[test]
601 fn view_configuration_equality() {
602 let a = create_view_configuration(Size::new(800.0, 600.0), 1.0);
603 let b = create_view_configuration(Size::new(800.0, 600.0), 1.0);
604 let c = create_view_configuration(Size::new(800.0, 600.0), 3.0);
605 assert_eq!(a, b);
606 assert_ne!(a, c);
607 }
608
609 #[test]
611 fn constraints_are_derived_from_configuration() {
612 let cell = AppCell::new();
613 let mut app = cell.borrow_mut();
614 let presented = Rc::new(Cell::new(0));
615 let view = RenderView::new(&mut app, None, None, test_view(&presented));
616 assert!(!view.has_configuration(&app));
617 let constraints = BoxConstraints::new()
618 .min_width(1.0)
619 .max_width(2.0)
620 .min_height(3.0)
621 .max_height(4.0);
622 view.set_configuration(
623 &mut app,
624 ViewConfiguration::new(constraints * 3.0, constraints, 3.0),
625 );
626 assert_eq!(view.constraints(&app), constraints);
627 }
628
629 #[test]
632 fn configuration_can_change_before_prepare_initial_frame() {
633 let cell = AppCell::new();
634 let mut app = cell.borrow_mut();
635 let presented = Rc::new(Cell::new(0));
636 let view = RenderView::new(&mut app, None, None, test_view(&presented));
637 view.set_configuration(
638 &mut app,
639 create_view_configuration(Size::new(100.0, 200.0), 3.0),
640 );
641 view.set_configuration(
642 &mut app,
643 create_view_configuration(Size::new(200.0, 300.0), 2.0),
644 );
645 let owner = PipelineOwner::new(&mut app, None, None);
646 owner.set_root_node(&mut app, Some(view.as_object()));
647 view.prepare_initial_frame(&mut app);
648 }
649
650 #[test]
653 fn does_not_replace_the_root_layer_unnecessarily() {
654 let cell = AppCell::new();
655 let mut app = cell.borrow_mut();
656 let presented = Rc::new(Cell::new(0));
657 let view = RenderView::new(
658 &mut app,
659 None,
660 Some(create_view_configuration(Size::new(100.0, 100.0), 1.0)),
661 test_view(&presented),
662 );
663 let owner = PipelineOwner::new(&mut app, None, None);
664 owner.set_root_node(&mut app, Some(view.as_object()));
665 view.prepare_initial_frame(&mut app);
666 owner.flush_layout(&mut app);
667 owner.flush_compositing_bits(&mut app);
668 owner.flush_paint(&mut app);
669 assert!(!view.as_object().debug_needs_paint(&app));
670
671 view.set_configuration(
672 &mut app,
673 create_view_configuration(Size::new(100.0, 1117.0), 1.0),
674 );
675 assert!(
676 !view.as_object().debug_needs_paint(&app),
677 "a resize keeps the root layer"
678 );
679
680 view.set_configuration(
681 &mut app,
682 create_view_configuration(Size::new(100.0, 1117.0), 5.0),
683 );
684 assert!(
685 view.as_object().debug_needs_paint(&app),
686 "a new ratio replaces it"
687 );
688 let layer = view.as_object().debug_layer(&app).expect("root layer");
689 let transform = app
690 .handle::<TransformLayer>(layer.id())
691 .expect("the root is a TransformLayer");
692 assert_eq!(transform.transform(&app), Some(Matrix4::scale(5.0, 5.0)));
693 }
694
695 #[test]
698 fn accounts_for_device_pixel_ratio_in_paint_bounds_and_presents() {
699 let cell = AppCell::new();
700 let mut app = cell.borrow_mut();
701 let presented = Rc::new(Cell::new(0));
702 let child =
703 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
704 let view = RenderView::new(
705 &mut app,
706 Some(child.as_box()),
707 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
708 test_view(&presented),
709 );
710 let owner = PipelineOwner::new(&mut app, None, None);
711 owner.set_root_node(&mut app, Some(view.as_object()));
712 view.prepare_initial_frame(&mut app);
713 owner.flush_layout(&mut app);
714 owner.flush_compositing_bits(&mut app);
715 owner.flush_paint(&mut app);
716 view.composite_frame(&mut app);
717
718 assert_eq!(view.size(&app), Size::new(800.0, 600.0));
719 assert_eq!(
720 view.paint_bounds(&app),
721 Offset::ZERO & Size::new(1600.0, 1200.0)
722 );
723 assert_eq!(presented.get(), 1);
724 }
725
726 #[test]
727 fn coordinates_convert_through_the_paint_transforms_below_the_view() {
728 let cell = AppCell::new();
729 let mut app = cell.borrow_mut();
730 let presented = Rc::new(Cell::new(0));
731 let child =
732 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
733 let padding = RenderPadding::new(
734 &mut app,
735 EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
736 None,
737 Some(child.as_box()),
738 );
739 let view = RenderView::new(
740 &mut app,
741 Some(padding.as_box()),
742 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
743 test_view(&presented),
744 );
745 let owner = PipelineOwner::new(&mut app, None, None);
746 owner.set_root_node(&mut app, Some(view.as_object()));
747 view.prepare_initial_frame(&mut app);
748 owner.flush_layout(&mut app);
749
750 let child = child.as_box();
752 assert_eq!(
753 child.local_to_global(&app, Offset::ZERO, None),
754 Offset::new(10.0, 20.0)
755 );
756 assert_eq!(
757 child.global_to_local(&app, Offset::new(15.0, 25.0), None),
758 Offset::new(5.0, 5.0)
759 );
760 assert_eq!(
761 child.local_to_global(&app, Offset::new(1.0, 1.0), Some(padding.as_object())),
762 Offset::new(11.0, 21.0)
763 );
764 assert_eq!(
765 padding.as_box().global_to_local(
766 &app,
767 Offset::new(10.0, 20.0),
768 Some(child.as_object())
769 ),
770 Offset::new(20.0, 40.0)
771 );
772 assert!(
773 child
774 .as_object()
775 .paint_bounds(&app)
776 .inflate(70.0)
777 .contains(child.global_to_local(&app, Offset::new(100.0, 100.0), None))
778 );
779 }
780
781 #[test]
782 fn a_proxy_box_adds_no_paint_transform() {
783 let cell = AppCell::new();
784 let mut app = cell.borrow_mut();
785 let presented = Rc::new(Cell::new(0));
786 let child =
787 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
788 let proxy =
789 RenderConstrainedBox::new(&mut app, BoxConstraints::new(), Some(child.as_box()));
790 let padding = RenderPadding::new(
791 &mut app,
792 EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
793 None,
794 Some(proxy.as_box()),
795 );
796 let view = RenderView::new(
797 &mut app,
798 Some(padding.as_box()),
799 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
800 test_view(&presented),
801 );
802 let owner = PipelineOwner::new(&mut app, None, None);
803 owner.set_root_node(&mut app, Some(view.as_object()));
804 view.prepare_initial_frame(&mut app);
805 owner.flush_layout(&mut app);
806
807 assert_eq!(
808 child.as_box().local_to_global(&app, Offset::ZERO, None),
809 Offset::new(10.0, 20.0)
810 );
811 assert_eq!(
812 child
813 .as_box()
814 .global_to_local(&app, Offset::new(15.0, 25.0), None),
815 Offset::new(5.0, 5.0)
816 );
817 }
818}