`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.
151 lines
5.2 KiB
Rust
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
|
|
}
|