use skia_safe::{Rect, Size};
use super::{Kids, LayoutProps, arrange_kid, measure_kid};
use crate::control::LayoutCx;
use crate::layout::{desired, fills_height, fills_width, snap};
use crate::tree::ControlId;
use crate::types::IntoProp;
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum GridLength {
Absolute(f32),
Auto,
Star(f32),
}
impl GridLength {
pub const STAR: GridLength = GridLength::Star(1.0);
fn parse(text: &str) -> GridLength {
let text = text.trim();
let number = |text: &str| text.trim().parse::<f32>().ok();
let parsed = match text.strip_suffix('*') {
Some("") => Some(GridLength::STAR),
Some(weight) => number(weight).map(GridLength::Star),
None if text.eq_ignore_ascii_case("auto") => Some(GridLength::Auto),
None => number(text).map(GridLength::Absolute),
};
parsed.unwrap_or_else(|| panic!("grid length `{text}`: expected a number, `Auto`, `*` or a weight like `2*`"))
}
}
impl IntoProp<GridLength> for &str {
fn into_prop(self) -> GridLength {
GridLength::parse(self)
}
}
impl IntoProp<Vec<GridLength>> for &str {
fn into_prop(self) -> Vec<GridLength> {
self.split(',').map(GridLength::parse).collect()
}
}
impl<const N: usize> IntoProp<Vec<GridLength>> for [GridLength; N] {
fn into_prop(self) -> Vec<GridLength> {
self.to_vec()
}
}
#[derive(Clone, Copy)]
struct Track {
length: GridLength,
size: f64,
}
impl Track {
fn new(length: GridLength) -> Track {
Track { length, size: if let GridLength::Absolute(points) = length { points as f64 } else { 0.0 } }
}
fn is_auto(&self) -> bool {
self.length == GridLength::Auto
}
fn is_star(&self) -> bool {
matches!(self.length, GridLength::Star(_))
}
fn weight(&self) -> f64 {
if let GridLength::Star(weight) = self.length { weight as f64 } else { 0.0 }
}
}
#[derive(Clone, Copy)]
struct Cell {
child: ControlId,
column: usize,
row: usize,
columns: usize,
rows: usize,
content: Option<((f32, f32), (f64, f64))>,
}
struct Span {
start: usize,
length: usize,
is_column: bool,
requested: f64,
}
#[derive(Default)]
pub(crate) struct Grid {
columns: Vec<Track>,
rows: Vec<Track>,
cells: Vec<Cell>,
previous: Vec<Cell>,
spans: Vec<Span>,
}
fn start(tracks: &mut Vec<Track>, definitions: &[GridLength], default: GridLength, split: i32) {
tracks.clear();
tracks.extend(definitions.iter().map(|length| Track::new(*length)));
let split = if definitions.is_empty() || split > 1 { split.max(0) as usize } else { 0 };
while tracks.len() < split {
tracks.push(Track::new(GridLength::STAR));
}
if tracks.is_empty() {
tracks.push(Track::new(default));
}
}
fn total(tracks: &[Track], gap: f64) -> f64 {
let mut sum = 0.0;
for (index, track) in tracks.iter().enumerate() {
sum += track.size;
if index > 0 {
sum += gap;
}
}
sum
}
fn extent(tracks: &[Track], start: usize, length: usize, gap: f64) -> f64 {
tracks[start..start + length].iter().map(|track| track.size).sum::<f64>() + (length - 1) as f64 * gap
}
fn edge(tracks: &[Track], index: usize, gap: f64) -> f64 {
let mut edge = 0.0;
for track in &tracks[..index] {
edge += track.size;
edge += gap;
}
edge
}
fn kinds(tracks: &[Track]) -> (bool, bool, bool) {
let (auto, star) = (tracks.iter().any(Track::is_auto), tracks.iter().any(Track::is_star));
(auto, star, !auto && !star)
}
fn room(tracks: &[Track], start: usize, length: usize, gap: f64, constraint: f64) -> f64 {
let own = extent(tracks, start, length, gap);
if kinds(&tracks[start..start + length]).2 { own } else { constraint - total(tracks, gap) + own }
}
fn resolve_stars(tracks: &mut [Track], gap: f64, constraint: f64, content: f64) {
for track in tracks.iter_mut().filter(|track| track.is_star()) {
track.size = 0.0;
}
let weights: f64 = tracks.iter().map(Track::weight).sum();
if weights > 0.0 {
let share = if constraint.is_finite() { ((constraint - total(tracks, gap)) / weights).max(0.0) } else { content };
for track in tracks.iter_mut().filter(|track| track.is_star()) {
track.size = share * track.weight();
}
}
}
fn resolve_span(tracks: &mut [Track], gap: f64, requested: f64) {
let missing = requested - total(tracks, gap);
let autos = tracks.iter().filter(|track| track.is_auto()).count();
if missing <= 0.0 || autos == 0 || kinds(tracks).1 {
return;
}
for track in tracks.iter_mut().filter(|track| track.is_auto()) {
track.size += missing / autos as f64;
}
}
impl Grid {
pub(crate) fn measure(&mut self, cx: &mut LayoutCx, p: &LayoutProps, kids: &mut Kids, w: f32, h: f32, auto: (bool, bool)) -> Size {
let scale = cx.scale;
let points = |pixels: f32| (pixels / scale) as f64;
let pixels = |points: f64| (points * scale as f64) as f32;
let (column_gap, row_gap) = (p.column_spacing as f64, p.row_spacing as f64);
let (width, height) = (points(w), points(h));
let Grid { columns, rows, cells, previous, spans } = self;
start(columns, &p.column_definitions, p.default_column_definition, p.split);
start(rows, &p.row_definitions, p.default_row_definition, 0);
std::mem::swap(cells, previous);
cells.clear();
for i in 0..cx.child_count() {
let child = cx.child(i);
let cp = &cx.child_base(child).p;
if !cp.is_visible {
continue;
}
let (column, row) = (cp.column.max(0) as usize, cp.row.max(0) as usize);
let (across, down) = (cp.column_span.max(1) as usize, cp.row_span.max(1) as usize);
while columns.len() < column + across {
columns.push(Track::new(p.default_column_definition));
}
while rows.len() < row + down {
rows.push(Track::new(p.default_row_definition));
}
cells.push(Cell { child, column, row, columns: across, rows: down, content: None });
}
spans.clear();
let mut in_stars = (0f64, 0f64);
for (index, cell) in cells.iter_mut().enumerate() {
let (column_auto, column_star, _) = kinds(&columns[cell.column..cell.column + cell.columns]);
let (row_auto, row_star, _) = kinds(&rows[cell.row..cell.row + cell.rows]);
if !(column_auto || row_auto || (column_star && !w.is_finite()) || (row_star && !h.is_finite())) {
continue;
}
let room_w = room(columns, cell.column, cell.columns, column_gap, width);
let room_h = room(rows, cell.row, cell.rows, row_gap, height);
let base = cx.child_base(cell.child);
let (unbound_w, unbound_h) = (column_auto && fills_width(&base.p), row_auto && fills_height(&base.p));
let offer_w = if unbound_w { f32::INFINITY } else { pixels(room_w).max(0.0) };
let offer_h = if unbound_h { f32::INFINITY } else { pixels(room_h).max(0.0) };
let same = |a: f32, b: f32| a.to_bits() == b.to_bits();
let kept = previous
.get(index)
.filter(|old| old.child == cell.child && !base.need_measure && base.scale == scale)
.and_then(|old| old.content)
.filter(|(offered, _)| same(offered.0, offer_w) && same(offered.1, offer_h));
let size = match kept {
Some((_, size)) => size,
None => {
measure_kid(kids, cx, cell.child, offer_w, offer_h);
let raw = desired(cx.child_base(cell.child));
(points(raw.width), points(raw.height))
}
};
cell.content = Some(((offer_w, offer_h), size));
if column_star {
in_stars.0 = in_stars.0.max(size.0);
}
if row_star {
in_stars.1 = in_stars.1.max(size.1);
}
let requested_w = if unbound_w && room_w.is_finite() && room_w >= 0.0 { size.0.min(room_w) } else { size.0 };
let requested_h = if unbound_h && room_h.is_finite() && room_h >= 0.0 { size.1.min(room_h) } else { size.1 };
let mut track = |tracks: &mut [Track], start: usize, length: usize, is_column: bool, requested: f64| {
if length == 1 {
tracks[start].size = tracks[start].size.max(requested);
} else if let Some(span) = spans.iter_mut().find(|s| (s.start, s.length, s.is_column) == (start, length, is_column)) {
span.requested = span.requested.max(requested);
} else {
spans.push(Span { start, length, is_column, requested });
}
};
if column_auto {
track(columns, cell.column, cell.columns, true, requested_w);
}
if row_auto {
track(rows, cell.row, cell.rows, false, requested_h);
}
}
for span in spans.iter() {
let (tracks, gap) = if span.is_column { (&mut *columns, column_gap) } else { (&mut *rows, row_gap) };
resolve_span(&mut tracks[span.start..span.start + span.length], gap, span.requested);
}
for cell in cells.iter() {
let cp = &cx.child_base(cell.child).p;
if fills_width(cp) && cp.minimum_width_request >= 0.0 {
let minimum = (cp.minimum_width_request + cp.margin.horizontal()) as f64 / cell.columns as f64;
for track in &mut columns[cell.column..cell.column + cell.columns] {
track.size = track.size.max(minimum);
}
}
if fills_height(cp) && cp.minimum_height_request >= 0.0 {
let minimum = (cp.minimum_height_request + cp.margin.vertical()) as f64 / cell.rows as f64;
for track in &mut rows[cell.row..cell.row + cell.rows] {
track.size = track.size.max(minimum);
}
}
}
resolve_stars(columns, column_gap, width, in_stars.0);
resolve_stars(rows, row_gap, height, in_stars.1);
if !auto.0 && width.is_finite() && total(columns, column_gap) < width {
columns.last_mut().expect("a grid has a column").size += width - total(columns, column_gap);
}
if !auto.1 && height.is_finite() && total(rows, row_gap) < height {
rows.last_mut().expect("a grid has a row").size += height - total(rows, row_gap);
}
for cell in cells.iter() {
let cell_width = extent(columns, cell.column, cell.columns, column_gap);
let cell_height = extent(rows, cell.row, cell.rows, row_gap);
if cell_width <= 0.0 || cell_height <= 0.0 {
continue;
}
let offer_h = (cell_height * scale as f64).round_ties_even() as f32;
measure_kid(kids, cx, cell.child, pixels(cell_width), offer_h);
let raw = desired(cx.child_base(cell.child));
if cell.columns == 1 && columns[cell.column].is_auto() {
columns[cell.column].size = columns[cell.column].size.max(points(raw.width));
}
if cell.rows == 1 && rows[cell.row].is_auto() {
rows[cell.row].size = rows[cell.row].size.max(points(raw.height));
}
}
Size::new(snap(pixels(total(columns, column_gap))), snap(pixels(total(rows, row_gap))))
}
pub(crate) fn columns(&self) -> usize {
self.columns.len()
}
pub(crate) fn rows(&self) -> usize {
self.rows.len()
}
pub(crate) fn column_gap(&self, index: usize, gap: f32, left: f32, scale: f32) -> (f32, f32) {
Self::gap_before(&self.columns, index, gap, left, scale)
}
pub(crate) fn row_gap(&self, index: usize, gap: f32, top: f32, scale: f32) -> (f32, f32) {
Self::gap_before(&self.rows, index, gap, top, scale)
}
fn gap_before(tracks: &[Track], index: usize, gap: f32, from: f32, scale: f32) -> (f32, f32) {
let pixels = |points: f64| (points * scale as f64) as f32;
let end = edge(tracks, index, gap as f64) + (from / scale) as f64;
(pixels(end - gap as f64), pixels(end))
}
pub(crate) fn arrange(&self, cx: &mut LayoutCx, p: &LayoutProps, kids: &mut Kids, left: f32, top: f32) {
let scale = cx.scale;
let pixels = |points: f64| (points * scale as f64) as f32;
let (column_gap, row_gap) = (p.column_spacing as f64, p.row_spacing as f64);
let (left, top) = ((left / scale) as f64, (top / scale) as f64);
for cell in &self.cells {
let x = edge(&self.columns, cell.column, column_gap) + left;
let y = edge(&self.rows, cell.row, row_gap) + top;
let right = x + extent(&self.columns, cell.column, cell.columns, column_gap);
let bottom = y + extent(&self.rows, cell.row, cell.rows, row_gap);
arrange_kid(kids, cx, cell.child, Rect::new(pixels(x), pixels(y), pixels(right), pixels(bottom)));
}
}
}