Skip to main content

theme/theme/
install.rs

1//! Installing a palette as the gpui global, and the per-app palette builder.
2
3use gpui::{App, Global};
4
5use crate::{
6    Appearance, paint,
7    theme::{Theme, layout},
8};
9
10impl Theme {
11    /// Install the palette for `appearance` as the gpui global and point the
12    /// context-free paint helpers at it. The **only** way the appearance should
13    /// change — setting the global directly leaves
14    /// [`current_appearance`](crate::paint::current_appearance) stale.
15    ///
16    /// Which palette that is comes from [`set_palette`], so an app with its own
17    /// colours keeps them across a light/dark switch.
18    pub fn install(appearance: Appearance, cx: &mut App) {
19        let build = cx
20            .try_global::<Palette>()
21            .map_or(Self::for_appearance as fn(Appearance) -> Theme, |p| p.0);
22        let brand = crate::brand(cx);
23        let mut theme = build(appearance);
24        brand.apply(&mut theme);
25        layout::set_base_radius(brand.radius);
26        Self::install_custom(theme, cx);
27    }
28
29    /// Install a palette the caller built: brand colours, one retuned token, or
30    /// a wholesale replacement. `Theme` is a plain struct with public fields, so
31    /// the usual shape is `Theme::light()` with a few fields overwritten.
32    ///
33    /// Use this rather than `cx.set_global(theme)`. The context-free paint
34    /// helpers ([`ink`], [`hairline`], [`wash`], …) read [`current_appearance`]
35    /// and not the global, so a palette installed around this one leaves them
36    /// painting for whatever appearance was last installed — light washes over a
37    /// dark palette, and nothing to point at.
38    ///
39    /// One-shot: [`appearance::apply`] rebuilds the palette whenever the
40    /// appearance changes, so what is installed here is replaced on a light/dark
41    /// switch. For colours that survive that, register a builder with
42    /// [`set_palette`] instead.
43    ///
44    /// [`appearance::apply`]: crate::appearance::apply
45    /// [`ink`]: crate::paint::ink
46    /// [`hairline`]: crate::paint::hairline
47    /// [`wash`]: crate::paint::wash
48    /// [`current_appearance`]: crate::paint::current_appearance
49    pub fn install_custom(theme: Theme, cx: &mut App) {
50        paint::set_current_appearance(theme.appearance);
51        paint::bump_generation();
52        cx.set_global(theme);
53        // Every window, cached subtrees included. gpui busts a view's cache
54        // when an *entity* it read changes, and a palette is a global — so a
55        // cached view would go on painting the palette it was built under, and
56        // an app that caches anything would find the appearance switch half
57        // working.
58        cx.refresh_windows();
59    }
60
61    /// Read the theme global.
62    pub fn of(cx: &App) -> &Theme {
63        cx.global::<Theme>()
64    }
65}
66
67/// How the app builds a palette for an appearance. See [`set_palette`].
68struct Palette(fn(Appearance) -> Theme);
69
70impl Global for Palette {}
71
72/// Teach bezel how this app builds its palette, so light/dark switching rebuilds
73/// *your* colours instead of replacing them with the built-in ones.
74///
75/// A palette installed with [`Theme::install_custom`] alone lasts only until the
76/// appearance changes, because [`appearance::apply`] rebuilds from scratch.
77/// Registering the builder is what makes brand colours survive:
78///
79/// ```ignore
80/// fn palette(appearance: Appearance) -> Theme {
81///     let mut theme = Theme::for_appearance(appearance);
82///     theme.accent = my_brand_accent(appearance);
83///     theme
84/// }
85/// theme::set_palette(palette, cx);          // before appearance::init
86/// ```
87///
88/// Call it before [`appearance::init`], which installs the first palette. Later
89/// than that, follow it with [`appearance::apply`] to repaint.
90///
91/// [`appearance::init`]: crate::appearance::init
92/// [`appearance::apply`]: crate::appearance::apply
93pub fn set_palette(build: fn(Appearance) -> Theme, cx: &mut App) {
94    cx.set_global(Palette(build));
95}