// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! `cargo run --example file_chooser` //! //! Demonstrates `FileChooser`. "Open…" picks a file from the home folder, "Save as…" a folder and a name, offering //! `untitled.txt` and asking before it replaces a file; the page then shows what was chosen, which nothing reads or //! writes. "Compact" switches the next chooser to the phone layout, with the places behind a button. The app holds the //! chooser, puts its view in place of the page, hands it its messages, and passes on the field it wants focused. //! //! Esc closes the chooser, then quits. //! //! NOTE: ltk is a Wayland layer-shell toolkit. This example needs a running Wayland compositor. use ltk::{ App, ButtonVariant, ChannelSender, Chosen, Element, FileChooser, FileChooserMsg, Keysym, Length, WidgetId, button, column, container, row, text, }; #[ derive( Clone ) ] enum Message { Open, SaveAs, ToggleCompact, Chooser( FileChooserMsg ), } struct FileChooserApp { sender: Option>, chooser: Option>, compact: bool, chosen: String, focus: Option, } impl FileChooserApp { fn new() -> Self { Self { sender: None, chooser: None, compact: false, chosen: "Nothing chosen yet".to_string(), focus: None } } fn show( &mut self, chooser: FileChooser ) { let mut chooser = chooser.compact( self.compact ); self.focus = chooser.take_focus_request(); self.chooser = Some( chooser ); } fn page( &self ) -> Element { let compact = if self.compact { "Desktop" } else { "Compact" }; let buttons = row() .spacing( Length::widget( 8.0 ) ) .push( button( "Open…" ).on_press( Message::Open ) ) .push( button( "Save as…" ).variant( ButtonVariant::Secondary ).on_press( Message::SaveAs ) ) .push( button( compact ).variant( ButtonVariant::Tertiary ).on_press( Message::ToggleCompact ) ); let page = column() .padding( 24.0 ) .spacing( Length::widget( 16.0 ) ) .push( buttons ) .push( text( self.chosen.clone() ).size( 16.0 ).wrap( true ) ); container( page ).into() } } impl App for FileChooserApp { type Message = Message; fn app_id( &self ) -> &str { "net.liberux.ltk.example.file_chooser" } fn save_state( &self ) -> Option> { None } fn restore_state( &mut self, _state: Vec ) {} fn set_channel_sender( &mut self, sender: ChannelSender ) { self.sender = Some( sender ); } fn view( &self ) -> Element { match &self.chooser { Some( chooser ) => chooser.view(), None => self.page(), } } fn update( &mut self, msg: Message ) { match msg { Message::Open => { let Some( sender ) = self.sender.clone() else { return }; self.show( FileChooser::open( &sender, Message::Chooser ) ); } Message::SaveAs => { let Some( sender ) = self.sender.clone() else { return }; self.show( FileChooser::save( &sender, Message::Chooser, "untitled.txt" ) ); } Message::ToggleCompact => self.compact = !self.compact, Message::Chooser( msg ) => { let Some( chooser ) = &mut self.chooser else { return }; match chooser.update( msg ) { Some( Chosen::Path( path ) ) => { self.chosen = format!( "Chosen: {}", path.display() ); self.chooser = None; } Some( Chosen::Cancelled ) => { self.chosen = "Cancelled".to_string(); self.chooser = None; } None => if let Some( focus ) = chooser.take_focus_request() { self.focus = Some( focus ); }, } } } } fn take_focus_request( &mut self ) -> Option { self.focus.take() } fn on_key( &mut self, keysym: Keysym ) -> Option { if keysym != Keysym::Escape { return None; } if self.chooser.is_some() { return Some( Message::Chooser( FileChooserMsg::Cancel ) ); } std::process::exit( 0 ); } } fn main() { ltk::run( FileChooserApp::new() ); }