Skip to main content

pfx_theme/
lib.rs

1use std::borrow::Cow;
2
3use egui::{Color32, RichText, WidgetText};
4use pfx_editor_style::Theme;
5use pfx_editor_style::theme::{Caps, Diff, Display, Face, Syntax};
6
7pub use pfx_editor_style as style;
8
9pub static MONO_REGULAR: &[u8] =
10    include_bytes!("../fonts/jetbrains-mono/JetBrainsMono-Regular.ttf");
11pub static MONO_BOLD: &[u8] = include_bytes!("../fonts/jetbrains-mono/JetBrainsMono-Bold.ttf");
12pub static MONO_LICENCE: &str = include_str!("../fonts/jetbrains-mono/OFL.txt");
13pub static DISPLAY: &[u8] = include_bytes!("../fonts/exo-2/Exo2-Light.ttf");
14pub static DISPLAY_LICENCE: &str = include_str!("../fonts/exo-2/OFL.txt");
15
16const fn rgb(hex: u32) -> Color32 {
17    Color32::from_rgb((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
18}
19
20pub const BG: Color32 = rgb(0x0E0E0E);
21pub const SURFACE: Color32 = rgb(0x1D1928);
22pub const WELL: Color32 = rgb(0x080808);
23pub const LINE: Color32 = rgb(0x2E2D30);
24pub const TRACK: Color32 = rgb(0x3B3A3A);
25pub const TEXT: Color32 = rgb(0xEEEDED);
26pub const TEXT2: Color32 = rgb(0x9E9BA1);
27pub const MUTED: Color32 = rgb(0x6F6D70);
28pub const DIM: Color32 = rgb(0x504E53);
29pub const CODE: Color32 = rgb(0xD4D1D8);
30pub const ACCENT: Color32 = rgb(0x5331A8);
31pub const ACCENT_BRIGHT: Color32 = rgb(0x542ACD);
32pub const ACCENT_DEEP: Color32 = rgb(0x342850);
33pub const SECONDARY: Color32 = rgb(0x9B80C6);
34pub const SECONDARY_SOFT: Color32 = rgb(0xB6ABCB);
35pub const HOT: Color32 = rgb(0xCEF03B);
36pub const HOT_DIM: Color32 = rgb(0xACC33B);
37pub const HOT_TINT: Color32 = rgb(0x23280F);
38pub const ERROR: Color32 = rgb(0xFF4D6D);
39pub const ERROR_TINT: Color32 = rgb(0x3A1820);
40pub const WARNING: Color32 = rgb(0xFF9F43);
41
42pub fn pfx() -> Theme {
43    Theme {
44        name: Cow::Borrowed("pfx"),
45        bg: BG,
46        surface: SURFACE,
47        well: WELL,
48        line: LINE,
49        track: TRACK,
50        text: TEXT,
51        text2: TEXT2,
52        muted: MUTED,
53        dim: DIM,
54        code: CODE,
55        on_accent: TEXT,
56        accent: ACCENT,
57        accent_bright: ACCENT_BRIGHT,
58        accent_deep: ACCENT_DEEP,
59        secondary: SECONDARY,
60        secondary_soft: SECONDARY_SOFT,
61        hot: HOT,
62        hot_dim: HOT_DIM,
63        ok: HOT_DIM,
64        ok_tint: HOT_TINT,
65        error: ERROR,
66        error_tint: ERROR_TINT,
67        warning: WARNING,
68        syntax: Syntax {
69            key: SECONDARY,
70            table: SECONDARY_SOFT,
71            string: HOT_DIM,
72            number: TEXT,
73            boolean: SECONDARY_SOFT,
74            date: TEXT2,
75            comment: MUTED,
76            punctuation: TEXT2,
77            plain: CODE,
78        },
79        diff: Diff {
80            add: HOT_TINT,
81            del: ERROR_TINT,
82            change: ACCENT_DEEP,
83        },
84        size_code: 11.0,
85        size_small: 12.0,
86        size_body: 13.0,
87        size_heading: 16.0,
88        size_title: 20.0,
89        radius: 2,
90        stroke: 1.0,
91        panel_padding: 10,
92        item_spacing: 6.0,
93        section_gap: 16.0,
94        dash: 2.0,
95        row_height: 19.0,
96        body: Face {
97            name: Cow::Borrowed("JetBrainsMono"),
98            regular: Cow::Borrowed(MONO_REGULAR),
99            bold: Cow::Borrowed(MONO_BOLD),
100        },
101        display: Some(Display {
102            name: Cow::Borrowed("Exo2-Light"),
103            bytes: Cow::Borrowed(DISPLAY),
104            licence: Cow::Borrowed(DISPLAY_LICENCE),
105        }),
106        titles: Caps {
107            upper: true,
108            tracking: 0.3,
109        },
110        labels: Caps {
111            upper: true,
112            tracking: 0.12,
113        },
114    }
115}
116
117pub fn label(theme: &Theme, text: &str, colour: Color32) -> WidgetText {
118    theme
119        .labels
120        .job(text, theme.font(theme.size_code), colour)
121        .into()
122}
123
124pub fn small(theme: &Theme, text: impl Into<String>, colour: Color32) -> RichText {
125    RichText::new(text)
126        .font(theme.font(theme.size_small))
127        .color(colour)
128}
129
130pub fn code(theme: &Theme, text: impl Into<String>, colour: Color32) -> RichText {
131    RichText::new(text)
132        .font(theme.font(theme.size_code))
133        .color(colour)
134}
135
136pub fn title(theme: &Theme, text: &str, size: f32, colour: Color32) -> WidgetText {
137    theme
138        .titles
139        .job(text, theme.title_font(size), colour)
140        .into()
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn pfx_carries_the_reference_roles_and_its_own_fonts() {
149        let theme = pfx();
150        let hex = |colour: Color32| {
151            (u32::from(colour.r()) << 16) | (u32::from(colour.g()) << 8) | u32::from(colour.b())
152        };
153        let roles = [
154            (theme.bg, 0x0E0E0E),
155            (theme.surface, 0x1D1928),
156            (theme.line, 0x2E2D30),
157            (theme.track, 0x3B3A3A),
158            (theme.text, 0xEEEDED),
159            (theme.text2, 0x9E9BA1),
160            (theme.muted, 0x6F6D70),
161            (theme.accent, 0x5331A8),
162            (theme.accent_bright, 0x542ACD),
163            (theme.accent_deep, 0x342850),
164            (theme.secondary, 0x9B80C6),
165            (theme.secondary_soft, 0xB6ABCB),
166            (theme.hot, 0xCEF03B),
167            (theme.hot_dim, 0xACC33B),
168            (theme.error, 0xFF4D6D),
169            (theme.warning, 0xFF9F43),
170        ];
171        for (colour, want) in roles {
172            assert_eq!(hex(colour), want, "{colour:?}");
173        }
174        assert_eq!(&MONO_REGULAR[..4], &[0, 1, 0, 0]);
175        assert_eq!(&MONO_BOLD[..4], &[0, 1, 0, 0]);
176        assert_eq!(&DISPLAY[..4], &[0, 1, 0, 0]);
177        assert!(
178            MONO_LICENCE.contains("JetBrains Mono")
179                && MONO_LICENCE.contains("SIL Open Font License")
180        );
181        assert!(
182            DISPLAY_LICENCE.contains("Exo 2") && DISPLAY_LICENCE.contains("SIL Open Font License")
183        );
184        let display = theme.display.as_ref().unwrap();
185        assert_eq!(display.bytes, DISPLAY);
186        assert_eq!(theme.titles.tracking, 0.3);
187        assert!(theme.titles.upper && theme.labels.upper);
188    }
189
190    #[test]
191    fn titles_and_labels_are_uppercase_and_tracked_and_titles_use_exo_2() {
192        let theme = pfx();
193        let ctx = egui::Context::default();
194        pfx_editor_style::theme::apply_theme(&ctx, &theme);
195        let job = theme
196            .titles
197            .job("outliner", theme.title_font(12.0), theme.text);
198        assert_eq!(job.text, "OUTLINER");
199        assert!((job.sections[0].format.extra_letter_spacing - 3.6).abs() < 1e-5);
200        assert_eq!(
201            job.sections[0].format.font_id.family,
202            pfx_editor_style::theme::display()
203        );
204        let data = theme.labels.job("roughness", theme.font(11.0), theme.text2);
205        assert_eq!(data.text, "ROUGHNESS");
206        assert_eq!(
207            data.sections[0].format.font_id.family,
208            egui::FontFamily::Monospace
209        );
210        ctx.run_ui(egui::RawInput::default(), |ui| {
211            egui::CentralPanel::default().show(ui, |ui| {
212                ui.label(title(&theme, "pfx edit", 13.0, theme.text));
213                ui.label(label(&theme, "roughness", theme.text2));
214            });
215        })
216        .textures_delta
217        .clear();
218        assert_eq!(*Theme::of(&ctx), theme);
219    }
220}