ltk/theme/document.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! The top-level theme document: metadata, fonts block and per-mode slots.
//!
//! This is the runtime representation of a fully parsed `theme.json`,
//! and the single source of truth for the active theme — installed via
//! [`super::set_active_document`] and read back via
//! [`super::active_document`].
//!
//! # Modes
//!
//! `fonts` is shared between light and dark and only what actually differs
//! — slot tables, wallpaper paths, optional window-controls overrides — is
//! nested under `modes.light` / `modes.dark`.
use std::collections::HashMap;
use std::path::{ Path, PathBuf };
use super::fonts::FontFamilyDef;
use super::slots::SlotStore;
use super::{ search_paths, LauncherSpec, ThemeError, WallpaperSpec, WindowControlsSpec };
// ─── Mode ────────────────────────────────────────────────────────────────────
/// Per-variant content: the slot table plus the surfaces that don't fit
/// cleanly in slots (wallpaper image, window-controls payload).
#[ derive( Debug, Clone ) ]
pub struct Mode
{
/// Homescreen / shell wallpaper. Absent means "solid background, no
/// image" (the renderer falls back to whatever the theme's surface slot
/// resolves to for the viewport).
pub wallpaper: Option<WallpaperSpec>,
/// Lockscreen / greeter wallpaper. Distinct from `wallpaper` because the
/// lockscreen is typically quieter (often a darker crop).
pub lockscreen: Option<WallpaperSpec>,
/// Launcher surface styling. Aggregate (background + border_radius)
/// rather than a slot because the radius is a scalar that widgets treat
/// as theme-imposed geometry, not a design-system colour token.
pub launcher: Option<LauncherSpec>,
/// Window-decoration controls payload. Kept out of the slot table
/// because it is a contract with an external app, not a
/// design-system token.
pub window_controls: Option<WindowControlsSpec>,
/// The typed slot table for this mode.
pub slots: SlotStore,
}
// ─── ThemeDocument ───────────────────────────────────────────────────────────
/// A fully parsed theme, as loaded from a `theme.json` on disk.
#[ derive( Debug, Clone ) ]
pub struct ThemeDocument
{
/// Stable identifier used to look up the theme across search paths.
pub id: String,
/// Human-readable display name.
pub name: String,
/// Directory the document was loaded from. `None` for documents built
/// in-memory (e.g. test fixtures).
pub root: Option<PathBuf>,
/// Font families declared in the document. Indexed by the id used by
/// [`super::FontRef::Named`]. The same registry is shared between modes.
pub fonts: HashMap<String, FontFamilyDef>,
/// Light-mode content.
pub light: Mode,
/// Dark-mode content.
pub dark: Mode,
}
impl ThemeDocument
{
/// Return the mode matching `mode`.
pub fn mode( &self, mode: super::ThemeMode ) -> &Mode
{
match mode
{
super::ThemeMode::Light => &self.light,
super::ThemeMode::Dark => &self.dark,
}
}
/// Load a document from a directory containing a `theme.json`.
///
/// Paths inside the document (wallpaper, lockscreen, font sources) are
/// resolved against `dir`.
pub fn load_from_dir( dir: &Path ) -> Result<Self, ThemeError>
{
super::schema::load_document_from_dir( dir )
}
/// Look up a document by id across the standard search paths.
///
/// Order, highest priority first:
/// 1. `$LTK_THEMES_DIR/<id>/` (when the env var is set)
/// 2. `$XDG_DATA_HOME/ltk/themes/<id>/` (defaults to `~/.local/share/...`)
/// 3. `/usr/share/ltk/themes/<id>/`
pub fn find( id: &str ) -> Result<Self, ThemeError>
{
for base in search_paths()
{
let dir = base.join( id );
if dir.join( "theme.json" ).is_file()
{
return Self::load_from_dir( &dir );
}
}
Err( ThemeError::NotFound( id.to_string() ) )
}
}