use std::sync::Arc;
use crate::layout::column::column;
use crate::layout::row::row;
use crate::layout::spacer::spacer;
use crate::layout::stack::{ stack, HAlign, VAlign };
use crate::types::{ Length, WidgetId };
use crate::widget::pressable::pressable;
use crate::widget::scroll::scroll;
use crate::widget::separator::separator;
use crate::widget::{ container, text, Element };
use super::theme::{ BACK_ICON, BODY_WIDTH, BUTTON_ICON, BUTTON_SIZE, CARD_ID, CARD_RADIUS, CLOSE_ICON_SIZE, COLUMNS, EDGE_WIDTH };
use super::cards::TabCards;
use super::{ parts, IndexMsg, TabItem, TabMotion, TabStripColors };
fn card_margin() -> Length { Length::vw( 8.0 ).at_most( 60.0 ) }
fn icon_size() -> Length { Length::vw( 14.0 ).at_most( 96.0 ) }
pub struct TabOverview<Msg: Clone>
{
pub( crate ) tabs: Vec<TabItem>,
pub( crate ) selected: usize,
pub( crate ) on_select: Option<IndexMsg<Msg>>,
pub( crate ) on_close: Option<IndexMsg<Msg>>,
pub( crate ) reorder: bool,
pub( crate ) motion: TabMotion,
pub( crate ) title: String,
pub( crate ) back: Option<Msg>,
pub( crate ) actions: Vec<Element<Msg>>,
pub( crate ) colors: TabStripColors,
}
impl<Msg: Clone + 'static> TabOverview<Msg>
{
pub fn new( tabs: impl IntoIterator<Item = TabItem> ) -> Self
{
Self
{
tabs: tabs.into_iter().collect(),
selected: 0,
on_select: None,
on_close: None,
reorder: false,
motion: TabMotion::default(),
title: String::new(),
back: None,
actions: Vec::new(),
colors: TabStripColors::default(),
}
}
pub fn selected( mut self, idx: usize ) -> Self
{
self.selected = idx;
self
}
pub fn on_select( mut self, f: impl Fn( usize ) -> Msg + 'static ) -> Self
{
self.on_select = Some( Arc::new( f ) );
self
}
pub fn on_close( mut self, f: impl Fn( usize ) -> Msg + 'static ) -> Self
{
self.on_close = Some( Arc::new( f ) );
self
}
pub fn reorderable( mut self, reorderable: bool ) -> Self
{
self.reorder = reorderable;
self
}
pub fn motion( mut self, motion: &TabMotion ) -> Self
{
self.motion = motion.clone();
self
}
pub fn title( mut self, title: impl Into<String> ) -> Self
{
self.title = title.into();
self
}
pub fn back( mut self, msg: Msg ) -> Self
{
self.back = Some( msg );
self
}
pub fn action( mut self, button: impl Into<Element<Msg>> ) -> Self
{
self.actions.push( button.into() );
self
}
pub fn colors( mut self, colors: TabStripColors ) -> Self
{
self.colors = colors;
self
}
pub fn build( mut self ) -> Element<Msg>
{
let colors = self.colors;
let header = self.header();
let body = column()
.padding( Length::widget( 12.0 ) )
.spacing( Length::widget( 4.0 ) )
.max_width( Length::widget( BODY_WIDTH ) )
.push( self.cards() );
let page = column()
.padding( 0.0 )
.spacing( 0.0 )
.push( header )
.push( separator().pad_v( 0.0 ) )
.push( scroll( body ) );
container( page ).background( colors.strip ).into()
}
pub fn cards( self ) -> Element<Msg>
{
let cards = self.tabs.iter().enumerate()
.map( |( idx, tab )| ( tab.key, card( tab, idx, idx == self.selected, self.reorder, &self.colors, self.on_select.as_ref(), self.on_close.as_ref() ) ) )
.collect();
TabCards { cards, columns: COLUMNS, spacing: Length::widget( 12.0 ), motion: self.motion }.into()
}
fn header( &mut self ) -> Element<Msg>
{
let ink = self.colors.ink;
let mut top = row().padding( Length::widget( 8.0 ) ).spacing( Length::widget( 8.0 ) );
if let Some( msg ) = self.back.take()
{
top = top.push( parts::symbol_button( BACK_ICON, "<", msg, ink, BUTTON_SIZE, BUTTON_ICON, true ) );
}
top = top.push( text( std::mem::take( &mut self.title ) ).size( 20.0 ).weight( 600 ).color( ink ) ).push( spacer() );
for button in std::mem::take( &mut self.actions )
{
top = top.push( button );
}
top.into()
}
}
impl<Msg: Clone + 'static> From<TabOverview<Msg>> for Element<Msg>
{
fn from( overview: TabOverview<Msg> ) -> Self
{
overview.build()
}
}
pub fn tab_overview<Msg: Clone + 'static>( tabs: impl IntoIterator<Item = TabItem> ) -> TabOverview<Msg>
{
TabOverview::new( tabs )
}
fn card<Msg: Clone + 'static>(
tab: &TabItem,
idx: usize,
active: bool,
draggable: bool,
colors: &TabStripColors,
on_select: Option<&IndexMsg<Msg>>,
on_close: Option<&IndexMsg<Msg>>,
) -> Element<Msg>
{
let ( fill, edge, ink ) = colors.of( active );
let mut content = column()
.padding( Length::widget( 12.0 ) )
.spacing( Length::widget( 10.0 ) )
.push( spacer().height( card_margin() ) );
if let Some( icon ) = parts::icon( tab, icon_size(), ink )
{
content = content.push( icon );
}
content = content
.push( text( tab.label.clone() ).size( 13.0 ).color( ink ).align_center() )
.push( spacer().height( card_margin() ) );
let face = container( content ).background( fill ).border( edge, EDGE_WIDTH ).radius( CARD_RADIUS );
let mut press = pressable( face ).id( WidgetId::keyed( CARD_ID, tab.key ) );
if let Some( select ) = on_select
{
press = press.on_press( select( idx ) );
if draggable { press = press.on_drag_start( select( idx ) ); }
}
let mut card = stack().push( press );
if let Some( close ) = on_close.filter( |_| tab.closable )
{
card = card.push_aligned_margin( parts::close( close( idx ), ink, CLOSE_ICON_SIZE ), HAlign::End, VAlign::Top, 4.0 );
}
card.into()
}