// 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] resolve_flex_auto_min_main(
view : ChicleView[C],
child_id : NodeId,
is_col : Bool,
parent_available_for_children : Size[AvailableSpace],
main_axis_available : AvailableSpace,
) -> Double raise ChicleError {
let tree = view.tree
let child = match tree.nodes.get(child_id) {
Some(c) => c
None => raise InvalidNodeId(child_id)
}
let main_from_size = if is_col {
@util.resolve_optional_dimension(
child.style.size.height,
main_axis_available,
)
} else {
@util.resolve_optional_dimension(
child.style.size.width,
main_axis_available,
)
}
let max_from_style = if is_col {
@util.resolve_optional_dimension(
child.style.max_size.height,
main_axis_available,
)
} else {
@util.resolve_optional_dimension(
child.style.max_size.width,
main_axis_available,
)
}
let original_style = child.style
let original_layout = child.unrounded_layout
let cleared_size = if is_col {
Size(width=original_style.size.width, height=DimAuto)
} else {
Size(width=DimAuto, height=original_style.size.height)
}
tree.nodes[child_id].style = { ..original_style, size: cleared_size }
let min_content_space = if is_col {
Size(width=parent_available_for_children.width, height=AvailMinContent)
} else {
Size(width=AvailMinContent, height=parent_available_for_children.height)
}
let min_content_main = view.measure_child_size(
child_id,
Size(width=None, height=None),
Size(width=None, height=None),
min_content_space,
ContentSize,
if is_col {
Vertical
} else {
Horizontal
},
Line(start=false, end=false),
Point::zero(),
)
tree.nodes[child_id].style = original_style
tree.set_unrounded_layout(child_id, original_layout)
let mut clamped = min_content_main
match main_from_size {
Some(v) => clamped = @util.min_double(clamped, v)
None => ()
}
match max_from_style {
Some(v) => clamped = @util.min_double(clamped, v)
None => ()
}
let resolved_padding = @util.resolve_rect_width_basis(
original_style.padding,
parent_available_for_children,
)
let resolved_border = @util.resolve_rect_width_basis(
original_style.border,
parent_available_for_children,
)
let padding_main_sum = if is_col {
resolved_padding.top + resolved_padding.bottom
} else {
resolved_padding.left + resolved_padding.right
}
let border_main_sum = if is_col {
resolved_border.top + resolved_border.bottom
} else {
resolved_border.left + resolved_border.right
}
@util.max_double(clamped, padding_main_sum + border_main_sum)
}
///|
fn[C] resolve_wrap_line_main_sizes(
view : ChicleView[C],
flow_children : Array[NodeId],
indices : Array[Int],
is_col : Bool,
container_main : Double,
gap_main : Double,
base_sizes : Array[Size[Double]],
flex_grow : Array[Double],
flex_shrink : Array[Double],
margin_main_start : Array[Double],
margin_main_end : Array[Double],
min_main_sizes : Array[Double],
max_main_sizes : Array[Double?],
main_available_pre : AvailableSpace,
parent_available_for_children : Size[AvailableSpace],
) -> Array[Double] raise ChicleError {
let tree = view.tree
let flow_count = flow_children.length()
let count = indices.length()
let line_main_sizes : Array[Double] = Array::make(flow_count, 0.0)
let line_frozen : Array[Bool] = Array::make(flow_count, false)
let line_violations : Array[Double] = Array::make(flow_count, 0.0)
for idx in indices {
line_main_sizes[idx] = get_main(base_sizes[idx], is_col)
}
let line_gap_main = if count > 1 {
gap_main * (count - 1).to_double()
} else {
0.0
}
let mut line_initial_used = line_gap_main
for idx in indices {
line_initial_used = line_initial_used +
margin_main_start[idx] +
margin_main_end[idx] +
line_main_sizes[idx]
}
let line_initial_free_space = container_main - line_initial_used
while true {
let mut all_line_frozen = true
for idx in indices {
if !line_frozen[idx] {
all_line_frozen = false
}
}
if all_line_frozen {
break
}
let mut used = line_gap_main
for idx in indices {
used = used +
margin_main_start[idx] +
margin_main_end[idx] +
(if line_frozen[idx] {
line_main_sizes[idx]
} else {
get_main(base_sizes[idx], is_col)
})
}
let free_space_raw = container_main - used
let mut sum_grow = 0.0
let mut sum_shrink = 0.0
let mut sum_scaled_shrink = 0.0
for idx in indices {
if !line_frozen[idx] {
sum_grow = sum_grow + flex_grow[idx]
sum_shrink = sum_shrink + flex_shrink[idx]
sum_scaled_shrink = sum_scaled_shrink +
get_main(base_sizes[idx], is_col) * flex_shrink[idx]
}
}
let free_space = if free_space_raw > 0.0 && sum_grow > 0.0 && sum_grow < 1.0 {
let scaled = line_initial_free_space * sum_grow
if @util.abs_double(scaled) < @util.abs_double(free_space_raw) {
scaled
} else {
free_space_raw
}
} else if free_space_raw < 0.0 && sum_shrink > 0.0 && sum_shrink < 1.0 {
let scaled = line_initial_free_space * sum_shrink
if @util.abs_double(scaled) < @util.abs_double(free_space_raw) {
scaled
} else {
free_space_raw
}
} else {
free_space_raw
}
for idx in indices {
if !line_frozen[idx] {
let base_main = get_main(base_sizes[idx], is_col)
if free_space > 0.0 && sum_grow > 0.0 {
line_main_sizes[idx] = base_main +
free_space * (flex_grow[idx] / sum_grow)
} else if free_space < 0.0 &&
sum_shrink > 0.0 &&
sum_scaled_shrink > 0.0 {
let scaled = base_main * flex_shrink[idx]
line_main_sizes[idx] = base_main +
free_space * (scaled / sum_scaled_shrink)
} else {
line_main_sizes[idx] = base_main
}
}
}
for idx in indices {
let child_for_min = match tree.nodes.get(flow_children[idx]) {
Some(c) => c
None => raise InvalidNodeId(flow_children[idx])
}
let has_definite_basis = @util.resolve_optional_dimension(
child_for_min.style.flex_basis,
main_available_pre,
)
is Some(_)
let should_resolve_auto_min = free_space < 0.0 ||
has_definite_basis ||
(
@util.double_approx_equal(free_space, 0.0) &&
tree.node_context_data[flow_children[idx]] is Some(_) &&
main_available_pre is AvailMaxContent
)
if !line_frozen[idx] &&
min_main_sizes[idx] < 0.0 &&
should_resolve_auto_min {
let auto_min = resolve_flex_auto_min_main(
view,
flow_children[idx],
is_col,
parent_available_for_children,
main_available_pre,
)
min_main_sizes[idx] = auto_min
}
}
let mut total_violation = 0.0
for idx in indices {
if !line_frozen[idx] {
let unclamped = line_main_sizes[idx]
let min_main = min_main_sizes[idx]
let max_main = max_main_sizes[idx]
let mut clamped = unclamped
if clamped < min_main {
clamped = min_main
}
match max_main {
Some(v) => if clamped > v { clamped = v }
None => ()
}
if clamped < 0.0 {
clamped = 0.0
}
line_violations[idx] = clamped - unclamped
line_main_sizes[idx] = clamped
total_violation = total_violation + line_violations[idx]
}
}
for idx in indices {
if !line_frozen[idx] {
if total_violation > 0.0 {
line_frozen[idx] = line_violations[idx] > 0.0
} else if total_violation < 0.0 {
line_frozen[idx] = line_violations[idx] < 0.0
} else {
line_frozen[idx] = true
}
}
}
}
line_main_sizes
}
///|
fn compute_single_line_auto_intrinsic_main(
flow_count : Int,
gap_main : Double,
base_main_sizes : Array[Double],
preferred_main_sizes : Array[Double?],
min_main_sizes : Array[Double],
max_main_sizes : Array[Double?],
flex_grow : Array[Double],
flex_shrink : Array[Double],
margin_main_start : Array[Double],
margin_main_end : Array[Double],
content_main_sizes : Array[Double],
) -> Double {
let mut intrinsic_main = if flow_count > 1 {
gap_main * (flow_count - 1).to_double()
} else {
0.0
}
for i in 0.. @util.max_double(flex_basis, pref)
None => flex_basis
}
let flex_basis_min = if flex_shrink[i] == 0.0 {
Some(clamping_basis)
} else {
None
}
let flex_basis_max = if flex_grow[i] == 0.0 {
Some(clamping_basis)
} else {
None
}
let min_main = @util.max_double(
match flex_basis_min {
Some(v) => v
None => resolved_min_main
},
resolved_min_main,
)
let max_main = match (max_main_sizes[i], flex_basis_max) {
(Some(a), Some(b)) => Some(@util.min_double(a, b))
(Some(a), None) => Some(a)
(None, Some(b)) => Some(b)
(None, None) => None
}
let contribution = match (preferred_main_sizes[i], max_main) {
(Some(pref), Some(max_v)) if max_v <= min_main || max_v <= pref => {
let mut v = pref
if v > max_v {
v = max_v
}
if v < min_main {
v = min_main
}
v
}
(_, Some(max_v)) if max_v <= min_main => min_main
_ => {
let mut v = content_main_sizes[i]
if v < min_main {
v = min_main
}
match max_main {
Some(max_v) => if v > max_v { v = max_v }
None => ()
}
v
}
}
intrinsic_main = intrinsic_main +
margin_main_start[i] +
contribution +
margin_main_end[i]
}
intrinsic_main
}