1use crate::model::LineStyle;
4use crate::tokens::DESIGN_TOKENS;
5use egui::Color32;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
10pub enum LineType {
11 #[default]
13 Simple,
14 Curved,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
21pub enum LastPriceAnimation {
22 #[default]
24 Disabled,
25 Continuous,
27 OnDataUpdate,
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33#[serde(default)]
34pub struct LinePlotStyle {
35 pub color: Color32,
37 pub width: f32,
39 pub style: LineStyle,
41 pub line_type: LineType,
44}
45
46impl Default for LinePlotStyle {
47 fn default() -> Self {
48 Self {
49 color: DESIGN_TOKENS.semantic.brand.accent,
50 width: DESIGN_TOKENS.stroke.thick,
51 style: LineStyle::Solid,
52 line_type: LineType::Simple,
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59#[serde(default)]
60pub struct AreaStyle {
61 pub line: LinePlotStyle,
63 pub marker_radius: f32,
66 pub top_fill: Color32,
68 pub bottom_fill: Color32,
70 pub relative_gradient: bool,
73 pub invert_filled_area: bool,
75}
76
77impl Default for AreaStyle {
78 fn default() -> Self {
79 let color = DESIGN_TOKENS.semantic.brand.accent;
80 Self {
81 line: LinePlotStyle::default(),
82 marker_radius: 0.0,
83 top_fill: Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), 115),
84 bottom_fill: Color32::TRANSPARENT,
85 relative_gradient: true,
86 invert_filled_area: false,
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93#[serde(default)]
94pub struct BaselineStyle {
95 pub above: LinePlotStyle,
97 pub below: LinePlotStyle,
99 pub marker_radius: f32,
102 pub above_fill: Color32,
104 pub below_fill: Color32,
106 pub above_fill_at_base: Option<Color32>,
109 pub below_fill_at_base: Option<Color32>,
111}
112
113impl Default for BaselineStyle {
114 fn default() -> Self {
115 let up = DESIGN_TOKENS.semantic.extended.bullish;
116 let down = DESIGN_TOKENS.semantic.extended.bearish;
117 Self {
118 above: LinePlotStyle {
119 color: up,
120 ..Default::default()
121 },
122 below: LinePlotStyle {
123 color: down,
124 ..Default::default()
125 },
126 marker_radius: 0.0,
127 above_fill: Color32::from_rgba_unmultiplied(up.r(), up.g(), up.b(), 30),
128 below_fill: Color32::from_rgba_unmultiplied(down.r(), down.g(), down.b(), 30),
129 above_fill_at_base: None,
130 below_fill_at_base: None,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
138#[serde(default)]
139pub struct HlcAreaStyle {
140 pub fill_opacity: f32,
143 pub line_width: f32,
145}
146
147impl Default for HlcAreaStyle {
148 fn default() -> Self {
149 Self {
150 fill_opacity: 30.0 / 255.0,
151 line_width: DESIGN_TOKENS.stroke.thick,
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158#[serde(default)]
159pub struct BarsStyle {
160 pub open_visible: bool,
162 pub thin_bars: bool,
165}
166
167impl Default for BarsStyle {
168 fn default() -> Self {
169 Self {
170 open_visible: true,
171 thin_bars: true,
172 }
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(default)]
180pub struct CandlesStyle {
181 pub body_visible: bool,
183 pub wick_visible: bool,
185 pub color_by_previous_close: bool,
188}
189
190impl Default for CandlesStyle {
191 fn default() -> Self {
192 Self {
193 body_visible: true,
194 wick_visible: true,
195 color_by_previous_close: false,
196 }
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
203#[serde(default)]
204pub struct KagiStyle {
205 pub thick_width: f32,
207 pub thin_width: f32,
209}
210
211impl Default for KagiStyle {
212 fn default() -> Self {
213 Self {
214 thick_width: 2.5,
215 thin_width: 1.5,
216 }
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223#[serde(default)]
224pub struct ChartTypeStyles {
225 pub line: LinePlotStyle,
227 pub step_line: LinePlotStyle,
229 pub line_with_markers: LinePlotStyle,
231 pub marker_radius: f32,
233 pub area: AreaStyle,
235 pub baseline: BaselineStyle,
237 pub hlc_area: HlcAreaStyle,
239 pub candles: CandlesStyle,
241 pub bars: BarsStyle,
243 pub kagi: KagiStyle,
245 pub crosshair_marker_radius: f32,
249 pub last_price_animation: LastPriceAnimation,
251}
252
253impl Default for ChartTypeStyles {
254 fn default() -> Self {
255 Self {
256 line: LinePlotStyle::default(),
257 step_line: LinePlotStyle::default(),
258 line_with_markers: LinePlotStyle::default(),
259 marker_radius: 3.0,
260 area: AreaStyle::default(),
261 baseline: BaselineStyle::default(),
262 hlc_area: HlcAreaStyle::default(),
263 candles: CandlesStyle::default(),
264 bars: BarsStyle::default(),
265 kagi: KagiStyle::default(),
266 crosshair_marker_radius: 0.0,
267 last_price_animation: LastPriceAnimation::Disabled,
268 }
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::model::BarColors;
276
277 #[test]
278 fn baseline_saved_colours_keep_legacy_flat_fills_and_gradient_endpoints() {
279 for gradient in [false, true] {
280 let mut saved = serde_json::json!({
281 "above_fill": [0, 200, 0, 200], "below_fill": [200, 0, 0, 200]
282 });
283 let mut point = serde_json::json!({
284 "top_fill": [0, 0, 160, 160], "bottom_fill": [160, 160, 0, 160]
285 });
286 if gradient {
287 saved["above_fill_at_base"] = serde_json::json!([0, 40, 0, 40]);
288 saved["below_fill_at_base"] = serde_json::json!([40, 0, 0, 40]);
289 point["top_fill_at_base"] = serde_json::json!([0, 0, 0, 0]);
290 point["bottom_fill_at_base"] = serde_json::json!([0, 0, 0, 0]);
291 }
292 let style: BaselineStyle = serde_json::from_value(saved.clone()).unwrap();
293 let own: BarColors = serde_json::from_value(point.clone()).unwrap();
294 let restored = serde_json::to_value(&style).unwrap();
295 let restored_point = serde_json::to_value(&own).unwrap();
296 for (original, restored) in [(&saved, &restored), (&point, &restored_point)] {
297 for (key, color) in original.as_object().unwrap() {
298 assert_eq!(&restored[key], color, "{key}");
299 }
300 }
301 for key in ["above_fill_at_base", "below_fill_at_base"] {
302 assert_eq!(restored[key], saved[key]);
303 }
304 for key in ["top_fill_at_base", "bottom_fill_at_base"] {
305 assert_eq!(restored_point[key], point[key]);
306 }
307 }
308 }
309}