1use egui::Key;
9use std::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
13pub struct KeyboardOptions {
14 pub enabled: bool,
16
17 pub pan_amount: f32,
19
20 pub zoom_step: f32,
22}
23
24impl Default for KeyboardOptions {
25 fn default() -> Self {
26 Self {
27 enabled: true,
28 pan_amount: 1.0,
29 zoom_step: 0.2,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[non_exhaustive]
37pub enum KeyboardAction {
38 PanLeftFast,
40 PanRightFast,
42 Undo,
44 Redo,
46 OpenQuickSearch,
48 OpenIntervalSearch,
50 GoToDate,
52 Snapshot,
54 InvertScale,
56 CopyDrawing,
58 PasteDrawing,
60 ToggleDrawings,
62 MaximizeChart,
64 CompleteDrawing,
66 DrawingTool(crate::config::DrawingToolType),
69
70 PanLeft,
73 PanRight,
75 PanUp,
77 PanDown,
79
80 ZoomIn,
83 ZoomOut,
85 PageUp,
87 PageDown,
89
90 ScrollToRealTime,
93 ScrollToFirstBar,
95 FitContent,
97 ResetZoom,
99 ResetChart,
101
102 ToggleAutoScale,
105 ToggleLogScale,
107 TogglePercentageScale,
109
110 ToggleCrosshair,
113 ToggleGrid,
115 ToggleVolume,
117
118 CycleChartType,
121
122 ToggleDrawingMode,
125 DeleteSelectedDrawing,
128 Cancel,
130
131 SetTimeframe1Min,
134 SetTimeframe5Min,
136 SetTimeframe15Min,
138 SetTimeframe30Min,
140 SetTimeframe1Hour,
142 SetTimeframe4Hour,
144 SetTimeframeDaily,
146 SetTimeframeWeekly,
148 SetTimeframeMonthly,
150
151 CreateAlert,
154 OpenIndicatorSearch,
156 OpenSymbolSearch,
158 SaveLayout,
160 OpenLayout,
162 OpenDataWindow,
164 ToggleSession,
166 ShowKeyboardHelp,
168
169 CycleChartFocus,
172 CycleChartFocusBack,
174}
175
176impl KeyboardAction {
177 pub fn from_key_press_with_modifiers(input: &egui::InputState) -> Option<Self> {
179 for event in &input.events {
180 if let egui::Event::Key {
181 key,
182 pressed: true,
183 modifiers,
184 ..
185 } = event
186 && let Some(action) = Self::from_key_press(*key, modifiers)
187 {
188 return Some(action);
189 }
190 }
191 None
192 }
193
194 pub fn from_key_press(key: Key, modifiers: &egui::Modifiers) -> Option<Self> {
197 Self::all_by_category()
198 .into_iter()
199 .flat_map(|(_, actions)| actions)
200 .find(|action| {
201 action.shortcuts().iter().any(|shortcut| {
202 shortcut.logical_key == key && modifiers.matches_exact(shortcut.modifiers)
203 })
204 })
205 }
206
207 pub fn desc(&self) -> &str {
209 self.label()
210 }
211
212 pub fn label(&self) -> &str {
214 match self {
215 Self::PanLeftFast => "Pan left by a page",
216 Self::PanRightFast => "Pan right by a page",
217 Self::Undo => "Undo the last edit",
218 Self::Redo => "Redo the last undone edit",
219 Self::OpenQuickSearch => "Open quick search",
220 Self::OpenIntervalSearch => "Open interval entry",
221 Self::GoToDate => "Go to a date",
222 Self::Snapshot => "Take a chart snapshot",
223 Self::InvertScale => "Invert the price scale",
224 Self::CopyDrawing => "Copy the selected drawing",
225 Self::PasteDrawing => "Paste a copied drawing",
226 Self::ToggleDrawings => "Hide or show drawings",
227 Self::MaximizeChart => "Maximize the active chart",
228 Self::CompleteDrawing => "Complete drawing",
229 Self::DrawingTool(tool) => tool.as_str(),
230 Self::PanLeft => "Pan left",
231 Self::PanRight => "Pan right",
232 Self::PanUp => "Pan up",
233 Self::PanDown => "Pan down",
234 Self::ZoomIn => "Zoom in",
235 Self::ZoomOut => "Zoom out",
236 Self::PageUp => "Zoom in more",
237 Self::PageDown => "Zoom out more",
238 Self::ScrollToRealTime => "Jump to latest",
239 Self::ScrollToFirstBar => "Jump to first bar",
240 Self::FitContent => "Fit to screen",
241 Self::ResetZoom => "Reset zoom",
242 Self::ResetChart => "Reset chart",
243 Self::ToggleAutoScale => "Toggle auto-scale",
244 Self::ToggleLogScale => "Toggle log scale",
245 Self::TogglePercentageScale => "Toggle percentage scale",
246 Self::ToggleCrosshair => "Toggle crosshair",
247 Self::ToggleGrid => "Toggle grid",
248 Self::ToggleVolume => "Toggle volume",
249 Self::CycleChartType => "Cycle chart type",
250 Self::ToggleDrawingMode => "Toggle drawing mode",
251 Self::DeleteSelectedDrawing => "Delete drawing or study",
252 Self::Cancel => "Cancel",
253 Self::SetTimeframe1Min => "1 minute",
254 Self::SetTimeframe5Min => "5 minutes",
255 Self::SetTimeframe15Min => "15 minutes",
256 Self::SetTimeframe30Min => "30 minutes",
257 Self::SetTimeframe1Hour => "1 hour",
258 Self::SetTimeframe4Hour => "4 hours",
259 Self::SetTimeframeDaily => "Daily",
260 Self::SetTimeframeWeekly => "Weekly",
261 Self::SetTimeframeMonthly => "Monthly",
262 Self::CreateAlert => "Create alert",
263 Self::OpenIndicatorSearch => "Open indicators",
264 Self::OpenSymbolSearch => "Symbol search",
265 Self::SaveLayout => "Save layout",
266 Self::OpenLayout => "Open layout",
267 Self::OpenDataWindow => "Data window",
268 Self::ToggleSession => "Switch session",
269 Self::ShowKeyboardHelp => "Keyboard shortcuts",
270 Self::CycleChartFocus => "Next chart",
271 Self::CycleChartFocusBack => "Previous chart",
272 }
273 }
274
275 pub fn shortcuts(&self) -> &'static [egui::KeyboardShortcut] {
278 use egui::{KeyboardShortcut, Modifiers};
279 const fn key(key: Key) -> KeyboardShortcut {
280 KeyboardShortcut::new(Modifiers::NONE, key)
281 }
282 match self {
283 Self::PanLeft => const { &[key(Key::ArrowLeft)] },
284 Self::PanRight => const { &[key(Key::ArrowRight)] },
285 Self::PanLeftFast => {
286 const { &[KeyboardShortcut::new(Modifiers::COMMAND, Key::ArrowLeft)] }
287 }
288 Self::PanRightFast => {
289 const { &[KeyboardShortcut::new(Modifiers::COMMAND, Key::ArrowRight)] }
290 }
291 Self::ZoomIn => {
292 const {
293 &[
294 key(Key::Plus),
295 key(Key::Equals),
296 KeyboardShortcut::new(Modifiers::COMMAND, Key::ArrowUp),
297 ]
298 }
299 }
300 Self::ZoomOut => {
301 const {
302 &[
303 key(Key::Minus),
304 KeyboardShortcut::new(Modifiers::COMMAND, Key::ArrowDown),
305 ]
306 }
307 }
308 Self::PageUp => const { &[key(Key::PageUp)] },
309 Self::PageDown => const { &[key(Key::PageDown)] },
310 Self::ScrollToRealTime => {
311 const {
312 &[
313 KeyboardShortcut::new(
314 Modifiers::ALT.plus(Modifiers::SHIFT),
315 Key::ArrowRight,
316 ),
317 key(Key::End),
318 ]
319 }
320 }
321 Self::ScrollToFirstBar => {
322 const {
323 &[
324 KeyboardShortcut::new(
325 Modifiers::ALT.plus(Modifiers::SHIFT),
326 Key::ArrowLeft,
327 ),
328 key(Key::Home),
329 ]
330 }
331 }
332
333 Self::ResetChart => const { &[KeyboardShortcut::new(Modifiers::ALT, Key::R)] },
334
335 Self::ToggleLogScale => const { &[KeyboardShortcut::new(Modifiers::ALT, Key::L)] },
336 Self::TogglePercentageScale => {
337 const { &[KeyboardShortcut::new(Modifiers::ALT, Key::P)] }
338 }
339 Self::InvertScale => const { &[KeyboardShortcut::new(Modifiers::ALT, Key::I)] },
340 Self::CreateAlert => const { &[KeyboardShortcut::new(Modifiers::ALT, Key::A)] },
341 Self::OpenIndicatorSearch => const { &[key(Key::Slash)] },
342 Self::OpenQuickSearch => const { &[KeyboardShortcut::new(Modifiers::COMMAND, Key::K)] },
343 Self::GoToDate => const { &[KeyboardShortcut::new(Modifiers::ALT, Key::G)] },
344 Self::Snapshot => const { &[KeyboardShortcut::new(Modifiers::ALT, Key::S)] },
345 Self::SaveLayout => const { &[KeyboardShortcut::new(Modifiers::COMMAND, Key::S)] },
346 Self::OpenLayout => const { &[key(Key::Period)] },
347 Self::OpenDataWindow => const { &[KeyboardShortcut::new(Modifiers::ALT, Key::D)] },
348 Self::ToggleSession => {
349 const {
350 &[KeyboardShortcut::new(
351 Modifiers::ALT.plus(Modifiers::SHIFT),
352 Key::E,
353 )]
354 }
355 }
356 Self::Undo => const { &[KeyboardShortcut::new(Modifiers::COMMAND, Key::Z)] },
357 Self::Redo => {
358 const {
359 &[
360 KeyboardShortcut::new(Modifiers::COMMAND, Key::Y),
361 KeyboardShortcut::new(Modifiers::COMMAND.plus(Modifiers::SHIFT), Key::Z),
362 ]
363 }
364 }
365 Self::CopyDrawing => const { &[KeyboardShortcut::new(Modifiers::COMMAND, Key::C)] },
366 Self::PasteDrawing => const { &[KeyboardShortcut::new(Modifiers::COMMAND, Key::V)] },
367 Self::ToggleDrawings => {
368 const {
369 &[KeyboardShortcut::new(
370 Modifiers::COMMAND.plus(Modifiers::ALT),
371 Key::H,
372 )]
373 }
374 }
375 Self::MaximizeChart => const { &[KeyboardShortcut::new(Modifiers::ALT, Key::Enter)] },
376 Self::CycleChartFocus => const { &[key(Key::Tab)] },
377 Self::CycleChartFocusBack => {
378 const { &[KeyboardShortcut::new(Modifiers::SHIFT, Key::Tab)] }
379 }
380 Self::DeleteSelectedDrawing => const { &[key(Key::Delete), key(Key::Backspace)] },
381 Self::Cancel => const { &[key(Key::Escape)] },
382 Self::CompleteDrawing => const { &[key(Key::Enter)] },
383 Self::ShowKeyboardHelp => {
384 const {
385 &[
386 KeyboardShortcut::new(Modifiers::COMMAND, Key::Slash),
387 key(Key::Questionmark),
388 key(Key::F1),
389 ]
390 }
391 }
392 Self::DrawingTool(crate::config::DrawingToolType::TrendLine) => {
393 const { &[KeyboardShortcut::new(Modifiers::ALT, Key::T)] }
394 }
395 Self::DrawingTool(crate::config::DrawingToolType::HorizontalLine) => {
396 const { &[KeyboardShortcut::new(Modifiers::ALT, Key::H)] }
397 }
398 Self::DrawingTool(crate::config::DrawingToolType::VerticalLine) => {
399 const { &[KeyboardShortcut::new(Modifiers::ALT, Key::V)] }
400 }
401 Self::DrawingTool(crate::config::DrawingToolType::CrossLine) => {
402 const { &[KeyboardShortcut::new(Modifiers::ALT, Key::C)] }
403 }
404 Self::DrawingTool(crate::config::DrawingToolType::HorizontalRay) => {
405 const { &[KeyboardShortcut::new(Modifiers::ALT, Key::J)] }
406 }
407 Self::DrawingTool(crate::config::DrawingToolType::FibonacciRetracement) => {
408 const { &[KeyboardShortcut::new(Modifiers::ALT, Key::F)] }
409 }
410 Self::DrawingTool(crate::config::DrawingToolType::Rect) => {
411 const {
412 &[KeyboardShortcut::new(
413 Modifiers::ALT.plus(Modifiers::SHIFT),
414 Key::R,
415 )]
416 }
417 }
418 _ => &[],
419 }
420 }
421
422 pub fn shortcut(&self) -> Option<egui::KeyboardShortcut> {
425 self.shortcuts().first().copied()
426 }
427
428 pub fn shortcut_key(&self) -> &'static str {
430 match self {
431 Self::PanLeft => "←",
432 Self::PanRight => "→",
433 Self::PanLeftFast => "Ctrl+Left",
434 Self::PanRightFast => "Ctrl+Right",
435 Self::ZoomIn => "+ / = / Ctrl+Up",
436 Self::ZoomOut => "- / Ctrl+Down",
437 Self::PageUp => "Page Up",
438 Self::PageDown => "Page Down",
439 Self::ScrollToRealTime => "Alt+Shift+Right / End",
440 Self::ScrollToFirstBar => "Alt+Shift+Left / Home",
441 Self::ResetChart => "Alt+R",
442 Self::ToggleLogScale => "Alt+L",
443 Self::TogglePercentageScale => "Alt+P",
444 Self::InvertScale => "Alt+I",
445 Self::Undo => "Ctrl+Z",
446 Self::Redo => "Ctrl+Y / Ctrl+Shift+Z",
447 Self::CopyDrawing => "Ctrl+C",
448 Self::PasteDrawing => "Ctrl+V",
449 Self::ToggleDrawings => "Ctrl+Alt+H",
450 Self::CreateAlert => "Alt+A",
451 Self::OpenQuickSearch => "Ctrl+K",
452 Self::OpenIndicatorSearch => "/",
453 Self::GoToDate => "Alt+G",
454 Self::Snapshot => "Alt+S",
455 Self::SaveLayout => "Ctrl+S",
456 Self::OpenLayout => ".",
457 Self::OpenDataWindow => "Alt+D",
458 Self::ToggleSession => "Alt+Shift+E",
459 Self::MaximizeChart => "Alt+Enter",
460 Self::DeleteSelectedDrawing => "Del / Backspace",
461 Self::Cancel => "Esc",
462 Self::CompleteDrawing => "Enter",
463 Self::ShowKeyboardHelp => "Ctrl+/ / ? / F1",
464 Self::CycleChartFocus => "Tab",
465 Self::CycleChartFocusBack => "Shift+Tab",
466 Self::DrawingTool(crate::config::DrawingToolType::TrendLine) => "Alt+T",
467 Self::DrawingTool(crate::config::DrawingToolType::HorizontalLine) => "Alt+H",
468 Self::DrawingTool(crate::config::DrawingToolType::VerticalLine) => "Alt+V",
469 Self::DrawingTool(crate::config::DrawingToolType::CrossLine) => "Alt+C",
470 Self::DrawingTool(crate::config::DrawingToolType::HorizontalRay) => "Alt+J",
471 Self::DrawingTool(crate::config::DrawingToolType::FibonacciRetracement) => "Alt+F",
472 Self::DrawingTool(crate::config::DrawingToolType::Rect) => "Alt+Shift+R",
473 _ => "",
474 }
475 }
476
477 pub fn all_by_category() -> Vec<(&'static str, Vec<KeyboardAction>)> {
479 use crate::config::DrawingToolType;
480 vec![
481 (
482 "Navigation",
483 vec![
484 Self::PanLeft,
485 Self::PanRight,
486 Self::PanLeftFast,
487 Self::PanRightFast,
488 ],
489 ),
490 (
491 "Zoom",
492 vec![Self::ZoomIn, Self::ZoomOut, Self::PageUp, Self::PageDown],
493 ),
494 (
495 "View",
496 vec![
497 Self::ScrollToRealTime,
498 Self::ScrollToFirstBar,
499 Self::FitContent,
500 Self::ResetChart,
501 Self::ToggleLogScale,
502 Self::TogglePercentageScale,
503 Self::InvertScale,
504 Self::ToggleSession,
505 ],
506 ),
507 (
508 "Editing",
509 vec![
510 Self::Undo,
511 Self::Redo,
512 Self::CopyDrawing,
513 Self::PasteDrawing,
514 Self::ToggleDrawings,
515 ],
516 ),
517 (
518 "Drawing tools",
519 vec![
520 Self::DrawingTool(DrawingToolType::TrendLine),
521 Self::DrawingTool(DrawingToolType::HorizontalLine),
522 Self::DrawingTool(DrawingToolType::HorizontalRay),
523 Self::DrawingTool(DrawingToolType::VerticalLine),
524 Self::DrawingTool(DrawingToolType::CrossLine),
525 Self::DrawingTool(DrawingToolType::FibonacciRetracement),
526 Self::DrawingTool(DrawingToolType::Rect),
527 Self::DeleteSelectedDrawing,
528 Self::Cancel,
529 Self::CompleteDrawing,
530 ],
531 ),
532 (
533 "App",
534 vec![
535 Self::OpenQuickSearch,
536 Self::OpenIndicatorSearch,
537 Self::CreateAlert,
538 Self::GoToDate,
539 Self::Snapshot,
540 Self::SaveLayout,
541 Self::OpenLayout,
542 Self::OpenDataWindow,
543 Self::MaximizeChart,
544 Self::CycleChartFocus,
545 Self::CycleChartFocusBack,
546 Self::ShowKeyboardHelp,
547 ],
548 ),
549 ]
550 }
551}
552
553impl fmt::Display for KeyboardAction {
554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555 write!(f, "{}", self.desc())
556 }
557}