refactor: split every monolithic module into focused submodules

Each source file that had grown beyond a single concern is replaced by an identically-named directory containing focused submodules. `src/event_loop/mod.rs` (878 lines) becomes a directory with clipboard, context_menu, cursor_shape, drag, focus, handlers, invalidation, overlays_reconcile, repeat, run, surface, text_editing, and tooltip. Every widget, input handler, and theme component follows the same split. Public interfaces are unchanged — only the internal file layout moves.
image bumped from 0.25.2 to 0.25.9.
This commit is contained in:
2026-05-15 23:46:56 +02:00
parent 3d237039c6
commit 4aa3480b64
155 changed files with 13832 additions and 13035 deletions
+173
View File
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! Tooltip — short hint anchored below a widget.
//!
//! Like [`Toast`](super::toast::Toast), `Tooltip` is **not** an
//! [`Element`]; it is a builder that produces an [`OverlaySpec`] for
//! the application to return from [`App::overlays`](crate::App::overlays).
//! The overlay is rendered as an `xdg-popup` anchored to the widget
//! tagged with [`Tooltip::anchor_id`] in the previous frame's layout,
//! so the hint appears flush below the trigger and follows it across
//! resizes and re-layouts.
//!
//! Hover detection and the show / hide delay are the **application's
//! responsibility**. ltk does not currently expose pointer-hover
//! callbacks at the widget level, so the typical pattern is:
//!
//! 1. Track which widget the pointer is over via `App::on_tap` or
//! a custom hover bookkeeping you maintain in `update`.
//! 2. Start a timer (via [`App::set_channel_sender`](crate::App::set_channel_sender))
//! that fires a "show tooltip" message after the desired delay.
//! 3. Return the [`Tooltip::overlay`] from `overlays()` only while the
//! tooltip should be visible.
//!
//! ```rust,no_run
//! # use ltk::{ tooltip, OverlaySpec, WidgetId };
//! # struct App { tooltip_for: Option<WidgetId> }
//! # impl App {
//! fn overlays( &self ) -> Vec<OverlaySpec<()>>
//! {
//! match self.tooltip_for
//! {
//! Some( id ) => vec![ tooltip( "Save the document", id ).overlay() ],
//! None => vec![],
//! }
//! }
//! # }
//! ```
use std::collections::hash_map::DefaultHasher;
use std::hash::{ Hash, Hasher };
use crate::app::{ Anchor, Layer, OverlayId, OverlaySpec };
use crate::types::{ Color, WidgetId };
use super::Element;
mod theme;
#[ cfg( test ) ]
mod tests;
/// Builder for an `xdg-popup`-backed hint anchored to a widget.
pub struct Tooltip<Msg: Clone>
{
/// Body text rendered inside the tooltip.
pub message: String,
/// Stable identifier of the widget the popup anchors to. The widget
/// must have been built with `.id( anchor_id )` so the runtime can
/// look its rect up in the previous frame's layout snapshot.
pub anchor_id: WidgetId,
/// Maximum width (logical pixels). The tooltip is single-line; long
/// strings are clipped at the surface boundary.
pub max_width: u32,
/// Optional override for the tooltip background colour.
pub bg: Option<Color>,
/// Optional override for the body text colour.
pub fg: Option<Color>,
/// Optional message fired when the user taps outside the popup.
pub on_dismiss: Option<Msg>,
}
impl<Msg: Clone + 'static> Tooltip<Msg>
{
/// Create a tooltip with the given message anchored to `anchor_id`.
pub fn new( message: impl Into<String>, anchor_id: WidgetId ) -> Self
{
Self
{
message: message.into(),
anchor_id,
max_width: theme::MAX_W,
bg: None,
fg: None,
on_dismiss: None,
}
}
/// Override the maximum tooltip width. Defaults to 320 logical px.
pub fn max_width( mut self, w: u32 ) -> Self
{
self.max_width = w;
self
}
/// Override the background colour.
pub fn background( mut self, c: Color ) -> Self
{
self.bg = Some( c );
self
}
/// Override the body text colour.
pub fn color( mut self, c: Color ) -> Self
{
self.fg = Some( c );
self
}
/// Set the dismiss message fired on a tap outside the popup.
pub fn on_dismiss( mut self, msg: Msg ) -> Self
{
self.on_dismiss = Some( msg );
self
}
/// Build the [`OverlaySpec`] the application returns from
/// [`App::overlays`](crate::App::overlays).
pub fn overlay( self ) -> OverlaySpec<Msg>
{
use super::{ container, text };
let mut hasher = DefaultHasher::new();
"tooltip".hash( &mut hasher );
self.anchor_id.0.hash( &mut hasher );
let overlay_id = OverlayId( hasher.finish() as u32 );
let bg = self.bg.unwrap_or_else( theme::bg );
let fg = self.fg.unwrap_or_else( theme::text );
let body: Element<Msg> = container(
text( self.message.clone() )
.size( theme::FONT_SIZE )
.color( fg )
)
.background( bg )
.padding_h( theme::PAD_H )
.padding_v( theme::PAD_V )
.radius( theme::RADIUS )
.into();
OverlaySpec
{
id: overlay_id,
layer: Layer::Overlay,
anchor: Anchor::TOP,
size: ( self.max_width, theme::HEIGHT ),
exclusive_zone: 0,
keyboard_exclusive: false,
input_region: None,
view: body,
on_dismiss: self.on_dismiss,
anchor_widget_id: Some( self.anchor_id ),
}
}
}
/// Create a [`Tooltip`] anchored to the widget tagged with `anchor_id`.
///
/// ```rust,no_run
/// # use ltk::{ tooltip, OverlaySpec, WidgetId };
/// # fn _ex() -> OverlaySpec<()> {
/// tooltip( "Click to save", WidgetId( "btn/save" ) )
/// .max_width( 240 )
/// .overlay()
/// # }
/// ```
pub fn tooltip<Msg: Clone + 'static>(
message: impl Into<String>,
anchor_id: WidgetId,
) -> Tooltip<Msg>
{
Tooltip::new( message, anchor_id )
}