1use crate::geometry::{Point, Size};
4use crate::style::{AvailableSpace, Overflow, Position};
5use crate::tree::{CollapsibleMarginSet, RunMode};
6use crate::tree::{LayoutInput, LayoutOutput, SizingMode};
7use crate::util::debug::debug_log;
8use crate::util::sys::f32_max;
9use crate::util::MaybeMath;
10use crate::util::{MaybeResolve, ResolveOrZero};
11use crate::{BoxSizing, CoreStyle};
12use core::unreachable;
13
14pub fn compute_leaf_layout<MeasureFunction>(
16 inputs: LayoutInput,
17 style: &impl CoreStyle,
18 resolve_calc_value: impl Fn(*const (), f32) -> f32,
19 measure_function: MeasureFunction,
20) -> LayoutOutput
21where
22 MeasureFunction: FnOnce(Size<Option<f32>>, Size<AvailableSpace>) -> Size<f32>,
23{
24 let LayoutInput { known_dimensions, parent_size, available_space, sizing_mode, run_mode, .. } = inputs;
25
26 let margin = style.margin().resolve_or_zero(parent_size.width, &resolve_calc_value);
29 let padding = style.padding().resolve_or_zero(parent_size.width, &resolve_calc_value);
30 let border = style.border().resolve_or_zero(parent_size.width, &resolve_calc_value);
31 let padding_border = padding + border;
32 let pb_sum = padding_border.sum_axes();
33 let box_sizing_adjustment = if style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
34
35 let (node_size, node_min_size, node_max_size, aspect_ratio) = match sizing_mode {
38 SizingMode::ContentSize => {
39 let node_size = known_dimensions;
40 let node_min_size = Size::NONE;
41 let node_max_size = Size::NONE;
42 (node_size, node_min_size, node_max_size, None)
43 }
44 SizingMode::InherentSize => {
45 let aspect_ratio = style.aspect_ratio();
46 let style_size = style
47 .size()
48 .maybe_resolve(parent_size, &resolve_calc_value)
49 .maybe_apply_aspect_ratio(aspect_ratio)
50 .maybe_add(box_sizing_adjustment);
51 let style_min_size = style
52 .min_size()
53 .maybe_resolve(parent_size, &resolve_calc_value)
54 .maybe_apply_aspect_ratio(aspect_ratio)
55 .maybe_add(box_sizing_adjustment);
56 let style_max_size =
57 style.max_size().maybe_resolve(parent_size, &resolve_calc_value).maybe_add(box_sizing_adjustment);
58
59 let node_size = known_dimensions.or(style_size);
60 (node_size, style_min_size, style_max_size, aspect_ratio)
61 }
62 };
63
64 let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
68 Overflow::Scroll => style.scrollbar_width(),
69 _ => 0.0,
70 });
71 let mut content_box_inset = padding_border;
73 content_box_inset.right += scrollbar_gutter.x;
74 content_box_inset.bottom += scrollbar_gutter.y;
75
76 let has_styles_preventing_being_collapsed_through = !style.is_block()
77 || style.overflow().x.is_scroll_container()
78 || style.overflow().y.is_scroll_container()
79 || style.position() == Position::Absolute
80 || padding.top > 0.0
81 || padding.bottom > 0.0
82 || border.top > 0.0
83 || border.bottom > 0.0
84 || matches!(node_size.height, Some(h) if h > 0.0)
85 || matches!(node_min_size.height, Some(h) if h > 0.0);
86
87 debug_log!("LEAF");
88 debug_log!("node_size", dbg:node_size);
89 debug_log!("min_size ", dbg:node_min_size);
90 debug_log!("max_size ", dbg:node_max_size);
91
92 if run_mode == RunMode::ComputeSize && has_styles_preventing_being_collapsed_through {
94 if let Size { width: Some(width), height: Some(height) } = node_size {
95 let size = Size { width, height }
96 .maybe_clamp(node_min_size, node_max_size)
97 .maybe_max(padding_border.sum_axes().map(Some));
98 return LayoutOutput {
99 size,
100 #[cfg(feature = "content_size")]
101 content_size: Size::ZERO,
102 first_baselines: Point::NONE,
103 top_margin: CollapsibleMarginSet::ZERO,
104 bottom_margin: CollapsibleMarginSet::ZERO,
105 margins_can_collapse_through: false,
106 };
107 };
108 }
109
110 let available_space = Size {
112 width: known_dimensions
113 .width
114 .map(AvailableSpace::from)
115 .unwrap_or(available_space.width)
116 .maybe_sub(margin.horizontal_axis_sum())
117 .maybe_set(known_dimensions.width)
118 .maybe_set(node_size.width)
119 .map_definite_value(|size| {
120 size.maybe_clamp(node_min_size.width, node_max_size.width) - content_box_inset.horizontal_axis_sum()
121 }),
122 height: known_dimensions
123 .height
124 .map(AvailableSpace::from)
125 .unwrap_or(available_space.height)
126 .maybe_sub(margin.vertical_axis_sum())
127 .maybe_set(known_dimensions.height)
128 .maybe_set(node_size.height)
129 .map_definite_value(|size| {
130 size.maybe_clamp(node_min_size.height, node_max_size.height) - content_box_inset.vertical_axis_sum()
131 }),
132 };
133
134 let measured_size = measure_function(
136 match run_mode {
137 RunMode::ComputeSize => known_dimensions,
138 RunMode::PerformLayout => Size::NONE,
139 RunMode::PerformHiddenLayout => unreachable!(),
140 },
141 available_space,
142 );
143 let clamped_size = known_dimensions
144 .or(node_size)
145 .unwrap_or(measured_size + content_box_inset.sum_axes())
146 .maybe_clamp(node_min_size, node_max_size);
147 let size = Size {
148 width: clamped_size.width,
149 height: f32_max(clamped_size.height, aspect_ratio.map(|ratio| clamped_size.width / ratio).unwrap_or(0.0)),
150 };
151 let size = size.maybe_max(padding_border.sum_axes().map(Some));
152
153 LayoutOutput {
154 size,
155 #[cfg(feature = "content_size")]
156 content_size: measured_size + padding.sum_axes(),
157 first_baselines: Point::NONE,
158 top_margin: CollapsibleMarginSet::ZERO,
159 bottom_margin: CollapsibleMarginSet::ZERO,
160 margins_can_collapse_through: !has_styles_preventing_being_collapsed_through
161 && size.height == 0.0
162 && measured_size.height == 0.0,
163 }
164}