cog_task/gui/
style.rs

1use crate::assets::{FONT_ICONS_BRANDS, FONT_ICONS_REGULAR, FONT_ICONS_SOLID};
2use crate::util::f32_with_precision;
3use eframe::egui;
4use eframe::egui::{FontId, TextStyle};
5use egui::{Color32, FontData, FontDefinitions, FontFamily, Rgba, Rounding, Stroke, Vec2};
6use std::convert::Into;
7use std::sync::{Arc, Mutex};
8use std::time::{Duration, Instant};
9
10pub const SCREEN_SIZE: Vec2 = Vec2::new(1920.0, 1080.0);
11
12pub const TEXT_SIZE_HEADING: f32 = 42.0;
13pub const TEXT_SIZE_BODY: f32 = 34.0;
14pub const TEXT_SIZE_MONOSPACE: f32 = 28.0;
15pub const TEXT_SIZE_BUTTON1: f32 = 38.0;
16pub const TEXT_SIZE_BUTTON2: f32 = 34.0;
17pub const TEXT_SIZE_TOOLTIP: f32 = 20.0;
18pub const TEXT_SIZE_ICON: f32 = 32.0;
19pub const TEXT_SIZE_DIALOGUE_TITLE: f32 = 30.0;
20pub const TEXT_SIZE_DIALOGUE_BODY: f32 = 26.0;
21
22pub const HOVERED: Rgba = Rgba::from_rgb(
23    0x67 as f32 / 255.0,
24    0x7B as f32 / 255.0,
25    0xC4 as f32 / 255.0,
26);
27
28pub const FOREST_GREEN: Rgba = Rgba::from_rgb(
29    0x22 as f32 / 255.0,
30    0x8B as f32 / 255.0,
31    0x22 as f32 / 255.0,
32);
33
34pub const CUSTOM_RED: Rgba = Rgba::from_rgb(
35    0xC0 as f32 / 255.0,
36    0x1C as f32 / 255.0,
37    0x1C as f32 / 255.0,
38);
39
40pub const CUSTOM_ORANGE: Rgba = Rgba::from_rgb(
41    0xFF as f32 / 255.0,
42    0x6D as f32 / 255.0,
43    0x0A as f32 / 255.0,
44);
45
46pub const CUSTOM_BLUE: Rgba = Rgba::from_rgb(
47    0x00 as f32 / 255.0,
48    0x33 as f32 / 255.0,
49    0x66 as f32 / 255.0,
50);
51
52pub const ACTIVE_BLUE: Rgba = Rgba::from_rgb(
53    0x72 as f32 / 255.0,
54    0x89 as f32 / 255.0,
55    0xDA as f32 / 255.0,
56);
57
58pub enum Style {
59    IconControls,
60    SelectButton,
61    CancelButton,
62    SubmitButton,
63    TodoButton,
64    DoneButton,
65    InterruptedButton,
66    FailedButton,
67    SoftFailedButton,
68    SingleLineTextEdit,
69}
70
71pub fn style_ui(ui: &mut egui::Ui, style: Style) {
72    match style {
73        Style::IconControls => {
74            let rounding = Rounding::same(30.0);
75            ui.spacing_mut().item_spacing = Vec2::splat(10.0);
76            ui.spacing_mut().button_padding = Vec2::splat(10.0);
77            ui.visuals_mut().widgets.inactive.bg_fill = Color32::TRANSPARENT;
78            ui.visuals_mut().widgets.inactive.bg_stroke = Stroke::none();
79            ui.visuals_mut().widgets.inactive.rounding = rounding;
80            ui.visuals_mut().widgets.hovered.bg_fill =
81                Rgba::from(Color32::LIGHT_GRAY).multiply(0.2).into();
82            ui.visuals_mut().widgets.hovered.bg_stroke = Stroke::new(1.5, Color32::LIGHT_GRAY);
83            ui.visuals_mut().widgets.hovered.rounding = rounding;
84            ui.visuals_mut().widgets.active.bg_fill =
85                Rgba::from(Color32::LIGHT_GRAY).multiply(0.4).into();
86            ui.visuals_mut().widgets.active.bg_stroke = Stroke::new(3.0, Color32::DARK_GRAY);
87            ui.visuals_mut().widgets.active.rounding = rounding;
88            ui.visuals_mut().widgets.noninteractive.bg_fill = Color32::TRANSPARENT;
89            ui.visuals_mut().widgets.noninteractive.bg_stroke = Stroke::none();
90            ui.visuals_mut().widgets.noninteractive.rounding = rounding;
91        }
92        Style::SelectButton => {
93            let rounding = Rounding::same(40.0);
94            ui.spacing_mut().item_spacing = Vec2::splat(10.0);
95            ui.spacing_mut().button_padding = Vec2::new(20.0, 8.0);
96            ui.visuals_mut().widgets.inactive.rounding = rounding;
97            ui.visuals_mut().widgets.inactive.bg_fill =
98                Rgba::from(Color32::LIGHT_GRAY).multiply(0.2).into();
99            ui.visuals_mut().widgets.inactive.bg_stroke = Stroke::new(2.0, Color32::LIGHT_GRAY);
100            ui.visuals_mut().widgets.inactive.fg_stroke = Stroke::new(2.0, Color32::BLACK);
101            ui.visuals_mut().widgets.hovered.rounding = rounding;
102            ui.visuals_mut().widgets.hovered.bg_fill = HOVERED.multiply(0.2).into();
103            ui.visuals_mut().widgets.hovered.bg_stroke = Stroke::new(3.0, Color32::LIGHT_GRAY);
104            ui.visuals_mut().widgets.hovered.fg_stroke = Stroke::new(2.0, Color32::BLACK);
105            ui.visuals_mut().widgets.active.rounding = rounding;
106            ui.visuals_mut().widgets.active.bg_fill = HOVERED.multiply(0.2).into();
107            ui.visuals_mut().widgets.active.bg_stroke = Stroke::new(4.0, Color32::DARK_GRAY);
108            ui.visuals_mut().widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
109            ui.visuals_mut().widgets.noninteractive.rounding = rounding;
110        }
111        Style::CancelButton => {
112            let rounding = Rounding::same(50.0);
113            ui.spacing_mut().button_padding = Vec2::new(60.0, 20.0);
114            ui.visuals_mut().widgets.inactive.rounding = rounding;
115            ui.visuals_mut().widgets.inactive.bg_fill =
116                Rgba::from(Color32::LIGHT_GRAY).multiply(0.2).into();
117            ui.visuals_mut().widgets.inactive.bg_stroke = Stroke::new(2.0, Color32::LIGHT_GRAY);
118            ui.visuals_mut().widgets.inactive.fg_stroke = Stroke::new(2.0, CUSTOM_RED);
119            ui.visuals_mut().widgets.hovered.rounding = rounding;
120            ui.visuals_mut().widgets.hovered.bg_fill =
121                Rgba::from(Color32::LIGHT_GRAY).multiply(0.4).into();
122            ui.visuals_mut().widgets.hovered.bg_stroke = Stroke::new(3.0, Color32::LIGHT_GRAY);
123            ui.visuals_mut().widgets.hovered.fg_stroke = Stroke::new(2.0, CUSTOM_RED);
124            ui.visuals_mut().widgets.active.rounding = rounding;
125            ui.visuals_mut().widgets.active.bg_fill =
126                Rgba::from(Color32::LIGHT_GRAY).multiply(0.4).into();
127            ui.visuals_mut().widgets.active.bg_stroke = Stroke::new(4.0, CUSTOM_RED);
128            ui.visuals_mut().widgets.active.fg_stroke = Stroke::new(2.0, CUSTOM_RED);
129            ui.visuals_mut().widgets.noninteractive.rounding = rounding;
130            ui.visuals_mut().override_text_color = Some(CUSTOM_RED.into());
131        }
132        Style::SubmitButton => {
133            let rounding = Rounding::same(50.0);
134            ui.spacing_mut().button_padding = Vec2::new(60.0, 20.0);
135            ui.visuals_mut().widgets.inactive.rounding = rounding;
136            ui.visuals_mut().widgets.inactive.bg_fill =
137                Rgba::from(Color32::LIGHT_GRAY).multiply(0.2).into();
138            ui.visuals_mut().widgets.inactive.bg_stroke = Stroke::new(2.0, Color32::LIGHT_GRAY);
139            ui.visuals_mut().widgets.inactive.fg_stroke = Stroke::new(2.0, FOREST_GREEN);
140            ui.visuals_mut().widgets.hovered.rounding = rounding;
141            ui.visuals_mut().widgets.hovered.bg_fill =
142                Rgba::from(Color32::LIGHT_GRAY).multiply(0.4).into();
143            ui.visuals_mut().widgets.hovered.bg_stroke = Stroke::new(3.0, Color32::LIGHT_GRAY);
144            ui.visuals_mut().widgets.hovered.fg_stroke = Stroke::new(2.0, FOREST_GREEN);
145            ui.visuals_mut().widgets.active.rounding = rounding;
146            ui.visuals_mut().widgets.active.bg_fill =
147                Rgba::from(Color32::LIGHT_GRAY).multiply(0.4).into();
148            ui.visuals_mut().widgets.active.bg_stroke = Stroke::new(4.0, FOREST_GREEN);
149            ui.visuals_mut().widgets.active.fg_stroke = Stroke::new(2.0, FOREST_GREEN);
150            ui.visuals_mut().widgets.noninteractive.rounding = rounding;
151            ui.visuals_mut().override_text_color = Some(FOREST_GREEN.into());
152        }
153        Style::SingleLineTextEdit => {
154            let rounding = Rounding::same(8.0);
155            ui.visuals_mut().widgets.inactive.rounding = rounding;
156            ui.visuals_mut().widgets.hovered.rounding = rounding;
157            ui.visuals_mut().widgets.active.rounding = rounding;
158            ui.visuals_mut().widgets.noninteractive.rounding = rounding;
159        }
160        Style::TodoButton => {
161            let rounding = Rounding::same(40.0);
162            ui.spacing_mut().button_padding = Vec2::new(30.0, 15.0);
163            ui.visuals_mut().widgets.inactive.rounding = rounding;
164            ui.visuals_mut().widgets.inactive.bg_fill = Color32::TRANSPARENT;
165            ui.visuals_mut().widgets.inactive.bg_stroke = Stroke::new(2.0, Color32::LIGHT_GRAY);
166            ui.visuals_mut().widgets.hovered.rounding = rounding;
167            ui.visuals_mut().widgets.hovered.bg_fill =
168                Rgba::from(Color32::LIGHT_GRAY).multiply(0.2).into();
169            ui.visuals_mut().widgets.hovered.bg_stroke = Stroke::new(3.0, Color32::LIGHT_GRAY);
170            ui.visuals_mut().widgets.active.rounding = rounding;
171            ui.visuals_mut().widgets.active.bg_fill =
172                Rgba::from(Color32::LIGHT_GRAY).multiply(0.4).into();
173            ui.visuals_mut().widgets.active.bg_stroke = Stroke::new(4.0, ACTIVE_BLUE);
174            ui.visuals_mut().widgets.noninteractive.rounding = rounding;
175        }
176        Style::DoneButton => {
177            let rounding = Rounding::same(40.0);
178            ui.spacing_mut().button_padding = Vec2::new(30.0, 15.0);
179            ui.visuals_mut().widgets.inactive.rounding = rounding;
180            ui.visuals_mut().widgets.inactive.bg_fill = Color32::TRANSPARENT;
181            ui.visuals_mut().widgets.inactive.bg_stroke = Stroke::none();
182            ui.visuals_mut().widgets.inactive.fg_stroke = Stroke::new(2.0, Color32::LIGHT_GRAY);
183            ui.visuals_mut().widgets.hovered.rounding = rounding;
184            ui.visuals_mut().widgets.hovered.bg_fill =
185                Rgba::from(Color32::LIGHT_GRAY).multiply(0.2).into();
186            ui.visuals_mut().widgets.hovered.bg_stroke = Stroke::none();
187            ui.visuals_mut().widgets.hovered.fg_stroke = Stroke::new(4.0, Color32::LIGHT_GRAY);
188            ui.visuals_mut().widgets.active.rounding = rounding;
189            ui.visuals_mut().widgets.active.bg_fill =
190                Rgba::from(Color32::LIGHT_GRAY).multiply(0.4).into();
191            ui.visuals_mut().widgets.active.bg_stroke = Stroke::new(4.0, ACTIVE_BLUE);
192            ui.visuals_mut().widgets.noninteractive.rounding = rounding;
193            ui.visuals_mut().override_text_color = Some(FOREST_GREEN.into());
194        }
195        Style::InterruptedButton => {
196            style_ui(ui, Style::TodoButton);
197            ui.visuals_mut().override_text_color = Some(CUSTOM_BLUE.into());
198        }
199        Style::FailedButton => {
200            style_ui(ui, Style::TodoButton);
201            ui.visuals_mut().override_text_color = Some(CUSTOM_RED.into());
202        }
203        Style::SoftFailedButton => {
204            style_ui(ui, Style::TodoButton);
205            ui.visuals_mut().override_text_color = Some(CUSTOM_ORANGE.into());
206        }
207    }
208}
209
210pub fn init(ctx: &egui::Context) {
211    // Start with the default fonts (we will be adding to them rather than replacing them).
212    let mut fonts = FontDefinitions::default();
213
214    // Icon fonts from font-awesome
215    fonts.font_data.insert(
216        "fa_brands_regular".to_owned(),
217        FontData::from_static(FONT_ICONS_BRANDS),
218    );
219    fonts.font_data.insert(
220        "fa_free_regular".to_owned(),
221        FontData::from_static(FONT_ICONS_REGULAR),
222    );
223    fonts.font_data.insert(
224        "fa_free_solid".to_owned(),
225        FontData::from_static(FONT_ICONS_SOLID),
226    );
227    fonts
228        .families
229        .entry(FontFamily::Name("fa_free".into()))
230        .or_default()
231        .extend(vec![
232            "fa_free_regular".to_owned(),
233            "fa_free_solid".to_owned(),
234            "fa_brands_regular".to_owned(),
235        ]);
236
237    // // Put my font first (highest priority) for proportional text:
238    // fonts
239    //     .families
240    //     .entry(egui::FontFamily::Proportional)
241    //     .or_default()
242    //     .insert(0, "my_font".to_owned());
243
244    // // Put my font as last fallback for monospace:
245    // fonts
246    //     .families
247    //     .entry(egui::FontFamily::Monospace)
248    //     .or_default()
249    //     .push("my_font".to_owned());
250
251    // Tell egui to use these fonts:
252    ctx.set_fonts(fonts);
253
254    // Redefine text_styles sizes
255    let mut style = (*ctx.style()).clone();
256    style.text_styles = [
257        (
258            TextStyle::Heading,
259            FontId::new(TEXT_SIZE_HEADING, FontFamily::Proportional),
260        ),
261        (
262            TextStyle::Body,
263            FontId::new(TEXT_SIZE_BODY, FontFamily::Proportional),
264        ),
265        (
266            TextStyle::Monospace,
267            FontId::new(TEXT_SIZE_MONOSPACE, FontFamily::Proportional),
268        ),
269        (
270            TextStyle::Button,
271            FontId::new(TEXT_SIZE_BUTTON2, FontFamily::Proportional),
272        ),
273        (
274            TextStyle::Small,
275            FontId::new(TEXT_SIZE_TOOLTIP, FontFamily::Proportional),
276        ),
277    ]
278    .into();
279    ctx.set_style(style);
280
281    let mut visuals = egui::Visuals::light();
282    visuals.override_text_color = Some(Color32::BLACK);
283    ctx.set_visuals(visuals);
284}
285
286pub fn set_fullscreen_scale(ctx: &egui::Context, scale: f32) {
287    static mut RESCALE_TIMER: Option<Arc<Mutex<Instant>>> = None;
288
289    let curr = ctx.pixels_per_point();
290    let size = ctx.input().screen_rect().size();
291    let mut scale = (size.x / SCREEN_SIZE.x).min(size.y / SCREEN_SIZE.y) * scale;
292    scale = f32_with_precision(scale, 6);
293
294    if (scale - 1.0).abs() > 1e-6 {
295        let now = Instant::now();
296        unsafe {
297            if RESCALE_TIMER.is_none() {
298                RESCALE_TIMER = Some(Arc::new(Mutex::new(Instant::now())));
299            }
300
301            let mut timer = RESCALE_TIMER.as_ref().unwrap().lock().unwrap();
302            if timer.elapsed() > Duration::from_millis(500) {
303                println!("Rescaling UI {curr}*{scale}={}", curr * scale);
304                *timer = now;
305            } else {
306                scale = 1.0;
307            }
308        }
309    } else {
310        scale = 1.0;
311    }
312
313    ctx.set_pixels_per_point(curr * scale);
314}
315
316pub mod text {
317    use super::*;
318    use eframe::egui::{Color32, RichText};
319
320    #[inline(always)]
321    pub fn heading(text: impl Into<String>) -> RichText {
322        RichText::new(text).heading()
323    }
324
325    #[inline(always)]
326    pub fn body(text: impl Into<String>) -> RichText {
327        RichText::new(text).size(TEXT_SIZE_BODY)
328    }
329
330    #[inline(always)]
331    pub fn inactive(text: impl Into<String>) -> RichText {
332        RichText::new(text)
333            .size(TEXT_SIZE_BODY)
334            .color(Color32::LIGHT_GRAY)
335    }
336
337    #[inline(always)]
338    pub fn button1(text: impl Into<String>) -> RichText {
339        RichText::new(text).size(TEXT_SIZE_BUTTON1)
340    }
341
342    #[inline(always)]
343    pub fn button2(text: impl Into<String>) -> RichText {
344        RichText::new(text).size(TEXT_SIZE_BUTTON2)
345    }
346
347    #[inline(always)]
348    pub fn tooltip(text: impl Into<String>) -> RichText {
349        RichText::new(text).size(TEXT_SIZE_TOOLTIP)
350    }
351
352    #[inline(always)]
353    pub fn icon(text: impl Into<String>) -> RichText {
354        RichText::new(text).size(TEXT_SIZE_ICON)
355    }
356}