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}