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) => {
61                        if modifiers.command {
62                            msgs.push(Message::HideCommandPrompt);
63                        }
64                    }
65                    (Key::H, true, false, false) => msgs.push(Message::MoveCursorToTransition {
66                        next: false,
67                        variable: None,
68                        skip_zero: modifiers.shift,
69                    }),
70                    (Key::J, true, false, false) => {
71                        if modifiers.alt {
72                            msgs.push(Message::MoveFocus(
73                                MoveDir::Down,
74                                self.get_count(),
75                                modifiers.shift,
76                            ));
77                        } else if modifiers.command {
78                            msgs.push(Message::MoveFocusedItem(MoveDir::Down, self.get_count()));
79                        } else {
80                            msgs.push(Message::VerticalScroll(MoveDir::Down, self.get_count()));
81                        }
82                        msgs.push(Message::InvalidateCount);
83                    }
84                    (Key::K, true, false, false) => {
85                        if modifiers.alt {
86                            msgs.push(Message::MoveFocus(
87                                MoveDir::Up,
88                                self.get_count(),
89                                modifiers.shift,
90                            ));
91                        } else if modifiers.command {
92                            msgs.push(Message::MoveFocusedItem(MoveDir::Up, self.get_count()));
93                        } else {
94                            msgs.push(Message::VerticalScroll(MoveDir::Up, self.get_count()));
95                        }
96                        msgs.push(Message::InvalidateCount);
97                    }
98                    (Key::L, true, false, false) => msgs.push(Message::MoveCursorToTransition {
99                        next: true,
100                        variable: None,
101                        skip_zero: modifiers.shift,
102                    }),
103                    (Key::N, true, true, false) => {
104                        if modifiers.command {
105                            msgs.push(Message::SelectNextCommand);
106                        }
107                    }
108                    (Key::P, true, true, false) => {
109                        if modifiers.command {
110                            msgs.push(Message::SelectPrevCommand);
111                        }
112                    }
113                    (Key::F11, true, false, _) => msgs.push(Message::ToggleFullscreen),
114                    (Key::ArrowRight, true, false, false) => {
115                        msgs.push(match self.user.config.behavior.arrow_key_bindings {
116                            ArrowKeyBindings::Edge => Message::MoveCursorToTransition {
117                                next: true,
118                                variable: None,
119                                skip_zero: modifiers.shift,
120                            },
121                            ArrowKeyBindings::Scroll => Message::CanvasScroll {
122                                delta: Vec2 {
123                                    x: 0.,
124                                    y: -PER_SCROLL_EVENT,
125                                },
126                                viewport_idx: 0,
127                            },
128                        });
129                    }
130                    (Key::ArrowLeft, true, false, false) => {
131                        msgs.push(match self.user.config.behavior.arrow_key_bindings {
132                            ArrowKeyBindings::Edge => Message::MoveCursorToTransition {
133                                next: false,
134                                variable: None,
135                                skip_zero: modifiers.shift,
136                            },
137                            ArrowKeyBindings::Scroll => Message::CanvasScroll {
138                                delta: Vec2 {
139                                    x: 0.,
140                                    y: PER_SCROLL_EVENT,
141                                },
142                                viewport_idx: 0,
143                            },
144                        });
145                    }
146                    (Key::ArrowDown, true, true, false) => msgs.push(Message::SelectNextCommand),
147                    (Key::ArrowDown, true, false, false) => {
148                        if modifiers.alt {
149                            msgs.push(Message::MoveFocus(
150                                MoveDir::Down,
151                                self.get_count(),
152                                modifiers.shift,
153                            ));
154                        } else if modifiers.command {
155                            msgs.push(Message::MoveFocusedItem(MoveDir::Down, self.get_count()));
156                        } else {
157                            msgs.push(Message::VerticalScroll(MoveDir::Down, self.get_count()));
158                        }
159                        msgs.push(Message::InvalidateCount);
160                    }
161                    (Key::ArrowUp, true, true, false) => msgs.push(Message::SelectPrevCommand),
162                    (Key::ArrowUp, true, false, false) => {
163                        if modifiers.alt {
164                            msgs.push(Message::MoveFocus(
165                                MoveDir::Up,
166                                self.get_count(),
167                                modifiers.shift,
168                            ));
169                        } else if modifiers.command {
170                            msgs.push(Message::MoveFocusedItem(MoveDir::Up, self.get_count()));
171                        } else {
172                            msgs.push(Message::VerticalScroll(MoveDir::Up, self.get_count()));
173                        }
174                        msgs.push(Message::InvalidateCount);
175                    }
176                    _ => {}
177                },
178                Event::Copy => msgs.push(Message::VariableValueToClipbord(
179                    MessageTarget::CurrentSelection,
180                )),
181                _ => {}
182            });
183        });
184    }
185
186    pub fn get_count(&self) -> usize {
187        self.user
188            .count
189            .as_deref()
190            .map(str::trim)
191            .and_then(|s| s.parse::<usize>().ok())
192            .unwrap_or(1)
193    }
194}
195
196fn handle_digit(digit: u8, modifiers: &Modifiers, msgs: &mut Vec<Message>) {
197    if modifiers.alt {
198        // Convert 0..9 to '0'..'9' safely and clearly
199        if let Some(c) = std::char::from_digit(digit as u32, 10) {
200            msgs.push(Message::AddCount(c));
201        }
202    } else if modifiers.command {
203        msgs.push(Message::MoveMarkerToCursor(digit));
204    } else {
205        msgs.push(Message::GoToMarkerPosition(digit, 0));
206    }
207}
208
209fn key_to_digit(key: &Key) -> Option<u8> {
210    match key {
211        Key::Num0 => Some(0),
212        Key::Num1 => Some(1),
213        Key::Num2 => Some(2),
214        Key::Num3 => Some(3),
215        Key::Num4 => Some(4),
216        Key::Num5 => Some(5),
217        Key::Num6 => Some(6),
218        Key::Num7 => Some(7),
219        Key::Num8 => Some(8),
220        Key::Num9 => Some(9),
221        _ => None,
222    }
223}