Files
ltk/src/theme/active.rs
Pedro M. de Echanove Pasquin 640db23de2
Some checks failed
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled
doc: silence rustdoc intra-doc warnings (private-item links and palette/surface ambiguities)
`cargo doc --no-deps` was emitting eight warnings about intra-doc links resolving to private items or ambiguous fn/module names. None reflect a behaviour bug; they were just noise that cluttered the docs build output.
Five of the warnings came from doc comments that cross-referenced items not in the rendered public API. `widget::scroll::Scroll` (struct), `Scroll::horizontal`, `Scroll::both`, `event_loop::text_editing` (module) and `text_shaping` (module) are all `pub` in their own modules, but the `widget` and `event_loop` parents are private to the crate root, so rustdoc treats them as private when resolving links from items that ARE on the public surface (`ScrollAxis`, the `scroll` constructor, `font_bytes`, the `text_edit` module docs). The fix is to drop the link syntax for those references: keep the identifier in backticks (so it still renders as code) but remove the surrounding `[...]` so rustdoc doesn't try to resolve it. Where the cross-reference had no semantic load beyond "see this module", the prose is rephrased to name the module without trying to link to it (e.g. "the `event_loop::text_editing` private module", "see the `text_shaping` private module").
The remaining three warnings were `palette` / `surface` linking ambiguously between a module of that name and a function of the same name within `crate::theme`. Adding `()` after the identifier inside the brackets (`palette` → `palette()`, `surface` → `surface()`) disambiguates to the function, which is the intent in all three sites — the surrounding text talks about "per-slot shorthand accessors", which is what the functions are.
After this `cargo doc --no-deps` runs clean with no warnings.
2026-05-19 22:41:42 +02:00

151 lines
5.2 KiB
Rust

// 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
}