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:
@@ -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 )
|
||||
}
|
||||
Reference in New Issue
Block a user