1use super::DrawingToolType;
4use serde::{Deserialize, Serialize};
5use std::cell::Cell;
6use std::f32::consts::SQRT_2;
7
8pub(crate) const GANN_SQUARE_UNITS: f32 = 5.0;
10pub(crate) const GANN_MAX_ARC: f32 = GANN_SQUARE_UNITS * SQRT_2;
12
13const GREY: [u8; 4] = [128, 128, 128, 255];
14const ORANGE: [u8; 4] = [255, 152, 0, 255];
15const CYAN: [u8; 4] = [0, 188, 212, 255];
16const GREEN: [u8; 4] = [76, 175, 80, 255];
17const TEAL: [u8; 4] = [8, 153, 129, 255];
18const BLUE: [u8; 4] = [41, 98, 255, 255];
19const RED: [u8; 4] = [242, 54, 69, 255];
20const PINK: [u8; 4] = [233, 30, 99, 255];
21const PURPLE: [u8; 4] = [156, 39, 176, 255];
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
25pub enum GannFanDirection {
26 #[default]
28 Forward,
29 Reverse,
31 Both,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
38#[serde(from = "SavedGannLevel")]
39pub struct GannLevel {
40 pub value: f32,
42 pub visible: bool,
44 pub color: [u8; 4],
46}
47
48impl GannLevel {
49 pub fn new(value: f32, color: [u8; 4]) -> Self {
51 Self {
52 value,
53 visible: true,
54 color,
55 }
56 }
57}
58
59#[derive(Deserialize)]
62#[serde(untagged)]
63enum SavedGannLevel {
64 Level {
65 value: f32,
66 visible: bool,
67 color: [u8; 4],
68 },
69 Value(f32),
70}
71
72impl From<SavedGannLevel> for GannLevel {
73 fn from(saved: SavedGannLevel) -> Self {
74 match saved {
75 SavedGannLevel::Level {
76 value,
77 visible,
78 color,
79 } => Self {
80 value,
81 visible,
82 color,
83 },
84 SavedGannLevel::Value(value) => Self::new(value, GREY),
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96#[serde(default)]
97pub struct GannConfig {
98 #[serde(default = "left_out_price_levels")]
101 pub price_levels: Vec<GannLevel>,
102 #[serde(default = "left_out_time_levels")]
104 pub time_levels: Vec<GannLevel>,
105 pub show_grid: bool,
107 pub show_fan: bool,
109 #[serde(default = "left_out_fan_ratios")]
112 pub fan_ratios: Vec<GannLevel>,
113 pub fan_direction: GannFanDirection,
115 pub show_arcs: bool,
117 pub arc_ratios: Vec<GannLevel>,
120 pub show_background: bool,
123 pub background_opacity: u8,
125 pub show_angles: bool,
127}
128
129thread_local! {
130 static LEFT_OUT: Cell<[bool; 3]> = const { Cell::new([false; 3]) };
133}
134
135fn left_out<T>(field: usize, value: fn(GannConfig) -> T) -> T {
137 LEFT_OUT.with(|left_out| {
138 let mut fields = left_out.get();
139 fields[field] = true;
140 left_out.set(fields);
141 });
142 value(GannConfig::default())
143}
144
145fn left_out_price_levels() -> Vec<GannLevel> {
146 left_out(0, |config| config.price_levels)
147}
148
149fn left_out_time_levels() -> Vec<GannLevel> {
150 left_out(1, |config| config.time_levels)
151}
152
153fn left_out_fan_ratios() -> Vec<GannLevel> {
154 left_out(2, |config| config.fan_ratios)
155}
156
157fn levels(entries: &[(f32, [u8; 4])]) -> Vec<GannLevel> {
158 entries
159 .iter()
160 .map(|&(value, color)| GannLevel::new(value, color))
161 .collect()
162}
163
164impl Default for GannConfig {
165 fn default() -> Self {
169 let box_levels = levels(&[
170 (0.0, GREY),
171 (0.25, ORANGE),
172 (0.382, CYAN),
173 (0.5, GREEN),
174 (0.618, TEAL),
175 (0.75, BLUE),
176 (1.0, GREY),
177 ]);
178 let mut fan_ratios = levels(&[
179 (8.0, GREY),
180 (5.0, GREY),
181 (4.0, GREY),
182 (3.0, GREY),
183 (2.0, CYAN),
184 (1.0, GREEN),
185 (0.5, TEAL),
186 (1.0 / 3.0, GREY),
187 (0.25, GREY),
188 (0.2, GREY),
189 (0.125, GREY),
190 ]);
191 for line in &mut fan_ratios {
192 line.visible = matches!(line.value, 2.0 | 1.0 | 0.5);
193 }
194 Self {
195 price_levels: box_levels.clone(),
196 time_levels: box_levels,
197 show_grid: true,
198 show_fan: true,
199 fan_ratios,
200 fan_direction: GannFanDirection::default(),
201 show_arcs: true,
202 arc_ratios: [ORANGE, CYAN, GREEN, TEAL, BLUE]
203 .into_iter()
204 .zip(1..=5)
205 .flat_map(|(color, n)| {
206 let n = n as f32;
207 [
208 GannLevel::new(n, color),
209 GannLevel::new((n * n + 1.0).sqrt(), color),
210 ]
211 })
212 .collect(),
213 show_background: true,
214 background_opacity: 51,
215 show_angles: false,
216 }
217 }
218}
219
220impl GannConfig {
221 pub fn for_tool(tool: DrawingToolType) -> Self {
226 match tool {
227 DrawingToolType::GannSquare | DrawingToolType::GannFixed => {
228 let units = levels(&[
229 (0.0, GREY),
230 (1.0, ORANGE),
231 (2.0, CYAN),
232 (3.0, GREEN),
233 (4.0, TEAL),
234 (5.0, GREY),
235 ]);
236 Self {
237 price_levels: units.clone(),
238 time_levels: units,
239 ..Self::default()
240 }
241 }
242 DrawingToolType::GannFan => Self {
243 fan_ratios: levels(&[
244 (8.0, RED),
245 (4.0, PINK),
246 (3.0, PURPLE),
247 (2.0, BLUE),
248 (1.0, CYAN),
249 (0.5, TEAL),
250 (1.0 / 3.0, GREEN),
251 (0.25, TEAL),
252 (0.125, ORANGE),
253 ]),
254 ..Self::default()
255 },
256 _ => Self::default(),
257 }
258 }
259
260 pub(crate) fn forget_left_out() {
263 LEFT_OUT.with(|left_out| left_out.set([false; 3]));
264 }
265
266 pub(crate) fn fill_left_out(&mut self, tool: DrawingToolType) {
269 let [price_levels, time_levels, fan_ratios] = LEFT_OUT.with(|l| l.replace([false; 3]));
270 if !(price_levels || time_levels || fan_ratios) {
271 return;
272 }
273 let defaults = Self::for_tool(tool);
274 if price_levels {
275 self.price_levels = defaults.price_levels;
276 }
277 if time_levels {
278 self.time_levels = defaults.time_levels;
279 }
280 if fan_ratios {
281 self.fan_ratios = defaults.fan_ratios;
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
293 fn new_tools_start_from_their_defaults() {
294 let square = GannConfig::for_tool(DrawingToolType::GannSquare);
295 let visible: Vec<f32> = square
296 .fan_ratios
297 .iter()
298 .filter(|l| l.visible)
299 .map(|l| l.value)
300 .collect();
301 assert_eq!(visible, [2.0, 1.0, 0.5]);
302 assert_eq!(square.fan_ratios.len(), 11);
303 let radii = [
304 1.0, SQRT_2, 2.0, 2.236068, 3.0, 3.1622777, 4.0, 4.1231055, 5.0, 5.0990195,
305 ];
306 assert_eq!(square.arc_ratios.len(), radii.len());
307 for (arc, radius) in square.arc_ratios.iter().zip(radii) {
308 assert!((arc.value - radius).abs() < 1e-5, "{arc:?} != {radius}");
309 }
310 let fan = GannConfig::for_tool(DrawingToolType::GannFan);
311 assert_eq!(fan.fan_ratios.iter().filter(|l| l.visible).count(), 9);
312 }
313}