Skip to main content

theme/theme/
install.rs

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