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