1use egui::Color32;
4
5use super::{ChartColors, Radii, Spacing, TextSizes, Theme, UiColors};
6
7const fn rgb(r: u8, g: u8, b: u8) -> Color32 {
8 Color32::from_rgb(r, g, b)
9}
10
11pub fn presets() -> [(&'static str, Theme); 3] {
13 [
14 ("dark", Theme::dark()),
15 ("light", Theme::light()),
16 ("high-contrast", Theme::high_contrast()),
17 ]
18}
19
20impl Theme {
21 pub fn dark() -> Self {
23 let accent = rgb(41, 98, 255);
24 Self {
25 dark: true,
26 ui: UiColors {
27 panel_bg: Color32::BLACK,
28 panel_bg_secondary: rgb(55, 59, 70),
29 panel_bg_floating: rgb(42, 46, 57),
30 modal_overlay: Color32::from_black_alpha(180),
31 text: rgb(219, 219, 219),
32 text_secondary: rgb(184, 184, 184),
33 text_muted: rgb(165, 165, 165),
34 text_disabled: rgb(87, 87, 87),
35 border: rgb(46, 46, 46),
36 border_control: rgb(144, 144, 144),
37 btn_bg_hover: rgb(32, 32, 32),
38 btn_bg_active: rgb(48, 48, 48),
39 selection_bg: accent,
40 selection_text: Color32::WHITE,
41 focus: Color32::WHITE,
42 accent,
43 accent_hover: rgb(35, 88, 230),
44 accent_active: rgb(31, 78, 205),
45 accent_text: Color32::WHITE,
46 danger_bg: rgb(211, 47, 47),
47 danger_bg_hover: rgb(198, 40, 40),
48 danger_bg_active: rgb(183, 28, 28),
49 danger_text: Color32::WHITE,
50 success: rgb(88, 187, 92),
51 take_profit: rgb(0, 166, 147),
52 stop_loss: rgb(255, 166, 0),
53 warning: rgb(255, 152, 0),
54 error: rgb(255, 130, 125),
55 info: rgb(100, 181, 246),
56 },
57 chart: ChartColors {
58 bg: Color32::BLACK,
59 bg_axis: Color32::BLACK,
60 bg_tooltip: rgb(55, 59, 70),
61 grid_line: rgb(34, 34, 34),
62 grid_line_major: Color32::from_rgba_unmultiplied(255, 255, 255, 18),
63 axis_text: Color32::WHITE,
64 axis_text_secondary: rgb(184, 184, 184),
65 crosshair_line: rgb(119, 119, 119),
66 crosshair_label_bg: rgb(55, 59, 70),
67 crosshair_label_text: rgb(209, 212, 220),
68 candle_up: rgb(8, 153, 129),
69 candle_down: rgb(242, 54, 69),
70 order_notable: rgb(255, 193, 7),
71 order_large: rgb(179, 136, 255),
72 volume_alpha: 128,
73 },
74 text: TextSizes::default(),
75 spacing: Spacing::default(),
76 radius: Radii::default(),
77 }
78 }
79
80 pub fn light() -> Self {
82 let dark = Self::dark();
83 Self {
84 dark: false,
85 ui: UiColors {
86 panel_bg: Color32::WHITE,
87 panel_bg_secondary: rgb(235, 235, 235),
88 panel_bg_floating: Color32::WHITE,
89 modal_overlay: Color32::from_black_alpha(96),
90 text: Color32::BLACK,
91 text_secondary: Color32::BLACK,
92 text_muted: rgb(105, 105, 105),
93 text_disabled: rgb(126, 126, 126),
94 border: rgb(235, 235, 235),
95 border_control: rgb(130, 130, 130),
96 btn_bg_hover: rgb(234, 234, 234),
97 btn_bg_active: rgb(214, 214, 214),
98 accent: rgb(41, 98, 255),
99 selection_bg: rgb(219, 229, 255),
100 selection_text: dark.ui.accent_active,
101 focus: dark.ui.accent_active,
102 success: rgb(40, 115, 45),
103 take_profit: rgb(0, 166, 147),
104 stop_loss: rgb(255, 166, 0),
105 warning: rgb(160, 75, 8),
106 error: rgb(198, 40, 40),
107 info: rgb(2, 119, 189),
108 ..dark.ui
109 },
110 chart: ChartColors {
111 bg: Color32::WHITE,
112 bg_axis: Color32::WHITE,
113 bg_tooltip: Color32::WHITE,
114 grid_line: rgb(229, 229, 229),
115 grid_line_major: rgb(229, 229, 229),
116 axis_text: Color32::BLACK,
117 axis_text_secondary: Color32::BLACK,
118 crosshair_line: rgb(116, 116, 116),
119 crosshair_label_bg: Color32::BLACK,
120 crosshair_label_text: rgb(219, 219, 219),
121 candle_up: rgb(8, 153, 129),
122 candle_down: rgb(242, 54, 69),
123 order_notable: rgb(180, 130, 0),
124 order_large: rgb(146, 74, 250),
125 volume_alpha: 128,
126 },
127 ..dark
128 }
129 }
130
131 pub fn high_contrast() -> Self {
136 let dark = Self::dark();
137 Self {
138 ui: UiColors {
139 panel_bg: Color32::BLACK,
140 panel_bg_secondary: rgb(18, 18, 18),
141 panel_bg_floating: rgb(10, 10, 10),
142 modal_overlay: Color32::TRANSPARENT,
143 text: Color32::WHITE,
144 text_secondary: rgb(235, 235, 235),
145 text_muted: rgb(200, 200, 200),
146 text_disabled: rgb(140, 140, 140),
147 border: rgb(150, 150, 150),
148 border_control: rgb(200, 200, 200),
149 btn_bg_hover: rgb(45, 45, 45),
150 btn_bg_active: rgb(70, 70, 70),
151 selection_bg: rgb(41, 71, 102),
154 selection_text: Color32::WHITE,
155 focus: rgb(255, 214, 0),
156 accent: rgb(102, 178, 255),
158 accent_hover: rgb(140, 198, 255),
159 accent_active: rgb(179, 218, 255),
160 accent_text: Color32::BLACK,
161 danger_bg: rgb(255, 120, 120),
162 danger_bg_hover: rgb(255, 154, 154),
163 danger_bg_active: rgb(255, 188, 188),
164 danger_text: Color32::BLACK,
165 success: rgb(90, 220, 120),
166 take_profit: rgb(0, 166, 147),
167 stop_loss: rgb(255, 166, 0),
168 warning: rgb(255, 200, 60),
169 error: rgb(255, 170, 170),
170 info: rgb(120, 200, 255),
171 },
172 chart: ChartColors {
173 bg: Color32::BLACK,
174 bg_axis: Color32::BLACK,
175 bg_tooltip: rgb(10, 10, 10),
176 grid_line: Color32::from_white_alpha(40),
177 grid_line_major: Color32::from_white_alpha(64),
178 axis_text: Color32::WHITE,
179 axis_text_secondary: rgb(200, 200, 200),
180 crosshair_line: Color32::WHITE,
181 crosshair_label_bg: rgb(45, 45, 45),
182 crosshair_label_text: Color32::WHITE,
183 candle_up: rgb(0, 220, 130),
184 candle_down: rgb(255, 95, 95),
185 order_notable: dark.chart.order_notable,
186 order_large: dark.chart.order_large,
187 volume_alpha: 128,
188 },
189 ..dark
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::theme::contrast;
198
199 #[test]
203 fn order_tiers_read_on_every_preset_and_differ_from_the_candles() {
204 let distance = |a: Color32, b: Color32| {
205 [(a.r(), b.r()), (a.g(), b.g()), (a.b(), b.b())]
206 .map(|(a, b)| (i32::from(a) - i32::from(b)).pow(2))
207 .iter()
208 .sum::<i32>()
209 };
210 for (name, theme) in presets() {
211 let chart = theme.chart;
212 for (tier, color, other) in [
213 ("notable", chart.order_notable, chart.order_large),
214 ("large", chart.order_large, chart.order_notable),
215 ] {
216 for (surface, bg) in [("chart", chart.bg), ("panel", theme.ui.panel_bg)] {
217 let ratio = contrast(color, bg);
218 assert!(
219 ratio >= 3.0,
220 "{name} {tier} {color:?} on the {surface} background: {ratio:.2}:1"
221 );
222 }
223 for (role, near) in [
224 ("up candle", chart.candle_up),
225 ("down candle", chart.candle_down),
226 ("other tier", other),
227 ] {
228 let squared = distance(color, near);
229 assert!(
230 squared >= 100 * 100,
231 "{name} {tier} {color:?} is {:.1} from the {role} {near:?}",
232 f64::from(squared).sqrt()
233 );
234 }
235 }
236 }
237 }
238}