1use inset_embedder::{Matrix4, Offset, Rect, SceneBuilder, Size, View, ViewRef};
6use inset_foundation::{App, Handle};
7use inset_gestures::{HitTestEntry, HitTestResult, HitTestTarget, PointerEvent};
8
9use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult};
10use crate::layer::{ContainerLayer, OffsetLayerMixin, TransformLayer};
11use crate::object::{
12 AnyRenderObject, Constraints, RenderHandle, RenderObject, RenderObjectData, RenderObjectVTable,
13 resolve,
14};
15use crate::painting_context::PaintingContext;
16
17#[derive(Clone, Copy, Debug, PartialEq)]
19pub struct ViewConfiguration {
20 pub logical_constraints: BoxConstraints,
22 pub physical_constraints: BoxConstraints,
24 pub device_pixel_ratio: f64,
26}
27
28impl ViewConfiguration {
29 pub const fn new(
33 physical_constraints: BoxConstraints,
34 logical_constraints: BoxConstraints,
35 device_pixel_ratio: f64,
36 ) -> ViewConfiguration {
37 ViewConfiguration {
38 logical_constraints,
39 physical_constraints,
40 device_pixel_ratio,
41 }
42 }
43
44 pub fn from_view(view: &dyn View) -> ViewConfiguration {
46 let metrics = view.metrics();
47 let physical_constraints =
48 BoxConstraints::from_view_constraints(metrics.physical_constraints);
49 let device_pixel_ratio = metrics.device_pixel_ratio;
50 ViewConfiguration {
51 logical_constraints: physical_constraints / device_pixel_ratio,
52 physical_constraints,
53 device_pixel_ratio,
54 }
55 }
56
57 pub fn to_matrix(&self) -> Matrix4 {
60 Matrix4::scale(
61 self.device_pixel_ratio as f32,
62 self.device_pixel_ratio as f32,
63 )
64 }
65
66 pub fn should_update_matrix(&self, old_configuration: &ViewConfiguration) -> bool {
69 old_configuration.device_pixel_ratio != self.device_pixel_ratio
70 }
71
72 pub fn to_physical_size(&self, logical_size: Size) -> Size {
74 self.physical_constraints
75 .constrain(logical_size * self.device_pixel_ratio)
76 }
77}
78
79impl Default for ViewConfiguration {
80 fn default() -> ViewConfiguration {
81 ViewConfiguration::new(
82 BoxConstraints::new().max_width(0.0).max_height(0.0),
83 BoxConstraints::new().max_width(0.0).max_height(0.0),
84 1.0,
85 )
86 }
87}
88
89pub struct RenderView {
98 render_object: RenderObjectData,
99 child: Option<AnyRenderBox>,
100 size: Size,
101 configuration: Option<ViewConfiguration>,
102 view: ViewRef,
103 root_transform: Option<Matrix4>,
104}
105
106impl RenderView {
107 pub fn new(
114 app: &mut App,
115 child: Option<AnyRenderBox>,
116 configuration: Option<ViewConfiguration>,
117 view: ViewRef,
118 ) -> RenderHandle<RenderView> {
119 let this = RenderHandle::from_handle(app.create(RenderView {
120 render_object: RenderObjectData::new(),
121 child: None,
122 size: Size::ZERO,
123 configuration: None,
124 view,
125 root_transform: None,
126 }));
127 let data = this.render_object_data_mut(app);
128 data.object_vtable = Some(&VTABLE);
129 data.was_repaint_boundary = true;
130 data.needs_compositing = true;
131 if let Some(configuration) = configuration {
132 this.set_configuration(app, configuration);
133 }
134 this.set_child(app, child);
135 this
136 }
137
138 pub fn as_object(self: RenderHandle<Self>) -> AnyRenderObject {
140 AnyRenderObject::from_vtable(self.id(), const { &VTABLE })
141 }
142
143 pub fn child(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderBox> {
145 self.get(app).child
146 }
147
148 pub fn set_child(self: RenderHandle<Self>, app: &mut App, value: Option<AnyRenderBox>) {
150 if let Some(old) = self.get(app).child {
151 self.as_object().drop_child(app, old.as_object());
152 }
153 self.get_mut(app).child = value;
154 if let Some(new) = value {
155 self.as_object().adopt_child(app, new.as_object());
156 }
157 }
158
159 pub fn size(self: RenderHandle<Self>, app: &App) -> Size {
161 self.get(app).size
162 }
163
164 pub fn configuration(self: RenderHandle<Self>, app: &App) -> ViewConfiguration {
176 self.get(app)
177 .configuration
178 .expect("RenderView has not been given a configuration yet")
179 }
180
181 pub fn set_configuration(self: RenderHandle<Self>, app: &mut App, value: ViewConfiguration) {
183 if self.get(app).configuration == Some(value) {
184 return;
185 }
186 let old_configuration = self.get(app).configuration;
187 self.get_mut(app).configuration = Some(value);
188 if self.get(app).root_transform.is_none() {
189 return;
191 }
192 if old_configuration.is_none_or(|old| value.should_update_matrix(&old)) {
193 let root_layer = self.update_matrices_and_create_new_root_layer(app);
194 self.as_object()
195 .replace_root_layer(app, root_layer.as_offset_layer());
196 }
197 debug_assert!(self.get(app).root_transform.is_some());
198 self.as_object().mark_needs_layout(app);
199 }
200
201 pub fn has_configuration(self: RenderHandle<Self>, app: &App) -> bool {
203 self.get(app).configuration.is_some()
204 }
205
206 pub fn constraints(self: RenderHandle<Self>, app: &App) -> BoxConstraints {
213 debug_assert!(
214 self.has_configuration(app),
215 "Constraints are not available because RenderView has not been given a configuration yet."
216 );
217 self.configuration(app).logical_constraints
218 }
219
220 pub fn flutter_view(self: RenderHandle<Self>, app: &App) -> ViewRef {
222 self.get(app).view.clone()
223 }
224
225 pub fn prepare_initial_frame(self: RenderHandle<Self>, app: &mut App) {
234 debug_assert!(
235 self.as_object().owner(app).is_some(),
236 "attach the RenderView to a PipelineOwner before calling prepare_initial_frame"
237 );
238 debug_assert!(
239 self.get(app).root_transform.is_none(),
240 "prepare_initial_frame must only be called once"
241 );
242 debug_assert!(
243 self.has_configuration(app),
244 "set a configuration before calling prepare_initial_frame"
245 );
246 self.as_object().schedule_initial_layout(app);
247 let root_layer = self.update_matrices_and_create_new_root_layer(app);
248 self.as_object()
249 .schedule_initial_paint(app, root_layer.as_container_layer());
250 debug_assert!(self.get(app).root_transform.is_some());
251 }
252
253 fn update_matrices_and_create_new_root_layer(
254 self: RenderHandle<Self>,
255 app: &mut App,
256 ) -> Handle<TransformLayer> {
257 debug_assert!(self.has_configuration(app));
258 let root_transform = self.configuration(app).to_matrix();
259 self.get_mut(app).root_transform = Some(root_transform);
260 let root_layer = TransformLayer::new(app);
261 root_layer.set_transform(app, root_transform);
262 root_layer.attach(app, self.id());
263 debug_assert!(self.get(app).root_transform.is_some());
264 root_layer
265 }
266
267 pub fn hit_test(
276 self: RenderHandle<Self>,
277 app: &mut App,
278 result: &mut HitTestResult,
279 position: Offset,
280 ) -> bool {
281 if let Some(child) = self.child(app) {
282 child.hit_test(app, &mut BoxHitTestResult::wrap(result), position);
283 }
284 result.add(HitTestEntry::new(self));
285 true
286 }
287
288 pub fn composite_frame(self: RenderHandle<Self>, app: &mut App) {
292 debug_assert!(
293 self.has_configuration(app),
294 "set the RenderView configuration before calling composite_frame"
295 );
296 debug_assert!(
297 self.get(app).root_transform.is_some(),
298 "call prepare_initial_frame before calling composite_frame"
299 );
300 let layer = self
301 .as_object()
302 .layer(app)
303 .expect("call prepare_initial_frame before calling composite_frame");
304 let scene = layer.build_scene(app, SceneBuilder::new());
305 debug_assert!(
306 self.configuration(app)
307 .logical_constraints
308 .is_satisfied_by(self.size(app))
309 );
310 self.get(app).view.present(&scene);
311 }
312
313 pub fn paint_bounds(self: RenderHandle<Self>, app: &App) -> Rect {
316 Offset::ZERO & (self.size(app) * self.configuration(app).device_pixel_ratio)
317 }
318}
319
320impl RenderObject for RenderView {
321 fn render_object_data(self: RenderHandle<Self>, app: &App) -> &RenderObjectData {
322 &self.get(app).render_object
323 }
324
325 fn render_object_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderObjectData {
326 &mut self.get_mut(app).render_object
327 }
328
329 fn perform_resize(self: RenderHandle<Self>, _app: &mut App) {
330 panic!("RenderView is never sized by its parent");
331 }
332
333 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
334 debug_assert!(self.get(app).root_transform.is_some());
335 let constraints = self.constraints(app);
336 let sized_by_child = !constraints.is_tight();
337 let child = self.child(app);
338 if let Some(child) = child {
339 child.layout(app, constraints, sized_by_child);
340 }
341 let size = match child {
342 Some(child) if sized_by_child => child.size(app),
343 _ => constraints.smallest(),
344 };
345 self.get_mut(app).size = size;
346 debug_assert!(size.is_finite());
347 debug_assert!(constraints.is_satisfied_by(size));
348 }
349
350 fn visit_children(
351 self: RenderHandle<Self>,
352 app: &App,
353 visitor: &mut dyn FnMut(AnyRenderObject),
354 ) {
355 if let Some(child) = self.child(app) {
356 visitor(child.as_object());
357 }
358 }
359
360 fn apply_paint_transform(
361 self: RenderHandle<Self>,
362 app: &App,
363 child: AnyRenderObject,
364 transform: &mut Matrix4,
365 ) {
366 let root_transform = self
367 .get(app)
368 .root_transform
369 .as_ref()
370 .expect("the view has a configuration");
371 crate::object::multiply(transform, root_transform);
372 debug_assert!(child.parent(app).map(AnyRenderObject::id) == Some(self.id()));
373 }
374
375 fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
376 true
377 }
378
379 fn paint(
380 self: RenderHandle<Self>,
381 app: &mut App,
382 context: &mut PaintingContext,
383 offset: Offset,
384 ) {
385 if let Some(child) = self.child(app) {
386 context.paint_child(app, child.as_object(), offset);
387 }
388 }
389}
390
391impl HitTestTarget for RenderHandle<RenderView> {
393 fn handle_event(&self, _app: &mut App, _event: &PointerEvent, _entry: &HitTestEntry) {}
394}
395
396static VTABLE: RenderObjectVTable = RenderObjectVTable::of::<RenderView>(
398 |app, id, child| <RenderView as RenderObject>::setup_parent_data(resolve(id), app, child),
399 |app, id| RenderView::paint_bounds(resolve(id), app),
400 |app, id, child, transform| {
401 <RenderView as RenderObject>::apply_paint_transform(resolve(id), app, child, transform)
402 },
403 |app, id| <RenderView as RenderObject>::perform_resize(resolve(id), app),
404 |app, id| crate::object::RenderObjectBase::mark_needs_layout(resolve::<RenderView>(id), app),
405 None,
406 None,
407);
408
409#[cfg(test)]
410mod tests {
411 use inset_foundation::AppCell;
412 use std::cell::Cell;
413 use std::rc::Rc;
414
415 use inset_embedder::{Picture, ViewId, ViewMetrics};
416
417 use super::*;
418 use crate::box_::RenderBox;
419 use crate::layer::TransformLayer;
420 use crate::pipeline_owner::PipelineOwner;
421 use crate::proxy_box::RenderConstrainedBox;
422 use crate::shifted_box::RenderPadding;
423 use inset_painting::EdgeInsetsGeometry;
424
425 struct TestView {
426 metrics: ViewMetrics,
427 presented: Rc<Cell<u32>>,
428 }
429
430 impl View for TestView {
431 fn id(&self) -> ViewId {
432 ViewId(0)
433 }
434
435 fn metrics(&self) -> ViewMetrics {
436 self.metrics
437 }
438
439 fn present(&self, _picture: &Picture) {
440 self.presented.set(self.presented.get() + 1);
441 }
442 }
443
444 fn test_view(presented: &Rc<Cell<u32>>) -> ViewRef {
445 Rc::new(TestView {
446 metrics: ViewMetrics::default(),
447 presented: Rc::clone(presented),
448 })
449 }
450
451 fn create_view_configuration(size: Size, device_pixel_ratio: f64) -> ViewConfiguration {
453 let constraints = BoxConstraints::tight(size);
454 ViewConfiguration::new(
455 constraints * device_pixel_ratio,
456 constraints,
457 device_pixel_ratio,
458 )
459 }
460
461 #[test]
463 fn view_configuration_equality() {
464 let a = create_view_configuration(Size::new(800.0, 600.0), 1.0);
465 let b = create_view_configuration(Size::new(800.0, 600.0), 1.0);
466 let c = create_view_configuration(Size::new(800.0, 600.0), 3.0);
467 assert_eq!(a, b);
468 assert_ne!(a, c);
469 }
470
471 #[test]
473 fn constraints_are_derived_from_configuration() {
474 let cell = AppCell::new();
475 let mut app = cell.borrow_mut();
476 let presented = Rc::new(Cell::new(0));
477 let view = RenderView::new(&mut app, None, None, test_view(&presented));
478 assert!(!view.has_configuration(&app));
479 let constraints = BoxConstraints::new()
480 .min_width(1.0)
481 .max_width(2.0)
482 .min_height(3.0)
483 .max_height(4.0);
484 view.set_configuration(
485 &mut app,
486 ViewConfiguration::new(constraints * 3.0, constraints, 3.0),
487 );
488 assert_eq!(view.constraints(&app), constraints);
489 }
490
491 #[test]
494 fn configuration_can_change_before_prepare_initial_frame() {
495 let cell = AppCell::new();
496 let mut app = cell.borrow_mut();
497 let presented = Rc::new(Cell::new(0));
498 let view = RenderView::new(&mut app, None, None, test_view(&presented));
499 view.set_configuration(
500 &mut app,
501 create_view_configuration(Size::new(100.0, 200.0), 3.0),
502 );
503 view.set_configuration(
504 &mut app,
505 create_view_configuration(Size::new(200.0, 300.0), 2.0),
506 );
507 let owner = PipelineOwner::new(&mut app, None);
508 owner.set_root_node(&mut app, Some(view.as_object()));
509 view.prepare_initial_frame(&mut app);
510 }
511
512 #[test]
515 fn does_not_replace_the_root_layer_unnecessarily() {
516 let cell = AppCell::new();
517 let mut app = cell.borrow_mut();
518 let presented = Rc::new(Cell::new(0));
519 let view = RenderView::new(
520 &mut app,
521 None,
522 Some(create_view_configuration(Size::new(100.0, 100.0), 1.0)),
523 test_view(&presented),
524 );
525 let owner = PipelineOwner::new(&mut app, None);
526 owner.set_root_node(&mut app, Some(view.as_object()));
527 view.prepare_initial_frame(&mut app);
528 owner.flush_layout(&mut app);
529 owner.flush_compositing_bits(&mut app);
530 owner.flush_paint(&mut app);
531 assert!(!view.as_object().debug_needs_paint(&app));
532
533 view.set_configuration(
534 &mut app,
535 create_view_configuration(Size::new(100.0, 1117.0), 1.0),
536 );
537 assert!(
538 !view.as_object().debug_needs_paint(&app),
539 "a resize keeps the root layer"
540 );
541
542 view.set_configuration(
543 &mut app,
544 create_view_configuration(Size::new(100.0, 1117.0), 5.0),
545 );
546 assert!(
547 view.as_object().debug_needs_paint(&app),
548 "a new ratio replaces it"
549 );
550 let layer = view.as_object().debug_layer(&app).expect("root layer");
551 let transform = app
552 .handle::<TransformLayer>(layer.id())
553 .expect("the root is a TransformLayer");
554 assert_eq!(transform.transform(&app), Some(Matrix4::scale(5.0, 5.0)));
555 }
556
557 #[test]
560 fn accounts_for_device_pixel_ratio_in_paint_bounds_and_presents() {
561 let cell = AppCell::new();
562 let mut app = cell.borrow_mut();
563 let presented = Rc::new(Cell::new(0));
564 let child =
565 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
566 let view = RenderView::new(
567 &mut app,
568 Some(child.as_box()),
569 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
570 test_view(&presented),
571 );
572 let owner = PipelineOwner::new(&mut app, None);
573 owner.set_root_node(&mut app, Some(view.as_object()));
574 view.prepare_initial_frame(&mut app);
575 owner.flush_layout(&mut app);
576 owner.flush_compositing_bits(&mut app);
577 owner.flush_paint(&mut app);
578 view.composite_frame(&mut app);
579
580 assert_eq!(view.size(&app), Size::new(800.0, 600.0));
581 assert_eq!(
582 view.paint_bounds(&app),
583 Offset::ZERO & Size::new(1600.0, 1200.0)
584 );
585 assert_eq!(presented.get(), 1);
586 }
587
588 #[test]
589 fn coordinates_convert_through_the_paint_transforms_below_the_view() {
590 let cell = AppCell::new();
591 let mut app = cell.borrow_mut();
592 let presented = Rc::new(Cell::new(0));
593 let child =
594 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
595 let padding = RenderPadding::new(
596 &mut app,
597 EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
598 None,
599 Some(child.as_box()),
600 );
601 let view = RenderView::new(
602 &mut app,
603 Some(padding.as_box()),
604 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
605 test_view(&presented),
606 );
607 let owner = PipelineOwner::new(&mut app, None);
608 owner.set_root_node(&mut app, Some(view.as_object()));
609 view.prepare_initial_frame(&mut app);
610 owner.flush_layout(&mut app);
611
612 let child = child.as_box();
614 assert_eq!(
615 child.local_to_global(&app, Offset::ZERO, None),
616 Offset::new(10.0, 20.0)
617 );
618 assert_eq!(
619 child.global_to_local(&app, Offset::new(15.0, 25.0), None),
620 Offset::new(5.0, 5.0)
621 );
622 assert_eq!(
623 child.local_to_global(&app, Offset::new(1.0, 1.0), Some(padding.as_object())),
624 Offset::new(11.0, 21.0)
625 );
626 assert_eq!(
627 padding.as_box().global_to_local(
628 &app,
629 Offset::new(10.0, 20.0),
630 Some(child.as_object())
631 ),
632 Offset::new(20.0, 40.0)
633 );
634 assert!(
635 child
636 .as_object()
637 .paint_bounds(&app)
638 .inflate(70.0)
639 .contains(child.global_to_local(&app, Offset::new(100.0, 100.0), None))
640 );
641 }
642
643 #[test]
644 fn a_proxy_box_adds_no_paint_transform() {
645 let cell = AppCell::new();
646 let mut app = cell.borrow_mut();
647 let presented = Rc::new(Cell::new(0));
648 let child =
649 RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
650 let proxy =
651 RenderConstrainedBox::new(&mut app, BoxConstraints::new(), Some(child.as_box()));
652 let padding = RenderPadding::new(
653 &mut app,
654 EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
655 None,
656 Some(proxy.as_box()),
657 );
658 let view = RenderView::new(
659 &mut app,
660 Some(padding.as_box()),
661 Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
662 test_view(&presented),
663 );
664 let owner = PipelineOwner::new(&mut app, None);
665 owner.set_root_node(&mut app, Some(view.as_object()));
666 view.prepare_initial_frame(&mut app);
667 owner.flush_layout(&mut app);
668
669 assert_eq!(
670 child.as_box().local_to_global(&app, Offset::ZERO, None),
671 Offset::new(10.0, 20.0)
672 );
673 assert_eq!(
674 child
675 .as_box()
676 .global_to_local(&app, Offset::new(15.0, 25.0), None),
677 Offset::new(5.0, 5.0)
678 );
679 }
680}