// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
fn[C] compute_flex_absolute_and_hidden_children(
view : ChicleView[C],
abs_children : Array[NodeId],
hidden_children : Array[NodeId],
absolute_origin : Point[Double],
border : Rect[Double],
padding : Rect[Double],
border_box_width : Double,
border_box_height : Double,
is_col : Bool,
container_main : Double,
container_cross : Double,
justify : AlignContent,
is_reverse : Bool,
is_wrap_reverse : Bool,
align_items : AlignItems?,
) -> Unit raise ChicleError {
let tree = view.tree
// Absolute positioned children: compute after container size is known, and do not affect flow layout.
// Follow the same sizing rules as the block layout absolute-positioning pass:
// inset constraints can determine auto sizes, and aspect-ratio participates in sizing.
let padding_origin = Point(
x=absolute_origin.x + border.left,
y=absolute_origin.y + border.top,
)
let padding_box_width = @util.max_double(
border_box_width - border.left - border.right,
0.0,
)
let padding_box_height = @util.max_double(
border_box_height - border.top - border.bottom,
0.0,
)
let abs_available = Size(
width=@geometry.AvailDefinite(padding_box_width),
height=AvailDefinite(padding_box_height),
)
for child_id in abs_children {
let child = match tree.nodes.get(child_id) {
Some(c) => c
None => raise InvalidNodeId(child_id)
}
let margin = child.style.margin
// CSS compatibility: margin percentages are resolved against the width.
let margin_left_auto = margin.left is DimAuto
let margin_right_auto = margin.right is DimAuto
let margin_top_auto = margin.top is DimAuto
let margin_bottom_auto = margin.bottom is DimAuto
let margin_left_fixed = @util.resolve_dimension_width_basis(
margin.left,
padding_box_width,
)
let margin_right_fixed = @util.resolve_dimension_width_basis(
margin.right,
padding_box_width,
)
let margin_top_fixed = @util.resolve_dimension_width_basis(
margin.top,
padding_box_width,
)
let margin_bottom_fixed = @util.resolve_dimension_width_basis(
margin.bottom,
padding_box_width,
)
let inset = child.style.inset
let left = @util.resolve_optional_dimension(inset.left, abs_available.width)
let right = @util.resolve_optional_dimension(
inset.right,
abs_available.width,
)
let top = @util.resolve_optional_dimension(inset.top, abs_available.height)
let bottom = @util.resolve_optional_dimension(
inset.bottom,
abs_available.height,
)
// Compute the used size for absolutely positioned items.
let mut used_width = @util.resolve_optional_dimension(
child.style.size.width,
abs_available.width,
)
let mut used_height = @util.resolve_optional_dimension(
child.style.size.height,
abs_available.height,
)
let width_was_auto = used_width is None
let height_was_auto = used_height is None
let mut width_from_inset = false
let mut height_from_inset = false
// If size is auto and both insets are definite, the size is determined by the inset constraints.
match used_width {
Some(_) => ()
None =>
match (left, right) {
(Some(l), Some(r)) =>
used_width = Some(
@util.max_double(
padding_box_width -
l -
r -
margin_left_fixed -
margin_right_fixed,
0.0,
),
)
_ => ()
}
}
match used_height {
Some(_) => ()
None =>
match (top, bottom) {
(Some(t), Some(b)) =>
used_height = Some(
@util.max_double(
padding_box_height -
t -
b -
margin_top_fixed -
margin_bottom_fixed,
0.0,
),
)
_ => ()
}
}
match (used_width, used_height) {
(Some(_), _) =>
if width_was_auto {
match (left, right) {
(Some(_), Some(_)) => width_from_inset = true
_ => ()
}
} else {
()
}
_ => ()
}
match (used_width, used_height) {
(_, Some(_)) =>
if height_was_auto {
match (top, bottom) {
(Some(_), Some(_)) => height_from_inset = true
_ => ()
}
} else {
()
}
_ => ()
}
// Apply aspect ratio when only one axis is known.
match child.style.aspect_ratio {
Some(ratio) =>
if ratio > 0.0 {
match (used_width, used_height) {
(Some(w), None) => used_height = Some((w / ratio).round())
(None, Some(h)) => used_width = Some((h * ratio).round())
(Some(w), Some(_h)) =>
if width_was_auto &&
height_was_auto &&
width_from_inset &&
height_from_inset {
used_height = Some((w / ratio).round())
} else {
()
}
_ => ()
}
}
None => ()
}
// Apply aspect ratio to min/max constraints (chicle 0.5 behavior).
let mut min_width = @util.resolve_optional_dimension(
child.style.min_size.width,
abs_available.width,
)
let mut min_height = @util.resolve_optional_dimension(
child.style.min_size.height,
abs_available.height,
)
let mut max_width = @util.resolve_optional_dimension(
child.style.max_size.width,
abs_available.width,
)
let mut max_height = @util.resolve_optional_dimension(
child.style.max_size.height,
abs_available.height,
)
match child.style.aspect_ratio {
Some(ratio) =>
if ratio > 0.0 {
match (min_width, min_height) {
(None, Some(h)) => min_width = Some((h * ratio).round())
(Some(w), None) => min_height = Some((w / ratio).round())
_ => ()
}
match (max_width, max_height) {
(None, Some(h)) => max_width = Some((h * ratio).round())
(Some(w), None) => max_height = Some((w / ratio).round())
_ => ()
}
} else {
()
}
None => ()
}
// First pass: determine intrinsic size under the containing block constraints, if needed.
let intrinsic = match (used_width, used_height) {
(Some(w), Some(h)) => Size(width=w, height=h)
_ => {
view.perform_child_layout(
child_id,
Size(width=None, height=None),
abs_available,
Point::zero(),
false,
)
tree.nodes[child_id].unrounded_layout.size
}
}
let final_width = match used_width {
Some(w) => w
None => intrinsic.width
}
let final_height = match used_height {
Some(h) => h
None => intrinsic.height
}
let mut clamped_final_width = final_width
let mut clamped_final_height = final_height
match min_width {
Some(m) => clamped_final_width = @util.max_double(clamped_final_width, m)
None => ()
}
match max_width {
Some(m) =>
if clamped_final_width > m {
clamped_final_width = m
} else {
()
}
None => ()
}
match min_height {
Some(m) =>
clamped_final_height = @util.max_double(clamped_final_height, m)
None => ()
}
match max_height {
Some(m) =>
if clamped_final_height > m {
clamped_final_height = m
} else {
()
}
None => ()
}
// Second pass: compute final size (may be clamped by min/max).
view.perform_child_layout(
child_id,
Size(width=Some(clamped_final_width), height=Some(clamped_final_height)),
abs_available,
Point::zero(),
false,
)
let final_size = tree.nodes[child_id].unrounded_layout.size
let mut final_margin_left = if margin_left_auto {
0.0
} else {
margin_left_fixed
}
let mut final_margin_right = if margin_right_auto {
0.0
} else {
margin_right_fixed
}
let mut final_margin_top = if margin_top_auto {
0.0
} else {
margin_top_fixed
}
let mut final_margin_bottom = if margin_bottom_auto {
0.0
} else {
margin_bottom_fixed
}
// Absolute auto margins: distribute remaining space when both inset sides are definite.
match (left, right) {
(Some(l), Some(r)) => {
let fixed = (if margin_left_auto { 0.0 } else { final_margin_left }) +
(if margin_right_auto { 0.0 } else { final_margin_right })
let remaining = padding_box_width - l - r - final_size.width - fixed
let auto_count = (if margin_left_auto { 1 } else { 0 }) +
(if margin_right_auto { 1 } else { 0 })
match auto_count {
2 =>
if remaining >= 0.0 {
final_margin_left = remaining / 2.0
final_margin_right = remaining / 2.0
} else {
final_margin_left = 0.0
final_margin_right = 0.0
}
1 =>
if margin_left_auto {
final_margin_left = remaining
} else {
final_margin_right = remaining
}
_ => ()
}
}
_ => ()
}
match (top, bottom) {
(Some(t), Some(b)) => {
let fixed = (if margin_top_auto { 0.0 } else { final_margin_top }) +
(if margin_bottom_auto { 0.0 } else { final_margin_bottom })
let remaining = padding_box_height - t - b - final_size.height - fixed
let auto_count = (if margin_top_auto { 1 } else { 0 }) +
(if margin_bottom_auto { 1 } else { 0 })
match auto_count {
2 =>
if remaining >= 0.0 {
final_margin_top = remaining / 2.0
final_margin_bottom = remaining / 2.0
} else {
final_margin_top = 0.0
final_margin_bottom = 0.0
}
1 =>
if margin_top_auto {
final_margin_top = remaining
} else {
final_margin_bottom = remaining
}
_ => ()
}
}
_ => ()
}
// Static position fallback when no inset is specified on an axis.
let main_margin_start = if is_col {
final_margin_top
} else {
final_margin_left
}
let main_margin_end = if is_col {
final_margin_bottom
} else {
final_margin_right
}
let cross_margin_start = if is_col {
final_margin_left
} else {
final_margin_top
}
let cross_margin_end = if is_col {
final_margin_right
} else {
final_margin_bottom
}
let main_size = if is_col { final_size.height } else { final_size.width }
let cross_size = if is_col { final_size.width } else { final_size.height }
let outer_main = main_margin_start + main_size + main_margin_end
let leftover_main = container_main - outer_main
let leftover_main = if leftover_main > 0.0 { leftover_main } else { 0.0 }
let static_start_main = resolve_justify_start_main(
justify, leftover_main, is_reverse,
)
let static_main = static_start_main + main_margin_start
let align = match child.style.align_self {
Some(v) => v
None =>
match align_items {
Some(v) => v
None => ItemsStretch
}
}
let available_cross_for_item = @util.max_double(
container_cross - cross_margin_start - cross_margin_end,
0.0,
)
let cross_pos_in_available = match align {
ItemsEnd | ItemsFlexEnd => available_cross_for_item - cross_size
ItemsCenter => (available_cross_for_item - cross_size) / 2.0
_ => 0.0
}
let cross_pos_in_available = if is_wrap_reverse {
available_cross_for_item - cross_size - cross_pos_in_available
} else {
cross_pos_in_available
}
let static_cross = cross_margin_start + cross_pos_in_available
let x_in_padding = match left {
Some(v) => v + final_margin_left
None =>
match right {
Some(v) =>
padding_box_width - v - final_margin_right - final_size.width
None =>
padding.left + (if is_col { static_cross } else { static_main })
}
}
let y_in_padding = match top {
Some(v) => v + final_margin_top
None =>
match bottom {
Some(v) =>
padding_box_height - v - final_margin_bottom - final_size.height
None =>
padding.top + (if is_col { static_main } else { static_cross })
}
}
view.perform_child_layout(
child_id,
Size(width=Some(final_size.width), height=Some(final_size.height)),
abs_available,
Point(
x=padding_origin.x + x_in_padding,
y=padding_origin.y + y_in_padding,
),
false,
)
}
// Ensure `display: none` children get laid out (to zero) as well.
for child_id in hidden_children {
view.perform_child_layout(
child_id,
Size(width=None, height=None),
abs_available,
Point::zero(),
false,
)
}
}