// 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.
///|
priv struct GridTrackContributions {
column_minimum : Array[Double]
column_max_content : Array[Double]
row_minimum : Array[Double]
row_max_content : Array[Double]
}
///|
fn GridTrackContributions::GridTrackContributions(
column_count~ : Int,
row_count~ : Int,
) -> GridTrackContributions {
{
column_minimum: Array::make(column_count, 0.0),
column_max_content: Array::make(column_count, 0.0),
row_minimum: Array::make(row_count, 0.0),
row_max_content: Array::make(row_count, 0.0),
}
}
///|
fn GridTrackContributions::reset(self : GridTrackContributions) -> Unit {
for i in 0.. Unit {
apply_col_contribution(
col_tracks,
item.column,
item.column_span,
item.column_minimum_contribution,
self.column_minimum,
)
apply_col_contribution(
col_tracks,
item.column,
item.column_span,
item.column_max_content_contribution,
self.column_max_content,
)
}
///|
fn GridTrackContributions::apply_row_item(
self : GridTrackContributions,
item : GridItem,
) -> Unit {
let min_share = item.row_minimum_contribution / item.row_span.to_double()
let max_share = item.row_max_content_contribution / item.row_span.to_double()
for j in item.row..<(item.row + item.row_span) {
if min_share > self.row_minimum[j] {
self.row_minimum[j] = min_share
}
if max_share > self.row_max_content[j] {
self.row_max_content[j] = max_share
}
}
}
///|
fn compute_alignment_gutter_adjustment(
alignment : AlignContent,
axis_inner_node_size : Double?,
track_sizes : Array[Double],
gap : Double,
) -> Double {
if track_sizes.length() <= 1 {
return 0.0
}
let inner_gutter_weight = match alignment {
AlignSpaceBetween => 1
AlignSpaceAround => 2
AlignSpaceEvenly => 1
_ => 0
}
if inner_gutter_weight == 0 {
return 0.0
}
let outer_gutter_weight = match alignment {
AlignFlexStart | AlignStart | AlignFlexEnd | AlignEnd | AlignCenter => 1
AlignSpaceAround | AlignSpaceEvenly => 1
_ => 0
}
match axis_inner_node_size {
Some(inner_size) => {
let mut track_size_sum = 0.0
for size in track_sizes {
track_size_sum = track_size_sum + size
}
track_size_sum = track_size_sum +
gap * (track_sizes.length() - 1).to_double()
let free_space = @util.max_double(inner_size - track_size_sum, 0.0)
let weighted_gutter_count = (track_sizes.length() - 1) *
inner_gutter_weight +
2 * outer_gutter_weight
if weighted_gutter_count == 0 {
0.0
} else {
free_space /
weighted_gutter_count.to_double() *
inner_gutter_weight.to_double()
}
}
None => 0.0
}
}
///|
fn grid_item_contribution_column_width(
item : GridItem,
col_sizes : Array[Double],
col_gap : Double,
col_gutter_alignment_adjustment : Double,
) -> Double {
let mut col_w = 0.0
for j in item.column..<(item.column + item.column_span) {
col_w = col_w + col_sizes[j]
}
if item.column_span > 1 {
col_w = col_w +
(col_gap + col_gutter_alignment_adjustment) *
(item.column_span - 1).to_double()
}
col_w
}
///|
fn grid_item_known_width_for_row_contribution(
child_style : Style,
col_w : Double,
resolved_margin : Rect[Double],
default_justify_items_for_contrib : AlignItems,
) -> Double? {
let margin_left_auto = child_style.margin.left is DimAuto
let margin_right_auto = child_style.margin.right is DimAuto
let has_auto_margin_x = margin_left_auto || margin_right_auto
let justify = match child_style.justify_self {
Some(value) => value
None => default_justify_items_for_contrib
}
let available_w_for_item = @util.max_double(
col_w - resolved_margin.left - resolved_margin.right,
0.0,
)
let mut known_w_for_contrib = @util.resolve_optional_dimension(
child_style.size.width,
AvailDefinite(col_w),
)
if known_w_for_contrib is None &&
child_style.size.width is DimAuto &&
justify is ItemsStretch &&
!has_auto_margin_x {
known_w_for_contrib = Some(available_w_for_item)
}
known_w_for_contrib
}
///|
fn[C] resolve_grid_item_baseline_shims_for_track_sizing(
view : ChicleView[C],
grid_items : Array[GridItem],
placement_count : Int,
col_sizes : Array[Double],
col_gap : Double,
col_gutter_alignment_adjustment : Double,
default_align_items_for_contrib : AlignItems,
default_justify_items_for_contrib : AlignItems,
) -> Unit raise ChicleError {
let mut row_count = 0
for item in grid_items {
if item.placed && item.row_span == 1 && item.row + 1 > row_count {
row_count = item.row + 1
}
}
if row_count == 0 {
return
}
let baseline_item_count : Array[Int] = Array::make(row_count, 0)
let row_max_baseline : Array[Double] = Array::make(row_count, 0.0)
let measured_baseline : Array[Double] = Array::make(placement_count, 0.0)
let measured_margin_top : Array[Double] = Array::make(placement_count, 0.0)
let tree = view.tree
for i in 0.. v
None => default_align_items_for_contrib
}
if !(align is ItemsBaseline) {
continue
}
baseline_item_count[item.row] = baseline_item_count[item.row] + 1
}
for i in 0.. Unit raise ChicleError {
let tree = view.tree
for i in 0.. @geometry.AvailDefinite(content_height)
None =>
if use_row_track_constraints {
match
spanned_definite_len(row_tracks, item.row, item.row_span, row_gap) {
Some(size) => AvailDefinite(size)
None => AvailMaxContent
}
} else {
AvailMaxContent
}
}
view.perform_child_layout(
child_id,
Size(width=None, height=None),
Size(width=AvailMaxContent, height=contrib_available_height),
Point::zero(),
false,
)
let max_sz = tree.nodes[child_id].unrounded_layout.size
let resolved_margin = @util.resolve_rect_width_basis(
child_style.margin,
Size(width=AvailMaxContent, height=AvailMaxContent),
)
let max_needed = @util.max_double(
max_sz.width + resolved_margin.left + resolved_margin.right - gap_total,
0.0,
)
view.perform_child_layout(
child_id,
Size(width=None, height=None),
Size(width=AvailMinContent, height=contrib_available_height),
Point::zero(),
false,
)
let min_col_raw = tree.nodes[child_id].unrounded_layout.size.width
let min_col = match (child_style.overflow.x, child_style.overflow.y) {
(OverflowVisible, OverflowVisible) => min_col_raw
_ => 0.0
}
let min_content_needed = @util.max_double(
min_col_raw + resolved_margin.left + resolved_margin.right - gap_total,
0.0,
)
let min_needed = @util.max_double(
min_col + resolved_margin.left + resolved_margin.right - gap_total,
0.0,
)
item.set_column_contributions(
minimum=min_needed,
min_content=min_content_needed,
max_content=max_needed,
)
contributions.apply_column_item(item, col_tracks)
}
}
///|
fn[C] compute_grid_row_track_contributions(
view : ChicleView[C],
grid_items : Array[GridItem],
placement_count : Int,
col_sizes : Array[Double],
contributions : GridTrackContributions,
col_gap : Double,
col_gutter_alignment_adjustment : Double,
row_gap : Double,
default_align_items_for_contrib : AlignItems,
default_justify_items_for_contrib : AlignItems,
) -> Unit raise ChicleError {
let tree = view.tree
resolve_grid_item_baseline_shims_for_track_sizing(
view, grid_items, placement_count, col_sizes, col_gap, col_gutter_alignment_adjustment,
default_align_items_for_contrib, default_justify_items_for_contrib,
)
for i in 0.. min_h_raw
_ => 0.0
}
let min_needed = @util.max_double(
min_h +
resolved_margin.top +
resolved_margin.bottom +
item.baseline_shim -
row_gap_total,
0.0,
)
item.set_row_contributions(minimum=min_needed, max_content=max_needed)
contributions.apply_row_item(item)
}
}