egui_charts/model/
watchlist.rs1use egui::Color32;
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum WatchlistFlag {
10 Red,
12 Blue,
14 Green,
16 Orange,
18 Purple,
20 Cyan,
22 Pink,
24}
25
26impl WatchlistFlag {
27 pub const ALL: [Self; 7] = [
29 Self::Red,
30 Self::Blue,
31 Self::Green,
32 Self::Orange,
33 Self::Purple,
34 Self::Cyan,
35 Self::Pink,
36 ];
37
38 pub fn color(self) -> Color32 {
40 match self {
41 Self::Red => Color32::from_rgb(229, 57, 53),
42 Self::Blue => Color32::from_rgb(30, 136, 229),
43 Self::Green => Color32::from_rgb(67, 160, 71),
44 Self::Orange => Color32::from_rgb(251, 140, 0),
45 Self::Purple => Color32::from_rgb(142, 36, 170),
46 Self::Cyan => Color32::from_rgb(0, 172, 193),
47 Self::Pink => Color32::from_rgb(216, 27, 96),
48 }
49 }
50}
51
52#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
54pub enum WatchlistItem {
55 Symbol {
57 symbol: String,
59 flag: Option<WatchlistFlag>,
61 },
62 Section(String),
64}
65
66#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
68pub struct Watchlist {
69 pub name: String,
71 pub items: Vec<WatchlistItem>,
73}
74
75impl Watchlist {
76 pub fn new(name: impl Into<String>) -> Self {
78 Self {
79 name: name.into(),
80 items: Vec::new(),
81 }
82 }
83
84 pub fn duplicate<'a>(&self, taken: impl IntoIterator<Item = &'a str>) -> Self {
87 let taken: Vec<&str> = taken.into_iter().collect();
88 let base = format!("{} copy", self.name);
89 let name = std::iter::once(base.clone())
90 .chain((2..).map(|n| format!("{base} {n}")))
91 .find(|name| !taken.contains(&name.as_str()))
92 .expect("an unused name exists");
93 Self {
94 name,
95 items: self.items.clone(),
96 }
97 }
98
99 pub fn symbols(&self) -> impl Iterator<Item = &str> {
101 self.items.iter().filter_map(|item| match item {
102 WatchlistItem::Symbol { symbol, .. } => Some(symbol.as_str()),
103 WatchlistItem::Section(_) => None,
104 })
105 }
106
107 pub fn add_symbol(&mut self, symbol: &str) -> bool {
110 let symbol = symbol.trim();
111 if symbol.is_empty()
112 || symbol.contains([',', '\n', '\r'])
113 || symbol.starts_with("###")
114 || self.symbols().any(|s| s == symbol)
115 {
116 return false;
117 }
118 self.items.push(WatchlistItem::Symbol {
119 symbol: symbol.to_owned(),
120 flag: None,
121 });
122 true
123 }
124
125 pub fn to_text(&self) -> String {
129 let entries: Vec<String> = self
130 .items
131 .iter()
132 .map(|item| match item {
133 WatchlistItem::Symbol { symbol, .. } => symbol.clone(),
134 WatchlistItem::Section(title) => {
135 format!("###{}", title.replace([',', '\n', '\r'], " "))
136 }
137 })
138 .collect();
139 entries.join(",")
140 }
141
142 pub fn from_text(name: impl Into<String>, text: &str) -> Self {
146 let mut list = Self::new(name);
147 for entry in text.split([',', '\n', '\r']).map(str::trim) {
148 if let Some(title) = entry.strip_prefix("###") {
149 list.items
150 .push(WatchlistItem::Section(title.trim().to_owned()));
151 } else {
152 list.add_symbol(entry);
153 }
154 }
155 list
156 }
157}