// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! `cargo run --example tab_strip` //! //! Demonstrates the `tab_strip()` widget. On the desktop layout tabs grow as they open, leave a shrinking gap as they close while //! the others wait a second, and can be dragged into another order; `+` after the last tab opens one. The button at the //! left switches to the phone layout, whose counter opens the `tab_overview()` grid, whose cards can be dragged too. The app keeps a `TabMotion`, reports it //! from `is_animating` and forwards the drag to it. //! //! Esc quits. //! //! NOTE: ltk is a Wayland layer-shell toolkit. This example needs a running Wayland compositor. use ltk::{ App, ButtonVariant, Element, Keysym, Length, TabLayout, TabMotion, button, column, container, flex, tab, tab_overview, tab_strip, text, }; #[ derive( Clone ) ] enum Message { Select( usize ), Close( usize ), Move( usize, usize ), New, Overview, Back, ToggleLayout, } struct TabStripApp { tabs: Vec, active: usize, next: u64, layout: TabLayout, overview: bool, motion: TabMotion, } impl TabStripApp { fn new() -> Self { Self { tabs: vec![ 1, 2, 3 ], active: 0, next: 4, layout: TabLayout::Desktop, overview: false, motion: TabMotion::new() } } fn items( &self ) -> impl Iterator + '_ { self.tabs.iter().map( |&key| tab( key, format!( "Document {key}" ) ).symbol( "archives/document" ) ) } fn switch_button( &self ) -> Element { let label = match self.layout { TabLayout::Desktop => "Phone", TabLayout::Mobile => "Desktop", }; button( label ).variant( ButtonVariant::Tertiary ).on_press( Message::ToggleLayout ).into() } fn new_button() -> Element { button( "+" ).variant( ButtonVariant::Tertiary ).width( Length::widget( 40.0 ) ).on_press( Message::New ).into() } fn page( &self ) -> Element { let title = self.tabs.get( self.active ).map_or_else( || "No tabs".to_string(), |key| format!( "Document {key}" ) ); container( text( title ).size( 28.0 ) ).padding( 24.0 ).into() } } impl App for TabStripApp { type Message = Message; fn app_id( &self ) -> &str { "net.liberux.ltk.example.tab_strip" } fn save_state( &self ) -> Option> { None } fn restore_state( &mut self, _state: Vec ) {} fn view( &self ) -> Element { if self.overview { return tab_overview( self.items() ) .selected( self.active ) .on_select( Message::Select ) .on_close( Message::Close ) .reorderable( true ) .motion( &self.motion ) .title( "Tabs" ) .back( Message::Back ) .action( Self::new_button() ) .into(); } let bar = tab_strip( self.items() ) .selected( self.active ) .on_select( Message::Select ) .on_close( Message::Close ) .on_overview( Message::Overview ) .reorderable( true ) .leading( self.switch_button() ) .after_tabs( Self::new_button() ) .trailing( button( "Menu" ).variant( ButtonVariant::Tertiary ) ) .layout( self.layout ) .motion( &self.motion ); column().padding( 0.0 ).spacing( 0.0 ).push( bar ).push( flex( self.page() ) ).into() } fn update( &mut self, msg: Message ) { match msg { Message::Select( idx ) => { self.active = idx.min( self.tabs.len().saturating_sub( 1 ) ); self.overview = false; } Message::Close( idx ) => { if idx >= self.tabs.len() { return; } self.tabs.remove( idx ); if idx < self.active || self.active >= self.tabs.len() { self.active = self.active.saturating_sub( 1 ); } } Message::Move( from, to ) => { let active = self.tabs.get( self.active ).copied(); let key = self.tabs.remove( from ); self.tabs.insert( to, key ); self.active = active.and_then( |key| self.tabs.iter().position( |&k| k == key ) ).unwrap_or( to ); } Message::New => { self.tabs.push( self.next ); self.next += 1; self.active = self.tabs.len() - 1; self.overview = false; } Message::Overview => self.overview = true, Message::Back => self.overview = false, Message::ToggleLayout => { self.layout = match self.layout { TabLayout::Desktop => TabLayout::Mobile, TabLayout::Mobile => TabLayout::Desktop, }; } } } fn is_animating( &self ) -> bool { self.motion.is_animating() } fn on_drag_move( &mut self, x: f32, y: f32 ) { self.motion.drag_move( x, y ); } fn on_drop( &mut self, _x: f32, _y: f32 ) -> Option { self.motion.drag_end().map( |( from, to )| Message::Move( from, to ) ) } fn on_key( &mut self, keysym: Keysym ) -> Option { if keysym == Keysym::Escape { std::process::exit( 0 ); } None } } fn main() { ltk::run( TabStripApp::new() ); }