egui_charts/ui/top_toolbar/components/settings_menu/
menu.rs1use super::config::ChartSettings;
7use crate::config::ChartConfig;
8use crate::localization as l10n;
9use crate::model::ChartType;
10use crate::scales::PriceScaleMode;
11use crate::theme::ThemeExt;
12use crate::tokens::DESIGN_TOKENS;
13use crate::ui_kit::ext::UiExt;
14use egui::{Align2, Context, Id, Pos2, Rect, Response, Ui};
15
16pub struct SettingsMenu {
22 pub settings: ChartSettings,
24 pub is_open: bool,
26 btn_rect: Option<Rect>,
28}
29
30impl SettingsMenu {
31 pub fn new() -> Self {
33 Self {
34 settings: ChartSettings::default(),
35 is_open: false,
36 btn_rect: None,
37 }
38 }
39
40 pub fn settings(&self) -> &ChartSettings {
42 &self.settings
43 }
44
45 pub fn settings_mut(&mut self) -> &mut ChartSettings {
47 &mut self.settings
48 }
49
50 pub fn apply_to_config(&self, mut config: ChartConfig) -> ChartConfig {
52 config.show_grid = self.settings.show_horizontal_grid || self.settings.show_vertical_grid;
53 config.show_horizontal_grid = self.settings.show_horizontal_grid;
54 config.show_vertical_grid = self.settings.show_vertical_grid;
55 config.show_right_axis = self.settings.show_right_axis;
56 config.show_left_axis = self.settings.show_left_axis;
57 config.show_symbol_labels = self.settings.show_symbol_labels;
58 config.show_symbol_last_val = self.settings.show_symbol_last_val;
59 config.show_symbol_prev_close = self.settings.show_symbol_prev_close;
60 config.show_indicator_labels = self.settings.show_indicator_labels;
61 config.show_indicator_last_val = self.settings.show_indicator_last_val;
62 config.show_countdown = self.settings.show_countdown;
63 config.lock_price_to_bar_ratio = self.settings.lock_scale;
64 config.scale_price_chart_only = self.settings.scale_price_chart_only;
65 config
66 }
67
68 pub fn chart_type(&self) -> ChartType {
70 self.settings.chart_type
71 }
72
73 pub fn show_btn(&mut self, ui: &mut Ui, chart_rect: Rect) -> Response {
75 let btn_size = egui::vec2(32.0, 32.0);
76 let btn_pos = Pos2::new(
77 chart_rect.max.x - btn_size.x - 10.0,
78 chart_rect.max.y - btn_size.y - 10.0,
79 );
80
81 let btn_rect = Rect::from_min_size(btn_pos, btn_size);
82 let btn_id = ui.scope_id().with("settings_btn");
83
84 let response = ui.interact(btn_rect, btn_id, egui::Sense::click());
85 let translated_label = l10n::text(ui.ctx(), "Chart settings");
86 response.widget_info(|| {
87 egui::WidgetInfo::labeled(egui::Role::Button, response.enabled(), &translated_label)
88 });
89 crate::styles::focus::draw_focus_ring(ui, &response, ui.active_theme().radius.md);
90
91 let visuals = ui.style().interact(&response);
92 let bg_color = if response.hovered() {
93 visuals.bg_fill
94 } else {
95 ui.visuals().widgets.inactive.bg_fill
96 };
97
98 ui.painter()
99 .rect_filled(btn_rect, ui.active_theme().radius.md, bg_color);
100 ui.painter().text(
101 btn_rect.center(),
102 Align2::CENTER_CENTER,
103 l10n::text(ui.ctx(), "S"),
104 egui::FontId::proportional(ui.active_theme().text.dialog_title),
105 visuals.text_color(),
106 );
107
108 if response.clicked() {
109 self.is_open = !self.is_open;
110 }
111
112 self.btn_rect = Some(btn_rect);
113 response
114 }
115
116 pub fn show_menu(&mut self, ctx: &Context) {
118 if !self.is_open {
119 return;
120 }
121 let Some(btn_rect) = self.btn_rect else {
122 return;
123 };
124
125 let popup_id = Id::unique("settings_popup");
126 let popup_pos = Pos2::new(btn_rect.min.x - 200.0, btn_rect.min.y - 400.0);
127
128 egui::Area::new(popup_id)
129 .fixed_pos(popup_pos)
130 .order(egui::Order::Foreground)
131 .show(ctx, |ui| {
132 egui::Frame::popup(ui.style()).show(ui, |ui| {
133 ui.set_min_width(180.0);
134 ui.set_max_width(220.0);
135 self.draw_menu_sections(ui);
136 });
137 });
138
139 self.handle_click_outside(ctx, btn_rect, popup_pos);
140 }
141
142 fn draw_menu_sections(&mut self, ui: &mut egui::Ui) {
143 self.draw_chart_type_section(ui);
144 self.draw_appearance_section(ui);
145 self.draw_scale_section(ui);
146 self.draw_labels_section(ui);
147
148 ui.separator();
149 if ui
150 .button(l10n::text(ui.ctx(), "Scales Properties..."))
151 .clicked()
152 {
153 self.is_open = false;
154 }
155 }
156
157 fn draw_chart_type_section(&mut self, ui: &mut egui::Ui) {
158 ui.strong_label("Chart Type");
159 ui.separator();
160
161 for chart_type in &[
162 ChartType::Candles,
163 ChartType::Bars,
164 ChartType::Line,
165 ChartType::Area,
166 ] {
167 if ui
168 .selectable_label(
169 self.settings.chart_type == *chart_type,
170 l10n::text(ui.ctx(), chart_type.as_str()),
171 )
172 .clicked()
173 {
174 self.settings.chart_type = *chart_type;
175 }
176 }
177 ui.separator();
178 }
179
180 fn draw_appearance_section(&mut self, ui: &mut egui::Ui) {
181 ui.strong_label("Appearance");
182 ui.separator();
183 ui.checkbox(
184 &mut self.settings.show_horizontal_grid,
185 l10n::text(ui.ctx(), "Horizontal Grid"),
186 );
187 ui.checkbox(
188 &mut self.settings.show_vertical_grid,
189 l10n::text(ui.ctx(), "Vertical Grid"),
190 );
191 ui.separator();
192 }
193
194 fn draw_scale_section(&mut self, ui: &mut egui::Ui) {
195 ui.strong_label("Scale");
196 ui.separator();
197
198 if ui.button(l10n::text(ui.ctx(), "Reset Scale")).clicked() {
199 self.is_open = false;
200 }
201
202 ui.checkbox(
203 &mut self.settings.show_left_axis,
204 l10n::text(ui.ctx(), "Left Axis"),
205 );
206 ui.checkbox(
207 &mut self.settings.show_right_axis,
208 l10n::text(ui.ctx(), "Right Axis"),
209 );
210
211 if ui
212 .selectable_label(
213 self.settings.scale_mode == PriceScaleMode::Normal,
214 l10n::text(ui.ctx(), "Auto Scale"),
215 )
216 .clicked()
217 {
218 self.settings.scale_mode = PriceScaleMode::Normal;
219 }
220
221 ui.checkbox(
222 &mut self.settings.lock_scale,
223 l10n::text(ui.ctx(), "Lock price to bar ratio"),
224 );
225 ui.checkbox(
226 &mut self.settings.scale_price_chart_only,
227 l10n::text(ui.ctx(), "Scale Price Chart Only"),
228 );
229 ui.separator();
230
231 if ui
232 .selectable_label(
233 self.settings.scale_mode == PriceScaleMode::Percentage,
234 l10n::text(ui.ctx(), "Percentage"),
235 )
236 .clicked()
237 {
238 self.settings.scale_mode = PriceScaleMode::Percentage;
239 }
240 if ui
241 .selectable_label(
242 self.settings.scale_mode == PriceScaleMode::Logarithmic,
243 l10n::text(ui.ctx(), "Log Scale"),
244 )
245 .clicked()
246 {
247 self.settings.scale_mode = PriceScaleMode::Logarithmic;
248 }
249 ui.separator();
250 }
251
252 fn draw_labels_section(&mut self, ui: &mut egui::Ui) {
253 ui.strong_label("Labels");
254 ui.separator();
255
256 ui.checkbox(
257 &mut self.settings.show_symbol_labels,
258 l10n::text(ui.ctx(), "Symbol Labels"),
259 );
260 ui.checkbox(
261 &mut self.settings.show_symbol_last_val,
262 l10n::text(ui.ctx(), "Symbol Last Value"),
263 );
264 ui.checkbox(
265 &mut self.settings.show_symbol_prev_close,
266 l10n::text(ui.ctx(), "Symbol Prev. Close Value"),
267 );
268 ui.separator();
269
270 ui.checkbox(
271 &mut self.settings.show_indicator_labels,
272 l10n::text(ui.ctx(), "Indicator Labels"),
273 );
274 ui.checkbox(
275 &mut self.settings.show_indicator_last_val,
276 l10n::text(ui.ctx(), "Indicator Last Value"),
277 );
278 ui.separator();
279
280 ui.checkbox(
281 &mut self.settings.show_countdown,
282 l10n::text(ui.ctx(), "Countdown"),
283 );
284 ui.checkbox(
285 &mut self.settings.no_overlapping_labels,
286 l10n::text(ui.ctx(), "No Overlapping Labels"),
287 );
288 }
289
290 fn handle_click_outside(&mut self, ctx: &Context, btn_rect: Rect, popup_pos: Pos2) {
291 if ctx.input(|i| i.pointer.any_released())
292 && let Some(pos) = ctx.input(|i| i.pointer.interact_pos())
293 {
294 let menu_rect = Rect::from_min_size(
295 popup_pos,
296 egui::vec2(
297 DESIGN_TOKENS.sizing.dialog.menu_settings_width,
298 DESIGN_TOKENS.sizing.dialog.menu_settings_height,
299 ),
300 );
301 if !menu_rect.contains(pos) && !btn_rect.contains(pos) {
302 self.is_open = false;
303 }
304 }
305 }
306}
307
308impl Default for SettingsMenu {
309 fn default() -> Self {
310 Self::new()
311 }
312}