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
//! Global "environment" query provider: `use_media_query()` and
//! `set_media_query()` (D126) — mirrors `safe_area.rs` exactly, same
//! reasoning: the platform layer measures an OS-level environment value
//! once and publishes it here, so widgets read it as ordinary state instead
//! of every widget branching on platform.
//!
//! # Text scale
//! iOS and Android both let the user scale system-wide text size up or
//! down for accessibility (iOS: Settings → Accessibility → Display & Text
//! Size → Larger Text, surfaced via `UIContentSizeCategory`; Android:
//! Settings → Display → Font size, surfaced via `Configuration.fontScale`).
//! `text_scale` carries that multiplier; `1.0` is the system default.
//! Desktop/web have no equivalent OS-wide accessibility text-scale concept
//! distinct from DPI (DPI/pixel density is already handled separately via
//! `rosace_state::render_scale`), so it stays `1.0` there — not a gap, an
//! intentional platform difference.
//!
//! Applied automatically wherever text size is resolved
//! (`FontCache::measure_text`/`measure_text_weighted` and
//! `PaintCtx::draw_text_at`/`text`/`text_styled`), so every existing
//! widget respects it with zero per-widget code changes — a widget that
//! asks to draw at `14.0`px actually renders (and is measured/laid out,
//! so surrounding containers size correctly around it) at `14.0 *
//! text_scale`px.
//!
//! # Other fields
//! `is_dark`, `bold_text`, `reduce_motion`, and `always_24_hour_format`
//! mirror `text_scale`'s reasoning — each is an OS accessibility/appearance
//! signal, sourced per-platform where the OS actually exposes it, and left
//! at its documented default (`false`) on platforms with no clean source
//! for it (see `rosace-platform`/`rosace-ffi` native push call sites for
//! exactly what's wired per platform). `is_dark` drives
//! `rosace_theme::sync_system_theme`; `bold_text` and `reduce_motion` are
//! applied at the same text/animation choke points `text_scale` uses;
//! `always_24_hour_format` is detection-only today — no widget consumes it
//! yet (`TimePicker` has no 24-hour dial mode).
use GlobalAtom;
use AtomId;
/// Reserved atom ID for the media-query atom (must not collide with other
/// reserved IDs — see `rosace_theme::provider::THEME_ATOM_ID` at 0xFFFF).
const MEDIA_QUERY_ATOM_ID: AtomId = AtomId;
/// OS/environment values a widget might want to adapt to, beyond what
/// `use_platform()`/`use_safe_area()` already cover.
static CURRENT_MEDIA_QUERY: = new;
/// Returns the currently active environment values (`text_scale: 1.0` on
/// platforms that don't have an OS-wide accessibility text-scale setting).
/// Replaces the active environment values. Called by the platform layer on
/// startup and whenever the OS setting changes (iOS:
/// `UIContentSizeCategory.didChangeNotification`; Android:
/// `onConfigurationChanged`); app code should not normally call this.
// ---------------------------------------------------------------------------
// Tests
//
// This is the one platform-agnostic choke point every native push (desktop
// `WindowEvent::ThemeChanged`, web `matchMedia` "change", iOS
// `traitCollectionDidChange`, Android `onConfigurationChanged`, all four in
// `rosace-platform`/`rosace-ffi`) funnels through — so a test here covers
// the push→cache→repaint contract for every platform's native call site at
// once, without needing a live OS on each one to prove it.
// ---------------------------------------------------------------------------