// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! Widgets — the interactive and decorative leaves of the [`Element`] tree. //! //! Each widget lives in its own submodule and is reached through the //! crate-root re-exports (`button`, `text`, `text_edit`, `slider`, …) plus //! the `img_widget` alias for [`image::Image`]. Construct one from its //! free constructor function, configure it through builder-style methods, //! and convert it into [`Element`] via `.into()` when pushing it //! into a layout. //! //! ```rust,no_run //! # use ltk::{ button, column, slider, text, Element }; //! # #[ derive( Clone ) ] enum Msg { SetVolume( f32 ), Mute } //! # struct App { volume: f32 } //! # impl App { fn _ex( &self ) -> Element { //! column() //! .push( text( "Volume" ) ) //! .push( slider( self.volume ).on_change( |v| Msg::SetVolume( v ) ) ) //! .push( button( "Mute" ).on_press( Msg::Mute ) ) //! .into() //! # }} //! ``` //! //! ## What lives here //! //! * **Buttons / activations**: [`button::Button`], //! [`pressable::Pressable`], [`window_button::WindowButton`], //! [`list_item::ListItem`]. //! * **Stateful binary controls**: [`toggle::Toggle`], //! [`checkbox::Checkbox`], [`radio::Radio`]. //! * **Continuous controls**: [`slider::Slider`], [`vslider::VSlider`], //! [`progress_bar::ProgressBar`]. //! * **Text**: [`text::Text`], [`text_edit::TextEdit`]. //! * **Images / decoration**: [`image::Image`], [`separator::Separator`], //! [`container::Container`]. //! * **Clipping wrappers**: [`scroll::Scroll`] (with gesture-driven //! scrolling), [`viewport::Viewport`] (passive clip / fade), //! [`flex::Flex`] (treats a non-spacer child as a row filler), and //! [`carousel::Carousel`] (horizontal focused-tile carousel with //! host-controlled offset). //! * **Overlays**: [`dialog::Dialog`] (modal / non-modal centered //! confirmation card with built-in scrim, ESC-to-cancel, and //! tap-outside-to-dismiss for the non-modal variant). //! //! Layouts ([`column`](crate::column), [`row`](crate::row), //! [`stack`](crate::stack), [`grid`](crate::grid), //! [`spacer`](crate::spacer)) live in [`crate::layout`]; they share the //! same [`Element`] tree but are kept separate to make the "what does //! this paint" / "how is this arranged" distinction explicit. //! //! ## Per-leaf handler snapshot //! //! [`WidgetHandlers`] is the snapshot the layout pass takes of every //! interactive widget so the input handlers can dispatch in O(1) without //! re-walking the [`Element`] tree. It is `pub( crate )` plumbing for the //! runtime; downstream apps usually never see it. The `test_support` //! module re-exports it for integration tests that want to assert on the //! handler shape. pub mod button; pub mod container; pub mod text_edit; pub mod image; pub mod text; pub mod rich_text; pub mod scroll; pub mod viewport; pub mod slider; pub mod vslider; pub mod toggle; pub mod separator; pub mod progress_bar; pub mod checkbox; pub mod radio; pub mod list_item; pub mod window_button; pub mod pressable; pub mod flex; pub mod combo; pub mod anchored_overlay; pub mod spinner; pub mod tab_bar; pub mod toast; pub mod tooltip; pub mod notebook; pub mod date_picker; pub mod time_picker; pub mod color_picker; pub mod dialog; pub mod external; pub mod carousel; pub mod element; pub mod handlers; pub mod laid_out; pub mod factory; pub use element::Element; pub use handlers::WidgetHandlers; pub use laid_out::LaidOutWidget; pub use factory::{ button, icon_button, text_edit, image, text, container, external }; /// Type alias for the message-mapping closure shared across an /// [`Element::map`] walk. Stored as `Arc` so every per-widget /// `map_msg` can clone and re-share it without copying the closure body /// — the same closure is invoked once per emitted message, regardless /// of how many leaves the sub-tree has. pub( crate ) type MapFn = std::sync::Arc U>; /// Shorten `text` with a trailing ellipsis so it fits `max_w` at `size`. /// /// The single truncation rule for widgets that paint a line of text into a /// rect they do not control: a leaf must stay inside what the layout gave /// it, or a long label runs under its neighbour. Widths accumulate per /// character, which is what [`text::Text`] does with its own inline copy of /// this — folding that one in is pending, as it measures through a font /// override this signature does not carry. pub( crate ) fn elide( canvas: &crate::render::Canvas, text: &str, size: f32, max_w: f32, ) -> String { if max_w <= 0.0 { return String::new(); } // Half-pixel slack: a parent that sized itself from this same // measurement can hand back a width a few ULP short after its // padding add-then-subtract round-trip. if canvas.measure_text( text, size ) <= max_w + 0.5 { return text.to_string(); } let ellipsis = "..."; let budget = max_w - canvas.measure_text( ellipsis, size ); if budget <= 0.0 { return ellipsis.to_string(); } let mut accum = 0.0_f32; let kept: String = text.chars().take_while( |ch| { accum += canvas.measure_text( &ch.to_string(), size ); accum <= budget } ).collect(); format!( "{kept}{ellipsis}" ) }