1use crate::api::math::Transform2;
19use crate::api::{borrow, LocalProperty, NodeContext, Size};
20use crate::node_interface::NodeLocal;
21use crate::{
22 apply_padding_frame, layout_hulls_equivalent, project_child_layout_hull_to_parent_space,
23 resolve_padded_autosize_axis, ExpandedNode, LayoutHull,
24};
25use pax_runtime_api::{properties::UntypedProperty, Interpolatable};
26use std::rc::Rc;
27
28const MEASURED_SIZE_EPSILON: f64 = 1e-9;
29
30pub trait Container {
44 fn bind_container(&self, _ctx: &NodeContext) {}
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48#[doc(hidden)]
49pub enum ContentMeasurementGeometry {
50 Intrinsic,
52 Placed,
54}
55
56pub fn measure_content_children_layout_hull(ctx: &NodeContext) -> LayoutHull {
62 let content_children = ctx.received_children.get();
63 if content_children.is_empty() {
64 return LayoutHull::from_axis_ranges(Some((0.0, 0.0)), Some((0.0, 0.0)));
65 }
66
67 let (padding_x, padding_y) = node_padding(ctx);
68 let content_transform_and_bounds =
69 apply_padding_frame(ctx.node_transform_and_bounds, padding_x, padding_y);
70
71 let mut hull: Option<LayoutHull> = None;
72 for child in content_children.iter() {
73 if child.is_layout_breakout() {
74 continue;
77 }
78 let projected_hull = project_child_layout_hull_to_parent_space(
79 content_transform_and_bounds,
80 child.transform_and_bounds.get(),
81 child.subtree_layout_hull.get(),
82 );
83 hull = Some(match hull {
84 Some(existing) => existing.union(projected_hull),
85 None => projected_hull,
86 });
87 }
88
89 hull.unwrap_or_else(|| LayoutHull::from_axis_ranges(Some((0.0, 0.0)), Some((0.0, 0.0))))
90}
91
92pub fn measure_content_children_forward_extents(ctx: &NodeContext) -> (Option<f64>, Option<f64>) {
94 let hull = measure_content_children_layout_hull(ctx);
95 (hull.forward_extent_x(), hull.forward_extent_y())
96}
97
98pub fn resolve_axis_autosize(
100 autosize: bool,
101 axis_override: Option<bool>,
102 default_when_enabled: bool,
103) -> bool {
104 axis_override.unwrap_or(autosize && default_when_enabled)
105}
106
107pub fn resolve_content_autosize_measurement(
113 ctx: &NodeContext,
114 width_explicit: bool,
115 height_explicit: bool,
116) -> Option<(f64, f64)> {
117 resolve_content_autosize_measurement_with_axes(ctx, width_explicit, height_explicit, true, true)
118}
119
120pub fn resolve_content_autosize_measurement_with_axes(
122 ctx: &NodeContext,
123 width_explicit: bool,
124 height_explicit: bool,
125 autosize_width: bool,
126 autosize_height: bool,
127) -> Option<(f64, f64)> {
128 if width_explicit && height_explicit {
129 return None;
130 }
131 if !autosize_width && !autosize_height {
132 return None;
133 }
134
135 let bounds = ctx.bounds_self.get();
136 let (content_width, content_height) = measure_content_children_forward_extents(ctx);
137 let (padding_x, padding_y) = node_padding(ctx);
138
139 Some((
140 if width_explicit || !autosize_width {
141 bounds.0
142 } else {
143 resolve_padded_autosize_axis(content_width?, padding_x)?
144 },
145 if height_explicit || !autosize_height {
146 bounds.1
147 } else {
148 resolve_padded_autosize_axis(content_height?, padding_y)?
149 },
150 ))
151}
152
153fn node_padding(ctx: &NodeContext) -> (Option<Size>, Option<Size>) {
154 let Some(node) = ctx.expanded_node.upgrade() else {
155 return (None, None);
156 };
157 let common_props = node.get_common_properties();
158 let common_props = borrow!(common_props);
159 (common_props.padding_x.get(), common_props.padding_y.get())
160}
161
162pub fn sync_content_autosize(expanded_node: &Rc<ExpandedNode>, ctx: &NodeContext, enabled: bool) {
164 sync_content_autosize_with_axes(expanded_node, ctx, enabled, enabled);
165}
166
167pub fn sync_content_autosize_with_axes(
169 expanded_node: &Rc<ExpandedNode>,
170 ctx: &NodeContext,
171 autosize_width: bool,
172 autosize_height: bool,
173) {
174 let measured_size = if autosize_width || autosize_height {
175 let common_props = expanded_node.get_common_properties();
176 let common_props = borrow!(common_props);
177 let width_explicit = common_props.width.get().is_some();
178 let height_explicit = common_props.height.get().is_some();
179 drop(common_props);
180
181 resolve_content_autosize_measurement_with_axes(
182 ctx,
183 width_explicit,
184 height_explicit,
185 autosize_width,
186 autosize_height,
187 )
188 } else {
189 None
190 };
191
192 match measured_size {
193 Some(measured_size) => {
194 let previous = expanded_node.measured_size.get();
195 if measured_size_needs_update(previous, measured_size) {
196 expanded_node.set_measured_size(measured_size.0, measured_size.1);
197 }
198 }
199 None => {
200 if expanded_node.measured_size.get().is_some() {
201 expanded_node.measured_size.set(None);
202 }
203 }
204 }
205}
206
207pub fn measured_size_needs_update(previous: Option<(f64, f64)>, next: (f64, f64)) -> bool {
208 match previous {
209 Some((previous_width, previous_height)) => {
210 axis_needs_update(previous_width, next.0) || axis_needs_update(previous_height, next.1)
211 }
212 None => true,
213 }
214}
215
216fn axis_needs_update(previous: f64, next: f64) -> bool {
217 if previous == next {
218 return false;
219 }
220 if !previous.is_finite() || !next.is_finite() {
221 return previous.to_bits() != next.to_bits();
222 }
223 (previous - next).abs() > MEASURED_SIZE_EPSILON
224}
225
226fn rebind_content_measurement_effect<F>(
227 expanded_node: &Rc<ExpandedNode>,
228 runtime_context: &Rc<crate::RuntimeContext>,
229 listener_name: &'static str,
230 geometry: ContentMeasurementGeometry,
231 extra_deps: &[UntypedProperty],
232 effect: F,
233) where
234 F: Fn(&Rc<ExpandedNode>, &NodeContext) + Clone + 'static,
235{
236 let common_props = expanded_node.get_common_properties();
237 let (width_prop, height_prop, padding_x_prop, padding_y_prop) = {
238 let common_props = borrow!(common_props);
239 (
240 common_props.width.clone(),
241 common_props.height.clone(),
242 common_props.padding_x.clone(),
243 common_props.padding_y.clone(),
244 )
245 };
246 let node_ctx = expanded_node.get_node_context(runtime_context);
247 let mut deps = vec![
248 node_ctx.bounds_self.untyped(),
249 width_prop.untyped(),
250 height_prop.untyped(),
251 padding_x_prop.untyped(),
252 padding_y_prop.untyped(),
253 ];
254 deps.extend(extra_deps.iter().cloned());
255
256 let placed_measurement = if geometry == ContentMeasurementGeometry::Placed {
257 let node_transform_and_bounds = expanded_node.transform_and_bounds.clone();
258 let mut projected_child_hulls = Vec::new();
259 for child in node_ctx.received_children.get().iter() {
260 let child_cp = child.get_common_properties();
261 let child_layout_role = borrow!(child_cp).layout_role.clone();
262 let projection_name = format!(
263 "{listener_name} projected child layout hull (parent id: {}, child id: {})",
264 expanded_node.id.0, child.id.0
265 );
266 projected_child_hulls.push(crate::projected_child_layout_hull_property(
267 node_transform_and_bounds.clone(),
268 padding_x_prop.clone(),
269 padding_y_prop.clone(),
270 child.transform_and_bounds.clone(),
271 child.subtree_layout_hull.clone(),
272 child_layout_role,
273 &projection_name,
274 ));
275 }
276
277 let measurement_deps = projected_child_hulls
278 .iter()
279 .map(LocalProperty::untyped)
280 .collect::<Vec<_>>();
281
282 let measurement_name = format!("{listener_name} placed measurement");
283 let measurement = LocalProperty::computed_with_cutoff_and_name(
284 move || {
285 if projected_child_hulls.is_empty() {
286 return LayoutHull::from_axis_ranges(Some((0.0, 0.0)), Some((0.0, 0.0)));
287 }
288 let hull = projected_child_hulls
289 .iter()
290 .fold(LayoutHull::default(), |hull, child| hull.union(child.get()));
291 if hull.x_range().is_none() && hull.y_range().is_none() {
292 LayoutHull::from_axis_ranges(Some((0.0, 0.0)), Some((0.0, 0.0)))
293 } else {
294 hull
295 }
296 },
297 &measurement_deps,
298 layout_hulls_equivalent,
299 &measurement_name,
300 );
301 deps.push(measurement.untyped());
302 Some(measurement)
303 } else {
304 for child in node_ctx.received_children.get().iter() {
305 let child_cp = child.get_common_properties();
306 deps.push(borrow!(child_cp).layout_role.untyped());
307 deps.push(child.subtree_layout_hull.untyped());
308 }
309 None
310 };
311
312 let weak_node = Rc::downgrade(expanded_node);
313 let runtime_context = Rc::clone(runtime_context);
314 let bounds_self = node_ctx.bounds_self.clone();
315 expanded_node
316 .content_measurement_listener
317 .replace_with(LocalProperty::computed_with_name(
318 move || {
319 let _ = bounds_self.get();
320 if let Some(measurement) = &placed_measurement {
321 let _ = measurement.get();
322 }
323 let Some(node) = weak_node.upgrade() else {
324 return;
325 };
326 let node_ctx = node.get_node_context(&runtime_context);
327 effect(&node, &node_ctx);
328 },
329 &deps,
330 listener_name,
331 ));
332}
333
334pub fn bind_content_measurement_effect<F>(
340 expanded_node: &Rc<ExpandedNode>,
341 ctx: &NodeContext,
342 listener_name: &'static str,
343 geometry: ContentMeasurementGeometry,
344 extra_deps: &[UntypedProperty],
345 effect: F,
346) where
347 F: Fn(&Rc<ExpandedNode>, &NodeContext) + Clone + 'static,
348{
349 if expanded_node.content_measurement_bound.replace(true) {
350 return;
351 }
352
353 rebind_content_measurement_effect(
354 expanded_node,
355 &ctx.runtime_context,
356 listener_name,
357 geometry,
358 extra_deps,
359 effect.clone(),
360 );
361 ctx.runtime_context
362 .register_expanded_node_effect_property_named(
363 expanded_node,
364 &expanded_node.content_measurement_listener,
365 listener_name,
366 );
367
368 let weak_node = Rc::downgrade(expanded_node);
369 let runtime_context = Rc::clone(&ctx.runtime_context);
370 let extra_deps = extra_deps.to_vec();
371 let rebind_name = format!("{listener_name} rebind");
372 expanded_node
373 .content_measurement_rebind_listener
374 .replace_with(LocalProperty::computed_with_name(
375 move || {
376 let Some(node) = weak_node.upgrade() else {
377 return;
378 };
379 rebind_content_measurement_effect(
380 &node,
381 &runtime_context,
382 listener_name,
383 geometry,
384 &extra_deps,
385 effect.clone(),
386 );
387 },
388 &[ctx.received_children.untyped()],
392 &rebind_name,
393 ));
394 ctx.runtime_context
395 .register_expanded_node_effect_property_named(
396 expanded_node,
397 &expanded_node.content_measurement_rebind_listener,
398 &rebind_name,
399 );
400}
401
402#[derive(Clone, Copy, Debug, PartialEq, Eq)]
414pub enum ReceivedChildrenSource {
415 Owned,
416 Projected,
417}
418
419#[derive(Clone, Copy, Debug, Default, PartialEq)]
425pub struct ContainerFrame {
426 pub transform: Transform2<NodeLocal, NodeLocal>,
427 pub bounds: (f64, f64),
428}
429
430impl Interpolatable for ContainerFrame {
431 fn interpolate(&self, other: &Self, t: f64) -> Self {
432 Self {
433 transform: self.transform.interpolate(&other.transform, t),
434 bounds: self.bounds.interpolate(&other.bounds, t),
435 }
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use crate::api::math::{Transform2, Vector2};
442 use crate::api::{CommonProperties, Layer, LayoutRole, Size};
443 use crate::{
444 bind_content_measurement_effect, measured_size_needs_update, sync_content_autosize,
445 sync_content_autosize_with_axes, BaseInstance, ComponentInstance, ContainerFrame,
446 ContentMeasurementGeometry, ExpandedNode, Globals, InstanceFlags, InstanceNode,
447 InstantiationArgs, RouteLocation, RuntimePropertiesStackFrame, TransformAndBounds,
448 };
449 use pax_runtime_api::pax_value::PaxAny;
450 use pax_runtime_api::{LocalProperty, Platform, TargetInfo, OS};
451 use std::cell::{Cell, RefCell};
452 use std::fmt;
453 use std::rc::Rc;
454
455 fn test_globals() -> Globals {
456 Globals {
457 elapsed_frames: LocalProperty::new(0),
458 elapsed_millis: LocalProperty::new(0),
459 viewport: LocalProperty::new(TransformAndBounds {
460 transform: Transform2::identity(),
461 bounds: (100.0, 100.0),
462 }),
463 gyro: LocalProperty::new(Default::default()),
464 accel: LocalProperty::new(Default::default()),
465 route_location: LocalProperty::new(RouteLocation::root()),
466 browser_allows_scroller_vector_layers: LocalProperty::new(true),
467 browser_allows_nested_scroller_vector_layers: LocalProperty::new(true),
468 platform: Platform::Unknown,
469 os: OS::Unknown,
470 target: TargetInfo::new(Platform::Unknown, OS::Unknown),
471 #[cfg(feature = "designtime")]
472 designtime: crate::test_support::designtime(),
473 get_elapsed_millis: Rc::new(|| 0),
474 }
475 }
476
477 fn default_properties_factory() -> Box<
478 dyn Fn(
479 Rc<RuntimePropertiesStackFrame>,
480 Option<Rc<ExpandedNode>>,
481 ) -> Option<Rc<RefCell<PaxAny>>>,
482 > {
483 Box::new(|_, expanded_node| {
484 expanded_node
485 .is_none()
486 .then(|| Rc::new(RefCell::new(PaxAny::Builtin(Default::default()))))
487 })
488 }
489
490 fn default_common_properties_factory() -> Box<
491 dyn Fn(
492 Rc<RuntimePropertiesStackFrame>,
493 Option<Rc<ExpandedNode>>,
494 ) -> Option<Rc<RefCell<CommonProperties>>>,
495 > {
496 Box::new(|_, expanded_node| {
497 expanded_node
498 .is_none()
499 .then(|| Rc::new(RefCell::new(CommonProperties::default())))
500 })
501 }
502
503 fn component_args(
504 template: Option<Vec<Rc<dyn InstanceNode>>>,
505 children: Option<Vec<Rc<dyn InstanceNode>>>,
506 ) -> InstantiationArgs {
507 InstantiationArgs {
508 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
509 default_common_properties_factory(),
510 ),
511 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
512 handler_registry: None,
513 children: children.map(RefCell::new),
514 component_template: template.map(RefCell::new),
515 component_settings: None,
516 template_node_identifier: None,
517 template_node_type_id: None,
518 template_node_selector_info: None,
519 transition_config: Default::default(),
520 properties_scope: crate::PropertiesScopeInit::None,
521 }
522 }
523
524 fn direct_node_args(children: Vec<Rc<dyn InstanceNode>>) -> InstantiationArgs {
525 InstantiationArgs {
526 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
527 default_common_properties_factory(),
528 ),
529 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
530 handler_registry: None,
531 children: Some(RefCell::new(children)),
532 component_template: None,
533 component_settings: None,
534 template_node_identifier: None,
535 template_node_type_id: None,
536 template_node_selector_info: None,
537 transition_config: Default::default(),
538 properties_scope: crate::PropertiesScopeInit::None,
539 }
540 }
541
542 struct TestDirectNode {
543 base: BaseInstance,
544 }
545
546 impl InstanceNode for TestDirectNode {
547 fn instantiate(args: InstantiationArgs) -> Rc<Self>
548 where
549 Self: Sized,
550 {
551 Rc::new(Self {
552 base: BaseInstance::new(
553 args,
554 InstanceFlags {
555 invisible_to_slot: false,
556 invisible_to_raycasting: true,
557 layer: Layer::DontCare,
558 is_component: false,
559 is_slot: false,
560 },
561 ),
562 })
563 }
564
565 fn resolve_debug(
566 &self,
567 f: &mut fmt::Formatter,
568 _expanded_node: Option<&ExpandedNode>,
569 ) -> fmt::Result {
570 f.debug_struct("TestDirectNode").finish()
571 }
572
573 fn base(&self) -> &BaseInstance {
574 &self.base
575 }
576 }
577
578 #[test]
579 fn owned_received_children_reflect_active_children() {
580 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
581 let direct: Rc<dyn InstanceNode> =
582 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
583 let root_component =
584 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
585 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
586 let root = ExpandedNode::initialize_root(root_component, &context);
587
588 root.recurse_update(&context);
589 let direct_node = root.children.get().first().cloned().unwrap();
590 let node_ctx = direct_node.get_node_context(&context);
591 let received_children = node_ctx.received_children.get();
592
593 assert_eq!(received_children.len(), 1);
594 assert_eq!(received_children[0].id, direct_node.children.get()[0].id);
595 assert_eq!(node_ctx.received_children_count.get(), 1);
596 assert!(node_ctx.retained_received_children.get().is_empty());
597 }
598
599 #[test]
600 fn projected_received_children_reflect_flattened_projected_children() {
601 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
602 let slotted_component: Rc<dyn InstanceNode> =
603 ComponentInstance::instantiate(component_args(Some(Vec::new()), Some(vec![leaf])));
604 let root_component = ComponentInstance::instantiate(component_args(
605 Some(vec![Rc::clone(&slotted_component)]),
606 None,
607 ));
608 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
609 let root = ExpandedNode::initialize_root(root_component, &context);
610
611 root.recurse_update(&context);
612 let component_node = root.children.get().first().cloned().unwrap();
613 let node_ctx = component_node.get_node_context(&context);
614 let received_children = node_ctx.received_children.get();
615
616 assert_eq!(received_children.len(), 1);
617 assert_eq!(
618 received_children[0].id,
619 component_node
620 .expanded_and_flattened_projected_children
621 .get()[0]
622 .id
623 );
624 assert_eq!(node_ctx.received_children_count.get(), 1);
625 assert!(node_ctx.retained_received_children.get().is_empty());
626 }
627
628 #[test]
629 fn sync_content_autosize_collapses_empty_content() {
630 let direct: Rc<dyn InstanceNode> =
631 TestDirectNode::instantiate(direct_node_args(Vec::new()));
632 let root_component =
633 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
634 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
635 let root = ExpandedNode::initialize_root(root_component, &context);
636
637 root.recurse_update(&context);
638 let direct_node = root.children.get().first().cloned().unwrap();
639 let node_ctx = direct_node.get_node_context(&context);
640
641 sync_content_autosize(&direct_node, &node_ctx, true);
642
643 assert_eq!(direct_node.measured_size.get(), Some((0.0, 0.0)));
644 }
645
646 #[test]
647 fn sync_content_autosize_with_axes_keeps_unmanaged_axis_at_layout_bounds() {
648 let direct: Rc<dyn InstanceNode> =
649 TestDirectNode::instantiate(direct_node_args(Vec::new()));
650 let root_component =
651 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
652 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
653 let root = ExpandedNode::initialize_root(root_component, &context);
654
655 root.recurse_update(&context);
656 let direct_node = root.children.get().first().cloned().unwrap();
657 let node_ctx = direct_node.get_node_context(&context);
658
659 sync_content_autosize_with_axes(&direct_node, &node_ctx, false, true);
660
661 assert_eq!(direct_node.measured_size.get(), Some((100.0, 0.0)));
662 }
663
664 #[test]
665 fn measured_size_update_ignores_float_jitter() {
666 assert!(!measured_size_needs_update(
667 Some((836.8000000000001, 228.6585365853658)),
668 (836.8000000000001, 228.65853658536582),
669 ));
670 assert!(measured_size_needs_update(
671 Some((836.8000000000001, 228.6585365853658)),
672 (836.8000000000001, 228.6585375853658),
673 ));
674 assert!(measured_size_needs_update(None, (0.0, 0.0)));
675 }
676
677 #[test]
678 fn placed_measurement_ignores_ancestor_translation_but_observes_child_position() {
679 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
680 let direct: Rc<dyn InstanceNode> =
681 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
682 let root_component =
683 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
684 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
685 let root = ExpandedNode::initialize_root(root_component, &context);
686 root.recurse_update(&context);
687 let direct_node = root.children.get().first().cloned().unwrap();
688 let child = direct_node.children.get().first().cloned().unwrap();
689 let child_common = child.get_common_properties();
690 child_common
691 .borrow()
692 .width
693 .set(Some(Size::Pixels(20.into())));
694 child_common
695 .borrow()
696 .height
697 .set(Some(Size::Pixels(20.into())));
698 child_common.borrow().x.set(Some(Size::Pixels(0.into())));
699 child_common.borrow().y.set(Some(Size::Pixels(0.into())));
700 context.drain_node_effects();
701
702 let effect_runs = Rc::new(Cell::new(0));
703 let effect_runs_for_effect = Rc::clone(&effect_runs);
704 let node_ctx = direct_node.get_node_context(&context);
705 bind_content_measurement_effect(
706 &direct_node,
707 &node_ctx,
708 "placed measurement test",
709 ContentMeasurementGeometry::Placed,
710 &[],
711 move |_, _| effect_runs_for_effect.set(effect_runs_for_effect.get() + 1),
712 );
713 context.drain_node_effects();
714 let baseline = effect_runs.get();
715
716 let viewport = context.globals().viewport;
717 viewport.set(TransformAndBounds {
718 transform: Transform2::translate(Vector2::new(25.0, 15.0)),
719 bounds: (100.0, 100.0),
720 });
721 context.drain_node_effects();
722 assert_eq!(effect_runs.get(), baseline);
723
724 child_common.borrow().x.set(Some(Size::Pixels(12.into())));
725 context.drain_node_effects();
726 assert_eq!(effect_runs.get(), baseline + 1);
727 }
728
729 #[test]
730 fn intrinsic_measurement_ignores_assigned_frame_but_observes_child_size() {
731 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
732 let direct: Rc<dyn InstanceNode> =
733 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
734 let root_component =
735 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
736 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
737 let root = ExpandedNode::initialize_root(root_component, &context);
738 root.recurse_update(&context);
739 let direct_node = root.children.get().first().cloned().unwrap();
740 let child = direct_node.children.get().first().cloned().unwrap();
741 let child_common = child.get_common_properties();
742 child_common
743 .borrow()
744 .width
745 .set(Some(Size::Pixels(20.into())));
746 child_common
747 .borrow()
748 .height
749 .set(Some(Size::Pixels(20.into())));
750 child_common.borrow().x.set(Some(Size::Pixels(0.into())));
751 child_common.borrow().y.set(Some(Size::Pixels(0.into())));
752 context.drain_node_effects();
753
754 let effect_runs = Rc::new(Cell::new(0));
755 let effect_runs_for_effect = Rc::clone(&effect_runs);
756 let node_ctx = direct_node.get_node_context(&context);
757 bind_content_measurement_effect(
758 &direct_node,
759 &node_ctx,
760 "intrinsic measurement test",
761 ContentMeasurementGeometry::Intrinsic,
762 &[],
763 move |_, _| effect_runs_for_effect.set(effect_runs_for_effect.get() + 1),
764 );
765 context.drain_node_effects();
766 let baseline = effect_runs.get();
767 let baseline_hull = child.subtree_layout_hull.get();
768
769 child.container_frame.set(Some(ContainerFrame {
770 transform: Transform2::translate(Vector2::new(20.0, 10.0)),
771 bounds: (100.0, 100.0),
772 }));
773 context.drain_node_effects();
774 assert_eq!(effect_runs.get(), baseline);
775
776 child_common
777 .borrow()
778 .width
779 .set(Some(Size::Pixels(40.into())));
780 context.drain_node_effects();
781 assert_ne!(child.subtree_layout_hull.get(), baseline_hull);
782 assert_eq!(effect_runs.get(), baseline + 1);
783 }
784
785 #[test]
786 fn sync_content_autosize_includes_padding() {
787 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
788 let direct: Rc<dyn InstanceNode> =
789 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
790 let root_component =
791 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
792 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
793 let root = ExpandedNode::initialize_root(root_component, &context);
794
795 root.recurse_update(&context);
796 let direct_node = root.children.get().first().cloned().unwrap();
797 let child = direct_node.children.get().first().cloned().unwrap();
798 child.set_measured_size(50.0, 60.0);
799 let direct_common_props = direct_node.get_common_properties();
800 direct_common_props
801 .borrow()
802 .padding_x
803 .set(Some(Size::Pixels(5.into())));
804 direct_common_props
805 .borrow()
806 .padding_y
807 .set(Some(Size::Pixels(10.into())));
808
809 let node_ctx = direct_node.get_node_context(&context);
810 sync_content_autosize(&direct_node, &node_ctx, true);
811
812 assert_eq!(direct_node.measured_size.get(), Some((60.0, 80.0)));
813 }
814
815 #[test]
816 fn subtree_layout_hull_includes_trailing_padding_for_ancestor_measurement() {
817 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
818 let direct: Rc<dyn InstanceNode> =
819 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
820 let root_component =
821 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
822 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
823 let root = ExpandedNode::initialize_root(root_component, &context);
824
825 root.recurse_update(&context);
826 let direct_node = root.children.get().first().cloned().unwrap();
827 let child = direct_node.children.get().first().cloned().unwrap();
828 child.set_measured_size(50.0, 60.0);
829 let direct_common_props = direct_node.get_common_properties();
830 direct_common_props
831 .borrow()
832 .padding_x
833 .set(Some(Size::Pixels(5.into())));
834 direct_common_props
835 .borrow()
836 .padding_y
837 .set(Some(Size::Pixels(10.into())));
838
839 root.recurse_update(&context);
840 context.drain_node_effects();
841
842 let hull = direct_node.subtree_layout_hull.get();
843
844 assert_eq!(hull.x_range(), Some((5.0, 60.0)));
845 assert_eq!(hull.y_range(), Some((10.0, 80.0)));
846 assert_eq!(hull.forward_extent_x(), Some(60.0));
847 assert_eq!(hull.forward_extent_y(), Some(80.0));
848 }
849
850 #[test]
851 fn subtree_layout_hull_solves_percent_padding_from_content_hull() {
852 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
853 let direct: Rc<dyn InstanceNode> =
854 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
855 let root_component =
856 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
857 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
858 let root = ExpandedNode::initialize_root(root_component, &context);
859
860 root.recurse_update(&context);
861 let direct_node = root.children.get().first().cloned().unwrap();
862 let child = direct_node.children.get().first().cloned().unwrap();
863 child.set_measured_size(60.0, 80.0);
864 direct_node.set_measured_size(333.0, 333.0);
865 let direct_common_props = direct_node.get_common_properties();
866 direct_common_props
867 .borrow()
868 .padding_x
869 .set(Some(Size::Percent(20.into())));
870 direct_common_props
871 .borrow()
872 .padding_y
873 .set(Some(Size::Percent(10.into())));
874
875 root.recurse_update(&context);
876 context.drain_node_effects();
877
878 let hull = direct_node.subtree_layout_hull.get();
879
880 assert_eq!(hull.forward_extent_x(), Some(100.0));
881 assert_eq!(hull.forward_extent_y(), Some(100.0));
882 assert!((hull.x_range().unwrap().0 - 20.0).abs() < 1e-9);
883 assert!((hull.y_range().unwrap().0 - 10.0).abs() < 1e-9);
884 }
885
886 #[test]
887 fn sync_content_autosize_solves_percent_padding() {
888 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
889 let direct: Rc<dyn InstanceNode> =
890 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
891 let root_component =
892 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
893 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
894 let root = ExpandedNode::initialize_root(root_component, &context);
895
896 root.recurse_update(&context);
897 let direct_node = root.children.get().first().cloned().unwrap();
898 let child = direct_node.children.get().first().cloned().unwrap();
899 child.set_measured_size(60.0, 80.0);
900 let direct_common_props = direct_node.get_common_properties();
901 direct_common_props
902 .borrow()
903 .padding_x
904 .set(Some(Size::Percent(20.into())));
905 direct_common_props
906 .borrow()
907 .padding_y
908 .set(Some(Size::Percent(10.into())));
909
910 let node_ctx = direct_node.get_node_context(&context);
911 sync_content_autosize(&direct_node, &node_ctx, true);
912
913 assert_eq!(direct_node.measured_size.get(), Some((100.0, 100.0)));
914 }
915
916 #[test]
917 fn sync_content_autosize_ignores_breakout_children() {
918 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
919 let direct: Rc<dyn InstanceNode> =
920 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
921 let root_component =
922 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
923 let context = Rc::new(crate::test_support::runtime_context(test_globals()));
924 let root = ExpandedNode::initialize_root(root_component, &context);
925
926 root.recurse_update(&context);
927 let direct_node = root.children.get().first().cloned().unwrap();
928 let child = direct_node.children.get().first().cloned().unwrap();
929 child.set_measured_size(50.0, 60.0);
930 let child_common_props = child.get_common_properties();
931 child_common_props
932 .borrow()
933 .layout_role
934 .set(Some(LayoutRole::Breakout));
935
936 let node_ctx = direct_node.get_node_context(&context);
937 sync_content_autosize(&direct_node, &node_ctx, true);
938
939 assert_eq!(direct_node.measured_size.get(), Some((0.0, 0.0)));
940 }
941}