use crate::render::Canvas;
use crate::types::{ Length, Rect };
use crate::widget::Element;
pub struct WrapGrid<Msg: Clone>
{
pub children: Vec<Element<Msg>>,
pub columns: usize,
pub spacing_x: Length,
pub spacing_y: Length,
pub padding: Length,
pub centre_last_row: bool,
}
impl<Msg: Clone> WrapGrid<Msg>
{
pub fn push( mut self, child: impl Into<Element<Msg>> ) -> Self
{
self.children.push( child.into() );
self
}
pub fn spacing( mut self, s: impl Into<Length> ) -> Self
{
let s = s.into();
self.spacing_x = s;
self.spacing_y = s;
self
}
pub fn spacing_x( mut self, s: impl Into<Length> ) -> Self
{
self.spacing_x = s.into();
self
}
pub fn spacing_y( mut self, s: impl Into<Length> ) -> Self
{
self.spacing_y = s.into();
self
}
pub fn padding( mut self, p: impl Into<Length> ) -> Self
{
self.padding = p.into();
self
}
pub fn centre_last_row( mut self, yes: bool ) -> Self
{
self.centre_last_row = yes;
self
}
fn resolved( &self, canvas: &Canvas ) -> ( f32, f32, f32 )
{
let vp = canvas.viewport_layout();
let em = Length::EM_BASE_DEFAULT;
(
self.spacing_x.resolve( vp, em ),
self.spacing_y.resolve( vp, em ),
self.padding.resolve( vp, em ),
)
}
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
{
if self.children.is_empty() || self.columns == 0
{
return ( max_width, 0.0 );
}
let ( sx, sy, pad ) = self.resolved( canvas );
let cols = self.columns;
let inner_w = (max_width - pad * 2.0).max( 0.0 );
let cell_w = (inner_w - sx * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
let row_count = (self.children.len() + cols - 1) / cols;
let mut total_h = pad * 2.0;
for row in 0..row_count
{
let start = row * cols;
let end = (start + cols).min( self.children.len() );
let row_h = self.children[start..end]
.iter()
.map( |c| c.preferred_size( cell_w, canvas ).1 )
.fold( 0.0_f32, f32::max );
total_h += row_h;
if row + 1 < row_count { total_h += sy; }
}
( max_width, total_h )
}
pub fn layout( &self, rect: Rect, canvas: &Canvas ) -> Vec<(Rect, usize)>
{
if self.children.is_empty() || self.columns == 0
{
return Vec::new();
}
let ( sx, sy, pad ) = self.resolved( canvas );
let cols = self.columns;
let inner_w = (rect.width - pad * 2.0).max( 0.0 );
let cell_w = (inner_w - sx * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
let x0 = rect.x + pad;
let mut y = rect.y + pad;
let row_count = (self.children.len() + cols - 1) / cols;
let mut out = Vec::with_capacity( self.children.len() );
for row in 0..row_count
{
let start = row * cols;
let end = (start + cols).min( self.children.len() );
let row_h = self.children[start..end]
.iter()
.map( |c| c.preferred_size( cell_w, canvas ).1 )
.fold( 0.0_f32, f32::max );
let items_in_row = end - start;
let row_offset = if self.centre_last_row && items_in_row < cols
{
let missing = (cols - items_in_row) as f32;
missing * (cell_w + sx) / 2.0
} else { 0.0 };
for col in 0..items_in_row
{
let x = x0 + row_offset + col as f32 * (cell_w + sx);
let crect = Rect { x, y, width: cell_w, height: row_h };
out.push( ( crect, start + col ) );
}
y += row_h + sy;
}
out
}
pub( crate ) fn map_msg<U>( self, f: &crate::widget::MapFn<Msg, U> ) -> WrapGrid<U>
where
U: Clone + 'static,
Msg: 'static,
{
WrapGrid
{
children: self.children.into_iter().map( |c| c.map_arc( f ) ).collect(),
columns: self.columns,
spacing_x: self.spacing_x,
spacing_y: self.spacing_y,
padding: self.padding,
centre_last_row: self.centre_last_row,
}
}
}
impl<Msg: Clone + 'static> From<WrapGrid<Msg>> for Element<Msg>
{
fn from( g: WrapGrid<Msg> ) -> Self
{
Element::WrapGrid( g )
}
}
#[cfg(test)]
mod tests
{
use super::*;
use crate::render::Canvas;
use crate::layout::spacer::spacer;
fn canvas() -> Canvas { Canvas::new( 1, 1 ) }
fn spacer_grid( cols: usize, n: usize, spacing: f32, padding: f32 ) -> WrapGrid<()>
{
let mut g = grid( cols ).spacing( spacing ).padding( padding );
for _ in 0..n { g = g.push( spacer() ); }
g
}
#[test]
fn empty_grid_height_is_zero()
{
let g: WrapGrid<()> = grid( 4 );
let ( _, h ) = g.preferred_size( 400.0, &canvas() );
assert_eq!( h, 0.0 );
}
#[test]
fn preferred_width_equals_max_width()
{
let g = spacer_grid( 4, 8, 0.0, 0.0 );
let ( w, _ ) = g.preferred_size( 320.0, &canvas() );
assert_eq!( w, 320.0 );
}
#[test]
fn cell_width_no_spacing_no_padding()
{
let g = spacer_grid( 4, 4, 0.0, 0.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 200.0 };
let rects = g.layout( rect, &c );
assert_eq!( rects.len(), 4 );
for ( r, _ ) in &rects { assert!( (r.width - 100.0).abs() < 0.01 ); }
}
#[test]
fn cell_width_with_spacing()
{
let g = spacer_grid( 4, 4, 10.0, 0.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 200.0 };
let rects = g.layout( rect, &c );
for ( r, _ ) in &rects { assert!( (r.width - 92.5).abs() < 0.01 ); }
}
#[test]
fn cell_width_with_padding()
{
let g = spacer_grid( 4, 4, 0.0, 20.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 200.0 };
let rects = g.layout( rect, &c );
for ( r, _ ) in &rects { assert!( (r.width - 90.0).abs() < 0.01 ); }
}
#[test]
fn layout_yields_one_rect_per_child()
{
let g = spacer_grid( 4, 7, 0.0, 0.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 400.0 };
let rects = g.layout( rect, &c );
assert_eq!( rects.len(), 7 );
}
#[test]
fn layout_indices_are_sequential()
{
let g = spacer_grid( 3, 5, 0.0, 0.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 300.0, height: 300.0 };
let rects = g.layout( rect, &c );
let indices: Vec<usize> = rects.iter().map( |( _, i )| *i ).collect();
assert_eq!( indices, vec![ 0, 1, 2, 3, 4 ] );
}
#[test]
fn column_x_positions_no_spacing()
{
let g = spacer_grid( 3, 3, 0.0, 0.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 300.0, height: 100.0 };
let rects = g.layout( rect, &c );
let xs: Vec<f32> = rects.iter().map( |( r, _ )| r.x ).collect();
assert!( (xs[0] - 0.0).abs() < 0.01 );
assert!( (xs[1] - 100.0).abs() < 0.01 );
assert!( (xs[2] - 200.0).abs() < 0.01 );
}
#[test]
fn column_x_positions_with_spacing()
{
let g = spacer_grid( 3, 3, 10.0, 0.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 300.0, height: 100.0 };
let rects = g.layout( rect, &c );
let cell_w = 280.0_f32 / 3.0;
let xs: Vec<f32> = rects.iter().map( |( r, _ )| r.x ).collect();
assert!( (xs[0] - 0.0).abs() < 0.01 );
assert!( (xs[1] - (cell_w + 10.0)).abs() < 0.01 );
assert!( (xs[2] - (2.0 * (cell_w + 10.0))).abs() < 0.01 );
}
#[test]
fn partial_last_row_has_correct_count()
{
let g = spacer_grid( 4, 7, 0.0, 0.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 400.0 };
let rects = g.layout( rect, &c );
assert_eq!( rects.len(), 7 );
for ( r, _ ) in &rects[..4] { assert!( r.y.abs() < 0.01 ); }
}
#[test]
fn layout_respects_rect_origin()
{
let g = spacer_grid( 2, 2, 0.0, 0.0 );
let c = canvas();
let rect = Rect { x: 50.0, y: 30.0, width: 200.0, height: 100.0 };
let rects = g.layout( rect, &c );
assert!( (rects[0].0.x - 50.0).abs() < 0.01 );
assert!( (rects[0].0.y - 30.0).abs() < 0.01 );
}
#[test]
fn last_row_centred_when_partial()
{
let g = spacer_grid( 2, 3, 0.0, 0.0 ).centre_last_row( true );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 200.0, height: 400.0 };
let rects = g.layout( rect, &c );
assert!( (rects[2].0.x - 50.0).abs() < 0.01 );
}
#[test]
fn centre_last_row_noop_on_full_row()
{
let g = spacer_grid( 2, 4, 0.0, 0.0 ).centre_last_row( true );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 200.0, height: 400.0 };
let rects = g.layout( rect, &c );
assert!( rects[2].0.x.abs() < 0.01 );
assert!( (rects[3].0.x - 100.0).abs() < 0.01 );
}
#[test]
fn centre_last_row_off_by_default()
{
let g = spacer_grid( 2, 3, 0.0, 0.0 );
let c = canvas();
let rect = Rect { x: 0.0, y: 0.0, width: 200.0, height: 400.0 };
let rects = g.layout( rect, &c );
assert!( rects[2].0.x.abs() < 0.01 );
}
}
pub fn grid<Msg: Clone>( columns: usize ) -> WrapGrid<Msg>
{
WrapGrid
{
children: Vec::new(),
columns,
spacing_x: Length::px( 8.0 ),
spacing_y: Length::px( 8.0 ),
padding: Length::px( 0.0 ),
centre_last_row: false,
}
}