use crate::types::{ Length, Rect };
use crate::render::Canvas;
use crate::widget::Element;
pub struct Column<Msg: Clone>
{
pub children: Vec<Element<Msg>>,
pub spacing: Length,
pub padding: Length,
pub align_center_x: bool,
pub center_y: bool,
pub max_width: Option<Length>,
pub fit_content: bool,
}
impl<Msg: Clone> Column<Msg>
{
pub fn new() -> Self
{
Self
{
children: Vec::new(),
spacing: Length::px( 8.0 ),
padding: Length::px( 16.0 ),
align_center_x: true,
center_y: false,
max_width: None,
fit_content: false,
}
}
pub fn push( mut self, e: impl Into<Element<Msg>> ) -> Self
{
self.children.push( e.into() );
self
}
pub fn spacing( mut self, s: impl Into<Length> ) -> Self
{
self.spacing = s.into();
self
}
pub fn padding( mut self, p: impl Into<Length> ) -> Self
{
self.padding = p.into();
self
}
pub fn align_center_x( mut self, c: bool ) -> Self
{
self.align_center_x = c;
self
}
pub fn center_y( mut self, c: bool ) -> Self
{
self.center_y = c;
self
}
pub fn max_width( mut self, w: impl Into<Length> ) -> Self
{
self.max_width = Some( w.into() );
self
}
#[ inline ]
fn resolved_spacing( &self, canvas: &Canvas ) -> f32
{
self.spacing.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT )
}
#[ inline ]
fn resolved_padding( &self, canvas: &Canvas ) -> f32
{
self.padding.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT )
}
#[ inline ]
fn resolved_max_width( &self, canvas: &Canvas ) -> Option<f32>
{
self.max_width.map( |l| l.resolve( canvas.viewport_layout(), Length::EM_BASE_DEFAULT ) )
}
pub fn fit_content( mut self ) -> Self
{
self.fit_content = true;
self
}
fn inner_w( &self, available: f32, canvas: &Canvas ) -> f32
{
let w = available - self.resolved_padding( canvas ) * 2.0;
self.resolved_max_width( canvas ).map( |m| w.min( m ) ).unwrap_or( w )
}
fn content_h( &self, inner_w: f32, canvas: &Canvas ) -> f32
{
self.children.iter()
.map( |c| match c
{
Element::Spacer( s ) => s.resolved_height( canvas ).unwrap_or( 0.0 ),
other => other.preferred_size( inner_w, canvas ).1,
} )
.sum::<f32>()
+ self.resolved_spacing( canvas ) * ( self.children.len().saturating_sub( 1 ) ) as f32
}
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
{
let inner_w = self.inner_w( max_width, canvas );
let pad = self.resolved_padding( canvas );
let total_h = self.content_h( inner_w, canvas ) + pad * 2.0;
let w = if self.fit_content
{
let content_w = self.children.iter()
.map( |c| match c
{
Element::Spacer( _ ) => 0.0,
Element::Separator( _ ) => 0.0,
Element::Scroll( _ ) => 0.0,
Element::ProgressBar( _ ) => 0.0,
Element::Slider( _ ) => 0.0,
Element::TextEdit( t ) => if t.fixed_width.is_some()
{
t.preferred_size( inner_w, canvas ).0
} else { 0.0 },
other => other.preferred_size( inner_w, canvas ).0,
} )
.fold( 0.0_f32, f32::max );
( content_w + pad * 2.0 ).min( max_width )
} else {
max_width
};
( w, total_h )
}
pub fn draw( &self, _canvas: &mut Canvas, _rect: Rect, _focused: bool ) {}
pub fn layout( &self, rect: Rect, canvas: &Canvas ) -> Vec<(Rect, usize)>
{
let inner_w = self.inner_w( rect.width, canvas );
let pad = self.resolved_padding( canvas );
let spacing = self.resolved_spacing( canvas );
let total_weight: u32 = self.children.iter()
.map( |c| match c
{
Element::Spacer( s ) if s.resolved_height( canvas ).is_none() => s.weight,
Element::Scroll( s ) if s.axis.allows_y() => 1,
_ => 0,
} )
.sum();
let fixed_h: f32 = self.children.iter()
.map( |c|
{
if matches!( c, Element::Scroll( s ) if s.axis.allows_y() )
{
0.0
} else if let Element::Spacer( s ) = c {
s.resolved_height( canvas ).unwrap_or( 0.0 )
} else {
c.preferred_size( inner_w, canvas ).1
}
} )
.sum::<f32>()
+ spacing * ( self.children.len().saturating_sub( 1 ) ) as f32;
let avail_h = rect.height - pad * 2.0;
let avail_spare = ( avail_h - fixed_h ).max( 0.0 );
let start_y = if total_weight == 0 && self.center_y
{
rect.y + pad + avail_spare / 2.0
} else {
rect.y + pad
};
let start_x = rect.x + (rect.width - inner_w) / 2.0;
let mut y = start_y;
let mut result = Vec::new();
for ( i, child ) in self.children.iter().enumerate()
{
let ( w, h ) = match child
{
Element::Spacer( s ) =>
{
let h = if let Some( fixed ) = s.resolved_height( canvas )
{
fixed
} else if total_weight > 0
{
avail_spare * s.weight as f32 / total_weight as f32
} else {
0.0
};
( inner_w, h )
},
Element::Scroll( s ) if s.axis.allows_y() =>
{
let h = if total_weight > 0
{
avail_spare / total_weight as f32
} else {
0.0
};
( inner_w, h )
},
other => other.preferred_size( inner_w, canvas ),
};
let x = if self.align_center_x && !matches!( child, Element::Spacer( _ ) )
{
start_x + (inner_w - w) / 2.0
} else {
start_x
};
result.push( ( Rect { x, y, width: w, height: h }, i ) );
y += h + spacing;
}
result
}
pub( crate ) fn map_msg<U>( self, f: &crate::widget::MapFn<Msg, U> ) -> Column<U>
where
U: Clone + 'static,
Msg: 'static,
{
Column
{
children: self.children.into_iter().map( |c| c.map_arc( f ) ).collect(),
spacing: self.spacing,
padding: self.padding,
align_center_x: self.align_center_x,
center_y: self.center_y,
max_width: self.max_width,
fit_content: self.fit_content,
}
}
}
pub fn column<Msg: Clone>() -> Column<Msg>
{
Column::new()
}
impl<Msg: Clone> Default for Column<Msg>
{
fn default() -> Self
{
Self::new()
}
}
#[ cfg( test ) ]
mod tests
{
use super::*;
use crate::render::Canvas;
fn make_canvas() -> Canvas { Canvas::new( 800, 600 ) }
#[ test ]
fn preferred_size_width_equals_max_width()
{
let canvas = make_canvas();
let col = column::<()>().padding( 10.0 );
let ( w, _ ) = col.preferred_size( 200.0, &canvas );
assert_eq!( w, 200.0 );
}
#[ test ]
fn empty_column_height_is_two_paddings()
{
let canvas = make_canvas();
let col = column::<()>().padding( 10.0 );
let ( _, h ) = col.preferred_size( 200.0, &canvas );
assert_eq!( h, 20.0 );
}
#[ test ]
fn max_width_caps_inner_w_not_preferred_w()
{
let canvas = make_canvas();
let col = column::<()>().padding( 0.0 ).max_width( 100.0 );
let ( w, _ ) = col.preferred_size( 200.0, &canvas );
assert_eq!( w, 200.0 );
}
#[ test ]
fn inner_w_respects_padding_and_max_width()
{
let canvas = make_canvas();
let col = column::<()>().padding( 20.0 ).max_width( 100.0 );
assert_eq!( col.inner_w( 200.0, &canvas ), 100.0 );
}
#[ test ]
fn inner_w_without_max_width_subtracts_padding()
{
let canvas = make_canvas();
let col = column::<()>().padding( 10.0 );
assert_eq!( col.inner_w( 200.0, &canvas ), 180.0 );
}
#[ test ]
fn spacing_between_children_accumulates()
{
let canvas = make_canvas();
let col = column::<()>()
.padding( 0.0 )
.spacing( 8.0 )
.push( crate::spacer() )
.push( crate::spacer() )
.push( crate::spacer() );
let ( _, h ) = col.preferred_size( 100.0, &canvas );
assert_eq!( h, 16.0 );
}
#[ test ]
fn vmin_spacing_resolves_against_canvas_viewport()
{
let canvas = make_canvas();
let col = column::<()>()
.padding( 0.0 )
.spacing( Length::vmin( 5.0 ) )
.push( crate::spacer() )
.push( crate::spacer() )
.push( crate::spacer() );
let ( _, h ) = col.preferred_size( 100.0, &canvas );
assert_eq!( h, 60.0 );
}
#[ test ]
fn vmin_padding_doubles_around_content()
{
let canvas = make_canvas();
let col = column::<()>().padding( Length::vmin( 4.0 ) );
let ( _, h ) = col.preferred_size( 100.0, &canvas );
assert_eq!( h, 48.0 );
}
#[ test ]
fn vmin_max_width_caps_inner_w()
{
let canvas = make_canvas();
let col = column::<()>().padding( 0.0 ).max_width( Length::vmin( 20.0 ) );
assert_eq!( col.inner_w( 200.0, &canvas ), 120.0 );
}
}