// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! 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<Msg>`] 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<Msg> {
//! 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<dyn Fn>` 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<Msg, U> = std::sync::Arc<dyn Fn( Msg ) -> 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}" )
}
