1use crate::geometry::{Line, Point, Rect, Size};
3use crate::style::{AvailableSpace, CoreStyle, LengthPercentageAuto, Overflow, Position};
4use crate::style_helpers::TaffyMaxContent;
5use crate::tree::{CollapsibleMarginSet, Layout, LayoutInput, LayoutOutput, RunMode, SizingMode};
6use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId};
7use crate::util::debug::debug_log;
8use crate::util::sys::f32_max;
9use crate::util::sys::Vec;
10use crate::util::MaybeMath;
11use crate::util::{MaybeResolve, ResolveOrZero};
12use crate::{BlockContainerStyle, BlockItemStyle, BoxGenerationMode, BoxSizing, LayoutBlockContainer, TextAlign};
13
14#[cfg(feature = "content_size")]
15use super::common::content_size::compute_content_size_contribution;
16
17struct BlockItem {
19 node_id: NodeId,
21
22 order: u32,
25
26 is_table: bool,
28
29 size: Size<Option<f32>>,
31 min_size: Size<Option<f32>>,
33 max_size: Size<Option<f32>>,
35
36 overflow: Point<Overflow>,
38 scrollbar_width: f32,
40
41 position: Position,
43 inset: Rect<LengthPercentageAuto>,
45 margin: Rect<LengthPercentageAuto>,
47 padding: Rect<f32>,
49 border: Rect<f32>,
51 padding_border_sum: Size<f32>,
53
54 computed_size: Size<f32>,
56 static_position: Point<f32>,
59 can_be_collapsed_through: bool,
61}
62
63pub fn compute_block_layout(
65 tree: &mut impl LayoutBlockContainer,
66 node_id: NodeId,
67 inputs: LayoutInput,
68) -> LayoutOutput {
69 let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs;
70 let style = tree.get_block_container_style(node_id);
71
72 let aspect_ratio = style.aspect_ratio();
74 let padding = style.padding().resolve_or_zero(parent_size.width);
75 let border = style.border().resolve_or_zero(parent_size.width);
76 let padding_border_size = (padding + border).sum_axes();
77 let box_sizing_adjustment =
78 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
79
80 let min_size = style
81 .min_size()
82 .maybe_resolve(parent_size)
83 .maybe_apply_aspect_ratio(aspect_ratio)
84 .maybe_add(box_sizing_adjustment);
85 let max_size = style
86 .max_size()
87 .maybe_resolve(parent_size)
88 .maybe_apply_aspect_ratio(aspect_ratio)
89 .maybe_add(box_sizing_adjustment);
90 let clamped_style_size = if inputs.sizing_mode == SizingMode::InherentSize {
91 style
92 .size()
93 .maybe_resolve(parent_size)
94 .maybe_apply_aspect_ratio(aspect_ratio)
95 .maybe_add(box_sizing_adjustment)
96 .maybe_clamp(min_size, max_size)
97 } else {
98 Size::NONE
99 };
100
101 drop(style);
102
103 let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
105 (Some(min), Some(max)) if max <= min => Some(min),
106 _ => None,
107 });
108
109 let styled_based_known_dimensions =
110 known_dimensions.or(min_max_definite_size).or(clamped_style_size).maybe_max(padding_border_size);
111
112 if run_mode == RunMode::ComputeSize {
115 if let Size { width: Some(width), height: Some(height) } = styled_based_known_dimensions {
116 return LayoutOutput::from_outer_size(Size { width, height });
117 }
118 }
119
120 debug_log!("BLOCK");
121 compute_inner(tree, node_id, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs })
122}
123
124fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
126 let LayoutInput {
127 known_dimensions, parent_size, available_space, run_mode, vertical_margins_are_collapsible, ..
128 } = inputs;
129
130 let style = tree.get_block_container_style(node_id);
131 let raw_padding = style.padding();
132 let raw_border = style.border();
133 let raw_margin = style.margin();
134 let aspect_ratio = style.aspect_ratio();
135 let padding = raw_padding.resolve_or_zero(parent_size.width);
136 let border = raw_border.resolve_or_zero(parent_size.width);
137
138 let scrollbar_gutter = {
142 let offsets = style.overflow().transpose().map(|overflow| match overflow {
143 Overflow::Scroll => style.scrollbar_width(),
144 _ => 0.0,
145 });
146 Rect { top: 0.0, left: 0.0, right: offsets.x, bottom: offsets.y }
148 };
149 let padding_border = padding + border;
150 let padding_border_size = padding_border.sum_axes();
151 let content_box_inset = padding_border + scrollbar_gutter;
152 let container_content_box_size = known_dimensions.maybe_sub(content_box_inset.sum_axes());
153
154 let box_sizing_adjustment =
155 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
156 let size =
157 style.size().maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio).maybe_add(box_sizing_adjustment);
158 let min_size = style
159 .min_size()
160 .maybe_resolve(parent_size)
161 .maybe_apply_aspect_ratio(aspect_ratio)
162 .maybe_add(box_sizing_adjustment);
163 let max_size = style
164 .max_size()
165 .maybe_resolve(parent_size)
166 .maybe_apply_aspect_ratio(aspect_ratio)
167 .maybe_add(box_sizing_adjustment);
168
169 let own_margins_collapse_with_children = Line {
171 start: vertical_margins_are_collapsible.start
172 && !style.overflow().x.is_scroll_container()
173 && !style.overflow().y.is_scroll_container()
174 && style.position() == Position::Relative
175 && padding.top == 0.0
176 && border.top == 0.0,
177 end: vertical_margins_are_collapsible.end
178 && !style.overflow().x.is_scroll_container()
179 && !style.overflow().y.is_scroll_container()
180 && style.position() == Position::Relative
181 && padding.bottom == 0.0
182 && border.bottom == 0.0
183 && size.height.is_none(),
184 };
185 let has_styles_preventing_being_collapsed_through = !style.is_block()
186 || style.overflow().x.is_scroll_container()
187 || style.overflow().y.is_scroll_container()
188 || style.position() == Position::Absolute
189 || padding.top > 0.0
190 || padding.bottom > 0.0
191 || border.top > 0.0
192 || border.bottom > 0.0
193 || matches!(size.height, Some(h) if h > 0.0)
194 || matches!(min_size.height, Some(h) if h > 0.0);
195
196 let text_align = style.text_align();
197
198 drop(style);
199
200 let mut items = generate_item_list(tree, node_id, container_content_box_size);
202
203 let container_outer_width = known_dimensions.width.unwrap_or_else(|| {
205 let available_width = available_space.width.maybe_sub(content_box_inset.horizontal_axis_sum());
206 let intrinsic_width = determine_content_based_container_width(tree, &items, available_width)
207 + content_box_inset.horizontal_axis_sum();
208 intrinsic_width.maybe_clamp(min_size.width, max_size.width).maybe_max(Some(padding_border_size.width))
209 });
210
211 if let (RunMode::ComputeSize, Some(container_outer_height)) = (run_mode, known_dimensions.height) {
213 return LayoutOutput::from_outer_size(Size { width: container_outer_width, height: container_outer_height });
214 }
215
216 let resolved_padding = raw_padding.resolve_or_zero(Some(container_outer_width));
218 let resolved_border = raw_border.resolve_or_zero(Some(container_outer_width));
219 let resolved_content_box_inset = resolved_padding + resolved_border + scrollbar_gutter;
220 let (inflow_content_size, intrinsic_outer_height, first_child_top_margin_set, last_child_bottom_margin_set) =
221 perform_final_layout_on_in_flow_children(
222 tree,
223 &mut items,
224 container_outer_width,
225 content_box_inset,
226 resolved_content_box_inset,
227 text_align,
228 own_margins_collapse_with_children,
229 );
230 let container_outer_height = known_dimensions
231 .height
232 .unwrap_or(intrinsic_outer_height.maybe_clamp(min_size.height, max_size.height))
233 .maybe_max(Some(padding_border_size.height));
234 let final_outer_size = Size { width: container_outer_width, height: container_outer_height };
235
236 if run_mode == RunMode::ComputeSize {
238 return LayoutOutput::from_outer_size(final_outer_size);
239 }
240
241 let absolute_position_inset = resolved_border + scrollbar_gutter;
243 let absolute_position_area = final_outer_size - absolute_position_inset.sum_axes();
244 let absolute_position_offset = Point { x: absolute_position_inset.left, y: absolute_position_inset.top };
245 let absolute_content_size =
246 perform_absolute_layout_on_absolute_children(tree, &items, absolute_position_area, absolute_position_offset);
247
248 let len = tree.child_count(node_id);
250 for order in 0..len {
251 let child = tree.get_child_id(node_id, order);
252 if tree.get_block_child_style(child).box_generation_mode() == BoxGenerationMode::None {
253 tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
254 tree.perform_child_layout(
255 child,
256 Size::NONE,
257 Size::NONE,
258 Size::MAX_CONTENT,
259 SizingMode::InherentSize,
260 Line::FALSE,
261 );
262 }
263 }
264
265 let all_in_flow_children_can_be_collapsed_through =
267 items.iter().all(|item| item.position == Position::Absolute || item.can_be_collapsed_through);
268 let can_be_collapsed_through =
269 !has_styles_preventing_being_collapsed_through && all_in_flow_children_can_be_collapsed_through;
270
271 #[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
272 let content_size = inflow_content_size.f32_max(absolute_content_size);
273
274 LayoutOutput {
275 size: final_outer_size,
276 #[cfg(feature = "content_size")]
277 content_size,
278 first_baselines: Point::NONE,
279 top_margin: if own_margins_collapse_with_children.start {
280 first_child_top_margin_set
281 } else {
282 let margin_top = raw_margin.top.resolve_or_zero(parent_size.width);
283 CollapsibleMarginSet::from_margin(margin_top)
284 },
285 bottom_margin: if own_margins_collapse_with_children.end {
286 last_child_bottom_margin_set
287 } else {
288 let margin_bottom = raw_margin.bottom.resolve_or_zero(parent_size.width);
289 CollapsibleMarginSet::from_margin(margin_bottom)
290 },
291 margins_can_collapse_through: can_be_collapsed_through,
292 }
293}
294
295#[inline]
297fn generate_item_list(
298 tree: &impl LayoutBlockContainer,
299 node: NodeId,
300 node_inner_size: Size<Option<f32>>,
301) -> Vec<BlockItem> {
302 tree.child_ids(node)
303 .map(|child_node_id| (child_node_id, tree.get_block_child_style(child_node_id)))
304 .filter(|(_, style)| style.box_generation_mode() != BoxGenerationMode::None)
305 .enumerate()
306 .map(|(order, (child_node_id, child_style))| {
307 let aspect_ratio = child_style.aspect_ratio();
308 let padding = child_style.padding().resolve_or_zero(node_inner_size);
309 let border = child_style.border().resolve_or_zero(node_inner_size);
310 let pb_sum = (padding + border).sum_axes();
311 let box_sizing_adjustment =
312 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
313 BlockItem {
314 node_id: child_node_id,
315 order: order as u32,
316 is_table: child_style.is_table(),
317 size: child_style
318 .size()
319 .maybe_resolve(node_inner_size)
320 .maybe_apply_aspect_ratio(aspect_ratio)
321 .maybe_add(box_sizing_adjustment),
322 min_size: child_style
323 .min_size()
324 .maybe_resolve(node_inner_size)
325 .maybe_apply_aspect_ratio(aspect_ratio)
326 .maybe_add(box_sizing_adjustment),
327 max_size: child_style
328 .max_size()
329 .maybe_resolve(node_inner_size)
330 .maybe_apply_aspect_ratio(aspect_ratio)
331 .maybe_add(box_sizing_adjustment),
332 overflow: child_style.overflow(),
333 scrollbar_width: child_style.scrollbar_width(),
334 position: child_style.position(),
335 inset: child_style.inset(),
336 margin: child_style.margin(),
337 padding,
338 border,
339 padding_border_sum: pb_sum,
340
341 computed_size: Size::zero(),
343 static_position: Point::zero(),
344 can_be_collapsed_through: false,
345 }
346 })
347 .collect()
348}
349
350#[inline]
352fn determine_content_based_container_width(
353 tree: &mut impl LayoutPartialTree,
354 items: &[BlockItem],
355 available_width: AvailableSpace,
356) -> f32 {
357 let available_space = Size { width: available_width, height: AvailableSpace::MinContent };
358
359 let mut max_child_width = 0.0;
360 for item in items.iter().filter(|item| item.position != Position::Absolute) {
361 let known_dimensions = item.size.maybe_clamp(item.min_size, item.max_size);
362
363 let width = known_dimensions.width.unwrap_or_else(|| {
364 let item_x_margin_sum =
365 item.margin.resolve_or_zero(available_space.width.into_option()).horizontal_axis_sum();
366 let size_and_baselines = tree.perform_child_layout(
367 item.node_id,
368 known_dimensions,
369 Size::NONE,
370 available_space.map_width(|w| w.maybe_sub(item_x_margin_sum)),
371 SizingMode::InherentSize,
372 Line::TRUE,
373 );
374
375 size_and_baselines.size.width + item_x_margin_sum
376 });
377 let width = f32_max(width, item.padding_border_sum.width);
378
379 max_child_width = f32_max(max_child_width, width);
380 }
381
382 max_child_width
383}
384
385#[inline]
387fn perform_final_layout_on_in_flow_children(
388 tree: &mut impl LayoutPartialTree,
389 items: &mut [BlockItem],
390 container_outer_width: f32,
391 content_box_inset: Rect<f32>,
392 resolved_content_box_inset: Rect<f32>,
393 text_align: TextAlign,
394 own_margins_collapse_with_children: Line<bool>,
395) -> (Size<f32>, f32, CollapsibleMarginSet, CollapsibleMarginSet) {
396 let container_inner_width = container_outer_width - content_box_inset.horizontal_axis_sum();
398 let parent_size = Size { width: Some(container_outer_width), height: None };
399 let available_space =
400 Size { width: AvailableSpace::Definite(container_inner_width), height: AvailableSpace::MinContent };
401
402 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
403 let mut inflow_content_size = Size::ZERO;
404 let mut committed_y_offset = resolved_content_box_inset.top;
405 let mut y_offset_for_absolute = resolved_content_box_inset.top;
406 let mut first_child_top_margin_set = CollapsibleMarginSet::ZERO;
407 let mut active_collapsible_margin_set = CollapsibleMarginSet::ZERO;
408 let mut is_collapsing_with_first_margin_set = true;
409 for item in items.iter_mut() {
410 if item.position == Position::Absolute {
411 item.static_position = Point { x: resolved_content_box_inset.left, y: y_offset_for_absolute }
412 } else {
413 let item_margin = item.margin.map(|margin| margin.resolve_to_option(container_outer_width));
414 let item_non_auto_margin = item_margin.map(|m| m.unwrap_or(0.0));
415 let item_non_auto_x_margin_sum = item_non_auto_margin.horizontal_axis_sum();
416 let known_dimensions = if item.is_table {
417 Size::NONE
418 } else {
419 item.size
420 .map_width(|width| {
421 Some(
424 width
425 .unwrap_or(container_inner_width - item_non_auto_x_margin_sum)
426 .maybe_clamp(item.min_size.width, item.max_size.width),
427 )
428 })
429 .maybe_clamp(item.min_size, item.max_size)
430 };
431
432 let item_layout = tree.perform_child_layout(
433 item.node_id,
434 known_dimensions,
435 parent_size,
436 available_space.map_width(|w| w.maybe_sub(item_non_auto_x_margin_sum)),
437 SizingMode::InherentSize,
438 Line::TRUE,
439 );
440 let final_size = item_layout.size;
441
442 let top_margin_set = item_layout.top_margin.collapse_with_margin(item_margin.top.unwrap_or(0.0));
443 let bottom_margin_set = item_layout.bottom_margin.collapse_with_margin(item_margin.bottom.unwrap_or(0.0));
444
445 let free_x_space = f32_max(0.0, container_inner_width - final_size.width - item_non_auto_x_margin_sum);
449 let x_axis_auto_margin_size = {
450 let auto_margin_count = item_margin.left.is_none() as u8 + item_margin.right.is_none() as u8;
451 if auto_margin_count > 0 {
452 free_x_space / auto_margin_count as f32
453 } else {
454 0.0
455 }
456 };
457 let resolved_margin = Rect {
458 left: item_margin.left.unwrap_or(x_axis_auto_margin_size),
459 right: item_margin.right.unwrap_or(x_axis_auto_margin_size),
460 top: top_margin_set.resolve(),
461 bottom: bottom_margin_set.resolve(),
462 };
463
464 let inset =
466 item.inset.zip_size(Size { width: container_inner_width, height: 0.0 }, |p, s| p.maybe_resolve(s));
467 let inset_offset = Point {
468 x: inset.left.or(inset.right.map(|x| -x)).unwrap_or(0.0),
469 y: inset.top.or(inset.bottom.map(|x| -x)).unwrap_or(0.0),
470 };
471
472 let y_margin_offset = if is_collapsing_with_first_margin_set && own_margins_collapse_with_children.start {
473 0.0
474 } else {
475 active_collapsible_margin_set.collapse_with_margin(resolved_margin.top).resolve()
476 };
477
478 item.computed_size = item_layout.size;
479 item.can_be_collapsed_through = item_layout.margins_can_collapse_through;
480 item.static_position = Point {
481 x: resolved_content_box_inset.left,
482 y: committed_y_offset + active_collapsible_margin_set.resolve(),
483 };
484 let mut location = Point {
485 x: resolved_content_box_inset.left + inset_offset.x + resolved_margin.left,
486 y: committed_y_offset + inset_offset.y + y_margin_offset,
487 };
488
489 let item_outer_width = item_layout.size.width + resolved_margin.horizontal_axis_sum();
491 if item_outer_width < container_inner_width {
492 match text_align {
493 TextAlign::Auto => {
494 }
496 TextAlign::LegacyLeft => {
497 }
499 TextAlign::LegacyRight => location.x += container_inner_width - item_outer_width,
500 TextAlign::LegacyCenter => location.x += (container_inner_width - item_outer_width) / 2.0,
501 }
502 }
503
504 let scrollbar_size = Size {
505 width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
506 height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
507 };
508
509 tree.set_unrounded_layout(
510 item.node_id,
511 &Layout {
512 order: item.order,
513 size: item_layout.size,
514 #[cfg(feature = "content_size")]
515 content_size: item_layout.content_size,
516 scrollbar_size,
517 location,
518 padding: item.padding,
519 border: item.border,
520 margin: resolved_margin,
521 },
522 );
523
524 #[cfg(feature = "content_size")]
525 {
526 inflow_content_size = inflow_content_size.f32_max(compute_content_size_contribution(
527 location,
528 final_size,
529 item_layout.content_size,
530 item.overflow,
531 ));
532 }
533
534 if is_collapsing_with_first_margin_set {
536 if item.can_be_collapsed_through {
537 first_child_top_margin_set = first_child_top_margin_set
538 .collapse_with_set(top_margin_set)
539 .collapse_with_set(bottom_margin_set);
540 } else {
541 first_child_top_margin_set = first_child_top_margin_set.collapse_with_set(top_margin_set);
542 is_collapsing_with_first_margin_set = false;
543 }
544 }
545
546 if item.can_be_collapsed_through {
548 active_collapsible_margin_set = active_collapsible_margin_set
549 .collapse_with_set(top_margin_set)
550 .collapse_with_set(bottom_margin_set);
551 y_offset_for_absolute = committed_y_offset + item_layout.size.height + y_margin_offset;
552 } else {
553 committed_y_offset += item_layout.size.height + y_margin_offset;
554 active_collapsible_margin_set = bottom_margin_set;
555 y_offset_for_absolute = committed_y_offset + active_collapsible_margin_set.resolve();
556 }
557 }
558 }
559
560 let last_child_bottom_margin_set = active_collapsible_margin_set;
561 let bottom_y_margin_offset =
562 if own_margins_collapse_with_children.end { 0.0 } else { last_child_bottom_margin_set.resolve() };
563
564 committed_y_offset += resolved_content_box_inset.bottom + bottom_y_margin_offset;
565 let content_height = f32_max(0.0, committed_y_offset);
566 (inflow_content_size, content_height, first_child_top_margin_set, last_child_bottom_margin_set)
567}
568
569#[inline]
571fn perform_absolute_layout_on_absolute_children(
572 tree: &mut impl LayoutBlockContainer,
573 items: &[BlockItem],
574 area_size: Size<f32>,
575 area_offset: Point<f32>,
576) -> Size<f32> {
577 let area_width = area_size.width;
578 let area_height = area_size.height;
579
580 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
581 let mut absolute_content_size = Size::ZERO;
582
583 for item in items.iter().filter(|item| item.position == Position::Absolute) {
584 let child_style = tree.get_block_child_style(item.node_id);
585
586 if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute
588 {
589 continue;
590 }
591
592 let aspect_ratio = child_style.aspect_ratio();
593 let margin = child_style.margin().map(|margin| margin.resolve_to_option(area_width));
594 let padding = child_style.padding().resolve_or_zero(Some(area_width));
595 let border = child_style.border().resolve_or_zero(Some(area_width));
596 let padding_border_sum = (padding + border).sum_axes();
597 let box_sizing_adjustment =
598 if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
599
600 let left = child_style.inset().left.maybe_resolve(area_width);
602 let right = child_style.inset().right.maybe_resolve(area_width);
603 let top = child_style.inset().top.maybe_resolve(area_height);
604 let bottom = child_style.inset().bottom.maybe_resolve(area_height);
605
606 let style_size = child_style
608 .size()
609 .maybe_resolve(area_size)
610 .maybe_apply_aspect_ratio(aspect_ratio)
611 .maybe_add(box_sizing_adjustment);
612 let min_size = child_style
613 .min_size()
614 .maybe_resolve(area_size)
615 .maybe_apply_aspect_ratio(aspect_ratio)
616 .maybe_add(box_sizing_adjustment)
617 .or(padding_border_sum.map(Some))
618 .maybe_max(padding_border_sum);
619 let max_size = child_style
620 .max_size()
621 .maybe_resolve(area_size)
622 .maybe_apply_aspect_ratio(aspect_ratio)
623 .maybe_add(box_sizing_adjustment);
624 let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
625
626 drop(child_style);
627
628 if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
632 let new_width_raw = area_width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
633 known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
634 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
635 }
636
637 if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
641 let new_height_raw = area_height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
642 known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
643 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
644 }
645
646 let layout_output = tree.perform_child_layout(
647 item.node_id,
648 known_dimensions,
649 area_size.map(Some),
650 Size {
651 width: AvailableSpace::Definite(area_width.maybe_clamp(min_size.width, max_size.width)),
652 height: AvailableSpace::Definite(area_height.maybe_clamp(min_size.height, max_size.height)),
653 },
654 SizingMode::ContentSize,
655 Line::FALSE,
656 );
657 let measured_size = layout_output.size;
658 let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
659
660 let non_auto_margin = Rect {
661 left: if left.is_some() { margin.left.unwrap_or(0.0) } else { 0.0 },
662 right: if right.is_some() { margin.right.unwrap_or(0.0) } else { 0.0 },
663 top: if top.is_some() { margin.top.unwrap_or(0.0) } else { 0.0 },
664 bottom: if bottom.is_some() { margin.left.unwrap_or(0.0) } else { 0.0 },
665 };
666
667 let auto_margin = {
670 let absolute_auto_margin_space = Point {
673 x: right.map(|right| area_size.width - right - left.unwrap_or(0.0)).unwrap_or(final_size.width),
674 y: bottom.map(|bottom| area_size.height - bottom - top.unwrap_or(0.0)).unwrap_or(final_size.height),
675 };
676 let free_space = Size {
677 width: absolute_auto_margin_space.x - final_size.width - non_auto_margin.horizontal_axis_sum(),
678 height: absolute_auto_margin_space.y - final_size.height - non_auto_margin.vertical_axis_sum(),
679 };
680
681 let auto_margin_size = Size {
682 width: {
692 let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
693 if auto_margin_count == 2
694 && (style_size.width.is_none() || style_size.width.unwrap() >= free_space.width)
695 {
696 0.0
697 } else if auto_margin_count > 0 {
698 free_space.width / auto_margin_count as f32
699 } else {
700 0.0
701 }
702 },
703 height: {
704 let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
705 if auto_margin_count == 2
706 && (style_size.height.is_none() || style_size.height.unwrap() >= free_space.height)
707 {
708 0.0
709 } else if auto_margin_count > 0 {
710 free_space.height / auto_margin_count as f32
711 } else {
712 0.0
713 }
714 },
715 };
716
717 Rect {
718 left: margin.left.map(|_| 0.0).unwrap_or(auto_margin_size.width),
719 right: margin.right.map(|_| 0.0).unwrap_or(auto_margin_size.width),
720 top: margin.top.map(|_| 0.0).unwrap_or(auto_margin_size.height),
721 bottom: margin.bottom.map(|_| 0.0).unwrap_or(auto_margin_size.height),
722 }
723 };
724
725 let resolved_margin = Rect {
726 left: margin.left.unwrap_or(auto_margin.left),
727 right: margin.right.unwrap_or(auto_margin.right),
728 top: margin.top.unwrap_or(auto_margin.top),
729 bottom: margin.bottom.unwrap_or(auto_margin.bottom),
730 };
731
732 let location = Point {
733 x: left
734 .map(|left| left + resolved_margin.left)
735 .or(right.map(|right| area_size.width - final_size.width - right - resolved_margin.right))
736 .maybe_add(area_offset.x)
737 .unwrap_or(item.static_position.x + resolved_margin.left),
738 y: top
739 .map(|top| top + resolved_margin.top)
740 .or(bottom.map(|bottom| area_size.height - final_size.height - bottom - resolved_margin.bottom))
741 .maybe_add(area_offset.y)
742 .unwrap_or(item.static_position.y + resolved_margin.top),
743 };
744 let scrollbar_size = Size {
747 width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
748 height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
749 };
750
751 tree.set_unrounded_layout(
752 item.node_id,
753 &Layout {
754 order: item.order,
755 size: final_size,
756 #[cfg(feature = "content_size")]
757 content_size: layout_output.content_size,
758 scrollbar_size,
759 location,
760 padding,
761 border,
762 margin: resolved_margin,
763 },
764 );
765
766 #[cfg(feature = "content_size")]
767 {
768 absolute_content_size = absolute_content_size.f32_max(compute_content_size_contribution(
769 location,
770 final_size,
771 layout_output.content_size,
772 item.overflow,
773 ));
774 }
775 }
776
777 absolute_content_size
778}