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 | self.user.variable_name_filter_focused) {
12            self.user.config.shortcuts.process(ctx, msgs, self);
13        }
14        ctx.input(|i| {
15            i.events.iter().for_each(|event| match event {
16                Event::Key {
17                    key,
18                    repeat: _,
19                    pressed,
20                    modifiers,
21                    physical_key: _,
22                } => match (
23                    key,
24                    pressed,
25                    self.command_prompt.visible,
26                    self.user.variable_name_filter_focused,
27                ) {
28                    // Consolidate numeric key handling into a single arm using helper
29                    (k, true, false, false)
30                        if matches!(
31                            k,
32                            Key::Num0
33                                | Key::Num1
34                                | Key::Num2
35                                | Key::Num3
36                                | Key::Num4
37                                | Key::Num5
38                                | Key::Num6
39                                | Key::Num7
40                                | Key::Num8
41                                | Key::Num9
42                        ) =>
43                    {
44                        if let Some(d) = key_to_digit(k) {
45                            handle_digit(d, modifiers, msgs);
46                        }
47                    }
48                    (Key::Escape, true, true, false) => msgs.push(Message::HideCommandPrompt),
49                    (Key::Escape, true, false, false) => {
50                        msgs.push(Message::InvalidateCount);
51                        msgs.push(Message::ItemSelectionClear);
52                    }
53                    (Key::Escape, true, _, true) => msgs.push(Message::SetFilterFocused(false)),
54                    (Key::G, true, true, false) => {
55                        if modifiers.command {
56                            msgs.push(Message::HideCommandPrompt);
57                        }
58                    }
59                    (Key::H, true, false, false) => msgs.push(Message::MoveCursorToTransition {
60                        next: false,
61                        variable: None,
62                        skip_zero: modifiers.shift,
63                    }),
64                    (Key::J, true, false, false) => {
65                        if modifiers.alt {
66                            msgs.push(Message::MoveFocus(
67                                MoveDir::Down,
68                                self.get_count(),
69                                modifiers.shift,
70                            ));
71                        } else if modifiers.command {
72                            msgs.push(Message::MoveFocusedItem(MoveDir::Down, self.get_count()));
73                        } else {
74                            msgs.push(Message::VerticalScroll(MoveDir::Down, self.get_count()));
75                        }
76                        msgs.push(Message::InvalidateCount);
77                    }
78                    (Key::K, true, false, false) => {
79                        if modifiers.alt {
80                            msgs.push(Message::MoveFocus(
81                                MoveDir::Up,
82                                self.get_count(),
83                                modifiers.shift,
84                            ));
85                        } else if modifiers.command {
86                            msgs.push(Message::MoveFocusedItem(MoveDir::Up, self.get_count()));
87                        } else {
88                            msgs.push(Message::VerticalScroll(MoveDir::Up, self.get_count()));
89                        }
90                        msgs.push(Message::InvalidateCount);
91                    }
92                    (Key::L, true, false, false) => msgs.push(Message::MoveCursorToTransition {
93                        next: true,
94                        variable: None,
95                        skip_zero: modifiers.shift,
96                    }),
97                    (Key::N, true, true, false) => {
98                        if modifiers.command {
99                            msgs.push(Message::SelectNextCommand);
100                        }
101                    }
102                    (Key::P, true, true, false) => {
103                        if modifiers.command {
104                            msgs.push(Message::SelectPrevCommand);
105                        }
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(digit as u32, 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}