use crate::types::{ Length, Rect };
use crate::render::Canvas;
use crate::widget::Element;
pub struct Row<Msg: Clone>
{
pub( crate ) children: Vec<Element<Msg>>,
pub( crate ) spacing: Length,
pub( crate ) padding: Length,
pub( crate ) align_right: bool,
pub( crate ) align_top: bool,
pub( crate ) fill_height: bool,
pub( crate ) shrink: bool,
}
impl<Msg: Clone> Row<Msg>
{
pub fn new() -> Self
{
Self
{
children: Vec::new(),
spacing: Length::px( 8.0 ),
padding: Length::px( 0.0 ),
align_right: false,
align_top: false,
fill_height: false,
shrink: true,
}
}
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
}
#[ inline ]
fn resolved_spacing( &self, canvas: &Canvas ) -> f32
{
canvas.resolve_geom( self.spacing )
}
#[ inline ]
fn resolved_padding( &self, canvas: &Canvas ) -> f32
{
canvas.resolve_geom( self.padding )
}
pub fn no_shrink( mut self ) -> Self
{
self.shrink = false;
self
}
pub fn align_right( mut self ) -> Self
{
self.align_right = true;
self
}
pub fn align_top( mut self ) -> Self
{
self.align_top = true;
self
}
pub fn fill_height( mut self ) -> Self
{
self.fill_height = true;
self
}
pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
{
let pad = self.resolved_padding( canvas );
let spacing = self.resolved_spacing( canvas );
let inner_w = ( max_width - pad * 2.0 ).max( 0.0 );
let gaps = spacing * self.children.len().saturating_sub( 1 ) as f32;
let fixed_w: f32 = self.children.iter()
.filter( |c| match c
{
Element::Flex( _ ) => false,
Element::Spacer( s ) => s.resolved_width( canvas ).is_some(),
_ => true,
} )
.map( |c| c.preferred_size( max_width, canvas ).0 )
.sum();
let residual = ( inner_w - fixed_w - gaps ).max( 0.0 );
let max_h: f32 = self.children.iter()
.map( |c| match c
{
Element::Flex( _ ) => c.preferred_size( residual, canvas ).1,
Element::Spacer( _ ) => c.preferred_size( max_width, canvas ).1,
_ => c.preferred_size( max_width, canvas ).1,
} )
.fold( 0.0_f32, f32::max );
let has_flex = self.children.iter().any( |c| match c
{
Element::Flex( _ ) => true,
Element::Spacer( s ) => s.resolved_width( canvas ).is_none(),
_ => false,
} );
let w = if self.align_right || has_flex
{
max_width
} else {
let total_w: f32 = self.children.iter()
.map( |c| c.preferred_size( max_width, canvas ).0 )
.sum::<f32>()
+ gaps
+ pad * 2.0;
total_w.min( max_width )
};
( w, max_h + pad * 2.0 )
}
pub fn draw( &self, _canvas: &mut Canvas, _rect: Rect, _focused: bool ) {}
pub fn layout( &self, rect: Rect, canvas: &Canvas ) -> Vec<(Rect, usize)>
{
let pad = self.resolved_padding( canvas );
let spacing = self.resolved_spacing( canvas );
let inner_h = rect.height - pad * 2.0;
let sizes: Vec<(f32, f32)> = self.children.iter()
.map( |c| c.preferred_size( rect.width, canvas ) )
.collect();
let gaps = spacing * self.children.len().saturating_sub( 1 ) as f32;
let fixed_w: f32 = self.children.iter().zip( sizes.iter() )
.filter( |( c, _ )| match c
{
Element::Flex( _ ) => false,
Element::Spacer( s ) => s.resolved_width( canvas ).is_some(),
_ => true,
} )
.map( |( _, ( w, _ ) )| *w )
.sum();
let total_weight: u32 = self.children.iter()
.filter_map( |c| match c {
Element::Spacer( s ) if s.resolved_width( canvas ).is_none() => Some( s.weight ),
Element::Flex( f ) => Some( f.weight ),
_ => None,
} )
.sum();
let inner_w = ( rect.width - pad * 2.0 ).max( 0.0 );
let mut flexible: Vec<f32> = self.children.iter().zip( sizes.iter() )
.filter( |( c, _ )| !matches!( c, Element::Flex( _ ) | Element::Spacer( _ ) ) )
.map( |( _, ( w, _ ) )| *w )
.collect();
let flexible_w: f32 = flexible.iter().sum();
let rigid_w = ( fixed_w - flexible_w ).max( 0.0 );
let cap = if self.shrink
{
width_cap( &mut flexible, ( inner_w - gaps - rigid_w ).max( 0.0 ) )
} else {
f32::INFINITY
};
let laid_w = rigid_w + flexible.iter().map( |w| w.min( cap ) ).sum::<f32>();
let leftover = ( inner_w - laid_w - gaps ).max( 0.0 );
let has_spacers = total_weight > 0;
let ( start_x, flex_unit ) = if has_spacers
{
( rect.x + pad, leftover / total_weight as f32 )
}
else if self.align_right
{
( rect.x + rect.width - ( laid_w + gaps ) - pad, 0.0 )
}
else
{
( rect.x + ( rect.width - laid_w - gaps ) / 2.0, 0.0 )
};
let mut x = start_x;
let mut result = Vec::with_capacity( self.children.len() );
for ( i, ( (w, h), child) ) in sizes.into_iter().zip( self.children.iter() ).enumerate()
{
let width = match child
{
Element::Spacer( s ) => match s.resolved_width( canvas )
{
Some( fw ) => fw,
None => flex_unit * s.weight as f32,
},
Element::Flex( f ) => flex_unit * f.weight as f32,
_ => w.min( cap ),
};
let ( y, height ) = if self.fill_height && !matches!( child, Element::Spacer( _ ) )
{
( rect.y + pad, inner_h )
} else if self.align_top {
( rect.y + pad, h )
} else {
( rect.y + pad + ( inner_h - h ) / 2.0, h )
};
result.push( ( Rect { x, y, width, height }, i ) );
x += width + spacing;
}
result
}
pub( crate ) fn map_msg<U>( self, f: &crate::widget::MapFn<Msg, U> ) -> Row<U>
where
U: Clone + 'static,
Msg: 'static,
{
Row
{
children: self.children.into_iter().map( |c| c.map_arc( f ) ).collect(),
spacing: self.spacing,
padding: self.padding,
align_right: self.align_right,
align_top: self.align_top,
fill_height: self.fill_height,
shrink: self.shrink,
}
}
}
fn width_cap( widths: &mut [f32], available: f32 ) -> f32
{
if widths.iter().sum::<f32>() <= available
{
return f32::INFINITY;
}
widths.sort_by( |a, b| a.partial_cmp( b ).unwrap_or( std::cmp::Ordering::Equal ) );
let mut prefix = 0.0_f32;
for ( i, w ) in widths.iter().enumerate()
{
let remaining = ( widths.len() - i ) as f32;
let cap = ( available - prefix ) / remaining;
if cap <= *w
{
return cap.max( 0.0 );
}
prefix += *w;
}
0.0
}
pub fn row<Msg: Clone>() -> Row<Msg>
{
Row::new()
}
impl<Msg: Clone> Default for Row<Msg>
{
fn default() -> Self
{
Self::new()
}
}
#[ cfg( test ) ]
mod tests
{
use super::*;
use crate::render::Canvas;
use crate::types::Rect;
fn make_canvas() -> Canvas { Canvas::new( 800, 600 ) }
#[ test ]
fn overflowing_children_shrink_to_fit()
{
let canvas = make_canvas();
let build = || row::<()>()
.spacing( 0.0 )
.padding( 0.0 )
.push( crate::toggle( false ) )
.push( crate::toggle( false ) );
let natural = build().preferred_size( 10_000.0, &canvas ).0;
assert!( natural > 0.0, "toggles must report a width for this to test anything" );
let rect = Rect { x: 0.0, y: 0.0, width: natural / 2.0, height: 48.0 };
let laid: f32 = build().layout( rect, &canvas )
.iter().map( |( r, _ )| r.width ).sum();
assert!( laid <= rect.width + 0.5, "laid out {laid} into {}", rect.width );
let kept: f32 = build().no_shrink().layout( rect, &canvas )
.iter().map( |( r, _ )| r.width ).sum();
assert!( kept > rect.width, "no_shrink kept {kept}, expected wider than {}", rect.width );
}
#[ test ]
fn narrow_child_survives_a_wide_neighbour()
{
let mut w = vec![ 20.0_f32, 300.0 ];
let cap = width_cap( &mut w, 200.0 );
assert_eq!( 20.0_f32.min( cap ), 20.0 );
assert_eq!( 300.0_f32.min( cap ), 180.0 );
}
#[ test ]
fn equal_children_shrink_equally()
{
let mut w = vec![ 100.0_f32, 100.0, 100.0 ];
let cap = width_cap( &mut w, 150.0 );
assert_eq!( cap, 50.0 );
}
#[ test ]
fn nothing_shrinks_when_it_all_fits()
{
let mut w = vec![ 10.0_f32, 20.0 ];
assert_eq!( width_cap( &mut w, 100.0 ), f32::INFINITY );
}
#[ test ]
fn pinned_spacers_keep_their_width_when_shrinking()
{
let canvas = make_canvas();
let r = row::<()>()
.spacing( 0.0 )
.padding( 0.0 )
.push( crate::spacer().width( 60.0 ).height( 10.0 ) )
.push( crate::toggle( false ) );
let rect = Rect { x: 0.0, y: 0.0, width: 70.0, height: 48.0 };
let cells = r.layout( rect, &canvas );
assert_eq!( cells[ 0 ].0.width, 60.0 );
}
#[ test ]
fn align_right_returns_full_max_width()
{
let canvas = make_canvas();
let r = row::<()>().align_right();
let ( w, _ ) = r.preferred_size( 500.0, &canvas );
assert_eq!( w, 500.0 );
}
#[ test ]
fn align_right_true_regardless_of_children()
{
let canvas = make_canvas();
let r = row::<()>().align_right().spacing( 999.0 );
let ( w, _ ) = r.preferred_size( 300.0, &canvas );
assert_eq!( w, 300.0 );
}
#[ test ]
fn centered_empty_row_returns_zero_width()
{
let canvas = make_canvas();
let r = row::<()>();
let ( w, _ ) = r.preferred_size( 500.0, &canvas );
assert_eq!( w, 0.0 );
}
#[ test ]
fn centered_empty_row_returns_zero_height()
{
let canvas = make_canvas();
let r = row::<()>().padding( 0.0 );
let ( _, h ) = r.preferred_size( 500.0, &canvas );
assert_eq!( h, 0.0 );
}
#[ test ]
fn padding_adds_to_height()
{
let canvas = make_canvas();
let r = row::<()>().padding( 8.0 );
let ( _, h ) = r.preferred_size( 500.0, &canvas );
assert_eq!( h, 16.0 );
}
#[ test ]
fn layout_of_empty_row_is_empty()
{
let canvas = make_canvas();
let r = row::<()>().align_right();
let rect = Rect { x: 0., y: 0., width: 400., height: 48. };
assert!( r.layout( rect, &canvas ).is_empty() );
}
#[ test ]
fn vmin_padding_doubles_around_content()
{
let canvas = make_canvas();
let r = row::<()>().padding( Length::vmin( 4.0 ) );
let ( _, h ) = r.preferred_size( 500.0, &canvas );
assert_eq!( h, 48.0 );
}
#[ test ]
fn vmin_spacing_pins_visible_layout_gap()
{
let canvas = make_canvas();
let r = row::<()>()
.padding( 0.0 )
.spacing( Length::vmin( 5.0 ) )
.push( crate::spacer().width( 10.0 ) )
.push( crate::spacer().width( 10.0 ) );
let rect = Rect { x: 0., y: 0., width: 200., height: 48. };
let placed = r.layout( rect, &canvas );
assert_eq!( placed.len(), 2 );
let ( first_rect, _ ) = placed[ 0 ];
let ( second_rect, _ ) = placed[ 1 ];
let gap = second_rect.x - ( first_rect.x + first_rect.width );
assert!( ( gap - 30.0 ).abs() < 1e-3, "expected ~30 px gap, got {gap}" );
}
}