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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//! Design-system-facing default-theme seed seam:
//! `set_default_theme`/`default_theme`.
//!
//! # The gap this closes
//!
//! Every shell seeds itself with a built-in fallback theme
//! (`Theme::neutral()`) that a design-system plugin has no way to
//! replace — the plugin can only reach for [`crate::theme_override::set_app_theme`],
//! but that seam **forces** the active theme end-to-end, pinning it against a
//! live platform appearance change until `clear_app_theme` runs (see
//! [`crate::theme_override::effective_brightness_for_platform_change`]). A
//! Glyph-themed app installed that way would silently stop honouring system
//! dark mode — wrong for a design system that only wants to supply the app's
//! *starting point*, not commandeer its appearance forever. This module is
//! the narrower seam that closes that gap: it supplies the **base** theme a
//! shell seeds itself with in place of its own built-in fallback, while
//! leaving brightness free to keep following the platform.
//!
//! # Precedence
//!
//! A shell resolves the active theme in this order, highest wins:
//!
//! 1. [`crate::theme_override::set_app_theme`] — an app-forced override, if
//! one is active. Brightness is pinned; see that module's
//! override-wins-over-appearance rule.
//! 2. [`set_default_theme`] — the design-system-supplied base, if one was
//! seeded. Brightness is **not** pinned: the shell's base theme object
//! re-derives light/dark from the platform's own appearance against this
//! same base (why [`default_theme`] is a non-destructive read — see
//! below).
//! 3. The shell's own built-in fallback (`Theme::neutral()`), when neither of
//! the above was ever set. A design system is installed, never assumed.
//!
//! # Layering choice
//!
//! This process-global slot lives in `frust-shell-common`, mirroring
//! [`crate::theme_override`]'s slot shape (see that module's doc comment for
//! the fuller layering rationale, which applies here unchanged):
//! `frust-shell-common` already owns the shared, non-FFI "poll a
//! process-global once per frame" plumbing every shell composes around, and
//! already depends on `frust-theme` for the [`Theme`] type this slot holds.
//!
//! # Thread contract
//!
//! Like `theme_override` and `font_registry`, this is a plain `Mutex`-guarded
//! slot with **no thread restriction** — [`set_default_theme`] may be called
//! from any thread (documented, not enforced by a panic): a `Mutex` guards
//! every access, and a shell only *reads* the slot at construction time and
//! when `clear_app_theme` is called (to revert to the base seeded here), so a
//! write racing in from a background thread is simply picked up (or not) on
//! the next such read.
//!
//! # Non-destructive read
//!
//! Unlike [`crate::font_registry`]'s drain-on-poll shape,
//! [`default_theme`] does **not** consume the slot: a shell needs the same
//! seeded base again every time it re-seeds itself (when `clear_app_theme` is
//! called to revert from an app override), not just once at construction. Two
//! consecutive calls to [`default_theme`] with no intervening
//! [`set_default_theme`] call return the same value.
//!
//! # Timing
//!
//! Intended to be called before a shell's first frame — typically from a
//! design-system plugin's `install()`, which runs during app construction.
//! A call *after* the first frame takes effect only on the next
//! `clear_app_theme`-driven reseed, which may never happen if no app override
//! is ever set. Late calls are supported but carry this limitation: a plugin
//! cannot dynamically re-theme a live app by calling this at runtime.
use Mutex;
use Theme;
/// The process-wide default-theme slot: the design-system-supplied base
/// [`Theme`] (`None` when no default has ever been seeded) plus a generation
/// counter bumped on every [`set_default_theme`] call — mirrors
/// [`crate::theme_override`]'s `OverrideSlot` shape.
static DEFAULT: = new;
/// Supply the base theme a shell seeds itself with, in place of its built-in
/// fallback. Call before the first frame — typically from a design-system
/// plugin's `install()`.
///
/// Unlike [`crate::theme_override::set_app_theme`], this does NOT pin
/// brightness: the shell's retained theme object continues to re-derive
/// light/dark from the platform's appearance against this same base (see the
/// module docs' Precedence section). A late call (after the first frame) takes
/// effect only if the app later calls `clear_app_theme`; until then, any
/// active override dominates.
///
/// Callable from any thread (see the module docs' thread contract); the
/// process-wide slot is a plain `Mutex`, not a UI-thread-only primitive.
/// Read the seeded default, if any (`None` when [`set_default_theme`] has
/// never been called). **Non-destructive** — a shell may need it again when
/// reverting an app override via `clear_app_theme`, to re-seed the base (see
/// the module docs' Non-destructive read section); unlike
/// [`crate::font_registry::FontRegistryWatcher::poll`], repeated calls with
/// no intervening [`set_default_theme`] all return the same value rather than
/// draining the slot.