diff --git a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc index af3f454b4c..424dcbb34f 100644 --- a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc @@ -357,7 +357,8 @@ to-idx (+ (dec (get cell prop)) (get cell prop-span)) indexed-tracks (subvec (d/enumerate track-list) from-idx to-idx) - to-allocate (size-to-allocate type parent (get children-map shape-id) cell bounds objects) + to-allocate + (size-to-allocate type parent (get children-map shape-id) cell bounds objects) ;; Remove the size and the tracks that are not allocated [to-allocate total-frs indexed-tracks] diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 6392b2adf0..825f44451b 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -255,8 +255,13 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec } if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout { - let mut children = - grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, &bounds); + let mut children = grid_layout::reflow_grid_layout( + shape, + layout_data, + grid_data, + shapes, + &mut bounds, + ); entries.append(&mut children); } reflown.insert(*id); diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index 97e1616383..7d58a5dd5c 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -412,94 +412,6 @@ fn calculate_track_positions( track.anchor = next_anchor; next_anchor = next_anchor + layout_axis.across_v * real_gap; } - - /* - match align_content { - AlignContent::End => { - let total_across_size_gap: f32 = - total_across_size + (tracks.len() - 1) as f32 * layout_axis.gap_across; - - let delta = - layout_axis.across_size - total_across_size_gap - layout_axis.padding_across_end; - let mut next_anchor = layout_bounds.nw + layout_axis.across_v * delta; - - for track in tracks.iter_mut() { - track.anchor = next_anchor; - next_anchor = next_anchor - + layout_axis.across_v * (track.across_size + layout_axis.gap_across); - } - } - - AlignContent::Center => { - let total_across_size_gap: f32 = - total_across_size + (tracks.len() - 1) as f32 * layout_axis.gap_across; - let center_margin = (layout_axis.across_size - total_across_size_gap) / 2.0; - - let mut next_anchor = layout_bounds.nw + layout_axis.across_v * center_margin; - - for track in tracks.iter_mut() { - track.anchor = next_anchor; - next_anchor = next_anchor - + layout_axis.across_v * (track.across_size + layout_axis.gap_across); - } - } - - AlignContent::SpaceBetween => { - let mut next_anchor = - layout_bounds.nw + layout_axis.across_v * layout_axis.padding_across_start; - - let effective_gap = f32::max( - layout_axis.gap_across, - (layout_axis.across_space() - total_across_size) / (tracks.len() - 1) as f32, - ); - - for track in tracks.iter_mut() { - track.anchor = next_anchor; - next_anchor = - next_anchor + layout_axis.across_v * (track.across_size + effective_gap); - } - } - - AlignContent::SpaceAround => { - let effective_gap = - (layout_axis.across_space() - total_across_size) / tracks.len() as f32; - - let mut next_anchor = layout_bounds.nw - + layout_axis.across_v * (layout_axis.padding_across_start + effective_gap / 2.0); - - for track in tracks.iter_mut() { - track.anchor = next_anchor; - next_anchor = - next_anchor + layout_axis.across_v * (track.across_size + effective_gap); - } - } - - AlignContent::SpaceEvenly => { - let effective_gap = - (layout_axis.across_space() - total_across_size) / (tracks.len() + 1) as f32; - - let mut next_anchor = layout_bounds.nw - + layout_axis.across_v * (layout_axis.padding_across_start + effective_gap); - - for track in tracks.iter_mut() { - track.anchor = next_anchor; - next_anchor = - next_anchor + layout_axis.across_v * (track.across_size + effective_gap); - } - } - - _ => { - let mut next_anchor = - layout_bounds.nw + layout_axis.across_v * layout_axis.padding_across_start; - - for track in tracks.iter_mut() { - track.anchor = next_anchor; - next_anchor = next_anchor - + layout_axis.across_v * (track.across_size + layout_axis.gap_across); - } - } - - }*/ } fn calculate_track_data( @@ -708,8 +620,8 @@ pub fn reflow_flex_layout( let auto_across_size = if layout_axis.is_auto_across { tracks.iter().map(|track| track.across_size).sum::() + (tracks.len() - 1) as f32 * layout_axis.gap_across - + layout_axis.padding_main_start - + layout_axis.padding_main_end + + layout_axis.padding_across_start + + layout_axis.padding_across_end } else { 0.0 }; @@ -723,8 +635,8 @@ pub fn reflow_flex_layout( }) .reduce(f32::max) .unwrap_or(0.01) - + layout_axis.padding_across_start - + layout_axis.padding_across_end + + layout_axis.padding_main_start + + layout_axis.padding_main_end } else { 0.0 }; diff --git a/render-wasm/src/shapes/modifiers/grid_layout.rs b/render-wasm/src/shapes/modifiers/grid_layout.rs index 3af603e8b0..23c40284d0 100644 --- a/render-wasm/src/shapes/modifiers/grid_layout.rs +++ b/render-wasm/src/shapes/modifiers/grid_layout.rs @@ -1,5 +1,4 @@ -#![allow(dead_code, unused_variables)] -use crate::math::{intersect_rays, Bounds, Matrix, Point, Ray, Vector, VectorExt}; +use crate::math::{self as math, intersect_rays, Bounds, Matrix, Point, Ray, Vector, VectorExt}; use crate::shapes::{ AlignContent, AlignItems, AlignSelf, GridCell, GridData, GridTrack, GridTrackType, JustifyContent, JustifyItems, JustifySelf, LayoutData, LayoutItem, Modifier, Shape, @@ -34,6 +33,7 @@ struct TrackData { fn calculate_tracks( is_column: bool, + shape: &Shape, layout_data: &LayoutData, grid_data: &GridData, layout_bounds: &Bounds, @@ -55,14 +55,11 @@ fn calculate_tracks( let mut tracks = init_tracks(grid_tracks, layout_size); set_auto_base_size(is_column, &mut tracks, cells, shapes, bounds); - - set_auto_multi_span(is_column, layout_data, &layout_bounds, &mut tracks); - set_flex_multi_span(is_column, layout_data, &layout_bounds, &mut tracks); - - set_fr_value(is_column, layout_data, &mut tracks, layout_size, 0.0); - stretch_tracks(is_column, layout_data, &mut tracks, layout_size); + set_auto_multi_span(is_column, &mut tracks, cells, shapes, bounds); + set_flex_multi_span(is_column, &mut tracks, cells, shapes, bounds); + set_fr_value(is_column, shape, layout_data, &mut tracks, layout_size); + stretch_tracks(is_column, shape, layout_data, &mut tracks, layout_size); assign_anchors(is_column, layout_data, &layout_bounds, &mut tracks); - return tracks; } @@ -87,6 +84,20 @@ fn init_tracks(track: &Vec, size: f32) -> Vec { .collect() } +fn min_size(column: bool, shape: &Shape, bounds: &HashMap) -> f32 { + if column && shape.is_layout_horizontal_fill() { + shape.layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE) + } else if !column && shape.is_layout_vertical_fill() { + shape.layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE) + } else if column { + let bounds = bounds.find(shape); + bounds.width() + } else { + let bounds = bounds.find(shape); + bounds.height() + } +} + // Go through cells to adjust auto sizes for span=1. Base is the max of its children fn set_auto_base_size( column: bool, @@ -108,6 +119,7 @@ fn set_auto_base_size( let track = &mut tracks[(prop - 1) as usize]; + // We change the size for auto+flex tracks if track.track_type != GridTrackType::Auto && track.track_type != GridTrackType::Flex { continue; } @@ -116,62 +128,208 @@ fn set_auto_base_size( continue; }; - let bounds = bounds.find(shape); - - let shape_size = if column { - bounds.width() - } else { - bounds.height() - }; - - let min_size = if column && shape.is_layout_horizontal_fill() { - shape.layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE) - } else if !column && shape.is_layout_vertical_fill() { - shape.layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE) - } else { - shape_size - }; - + let min_size = min_size(column, shape, bounds); track.size = f32::max(track.size, min_size); } } +fn track_index(is_column: bool, c: &GridCell) -> (usize, usize) { + if is_column { + ( + (c.column - 1) as usize, + (c.column + c.column_span - 1) as usize, + ) + } else { + ((c.row - 1) as usize, (c.row + c.row_span - 1) as usize) + } +} + +fn has_flex(is_column: bool, cell: &GridCell, tracks: &mut Vec) -> bool { + let (start, end) = track_index(is_column, cell); + (start..end).any(|i| tracks[i].track_type == GridTrackType::Flex) +} + // Adjust multi-spaned cells with no flex columns fn set_auto_multi_span( - _column: bool, - _layout_data: &LayoutData, - _layout_bounds: &Bounds, - _tracks: &mut Vec, + column: bool, + tracks: &mut Vec, + cells: &Vec, + shapes: &HashMap, + bounds: &HashMap, ) { - // Sort descendant order of prop-span // Remove groups with flex (will be set in flex_multi_span) - // Retrieve teh value we need to distribute (fixed cell size minus gaps) - // Distribute the size between the tracks that already have a set value - // Distribute the space between auto tracks - // If we still have more space we distribute equally between all tracks + let mut selected_cells: Vec<&GridCell> = cells + .iter() + .filter(|c| { + if column { + c.column_span > 1 + } else { + c.row_span > 1 + } + }) + .filter(|c| !has_flex(column, c, tracks)) + .collect(); + + // Sort descendant order of prop-span + selected_cells.sort_by(|a, b| { + if column { + b.column_span.cmp(&a.row_span) + } else { + b.row_span.cmp(&a.row_span) + } + }); + + for cell in selected_cells { + let Some(child) = cell.shape.and_then(|id| shapes.get(&id)) else { + continue; + }; + + // Retrieve the value we need to distribute (fixed cell size minus gaps) + let mut dist = min_size(column, child, bounds); + let mut num_auto = 0; + + let (start, end) = track_index(column, cell); + + // Distribute the size between the tracks that already have a set value + for i in start..end { + dist = dist - tracks[i].size; + + if tracks[i].track_type == GridTrackType::Auto { + num_auto = num_auto + 1; + } + } + + // If we still have more space we distribute equally between all auto tracks + while dist > MIN_SIZE && num_auto > 0 { + let rest = dist / num_auto as f32; + + // Distribute the space between auto tracks + for i in start..end { + if tracks[i].track_type == GridTrackType::Auto { + // dist = dist - track[i].size; + let new_size = if tracks[i].size + rest < tracks[i].max_size { + tracks[i].size + rest + } else { + num_auto = num_auto - 1; + tracks[i].max_size + }; + + let aloc = new_size - tracks[i].size; + dist = dist - aloc; + tracks[i].size = tracks[i].size + aloc; + } + } + } + } } // Adjust multi-spaned cells with flex columns fn set_flex_multi_span( - _column: bool, - _layout_data: &LayoutData, - _layout_bounds: &Bounds, - _tracks: &mut Vec, + column: bool, + tracks: &mut Vec, + cells: &Vec, + shapes: &HashMap, + bounds: &HashMap, ) { + // Remove groups without flex + let mut selected_cells: Vec<&GridCell> = cells + .iter() + .filter(|c| { + if column { + c.column_span > 1 + } else { + c.row_span > 1 + } + }) + .filter(|c| has_flex(column, c, tracks)) + .collect(); + // Sort descendant order of prop-span - // Remove groups without flex (will be set in flex_auto_span) - // Retrieve the value that we need to distribute (fixed size of cell minus gaps) - // Distribute the size first between the tracks that have the fixed size - // When finished we distribute equally between the the rest of the tracks + selected_cells.sort_by(|a, b| { + if column { + b.column_span.cmp(&a.row_span) + } else { + b.row_span.cmp(&a.row_span) + } + }); + + // Retrieve the value that we need to distribute and the number of frs + for cell in selected_cells { + let Some(child) = cell.shape.and_then(|id| shapes.get(&id)) else { + continue; + }; + + // Retrieve the value we need to distribute (fixed cell size minus gaps) + let mut dist = min_size(column, child, bounds); + let mut num_flex = 0.0; + let mut num_auto = 0; + + let (start, end) = track_index(column, cell); + + // Distribute the size between the tracks that already have a set value + for i in start..end { + dist = dist - tracks[i].size; + + match tracks[i].track_type { + GridTrackType::Flex => { + num_flex = num_flex + tracks[i].value; + num_auto = num_auto + 1; + } + GridTrackType::Auto => { + num_auto = num_auto + 1; + } + _ => {} + } + } + + if dist <= MIN_SIZE { + // No space available to distribute + continue; + } + + let rest = dist / num_flex as f32; + + // Distribute the space between flex tracks in proportion to the division + for i in start..end { + if tracks[i].track_type == GridTrackType::Flex { + let new_size = f32::min(tracks[i].size + rest, tracks[i].max_size); + let aloc = new_size - tracks[i].size; + dist = dist - aloc; + tracks[i].size = tracks[i].size + aloc; + } + } + + // Distribute the space between auto tracks if any + while dist > MIN_SIZE && num_auto > 0 { + let rest = dist / num_auto as f32; + + for i in start..end { + if tracks[i].track_type == GridTrackType::Auto + || tracks[i].track_type == GridTrackType::Flex + { + let new_size = if tracks[i].size + rest < tracks[i].max_size { + tracks[i].size + rest + } else { + num_auto = num_auto - 1; + tracks[i].max_size + }; + + let aloc = new_size - tracks[i].size; + dist = dist - aloc; + tracks[i].size = tracks[i].size + aloc; + } + } + } + } } // Calculate the `fr` unit and adjust the size fn set_fr_value( column: bool, + shape: &Shape, layout_data: &LayoutData, tracks: &mut Vec, layout_size: f32, - min_fr_size: f32, ) { let tot_gap: f32 = if column { layout_data.column_gap * (tracks.len() - 1) as f32 @@ -187,31 +345,48 @@ fn set_fr_value( .sum::() + tot_gap; - // Get the total of frs to divide the space into let tot_frs: f32 = tracks .iter() .filter(|t| t.track_type == GridTrackType::Flex) .map(|t| t.value) .sum(); + let cur_fr_size = tracks + .iter() + .filter(|t| t.track_type == GridTrackType::Flex) + .map(|t| t.size / t.value) + .reduce(f32::max) + .unwrap_or(0.0); + // Divide the space between FRS - let fr = f32::max(min_fr_size, (layout_size - tot_size) / tot_frs); + let fr = if column && shape.is_layout_horizontal_auto() + || !column && shape.is_layout_vertical_auto() + { + cur_fr_size + } else { + f32::max(cur_fr_size, (layout_size - tot_size) / tot_frs) + }; // Assign the space to the FRS tracks .iter_mut() .filter(|t| t.track_type == GridTrackType::Flex) - .for_each(|t| t.size = f32::max(t.size, fr * t.value)); + .for_each(|t| t.size = f32::min(fr * t.value, t.max_size)); } fn stretch_tracks( column: bool, + shape: &Shape, layout_data: &LayoutData, tracks: &mut Vec, layout_size: f32, ) { - if (column && layout_data.justify_content != JustifyContent::Stretch) - || (!column && layout_data.align_content != AlignContent::Stretch) + if (column + && (layout_data.justify_content != JustifyContent::Stretch + || shape.is_layout_horizontal_auto())) + || (!column + && (layout_data.align_content != AlignContent::Stretch + || shape.is_layout_vertical_auto())) { return; } @@ -373,46 +548,8 @@ fn create_cell_data<'a>( result } -fn calculate_cell_data<'a>( - shape: &Shape, - layout_data: &LayoutData, - grid_data: &GridData, - shapes: &'a HashMap, - bounds: &HashMap, -) -> Vec> { - let result: Vec> = vec![]; - - let layout_bounds = bounds.find(shape); - - let column_tracks = calculate_tracks( - true, - layout_data, - grid_data, - &layout_bounds, - &grid_data.cells, - shapes, - bounds, - ); - let row_tracks = calculate_tracks( - false, - layout_data, - grid_data, - &layout_bounds, - &grid_data.cells, - shapes, - bounds, - ); - - create_cell_data( - &layout_bounds, - shapes, - &grid_data.cells, - &column_tracks, - &row_tracks, - ) -} - fn child_position( + child: &Shape, layout_bounds: &Bounds, layout_data: &LayoutData, child_bounds: &Bounds, @@ -427,23 +564,37 @@ fn child_position( let margin_right = layout_item.map(|i| i.margin_right).unwrap_or(0.0); let margin_bottom = layout_item.map(|i| i.margin_bottom).unwrap_or(0.0); - cell.anchor - + vv * match (cell.align_self, layout_data.align_items) { - (Some(AlignSelf::Start), _) => margin_left, - (Some(AlignSelf::Center), _) => (cell.height - child_bounds.height()) / 2.0, - (Some(AlignSelf::End), _) => margin_right + cell.height - child_bounds.height(), - (_, AlignItems::Center) => (cell.height - child_bounds.height()) / 2.0, - (_, AlignItems::End) => margin_right + cell.height - child_bounds.height(), - _ => margin_left, - } - + hv * match (cell.justify_self, layout_data.justify_items) { - (Some(JustifySelf::Start), _) => margin_top, - (Some(JustifySelf::Center), _) => (cell.width - child_bounds.width()) / 2.0, - (Some(JustifySelf::End), _) => margin_bottom + cell.width - child_bounds.width(), - (_, JustifyItems::Center) => (cell.width - child_bounds.width()) / 2.0, - (_, JustifyItems::End) => margin_bottom + cell.width - child_bounds.width(), - _ => margin_top, - } + let vpos = match (cell.align_self, layout_data.align_items) { + (Some(AlignSelf::Start), _) => margin_top, + (Some(AlignSelf::Center), _) => (cell.height - child_bounds.height()) / 2.0, + (Some(AlignSelf::End), _) => margin_bottom + cell.height - child_bounds.height(), + (_, AlignItems::Center) => (cell.height - child_bounds.height()) / 2.0, + (_, AlignItems::End) => margin_bottom + cell.height - child_bounds.height(), + _ => margin_top, + }; + + let vpos = if child.is_layout_vertical_fill() { + margin_top + } else { + vpos + }; + + let hpos = match (cell.justify_self, layout_data.justify_items) { + (Some(JustifySelf::Start), _) => margin_left, + (Some(JustifySelf::Center), _) => (cell.width - child_bounds.width()) / 2.0, + (Some(JustifySelf::End), _) => margin_right + cell.width - child_bounds.width(), + (_, JustifyItems::Center) => (cell.width - child_bounds.width()) / 2.0, + (_, JustifyItems::End) => margin_right + cell.width - child_bounds.width(), + _ => margin_left, + }; + + let hpos = if child.is_layout_horizontal_fill() { + margin_left + } else { + hpos + }; + + cell.anchor + vv * vpos + hv * hpos } pub fn reflow_grid_layout<'a>( @@ -451,17 +602,78 @@ pub fn reflow_grid_layout<'a>( layout_data: &LayoutData, grid_data: &GridData, shapes: &'a HashMap, - bounds: &HashMap, + bounds: &mut HashMap, ) -> VecDeque { let mut result = VecDeque::new(); - - let cells = calculate_cell_data(shape, layout_data, grid_data, shapes, bounds); let layout_bounds = bounds.find(shape); + let column_tracks = calculate_tracks( + true, + shape, + layout_data, + grid_data, + &layout_bounds, + &grid_data.cells, + shapes, + bounds, + ); + + let row_tracks = calculate_tracks( + false, + shape, + layout_data, + grid_data, + &layout_bounds, + &grid_data.cells, + shapes, + bounds, + ); + + let cells = create_cell_data( + &layout_bounds, + shapes, + &grid_data.cells, + &column_tracks, + &row_tracks, + ); + for cell in cells.iter() { let child = cell.shape; let child_bounds = bounds.find(child); + + let mut new_width = child_bounds.width(); + if child.is_layout_horizontal_fill() { + let margin_left = child.layout_item.map(|i| i.margin_left).unwrap_or(0.0); + new_width = cell.width - margin_left; + let min_width = child.layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE); + let max_width = child.layout_item.and_then(|i| i.max_w).unwrap_or(MAX_SIZE); + new_width = new_width.clamp(min_width, max_width); + } + + let mut new_height = child_bounds.height(); + if child.is_layout_vertical_fill() { + let margin_top = child.layout_item.map(|i| i.margin_top).unwrap_or(0.0); + new_height = cell.height - margin_top; + let min_height = child.layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE); + let max_height = child.layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE); + new_height = new_height.clamp(min_height, max_height); + } + + let mut transform = Matrix::default(); + + if (new_width - child_bounds.width()).abs() > MIN_SIZE + || (new_height - child_bounds.height()).abs() > MIN_SIZE + { + transform.post_concat(&math::resize_matrix( + &layout_bounds, + &child_bounds, + new_width, + new_height, + )); + } + let position = child_position( + &child, &layout_bounds, &layout_data, &child_bounds, @@ -469,7 +681,6 @@ pub fn reflow_grid_layout<'a>( cell, ); - let mut transform = Matrix::default(); let delta_v = Vector::new_points(&child_bounds.nw, &position); if delta_v.x.abs() > MIN_SIZE || delta_v.y.abs() > MIN_SIZE { @@ -479,5 +690,46 @@ pub fn reflow_grid_layout<'a>( result.push_back(Modifier::transform(child.id, transform)); } + if shape.is_layout_horizontal_auto() || shape.is_layout_vertical_auto() { + let width = layout_bounds.width(); + let height = layout_bounds.height(); + + let mut scale_width = 1.0; + let mut scale_height = 1.0; + + if shape.is_layout_horizontal_auto() { + let auto_width = column_tracks.iter().map(|t| t.size).sum::() + + layout_data.padding_left + + layout_data.padding_right + + (column_tracks.len() - 1) as f32 * layout_data.column_gap; + scale_width = auto_width / width; + } + + if shape.is_layout_vertical_auto() { + let auto_height = row_tracks.iter().map(|t| t.size).sum::() + + layout_data.padding_top + + layout_data.padding_bottom + + (row_tracks.len() - 1) as f32 * layout_data.row_gap; + scale_height = auto_height / height; + } + + let parent_transform = layout_bounds + .transform_matrix() + .unwrap_or(Matrix::default()); + + let parent_transform_inv = &parent_transform.invert().unwrap(); + let origin = parent_transform_inv.map_point(layout_bounds.nw); + + let mut scale = Matrix::scale((scale_width, scale_height)); + scale.post_translate(origin); + scale.post_concat(&parent_transform); + scale.pre_translate(-origin); + scale.pre_concat(&parent_transform_inv); + + let layout_bounds_after = layout_bounds.transform(&scale); + result.push_back(Modifier::parent(shape.id, scale)); + bounds.insert(shape.id, layout_bounds_after); + } + result }