1use crate::api::math::Transform2;
19use crate::api::{borrow, NodeContext, Property, 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(Property::untyped)
280 .collect::<Vec<_>>();
281
282 let measurement_name = format!("{listener_name} placed measurement");
283 let measurement = Property::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(Property::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(Property::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_changed.untyped()],
389 &rebind_name,
390 ));
391 ctx.runtime_context
392 .register_expanded_node_effect_property_named(
393 expanded_node,
394 &expanded_node.content_measurement_rebind_listener,
395 &rebind_name,
396 );
397}
398
399#[derive(Clone, Copy, Debug, PartialEq, Eq)]
411pub enum ReceivedChildrenSource {
412 Owned,
413 Projected,
414}
415
416#[derive(Clone, Copy, Debug, Default, PartialEq)]
422pub struct ContainerFrame {
423 pub transform: Transform2<NodeLocal, NodeLocal>,
424 pub bounds: (f64, f64),
425}
426
427impl Interpolatable for ContainerFrame {
428 fn interpolate(&self, other: &Self, t: f64) -> Self {
429 Self {
430 transform: self.transform.interpolate(&other.transform, t),
431 bounds: self.bounds.interpolate(&other.bounds, t),
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use crate::api::math::{Transform2, Vector2};
439 use crate::api::{CommonProperties, Layer, LayoutRole, Size};
440 use crate::{
441 bind_content_measurement_effect, measured_size_needs_update, sync_content_autosize,
442 sync_content_autosize_with_axes, BaseInstance, ComponentInstance, ContainerFrame,
443 ContentMeasurementGeometry, ExpandedNode, Globals, InstanceFlags, InstanceNode,
444 InstantiationArgs, RouteLocation, RuntimeContext, RuntimePropertiesStackFrame,
445 TransformAndBounds,
446 };
447 use pax_runtime_api::pax_value::PaxAny;
448 use pax_runtime_api::{Platform, Property, TargetInfo, OS};
449 use std::cell::{Cell, RefCell};
450 use std::fmt;
451 use std::rc::Rc;
452
453 fn test_globals() -> Globals {
454 Globals {
455 elapsed_frames: Property::new(0),
456 elapsed_millis: Property::new(0),
457 viewport: Property::new(TransformAndBounds {
458 transform: Transform2::identity(),
459 bounds: (100.0, 100.0),
460 }),
461 gyro: Property::new(Default::default()),
462 accel: Property::new(Default::default()),
463 route_location: Property::new(RouteLocation::root()),
464 browser_allows_scroller_vector_layers: Property::new(true),
465 browser_allows_nested_scroller_vector_layers: Property::new(true),
466 platform: Platform::Unknown,
467 os: OS::Unknown,
468 target: TargetInfo::new(Platform::Unknown, OS::Unknown),
469 get_elapsed_millis: Rc::new(|| 0),
470 }
471 }
472
473 fn default_properties_factory() -> Box<
474 dyn Fn(
475 Rc<RuntimePropertiesStackFrame>,
476 Option<Rc<ExpandedNode>>,
477 ) -> Option<Rc<RefCell<PaxAny>>>,
478 > {
479 Box::new(|_, expanded_node| {
480 expanded_node
481 .is_none()
482 .then(|| Rc::new(RefCell::new(PaxAny::Builtin(Default::default()))))
483 })
484 }
485
486 fn default_common_properties_factory() -> Box<
487 dyn Fn(
488 Rc<RuntimePropertiesStackFrame>,
489 Option<Rc<ExpandedNode>>,
490 ) -> Option<Rc<RefCell<CommonProperties>>>,
491 > {
492 Box::new(|_, expanded_node| {
493 expanded_node
494 .is_none()
495 .then(|| Rc::new(RefCell::new(CommonProperties::default())))
496 })
497 }
498
499 fn component_args(
500 template: Option<Vec<Rc<dyn InstanceNode>>>,
501 children: Option<Vec<Rc<dyn InstanceNode>>>,
502 ) -> InstantiationArgs {
503 InstantiationArgs {
504 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
505 default_common_properties_factory(),
506 ),
507 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
508 handler_registry: None,
509 children: children.map(RefCell::new),
510 component_template: template.map(RefCell::new),
511 component_settings: None,
512 template_node_identifier: None,
513 template_node_type_id: None,
514 template_node_selector_info: None,
515 transition_config: Default::default(),
516 properties_scope: crate::PropertiesScopeInit::None,
517 }
518 }
519
520 fn direct_node_args(children: Vec<Rc<dyn InstanceNode>>) -> InstantiationArgs {
521 InstantiationArgs {
522 prototypical_common_properties: crate::CommonPropertiesInit::Factory(
523 default_common_properties_factory(),
524 ),
525 prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
526 handler_registry: None,
527 children: Some(RefCell::new(children)),
528 component_template: None,
529 component_settings: None,
530 template_node_identifier: None,
531 template_node_type_id: None,
532 template_node_selector_info: None,
533 transition_config: Default::default(),
534 properties_scope: crate::PropertiesScopeInit::None,
535 }
536 }
537
538 struct TestDirectNode {
539 base: BaseInstance,
540 }
541
542 impl InstanceNode for TestDirectNode {
543 fn instantiate(args: InstantiationArgs) -> Rc<Self>
544 where
545 Self: Sized,
546 {
547 Rc::new(Self {
548 base: BaseInstance::new(
549 args,
550 InstanceFlags {
551 invisible_to_slot: false,
552 invisible_to_raycasting: true,
553 layer: Layer::DontCare,
554 is_component: false,
555 is_slot: false,
556 },
557 ),
558 })
559 }
560
561 fn resolve_debug(
562 &self,
563 f: &mut fmt::Formatter,
564 _expanded_node: Option<&ExpandedNode>,
565 ) -> fmt::Result {
566 f.debug_struct("TestDirectNode").finish()
567 }
568
569 fn base(&self) -> &BaseInstance {
570 &self.base
571 }
572 }
573
574 #[test]
575 fn owned_received_children_reflect_active_children() {
576 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
577 let direct: Rc<dyn InstanceNode> =
578 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
579 let root_component =
580 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
581 let context = Rc::new(RuntimeContext::new(test_globals()));
582 let root = ExpandedNode::initialize_root(root_component, &context);
583
584 root.recurse_update(&context);
585 let direct_node = root.children.get().first().cloned().unwrap();
586 let node_ctx = direct_node.get_node_context(&context);
587 let received_children = node_ctx.received_children.get();
588
589 assert_eq!(received_children.len(), 1);
590 assert_eq!(received_children[0].id, direct_node.children.get()[0].id);
591 assert_eq!(node_ctx.received_children_count.get(), 1);
592 assert!(node_ctx.retained_received_children.get().is_empty());
593 }
594
595 #[test]
596 fn projected_received_children_reflect_flattened_projected_children() {
597 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
598 let slotted_component: Rc<dyn InstanceNode> =
599 ComponentInstance::instantiate(component_args(Some(Vec::new()), Some(vec![leaf])));
600 let root_component = ComponentInstance::instantiate(component_args(
601 Some(vec![Rc::clone(&slotted_component)]),
602 None,
603 ));
604 let context = Rc::new(RuntimeContext::new(test_globals()));
605 let root = ExpandedNode::initialize_root(root_component, &context);
606
607 root.recurse_update(&context);
608 let component_node = root.children.get().first().cloned().unwrap();
609 let node_ctx = component_node.get_node_context(&context);
610 let received_children = node_ctx.received_children.get();
611
612 assert_eq!(received_children.len(), 1);
613 assert_eq!(
614 received_children[0].id,
615 component_node
616 .expanded_and_flattened_projected_children
617 .get()[0]
618 .id
619 );
620 assert_eq!(node_ctx.received_children_count.get(), 1);
621 assert!(node_ctx.retained_received_children.get().is_empty());
622 }
623
624 #[test]
625 fn sync_content_autosize_collapses_empty_content() {
626 let direct: Rc<dyn InstanceNode> =
627 TestDirectNode::instantiate(direct_node_args(Vec::new()));
628 let root_component =
629 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
630 let context = Rc::new(RuntimeContext::new(test_globals()));
631 let root = ExpandedNode::initialize_root(root_component, &context);
632
633 root.recurse_update(&context);
634 let direct_node = root.children.get().first().cloned().unwrap();
635 let node_ctx = direct_node.get_node_context(&context);
636
637 sync_content_autosize(&direct_node, &node_ctx, true);
638
639 assert_eq!(direct_node.measured_size.get(), Some((0.0, 0.0)));
640 }
641
642 #[test]
643 fn sync_content_autosize_with_axes_keeps_unmanaged_axis_at_layout_bounds() {
644 let direct: Rc<dyn InstanceNode> =
645 TestDirectNode::instantiate(direct_node_args(Vec::new()));
646 let root_component =
647 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
648 let context = Rc::new(RuntimeContext::new(test_globals()));
649 let root = ExpandedNode::initialize_root(root_component, &context);
650
651 root.recurse_update(&context);
652 let direct_node = root.children.get().first().cloned().unwrap();
653 let node_ctx = direct_node.get_node_context(&context);
654
655 sync_content_autosize_with_axes(&direct_node, &node_ctx, false, true);
656
657 assert_eq!(direct_node.measured_size.get(), Some((100.0, 0.0)));
658 }
659
660 #[test]
661 fn measured_size_update_ignores_float_jitter() {
662 assert!(!measured_size_needs_update(
663 Some((836.8000000000001, 228.6585365853658)),
664 (836.8000000000001, 228.65853658536582),
665 ));
666 assert!(measured_size_needs_update(
667 Some((836.8000000000001, 228.6585365853658)),
668 (836.8000000000001, 228.6585375853658),
669 ));
670 assert!(measured_size_needs_update(None, (0.0, 0.0)));
671 }
672
673 #[test]
674 fn placed_measurement_ignores_ancestor_translation_but_observes_child_position() {
675 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
676 let direct: Rc<dyn InstanceNode> =
677 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
678 let root_component =
679 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
680 let context = Rc::new(RuntimeContext::new(test_globals()));
681 let root = ExpandedNode::initialize_root(root_component, &context);
682 root.recurse_update(&context);
683 let direct_node = root.children.get().first().cloned().unwrap();
684 let child = direct_node.children.get().first().cloned().unwrap();
685 let child_common = child.get_common_properties();
686 child_common
687 .borrow()
688 .width
689 .set(Some(Size::Pixels(20.into())));
690 child_common
691 .borrow()
692 .height
693 .set(Some(Size::Pixels(20.into())));
694 child_common.borrow().x.set(Some(Size::Pixels(0.into())));
695 child_common.borrow().y.set(Some(Size::Pixels(0.into())));
696 context.drain_node_effects();
697
698 let effect_runs = Rc::new(Cell::new(0));
699 let effect_runs_for_effect = Rc::clone(&effect_runs);
700 let node_ctx = direct_node.get_node_context(&context);
701 bind_content_measurement_effect(
702 &direct_node,
703 &node_ctx,
704 "placed measurement test",
705 ContentMeasurementGeometry::Placed,
706 &[],
707 move |_, _| effect_runs_for_effect.set(effect_runs_for_effect.get() + 1),
708 );
709 context.drain_node_effects();
710 let baseline = effect_runs.get();
711
712 let viewport = context.globals().viewport;
713 viewport.set(TransformAndBounds {
714 transform: Transform2::translate(Vector2::new(25.0, 15.0)),
715 bounds: (100.0, 100.0),
716 });
717 context.drain_node_effects();
718 assert_eq!(effect_runs.get(), baseline);
719
720 child_common.borrow().x.set(Some(Size::Pixels(12.into())));
721 context.drain_node_effects();
722 assert_eq!(effect_runs.get(), baseline + 1);
723 }
724
725 #[test]
726 fn intrinsic_measurement_ignores_assigned_frame_but_observes_child_size() {
727 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
728 let direct: Rc<dyn InstanceNode> =
729 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
730 let root_component =
731 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
732 let context = Rc::new(RuntimeContext::new(test_globals()));
733 let root = ExpandedNode::initialize_root(root_component, &context);
734 root.recurse_update(&context);
735 let direct_node = root.children.get().first().cloned().unwrap();
736 let child = direct_node.children.get().first().cloned().unwrap();
737 let child_common = child.get_common_properties();
738 child_common
739 .borrow()
740 .width
741 .set(Some(Size::Pixels(20.into())));
742 child_common
743 .borrow()
744 .height
745 .set(Some(Size::Pixels(20.into())));
746 child_common.borrow().x.set(Some(Size::Pixels(0.into())));
747 child_common.borrow().y.set(Some(Size::Pixels(0.into())));
748 context.drain_node_effects();
749
750 let effect_runs = Rc::new(Cell::new(0));
751 let effect_runs_for_effect = Rc::clone(&effect_runs);
752 let node_ctx = direct_node.get_node_context(&context);
753 bind_content_measurement_effect(
754 &direct_node,
755 &node_ctx,
756 "intrinsic measurement test",
757 ContentMeasurementGeometry::Intrinsic,
758 &[],
759 move |_, _| effect_runs_for_effect.set(effect_runs_for_effect.get() + 1),
760 );
761 context.drain_node_effects();
762 let baseline = effect_runs.get();
763 let baseline_hull = child.subtree_layout_hull.get();
764
765 child.container_frame.set(Some(ContainerFrame {
766 transform: Transform2::translate(Vector2::new(20.0, 10.0)),
767 bounds: (100.0, 100.0),
768 }));
769 context.drain_node_effects();
770 assert_eq!(effect_runs.get(), baseline);
771
772 child_common
773 .borrow()
774 .width
775 .set(Some(Size::Pixels(40.into())));
776 context.drain_node_effects();
777 assert_ne!(child.subtree_layout_hull.get(), baseline_hull);
778 assert_eq!(effect_runs.get(), baseline + 1);
779 }
780
781 #[test]
782 fn sync_content_autosize_includes_padding() {
783 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
784 let direct: Rc<dyn InstanceNode> =
785 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
786 let root_component =
787 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
788 let context = Rc::new(RuntimeContext::new(test_globals()));
789 let root = ExpandedNode::initialize_root(root_component, &context);
790
791 root.recurse_update(&context);
792 let direct_node = root.children.get().first().cloned().unwrap();
793 let child = direct_node.children.get().first().cloned().unwrap();
794 child.set_measured_size(50.0, 60.0);
795 let direct_common_props = direct_node.get_common_properties();
796 direct_common_props
797 .borrow()
798 .padding_x
799 .set(Some(Size::Pixels(5.into())));
800 direct_common_props
801 .borrow()
802 .padding_y
803 .set(Some(Size::Pixels(10.into())));
804
805 let node_ctx = direct_node.get_node_context(&context);
806 sync_content_autosize(&direct_node, &node_ctx, true);
807
808 assert_eq!(direct_node.measured_size.get(), Some((60.0, 80.0)));
809 }
810
811 #[test]
812 fn subtree_layout_hull_includes_trailing_padding_for_ancestor_measurement() {
813 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
814 let direct: Rc<dyn InstanceNode> =
815 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
816 let root_component =
817 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
818 let context = Rc::new(RuntimeContext::new(test_globals()));
819 let root = ExpandedNode::initialize_root(root_component, &context);
820
821 root.recurse_update(&context);
822 let direct_node = root.children.get().first().cloned().unwrap();
823 let child = direct_node.children.get().first().cloned().unwrap();
824 child.set_measured_size(50.0, 60.0);
825 let direct_common_props = direct_node.get_common_properties();
826 direct_common_props
827 .borrow()
828 .padding_x
829 .set(Some(Size::Pixels(5.into())));
830 direct_common_props
831 .borrow()
832 .padding_y
833 .set(Some(Size::Pixels(10.into())));
834
835 root.recurse_update(&context);
836 context.drain_node_effects();
837
838 let hull = direct_node.subtree_layout_hull.get();
839
840 assert_eq!(hull.x_range(), Some((5.0, 60.0)));
841 assert_eq!(hull.y_range(), Some((10.0, 80.0)));
842 assert_eq!(hull.forward_extent_x(), Some(60.0));
843 assert_eq!(hull.forward_extent_y(), Some(80.0));
844 }
845
846 #[test]
847 fn subtree_layout_hull_solves_percent_padding_from_content_hull() {
848 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
849 let direct: Rc<dyn InstanceNode> =
850 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
851 let root_component =
852 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
853 let context = Rc::new(RuntimeContext::new(test_globals()));
854 let root = ExpandedNode::initialize_root(root_component, &context);
855
856 root.recurse_update(&context);
857 let direct_node = root.children.get().first().cloned().unwrap();
858 let child = direct_node.children.get().first().cloned().unwrap();
859 child.set_measured_size(60.0, 80.0);
860 direct_node.set_measured_size(333.0, 333.0);
861 let direct_common_props = direct_node.get_common_properties();
862 direct_common_props
863 .borrow()
864 .padding_x
865 .set(Some(Size::Percent(20.into())));
866 direct_common_props
867 .borrow()
868 .padding_y
869 .set(Some(Size::Percent(10.into())));
870
871 root.recurse_update(&context);
872 context.drain_node_effects();
873
874 let hull = direct_node.subtree_layout_hull.get();
875
876 assert_eq!(hull.forward_extent_x(), Some(100.0));
877 assert_eq!(hull.forward_extent_y(), Some(100.0));
878 assert!((hull.x_range().unwrap().0 - 20.0).abs() < 1e-9);
879 assert!((hull.y_range().unwrap().0 - 10.0).abs() < 1e-9);
880 }
881
882 #[test]
883 fn sync_content_autosize_solves_percent_padding() {
884 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
885 let direct: Rc<dyn InstanceNode> =
886 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
887 let root_component =
888 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
889 let context = Rc::new(RuntimeContext::new(test_globals()));
890 let root = ExpandedNode::initialize_root(root_component, &context);
891
892 root.recurse_update(&context);
893 let direct_node = root.children.get().first().cloned().unwrap();
894 let child = direct_node.children.get().first().cloned().unwrap();
895 child.set_measured_size(60.0, 80.0);
896 let direct_common_props = direct_node.get_common_properties();
897 direct_common_props
898 .borrow()
899 .padding_x
900 .set(Some(Size::Percent(20.into())));
901 direct_common_props
902 .borrow()
903 .padding_y
904 .set(Some(Size::Percent(10.into())));
905
906 let node_ctx = direct_node.get_node_context(&context);
907 sync_content_autosize(&direct_node, &node_ctx, true);
908
909 assert_eq!(direct_node.measured_size.get(), Some((100.0, 100.0)));
910 }
911
912 #[test]
913 fn sync_content_autosize_ignores_breakout_children() {
914 let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
915 let direct: Rc<dyn InstanceNode> =
916 TestDirectNode::instantiate(direct_node_args(vec![leaf]));
917 let root_component =
918 ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
919 let context = Rc::new(RuntimeContext::new(test_globals()));
920 let root = ExpandedNode::initialize_root(root_component, &context);
921
922 root.recurse_update(&context);
923 let direct_node = root.children.get().first().cloned().unwrap();
924 let child = direct_node.children.get().first().cloned().unwrap();
925 child.set_measured_size(50.0, 60.0);
926 let child_common_props = child.get_common_properties();
927 child_common_props
928 .borrow()
929 .layout_role
930 .set(Some(LayoutRole::Breakout));
931
932 let node_ctx = direct_node.get_node_context(&context);
933 sync_content_autosize(&direct_node, &node_ctx, true);
934
935 assert_eq!(direct_node.measured_size.get(), Some((0.0, 0.0)));
936 }
937}