ltk/theme/accessors.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 112 113 114 115 116
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! Slot-typed shorthand accessors against the active theme document.
//!
//! Widgets speak in terms of slot ids. The plumbing for "which document is
//! active" and "which mode" is captured here so widget code stays
//! free of that plumbing.
use crate::types::Color;
use super::active::ensure_active;
use super::paint::Paint;
use super::palette::{ default_window_controls, Palette };
use super::prefs::WindowControlsSpec;
use super::shadow::{ Shadow, ShadowsRef };
use super::surface::Surface;
use super::text_style::TextStyle;
/// Resolve a colour slot in the active mode. `None` when the slot is
/// missing, empty, or not a `Slot::Color` (use [`paint()`] if a gradient
/// should also be acceptable).
pub fn color( id: &str ) -> Option<Color>
{
let state = ensure_active();
state.document.mode( state.mode ).slots.color( id )
}
/// Like [`color`] but returns `fallback` when the slot is missing. The
/// typical pattern for widgets that must always paint something.
pub fn color_or( id: &str, fallback: Color ) -> Color
{
color( id ).unwrap_or( fallback )
}
/// Resolve a paint slot (solid or gradient) in the active mode. A plain
/// `Slot::Color` is promoted to `Paint::Solid`. `None` for missing or
/// non-paintable slots (e.g. a text-style).
pub fn paint( id: &str ) -> Option<Paint>
{
let state = ensure_active();
state.document.mode( state.mode ).slots.paint( id )
}
/// Resolve an elevation (outer-shadow stack) slot in the active mode. The
/// returned `Vec` is a freshly cloned copy so call sites can hold it
/// across frames without borrowing the global state.
pub fn shadows( id: &str ) -> Option<Vec<Shadow>>
{
let state = ensure_active();
state.document.mode( state.mode ).slots.shadows( id ).map( |s| s.to_vec() )
}
/// Resolve a surface slot in the active mode. A colour or paint slot
/// promotes to a surface with that fill and no decorations; a full
/// surface slot returns unchanged.
pub fn surface( id: &str ) -> Option<Surface>
{
let state = ensure_active();
state.document.mode( state.mode ).slots.surface( id )
}
/// Resolve a surface slot together with its outer shadow stack.
///
/// [`Surface::shadows`] may be `ShadowsRef::Named(id)`, pointing at a
/// separate `Slot::Shadows` entry in the same mode. `canvas.fill_surface`
/// expects the stack resolved as a plain `&[Shadow]`, so this helper does
/// the second lookup in one place: widgets call `resolve_surface(id)` and
/// get back the surface plus a `Vec<Shadow>` ready to hand to the canvas.
///
/// Returns `None` only when the surface slot itself is absent. A missing
/// named shadow ref degrades silently to an empty stack — the surface
/// still paints its fill and inset decorations, just without the outer
/// glow.
pub fn resolve_surface( id: &str ) -> Option<( Surface, Vec<Shadow> )>
{
let state = ensure_active();
let mode = state.document.mode( state.mode );
let surface = mode.slots.surface( id )?;
let shadows = match &surface.shadows
{
Some( ShadowsRef::Named( sid ) ) => mode.slots.shadows( sid ).map( |s| s.to_vec() ).unwrap_or_default(),
Some( ShadowsRef::Inline( v ) ) => v.clone(),
None => Vec::new(),
};
Some( ( surface, shadows ) )
}
/// Resolve a typography slot in the active mode. Cloned so the result
/// can outlive the global-state borrow.
pub fn text_style( id: &str ) -> Option<TextStyle>
{
let state = ensure_active();
state.document.mode( state.mode ).slots.text_style( id ).cloned()
}
/// The active mode's window-controls payload, derived from slots if the
/// document did not declare an explicit block.
pub fn window_controls() -> WindowControlsSpec
{
let state = ensure_active();
let mode = state.document.mode( state.mode );
if let Some( wc ) = mode.window_controls { return wc; }
default_window_controls( Palette::from_slots( &mode.slots ) )
}
/// The eight canonical palette slots of the active mode projected as a
/// [`Palette`] struct. This is a one-call shortcut equivalent to
/// `Palette::from_slots(&active_document().mode(active_mode()).slots)`,
/// covering the common case where a widget needs `text_primary` /
/// `surface` / `accent` / etc. without picking specific slot ids.
pub fn palette() -> Palette
{
let state = ensure_active();
Palette::from_slots( &state.document.mode( state.mode ).slots )
}