Use macros to reduce noise from debug logging code (#561)
1//! Computes size using styles and measure functions
2
3use crate::geometry::{Point, Size};
4use crate::style::{AvailableSpace, Display, Overflow, Position, Style};
5use crate::tree::CollapsibleMarginSet;
6use crate::tree::NodeId;
7use crate::tree::{SizeBaselinesAndMargins, SizingMode};
8use crate::util::debug::debug_log;
9use crate::util::sys::f32_max;
10use crate::util::MaybeMath;
11use crate::util::{MaybeResolve, ResolveOrZero};
12
13/// Perform full layout on a leaf node
14#[allow(clippy::too_many_arguments)]
15pub(crate) fn perform_layout<NodeContext, MeasureFunction>(
16 style: &Style,
17 known_dimensions: Size<Option<f32>>,
18 parent_size: Size<Option<f32>>,
19 available_space: Size<AvailableSpace>,
20 sizing_mode: SizingMode,
21 measure_function: MeasureFunction,
22 node_id: NodeId,
23 context: Option<&mut NodeContext>,
24) -> SizeBaselinesAndMargins
25where
26 MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
27{
28 compute(style, known_dimensions, parent_size, available_space, sizing_mode, measure_function, node_id, context)
29}
30
31/// Measure a leaf node's size
32#[allow(clippy::too_many_arguments)]
33pub(crate) fn measure_size<NodeContext, MeasureFunction>(
34 style: &Style,
35 known_dimensions: Size<Option<f32>>,
36 parent_size: Size<Option<f32>>,
37 available_space: Size<AvailableSpace>,
38 sizing_mode: SizingMode,
39 measure_function: MeasureFunction,
40 node_id: NodeId,
41 context: Option<&mut NodeContext>,
42) -> Size<f32>
43where
44 MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
45{
46 compute(style, known_dimensions, parent_size, available_space, sizing_mode, measure_function, node_id, context).size
47}
48
49/// Compute the size of a leaf node (node with no children)
50#[allow(clippy::too_many_arguments)]
51pub fn compute<NodeContext, MeasureFunction>(
52 style: &Style,
53 known_dimensions: Size<Option<f32>>,
54 parent_size: Size<Option<f32>>,
55 available_space: Size<AvailableSpace>,
56 sizing_mode: SizingMode,
57 mut measure_function: MeasureFunction,
58 node_id: NodeId,
59 context: Option<&mut NodeContext>,
60) -> SizeBaselinesAndMargins
61where
62 MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
63{
64 // Resolve node's preferred/min/max sizes (width/heights) against the available space (percentages resolve to pixel values)
65 // For ContentSize mode, we pretend that the node has no size styles as these should be ignored.
66 let (node_size, node_min_size, node_max_size, aspect_ratio) = match sizing_mode {
67 SizingMode::ContentSize => {
68 let node_size = known_dimensions;
69 let node_min_size = Size::NONE;
70 let node_max_size = Size::NONE;
71 (node_size, node_min_size, node_max_size, None)
72 }
73 SizingMode::InherentSize => {
74 let aspect_ratio = style.aspect_ratio;
75 let style_size = style.size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
76 let style_min_size = style.min_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
77 let style_max_size = style.max_size.maybe_resolve(parent_size);
78
79 let node_size = known_dimensions.or(style_size);
80 (node_size, style_min_size, style_max_size, aspect_ratio)
81 }
82 };
83
84 // Note: both horizontal and vertical percentage padding/borders are resolved against the container's inline size (i.e. width).
85 // This is not a bug, but is how CSS is specified (see: https://developer.mozilla.org/en-US/docs/Web/CSS/padding#values)
86 let margin = style.margin.resolve_or_zero(parent_size.width);
87 let padding = style.padding.resolve_or_zero(parent_size.width);
88 let border = style.border.resolve_or_zero(parent_size.width);
89 let padding_border = padding + border;
90
91 // Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
92 // However, the axis are switched (transposed) because a node that scrolls vertically needs
93 // *horizontal* space to be reserved for a scrollbar
94 let scrollbar_gutter = style.overflow.transpose().map(|overflow| match overflow {
95 Overflow::Scroll => style.scrollbar_width,
96 _ => 0.0,
97 });
98 // TODO: make side configurable based on the `direction` property
99 let mut content_box_inset = padding_border;
100 content_box_inset.right += scrollbar_gutter.x;
101 content_box_inset.bottom += scrollbar_gutter.y;
102
103 #[cfg(feature = "block_layout")]
104 let is_block = style.display == Display::Block;
105 #[cfg(not(feature = "block_layout"))]
106 let is_block = false;
107
108 let has_styles_preventing_being_collapsed_through = !is_block
109 || style.overflow.x.is_scroll_container()
110 || style.overflow.y.is_scroll_container()
111 || style.position == Position::Absolute
112 || padding.top > 0.0
113 || padding.bottom > 0.0
114 || border.top > 0.0
115 || border.bottom > 0.0;
116
117 debug_log!("LEAF");
118 debug_log!("node_size", dbg:node_size);
119 debug_log!("min_size ", dbg:node_min_size);
120 debug_log!("max_size ", dbg:node_max_size);
121
122 // Return early if both width and height are known
123 if let Size { width: Some(width), height: Some(height) } = node_size {
124 let size = Size { width, height }
125 .maybe_clamp(node_min_size, node_max_size)
126 .maybe_max(padding_border.sum_axes().map(Some));
127 return SizeBaselinesAndMargins {
128 size,
129 first_baselines: Point::NONE,
130 top_margin: CollapsibleMarginSet::ZERO,
131 bottom_margin: CollapsibleMarginSet::ZERO,
132 margins_can_collapse_through: !has_styles_preventing_being_collapsed_through
133 && size.height == 0.0
134 && context.is_none(),
135 };
136 };
137
138 if let Some(context) = context {
139 // Compute available space
140 let available_space = Size {
141 width: available_space
142 .width
143 .maybe_sub(margin.horizontal_axis_sum())
144 .maybe_set(node_size.width)
145 .maybe_set(node_max_size.width)
146 .map_definite_value(|size| {
147 size.maybe_clamp(node_min_size.width, node_max_size.width) - content_box_inset.horizontal_axis_sum()
148 }),
149 height: available_space
150 .height
151 .maybe_sub(margin.vertical_axis_sum())
152 .maybe_set(node_size.height)
153 .maybe_set(node_max_size.height)
154 .map_definite_value(|size| {
155 size.maybe_clamp(node_min_size.height, node_max_size.height) - content_box_inset.vertical_axis_sum()
156 }),
157 };
158
159 // Measure node
160 let measured_size = measure_function(known_dimensions, available_space, node_id, Some(context));
161 let clamped_size =
162 node_size.unwrap_or(measured_size + content_box_inset.sum_axes()).maybe_clamp(node_min_size, node_max_size);
163 let size = Size {
164 width: clamped_size.width,
165 height: f32_max(clamped_size.height, aspect_ratio.map(|ratio| clamped_size.width / ratio).unwrap_or(0.0)),
166 };
167 let size = size.maybe_max(padding_border.sum_axes().map(Some));
168
169 return SizeBaselinesAndMargins {
170 size,
171 first_baselines: Point::NONE,
172 top_margin: CollapsibleMarginSet::ZERO,
173 bottom_margin: CollapsibleMarginSet::ZERO,
174 margins_can_collapse_through: !has_styles_preventing_being_collapsed_through
175 && size.height == 0.0
176 && measured_size.height == 0.0,
177 };
178 }
179
180 let size = Size {
181 width: node_size
182 .width
183 // .unwrap_or(0.0) + padding.horizontal_axis_sum() + border.horizontal_axis_sum(), // content-box
184 .unwrap_or(content_box_inset.horizontal_axis_sum()) // border-box
185 .maybe_clamp(node_min_size.width, node_max_size.width)
186 .maybe_max(padding_border.horizontal_axis_sum().into()),
187 height: node_size
188 .height
189 // .unwrap_or(0.0) + padding.vertical_axis_sum() + border.vertical_axis_sum(), // content-box
190 .unwrap_or(content_box_inset.vertical_axis_sum()) // border-box
191 .maybe_clamp(node_min_size.height, node_max_size.height)
192 .maybe_max(padding_border.vertical_axis_sum().into()),
193 };
194
195 let size = Size {
196 width: f32_max(size.width, aspect_ratio.map(|ratio| size.height * ratio).unwrap_or(0.0)),
197 height: f32_max(size.height, aspect_ratio.map(|ratio| size.width / ratio).unwrap_or(0.0)),
198 };
199
200 SizeBaselinesAndMargins {
201 size,
202 first_baselines: Point::NONE,
203 top_margin: CollapsibleMarginSet::ZERO,
204 bottom_margin: CollapsibleMarginSet::ZERO,
205 margins_can_collapse_through: !has_styles_preventing_being_collapsed_through && size.height == 0.0,
206 }
207}