GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/color/mod.rs (68.1K)
1 mod load;
2 pub use load::*;
3 mod math;
4 pub use math::*;
5 mod chords;
6 pub use chords::*;
7 mod materials;
8 pub use materials::*;
9
10 pub const HEADER_BG: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
11 pub const HEADER_ACCENT: [f32; 4] = [0.60, 0.40, 0.20, 1.0];
12 pub const SIDEBAR_BG: [f32; 4] = [0.10, 0.10, 0.13, 1.0];
13 pub const CONTENT_BG: [f32; 4] = [0.13, 0.13, 0.16, 0.2];
14 pub const PANEL_IDLE: [f32; 4] = [0.14, 0.70, 0.38, 1.0];
15 pub const PANEL_DRAG: [f32; 4] = [0.24, 0.85, 0.50, 1.0];
16 pub const NODE_IDLE: [f32; 4] = [0.10, 0.45, 0.70, 1.0];
17 pub const NODE_SELECTED: [f32; 4] = [0.20, 0.65, 0.90, 1.0];
18 pub const NODE_DRAG: [f32; 4] = [0.30, 0.80, 1.00, 1.0];
19 pub const BUTTON_IDLE: [f32; 4] = [0.20, 0.40, 0.65, 0.4];
20 pub const BUTTON_HOVER: [f32; 4] = [0.30, 0.52, 0.78, 0.6];
21 pub const BUTTON_PRESS: [f32; 4] = [0.12, 0.28, 0.50, 0.8];
22 pub const STATUS_BG: [f32; 4] = [0.06, 0.06, 0.10, 1.0];
23 pub const STATUS_ACCENT: [f32; 4] = [0.20, 0.20, 0.25, 1.0];
24 pub const RESET_BTN_IDLE: [f32; 4] = [0.55, 0.20, 0.20, 0.4];
25 pub const RESET_BTN_HOVER: [f32; 4] = [0.70, 0.30, 0.30, 0.6];
26 pub const RESET_BTN_PRESS: [f32; 4] = [0.40, 0.12, 0.12, 0.8];
27 pub const TOGGLE_OFF: [f32; 4] = [0.25, 0.25, 0.30, 1.0];
28 pub const TOGGLE_ON: [f32; 4] = [0.14, 0.70, 0.38, 1.0];
29 pub const TOGGLE_HOVER: [f32; 4] = [0.30, 0.30, 0.35, 1.0];
30 pub const SLIDER_TRACK: [f32; 4] = [0.18, 0.18, 0.22, 1.0];
31
32
33 // This module's part of the one style snapshot (`crate::style`): every slot below,
34 // with its default. Each slot's `static` handle stands where its lock did.
35 crate::style::style_slots! {
36 NAMED_MATERIALS: Vec<(String, crate::scene::material::MaterialDef)> = Vec::new();
37 MATERIAL_BINDINGS: [Option<String>; 3] = [None, None, None];
38 PAGE_LOW_COLOR: [f32; 4] = [0.0600316, 0.0600316, 0.080219, 1.0];
39 COLOR_BORDERS_COLOR: [f32; 4] = [0.2039, 0.2039, 0.2530, 1.0];
40 NODE_COLOR: [f32; 4] = NODE_IDLE;
41 NODE_SELECTED_COLOR: [f32; 4] = NODE_SELECTED;
42 NODE_DRAG_COLOR: [f32; 4] = NODE_DRAG;
43 SIDEBAR_BG_COLOR: [f32; 4] = SIDEBAR_BG;
44 HIGHLIGHT_PRIMARY_COLOR: [f32; 4] = HIGHLIGHT_PRIMARY;
45 MENUBAR_TAB_LABEL_COLOR: [f32; 4] = [0.90196, 0.90196, 0.94902, 1.0];
46 CONTROL_LABEL_COLOR: [f32; 4] = [0.61206, 0.61206, 0.68666, 1.0];
47 CONTROL_LABEL_COLOR_DETACHED: [f32; 4] = [0.22416, 0.22416, 0.2526, 1.0];
48 CONTROL_LABEL_HOVER_COLOR: Option<[f32; 4]> = None;
49 CONTROL_LABEL_FOCUS_COLOR: Option<[f32; 4]> = None;
50 OPACITY: Option<f32> = None;
51 ROOT_PLATE_OPACITY: Option<f32> = None;
52 LIST_BG_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 0.3];
53 LIST_ENTRY_BG_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 0.04];
54 LIST_ENTRY_HIGHLIGHT_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 0.8];
55 LIST_FONT_COLOR: [f32; 4] = [0.80, 0.80, 0.85, 1.0];
56 BREADCRUMB_BG_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
57 POPOVER_BG_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
58 PAGE_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
59 LAYER_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
60 ROOT_PLATE_CORNER_RADIUS: f32 = 12.0;
61 DROPDOWN_BACKGROUND_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
62 TEXTBOX_PLACEHOLDER_TEXT_COLOR: [u8; 3] = [0x60, 0x60, 0x6a];
63 TEXTBOX_BACKGROUND_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
64 TEXTBOX_BACKGROUND_EDIT_COLOR: [f32; 4] = [0.06, 0.10, 0.18, 1.0];
65 ROOT_PLATE_MENUBAR_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
66 ROOT_PLATE_MENUBAR_TEXT_COLOR: [f32; 4] = [0.90196, 0.90196, 0.94902, 1.0];
67 ROOT_PLATE_MENUBAR_BLUR: bool = false;
68 MENU_OPACITY: f32 = 0.8;
69 MENU_COMPRESSION: f32 = 0.6;
70 MENU_COLOR: Option<[f32; 4]> = None;
71 ROOT_PLATE_STATUSBAR_COLOR: [f32; 4] = [0.06, 0.06, 0.10, 1.0];
72 ROOT_PLATE_STATUSBAR_TEXT_COLOR: [f32; 4] = [0.6666, 0.6666, 0.7333, 1.0];
73 ROOT_PLATE_STATUSBAR_BLUR: bool = false;
74 BUTTON_BACKGROUND_COLOR: [f32; 4] = BUTTON_IDLE;
75 RAMP_BACKGROUND_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
76 RAMP_BORDER_COLOR: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
77 CONTROL_PANEL_COLOR: [f32; 4] = [0.075, 0.082, 0.11, 1.0];
78 CONTROL_PANEL_BORDER_COLOR: [f32; 4] = [0.161, 0.173, 0.216, 1.0];
79 PROGRESS_BG_COLOR: [f32; 4] = PROGRESS_BG;
80 PROGRESS_FILL_COLOR: [f32; 4] = PROGRESS_FILL;
81 SPINBOX_DISPLAY_COLOR: [f32; 4] = SPINBOX_DISPLAY;
82 SPINBOX_BUTTON_COLOR: [f32; 4] = SPINBOX_BUTTON;
83 SPINBOX_BUTTON_HOVER_COLOR: [f32; 4] = SPINBOX_BUTTON_HOVER;
84 SPINBOX_TEXT_COLOR: [f32; 4] = [0.8, 0.8, 0.83, 1.0];
85 BUTTON_BORDER_COLOR: Option<[f32; 4]> = None;
86 DROPDOWN_BORDER_COLOR: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
87 DROPDOWN_TEXT_COLOR: [f32; 4] = [0.72305, 0.72305, 0.76008, 1.0];
88 SLIDER_THUMB_COLOR: [f32; 4] = SLIDER_THUMB;
89 SLIDER_THUMB_DRAG_COLOR: [f32; 4] = SLIDER_THUMB_DRAG;
90 RANGE_SLIDER_THUMB_COLOR: [f32; 4] = SLIDER_THUMB;
91 RANGE_SLIDER_THUMB_DRAG_COLOR: [f32; 4] = SLIDER_THUMB_DRAG;
92 TREE_BACKGROUND_COLOR: [f32; 4] = [0.08, 0.08, 0.12, 0.3];
93 TREE_BORDER_COLOR: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
94 TREE_BORDER_HOVER_COLOR: [f32; 4] = [0.25, 0.25, 0.35, 1.0];
95 TREE_BORDER_FOCUS_COLOR: [f32; 4] = [0.30, 0.50, 0.32, 1.0];
96 TREE_OPEN_SEARCH_KEY: String = String::new();
97 LIST_OPEN_SEARCH_KEY: String = String::new();
98 LIST_CLOSE_SEARCH_KEY: String = String::new();
99 TREE_SECTION_BG_COLOR: [f32; 4] = [0.07, 0.07, 0.09, 1.0];
100 TREE_SECTION_BG_HOVER_COLOR: [f32; 4] = [0.10, 0.12, 0.18, 1.0];
101 TREE_LEAF_BG_EVEN_COLOR: [f32; 4] = [0.09, 0.09, 0.11, 1.0];
102 TREE_LEAF_BG_ODD_COLOR: [f32; 4] = [0.08, 0.08, 0.10, 1.0];
103 TREE_LEAF_BG_HOVER_COLOR: [f32; 4] = [0.12, 0.12, 0.16, 1.0];
104 TREE_LEAF_BG_SELECTED_COLOR: [f32; 4] = [0.15, 0.20, 0.30, 1.0];
105 TREE_SECTION_TEXT_COLOR: [f32; 4] = [0.38, 0.69, 0.94, 1.0];
106 TREE_LEAF_TEXT_COLOR: [f32; 4] = [0.80, 0.80, 0.83, 1.0];
107 TREE_LEAF_TEXT_SELECTED_COLOR: [f32; 4] = [0.49, 1.0, 1.0, 1.0];
108 TREE_TYPE_TEXT_COLOR: [f32; 4] = [0.78, 0.47, 0.87, 1.0];
109 TREE_VALUE_TEXT_COLOR: [f32; 4] = [0.51, 0.51, 0.54, 1.0];
110 TREE_SEPARATOR_COLOR: [f32; 4] = [0.15, 0.15, 0.19, 1.0];
111 SCROLLBAR_TRACK_COLOR: [f32; 4] = [0.15, 0.15, 0.20, 0.3];
112 SCROLLBAR_THUMB_COLOR: [f32; 4] = [0.60, 0.60, 0.65, 0.4];
113 GRAPH_GRID_COLOR: [f32; 3] = [0.07, 0.07, 0.09];
114 GRAPH_OPACITY: f32 = 0.95;
115 GRAPH_NODE_OPACITY: f32 = 1.0;
116 GRAPH_NODE_COLOR: [f32; 4] = NODE_IDLE;
117 GRAPH_NODE_SELECTED_COLOR: [f32; 4] = NODE_SELECTED;
118 GRAPH_NODE_DRAG_COLOR: [f32; 4] = NODE_DRAG;
119 GRAPH_WIRE_COLOR: [f32; 4] = [0.1, 0.8, 0.4, 1.0];
120 GRAPH_WIRE_HIGHLIGHT_COLOR: [f32; 4] = [0.0, 1.0, 0.9, 1.0];
121 GRAPH_CONNECTOR_COLOR: [f32; 4] = [0.1, 0.8, 0.4, 1.0];
122 GRAPH_CONNECTOR_HIGHLIGHT_COLOR: [f32; 4] = [0.0, 1.0, 0.9, 1.0];
123 PARAM_BG_COLOR: [f32; 4] = PARAM_BG;
124 PANE_COLOR_WHOLE: bool = false;
125 PLATE_COLOR: Option<[f32; 4]> = Some([0.15, 0.15, 0.2, 0.95]);
126 PLATE_BORDER_COLOR: Option<[f32; 4]> = Some([0.3, 0.3, 0.4, 1.0]);
127 PLATE_BORDER_THICKNESS: f32 = 1.0;
128 PLATE_BACKDROP_COMPRESSION: f32 = 0.0;
129 PLATE_REFRACTION: f32 = 0.0;
130 PLATE_BLUR: bool = false;
131 FINISH_SPEC: f32 = FINISH_SPEC_DEFAULT;
132 FINISH_SHININESS: f32 = FINISH_SHININESS_DEFAULT;
133 FINISH_CURVATURE: f32 = FINISH_CURVATURE_DEFAULT;
134 PLATE_FROST_RADIUS: f32 = crate::scene::material::Frost::DEFAULT_RADIUS;
135 }
136
137 /// Per-thread overrides for runtime colour writes, under `cfg(test)` only.
138 ///
139 /// Same defect as the font flake (cce-ui 1dc0ab1) and the style registry
140 /// beside it: these are process-wide `RwLock` statics, so a test that pins a
141 /// colour pins it for every test running alongside. It was live —
142 /// `test_graph_style_configuration` sets a dozen of them and restores none.
143 ///
144 /// Keyed by the address of the static itself, so a slot needs no name
145 /// repeated in two places and cannot be typo'd into a silent miss. Config
146 /// loading is unaffected: `parse_and_set_colors` writes the statics directly
147 /// and never goes through these setters, so the loaded palette stays the
148 /// shared base every test thread reads.
149 #[cfg(test)]
150 mod test_overlay {
151 use std::any::Any;
152 use std::cell::RefCell;
153 use std::collections::HashMap;
154 thread_local! {
155 static MAP: RefCell<HashMap<usize, Box<dyn Any>>> = RefCell::new(HashMap::new());
156 }
157 pub fn get<T: Clone + 'static>(key: usize) -> Option<T> {
158 MAP.with(|m| m.borrow().get(&key).and_then(|b| b.downcast_ref::<T>()).cloned())
159 }
160 pub fn set<T: 'static>(key: usize, val: T) {
161 MAP.with(|m| m.borrow_mut().insert(key, Box::new(val)));
162 }
163 }
164
165 /// Read a style static, preferring this thread's test override.
166 #[inline]
167 fn style_read<T: Clone + 'static>(cell: &'static crate::style::StyleCell<T>) -> T {
168 #[cfg(test)]
169 if let Some(v) = test_overlay::get::<T>(cell as *const _ as usize) {
170 return v;
171 }
172 cell.read().unwrap().clone()
173 }
174
175 /// Write a style static: per-thread under `cfg(test)`, process-wide otherwise.
176 #[inline]
177 fn style_write<T: Clone + 'static>(cell: &'static crate::style::StyleCell<T>, val: T) {
178 #[cfg(test)]
179 test_overlay::set(cell as *const _ as usize, val);
180 #[cfg(not(test))]
181 if let Ok(mut lock) = cell.write() {
182 *lock = val;
183 }
184 }
185
186
187 static PAGE_LOW_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PAGE_LOW_COLOR, |s| &mut s.color.PAGE_LOW_COLOR);
188 static COLOR_BORDERS_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.COLOR_BORDERS_COLOR, |s| &mut s.color.COLOR_BORDERS_COLOR);
189 static NODE_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.NODE_COLOR, |s| &mut s.color.NODE_COLOR);
190 static NODE_SELECTED_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.NODE_SELECTED_COLOR, |s| &mut s.color.NODE_SELECTED_COLOR);
191 static NODE_DRAG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.NODE_DRAG_COLOR, |s| &mut s.color.NODE_DRAG_COLOR);
192 static SIDEBAR_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SIDEBAR_BG_COLOR, |s| &mut s.color.SIDEBAR_BG_COLOR);
193 static HIGHLIGHT_PRIMARY_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.HIGHLIGHT_PRIMARY_COLOR, |s| &mut s.color.HIGHLIGHT_PRIMARY_COLOR);
194 static MENUBAR_TAB_LABEL_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.MENUBAR_TAB_LABEL_COLOR, |s| &mut s.color.MENUBAR_TAB_LABEL_COLOR); // sRGB [230, 230, 242] linear
195 static CONTROL_LABEL_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.CONTROL_LABEL_COLOR, |s| &mut s.color.CONTROL_LABEL_COLOR); // sRGB [204, 204, 212]
196 static CONTROL_LABEL_COLOR_DETACHED: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.CONTROL_LABEL_COLOR_DETACHED, |s| &mut s.color.CONTROL_LABEL_COLOR_DETACHED); // sRGB [131, 131, 138]
197 static CONTROL_LABEL_HOVER_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.CONTROL_LABEL_HOVER_COLOR, |s| &mut s.color.CONTROL_LABEL_HOVER_COLOR);
198 static CONTROL_LABEL_FOCUS_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.CONTROL_LABEL_FOCUS_COLOR, |s| &mut s.color.CONTROL_LABEL_FOCUS_COLOR);
199 static OPACITY: crate::style::StyleCell<Option<f32>> = crate::style::StyleCell::new(|s| &s.color.OPACITY, |s| &mut s.color.OPACITY);
200 static ROOT_PLATE_OPACITY: crate::style::StyleCell<Option<f32>> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_OPACITY, |s| &mut s.color.ROOT_PLATE_OPACITY);
201 static LIST_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LIST_BG_COLOR, |s| &mut s.color.LIST_BG_COLOR);
202 static LIST_ENTRY_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LIST_ENTRY_BG_COLOR, |s| &mut s.color.LIST_ENTRY_BG_COLOR);
203 static LIST_ENTRY_HIGHLIGHT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LIST_ENTRY_HIGHLIGHT_COLOR, |s| &mut s.color.LIST_ENTRY_HIGHLIGHT_COLOR);
204 static LIST_FONT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LIST_FONT_COLOR, |s| &mut s.color.LIST_FONT_COLOR);
205 static BREADCRUMB_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.BREADCRUMB_BG_COLOR, |s| &mut s.color.BREADCRUMB_BG_COLOR);
206 static POPOVER_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.POPOVER_BG_COLOR, |s| &mut s.color.POPOVER_BG_COLOR);
207 static PAGE_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PAGE_COLOR, |s| &mut s.color.PAGE_COLOR);
208 static LAYER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.LAYER_COLOR, |s| &mut s.color.LAYER_COLOR);
209 static ROOT_PLATE_CORNER_RADIUS: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_CORNER_RADIUS, |s| &mut s.color.ROOT_PLATE_CORNER_RADIUS);
210
211 // Transparent by default (alpha 0): a dropdown picks up the surface it sits
212 // on, and its closed-state chrome is the flush inset trough alone — the
213 // cce-files treatment, DE-wide. A configured `dropdown color=` opts a theme
214 // back into a filled face (the paint path judges the RAW alpha).
215 static DROPDOWN_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.DROPDOWN_BACKGROUND_COLOR, |s| &mut s.color.DROPDOWN_BACKGROUND_COLOR);
216
217 static TEXTBOX_PLACEHOLDER_TEXT_COLOR: crate::style::StyleCell<[u8; 3]> = crate::style::StyleCell::new(|s| &s.color.TEXTBOX_PLACEHOLDER_TEXT_COLOR, |s| &mut s.color.TEXTBOX_PLACEHOLDER_TEXT_COLOR);
218 static TEXTBOX_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TEXTBOX_BACKGROUND_COLOR, |s| &mut s.color.TEXTBOX_BACKGROUND_COLOR);
219 static TEXTBOX_BACKGROUND_EDIT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TEXTBOX_BACKGROUND_EDIT_COLOR, |s| &mut s.color.TEXTBOX_BACKGROUND_EDIT_COLOR);
220
221 static ROOT_PLATE_MENUBAR_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_MENUBAR_COLOR, |s| &mut s.color.ROOT_PLATE_MENUBAR_COLOR);
222 static ROOT_PLATE_MENUBAR_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_MENUBAR_TEXT_COLOR, |s| &mut s.color.ROOT_PLATE_MENUBAR_TEXT_COLOR);
223 static ROOT_PLATE_MENUBAR_BLUR: crate::style::StyleCell<bool> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_MENUBAR_BLUR, |s| &mut s.color.ROOT_PLATE_MENUBAR_BLUR);
224 /// Tint strength of frosted menus/popovers over the blurred backdrop:
225 /// 1.0 is fully opaque (frost invisible), lower shows more content through.
226 static MENU_OPACITY: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.MENU_OPACITY, |s| &mut s.color.MENU_OPACITY);
227 /// Backdrop compression of frosted menus/popovers (`style.surface.menu.
228 /// compression`, 0..1): how hard the blurred content beneath a menu is
229 /// pulled toward the menu's own key, so the menu holds its legibility over
230 /// whatever it opens above. Menu-scoped, overriding the DE recipe's
231 /// `plate.backdrop_compression` for popovers only; a menu is read while
232 /// something else is going on beneath it, which a pane is not.
233 static MENU_COMPRESSION: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.MENU_COMPRESSION, |s| &mut s.color.MENU_COMPRESSION);
234 /// The colour a menu's face is tinted with (`style.surface.menu.color`),
235 /// when the config names one; `None` follows the root plate colour
236 /// (`page_low_color`), which is what every menu wore before the key
237 /// existed. The alpha is ignored — `menu.opacity` is the tint strength —
238 /// so a menu can be dark on a light window without the window's own
239 /// plate going dark with it.
240 static MENU_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.MENU_COLOR, |s| &mut s.color.MENU_COLOR);
241
242 static ROOT_PLATE_STATUSBAR_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_STATUSBAR_COLOR, |s| &mut s.color.ROOT_PLATE_STATUSBAR_COLOR);
243 static ROOT_PLATE_STATUSBAR_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_STATUSBAR_TEXT_COLOR, |s| &mut s.color.ROOT_PLATE_STATUSBAR_TEXT_COLOR);
244 static ROOT_PLATE_STATUSBAR_BLUR: crate::style::StyleCell<bool> = crate::style::StyleCell::new(|s| &s.color.ROOT_PLATE_STATUSBAR_BLUR, |s| &mut s.color.ROOT_PLATE_STATUSBAR_BLUR);
245 static BUTTON_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.BUTTON_BACKGROUND_COLOR, |s| &mut s.color.BUTTON_BACKGROUND_COLOR);
246 static RAMP_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.RAMP_BACKGROUND_COLOR, |s| &mut s.color.RAMP_BACKGROUND_COLOR);
247 static RAMP_BORDER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.RAMP_BORDER_COLOR, |s| &mut s.color.RAMP_BORDER_COLOR);
248 static CONTROL_PANEL_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.CONTROL_PANEL_COLOR, |s| &mut s.color.CONTROL_PANEL_COLOR); // default #13151cff
249 static CONTROL_PANEL_BORDER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.CONTROL_PANEL_BORDER_COLOR, |s| &mut s.color.CONTROL_PANEL_BORDER_COLOR); // default #292c37ff
250
251 static PROGRESS_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PROGRESS_BG_COLOR, |s| &mut s.color.PROGRESS_BG_COLOR);
252 static PROGRESS_FILL_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PROGRESS_FILL_COLOR, |s| &mut s.color.PROGRESS_FILL_COLOR);
253 static SPINBOX_DISPLAY_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SPINBOX_DISPLAY_COLOR, |s| &mut s.color.SPINBOX_DISPLAY_COLOR);
254 static SPINBOX_BUTTON_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SPINBOX_BUTTON_COLOR, |s| &mut s.color.SPINBOX_BUTTON_COLOR);
255 static SPINBOX_BUTTON_HOVER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SPINBOX_BUTTON_HOVER_COLOR, |s| &mut s.color.SPINBOX_BUTTON_HOVER_COLOR);
256 static SPINBOX_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SPINBOX_TEXT_COLOR, |s| &mut s.color.SPINBOX_TEXT_COLOR);
257
258 static BUTTON_BORDER_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.BUTTON_BORDER_COLOR, |s| &mut s.color.BUTTON_BORDER_COLOR);
259
260 static DROPDOWN_BORDER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.DROPDOWN_BORDER_COLOR, |s| &mut s.color.DROPDOWN_BORDER_COLOR);
261 static DROPDOWN_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.DROPDOWN_TEXT_COLOR, |s| &mut s.color.DROPDOWN_TEXT_COLOR);
262
263 static SLIDER_THUMB_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SLIDER_THUMB_COLOR, |s| &mut s.color.SLIDER_THUMB_COLOR);
264 static SLIDER_THUMB_DRAG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SLIDER_THUMB_DRAG_COLOR, |s| &mut s.color.SLIDER_THUMB_DRAG_COLOR);
265
266 static RANGE_SLIDER_THUMB_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.RANGE_SLIDER_THUMB_COLOR, |s| &mut s.color.RANGE_SLIDER_THUMB_COLOR);
267 static RANGE_SLIDER_THUMB_DRAG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.RANGE_SLIDER_THUMB_DRAG_COLOR, |s| &mut s.color.RANGE_SLIDER_THUMB_DRAG_COLOR);
268
269 static TREE_BACKGROUND_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_BACKGROUND_COLOR, |s| &mut s.color.TREE_BACKGROUND_COLOR);
270 static TREE_BORDER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_BORDER_COLOR, |s| &mut s.color.TREE_BORDER_COLOR);
271 static TREE_BORDER_HOVER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_BORDER_HOVER_COLOR, |s| &mut s.color.TREE_BORDER_HOVER_COLOR);
272 static TREE_BORDER_FOCUS_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_BORDER_FOCUS_COLOR, |s| &mut s.color.TREE_BORDER_FOCUS_COLOR);
273 static TREE_OPEN_SEARCH_KEY: crate::style::StyleCell<String> = crate::style::StyleCell::new(|s| &s.color.TREE_OPEN_SEARCH_KEY, |s| &mut s.color.TREE_OPEN_SEARCH_KEY);
274 static LIST_OPEN_SEARCH_KEY: crate::style::StyleCell<String> = crate::style::StyleCell::new(|s| &s.color.LIST_OPEN_SEARCH_KEY, |s| &mut s.color.LIST_OPEN_SEARCH_KEY);
275 static LIST_CLOSE_SEARCH_KEY: crate::style::StyleCell<String> = crate::style::StyleCell::new(|s| &s.color.LIST_CLOSE_SEARCH_KEY, |s| &mut s.color.LIST_CLOSE_SEARCH_KEY);
276
277 static TREE_SECTION_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_SECTION_BG_COLOR, |s| &mut s.color.TREE_SECTION_BG_COLOR);
278 static TREE_SECTION_BG_HOVER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_SECTION_BG_HOVER_COLOR, |s| &mut s.color.TREE_SECTION_BG_HOVER_COLOR);
279
280 static TREE_LEAF_BG_EVEN_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_BG_EVEN_COLOR, |s| &mut s.color.TREE_LEAF_BG_EVEN_COLOR);
281 static TREE_LEAF_BG_ODD_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_BG_ODD_COLOR, |s| &mut s.color.TREE_LEAF_BG_ODD_COLOR);
282 static TREE_LEAF_BG_HOVER_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_BG_HOVER_COLOR, |s| &mut s.color.TREE_LEAF_BG_HOVER_COLOR);
283 static TREE_LEAF_BG_SELECTED_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_BG_SELECTED_COLOR, |s| &mut s.color.TREE_LEAF_BG_SELECTED_COLOR);
284
285 static TREE_SECTION_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_SECTION_TEXT_COLOR, |s| &mut s.color.TREE_SECTION_TEXT_COLOR);
286 static TREE_LEAF_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_TEXT_COLOR, |s| &mut s.color.TREE_LEAF_TEXT_COLOR);
287 static TREE_LEAF_TEXT_SELECTED_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_LEAF_TEXT_SELECTED_COLOR, |s| &mut s.color.TREE_LEAF_TEXT_SELECTED_COLOR);
288
289 static TREE_TYPE_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_TYPE_TEXT_COLOR, |s| &mut s.color.TREE_TYPE_TEXT_COLOR);
290 static TREE_VALUE_TEXT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_VALUE_TEXT_COLOR, |s| &mut s.color.TREE_VALUE_TEXT_COLOR);
291 static TREE_SEPARATOR_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.TREE_SEPARATOR_COLOR, |s| &mut s.color.TREE_SEPARATOR_COLOR);
292
293 static SCROLLBAR_TRACK_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SCROLLBAR_TRACK_COLOR, |s| &mut s.color.SCROLLBAR_TRACK_COLOR);
294 static SCROLLBAR_THUMB_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.SCROLLBAR_THUMB_COLOR, |s| &mut s.color.SCROLLBAR_THUMB_COLOR);
295
296 static GRAPH_GRID_COLOR: crate::style::StyleCell<[f32; 3]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_GRID_COLOR, |s| &mut s.color.GRAPH_GRID_COLOR);
297 static GRAPH_OPACITY: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.GRAPH_OPACITY, |s| &mut s.color.GRAPH_OPACITY);
298 /// Opacity of the graph's NODE-domain content (node bodies, wires, connectors,
299 /// node text) — `style.surface.graph.node.opacity`, deliberately independent of
300 /// `GRAPH_OPACITY`, which fades only the pane surface (grid cells/gaps).
301 static GRAPH_NODE_OPACITY: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.GRAPH_NODE_OPACITY, |s| &mut s.color.GRAPH_NODE_OPACITY);
302
303 static GRAPH_NODE_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_NODE_COLOR, |s| &mut s.color.GRAPH_NODE_COLOR);
304 static GRAPH_NODE_SELECTED_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_NODE_SELECTED_COLOR, |s| &mut s.color.GRAPH_NODE_SELECTED_COLOR);
305 static GRAPH_NODE_DRAG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_NODE_DRAG_COLOR, |s| &mut s.color.GRAPH_NODE_DRAG_COLOR);
306
307 static GRAPH_WIRE_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_WIRE_COLOR, |s| &mut s.color.GRAPH_WIRE_COLOR);
308 static GRAPH_WIRE_HIGHLIGHT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_WIRE_HIGHLIGHT_COLOR, |s| &mut s.color.GRAPH_WIRE_HIGHLIGHT_COLOR);
309
310 static GRAPH_CONNECTOR_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_CONNECTOR_COLOR, |s| &mut s.color.GRAPH_CONNECTOR_COLOR);
311 static GRAPH_CONNECTOR_HIGHLIGHT_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.GRAPH_CONNECTOR_HIGHLIGHT_COLOR, |s| &mut s.color.GRAPH_CONNECTOR_HIGHLIGHT_COLOR);
312
313 pub fn button_background_color() -> [f32; 4] {
314 style_read(&BUTTON_BACKGROUND_COLOR)
315 }
316
317 pub fn set_button_background_color(color: [f32; 4]) {
318 style_write(&BUTTON_BACKGROUND_COLOR, color);
319 }
320
321 pub fn button_hover_color() -> [f32; 4] {
322 let base = button_background_color();
323 let mut oklab = linear_srgb_to_oklab([base[0], base[1], base[2]]);
324 oklab[0] = (oklab[0] + 0.05).min(1.0); // increase lightness slightly
325 let rgb = oklab_to_linear_srgb(oklab);
326 [
327 rgb[0].clamp(0.0, 1.0),
328 rgb[1].clamp(0.0, 1.0),
329 rgb[2].clamp(0.0, 1.0),
330 (base[3] + 0.20).min(1.0),
331 ]
332 }
333
334 pub fn button_press_color() -> [f32; 4] {
335 let base = button_background_color();
336 let mut oklab = linear_srgb_to_oklab([base[0], base[1], base[2]]);
337 oklab[0] = (oklab[0] - 0.07).max(0.0); // decrease lightness
338 let rgb = oklab_to_linear_srgb(oklab);
339 [
340 rgb[0].clamp(0.0, 1.0),
341 rgb[1].clamp(0.0, 1.0),
342 rgb[2].clamp(0.0, 1.0),
343 (base[3] + 0.40).min(1.0),
344 ]
345 }
346
347 pub fn node_color() -> [f32; 4] {
348 load_colors_once();
349 style_read(&NODE_COLOR)
350 }
351
352 pub fn set_node_color(color: [f32; 4]) {
353 style_write(&NODE_COLOR, color);
354 }
355
356 pub fn node_selected_color() -> [f32; 4] {
357 load_colors_once();
358 style_read(&NODE_SELECTED_COLOR)
359 }
360
361 pub fn set_node_selected_color(color: [f32; 4]) {
362 style_write(&NODE_SELECTED_COLOR, color);
363 }
364
365 pub fn node_drag_color() -> [f32; 4] {
366 load_colors_once();
367 style_read(&NODE_DRAG_COLOR)
368 }
369
370 pub fn set_node_drag_color(color: [f32; 4]) {
371 style_write(&NODE_DRAG_COLOR, color);
372 }
373
374 pub fn textbox_placeholder_text_color() -> [u8; 3] {
375 load_colors_once();
376 style_read(&TEXTBOX_PLACEHOLDER_TEXT_COLOR)
377 }
378
379 pub fn set_textbox_placeholder_text_color(color: [u8; 3]) {
380 style_write(&TEXTBOX_PLACEHOLDER_TEXT_COLOR, color);
381 }
382
383 pub fn textbox_background_color() -> [f32; 4] {
384 load_colors_once();
385 style_read(&TEXTBOX_BACKGROUND_COLOR)
386 }
387
388 pub fn set_textbox_background_color(color: [f32; 4]) {
389 style_write(&TEXTBOX_BACKGROUND_COLOR, color);
390 }
391
392 pub fn textbox_background_edit_color() -> [f32; 4] {
393 load_colors_once();
394 style_read(&TEXTBOX_BACKGROUND_EDIT_COLOR)
395 }
396
397 pub fn set_textbox_background_edit_color(color: [f32; 4]) {
398 style_write(&TEXTBOX_BACKGROUND_EDIT_COLOR, color);
399 }
400
401 pub fn graph_wire_color() -> [f32; 4] {
402 load_colors_once();
403 style_read(&GRAPH_WIRE_COLOR)
404 }
405
406 pub fn set_graph_wire_color(color: [f32; 4]) {
407 style_write(&GRAPH_WIRE_COLOR, color);
408 }
409
410 pub fn graph_wire_highlight_color() -> [f32; 4] {
411 load_colors_once();
412 style_read(&GRAPH_WIRE_HIGHLIGHT_COLOR)
413 }
414
415 pub fn set_graph_wire_highlight_color(color: [f32; 4]) {
416 style_write(&GRAPH_WIRE_HIGHLIGHT_COLOR, color);
417 }
418
419 pub fn graph_connector_color() -> [f32; 4] {
420 load_colors_once();
421 style_read(&GRAPH_CONNECTOR_COLOR)
422 }
423
424 pub fn set_graph_connector_color(color: [f32; 4]) {
425 style_write(&GRAPH_CONNECTOR_COLOR, color);
426 }
427
428 pub fn graph_connector_highlight_color() -> [f32; 4] {
429 load_colors_once();
430 style_read(&GRAPH_CONNECTOR_HIGHLIGHT_COLOR)
431 }
432
433 pub fn set_graph_connector_highlight_color(color: [f32; 4]) {
434 style_write(&GRAPH_CONNECTOR_HIGHLIGHT_COLOR, color);
435 }
436
437 pub fn graph_node_color() -> [f32; 4] {
438 load_colors_once();
439 style_read(&GRAPH_NODE_COLOR)
440 }
441
442 pub fn set_graph_node_color(color: [f32; 4]) {
443 style_write(&GRAPH_NODE_COLOR, color);
444 }
445
446 pub fn graph_node_selected_color() -> [f32; 4] {
447 load_colors_once();
448 style_read(&GRAPH_NODE_SELECTED_COLOR)
449 }
450
451 pub fn set_graph_node_selected_color(color: [f32; 4]) {
452 style_write(&GRAPH_NODE_SELECTED_COLOR, color);
453 }
454
455 pub fn graph_node_drag_color() -> [f32; 4] {
456 load_colors_once();
457 style_read(&GRAPH_NODE_DRAG_COLOR)
458 }
459
460 pub fn set_graph_node_drag_color(color: [f32; 4]) {
461 style_write(&GRAPH_NODE_DRAG_COLOR, color);
462 }
463
464 /// The colour of a graph's grid lines (`style.surface.graph.grid_color`).
465 pub fn graph_grid_color() -> [f32; 3] {
466 load_colors_once();
467 style_read(&GRAPH_GRID_COLOR)
468 }
469
470 pub fn set_graph_grid_color(color: [f32; 3]) {
471 style_write(&GRAPH_GRID_COLOR, color);
472 }
473
474 pub fn graph_opacity() -> f32 {
475 load_colors_once();
476 style_read(&GRAPH_OPACITY)
477 }
478
479 pub fn graph_node_opacity() -> f32 {
480 load_colors_once();
481 style_read(&GRAPH_NODE_OPACITY)
482 }
483
484 pub fn set_graph_node_opacity(opacity: f32) {
485 style_write(&GRAPH_NODE_OPACITY, opacity);
486 }
487
488 pub fn set_graph_opacity(opacity: f32) {
489 style_write(&GRAPH_OPACITY, opacity);
490 }
491
492 pub fn page_low_color() -> [f32; 4] {
493 load_colors_once();
494 let mut color = *PAGE_LOW_COLOR.read().unwrap();
495 if let Some(opacity) = read_root_plate_opacity_if_configured() {
496 color[3] = opacity;
497 }
498 color
499 }
500
501 pub fn set_page_low_color(color: [f32; 4]) {
502 style_write(&PAGE_LOW_COLOR, color);
503 }
504
505 pub fn page_color() -> [f32; 4] {
506 load_colors_once();
507 style_read(&PAGE_COLOR)
508 }
509
510 pub fn set_page_color(color: [f32; 4]) {
511 style_write(&PAGE_COLOR, color);
512 }
513
514 pub fn layer_color() -> [f32; 4] {
515 load_colors_once();
516 style_read(&LAYER_COLOR)
517 }
518
519 pub fn set_layer_color(color: [f32; 4]) {
520 style_write(&LAYER_COLOR, color);
521 }
522
523
524 pub fn color_borders_color() -> [f32; 4] {
525 load_colors_once();
526 style_read(&COLOR_BORDERS_COLOR)
527 }
528
529 pub fn set_color_borders_color(color: [f32; 4]) {
530 style_write(&COLOR_BORDERS_COLOR, color);
531 }
532
533 pub const SLIDER_THUMB: [f32; 4] = [0.60, 0.60, 0.65, 1.0];
534 pub const SLIDER_THUMB_DRAG: [f32; 4] = [0.80, 0.80, 0.85, 1.0];
535 pub const PROGRESS_BG: [f32; 4] = [0.18, 0.18, 0.22, 1.0];
536 pub const PROGRESS_FILL: [f32; 4] = [0.20, 0.50, 0.75, 1.0];
537
538 pub const HIGHLIGHT_PRIMARY: [f32; 4] = [1.0, 1.0, 1.0, 0.12];
539 pub const HIGHLIGHT_SECONDARY: [f32; 4] = [1.0, 1.0, 1.0, 0.06];
540
541 pub const SPINBOX_BG: [f32; 4] = [0.18, 0.18, 0.22, 1.0];
542 pub const SPINBOX_BUTTON: [f32; 4] = [0.25, 0.25, 0.32, 1.0];
543 pub const SPINBOX_BUTTON_HOVER: [f32; 4] = [0.35, 0.35, 0.42, 1.0];
544 pub const SPINBOX_DISPLAY: [f32; 4] = [0.12, 0.12, 0.16, 1.0];
545
546 pub const CANVAS_BG: [f32; 4] = [0.05, 0.05, 0.10, 1.0];
547 pub const VIEWPORT_BG: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
548 pub const PARAM_BG: [f32; 4] = [0.10, 0.10, 0.14, 0.25];
549
550 /// The params pane's plate tint (linear rgba) — [`PARAM_BG`] made live:
551 /// `style.surface.param.color` in config overrides it, and apps can retint at
552 /// runtime (the designer's Style section "Plate Color"). Alpha doubles as the
553 /// frost strength under plate blur.
554 static PARAM_BG_COLOR: crate::style::StyleCell<[f32; 4]> = crate::style::StyleCell::new(|s| &s.color.PARAM_BG_COLOR, |s| &mut s.color.PARAM_BG_COLOR);
555 /// Whether the pane tint came in as `style.surface.plate.pane.color` — the
556 /// spelling whose alpha IS the tint strength — rather than the legacy
557 /// `style.surface.param.color`, which the top-level `plate_opacity` line
558 /// still multiplies (`Material::pane_legacy`). Two spellings, one tint.
559 static PANE_COLOR_WHOLE: crate::style::StyleCell<bool> = crate::style::StyleCell::new(|s| &s.color.PANE_COLOR_WHOLE, |s| &mut s.color.PANE_COLOR_WHOLE);
560
561 pub fn param_bg_color() -> [f32; 4] {
562 load_colors_once();
563 style_read(&PARAM_BG_COLOR)
564 }
565
566 /// Whether the pane tint was spelled `style.surface.plate.pane.color`, in
567 /// which case its alpha is the whole tint strength and `plate_opacity` does
568 /// not multiply it. See `PANE_COLOR_WHOLE`.
569 pub fn pane_color_is_whole() -> bool {
570 load_colors_once();
571 style_read(&PANE_COLOR_WHOLE)
572 }
573
574 pub fn set_param_bg_color(color: [f32; 4]) {
575 style_write(&PARAM_BG_COLOR, color);
576 }
577
578 /// The params plate's final fill as the renderer consumes it: the pane rung's
579 /// material ([`crate::scene::Material::pane`] — the tint at the global plate
580 /// opacity, frosted under plate blur) encoded for a nested plate. The single
581 /// source both `ParametersBg`'s own plate and any surface that wants to match
582 /// it (the designer's node bodies) draw from, so they track a live retint /
583 /// opacity / blur toggle together.
584 pub fn param_plate_fill() -> [f32; 4] {
585 use crate::scene::material::{Material, PlateRole};
586 Material::pane().fill(PlateRole::Nested)
587 }
588
589 pub const PANEL_MENU_BG: [f32; 4] = [0.08, 0.08, 0.12, 1.0];
590 pub const PANEL_MENU_HOVER: [f32; 4] = [0.18, 0.18, 0.25, 1.0];
591 pub const PANEL_MENU_FOCUSED: [f32; 4] = [0.08, 0.16, 0.28, 1.0];
592
593 pub const SPLITTER_IDLE: [f32; 4] = [0.20, 0.20, 0.27, 1.0];
594 pub const SPLITTER_HOVER: [f32; 4] = [0.40, 0.40, 0.50, 1.0];
595 pub const SPLITTER_DRAG: [f32; 4] = [0.50, 0.50, 0.60, 1.0];
596
597 pub const TEXT_FG: [f32; 4] = [0.80, 0.80, 0.85, 1.0];
598 pub const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
599
600 /// A canvas well's floor: the plate darkened, not a fill of its own, so every
601 /// opening you look into — Trackpad, Slider2D, the bevel and ramp previews —
602 /// is cut from the one material ([`crate::scene::paint::PaintCtx::well_floor`]).
603 pub const WELL_FLOOR: [f32; 4] = [0.0, 0.0, 0.0, 0.18];
604 /// [`WELL_FLOOR`] risen toward the plate: a clickable canvas's hover cue.
605 pub const WELL_FLOOR_LIFTED: [f32; 4] = [0.0, 0.0, 0.0, 0.10];
606 /// The hairline a well is framed with when relief is off, idle; see
607 /// [`well_frame_color`].
608 pub const WELL_FRAME: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
609 /// [`WELL_FRAME`] under the pointer.
610 pub const WELL_FRAME_HOVER: [f32; 4] = [0.25, 0.25, 0.35, 1.0];
611
612 /// The frame a flat well is drawn in — the one hairline every well (text,
613 /// keybind, colour and font fields, the canvases) wears when relief is off:
614 /// neutral greys idle and hovered, the highlight accent while the well is
615 /// active (editing, recording, pressed) — the colour the relief wells light
616 /// their rims with, so the two styles share one focus cue. A flat well has no
617 /// floor of its own any more than a relief one: the plate is the floor.
618 pub fn well_frame_color(hovered: bool, active: bool) -> [f32; 4] {
619 if active {
620 let c = highlight_primary_color();
621 [c[0], c[1], c[2], 1.0]
622 } else if hovered {
623 WELL_FRAME_HOVER
624 } else {
625 WELL_FRAME
626 }
627 }
628 pub const TEXT_HEADER: [f32; 4] = [0.90, 0.90, 0.95, 1.0];
629 pub const TEXT_ACCENT: [f32; 4] = [0.56, 0.83, 0.56, 1.0];
630
631 pub fn read_opacity_if_configured() -> Option<f32> {
632 load_colors_once();
633 style_read(&OPACITY)
634 }
635
636 /// Tint strength for frosted menus/popovers (`/style/surface/menu/opacity`,
637 /// default 0.8): the |alpha| of the blur-behind sentinel their plates carry.
638 pub fn menu_opacity() -> f32 {
639 load_colors_once();
640 style_read(&MENU_OPACITY)
641 }
642
643 /// Backdrop compression of frosted menus/popovers
644 /// (`/style/surface/menu/compression`, default 0.6) — see `MENU_COMPRESSION`.
645 pub fn menu_compression() -> f32 {
646 load_colors_once();
647 style_read(&MENU_COMPRESSION)
648 }
649
650 pub fn set_menu_compression(c: f32) {
651 style_write(&MENU_COMPRESSION, c.clamp(0.0, 1.0));
652 }
653
654 /// The colour a menu's face is tinted with: `style.surface.menu.color` when
655 /// configured, else the root plate colour (`page_low_color`) — the face
656 /// every menu wore before the key existed, so an unconfigured menu looks as
657 /// it always did. Its alpha is not the tint strength; `menu_opacity` is
658 /// (`Material::popover` reads both).
659 pub fn menu_color() -> [f32; 4] {
660 load_colors_once();
661 style_read(&MENU_COLOR).unwrap_or_else(page_low_color)
662 }
663
664 pub fn set_menu_color(c: Option<[f32; 4]>) {
665 style_write(&MENU_COLOR, c);
666 }
667
668 pub fn read_root_plate_opacity_if_configured() -> Option<f32> {
669 load_colors_once();
670 style_read(&ROOT_PLATE_OPACITY)
671 }
672
673 // The toggle's own palette (enabled/disabled/background) is RETIRED: a toggle
674 // inherits the plate it sits on and marks state with light and relief, so
675 // there is nothing left to tint. The `TOGGLE_*` consts above survive for the
676 // graph's node geometry switch, which is a different control.
677
678 pub fn list_bg_color() -> [f32; 4] {
679 load_colors_once();
680 style_read(&LIST_BG_COLOR)
681 }
682
683 pub fn set_list_bg_color(color: [f32; 4]) {
684 if let Ok(mut lock) = LIST_BG_COLOR.write() {
685 *lock = [color[0], color[1], color[2], 0.3];
686 }
687 }
688
689 pub fn list_entry_bg_color() -> [f32; 4] {
690 load_colors_once();
691 style_read(&LIST_ENTRY_BG_COLOR)
692 }
693
694 pub fn set_list_entry_bg_color(color: [f32; 4]) {
695 style_write(&LIST_ENTRY_BG_COLOR, color);
696 }
697
698 pub fn list_entry_highlight_color() -> [f32; 4] {
699 load_colors_once();
700 style_read(&LIST_ENTRY_HIGHLIGHT_COLOR)
701 }
702
703 pub fn set_list_entry_highlight_color(color: [f32; 4]) {
704 style_write(&LIST_ENTRY_HIGHLIGHT_COLOR, color);
705 }
706
707 pub fn list_font_color() -> [f32; 4] {
708 load_colors_once();
709 style_read(&LIST_FONT_COLOR)
710 }
711
712 pub fn set_list_font_color(color: [f32; 4]) {
713 style_write(&LIST_FONT_COLOR, color);
714 }
715
716 pub fn breadcrumb_bg_color() -> [f32; 4] {
717 load_colors_once();
718 style_read(&BREADCRUMB_BG_COLOR)
719 }
720
721 pub fn set_breadcrumb_bg_color(color: [f32; 4]) {
722 if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
723 *lock = [color[0], color[1], color[2], 1.0];
724 }
725 }
726
727 pub fn popover_bg_color() -> [f32; 4] {
728 load_colors_once();
729 style_read(&POPOVER_BG_COLOR)
730 }
731
732 pub fn set_popover_bg_color(color: [f32; 4]) {
733 if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
734 *lock = [color[0], color[1], color[2], 1.0];
735 }
736 }
737
738 /// Corner radius of the root plate (`style.surface.plate.root.corner_radius`).
739 /// The window silhouette value — the compositor clips windows from the SHARED
740 /// copy of this.
741 pub fn root_plate_corner_radius() -> f32 {
742 load_colors_once();
743 style_read(&ROOT_PLATE_CORNER_RADIUS)
744 }
745
746 pub fn ramp_background_color() -> [f32; 4] {
747 load_colors_once();
748 style_read(&RAMP_BACKGROUND_COLOR)
749 }
750
751 pub fn ramp_border_color() -> [f32; 4] {
752 load_colors_once();
753 style_read(&RAMP_BORDER_COLOR)
754 }
755
756 pub fn control_panel_color() -> [f32; 4] {
757 load_colors_once();
758 style_read(&CONTROL_PANEL_COLOR)
759 }
760
761 pub fn set_control_panel_color(color: [f32; 4]) {
762 style_write(&CONTROL_PANEL_COLOR, color);
763 }
764
765 pub fn control_panel_border_color() -> [f32; 4] {
766 load_colors_once();
767 style_read(&CONTROL_PANEL_BORDER_COLOR)
768 }
769
770 pub fn set_control_panel_border_color(color: [f32; 4]) {
771 style_write(&CONTROL_PANEL_BORDER_COLOR, color);
772 }
773
774 pub fn progress_bg() -> [f32; 4] {
775 load_colors_once();
776 style_read(&PROGRESS_BG_COLOR)
777 }
778
779 pub fn set_progress_bg(color: [f32; 4]) {
780 style_write(&PROGRESS_BG_COLOR, color);
781 }
782
783 pub fn progress_fill() -> [f32; 4] {
784 load_colors_once();
785 style_read(&PROGRESS_FILL_COLOR)
786 }
787
788 pub fn set_progress_fill(color: [f32; 4]) {
789 style_write(&PROGRESS_FILL_COLOR, color);
790 }
791
792 pub fn button_border_color() -> Option<[f32; 4]> {
793 load_colors_once();
794 style_read(&BUTTON_BORDER_COLOR)
795 }
796
797 pub fn set_button_border_color(color: [f32; 4]) {
798 if let Ok(mut lock) = BUTTON_BORDER_COLOR.write() {
799 *lock = Some(color);
800 }
801 }
802
803 pub fn dropdown_border_color() -> [f32; 4] {
804 load_colors_once();
805 style_read(&DROPDOWN_BORDER_COLOR)
806 }
807
808 pub fn set_dropdown_border_color(color: [f32; 4]) {
809 style_write(&DROPDOWN_BORDER_COLOR, color);
810 }
811
812 pub fn dropdown_text_color() -> [f32; 4] {
813 load_colors_once();
814 style_read(&DROPDOWN_TEXT_COLOR)
815 }
816
817 pub fn set_dropdown_text_color(color: [f32; 4]) {
818 style_write(&DROPDOWN_TEXT_COLOR, color);
819 }
820
821
822 pub fn slider_thumb() -> [f32; 4] {
823 load_colors_once();
824 style_read(&SLIDER_THUMB_COLOR)
825 }
826
827 pub fn set_slider_thumb(color: [f32; 4]) {
828 style_write(&SLIDER_THUMB_COLOR, color);
829 }
830
831 pub fn slider_thumb_drag() -> [f32; 4] {
832 load_colors_once();
833 style_read(&SLIDER_THUMB_DRAG_COLOR)
834 }
835
836 pub fn set_slider_thumb_drag(color: [f32; 4]) {
837 style_write(&SLIDER_THUMB_DRAG_COLOR, color);
838 }
839
840 pub fn rangeslider_thumb() -> [f32; 4] {
841 load_colors_once();
842 style_read(&RANGE_SLIDER_THUMB_COLOR)
843 }
844
845 pub fn set_rangeslider_thumb(color: [f32; 4]) {
846 style_write(&RANGE_SLIDER_THUMB_COLOR, color);
847 }
848
849 pub fn rangeslider_thumb_drag() -> [f32; 4] {
850 load_colors_once();
851 style_read(&RANGE_SLIDER_THUMB_DRAG_COLOR)
852 }
853
854 pub fn set_rangeslider_thumb_drag(color: [f32; 4]) {
855 style_write(&RANGE_SLIDER_THUMB_DRAG_COLOR, color);
856 }
857
858 pub fn spinbox_display() -> [f32; 4] {
859 load_colors_once();
860 style_read(&SPINBOX_DISPLAY_COLOR)
861 }
862
863 pub fn set_spinbox_display(color: [f32; 4]) {
864 style_write(&SPINBOX_DISPLAY_COLOR, color);
865 }
866
867 pub fn spinbox_button() -> [f32; 4] {
868 load_colors_once();
869 style_read(&SPINBOX_BUTTON_COLOR)
870 }
871
872 pub fn set_spinbox_button(color: [f32; 4]) {
873 style_write(&SPINBOX_BUTTON_COLOR, color);
874 }
875
876 pub fn spinbox_button_hover() -> [f32; 4] {
877 load_colors_once();
878 style_read(&SPINBOX_BUTTON_HOVER_COLOR)
879 }
880
881 pub fn set_spinbox_button_hover(color: [f32; 4]) {
882 style_write(&SPINBOX_BUTTON_HOVER_COLOR, color);
883 }
884
885 pub fn spinbox_text_color() -> [f32; 4] {
886 load_colors_once();
887 style_read(&SPINBOX_TEXT_COLOR)
888 }
889
890 pub fn set_spinbox_text_color(color: [f32; 4]) {
891 style_write(&SPINBOX_TEXT_COLOR, color);
892 }
893
894 pub fn set_root_plate_corner_radius(radius: f32) {
895 style_write(&ROOT_PLATE_CORNER_RADIUS, radius);
896 }
897
898 #[derive(Debug, Clone, Copy)]
899 pub struct Theme {
900 pub surface_bg: [f32; 4],
901 pub surface_border: [f32; 4],
902 pub primary_accent: [f32; 4],
903 pub press_overlay: [f32; 4],
904 pub hover_overlay: [f32; 4],
905 }
906
907 pub fn active_theme() -> Theme {
908 Theme {
909 surface_bg: popover_bg_color(),
910 surface_border: [0.25, 0.25, 0.35, 0.8],
911 primary_accent: [0.20, 0.50, 0.75, 1.0],
912 press_overlay: [1.0, 1.0, 1.0, 0.15],
913 hover_overlay: [1.0, 1.0, 1.0, 0.08],
914 }
915 }
916
917 pub fn active_window_mode() -> String {
918 let app_id = crate::scale::app_id();
919 if app_id.is_empty() {
920 return "floating".to_string();
921 }
922
923 let config_path = crate::config::get_config_path();
924 let content = match std::fs::read_to_string(&config_path) {
925 Ok(c) => c,
926 Err(_) => return "floating".to_string(),
927 };
928 let val = crate::config::parse_kdl_to_json(&content);
929
930 if let Some(mode_rules) = val.get("mode_rule").and_then(|r| r.as_array()) {
931 for rule in mode_rules {
932 if rule.get("app_id").and_then(|id| id.as_str()) == Some(&app_id) {
933 if let Some(mode) = rule.get("mode").and_then(|m| m.as_str()) {
934 return mode.to_string();
935 }
936 }
937 }
938 }
939
940 let mut default_tile_mode = "cascade".to_string();
941 if let Some(tag_layouts) = val.get("tag_layout").and_then(|l| l.as_array()) {
942 for tl in tag_layouts {
943 if tl.get("tag").and_then(|t| t.as_u64()) == Some(1) {
944 if let Some(m) = tl.get("mode").and_then(|m| m.as_str()) {
945 default_tile_mode = m.to_string();
946 }
947 }
948 }
949 }
950
951 if crate::scale::is_fullscreen() {
952 return "fullscreen".to_string();
953 }
954 if crate::scale::is_maximized() {
955 return default_tile_mode;
956 }
957
958 "floating".to_string()
959 }
960
961 /// Opacity of the root plate's fill (the alpha of the root-plate color).
962 pub fn root_plate_opacity() -> f32 {
963 page_low_color()[3]
964 }
965
966 pub fn tree_background_color() -> [f32; 4] {
967 style_read(&TREE_BACKGROUND_COLOR)
968 }
969 pub fn tree_border_color() -> [f32; 4] {
970 style_read(&TREE_BORDER_COLOR)
971 }
972 pub fn tree_border_hover_color() -> [f32; 4] {
973 style_read(&TREE_BORDER_HOVER_COLOR)
974 }
975 pub fn tree_border_focus_color() -> [f32; 4] {
976 style_read(&TREE_BORDER_FOCUS_COLOR)
977 }
978
979 pub fn tree_section_bg_color() -> [f32; 4] {
980 style_read(&TREE_SECTION_BG_COLOR)
981 }
982 pub fn tree_section_bg_hover_color() -> [f32; 4] {
983 style_read(&TREE_SECTION_BG_HOVER_COLOR)
984 }
985
986 pub fn tree_leaf_bg_even_color() -> [f32; 4] {
987 style_read(&TREE_LEAF_BG_EVEN_COLOR)
988 }
989 pub fn tree_leaf_bg_odd_color() -> [f32; 4] {
990 style_read(&TREE_LEAF_BG_ODD_COLOR)
991 }
992 pub fn tree_leaf_bg_hover_color() -> [f32; 4] {
993 style_read(&TREE_LEAF_BG_HOVER_COLOR)
994 }
995 pub fn tree_leaf_bg_selected_color() -> [f32; 4] {
996 style_read(&TREE_LEAF_BG_SELECTED_COLOR)
997 }
998
999 pub fn tree_section_text_color() -> [f32; 4] {
1000 style_read(&TREE_SECTION_TEXT_COLOR)
1001 }
1002 pub fn tree_leaf_text_color() -> [f32; 4] {
1003 style_read(&TREE_LEAF_TEXT_COLOR)
1004 }
1005 pub fn tree_leaf_text_selected_color() -> [f32; 4] {
1006 style_read(&TREE_LEAF_TEXT_SELECTED_COLOR)
1007 }
1008
1009 pub fn tree_type_text_color() -> [f32; 4] {
1010 style_read(&TREE_TYPE_TEXT_COLOR)
1011 }
1012 pub fn tree_value_text_color() -> [f32; 4] {
1013 style_read(&TREE_VALUE_TEXT_COLOR)
1014 }
1015 pub fn tree_separator_color() -> [f32; 4] {
1016 style_read(&TREE_SEPARATOR_COLOR)
1017 }
1018
1019 pub fn set_tree_background_color(c: [f32; 4]) {
1020 style_write(&TREE_BACKGROUND_COLOR, c);
1021 }
1022 pub fn set_tree_border_color(c: [f32; 4]) {
1023 style_write(&TREE_BORDER_COLOR, c);
1024 }
1025 pub fn set_tree_border_hover_color(c: [f32; 4]) {
1026 style_write(&TREE_BORDER_HOVER_COLOR, c);
1027 }
1028 pub fn set_tree_border_focus_color(c: [f32; 4]) {
1029 style_write(&TREE_BORDER_FOCUS_COLOR, c);
1030 }
1031
1032 pub fn set_tree_section_bg_color(c: [f32; 4]) {
1033 style_write(&TREE_SECTION_BG_COLOR, c);
1034 }
1035 pub fn set_tree_section_bg_hover_color(c: [f32; 4]) {
1036 style_write(&TREE_SECTION_BG_HOVER_COLOR, c);
1037 }
1038
1039 pub fn set_tree_leaf_bg_even_color(c: [f32; 4]) {
1040 style_write(&TREE_LEAF_BG_EVEN_COLOR, c);
1041 }
1042 pub fn set_tree_leaf_bg_odd_color(c: [f32; 4]) {
1043 style_write(&TREE_LEAF_BG_ODD_COLOR, c);
1044 }
1045 pub fn set_tree_leaf_bg_hover_color(c: [f32; 4]) {
1046 style_write(&TREE_LEAF_BG_HOVER_COLOR, c);
1047 }
1048 pub fn set_tree_leaf_bg_selected_color(c: [f32; 4]) {
1049 style_write(&TREE_LEAF_BG_SELECTED_COLOR, c);
1050 }
1051
1052 pub fn set_tree_section_text_color(c: [f32; 4]) {
1053 style_write(&TREE_SECTION_TEXT_COLOR, c);
1054 }
1055 pub fn set_tree_leaf_text_color(c: [f32; 4]) {
1056 style_write(&TREE_LEAF_TEXT_COLOR, c);
1057 }
1058 pub fn set_tree_leaf_text_selected_color(c: [f32; 4]) {
1059 style_write(&TREE_LEAF_TEXT_SELECTED_COLOR, c);
1060 }
1061
1062 pub fn set_tree_type_text_color(c: [f32; 4]) {
1063 style_write(&TREE_TYPE_TEXT_COLOR, c);
1064 }
1065 pub fn set_tree_value_text_color(c: [f32; 4]) {
1066 style_write(&TREE_VALUE_TEXT_COLOR, c);
1067 }
1068 pub fn set_tree_separator_color(c: [f32; 4]) {
1069 style_write(&TREE_SEPARATOR_COLOR, c);
1070 }
1071
1072 pub fn scrollbar_track_color() -> [f32; 4] {
1073 style_read(&SCROLLBAR_TRACK_COLOR)
1074 }
1075 pub fn set_scrollbar_track_color(c: [f32; 4]) {
1076 style_write(&SCROLLBAR_TRACK_COLOR, c);
1077 }
1078 pub fn scrollbar_thumb_color() -> [f32; 4] {
1079 style_read(&SCROLLBAR_THUMB_COLOR)
1080 }
1081 pub fn set_scrollbar_thumb_color(c: [f32; 4]) {
1082 style_write(&SCROLLBAR_THUMB_COLOR, c);
1083 }
1084
1085 pub fn root_plate_menubar_color() -> [f32; 4] {
1086 load_colors_once();
1087 style_read(&ROOT_PLATE_MENUBAR_COLOR)
1088 }
1089
1090 pub fn set_root_plate_menubar_color(c: [f32; 4]) {
1091 style_write(&ROOT_PLATE_MENUBAR_COLOR, c);
1092 }
1093
1094 pub fn root_plate_menubar_text_color() -> [f32; 4] {
1095 load_colors_once();
1096 style_read(&ROOT_PLATE_MENUBAR_TEXT_COLOR)
1097 }
1098
1099 pub fn set_root_plate_menubar_text_color(c: [f32; 4]) {
1100 style_write(&ROOT_PLATE_MENUBAR_TEXT_COLOR, c);
1101 }
1102
1103 pub fn root_plate_menubar_blur() -> bool {
1104 load_colors_once();
1105 style_read(&ROOT_PLATE_MENUBAR_BLUR)
1106 }
1107
1108 pub fn set_root_plate_menubar_blur(b: bool) {
1109 style_write(&ROOT_PLATE_MENUBAR_BLUR, b);
1110 }
1111
1112 pub fn root_plate_statusbar_color() -> [f32; 4] {
1113 load_colors_once();
1114 style_read(&ROOT_PLATE_STATUSBAR_COLOR)
1115 }
1116
1117 pub fn set_root_plate_statusbar_color(c: [f32; 4]) {
1118 style_write(&ROOT_PLATE_STATUSBAR_COLOR, c);
1119 }
1120
1121 pub fn root_plate_statusbar_text_color() -> [f32; 4] {
1122 load_colors_once();
1123 style_read(&ROOT_PLATE_STATUSBAR_TEXT_COLOR)
1124 }
1125
1126 pub fn set_root_plate_statusbar_text_color(c: [f32; 4]) {
1127 style_write(&ROOT_PLATE_STATUSBAR_TEXT_COLOR, c);
1128 }
1129
1130 pub fn root_plate_statusbar_blur() -> bool {
1131 load_colors_once();
1132 style_read(&ROOT_PLATE_STATUSBAR_BLUR)
1133 }
1134
1135 pub fn set_root_plate_statusbar_blur(b: bool) {
1136 style_write(&ROOT_PLATE_STATUSBAR_BLUR, b);
1137 }
1138
1139 static PLATE_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.PLATE_COLOR, |s| &mut s.color.PLATE_COLOR);
1140 static PLATE_BORDER_COLOR: crate::style::StyleCell<Option<[f32; 4]>> = crate::style::StyleCell::new(|s| &s.color.PLATE_BORDER_COLOR, |s| &mut s.color.PLATE_BORDER_COLOR);
1141 static PLATE_BORDER_THICKNESS: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.PLATE_BORDER_THICKNESS, |s| &mut s.color.PLATE_BORDER_THICKNESS);
1142 pub fn plate_color() -> Option<[f32; 4]> {
1143 load_colors_once();
1144 style_read(&PLATE_COLOR)
1145 }
1146
1147 pub fn set_plate_color(c: Option<[f32; 4]>) {
1148 style_write(&PLATE_COLOR, c);
1149 }
1150
1151 pub fn plate_border_color() -> Option<[f32; 4]> {
1152 load_colors_once();
1153 style_read(&PLATE_BORDER_COLOR)
1154 }
1155
1156 pub fn set_plate_border_color(c: Option<[f32; 4]>) {
1157 style_write(&PLATE_BORDER_COLOR, c);
1158 }
1159
1160 pub fn plate_border_thickness() -> f32 {
1161 load_colors_once();
1162 style_read(&PLATE_BORDER_THICKNESS)
1163 }
1164
1165 pub fn set_plate_border_thickness(t: f32) {
1166 style_write(&PLATE_BORDER_THICKNESS, t);
1167 }
1168
1169 /// Roll width of a beveled pane plate (the control_relief replacement for the
1170 /// flat border line), in logical px.
1171 ///
1172 /// **This is `style.surface.relief.width`** — the one roll width, the same
1173 /// number the root plate rolls over, every control wall runs, and
1174 /// `relief.edge.height` is a rise against. Until 2026-09-28 it was a second
1175 /// width of its own (`style.surface.plate.bevel_width`, default 6 against the
1176 /// relief's 9.3), so a `PlateSpec` pane plate and an `append_widget_plate`
1177 /// pane plate rolled over different widths in one window, the designer's
1178 /// panes could not be made to match its own window lip by editing one key,
1179 /// and the edge height was expressed against a width the panes did not use.
1180 /// The old key was an explicit override for the rest of that day and is
1181 /// RETIRED: a config still carrying it is reported by path
1182 /// (`retired_surface_keys`) and the key is not read. The getter survives
1183 /// as the name the plate painters call, so a caller need not know which
1184 /// registry key a roll is.
1185 pub fn plate_bevel_width() -> f32 {
1186 crate::layout::bevel_width()
1187 }
1188
1189 /// How hard a frosted plate pulls its backdrop's LUMINANCE toward its own key
1190 /// before tinting: 0 = the backdrop passes through untouched, 1 = flat.
1191 ///
1192 /// This is the plate's legibility control, and it is NOT opacity. Blur
1193 /// destroys a backdrop's spatial detail but preserves its mean luminance, and
1194 /// text contrast is a mean-luminance property — so a frosted plate over
1195 /// something bright washes out however hard it is blurred, which is the whole
1196 /// of the "liquid glass" legibility problem. Compression remaps the backdrop's
1197 /// luminance toward the plate's own, symmetrically: a bright backdrop comes
1198 /// down and a DARK one comes up, so the plate stops swinging through the ink's
1199 /// luminance while its hue, chroma and movement still read.
1200 ///
1201 /// Default 0.0 — the behavior every existing config already has.
1202 static PLATE_BACKDROP_COMPRESSION: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.PLATE_BACKDROP_COMPRESSION, |s| &mut s.color.PLATE_BACKDROP_COMPRESSION);
1203
1204 pub fn plate_backdrop_compression() -> f32 {
1205 load_colors_once();
1206 style_read(&PLATE_BACKDROP_COMPRESSION)
1207 }
1208
1209 pub fn set_plate_backdrop_compression(c: f32) {
1210 style_write(&PLATE_BACKDROP_COMPRESSION, c.clamp(0.0, 1.0));
1211 }
1212
1213 /// How far a frosted plate's roll bends what it samples, and how much clearer
1214 /// its rim is than its frosted body: 0 = a flat window, 1 = full.
1215 ///
1216 /// This buys no legibility and is not meant to — see
1217 /// [`plate_backdrop_compression`] for that. What it buys is the plate reading
1218 /// as an OBJECT: a curved edge displaces the view through it, so the
1219 /// silhouette stops being where the haze ends and becomes where a slab with a
1220 /// thickness begins. The two are complementary, and on a dark desktop
1221 /// especially: compression flattens the body toward the tint, which leaves the
1222 /// rim as the only place the material can still say what it is.
1223 ///
1224 /// Default 0.0 — no existing config changes appearance.
1225 static PLATE_REFRACTION: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.PLATE_REFRACTION, |s| &mut s.color.PLATE_REFRACTION);
1226
1227 pub fn plate_refraction() -> f32 {
1228 load_colors_once();
1229 style_read(&PLATE_REFRACTION)
1230 }
1231
1232 pub fn set_plate_refraction(r: f32) {
1233 style_write(&PLATE_REFRACTION, r.clamp(0.0, 1.0));
1234 }
1235
1236 /// Whether the default plate material is frosted at all — the switch on
1237 /// `Frost::from_style`. Spelled `style.surface.plate.frost` (a block, the
1238 /// knobs inside it; see the loader). The older `style.surface.plate.blur`
1239 /// bool is retired and reported, not read.
1240 static PLATE_BLUR: crate::style::StyleCell<bool> = crate::style::StyleCell::new(|s| &s.color.PLATE_BLUR, |s| &mut s.color.PLATE_BLUR);
1241
1242 /// The finish's three fixed terms — specular strength, shininess exponent,
1243 /// curvature/AO strength — as `scene::material::Finish::from_style` reads
1244 /// them. Until 2026-09-20 these were literals in the finish constructor
1245 /// (0.4 / 24 / 0.2); the getters exist so the DE's plastic can be edited and
1246 /// so a named material (RFC material, step 4) has somewhere to land. No config
1247 /// path yet: the defaults ARE the shipped look.
1248 static FINISH_SPEC: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.FINISH_SPEC, |s| &mut s.color.FINISH_SPEC);
1249 static FINISH_SHININESS: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.FINISH_SHININESS, |s| &mut s.color.FINISH_SHININESS);
1250 static FINISH_CURVATURE: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.FINISH_CURVATURE, |s| &mut s.color.FINISH_CURVATURE);
1251 /// The finish a config without `relief spec` / `shininess` / `curvature`
1252 /// gets: the literals the shader shipped with.
1253 pub(crate) const FINISH_SPEC_DEFAULT: f32 = 0.4;
1254 pub(crate) const FINISH_SHININESS_DEFAULT: f32 = 24.0;
1255 pub(crate) const FINISH_CURVATURE_DEFAULT: f32 = 0.2;
1256
1257 /// The default material's blur radius (`style.surface.plate.frost.radius`,
1258 /// the kernel sigma in logical px) — [`crate::scene::Frost::DEFAULT_RADIUS`]
1259 /// unless config says otherwise.
1260 static PLATE_FROST_RADIUS: crate::style::StyleCell<f32> = crate::style::StyleCell::new(|s| &s.color.PLATE_FROST_RADIUS, |s| &mut s.color.PLATE_FROST_RADIUS);
1261
1262 pub fn plate_frost_radius() -> f32 {
1263 load_colors_once();
1264 style_read(&PLATE_FROST_RADIUS)
1265 }
1266 pub fn set_plate_frost_radius(r: f32) {
1267 style_write(&PLATE_FROST_RADIUS, r.max(0.0));
1268 }
1269
1270 pub fn finish_spec() -> f32 {
1271 style_read(&FINISH_SPEC)
1272 }
1273 pub fn set_finish_spec(v: f32) {
1274 style_write(&FINISH_SPEC, v.max(0.0));
1275 }
1276 pub fn finish_shininess() -> f32 {
1277 style_read(&FINISH_SHININESS)
1278 }
1279 pub fn set_finish_shininess(v: f32) {
1280 style_write(&FINISH_SHININESS, v.max(1.0));
1281 }
1282 pub fn finish_curvature() -> f32 {
1283 style_read(&FINISH_CURVATURE)
1284 }
1285 pub fn set_finish_curvature(v: f32) {
1286 style_write(&FINISH_CURVATURE, v.max(0.0));
1287 }
1288
1289 pub fn plate_blur() -> bool {
1290 load_colors_once();
1291 style_read(&PLATE_BLUR)
1292 }
1293
1294 pub fn set_plate_blur(b: bool) {
1295 style_write(&PLATE_BLUR, b);
1296 }
1297
1298 pub fn control_label_color() -> [f32; 4] {
1299 style_read(&CONTROL_LABEL_COLOR)
1300 }
1301
1302 pub fn control_label_color_u8() -> [u8; 3] {
1303 let c = control_label_color();
1304 [
1305 (linear_to_srgb(c[0]) * 255.0).round() as u8,
1306 (linear_to_srgb(c[1]) * 255.0).round() as u8,
1307 (linear_to_srgb(c[2]) * 255.0).round() as u8,
1308 ]
1309 }
1310
1311 pub fn set_control_label_color(color: [f32; 4]) {
1312 style_write(&CONTROL_LABEL_COLOR, color);
1313 }
1314
1315 pub fn control_label_hover_color() -> Option<[f32; 4]> {
1316 style_read(&CONTROL_LABEL_HOVER_COLOR)
1317 }
1318
1319 pub fn control_label_focus_color() -> Option<[f32; 4]> {
1320 style_read(&CONTROL_LABEL_FOCUS_COLOR)
1321 }
1322
1323 pub fn control_label_color_for_state(hovered: bool, focused: bool) -> [u8; 3] {
1324 let c = if focused {
1325 control_label_focus_color().unwrap_or_else(control_label_color)
1326 } else if hovered {
1327 control_label_hover_color().unwrap_or_else(control_label_color)
1328 } else {
1329 control_label_color()
1330 };
1331 [
1332 (linear_to_srgb(c[0]) * 255.0).round() as u8,
1333 (linear_to_srgb(c[1]) * 255.0).round() as u8,
1334 (linear_to_srgb(c[2]) * 255.0).round() as u8,
1335 ]
1336 }
1337
1338 pub fn control_label_color_detached() -> [f32; 4] {
1339 style_read(&CONTROL_LABEL_COLOR_DETACHED)
1340 }
1341
1342 pub fn control_label_color_detached_u8() -> [u8; 3] {
1343 let c = control_label_color_detached();
1344 [
1345 (linear_to_srgb(c[0]) * 255.0).round() as u8,
1346 (linear_to_srgb(c[1]) * 255.0).round() as u8,
1347 (linear_to_srgb(c[2]) * 255.0).round() as u8,
1348 ]
1349 }
1350
1351 pub fn set_control_label_color_detached(color: [f32; 4]) {
1352 style_write(&CONTROL_LABEL_COLOR_DETACHED, color);
1353 }
1354
1355 pub fn control_label_color_detached_for_state(hovered: bool, focused: bool) -> [u8; 3] {
1356 let c = if focused {
1357 control_label_focus_color().unwrap_or_else(control_label_color_detached)
1358 } else if hovered {
1359 control_label_hover_color().unwrap_or_else(control_label_color_detached)
1360 } else {
1361 control_label_color_detached()
1362 };
1363 [
1364 (linear_to_srgb(c[0]) * 255.0).round() as u8,
1365 (linear_to_srgb(c[1]) * 255.0).round() as u8,
1366 (linear_to_srgb(c[2]) * 255.0).round() as u8,
1367 ]
1368 }
1369
1370 #[cfg(test)]
1371 mod color_tests {
1372 use super::*;
1373
1374 #[test]
1375 fn test_parse_hex_rgba() {
1376 // 6-digit → alpha 1.0, raw sRGB
1377 assert_eq!(parse_hex_rgba("#ff0000"), Some([1.0, 0.0, 0.0, 1.0]));
1378 // 8-digit → explicit alpha
1379 assert_eq!(parse_hex_rgba("#00ff0080"), Some([0.0, 1.0, 0.0, 128.0 / 255.0]));
1380 // leading '#' optional; quotes/whitespace tolerated
1381 assert_eq!(parse_hex_rgba("\" ffffff \""), Some([1.0, 1.0, 1.0, 1.0]));
1382 assert_eq!(parse_hex_rgba("000000"), Some([0.0, 0.0, 0.0, 1.0]));
1383 // invalid
1384 assert_eq!(parse_hex_rgba("#fff"), None);
1385 assert_eq!(parse_hex_rgba("nothex"), None);
1386 assert_eq!(parse_hex_rgba(""), None);
1387 // rgb drops alpha; linear applies gamma to rgb only
1388 assert_eq!(parse_hex_rgb("#ff0000"), Some([1.0, 0.0, 0.0]));
1389 assert_eq!(parse_hex_rgba_linear("#000000ff"), Some([0.0, 0.0, 0.0, 1.0]));
1390 // byte primitive
1391 assert_eq!(parse_hex_bytes("#010203"), Some([1, 2, 3, 255]));
1392 assert_eq!(parse_hex_bytes("#01020304"), Some([1, 2, 3, 4]));
1393 assert_eq!(parse_hex_bytes("#fff"), None);
1394 }
1395
1396 #[test]
1397 fn perceptual_fade_alpha_walks_lightness_not_luminance() {
1398 let overlay = [0.62, 0.70, 0.95];
1399 let backdrop = [0.028, 0.028, 0.041];
1400 let a = |t: f32| perceptual_fade_alpha(t, overlay, backdrop, 1.0);
1401
1402 // Endpoints are the plain ones, and the ramp only ever falls.
1403 assert!((a(0.0) - 1.0).abs() < 1e-4);
1404 assert!(a(1.0).abs() < 1e-4);
1405 for i in 1..=20 {
1406 assert!(a(i as f32 / 20.0) <= a((i - 1) as f32 / 20.0));
1407 }
1408
1409 // The correction runs BELOW the straight ramp — that ramp's excess brightness
1410 // through the middle is the bow the eye reads as non-linear.
1411 assert!(a(0.5) < 0.5 - 0.1, "midpoint {} should sit well under 0.5", a(0.5));
1412
1413 // What it buys: composited lightness lands on a straight line. cbrt(luminance) is
1414 // the same proxy the implementation uses, checked here end to end through blending.
1415 let lum = |c: [f32; 3]| 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
1416 let l_at = |t: f32| {
1417 let a = a(t);
1418 let y: f32 = a * lum(overlay) + (1.0 - a) * lum(backdrop);
1419 y.cbrt()
1420 };
1421 let (top, bottom) = (l_at(0.0), l_at(1.0));
1422 for i in 0..=10 {
1423 let t = i as f32 / 10.0;
1424 let ideal = top + (bottom - top) * t;
1425 assert!((l_at(t) - ideal).abs() < 1e-3, "t={t}: {} vs {ideal}", l_at(t));
1426 }
1427
1428 // No lightness contrast to shape: falls back to the straight ramp.
1429 let flat = perceptual_fade_alpha(0.5, overlay, overlay, 1.0);
1430 assert!((flat - 0.5).abs() < 1e-4);
1431 }
1432
1433 #[test]
1434 fn test_print_active_config() {
1435 let path = crate::config::get_config_path();
1436 println!("ACTIVE CONFIG PATH: {:?}", path);
1437 if let Ok(content) = std::fs::read_to_string(&path) {
1438 println!("FILE READ OK! Length: {}", content.len());
1439 let val = crate::config::parse_kdl_to_json(&content);
1440 println!("PARSED JSON POINTER: {:?}", val.pointer("/style/control/dropdown/color"));
1441 } else {
1442 println!("FILE READ FAILED!");
1443 }
1444 println!("DROPDOWN COLOR GETTER: {:?}", dropdown_background_color());
1445 println!("LIST FONT COLOR GETTER: {:?}", list_font_color());
1446 println!("PLATE COLOR: {:?}", plate_color());
1447 println!("PLATE BORDER COLOR: {:?}", plate_border_color());
1448 println!("PLATE BORDER THICKNESS: {:?}", plate_border_thickness());
1449 println!("PLATE BLUR: {:?}", plate_blur());
1450 }
1451 }
1452
1453
1454 #[cfg(test)]
1455 mod tests {
1456 use super::*;
1457
1458 /// The hex that pins a built-in colour is NOT its floats times 255.
1459 ///
1460 /// The style loader gamma-decodes every config hex; the constants in this
1461 /// file are already linear. So `PARAM_BG = [0.10, 0.10, 0.14]` is spelled
1462 /// `#595969` in config, and the naive `#1a1a24` decodes to a plate ten
1463 /// times darker — silently, both being valid config. Pinned here because
1464 /// it cost a measurement round: a sweep meant to hold the tint constant
1465 /// was quietly sweeping it, and the two halves disagreed by 3x.
1466 #[test]
1467 fn the_hex_that_pins_a_default_round_trips_through_the_gamma_decode() {
1468 let decoded = parse_hex_rgba_linear("#595969").expect("valid hex");
1469 // RGB only — a 6-digit hex carries no alpha, which the asserts at the
1470 // bottom cover as its own hazard.
1471 for (got, want) in decoded.iter().take(3).zip(PARAM_BG.iter().take(3)) {
1472 assert!(
1473 (got - want).abs() < 0.005,
1474 "#595969 decodes to {decoded:?}, PARAM_BG is {PARAM_BG:?}"
1475 );
1476 }
1477
1478 // The naive spelling, and how far off it lands.
1479 let naive = parse_hex_rgba_linear("#1a1a24").expect("valid hex");
1480 assert!(
1481 naive[0] < PARAM_BG[0] / 5.0,
1482 "#1a1a24 was supposed to be nowhere near PARAM_BG, got {naive:?}"
1483 );
1484
1485 // Alpha is NOT decoded, and a 6-digit hex means OPAQUE — dropping the
1486 // last byte off a translucent plate colour does not leave it alone.
1487 assert!((parse_hex_rgba_linear("#05050840").unwrap()[3] - 0.251).abs() < 0.002);
1488 assert_eq!(parse_hex_rgba_linear("#050508").unwrap()[3], 1.0);
1489 }
1490
1491 /// Rim refraction defaults OFF and clamps, like its neighbour.
1492 /// The pane plates roll over the relief width — the one roll width —
1493 /// and nothing, a loaded `plate.bevel_width` included, moves them off it.
1494 #[test]
1495 fn the_pane_roll_is_the_relief_width() {
1496 let _lock = test_color_state_lock();
1497 crate::layout::lazy_init_style_registry();
1498 let _ = plate_blur();
1499 reload_colors("style {\n surface {\n plate bevel_width=(f64)12.0\n }\n}\n");
1500 assert_eq!(plate_bevel_width(), crate::layout::bevel_width(), "a retired key moves nothing");
1501 assert_eq!(
1502 retired_surface_keys(&crate::config::parse_kdl_to_json("style {\n surface {\n plate bevel_width=(f64)12.0\n }\n}\n")),
1503 vec!["style.surface.plate.bevel_width"]
1504 );
1505 reload_colors("");
1506 }
1507
1508 /// The four flat frost keys, the `backdrop_compression` spelling inside
1509 /// a `frost` child, `plate.bevel_width`, `depth` (on the relief, or in a
1510 /// material's `finish`), the relief's flat geometry keys and the
1511 /// window_manager bevel spellings are reported by path; the blocks
1512 /// themselves and a material's `compression` / `light` are not.
1513 #[test]
1514 fn retired_surface_keys_are_named_by_path_and_the_block_is_not() {
1515 let clean: serde_json::Value = serde_json::json!({ "style": { "surface": {
1516 "plate": { "frost": { "radius": 5.5, "compression": 0.0, "refraction": 0.0 }, "color": "#6c6c7bf2" },
1517 "relief": { "light": 0.15, "wall": { "height": 1.0, "profile": "a" }, "edge": { "height": 2.0, "profile": "b" } },
1518 "material": { "glass": { "frost": { "compression": 0.6 }, "finish": { "light": 0.2 } } }
1519 } }, "window_manager": { "corner_shape": 4.5, "control_relief": true } });
1520 assert!(retired_surface_keys(&clean).is_empty());
1521 let old: serde_json::Value = serde_json::json!({ "style": { "surface": {
1522 "plate": { "blur": true, "radius": 1.5, "backdrop_compression": 0.85, "refraction": 0.3,
1523 "bevel_width": 12.0, "frost": { "backdrop_compression": 0.2 } },
1524 "relief": { "depth": 0.15, "height": 1.0, "profile": "a", "edge_height": 2.0, "edge_profile": "b" },
1525 "material": { "glass": { "frost": { "backdrop_compression": 0.6 }, "finish": { "depth": 0.2 } } }
1526 } }, "window_manager": { "bevel_depth": 0.15, "bevel_width": 9.3, "bevel_shader": 0 } });
1527 let mut old = old;
1528 old["style"]["surface"]["graph"] = serde_json::json!({ "cell_color": "#545467", "gap_color": "#48485b", "grid_color": "#48485b", "uniform_background": false });
1529 assert_eq!(retired_surface_keys(&old), vec![
1530 "style.surface.plate.blur",
1531 "style.surface.plate.radius",
1532 "style.surface.plate.backdrop_compression",
1533 "style.surface.plate.refraction",
1534 "style.surface.plate.bevel_width",
1535 "style.surface.plate.frost.backdrop_compression",
1536 "style.surface.relief.depth",
1537 "style.surface.relief.height",
1538 "style.surface.relief.profile",
1539 "style.surface.relief.edge_height",
1540 "style.surface.relief.edge_profile",
1541 "window_manager.bevel_depth",
1542 "window_manager.bevel_width",
1543 "window_manager.bevel_shader",
1544 "style.surface.material.glass.frost.backdrop_compression",
1545 "style.surface.material.glass.finish.depth",
1546 "style.surface.graph.cell_color",
1547 "style.surface.graph.gap_color",
1548 "style.surface.graph.uniform_background",
1549 ]);
1550 assert!(retired_surface_keys(&serde_json::json!({})).is_empty());
1551 }
1552
1553 #[test]
1554 fn plate_refraction_defaults_off_and_clamps() {
1555 assert_eq!(plate_refraction(), 0.0, "off unless a config asks");
1556 set_plate_refraction(0.6);
1557 assert_eq!(plate_refraction(), 0.6);
1558 set_plate_refraction(9.0);
1559 assert_eq!(plate_refraction(), 1.0);
1560 set_plate_refraction(-0.5);
1561 assert_eq!(plate_refraction(), 0.0);
1562 }
1563
1564 /// The plate's backdrop compression defaults OFF and clamps.
1565 ///
1566 /// Off is load-bearing: it is the behaviour every config already in the
1567 /// wild has, and a toolkit-wide default that changed how every frosted
1568 /// surface in the DE looks would arrive unannounced in eighteen apps.
1569 /// Opting in is a per-app `style.surface.plate { frost compression=… }`.
1570 #[test]
1571 fn backdrop_compression_defaults_off_and_clamps() {
1572 assert_eq!(plate_backdrop_compression(), 0.0, "off unless a config asks");
1573
1574 set_plate_backdrop_compression(0.85);
1575 assert_eq!(plate_backdrop_compression(), 0.85);
1576
1577 // The shader clamps too, but a nonsense config value should not be
1578 // able to reach it and make the plate flat or inverted.
1579 set_plate_backdrop_compression(4.0);
1580 assert_eq!(plate_backdrop_compression(), 1.0);
1581 set_plate_backdrop_compression(-1.0);
1582 assert_eq!(plate_backdrop_compression(), 0.0);
1583 }
1584
1585 /// A colour pinned by one test is invisible to a test beside it.
1586 ///
1587 /// The invariant that ends the parallel-flake class here. These statics
1588 /// are process-wide, and `test_graph_style_configuration` pins a dozen of
1589 /// them without restoring any; before `style_write` became per-thread
1590 /// under `cfg(test)`, every test running alongside it saw those values.
1591 #[test]
1592 fn a_pinned_colour_is_private_to_its_thread() {
1593 let base = node_color();
1594 let pinned = [0.123, 0.456, 0.789, 1.0];
1595 assert_ne!(base, pinned, "pick a value the config cannot already hold");
1596
1597 set_node_color(pinned);
1598 assert_eq!(node_color(), pinned, "the pinning thread sees its own value");
1599
1600 let elsewhere = std::thread::spawn(node_color).join().unwrap();
1601 assert_eq!(elsewhere, base, "a thread beside it must still see the shared base");
1602 }
1603 }