summaryrefslogtreecommitdiffstats
path: root/gui/src/theme.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gui/src/theme.rs211
1 files changed, 211 insertions, 0 deletions
diff --git a/gui/src/theme.rs b/gui/src/theme.rs
new file mode 100644
index 0000000..9270aa6
--- /dev/null
+++ b/gui/src/theme.rs
@@ -0,0 +1,211 @@
1//! Look and feel for the desktop frontend.
2//!
3//! The palette is derived from the terminal client's ANSI scheme so the two
4//! frontends read as the same product: near-black ground, low-contrast panels,
5//! cyan for focus, and the felt green reserved for the roulette table.
6//!
7//! egui's defaults are a debug tool's palette. Every colour, size and corner
8//! used by the app comes from this module; widgets never pick their own.
9
10use std::sync::Arc;
11
12use eframe::egui::style::WidgetVisuals;
13use eframe::egui::{
14 self, Color32, Context, CornerRadius, FontData, FontDefinitions, FontFamily, FontId, Margin,
15 RichText, Stroke, TextStyle, Theme, Visuals,
16};
17
18// ---------------------------------------------------------------------------
19// Colour tokens
20// ---------------------------------------------------------------------------
21
22/// Window ground. Everything else sits above this.
23pub const BG: Color32 = Color32::from_rgb(0x0a, 0x0e, 0x12);
24/// Cards, header, sidebar.
25pub const SURFACE: Color32 = Color32::from_rgb(0x11, 0x17, 0x1d);
26/// Inputs, pills, raised rows.
27pub const SURFACE_RAISED: Color32 = Color32::from_rgb(0x17, 0x1f, 0x27);
28/// Hover state.
29pub const SURFACE_HOVER: Color32 = Color32::from_rgb(0x1d, 0x27, 0x31);
30/// Hairlines and table borders.
31pub const LINE: Color32 = Color32::from_rgb(0x24, 0x2e, 0x38);
32/// Primary text.
33pub const TEXT: Color32 = Color32::from_rgb(0xd9, 0xe2, 0xea);
34/// Labels and secondary text.
35pub const TEXT_DIM: Color32 = Color32::from_rgb(0x8a, 0x98, 0xa6);
36/// Disabled text, captions.
37pub const TEXT_FAINT: Color32 = Color32::from_rgb(0x5b, 0x67, 0x73);
38/// Focus, cursor, selection.
39pub const ACCENT: Color32 = Color32::from_rgb(0x4f, 0xb8, 0xc9);
40/// Wins, "online", the spin button.
41pub const WIN: Color32 = Color32::from_rgb(0x4e, 0xc2, 0x7b);
42/// Losses and errors.
43pub const LOSS: Color32 = Color32::from_rgb(0xe0, 0x5a, 0x5a);
44/// Chips and pending bets.
45pub const GOLD: Color32 = Color32::from_rgb(0xe0, 0xb6, 0x4a);
46
47/// Roulette felt. Never used outside the table.
48pub const FELT: Color32 = Color32::from_rgb(0x0d, 0x5c, 0x39);
49pub const FELT_DARK: Color32 = Color32::from_rgb(0x09, 0x47, 0x2c);
50pub const FELT_LINE: Color32 = Color32::from_rgb(0x1f, 0x7d, 0x52);
51pub const FELT_TEXT: Color32 = Color32::from_rgb(0xec, 0xf7, 0xf0);
52pub const ROULETTE_RED: Color32 = Color32::from_rgb(0xb0, 0x31, 0x3a);
53pub const ROULETTE_BLACK: Color32 = Color32::from_rgb(0x15, 0x1b, 0x21);
54pub const ROULETTE_ZERO: Color32 = Color32::from_rgb(0x10, 0x6b, 0x45);
55
56// ---------------------------------------------------------------------------
57// Spacing, radius, type scale
58// ---------------------------------------------------------------------------
59
60pub const SPACE_1: f32 = 4.0;
61pub const SPACE_2: f32 = 8.0;
62pub const SPACE_3: f32 = 12.0;
63pub const SPACE_4: f32 = 16.0;
64pub const SPACE_5: f32 = 24.0;
65
66pub const RADIUS_SM: CornerRadius = CornerRadius::same(4);
67pub const RADIUS_MD: CornerRadius = CornerRadius::same(8);
68pub const RADIUS_LG: CornerRadius = CornerRadius::same(12);
69
70pub const FONT_BODY: f32 = 14.0;
71pub const FONT_SMALL: f32 = 12.0;
72pub const FONT_LABEL: f32 = 11.0;
73pub const FONT_TITLE: f32 = 24.0;
74pub const FONT_MONO: f32 = 16.0;
75
76// ---------------------------------------------------------------------------
77// Fonts
78// ---------------------------------------------------------------------------
79
80/// Open Sans, SIL OFL 1.1. See `assets/fonts/OFL-OpenSans.txt`.
81pub const FONT_SANS_BYTES: &[u8] = include_bytes!("../assets/fonts/OpenSans-Regular.ttf");
82/// Hack, MIT. See `assets/fonts/LICENSE-Hack.md`.
83pub const FONT_MONO_BYTES: &[u8] = include_bytes!("../assets/fonts/Hack-Regular.ttf");
84
85pub fn sans(size: f32) -> FontId {
86 FontId::new(size, FontFamily::Proportional)
87}
88
89pub fn mono(size: f32) -> FontId {
90 FontId::new(size, FontFamily::Monospace)
91}
92
93/// A small uppercase label, the terminal's dim section headers.
94pub fn label(text: &str) -> RichText {
95 RichText::new(text.to_uppercase())
96 .font(mono(FONT_LABEL))
97 .color(TEXT_DIM)
98}
99
100/// Group an amount in threes: 1234567 -> "1 234 567".
101pub fn group_digits(amount: i64) -> String {
102 let negative = amount < 0;
103 let digits = amount.unsigned_abs().to_string();
104 let mut grouped = String::with_capacity(digits.len() + digits.len() / 3);
105 for (index, character) in digits.chars().enumerate() {
106 if index > 0 && (digits.len() - index) % 3 == 0 {
107 grouped.push(' ');
108 }
109 grouped.push(character);
110 }
111 if negative {
112 format!("-{grouped}")
113 } else {
114 grouped
115 }
116}
117
118/// `1000` -> `"1 000 Kr"`.
119pub fn money(amount: i64) -> String {
120 format!("{} Kr", group_digits(amount))
121}
122
123// ---------------------------------------------------------------------------
124// Installation
125// ---------------------------------------------------------------------------
126
127/// Install the embedded fonts and the dark style. Call once, from the app
128/// constructor, before the first frame.
129pub fn install(ctx: &Context) {
130 let mut fonts = FontDefinitions::default();
131 fonts.font_data.insert(
132 "zagan-sans".to_owned(),
133 Arc::new(FontData::from_static(FONT_SANS_BYTES)),
134 );
135 fonts.font_data.insert(
136 "zagan-mono".to_owned(),
137 Arc::new(FontData::from_static(FONT_MONO_BYTES)),
138 );
139 fonts
140 .families
141 .get_mut(&FontFamily::Proportional)
142 .expect("proportional family exists")
143 .insert(0, "zagan-sans".to_owned());
144 fonts
145 .families
146 .get_mut(&FontFamily::Monospace)
147 .expect("monospace family exists")
148 .insert(0, "zagan-mono".to_owned());
149 ctx.set_fonts(fonts);
150
151 ctx.set_theme(Theme::Dark);
152 let mut style = (*ctx.global_style()).clone();
153 style.text_styles = [
154 (TextStyle::Heading, sans(FONT_TITLE)),
155 (TextStyle::Body, sans(FONT_BODY)),
156 (TextStyle::Button, sans(FONT_BODY)),
157 (TextStyle::Small, sans(FONT_SMALL)),
158 (TextStyle::Monospace, mono(FONT_MONO)),
159 ]
160 .into();
161 style.spacing.item_spacing = egui::vec2(SPACE_2, SPACE_2);
162 style.spacing.button_padding = egui::vec2(SPACE_3, SPACE_2);
163 style.spacing.interact_size = egui::vec2(36.0, 32.0);
164 style.spacing.window_margin = Margin::same(SPACE_3 as i8);
165 style.visuals = visuals();
166 ctx.set_global_style(style);
167}
168
169fn widget(
170 bg_fill: Color32,
171 bg_stroke: Color32,
172 fg: Color32,
173 corner_radius: CornerRadius,
174) -> WidgetVisuals {
175 WidgetVisuals {
176 bg_fill,
177 weak_bg_fill: bg_fill,
178 bg_stroke: Stroke::new(1.0, bg_stroke),
179 corner_radius,
180 fg_stroke: Stroke::new(1.0, fg),
181 expansion: 0.0,
182 }
183}
184
185fn visuals() -> Visuals {
186 let mut visuals = Visuals::dark();
187 visuals.override_text_color = Some(TEXT);
188 visuals.panel_fill = BG;
189 visuals.window_fill = SURFACE;
190 visuals.window_stroke = Stroke::new(1.0, LINE);
191 visuals.window_corner_radius = RADIUS_LG;
192 visuals.extreme_bg_color = BG;
193 visuals.faint_bg_color = SURFACE_RAISED;
194 visuals.code_bg_color = SURFACE_RAISED;
195 visuals.hyperlink_color = ACCENT;
196 visuals.warn_fg_color = GOLD;
197 visuals.error_fg_color = LOSS;
198 visuals.selection.bg_fill = ACCENT.gamma_multiply(0.30);
199 visuals.selection.stroke = Stroke::new(1.0, ACCENT);
200 visuals.widgets.noninteractive = widget(SURFACE, LINE, TEXT, RADIUS_MD);
201 visuals.widgets.inactive = widget(SURFACE_RAISED, LINE, TEXT, RADIUS_MD);
202 visuals.widgets.hovered = widget(SURFACE_HOVER, ACCENT.gamma_multiply(0.75), TEXT, RADIUS_MD);
203 visuals.widgets.active = widget(
204 ACCENT.gamma_multiply(0.25),
205 ACCENT,
206 Color32::WHITE,
207 RADIUS_MD,
208 );
209 visuals.widgets.open = visuals.widgets.active;
210 visuals
211}