// 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 resolve_track_dimension(d : Dimension, available : Double) -> Double {
match d {
DimAuto => 0.0
DimLength(v) => v
DimPercent(p) => available * p
DimFr(_) => 0.0
DimMinMax(_, max) => resolve_track_dimension(max, available)
DimMinContent => 0.0
DimMaxContent => 0.0
DimFitContent(limit) => resolve_track_dimension(limit, available)
DimRepeat(_, _) => 0.0
}
}
///|
fn is_near_int(v : Double) -> Bool {
@util.abs_double(v - v.floor()) < 0.000001
}
///|
priv struct GridTrackSizingState {
sizes : Array[Double]
min_sizes : Array[Double]
fr_weights : Array[Double]
stretch_auto : Array[Bool]
max_limits : Array[Double]
inf : Double
}
///|
priv struct GridTrackSizingAlgorithmInput {
tracks : Array[Dimension]
available : Double
gap : Double
min_contribution : Array[Double]
max_content_contribution : Array[Double]
expand_to_fill : Bool
}
///|
priv struct GridTrackSizingAlgorithmOutput {
sizes : Array[Double]
}
///|
fn GridTrackSizingAlgorithmInput::GridTrackSizingAlgorithmInput(
tracks~ : Array[Dimension],
available~ : Double,
gap~ : Double,
min_contribution~ : Array[Double],
max_content_contribution~ : Array[Double],
expand_to_fill~ : Bool,
) -> GridTrackSizingAlgorithmInput {
{
tracks,
available,
gap,
min_contribution,
max_content_contribution,
expand_to_fill,
}
}
///|
fn clamp_track_size(v : Double, lo : Double, hi : Double) -> Double {
let clamped_lo = if v < lo { lo } else { v }
if clamped_lo > hi {
hi
} else {
clamped_lo
}
}
///|
fn grid_track_min_function_value(
d : Dimension,
available : Double,
min_c : Double,
max_c : Double,
) -> Double {
match d {
DimAuto => 0.0
DimMinContent => min_c
DimMaxContent => max_c
DimFitContent(limit) => {
let lim = resolve_track_dimension(limit, available)
let max_limited = if max_c < lim { max_c } else { lim }
if max_limited < min_c {
min_c
} else {
max_limited
}
}
DimFr(_) => 0.0
DimRepeat(_, _) => 0.0
_ => {
let v = resolve_track_dimension(d, available)
if v > 0.0 {
v
} else {
0.0
}
}
}
}
///|
fn grid_track_max_function_value_and_limit(
d : Dimension,
available : Double,
min_c : Double,
max_c : Double,
inf : Double,
) -> (Double, Double, Double) {
match d {
DimAuto => (max_c, inf, 0.0)
DimMinContent => (min_c, inf, 0.0)
DimMaxContent => (max_c, inf, 0.0)
DimFitContent(limit) => {
let lim = resolve_track_dimension(limit, available)
let preferred = if max_c < lim { max_c } else { lim }
let preferred = if preferred < min_c { min_c } else { preferred }
(preferred, preferred, 0.0)
}
DimFr(w) => (0.0, inf, if w > 0.0 { w } else { 0.0 })
DimRepeat(_, _) => (0.0, inf, 0.0)
_ => {
let v = resolve_track_dimension(d, available)
let v = if v > 0.0 { v } else { 0.0 }
(v, v, 0.0)
}
}
}
///|
fn initialize_track_sizes(
input : GridTrackSizingAlgorithmInput,
) -> GridTrackSizingState {
let tracks = input.tracks
let available = input.available
let min_contrib = input.min_contribution
let max_contrib = input.max_content_contribution
let count = tracks.length()
let sizes : Array[Double] = Array::make(count, 0.0)
let min_sizes : Array[Double] = Array::make(count, 0.0)
let fr_weights : Array[Double] = Array::make(count, 0.0)
let stretch_auto : Array[Bool] = Array::make(count, false)
let max_limits : Array[Double] = Array::make(count, 0.0)
let inf = 1.0e30
for i in 0.. {
min_sizes[i] = min_c
sizes[i] = max_c
max_limits[i] = inf
stretch_auto[i] = true
}
DimMinContent => {
min_sizes[i] = min_c
sizes[i] = min_c
max_limits[i] = inf
}
DimMaxContent => {
min_sizes[i] = max_c
sizes[i] = max_c
max_limits[i] = inf
}
DimFitContent(limit) => {
let lim = resolve_track_dimension(limit, available)
let preferred = if max_c < lim { max_c } else { lim }
let preferred = if preferred < min_c { min_c } else { preferred }
min_sizes[i] = min_c
sizes[i] = preferred
max_limits[i] = preferred
}
DimLength(v) => {
sizes[i] = v
min_sizes[i] = v
max_limits[i] = v
}
DimPercent(p) => {
let v = available * p
sizes[i] = v
min_sizes[i] = v
max_limits[i] = v
}
DimFr(w) => {
fr_weights[i] = if w > 0.0 { w } else { 0.0 }
let base = if w > 0.0 { min_c } else { max_c }
sizes[i] = base
min_sizes[i] = base
max_limits[i] = inf
}
DimMinMax(min_d, max_d) => {
let min_v = grid_track_min_function_value(
min_d, available, min_c, max_c,
)
let max_r = grid_track_max_function_value_and_limit(
max_d, available, min_c, max_c, inf,
)
let max_v = max_r.0
let max_lim = max_r.1
let fr_w = max_r.2
min_sizes[i] = min_v
if fr_w > 0.0 {
fr_weights[i] = fr_w
sizes[i] = min_v
max_limits[i] = inf
} else {
let preferred = if max_v < min_v { min_v } else { max_v }
sizes[i] = preferred
max_limits[i] = if max_lim < min_v { min_v } else { max_lim }
stretch_auto[i] = match max_d {
DimAuto => true
_ => false
}
}
}
DimRepeat(_, _) => {
// Expanded earlier; treat as auto when encountered.
min_sizes[i] = min_c
sizes[i] = max_c
max_limits[i] = inf
stretch_auto[i] = true
}
}
}
{ sizes, min_sizes, fr_weights, stretch_auto, max_limits, inf }
}
///|
fn resolve_intrinsic_track_sizes(
state : GridTrackSizingState,
input : GridTrackSizingAlgorithmInput,
) -> Unit {
let min_contrib = input.min_contribution
let max_contrib = input.max_content_contribution
let sizes = state.sizes
let min_sizes = state.min_sizes
let fr_weights = state.fr_weights
let max_limits = state.max_limits
let inf = state.inf
let count = sizes.length()
let mut flex_fraction = 0.0
for i in 0.. 0.0 {
let max_c = if i < max_contrib.length() { max_contrib[i] } else { 0.0 }
let ratio = max_c / weight
if ratio > flex_fraction {
flex_fraction = ratio
}
}
}
if flex_fraction > 0.0 {
for i in 0.. 0.0 {
let flex_size = flex_fraction * weight
sizes[i] = if flex_size < min_sizes[i] {
min_sizes[i]
} else {
flex_size
}
min_sizes[i] = if i < min_contrib.length() {
min_contrib[i]
} else {
0.0
}
max_limits[i] = inf
}
}
}
}
///|
fn grid_track_used_size(state : GridTrackSizingState, gap : Double) -> Double {
let sizes = state.sizes
let mut used = 0.0
for size in sizes {
used = used + size
}
let count = sizes.length()
if count > 1 {
used + gap * (count - 1).to_double()
} else {
used
}
}
///|
fn shrink_tracks_to_minimum(
state : GridTrackSizingState,
available : Double,
gap : Double,
) -> Unit {
let sizes = state.sizes
let min_sizes = state.min_sizes
let used = grid_track_used_size(state, gap)
if used <= available {
return
}
let eps = 0.000001
let mut over = used - available
let mut shrinkable : Array[Int] = []
for i in 0.. min_sizes[i] + eps {
shrinkable.push(i)
}
}
while over > eps && shrinkable.length() > 0 {
let per_track = over / shrinkable.length().to_double()
let mut shrunk = 0.0
let next : Array[Int] = []
for idx in shrinkable {
let can_shrink = sizes[idx] - min_sizes[idx]
let shrink = if can_shrink < per_track { can_shrink } else { per_track }
if shrink > 0.0 {
sizes[idx] = sizes[idx] - shrink
shrunk = shrunk + shrink
}
if sizes[idx] > min_sizes[idx] + eps {
next.push(idx)
}
}
if shrunk <= 0.0 {
break
}
over = over - shrunk
shrinkable = next
}
}
///|
fn expand_flexible_tracks(
state : GridTrackSizingState,
available : Double,
gap : Double,
) -> Bool {
let sizes = state.sizes
let min_sizes = state.min_sizes
let fr_weights = state.fr_weights
let mut sum_fr = 0.0
for weight in fr_weights {
sum_fr = sum_fr + weight
}
if sum_fr <= 0.0 {
return false
}
let mut fixed_used = 0.0
for i in 0.. 1 { sizes.length() - 1 } else { 0 }
let gap_total = gap * gap_count.to_double()
let mut remaining_for_fr = @util.max_double(
available - fixed_used - gap_total,
0.0,
)
let mut unresolved : Array[Int] = []
for i in 0.. 0.0 {
unresolved.push(i)
sizes[i] = min_sizes[i]
}
}
while unresolved.length() > 0 {
let mut unresolved_sum = 0.0
for idx in unresolved {
unresolved_sum = unresolved_sum + fr_weights[idx]
}
let denom = if unresolved_sum < 1.0 { 1.0 } else { unresolved_sum }
let flex_fraction = remaining_for_fr / denom
let mut froze_any = false
let next_unresolved : Array[Int] = []
for idx in unresolved {
let proposed = flex_fraction * fr_weights[idx]
if proposed + 0.000001 < min_sizes[idx] {
sizes[idx] = min_sizes[idx]
remaining_for_fr = @util.max_double(
remaining_for_fr - min_sizes[idx],
0.0,
)
froze_any = true
} else {
next_unresolved.push(idx)
}
}
if !froze_any {
let total_fr_space = if unresolved_sum < 1.0 {
remaining_for_fr * unresolved_sum
} else {
remaining_for_fr
}
if is_near_int(total_fr_space) {
let total_int = total_fr_space.floor().to_int()
let raw_bases : Array[Int] = []
let raw_fracs : Array[Double] = []
let mut sum_bases = 0
for idx in unresolved {
let raw = flex_fraction * fr_weights[idx]
let base = raw.floor().to_int()
raw_bases.push(base)
raw_fracs.push(raw - base.to_double())
sum_bases = sum_bases + base
}
let mut remainder = total_int - sum_bases
while remainder > 0 {
let mut best_j = 0
let mut best_frac = -1.0
for j in 0.. best_frac {
best_frac = raw_fracs[j]
best_j = j
}
}
raw_bases[best_j] = raw_bases[best_j] + 1
raw_fracs[best_j] = -1.0
remainder = remainder - 1
}
for j in 0.. Unit {
let sizes = state.sizes
let max_limits = state.max_limits
let mut remaining = free
let mut growable : Array[Int] = []
for i in 0.. 0.000001 && growable.length() > 0 {
let extra = remaining / growable.length().to_double()
let mut used_extra = 0.0
let next : Array[Int] = []
for idx in growable {
let cap = max_limits[idx] - sizes[idx]
let increase = if cap < extra { cap } else { extra }
if increase > 0.0 {
sizes[idx] = sizes[idx] + increase
used_extra = used_extra + increase
}
if sizes[idx] + 0.000001 < max_limits[idx] {
next.push(idx)
}
}
if used_extra <= 0.0 {
break
}
remaining = remaining - used_extra
growable = next
}
}
///|
fn maximize_tracks(
state : GridTrackSizingState,
available : Double,
gap : Double,
) -> Unit {
let used = grid_track_used_size(state, gap)
let free = available - used
if free <= 0.0 {
return
}
distribute_space_up_to_growth_limits(state, free)
}
///|
fn stretch_auto_tracks(
state : GridTrackSizingState,
available : Double,
gap : Double,
) -> Unit {
let used = grid_track_used_size(state, gap)
let free = available - used
if free <= 0.0 {
return
}
let sizes = state.sizes
let stretch_auto = state.stretch_auto
let stretchable : Array[Int] = []
for i in 0.. Unit {
let sizes = state.sizes
let min_sizes = state.min_sizes
for i in 0.. GridTrackSizingAlgorithmOutput {
let state = initialize_track_sizes(input)
let sizes = state.sizes
// Intrinsic sizing for flexible tracks: derive a flex fraction from content contributions.
// This matches chicle 0.5 behavior for `fr` tracks under indefinite available space.
if !input.expand_to_fill {
resolve_intrinsic_track_sizes(state, input)
}
// Shrink tracks down to their min-content contributions if the sum of preferred sizes overflows.
shrink_tracks_to_minimum(state, input.available, input.gap)
// Distribute any remaining free space (only when the grid container has a definite size).
if input.expand_to_fill {
maximize_tracks(state, input.available, input.gap)
if expand_flexible_tracks(state, input.available, input.gap) {
()
} else {
()
}
stretch_auto_tracks(state, input.available, input.gap)
}
// Ensure no track is below its min size.
clamp_grid_tracks_to_minimum(state)
{ sizes, }
}