Skip to main content

libsurfer/
keys.rs

1//! Keyboard handling.
2use egui::{Context, Event, Key, Modifiers};
3use emath::Vec2;
4
5use crate::config::ArrowKeyBindings;
6use crate::message::MessageTarget;
7use crate::{MoveDir, SystemState, message::Message, wave_data::PER_SCROLL_EVENT};
8
9impl SystemState {
10    pub fn handle_pressed_keys(&self, ctx: &Context, msgs: &mut Vec<Message>) {
11        let any_widget_focused = self.text_edit_focused.values().any(|&v| v);
12
13        if !(self.command_prompt.visible || any_widget_focused) {
14            self.user.config.shortcuts.process(ctx, msgs, self);
15        }
16        ctx.input(|i| {
17            i.events.iter().for_each(|event| match event {
18                Event::Key {
19                    key,
20                    repeat: _,
21                    pressed,
22                    modifiers,
23                    physical_key: _,
24                } => match (
25                    key,
26                    pressed,
27                    self.command_prompt.visible,
28                    any_widget_focused,
29                ) {
30                    // Consolidate numeric key handling into a single arm using helper
31                    (k, true, false, false)
32                        if matches!(
33                            k,
34                            Key::Num0
35                                | Key::Num1
36                                | Key::Num2
37                                | Key::Num3
38                                | Key::Num4
39                                | Key::Num5
40                                | Key::Num6
41                                | Key::Num7
42                                | Key::Num8
43                                | Key::Num9
44                        ) =>
45                    {
46                        if let Some(d) = key_to_digit(k) {
47                            handle_digit(d, modifiers, msgs);
48                        }
49                    }
50                    (Key::Escape, true, true, false) => msgs.push(Message::HideCommandPrompt),
51                    (Key::Escape, true, false, false) => {
52                        msgs.push(Message::InvalidateCount);
53                        msgs.push(Message::ItemSelectionClear);
54                    }
55                    (Key::Escape, true, _, true) => {
56                        msgs.push(Message::ClearAllTextEditFocuses);
57                    }
58                    (Key::G, true, true, false) if modifiers.command => {
59                        msgs.push(Message::HideCommandPrompt);
60                    }
61                    (Key::H, true, false, false) => msgs.push(Message::MoveCursorToTransition {
62                        next: false,
63                        variable: None,
64                        skip_zero: modifiers.shift,
65                    }),
66                    (Key::J, true, false, false) => {
67                        if modifiers.alt {
68                            msgs.push(Message::MoveFocus(
69                                MoveDir::Down,
70                                self.get_count(),
71                                modifiers.shift,
72                            ));
73                        } else if modifiers.command {
74                            msgs.push(Message::MoveFocusedItem(MoveDir::Down, self.get_count()));
75                        } else {
76                            msgs.push(Message::VerticalScroll(MoveDir::Down, self.get_count()));
77                        }
78                        msgs.push(Message::InvalidateCount);
79                    }
80                    (Key::K, true, false, false) => {
81                        if modifiers.alt {
82                            msgs.push(Message::MoveFocus(
83                                MoveDir::Up,
84                                self.get_count(),
85                                modifiers.shift,
86                            ));
87                        } else if modifiers.command {
88                            msgs.push(Message::MoveFocusedItem(MoveDir::Up, self.get_count()));
89                        } else {
90                            msgs.push(Message::VerticalScroll(MoveDir::Up, self.get_count()));
91                        }
92                        msgs.push(Message::InvalidateCount);
93                    }
94                    (Key::L, true, false, false) => msgs.push(Message::MoveCursorToTransition {
95                        next: true,
96                        variable: None,
97                        skip_zero: modifiers.shift,
98                    }),
99                    (Key::N, true, true, false) if modifiers.command => {
100                        msgs.push(Message::SelectNextCommand);
101                    }
102                    (Key::P, true, true, false) if modifiers.command => {
103                        msgs.push(Message::SelectPrevCommand);
104                    }
105                    (Key::F11, true, false, _) => msgs.push(Message::ToggleFullscreen),
106                    (Key::ArrowRight, true, false, false) => {
107                        msgs.push(match self.user.config.behavior.arrow_key_bindings() {
108                            ArrowKeyBindings::Edge => Message::MoveCursorToTransition {
109                                next: true,
110                                variable: None,
111                                skip_zero: modifiers.shift,
112                            },
113                            ArrowKeyBindings::Scroll => Message::CanvasScroll {
114                                delta: Vec2 {
115                                    x: 0.,
116                                    y: -PER_SCROLL_EVENT,
117                                },
118                                viewport_idx: 0,
119                            },
120                        });
121                    }
122                    (Key::ArrowLeft, true, false, false) => {
123                        msgs.push(match self.user.config.behavior.arrow_key_bindings() {
124                            ArrowKeyBindings::Edge => Message::MoveCursorToTransition {
125                                next: false,
126                                variable: None,
127                                skip_zero: modifiers.shift,
128                            },
129                            ArrowKeyBindings::Scroll => Message::CanvasScroll {
130                                delta: Vec2 {
131                                    x: 0.,
132                                    y: PER_SCROLL_EVENT,
133                                },
134                                viewport_idx: 0,
135                            },
136                        });
137                    }
138                    (Key::ArrowDown, true, true, false) => msgs.push(Message::SelectNextCommand),
139                    (Key::ArrowDown, true, false, false) => {
140                        if modifiers.alt {
141                            msgs.push(Message::MoveFocus(
142                                MoveDir::Down,
143                                self.get_count(),
144                                modifiers.shift,
145                            ));
146                        } else if modifiers.command {
147                            msgs.push(Message::MoveFocusedItem(MoveDir::Down, self.get_count()));
148                        } else {
149                            msgs.push(Message::VerticalScroll(MoveDir::Down, self.get_count()));
150                        }
151                        msgs.push(Message::InvalidateCount);
152                    }
153                    (Key::ArrowUp, true, true, false) => msgs.push(Message::SelectPrevCommand),
154                    (Key::ArrowUp, true, false, false) => {
155                        if modifiers.alt {
156                            msgs.push(Message::MoveFocus(
157                                MoveDir::Up,
158                                self.get_count(),
159                                modifiers.shift,
160                            ));
161                        } else if modifiers.command {
162                            msgs.push(Message::MoveFocusedItem(MoveDir::Up, self.get_count()));
163                        } else {
164                            msgs.push(Message::VerticalScroll(MoveDir::Up, self.get_count()));
165                        }
166                        msgs.push(Message::InvalidateCount);
167                    }
168                    _ => {}
169                },
170                Event::Copy => msgs.push(Message::VariableValueToClipbord(
171                    MessageTarget::CurrentSelection,
172                )),
173                _ => {}
174            });
175        });
176    }
177
178    pub fn get_count(&self) -> usize {
179        self.user
180            .count
181            .as_deref()
182            .map(str::trim)
183            .and_then(|s| s.parse::<usize>().ok())
184            .unwrap_or(1)
185    }
186}
187
188fn handle_digit(digit: u8, modifiers: &Modifiers, msgs: &mut Vec<Message>) {
189    if modifiers.alt {
190        // Convert 0..9 to '0'..'9' safely and clearly
191        if let Some(c) = std::char::from_digit(u32::from(digit), 10) {
192            msgs.push(Message::AddCount(c));
193        }
194    } else if modifiers.command {
195        msgs.push(Message::MoveMarkerToCursor(digit));
196    } else {
197        msgs.push(Message::GoToMarkerPosition(digit, 0));
198    }
199}
200
201fn key_to_digit(key: &Key) -> Option<u8> {
202    match key {
203        Key::Num0 => Some(0),
204        Key::Num1 => Some(1),
205        Key::Num2 => Some(2),
206        Key::Num3 => Some(3),
207        Key::Num4 => Some(4),
208        Key::Num5 => Some(5),
209        Key::Num6 => Some(6),
210        Key::Num7 => Some(7),
211        Key::Num8 => Some(8),
212        Key::Num9 => Some(9),
213        _ => None,
214    }
215}