Skip to main content

bern_theme/
recipes.rs

1//! Control recipes: which token fills each slot of a control in each
2//! state. Recipes name roles, never colors, so one table serves every mode,
3//! seed and contrast policy. `transparent` means the slot is not painted.
4
5/// The slots of one control state, as `(slot, token)`.
6#[derive(Debug)]
7pub struct RecipeState {
8    pub state: &'static str,
9    pub slots: &'static [(&'static str, &'static str)],
10}
11
12#[derive(Debug)]
13pub struct Recipe {
14    pub name: &'static str,
15    pub states: &'static [RecipeState],
16}
17
18impl Recipe {
19    pub fn state(&self, name: &str) -> Option<&RecipeState> {
20        self.states.iter().find(|state| state.state == name)
21    }
22}
23
24const fn state(state: &'static str, slots: &'static [(&'static str, &'static str)]) -> RecipeState {
25    RecipeState { state, slots }
26}
27
28/// The focus ring every interactive control shares.
29const FOCUS: RecipeState = state("focus", &[("ring", "focus.indicator")]);
30
31pub const RECIPES: &[Recipe] = &[
32    Recipe {
33        name: "button.standard",
34        // The label identifies the button; its outline is decorative.
35        states: &[
36            state(
37                "rest",
38                &[
39                    ("background", "surface.canvas"),
40                    ("foreground", "text.primary"),
41                    ("border", "separator"),
42                ],
43            ),
44            state(
45                "hover",
46                &[
47                    ("background", "button.standard.hover"),
48                    ("foreground", "text.primary"),
49                    ("border", "separator"),
50                ],
51            ),
52            state(
53                "pressed",
54                &[
55                    ("background", "button.standard.pressed"),
56                    ("foreground", "text.primary"),
57                    ("border", "separator"),
58                ],
59            ),
60            FOCUS,
61            state(
62                "disabled",
63                &[
64                    ("background", "disabled.surface"),
65                    ("foreground", "disabled.foreground"),
66                    ("border", "transparent"),
67                ],
68            ),
69        ],
70    },
71    Recipe {
72        name: "button.prominent",
73        states: &[
74            state(
75                "rest",
76                &[("background", "accent"), ("foreground", "on_accent")],
77            ),
78            state(
79                "hover",
80                &[
81                    ("background", "button.prominent.hover"),
82                    ("foreground", "on_accent"),
83                ],
84            ),
85            state(
86                "pressed",
87                &[
88                    ("background", "button.prominent.pressed"),
89                    ("foreground", "on_accent"),
90                ],
91            ),
92            FOCUS,
93            state(
94                "disabled",
95                &[
96                    ("background", "disabled.surface"),
97                    ("foreground", "disabled.foreground"),
98                ],
99            ),
100        ],
101    },
102    Recipe {
103        name: "button.plain",
104        states: &[
105            state(
106                "rest",
107                &[("background", "transparent"), ("foreground", "accent.text")],
108            ),
109            state(
110                "hover",
111                &[
112                    ("background", "hover.overlay"),
113                    ("foreground", "accent.text"),
114                ],
115            ),
116            state(
117                "pressed",
118                &[
119                    ("background", "pressed.overlay"),
120                    ("foreground", "accent.text"),
121                ],
122            ),
123            FOCUS,
124            state(
125                "disabled",
126                &[
127                    ("background", "transparent"),
128                    ("foreground", "disabled.foreground"),
129                ],
130            ),
131        ],
132    },
133    Recipe {
134        name: "button.destructive",
135        states: &[
136            state(
137                "rest",
138                &[
139                    ("background", "status.error.surface"),
140                    ("foreground", "on_error"),
141                ],
142            ),
143            state(
144                "hover",
145                &[
146                    ("background", "button.destructive.hover"),
147                    ("foreground", "on_error"),
148                ],
149            ),
150            state(
151                "pressed",
152                &[
153                    ("background", "button.destructive.pressed"),
154                    ("foreground", "on_error"),
155                ],
156            ),
157            FOCUS,
158            state(
159                "disabled",
160                &[
161                    ("background", "disabled.surface"),
162                    ("foreground", "disabled.foreground"),
163                ],
164            ),
165        ],
166    },
167    Recipe {
168        name: "icon_button",
169        states: &[
170            state(
171                "rest",
172                &[
173                    ("background", "transparent"),
174                    ("foreground", "icon.secondary"),
175                ],
176            ),
177            state(
178                "hover",
179                &[("background", "transparent"), ("foreground", "accent")],
180            ),
181            state(
182                "pressed",
183                &[("background", "transparent"), ("foreground", "accent.text")],
184            ),
185            state(
186                "selected",
187                &[("background", "transparent"), ("foreground", "accent.text")],
188            ),
189            FOCUS,
190            state(
191                "disabled",
192                &[
193                    ("background", "transparent"),
194                    ("foreground", "disabled.foreground"),
195                ],
196            ),
197        ],
198    },
199    Recipe {
200        name: "field",
201        states: &[
202            state(
203                "rest",
204                &[
205                    ("background", "surface.raised"),
206                    ("foreground", "text.primary"),
207                    ("placeholder", "text.placeholder"),
208                    ("border", "control.border"),
209                ],
210            ),
211            state(
212                "focus",
213                &[
214                    ("background", "surface.raised"),
215                    ("foreground", "text.primary"),
216                    ("border", "accent"),
217                    ("ring", "focus.indicator"),
218                ],
219            ),
220            state(
221                "error",
222                &[
223                    ("background", "surface.raised"),
224                    ("foreground", "text.primary"),
225                    ("border", "status.error"),
226                ],
227            ),
228            state(
229                "disabled",
230                &[
231                    ("background", "disabled.surface"),
232                    ("foreground", "disabled.foreground"),
233                    ("border", "transparent"),
234                ],
235            ),
236        ],
237    },
238    Recipe {
239        name: "switch",
240        // An outlined track when off, a filled one when on.
241        states: &[
242            state(
243                "off",
244                &[
245                    ("track", "transparent"),
246                    ("border", "control.border"),
247                    ("thumb", "control.border"),
248                ],
249            ),
250            state(
251                "off.hover",
252                &[
253                    ("track", "transparent"),
254                    ("border", "text.secondary"),
255                    ("thumb", "text.secondary"),
256                ],
257            ),
258            state(
259                "on",
260                &[
261                    ("track", "accent"),
262                    ("border", "transparent"),
263                    ("thumb", "on_accent"),
264                ],
265            ),
266            state(
267                "on.hover",
268                &[
269                    ("track", "button.prominent.hover"),
270                    ("border", "transparent"),
271                    ("thumb", "on_accent"),
272                ],
273            ),
274            FOCUS,
275            state(
276                "disabled",
277                &[
278                    ("track", "transparent"),
279                    ("border", "disabled.foreground"),
280                    ("thumb", "disabled.foreground"),
281                ],
282            ),
283        ],
284    },
285    Recipe {
286        name: "slider",
287        // The thumb is an accent dot inside a ring of the thumb color.
288        states: &[
289            state(
290                "rest",
291                &[
292                    ("fill", "accent"),
293                    ("track", "control.track"),
294                    ("thumb", "control.thumb"),
295                    ("thumb_border", "control.border"),
296                    ("thumb_center", "accent"),
297                ],
298            ),
299            state(
300                "hover",
301                &[
302                    ("fill", "accent"),
303                    ("track", "control.track"),
304                    ("thumb", "control.thumb"),
305                    ("thumb_border", "control.border"),
306                    ("thumb_center", "button.prominent.hover"),
307                ],
308            ),
309            state(
310                "active",
311                &[
312                    ("fill", "accent"),
313                    ("track", "control.track"),
314                    ("thumb", "control.thumb"),
315                    ("thumb_border", "control.border"),
316                    ("thumb_center", "button.prominent.pressed"),
317                ],
318            ),
319            FOCUS,
320            state(
321                "disabled",
322                &[
323                    ("fill", "disabled.foreground"),
324                    ("track", "disabled.surface"),
325                    ("thumb", "disabled.surface"),
326                    ("thumb_border", "disabled.foreground"),
327                    ("thumb_center", "disabled.foreground"),
328                ],
329            ),
330        ],
331    },
332    Recipe {
333        name: "media_scrubber",
334        // Neutral progress, so it does not compete with the picture.
335        states: &[
336            state(
337                "rest",
338                &[
339                    ("fill", "media.played"),
340                    ("track", "media.track"),
341                    ("thumb", "control.thumb"),
342                    ("thumb_border", "control.border"),
343                    ("shadow", "scrim"),
344                ],
345            ),
346            FOCUS,
347            state(
348                "disabled",
349                &[
350                    ("fill", "disabled.foreground"),
351                    ("track", "media.track"),
352                    ("thumb", "disabled.surface"),
353                    ("thumb_border", "disabled.foreground"),
354                    ("shadow", "transparent"),
355                ],
356            ),
357        ],
358    },
359    Recipe {
360        name: "segmented",
361        states: &[
362            state(
363                "rest",
364                &[
365                    ("track", "surface.panel"),
366                    ("background", "transparent"),
367                    ("foreground", "text.secondary"),
368                    ("border", "control.border"),
369                ],
370            ),
371            state(
372                "hover",
373                &[
374                    ("background", "hover.overlay"),
375                    ("foreground", "text.primary"),
376                ],
377            ),
378            state(
379                "selected",
380                &[
381                    ("background", "surface.canvas"),
382                    ("foreground", "text.primary"),
383                    ("border", "separator"),
384                ],
385            ),
386            FOCUS,
387            state(
388                "disabled",
389                &[
390                    ("background", "transparent"),
391                    ("foreground", "disabled.foreground"),
392                ],
393            ),
394        ],
395    },
396    Recipe {
397        name: "chip",
398        // A pill per option; the chosen one is inverted, as text on the
399        // page would be.
400        states: &[
401            state(
402                "rest",
403                &[
404                    ("background", "transparent"),
405                    ("foreground", "text.secondary"),
406                    ("border", "control.border"),
407                ],
408            ),
409            state(
410                "hover",
411                &[
412                    ("background", "hover.overlay"),
413                    ("foreground", "text.primary"),
414                ],
415            ),
416            state(
417                "selected",
418                &[
419                    ("background", "text.primary"),
420                    ("foreground", "surface.canvas"),
421                    ("border", "transparent"),
422                ],
423            ),
424            FOCUS,
425            state(
426                "disabled",
427                &[
428                    ("background", "transparent"),
429                    ("foreground", "disabled.foreground"),
430                ],
431            ),
432        ],
433    },
434    Recipe {
435        name: "nav_item",
436        // A selected item is filled with the accent, its text and icon in
437        // `on_accent`; there is no indicator bar.
438        states: &[
439            state(
440                "rest",
441                &[
442                    ("background", "transparent"),
443                    ("foreground", "text.secondary"),
444                ],
445            ),
446            state(
447                "hover",
448                &[
449                    ("background", "hover.overlay"),
450                    ("foreground", "text.primary"),
451                ],
452            ),
453            state(
454                "selected",
455                &[("background", "accent"), ("foreground", "on_accent")],
456            ),
457            state(
458                "selected.hover",
459                &[
460                    ("background", "button.prominent.hover"),
461                    ("foreground", "on_accent"),
462                ],
463            ),
464            FOCUS,
465        ],
466    },
467    Recipe {
468        name: "row",
469        states: &[
470            state(
471                "rest",
472                &[
473                    ("background", "transparent"),
474                    ("foreground", "text.primary"),
475                ],
476            ),
477            state(
478                "hover",
479                &[
480                    ("background", "hover.overlay"),
481                    ("foreground", "text.primary"),
482                ],
483            ),
484            state(
485                "pressed",
486                &[
487                    ("background", "pressed.overlay"),
488                    ("foreground", "text.primary"),
489                ],
490            ),
491            FOCUS,
492        ],
493    },
494    Recipe {
495        name: "tab",
496        states: &[
497            state(
498                "rest",
499                &[
500                    ("background", "transparent"),
501                    ("foreground", "text.secondary"),
502                ],
503            ),
504            state(
505                "hover",
506                &[
507                    ("background", "transparent"),
508                    ("foreground", "text.primary"),
509                ],
510            ),
511            state(
512                "selected",
513                &[
514                    ("background", "transparent"),
515                    ("foreground", "accent.text"),
516                    ("indicator", "accent"),
517                ],
518            ),
519            // Tabs set vertically, as a wide dialog's navigation, select
520            // as navigation items do.
521            state(
522                "selected.vertical",
523                &[("background", "accent"), ("foreground", "on_accent")],
524            ),
525            FOCUS,
526        ],
527    },
528    Recipe {
529        name: "menu",
530        // A popup list; the trigger uses `field`.
531        states: &[
532            state(
533                "popup",
534                &[
535                    ("background", "surface.raised"),
536                    ("border", "separator"),
537                    ("shadow", "scrim"),
538                ],
539            ),
540            state(
541                "rest",
542                &[
543                    ("background", "transparent"),
544                    ("foreground", "text.primary"),
545                ],
546            ),
547            state(
548                "hover",
549                &[
550                    ("background", "hover.overlay"),
551                    ("foreground", "text.primary"),
552                ],
553            ),
554            state(
555                "selected",
556                &[
557                    ("background", "transparent"),
558                    ("foreground", "text.primary"),
559                    ("check", "accent.text"),
560                ],
561            ),
562            FOCUS,
563        ],
564    },
565    Recipe {
566        name: "dialog",
567        states: &[state(
568            "open",
569            &[
570                ("background", "surface.raised"),
571                ("foreground", "text.primary"),
572                ("border", "separator"),
573                ("scrim", "scrim"),
574            ],
575        )],
576    },
577    Recipe {
578        name: "window.chrome",
579        states: &[
580            state(
581                "active",
582                &[
583                    ("background", "surface.raised"),
584                    ("foreground", "text.primary"),
585                    ("border", "separator"),
586                ],
587            ),
588            state(
589                "inactive",
590                &[
591                    ("background", "surface.raised"),
592                    ("foreground", "text.secondary"),
593                    ("border", "separator"),
594                ],
595            ),
596        ],
597    },
598];