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

150
src/theme/active.rs Normal file
View File

@@ -0,0 +1,150 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! Process-wide active theme state.
//!
//! The active document is published once and read by every per-slot
//! accessor in [`crate::theme`]. First access triggers a disk load of the
//! `default` theme; if that fails, an embedded B/W document takes over
//! and the [`is_fallback_active`] flag flips on so the draw layer can paint
//! a warning banner.
use std::sync::{ Arc, RwLock };
use super::assets;
use super::document::ThemeDocument;
use super::fallback;
use super::prefs::ThemeMode;
#[ derive( Clone ) ]
pub ( crate ) struct ActiveState
{
/// Currently loaded theme document. The single source of truth —
/// every consumer-facing accessor in this module reads through it
/// against the active [`ThemeMode`].
pub ( crate ) document: Arc<ThemeDocument>,
pub ( crate ) mode: ThemeMode,
/// `true` when the active document was produced by
/// [`fallback::document`] because `ThemeDocument::find("default")`
/// failed. The draw path stamps every frame with a red banner in
/// this state so the user sees the missing-theme signal without
/// the process having to abort.
pub ( crate ) is_fallback: bool,
}
// `RwLock::new` and `Option::None` are both const, so this needs no lazy init.
static ACTIVE: RwLock<Option<ActiveState>> = RwLock::new( None );
/// Read the active state, loading the `default` theme from disk on
/// first access. If the `default` theme cannot be found in any search
/// path, the crate falls back to an in-memory B/W
/// [`fallback::document`], marks the state with `is_fallback = true`
/// (so the draw path can paint the warning banner), and logs a line
/// to stderr pointing at the install instructions. The process never
/// panics on first-access — making ltk embeddable inside programs
/// that want to handle missing theme gracefully.
pub ( crate ) fn ensure_active() -> ActiveState
{
{
let guard = ACTIVE.read().expect( "theme: ACTIVE poisoned" );
if let Some( s ) = guard.as_ref()
{
return s.clone();
}
}
let mut guard = ACTIVE.write().expect( "theme: ACTIVE poisoned" );
if guard.is_none()
{
let ( doc, is_fallback ) = match ThemeDocument::find( "default" )
{
Ok( d ) => ( d, false ),
Err( e ) =>
{
eprintln!
(
"[ltk] default theme not found ({e}); using embedded B/W \
fallback. Install the `ltk-theme-default` Debian package \
(Provides: ltk-theme) or set `LTK_THEMES_DIR` to a \
directory containing `default/theme.json` to get the \
real theme back."
);
( fallback::document(), true )
}
};
*guard = Some( ActiveState
{
document: Arc::new( doc ),
mode: ThemeMode::Light,
is_fallback,
});
}
guard.as_ref().expect( "just installed" ).clone()
}
/// Install `doc` as the active theme. The current mode is preserved
/// (defaulting to [`ThemeMode::Light`] if nothing was set yet). Also
/// clears the fallback flag — an explicit install supersedes the
/// embedded B/W document and the warning banner stops painting from
/// the next frame on.
pub fn set_active_document( doc: ThemeDocument )
{
let mut guard = ACTIVE.write().expect( "theme: ACTIVE poisoned" );
let mode = guard.as_ref().map( |s| s.mode ).unwrap_or( ThemeMode::Light );
*guard = Some( ActiveState
{
document: Arc::new( doc ),
mode,
is_fallback: false,
});
// Drop any rasterised icons cached against the previous theme's
// paths. Cache keys embed absolute paths so old entries are never
// *wrong*, just dead memory; clearing keeps the working set small.
assets::clear_svg_cache();
}
/// Switch the active variant. The document is left untouched — if nothing
/// has been loaded yet, the default theme is loaded first.
pub fn set_active_mode( mode: ThemeMode )
{
// Ensure the default theme is loaded before mutating the mode so we
// never publish an `ActiveState` with a missing document.
let _ = ensure_active();
let mut guard = ACTIVE.write().expect( "theme: ACTIVE poisoned" );
if let Some( s ) = guard.as_mut() { s.mode = mode; }
}
/// The id of the active theme.
pub fn active_theme_id() -> String
{
ensure_active().document.id.clone()
}
/// The active variant (light or dark).
pub fn active_mode() -> ThemeMode
{
ensure_active().mode
}
/// The currently loaded theme document. Use this for slot-typed lookups
/// when the per-slot helpers ([`crate::theme::color`], [`crate::theme::surface`], …) are not
/// expressive enough — e.g. iterating `mode.slots.entries`.
pub fn active_document() -> Arc<ThemeDocument>
{
ensure_active().document
}
/// `true` when the active theme was produced by the embedded B/W
/// fallback because `ThemeDocument::find("default")` failed at
/// first-access. Flipped back to `false` the moment a consumer calls
/// [`set_active_document`] with any document (even another call to
/// the same fallback — the point is "this state came from an
/// explicit install, not from the missing-theme code path").
///
/// The draw layer reads this to decide whether to stamp each surface
/// with the red "install `ltk-theme-default`" banner; apps can read
/// it too, e.g. to log a diagnostic or surface a first-run
/// installation helper.
pub fn is_fallback_active() -> bool
{
ensure_active().is_fallback
}