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