use crate::api::math::Transform2;
use crate::api::{borrow, LocalProperty, NodeContext, Size};
use crate::node_interface::NodeLocal;
use crate::{
apply_padding_frame, layout_hulls_equivalent, project_child_layout_hull_to_parent_space,
resolve_padded_autosize_axis, ExpandedNode, LayoutHull,
};
use pax_runtime_api::{properties::UntypedProperty, Interpolatable};
use std::rc::Rc;
const MEASURED_SIZE_EPSILON: f64 = 1e-9;
pub trait Container {
fn bind_container(&self, _ctx: &NodeContext) {}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[doc(hidden)]
pub enum ContentMeasurementGeometry {
Intrinsic,
Placed,
}
pub fn measure_content_children_layout_hull(ctx: &NodeContext) -> LayoutHull {
let content_children = ctx.received_children.get();
if content_children.is_empty() {
return LayoutHull::from_axis_ranges(Some((0.0, 0.0)), Some((0.0, 0.0)));
}
let (padding_x, padding_y) = node_padding(ctx);
let content_transform_and_bounds =
apply_padding_frame(ctx.node_transform_and_bounds, padding_x, padding_y);
let mut hull: Option<LayoutHull> = None;
for child in content_children.iter() {
if child.is_layout_breakout() {
continue;
}
let projected_hull = project_child_layout_hull_to_parent_space(
content_transform_and_bounds,
child.transform_and_bounds.get(),
child.subtree_layout_hull.get(),
);
hull = Some(match hull {
Some(existing) => existing.union(projected_hull),
None => projected_hull,
});
}
hull.unwrap_or_else(|| LayoutHull::from_axis_ranges(Some((0.0, 0.0)), Some((0.0, 0.0))))
}
pub fn measure_content_children_forward_extents(ctx: &NodeContext) -> (Option<f64>, Option<f64>) {
let hull = measure_content_children_layout_hull(ctx);
(hull.forward_extent_x(), hull.forward_extent_y())
}
pub fn resolve_axis_autosize(
autosize: bool,
axis_override: Option<bool>,
default_when_enabled: bool,
) -> bool {
axis_override.unwrap_or(autosize && default_when_enabled)
}
pub fn resolve_content_autosize_measurement(
ctx: &NodeContext,
width_explicit: bool,
height_explicit: bool,
) -> Option<(f64, f64)> {
resolve_content_autosize_measurement_with_axes(ctx, width_explicit, height_explicit, true, true)
}
pub fn resolve_content_autosize_measurement_with_axes(
ctx: &NodeContext,
width_explicit: bool,
height_explicit: bool,
autosize_width: bool,
autosize_height: bool,
) -> Option<(f64, f64)> {
if width_explicit && height_explicit {
return None;
}
if !autosize_width && !autosize_height {
return None;
}
let bounds = ctx.bounds_self.get();
let (content_width, content_height) = measure_content_children_forward_extents(ctx);
let (padding_x, padding_y) = node_padding(ctx);
Some((
if width_explicit || !autosize_width {
bounds.0
} else {
resolve_padded_autosize_axis(content_width?, padding_x)?
},
if height_explicit || !autosize_height {
bounds.1
} else {
resolve_padded_autosize_axis(content_height?, padding_y)?
},
))
}
fn node_padding(ctx: &NodeContext) -> (Option<Size>, Option<Size>) {
let Some(node) = ctx.expanded_node.upgrade() else {
return (None, None);
};
let common_props = node.get_common_properties();
let common_props = borrow!(common_props);
(common_props.padding_x.get(), common_props.padding_y.get())
}
pub fn sync_content_autosize(expanded_node: &Rc<ExpandedNode>, ctx: &NodeContext, enabled: bool) {
sync_content_autosize_with_axes(expanded_node, ctx, enabled, enabled);
}
pub fn sync_content_autosize_with_axes(
expanded_node: &Rc<ExpandedNode>,
ctx: &NodeContext,
autosize_width: bool,
autosize_height: bool,
) {
let measured_size = if autosize_width || autosize_height {
let common_props = expanded_node.get_common_properties();
let common_props = borrow!(common_props);
let width_explicit = common_props.width.get().is_some();
let height_explicit = common_props.height.get().is_some();
drop(common_props);
resolve_content_autosize_measurement_with_axes(
ctx,
width_explicit,
height_explicit,
autosize_width,
autosize_height,
)
} else {
None
};
match measured_size {
Some(measured_size) => {
let previous = expanded_node.measured_size.get();
if measured_size_needs_update(previous, measured_size) {
expanded_node.set_measured_size(measured_size.0, measured_size.1);
}
}
None => {
if expanded_node.measured_size.get().is_some() {
expanded_node.measured_size.set(None);
}
}
}
}
pub fn measured_size_needs_update(previous: Option<(f64, f64)>, next: (f64, f64)) -> bool {
match previous {
Some((previous_width, previous_height)) => {
axis_needs_update(previous_width, next.0) || axis_needs_update(previous_height, next.1)
}
None => true,
}
}
fn axis_needs_update(previous: f64, next: f64) -> bool {
if previous == next {
return false;
}
if !previous.is_finite() || !next.is_finite() {
return previous.to_bits() != next.to_bits();
}
(previous - next).abs() > MEASURED_SIZE_EPSILON
}
fn rebind_content_measurement_effect<F>(
expanded_node: &Rc<ExpandedNode>,
runtime_context: &Rc<crate::RuntimeContext>,
listener_name: &'static str,
geometry: ContentMeasurementGeometry,
extra_deps: &[UntypedProperty],
effect: F,
) where
F: Fn(&Rc<ExpandedNode>, &NodeContext) + Clone + 'static,
{
let common_props = expanded_node.get_common_properties();
let (width_prop, height_prop, padding_x_prop, padding_y_prop) = {
let common_props = borrow!(common_props);
(
common_props.width.clone(),
common_props.height.clone(),
common_props.padding_x.clone(),
common_props.padding_y.clone(),
)
};
let node_ctx = expanded_node.get_node_context(runtime_context);
let mut deps = vec![
node_ctx.bounds_self.untyped(),
width_prop.untyped(),
height_prop.untyped(),
padding_x_prop.untyped(),
padding_y_prop.untyped(),
];
deps.extend(extra_deps.iter().cloned());
let placed_measurement = if geometry == ContentMeasurementGeometry::Placed {
let node_transform_and_bounds = expanded_node.transform_and_bounds.clone();
let mut projected_child_hulls = Vec::new();
for child in node_ctx.received_children.get().iter() {
let child_cp = child.get_common_properties();
let child_layout_role = borrow!(child_cp).layout_role.clone();
let projection_name = format!(
"{listener_name} projected child layout hull (parent id: {}, child id: {})",
expanded_node.id.0, child.id.0
);
projected_child_hulls.push(crate::projected_child_layout_hull_property(
node_transform_and_bounds.clone(),
padding_x_prop.clone(),
padding_y_prop.clone(),
child.transform_and_bounds.clone(),
child.subtree_layout_hull.clone(),
child_layout_role,
&projection_name,
));
}
let measurement_deps = projected_child_hulls
.iter()
.map(LocalProperty::untyped)
.collect::<Vec<_>>();
let measurement_name = format!("{listener_name} placed measurement");
let measurement = LocalProperty::computed_with_cutoff_and_name(
move || {
if projected_child_hulls.is_empty() {
return LayoutHull::from_axis_ranges(Some((0.0, 0.0)), Some((0.0, 0.0)));
}
let hull = projected_child_hulls
.iter()
.fold(LayoutHull::default(), |hull, child| hull.union(child.get()));
if hull.x_range().is_none() && hull.y_range().is_none() {
LayoutHull::from_axis_ranges(Some((0.0, 0.0)), Some((0.0, 0.0)))
} else {
hull
}
},
&measurement_deps,
layout_hulls_equivalent,
&measurement_name,
);
deps.push(measurement.untyped());
Some(measurement)
} else {
for child in node_ctx.received_children.get().iter() {
let child_cp = child.get_common_properties();
deps.push(borrow!(child_cp).layout_role.untyped());
deps.push(child.subtree_layout_hull.untyped());
}
None
};
let weak_node = Rc::downgrade(expanded_node);
let runtime_context = Rc::clone(runtime_context);
let bounds_self = node_ctx.bounds_self.clone();
expanded_node
.content_measurement_listener
.replace_with(LocalProperty::computed_with_name(
move || {
let _ = bounds_self.get();
if let Some(measurement) = &placed_measurement {
let _ = measurement.get();
}
let Some(node) = weak_node.upgrade() else {
return;
};
let node_ctx = node.get_node_context(&runtime_context);
effect(&node, &node_ctx);
},
&deps,
listener_name,
));
}
pub fn bind_content_measurement_effect<F>(
expanded_node: &Rc<ExpandedNode>,
ctx: &NodeContext,
listener_name: &'static str,
geometry: ContentMeasurementGeometry,
extra_deps: &[UntypedProperty],
effect: F,
) where
F: Fn(&Rc<ExpandedNode>, &NodeContext) + Clone + 'static,
{
if expanded_node.content_measurement_bound.replace(true) {
return;
}
rebind_content_measurement_effect(
expanded_node,
&ctx.runtime_context,
listener_name,
geometry,
extra_deps,
effect.clone(),
);
ctx.runtime_context
.register_expanded_node_effect_property_named(
expanded_node,
&expanded_node.content_measurement_listener,
listener_name,
);
let weak_node = Rc::downgrade(expanded_node);
let runtime_context = Rc::clone(&ctx.runtime_context);
let extra_deps = extra_deps.to_vec();
let rebind_name = format!("{listener_name} rebind");
expanded_node
.content_measurement_rebind_listener
.replace_with(LocalProperty::computed_with_name(
move || {
let Some(node) = weak_node.upgrade() else {
return;
};
rebind_content_measurement_effect(
&node,
&runtime_context,
listener_name,
geometry,
&extra_deps,
effect.clone(),
);
},
&[ctx.received_children.untyped()],
&rebind_name,
));
ctx.runtime_context
.register_expanded_node_effect_property_named(
expanded_node,
&expanded_node.content_measurement_rebind_listener,
&rebind_name,
);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReceivedChildrenSource {
Owned,
Projected,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ContainerFrame {
pub transform: Transform2<NodeLocal, NodeLocal>,
pub bounds: (f64, f64),
}
impl Interpolatable for ContainerFrame {
fn interpolate(&self, other: &Self, t: f64) -> Self {
Self {
transform: self.transform.interpolate(&other.transform, t),
bounds: self.bounds.interpolate(&other.bounds, t),
}
}
}
#[cfg(test)]
mod tests {
use crate::api::math::{Transform2, Vector2};
use crate::api::{CommonProperties, Layer, LayoutRole, Size};
use crate::{
bind_content_measurement_effect, measured_size_needs_update, sync_content_autosize,
sync_content_autosize_with_axes, BaseInstance, ComponentInstance, ContainerFrame,
ContentMeasurementGeometry, ExpandedNode, Globals, InstanceFlags, InstanceNode,
InstantiationArgs, RouteLocation, RuntimePropertiesStackFrame, TransformAndBounds,
};
use pax_runtime_api::pax_value::PaxAny;
use pax_runtime_api::{LocalProperty, Platform, TargetInfo, OS};
use std::cell::{Cell, RefCell};
use std::fmt;
use std::rc::Rc;
fn test_globals() -> Globals {
Globals {
elapsed_frames: LocalProperty::new(0),
elapsed_millis: LocalProperty::new(0),
viewport: LocalProperty::new(TransformAndBounds {
transform: Transform2::identity(),
bounds: (100.0, 100.0),
}),
gyro: LocalProperty::new(Default::default()),
accel: LocalProperty::new(Default::default()),
route_location: LocalProperty::new(RouteLocation::root()),
browser_allows_scroller_vector_layers: LocalProperty::new(true),
browser_allows_nested_scroller_vector_layers: LocalProperty::new(true),
platform: Platform::Unknown,
os: OS::Unknown,
target: TargetInfo::new(Platform::Unknown, OS::Unknown),
#[cfg(feature = "designtime")]
designtime: crate::test_support::designtime(),
get_elapsed_millis: Rc::new(|| 0),
}
}
fn default_properties_factory() -> Box<
dyn Fn(
Rc<RuntimePropertiesStackFrame>,
Option<Rc<ExpandedNode>>,
) -> Option<Rc<RefCell<PaxAny>>>,
> {
Box::new(|_, expanded_node| {
expanded_node
.is_none()
.then(|| Rc::new(RefCell::new(PaxAny::Builtin(Default::default()))))
})
}
fn default_common_properties_factory() -> Box<
dyn Fn(
Rc<RuntimePropertiesStackFrame>,
Option<Rc<ExpandedNode>>,
) -> Option<Rc<RefCell<CommonProperties>>>,
> {
Box::new(|_, expanded_node| {
expanded_node
.is_none()
.then(|| Rc::new(RefCell::new(CommonProperties::default())))
})
}
fn component_args(
template: Option<Vec<Rc<dyn InstanceNode>>>,
children: Option<Vec<Rc<dyn InstanceNode>>>,
) -> InstantiationArgs {
InstantiationArgs {
prototypical_common_properties: crate::CommonPropertiesInit::Factory(
default_common_properties_factory(),
),
prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
handler_registry: None,
children: children.map(RefCell::new),
component_template: template.map(RefCell::new),
component_settings: None,
template_node_identifier: None,
template_node_type_id: None,
template_node_selector_info: None,
transition_config: Default::default(),
properties_scope: crate::PropertiesScopeInit::None,
}
}
fn direct_node_args(children: Vec<Rc<dyn InstanceNode>>) -> InstantiationArgs {
InstantiationArgs {
prototypical_common_properties: crate::CommonPropertiesInit::Factory(
default_common_properties_factory(),
),
prototypical_properties: crate::PropertiesInit::Factory(default_properties_factory()),
handler_registry: None,
children: Some(RefCell::new(children)),
component_template: None,
component_settings: None,
template_node_identifier: None,
template_node_type_id: None,
template_node_selector_info: None,
transition_config: Default::default(),
properties_scope: crate::PropertiesScopeInit::None,
}
}
struct TestDirectNode {
base: BaseInstance,
}
impl InstanceNode for TestDirectNode {
fn instantiate(args: InstantiationArgs) -> Rc<Self>
where
Self: Sized,
{
Rc::new(Self {
base: BaseInstance::new(
args,
InstanceFlags {
invisible_to_slot: false,
invisible_to_raycasting: true,
layer: Layer::DontCare,
is_component: false,
is_slot: false,
},
),
})
}
fn resolve_debug(
&self,
f: &mut fmt::Formatter,
_expanded_node: Option<&ExpandedNode>,
) -> fmt::Result {
f.debug_struct("TestDirectNode").finish()
}
fn base(&self) -> &BaseInstance {
&self.base
}
}
#[test]
fn owned_received_children_reflect_active_children() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(vec![leaf]));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let node_ctx = direct_node.get_node_context(&context);
let received_children = node_ctx.received_children.get();
assert_eq!(received_children.len(), 1);
assert_eq!(received_children[0].id, direct_node.children.get()[0].id);
assert_eq!(node_ctx.received_children_count.get(), 1);
assert!(node_ctx.retained_received_children.get().is_empty());
}
#[test]
fn projected_received_children_reflect_flattened_projected_children() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let slotted_component: Rc<dyn InstanceNode> =
ComponentInstance::instantiate(component_args(Some(Vec::new()), Some(vec![leaf])));
let root_component = ComponentInstance::instantiate(component_args(
Some(vec![Rc::clone(&slotted_component)]),
None,
));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let component_node = root.children.get().first().cloned().unwrap();
let node_ctx = component_node.get_node_context(&context);
let received_children = node_ctx.received_children.get();
assert_eq!(received_children.len(), 1);
assert_eq!(
received_children[0].id,
component_node
.expanded_and_flattened_projected_children
.get()[0]
.id
);
assert_eq!(node_ctx.received_children_count.get(), 1);
assert!(node_ctx.retained_received_children.get().is_empty());
}
#[test]
fn sync_content_autosize_collapses_empty_content() {
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(Vec::new()));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let node_ctx = direct_node.get_node_context(&context);
sync_content_autosize(&direct_node, &node_ctx, true);
assert_eq!(direct_node.measured_size.get(), Some((0.0, 0.0)));
}
#[test]
fn sync_content_autosize_with_axes_keeps_unmanaged_axis_at_layout_bounds() {
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(Vec::new()));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let node_ctx = direct_node.get_node_context(&context);
sync_content_autosize_with_axes(&direct_node, &node_ctx, false, true);
assert_eq!(direct_node.measured_size.get(), Some((100.0, 0.0)));
}
#[test]
fn measured_size_update_ignores_float_jitter() {
assert!(!measured_size_needs_update(
Some((836.8000000000001, 228.6585365853658)),
(836.8000000000001, 228.65853658536582),
));
assert!(measured_size_needs_update(
Some((836.8000000000001, 228.6585365853658)),
(836.8000000000001, 228.6585375853658),
));
assert!(measured_size_needs_update(None, (0.0, 0.0)));
}
#[test]
fn placed_measurement_ignores_ancestor_translation_but_observes_child_position() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(vec![leaf]));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let child = direct_node.children.get().first().cloned().unwrap();
let child_common = child.get_common_properties();
child_common
.borrow()
.width
.set(Some(Size::Pixels(20.into())));
child_common
.borrow()
.height
.set(Some(Size::Pixels(20.into())));
child_common.borrow().x.set(Some(Size::Pixels(0.into())));
child_common.borrow().y.set(Some(Size::Pixels(0.into())));
context.drain_node_effects();
let effect_runs = Rc::new(Cell::new(0));
let effect_runs_for_effect = Rc::clone(&effect_runs);
let node_ctx = direct_node.get_node_context(&context);
bind_content_measurement_effect(
&direct_node,
&node_ctx,
"placed measurement test",
ContentMeasurementGeometry::Placed,
&[],
move |_, _| effect_runs_for_effect.set(effect_runs_for_effect.get() + 1),
);
context.drain_node_effects();
let baseline = effect_runs.get();
let viewport = context.globals().viewport;
viewport.set(TransformAndBounds {
transform: Transform2::translate(Vector2::new(25.0, 15.0)),
bounds: (100.0, 100.0),
});
context.drain_node_effects();
assert_eq!(effect_runs.get(), baseline);
child_common.borrow().x.set(Some(Size::Pixels(12.into())));
context.drain_node_effects();
assert_eq!(effect_runs.get(), baseline + 1);
}
#[test]
fn intrinsic_measurement_ignores_assigned_frame_but_observes_child_size() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(vec![leaf]));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let child = direct_node.children.get().first().cloned().unwrap();
let child_common = child.get_common_properties();
child_common
.borrow()
.width
.set(Some(Size::Pixels(20.into())));
child_common
.borrow()
.height
.set(Some(Size::Pixels(20.into())));
child_common.borrow().x.set(Some(Size::Pixels(0.into())));
child_common.borrow().y.set(Some(Size::Pixels(0.into())));
context.drain_node_effects();
let effect_runs = Rc::new(Cell::new(0));
let effect_runs_for_effect = Rc::clone(&effect_runs);
let node_ctx = direct_node.get_node_context(&context);
bind_content_measurement_effect(
&direct_node,
&node_ctx,
"intrinsic measurement test",
ContentMeasurementGeometry::Intrinsic,
&[],
move |_, _| effect_runs_for_effect.set(effect_runs_for_effect.get() + 1),
);
context.drain_node_effects();
let baseline = effect_runs.get();
let baseline_hull = child.subtree_layout_hull.get();
child.container_frame.set(Some(ContainerFrame {
transform: Transform2::translate(Vector2::new(20.0, 10.0)),
bounds: (100.0, 100.0),
}));
context.drain_node_effects();
assert_eq!(effect_runs.get(), baseline);
child_common
.borrow()
.width
.set(Some(Size::Pixels(40.into())));
context.drain_node_effects();
assert_ne!(child.subtree_layout_hull.get(), baseline_hull);
assert_eq!(effect_runs.get(), baseline + 1);
}
#[test]
fn sync_content_autosize_includes_padding() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(vec![leaf]));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let child = direct_node.children.get().first().cloned().unwrap();
child.set_measured_size(50.0, 60.0);
let direct_common_props = direct_node.get_common_properties();
direct_common_props
.borrow()
.padding_x
.set(Some(Size::Pixels(5.into())));
direct_common_props
.borrow()
.padding_y
.set(Some(Size::Pixels(10.into())));
let node_ctx = direct_node.get_node_context(&context);
sync_content_autosize(&direct_node, &node_ctx, true);
assert_eq!(direct_node.measured_size.get(), Some((60.0, 80.0)));
}
#[test]
fn subtree_layout_hull_includes_trailing_padding_for_ancestor_measurement() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(vec![leaf]));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let child = direct_node.children.get().first().cloned().unwrap();
child.set_measured_size(50.0, 60.0);
let direct_common_props = direct_node.get_common_properties();
direct_common_props
.borrow()
.padding_x
.set(Some(Size::Pixels(5.into())));
direct_common_props
.borrow()
.padding_y
.set(Some(Size::Pixels(10.into())));
root.recurse_update(&context);
context.drain_node_effects();
let hull = direct_node.subtree_layout_hull.get();
assert_eq!(hull.x_range(), Some((5.0, 60.0)));
assert_eq!(hull.y_range(), Some((10.0, 80.0)));
assert_eq!(hull.forward_extent_x(), Some(60.0));
assert_eq!(hull.forward_extent_y(), Some(80.0));
}
#[test]
fn subtree_layout_hull_solves_percent_padding_from_content_hull() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(vec![leaf]));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let child = direct_node.children.get().first().cloned().unwrap();
child.set_measured_size(60.0, 80.0);
direct_node.set_measured_size(333.0, 333.0);
let direct_common_props = direct_node.get_common_properties();
direct_common_props
.borrow()
.padding_x
.set(Some(Size::Percent(20.into())));
direct_common_props
.borrow()
.padding_y
.set(Some(Size::Percent(10.into())));
root.recurse_update(&context);
context.drain_node_effects();
let hull = direct_node.subtree_layout_hull.get();
assert_eq!(hull.forward_extent_x(), Some(100.0));
assert_eq!(hull.forward_extent_y(), Some(100.0));
assert!((hull.x_range().unwrap().0 - 20.0).abs() < 1e-9);
assert!((hull.y_range().unwrap().0 - 10.0).abs() < 1e-9);
}
#[test]
fn sync_content_autosize_solves_percent_padding() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(vec![leaf]));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let child = direct_node.children.get().first().cloned().unwrap();
child.set_measured_size(60.0, 80.0);
let direct_common_props = direct_node.get_common_properties();
direct_common_props
.borrow()
.padding_x
.set(Some(Size::Percent(20.into())));
direct_common_props
.borrow()
.padding_y
.set(Some(Size::Percent(10.into())));
let node_ctx = direct_node.get_node_context(&context);
sync_content_autosize(&direct_node, &node_ctx, true);
assert_eq!(direct_node.measured_size.get(), Some((100.0, 100.0)));
}
#[test]
fn sync_content_autosize_ignores_breakout_children() {
let leaf: Rc<dyn InstanceNode> = TestDirectNode::instantiate(direct_node_args(Vec::new()));
let direct: Rc<dyn InstanceNode> =
TestDirectNode::instantiate(direct_node_args(vec![leaf]));
let root_component =
ComponentInstance::instantiate(component_args(Some(vec![Rc::clone(&direct)]), None));
let context = Rc::new(crate::test_support::runtime_context(test_globals()));
let root = ExpandedNode::initialize_root(root_component, &context);
root.recurse_update(&context);
let direct_node = root.children.get().first().cloned().unwrap();
let child = direct_node.children.get().first().cloned().unwrap();
child.set_measured_size(50.0, 60.0);
let child_common_props = child.get_common_properties();
child_common_props
.borrow()
.layout_role
.set(Some(LayoutRole::Breakout));
let node_ctx = direct_node.get_node_context(&context);
sync_content_autosize(&direct_node, &node_ctx, true);
assert_eq!(direct_node.measured_size.get(), Some((0.0, 0.0)));
}
}