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 let mut fonts = FontDefinitions::default();
213
214 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 ctx.set_fonts(fonts);
253
254 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}