Skip to main content

jay_config/
theme.rs

1//! Tools for configuring the look of the compositor.
2
3use serde::{Deserialize, Serialize};
4
5/// A color.
6///
7/// When specifying RGBA values of a color, the RGB values can either be specified
8/// *straight* or *premultiplied*. Premultiplied means that the RGB values have already
9/// been multiplied by the alpha value.
10///
11/// Given a color, to reduce its opacity by half,
12///
13/// - if you're working with premultiplied values, you would multiply each component by `0.5`;
14/// - if you're working with straight values, you would multiply only the alpha component by `0.5`.
15///
16/// When using hexadecimal notation, `#RRGGBBAA`, the RGB values are usually straight.
17// values are stored premultiplied
18#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
19pub struct Color {
20    r: f32,
21    g: f32,
22    b: f32,
23    a: f32,
24}
25
26fn to_f32(c: u8) -> f32 {
27    c as f32 / 255f32
28}
29
30fn to_u8(c: f32) -> u8 {
31    (c * 255f32) as u8
32}
33
34fn validate_f32(f: f32) -> bool {
35    f >= 0.0 && f <= 1.0
36}
37
38fn validate_f32_all(f: [f32; 4]) -> bool {
39    if !f.into_iter().all(validate_f32) {
40        log::warn!(
41            "f32 values {:?} are not in the valid color range. Using solid black instead xyz",
42            f
43        );
44        return false;
45    }
46    true
47}
48
49impl Color {
50    /// Solid black.
51    pub const BLACK: Self = Self {
52        r: 0.0,
53        g: 0.0,
54        b: 0.0,
55        a: 1.0,
56    };
57
58    /// Creates a new color from `u8` RGB values.
59    pub fn new(r: u8, g: u8, b: u8) -> Self {
60        Self {
61            r: to_f32(r),
62            g: to_f32(g),
63            b: to_f32(b),
64            a: 1.0,
65        }
66    }
67
68    /// Creates a new color from straight `u8` RGBA values.
69    pub fn new_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
70        Self::new_f32_straight(to_f32(r), to_f32(g), to_f32(b), to_f32(a))
71    }
72
73    /// Creates a new color from premultiplied `f32` RGBA values.
74    pub fn new_f32_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
75        if !validate_f32_all([r, g, b, a]) {
76            Self::BLACK
77        } else if r > a || g > a || b > a {
78            log::warn!(
79                "f32 values {:?} are not valid for a premultiplied color. Using solid black instead.",
80                [r, g, b, a]
81            );
82            Self::BLACK
83        } else {
84            Self { r, g, b, a }
85        }
86    }
87
88    /// Creates a new color from straight `f32` RGBA values.
89    pub fn new_f32_straight(r: f32, g: f32, b: f32, a: f32) -> Self {
90        if !validate_f32_all([r, g, b, a]) {
91            Self::BLACK
92        } else {
93            Self {
94                r: r * a,
95                g: g * a,
96                b: b * a,
97                a,
98            }
99        }
100    }
101
102    /// Creates a new color from `f32` RGB values.
103    pub fn new_f32(r: f32, g: f32, b: f32) -> Self {
104        Self { r, g, b, a: 1.0 }
105    }
106
107    /// Converts the color to its premultiplied `f32` RGBA values.
108    pub fn to_f32_premultiplied(&self) -> [f32; 4] {
109        [self.r, self.g, self.b, self.a]
110    }
111
112    /// Converts the color to its straight `f32` RGBA values.
113    pub fn to_f32_straight(&self) -> [f32; 4] {
114        if self.a == 0.0 {
115            [0.0, 0.0, 0.0, 0.0]
116        } else {
117            let a = self.a;
118            [self.r / a, self.g / a, self.b / a, a]
119        }
120    }
121
122    /// Converts the color to its straight `u8` RGBA values.
123    pub fn to_u8_straight(&self) -> [u8; 4] {
124        let [r, g, b, a] = self.to_f32_straight();
125        [to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
126    }
127}
128
129/// Resets all sizes to their defaults.
130pub fn reset_sizes() {
131    get!().reset_sizes();
132}
133
134/// Resets all colors to their defaults.
135pub fn reset_colors() {
136    get!().reset_colors();
137}
138
139/// Returns the current font.
140pub fn get_font() -> String {
141    get!().get_font()
142}
143
144/// Sets the font.
145///
146/// Default: `monospace 8`.
147///
148/// See also [`set_bar_font`] and [`set_title_font`].
149///
150/// The font name should be specified in [pango][pango] syntax.
151///
152/// [pango]: https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html
153pub fn set_font(font: &str) {
154    get!().set_font(font)
155}
156
157/// Sets the font used by the bar.
158///
159/// If this function is not called, the font set by [`set_font`] is used. See that
160/// function for more details.
161pub fn set_bar_font(font: &str) {
162    get!().set_bar_font(font)
163}
164
165/// Sets the font used by window titles.
166///
167/// If this function is not called, the font set by [`set_font`] is used. See that
168/// function for more details.
169pub fn set_title_font(font: &str) {
170    get!().set_title_font(font)
171}
172
173/// Resets the fonts to the defaults.
174///
175/// Currently the default is `monospace 8`.
176pub fn reset_font() {
177    get!().reset_font()
178}
179
180#[non_exhaustive]
181#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Default)]
182pub enum BarPosition {
183    #[default]
184    Top,
185    Bottom,
186}
187
188/// Sets the position of the bar.
189///
190/// Default: `Top`.
191pub fn set_bar_position(position: BarPosition) {
192    get!().set_bar_position(position);
193}
194
195/// Gets the position of the bar.
196pub fn get_bar_position() -> BarPosition {
197    get!(BarPosition::Top).get_bar_position()
198}
199
200#[non_exhaustive]
201#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Default)]
202pub enum ContainerBorders {
203    /// Only separators are drawn between children.
204    #[default]
205    Separators,
206    /// A border is drawn around the entire container.
207    Full,
208}
209
210/// Sets the container border style.
211///
212/// Default: `Separators`.
213pub fn set_container_borders(borders: ContainerBorders) {
214    get!().set_container_borders(borders);
215}
216
217/// Gets the container border style.
218pub fn get_container_borders() -> ContainerBorders {
219    get!(ContainerBorders::Separators).get_container_borders()
220}
221
222/// Sets the proportional fonts used by egui windows.
223///
224/// The default is `["sans-serif", "Noto Sans", "Noto Color Emoji"]`.
225pub fn set_egui_proportional_fonts<'a>(fonts: impl IntoIterator<Item = &'a str>) {
226    get!().set_egui_fonts(Some(fonts.into_iter().collect()), None);
227}
228
229/// Sets the monospace fonts used by egui windows.
230///
231/// The default is `["monospace", "Noto Sans Mono", "Noto Color Emoji"]`.
232pub fn set_egui_monospace_fonts<'a>(fonts: impl IntoIterator<Item = &'a str>) {
233    get!().set_egui_fonts(None, Some(fonts.into_iter().collect()));
234}
235
236/// Sets whether window icons set by the client are shown.
237///
238/// The default is `true`.
239pub fn set_show_window_icons(show: bool) {
240    get!().set_show_window_icons(show);
241}
242
243/// Sets whether window icons set by the client are rendered as grayscale.
244///
245/// This is only supported on the Vulkan renderer.
246///
247/// The default is `false`.
248pub fn set_window_icons_grayscale(grayscale: bool) {
249    get!().set_window_icons_grayscale(grayscale);
250}
251
252/// Elements of the compositor whose color can be changed.
253pub mod colors {
254    use {
255        crate::theme::Color,
256        serde::{Deserialize, Serialize},
257    };
258
259    /// An element of the GUI whose color can be changed.
260    #[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
261    pub struct Colorable(#[doc(hidden)] pub u32);
262
263    impl Colorable {
264        /// Sets the color to an RGB value.
265        pub fn set(self, r: u8, g: u8, b: u8) {
266            let color = Color::new(r, g, b);
267            get!().set_color(self, color);
268        }
269
270        /// Sets the color to a `Color` that might contain an alpha component.
271        pub fn set_color(self, color: Color) {
272            get!().set_color(self, color);
273        }
274
275        /// Gets the current color.
276        pub fn get(self) -> Color {
277            get!(Color::BLACK).get_color(self)
278        }
279    }
280
281    macro_rules! colors {
282        ($($(#[$attr:meta])* const $n:expr => $name:ident,)*) => {
283            $(
284                $(#[$attr])*
285                pub const $name: Colorable = Colorable($n);
286            )*
287        }
288    }
289
290    colors! {
291        /// The title background color of an unfocused window.
292        ///
293        /// Default: `#222222`.
294        const 01 => UNFOCUSED_TITLE_BACKGROUND_COLOR,
295        /// The title background color of a focused window.
296        ///
297        /// Default: `#285577`.
298        const 02 => FOCUSED_TITLE_BACKGROUND_COLOR,
299        /// The title background color of an unfocused window that was the last focused
300        /// window in its container.
301        ///
302        /// Default: `#5f676a`.
303        const 03 => FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR,
304        /// The background color of the desktop.
305        ///
306        /// Default: `#001019`.
307        ///
308        /// You can use an application such as [swaybg][swaybg] to further customize the background.
309        ///
310        /// [swaybg]: https://github.com/swaywm/swaybg
311        const 04 => BACKGROUND_COLOR,
312        /// The background color of the bar.
313        ///
314        /// Default: `#000000`.
315        const 05 => BAR_BACKGROUND_COLOR,
316        /// The color of the 1px separator below window titles.
317        ///
318        /// Default: `#333333`.
319        const 06 => SEPARATOR_COLOR,
320        /// The color of the border between windows.
321        ///
322        /// Default: `#3f474a`.
323        const 07 => BORDER_COLOR,
324        /// The title text color of an unfocused window.
325        ///
326        /// Default: `#888888`.
327        const 08 => UNFOCUSED_TITLE_TEXT_COLOR,
328        /// The title text color of a focused window.
329        ///
330        /// Default: `#ffffff`.
331        const 09 => FOCUSED_TITLE_TEXT_COLOR,
332        /// The title text color of an unfocused window that was the last focused
333        /// window in its container.
334        ///
335        /// Default: `#ffffff`.
336        const 10 => FOCUSED_INACTIVE_TITLE_TEXT_COLOR,
337        /// The color of the status text in the bar.
338        ///
339        /// Default: `#ffffff`.
340        const 11 => BAR_STATUS_TEXT_COLOR,
341        /// The title background color of an unfocused window that might be captured.
342        ///
343        /// Default: `#220303`.
344        const 12 => CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR,
345        /// The title background color of a focused window that might be captured.
346        ///
347        /// Default: `#772831`.
348        const 13 => CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR,
349        /// The title background color of a window that has requested attention.
350        ///
351        /// Default: `#23092c`.
352        const 14 => ATTENTION_REQUESTED_BACKGROUND_COLOR,
353        /// Color used to highlight parts of the UI.
354        ///
355        /// Default: `#9d28c67f`.
356        const 15 => HIGHLIGHT_COLOR,
357        /// The color of the border between windows where at least one of the windows is
358        /// focused.
359        ///
360        /// For containers, this requires `Full` [`ContainerBorders`].
361        ///
362        /// Default: The `BORDER` color.
363        const 16 => FOCUSED_BORDER_COLOR,
364    }
365
366    /// Sets the color of GUI element.
367    pub fn set_color(element: Colorable, color: Color) {
368        get!().set_color(element, color);
369    }
370
371    /// Gets the color of GUI element.
372    pub fn get_color(element: Colorable) -> Color {
373        get!(Color::BLACK).get_color(element)
374    }
375}
376
377/// Elements of the compositor whose size can be changed.
378pub mod sized {
379    use serde::{Deserialize, Serialize};
380
381    /// An element of the GUI whose size can be changed.
382    #[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
383    pub struct Resizable(#[doc(hidden)] pub u32);
384
385    impl Resizable {
386        /// Gets the current size.
387        pub fn get(self) -> i32 {
388            get!(0).get_size(self)
389        }
390
391        /// Sets the size.
392        pub fn set(self, size: i32) {
393            get!().set_size(self, size)
394        }
395    }
396
397    macro_rules! sizes {
398        ($($(#[$attr:meta])* const $n:expr => $name:ident,)*) => {
399            $(
400                $(#[$attr])*
401                pub const $name: Resizable = Resizable($n);
402            )*
403        }
404    }
405
406    sizes! {
407        /// The height of window titles.
408        ///
409        /// Default: 17
410        const 01 => TITLE_HEIGHT,
411        /// The width of borders between windows.
412        ///
413        /// Default: 4
414        const 02 => BORDER_WIDTH,
415        /// The height of the bar.
416        ///
417        /// Defaults to the TITLE_HEIGHT if not set explicitly.
418        ///
419        /// Default: 17
420        const 03 => BAR_HEIGHT,
421        /// The width of the bar's separator.
422        ///
423        /// Default: 1
424        const 04 => BAR_SEPARATOR_WIDTH,
425    }
426}