// 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 grid_area_axis_origin(
track_sizes : Array[Double],
gap : Double,
start_offset : Double,
start_index : Int,
) -> Double {
let mut origin = start_offset
for i in 0.. Double {
let mut size = 0.0
for i in start_index..<(start_index + span) {
size = size + track_sizes[i]
}
if span > 1 {
size + gap * (span - 1).to_double()
} else {
size
}
}
///|
fn GridItem::grid_area_origin(
self : GridItem,
col_sizes : Array[Double],
row_sizes : Array[Double],
col_gap : Double,
row_gap : Double,
start_x : Double,
start_y : Double,
) -> Point[Double] {
Point(
x=grid_area_axis_origin(col_sizes, col_gap, start_x, self.column),
y=grid_area_axis_origin(row_sizes, row_gap, start_y, self.row),
)
}
///|
fn GridItem::grid_area_size(
self : GridItem,
col_sizes : Array[Double],
row_sizes : Array[Double],
col_gap : Double,
row_gap : Double,
) -> Size[Double] {
Size(
width=grid_area_axis_size(col_sizes, col_gap, self.column, self.column_span),
height=grid_area_axis_size(row_sizes, row_gap, self.row, self.row_span),
)
}
///|
fn[C] compute_grid_absolute_placement(
view : ChicleView[C],
children : Array[NodeId],
node_style : Style,
col_sizes : Array[Double],
row_sizes : Array[Double],
col_gap_effective : Double,
row_gap_effective : Double,
start_x : Double,
start_y : Double,
absolute_origin : Point[Double],
border : Rect[Double],
padding : Rect[Double],
border_box_width : Double,
border_box_height : Double,
explicit_col_count : Int,
explicit_row_count : Int,
negative_cols : Int,
negative_rows : Int,
) -> Unit raise ChicleError {
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 content_start_x = padding.left + start_x
let content_start_y = padding.top + start_y
let explicit_col_lines : Array[Double] = [content_start_x]
let explicit_row_lines : Array[Double] = [content_start_y]
let mut col_line_pos = content_start_x
let mut row_line_pos = content_start_y
for i in 0..= 0 && track_idx < col_sizes.length() {
col_line_pos = col_line_pos + col_sizes[track_idx]
}
if i + 1 < explicit_col_count {
col_line_pos = col_line_pos + col_gap_effective
}
explicit_col_lines.push(col_line_pos)
}
for i in 0..= 0 && track_idx < row_sizes.length() {
row_line_pos = row_line_pos + row_sizes[track_idx]
}
if i + 1 < explicit_row_count {
row_line_pos = row_line_pos + row_gap_effective
}
explicit_row_lines.push(row_line_pos)
}
let default_abs_align = match node_style.align_items {
Some(v) => v
None => ItemsStretch
}
let default_abs_justify = match node_style.justify_items {
Some(v) => v
None => ItemsStretch
}
compute_grid_absolute_children(
view, children, explicit_col_lines, explicit_row_lines, padding_origin, padding_box_width,
padding_box_height, default_abs_align, default_abs_justify,
)
}
///|
fn[C] compute_grid_absolute_children(
view : ChicleView[C],
children : Array[NodeId],
explicit_col_lines : Array[Double],
explicit_row_lines : Array[Double],
padding_origin : Point[Double],
padding_box_width : Double,
padding_box_height : Double,
default_abs_align : AlignItems,
default_abs_justify : AlignItems,
) -> Unit raise ChicleError {
let tree = view.tree
for child_id in children {
let child = match tree.nodes.get(child_id) {
Some(c) => c
None => raise InvalidNodeId(child_id)
}
match child.style.display {
DisplayNone => ()
_ =>
match child.style.position {
PosRelative => ()
PosAbsolute => {
let col_line = merged_placement_line(
child.style.grid_column,
child.style.grid_column_start,
)
let row_line = merged_placement_line(
child.style.grid_row,
child.style.grid_row_start,
)
let cb_x = resolve_grid_abs_axis_containing_block(
col_line, explicit_col_lines, padding_box_width,
)
let cb_y = resolve_grid_abs_axis_containing_block(
row_line, explicit_row_lines, padding_box_height,
)
let containing_x = cb_x.0
let containing_y = cb_y.0
let containing_width = @util.max_double(cb_x.1 - cb_x.0, 0.0)
let containing_height = @util.max_double(cb_y.1 - cb_y.0, 0.0)
let abs_available = Size(
width=@geometry.AvailDefinite(containing_width),
height=AvailDefinite(containing_height),
)
let margin = child.style.margin
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,
containing_width,
)
let margin_right_fixed = @util.resolve_dimension_width_basis(
margin.right,
containing_width,
)
let margin_top_fixed = @util.resolve_dimension_width_basis(
margin.top,
containing_width,
)
let margin_bottom_fixed = @util.resolve_dimension_width_basis(
margin.bottom,
containing_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,
)
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
match used_width {
Some(_) => ()
None =>
match (left, right) {
(Some(l), Some(r)) =>
used_width = Some(
@util.max_double(
containing_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(
containing_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 {
()
}
_ => ()
}
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 => ()
}
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 => ()
}
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 => ()
}
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
}
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 = containing_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 = containing_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
}
_ => ()
}
}
_ => ()
}
let justify = match child.style.justify_self {
Some(v) => v
None => default_abs_justify
}
let align = match child.style.align_self {
Some(v) => v
None => default_abs_align
}
let available_width_for_static = @util.max_double(
containing_width - final_margin_left - final_margin_right,
0.0,
)
let available_height_for_static = @util.max_double(
containing_height - final_margin_top - final_margin_bottom,
0.0,
)
let static_x = final_margin_left +
(match justify {
ItemsEnd | ItemsFlexEnd =>
available_width_for_static - final_size.width
ItemsCenter =>
(available_width_for_static - final_size.width) / 2.0
_ => 0.0
})
let static_y = final_margin_top +
(match align {
ItemsEnd | ItemsFlexEnd =>
available_height_for_static - final_size.height
ItemsCenter =>
(available_height_for_static - final_size.height) / 2.0
_ => 0.0
})
let x_in_padding = match left {
Some(v) => containing_x + v + final_margin_left
None =>
match right {
Some(v) =>
containing_x +
containing_width -
v -
final_margin_right -
final_size.width
None => containing_x + static_x
}
}
let y_in_padding = match top {
Some(v) => containing_y + v + final_margin_top
None =>
match bottom {
Some(v) =>
containing_y +
containing_height -
v -
final_margin_bottom -
final_size.height
None => containing_y + static_y
}
}
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,
)
}
}
}
}
}