egui_charts/ui/context_menu/
series.rs1use crate::chart::series::SeriesId;
6use crate::config::KeyboardAction;
7use crate::icons::icons as embedded_icons;
8use crate::localization as l10n;
9use crate::theme::Theme;
10use crate::ui::Control;
11use crate::ui::context_menu::{MenuEntry, MenuItem, POINTER_GAP, format_price, show_menu};
12
13use egui::{Context, Id, Pos2};
14use std::collections::HashSet;
15
16#[derive(Debug, Clone, PartialEq)]
22#[non_exhaustive]
23pub enum SeriesContextMenuAction {
24 None,
26 AddAlert,
28 AddIndicator,
30 CopyPrice,
32 OpenSettings,
34}
35
36pub struct SeriesContextMenu {
42 is_open: bool,
43 position: Pos2,
44 pub series_id: Option<SeriesId>,
46 pub symbol: String,
48 pub click_price: f64,
50 pub last_action: SeriesContextMenuAction,
52 just_opened: bool,
54 pub(crate) hidden: HashSet<Control>,
57}
58
59impl Default for SeriesContextMenu {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl SeriesContextMenu {
66 pub fn new() -> Self {
68 Self {
69 is_open: false,
70 position: Pos2::ZERO,
71 series_id: None,
72 symbol: String::new(),
73 click_price: 0.0,
74 last_action: SeriesContextMenuAction::None,
75 just_opened: false,
76 hidden: HashSet::new(),
77 }
78 }
79
80 pub fn open(&mut self, pos: Pos2, series_id: SeriesId) {
82 self.is_open = true;
83 self.position = pos;
84 self.series_id = Some(series_id);
85 self.last_action = SeriesContextMenuAction::None;
86 self.just_opened = true;
87 }
88
89 pub fn set_context(&mut self, symbol: impl Into<String>, price: f64) {
91 self.symbol = symbol.into();
92 self.click_price = price;
93 }
94
95 pub fn close(&mut self) {
97 self.is_open = false;
98 }
99
100 pub fn is_open(&self) -> bool {
102 self.is_open
103 }
104
105 pub fn take_action(&mut self) -> SeriesContextMenuAction {
107 std::mem::replace(&mut self.last_action, SeriesContextMenuAction::None)
108 }
109
110 pub(crate) fn build_menu_items(
112 &self,
113 ctx: &Context,
114 shortcuts: &crate::widget::ShortcutMap,
115 ) -> Vec<MenuEntry<SeriesContextMenuAction>> {
116 let price_str = format_price(self.click_price);
117 let symbol = &self.symbol;
118 let has_symbol = !symbol.is_empty();
119
120 let mut items = Vec::new();
121 if has_symbol && !self.hidden.contains(&Control::Alerts) {
123 items.push(MenuEntry::Item(
124 MenuItem::new(
125 l10n::format_text(
126 ctx,
127 "Add alert on {name} at {price}…",
128 &[("name", symbol.as_str()), ("price", price_str.as_str())],
129 ),
130 SeriesContextMenuAction::AddAlert,
131 )
132 .with_icon(&embedded_icons::ALERTS)
133 .with_shortcut(shortcuts.shortcut(KeyboardAction::CreateAlert)),
134 ));
135 }
136 items.extend([
137 MenuEntry::Item(
138 MenuItem::new(
139 if has_symbol {
140 l10n::format_text(
141 ctx,
142 "Add indicator on {name}…",
143 &[("name", symbol.as_str())],
144 )
145 } else {
146 l10n::text(ctx, "Add indicator…")
147 },
148 SeriesContextMenuAction::AddIndicator,
149 )
150 .with_icon(&embedded_icons::INDICATORS),
151 ),
152 MenuEntry::Separator,
153 MenuEntry::Item(MenuItem::new(
154 l10n::format_text(ctx, "Copy price {price}", &[("price", price_str.as_str())]),
155 SeriesContextMenuAction::CopyPrice,
156 )),
157 MenuEntry::Separator,
158 MenuEntry::Item(
159 MenuItem::new("Settings…", SeriesContextMenuAction::OpenSettings)
160 .with_icon(&embedded_icons::SETTINGS_GEAR),
161 ),
162 ]);
163 items.retain(|entry| {
164 let control = match entry {
165 MenuEntry::Item(item) => match item.action {
166 SeriesContextMenuAction::AddIndicator => Control::Indicators,
167 SeriesContextMenuAction::CopyPrice => Control::CopyPrice,
168 SeriesContextMenuAction::OpenSettings => Control::Settings,
169 _ => return true,
170 },
171 MenuEntry::Submenu { .. } | MenuEntry::Separator => return true,
172 };
173 !self.hidden.contains(&control)
174 });
175 crate::ui::context_menu::trim_separators(&mut items);
176 items
177 }
178
179 pub fn show(&mut self, ctx: &Context, theme: &Theme) {
181 self.show_with_shortcuts(ctx, theme, &crate::widget::ShortcutMap::default());
182 }
183
184 pub fn show_with_shortcuts(
186 &mut self,
187 ctx: &Context,
188 theme: &Theme,
189 shortcuts: &crate::widget::ShortcutMap,
190 ) {
191 if !self.is_open {
192 return;
193 }
194 let entries = self.build_menu_items(ctx, shortcuts);
195 if entries.is_empty() {
196 self.close();
197 return;
198 }
199 let just_opened = std::mem::take(&mut self.just_opened);
200 let id = Id::unique("series_context_menu");
201 if let Some(action) = show_menu(
202 ctx,
203 id,
204 theme,
205 self.position,
206 POINTER_GAP,
207 &entries,
208 just_opened,
209 ) {
210 self.last_action = action.unwrap_or(SeriesContextMenuAction::None);
211 self.close();
212 }
213 }
214}