Skip to main content

void/app/
keys.rs

1use super::*;
2use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
3
4impl App {
5    pub fn handle_key(&mut self, key: KeyEvent) {
6        if self.searching {
7            self.handle_search_key(key);
8            return;
9        }
10        if self.popup.is_some() {
11            self.handle_popup_key(key);
12            return;
13        }
14        let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
15        match key.code {
16            KeyCode::Char('q') => self.should_quit = true,
17            KeyCode::Esc => self.should_quit = true,
18            KeyCode::Char('c') if ctrl => self.should_quit = true,
19            KeyCode::Char('s') if ctrl => self.export_backup(),
20            KeyCode::Char('1') => self.tab = FocusTab::Dashboard,
21            KeyCode::Char('2') => self.tab = FocusTab::Tasks,
22            KeyCode::Char('3') => self.tab = FocusTab::Stats,
23            KeyCode::Char('4') => self.tab = FocusTab::Settings,
24            KeyCode::Char('5') | KeyCode::Char('h') => self.tab = FocusTab::Help,
25            KeyCode::Tab => self.next_tab(),
26            KeyCode::BackTab => self.prev_tab(),
27            _ => match self.tab {
28                FocusTab::Dashboard => self.handle_dashboard_key(key),
29                FocusTab::Tasks => self.handle_tasks_key(key),
30                FocusTab::Stats => self.handle_stats_key(key),
31                FocusTab::Settings => self.handle_settings_key(key),
32                FocusTab::Help => {}
33            },
34        }
35    }
36
37    pub(crate) fn next_tab(&mut self) {
38        let cur = FocusTab::all()
39            .iter()
40            .position(|t| *t == self.tab)
41            .unwrap_or(0);
42        self.tab = FocusTab::all()[(cur + 1) % FocusTab::all().len()];
43    }
44
45    pub(crate) fn prev_tab(&mut self) {
46        let cur = FocusTab::all()
47            .iter()
48            .position(|t| *t == self.tab)
49            .unwrap_or(0);
50        let n = FocusTab::all().len();
51        self.tab = FocusTab::all()[(cur + n - 1) % n];
52    }
53
54    pub(crate) fn handle_dashboard_key(&mut self, key: KeyEvent) {
55        match key.code {
56            KeyCode::Char('s') | KeyCode::Char(' ') => self.toggle_timer(),
57            KeyCode::Char('p') => self.pause_timer(),
58            KeyCode::Char('r') => self.reset_timer(),
59            KeyCode::Char('n') => {
60                self.timer.skip();
61                self.on_timer_finished(true);
62            }
63            KeyCode::Char('m') => self.cycle_mode(),
64            KeyCode::Char('+') | KeyCode::Char('=') => self.adjust_minutes(1),
65            KeyCode::Char('-') | KeyCode::Char('_') => self.adjust_minutes(-1),
66            KeyCode::Char('a') => self.open_add_task(),
67            KeyCode::Char('f') => {
68                if let Some(id) = self.dashboard_selected_task_id() {
69                    self.set_active_task(Some(id));
70                    self.set_status("Task set as active.", false);
71                }
72            }
73            KeyCode::Char('g') => {
74                self.cycle_task_filter();
75            }
76            KeyCode::Char('t') => {
77                self.cycle_tag_filter();
78            }
79            KeyCode::Char('z') => {
80                self.zen_mode = !self.zen_mode;
81                self.set_status(
82                    format!("Zen mode {}.", if self.zen_mode { "on" } else { "off" }),
83                    false,
84                );
85            }
86            KeyCode::Down | KeyCode::Char('j') => self.move_dashboard_task_selection(1),
87            KeyCode::Up | KeyCode::Char('k') => self.move_dashboard_task_selection(-1),
88            KeyCode::Enter => {
89                if let Some(id) = self.dashboard_selected_task_id() {
90                    self.set_active_task(Some(id));
91                    self.persist_data(|db, data| storage::cycle_task_status(db, data, id));
92                    let status = self
93                        .data
94                        .tasks
95                        .iter()
96                        .find(|t| t.id == id)
97                        .map(|t| t.status);
98                    if let Some(status) = status {
99                        if status == crate::model::TaskStatus::Done {
100                            self.active_task = None;
101                            self.data.active_task_id = None;
102                            self.persist(|db| db.persist_active_task(None));
103                            self.maybe_advance_task();
104                            self.check_queue_empty();
105                        }
106                        self.bump_data();
107                        self.clamp_dashboard_task_selection();
108                        self.set_status(format!("Task status: {}", status.label()), false);
109                    }
110                } else {
111                    self.cycle_active_task_status();
112                }
113            }
114            KeyCode::Char('x') => {
115                if let Some(id) = self.dashboard_selected_task_id() {
116                    self.persist_data(|db, data| storage::mark_task_done(db, data, id));
117                    if self.active_task == Some(id) {
118                        self.active_task = None;
119                        self.data.active_task_id = None;
120                        self.persist(|db| db.persist_active_task(None));
121                        self.maybe_advance_task();
122                    }
123                    self.bump_data();
124                    self.clamp_dashboard_task_selection();
125                    self.check_queue_empty();
126                    self.set_status("Task marked done.", false);
127                } else {
128                    self.mark_active_task_done();
129                }
130            }
131            KeyCode::Char('e') | KeyCode::Char('E') => self.end_session(),
132            _ => {}
133        }
134    }
135
136    pub(crate) fn handle_stats_key(&mut self, key: KeyEvent) {
137        if self.recent_sessions.is_empty() {
138            if matches!(key.code, KeyCode::Char('e') | KeyCode::Char('E')) {
139                self.end_session();
140            }
141            return;
142        }
143        let n = self.recent_sessions.len();
144        match key.code {
145            KeyCode::Down | KeyCode::Char('j') => {
146                self.stats_session_selected = (self.stats_session_selected + 1) % n;
147            }
148            KeyCode::Up | KeyCode::Char('k') => {
149                self.stats_session_selected = if self.stats_session_selected == 0 {
150                    n - 1
151                } else {
152                    self.stats_session_selected - 1
153                };
154            }
155            KeyCode::Char('d') => {
156                let id = self.recent_sessions[self.stats_session_selected].id;
157                self.persist_data(|db, data| storage::delete_session(db, data, id));
158                self.bump_data();
159                self.set_status("Session deleted.", false);
160            }
161            KeyCode::Char('+') | KeyCode::Char('=') => {
162                let entry = &self.recent_sessions[self.stats_session_selected];
163                let new_mins = entry.record.minutes.saturating_add(5);
164                let id = entry.id;
165                self.persist_data(|db, data| {
166                    storage::adjust_session_minutes(db, data, id, new_mins)
167                });
168                self.bump_data();
169            }
170            KeyCode::Char('-') => {
171                let entry = &self.recent_sessions[self.stats_session_selected];
172                let new_mins = entry.record.minutes.saturating_sub(5).max(1);
173                let id = entry.id;
174                self.persist_data(|db, data| {
175                    storage::adjust_session_minutes(db, data, id, new_mins)
176                });
177                self.bump_data();
178            }
179            KeyCode::Char('e') | KeyCode::Char('E') => self.end_session(),
180            _ => {}
181        }
182    }
183
184    pub(crate) fn handle_search_key(&mut self, key: KeyEvent) {
185        match key.code {
186            KeyCode::Esc => {
187                self.searching = false;
188                self.task_search.clear();
189            }
190            KeyCode::Enter => {
191                self.searching = false;
192            }
193            KeyCode::Backspace => {
194                self.task_search.pop();
195            }
196            KeyCode::Char(c) => {
197                self.task_search.push(c);
198            }
199            _ => {}
200        }
201    }
202
203    pub(crate) fn handle_tasks_key(&mut self, key: KeyEvent) {
204        let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
205        match key.code {
206            KeyCode::Char('f') => {
207                if let Some(id) = self.selected_task_id() {
208                    self.start_focus_on_task(id);
209                }
210            }
211            KeyCode::Char('g') => {
212                self.cycle_task_filter();
213            }
214            KeyCode::Char('/') => {
215                self.searching = true;
216                self.task_search.clear();
217            }
218            KeyCode::Char('t') => {
219                if let Some(id) = self.selected_task_id() {
220                    self.persist_data(|db, data| storage::toggle_today(db, data, id));
221                    self.bump_data();
222                }
223            }
224            KeyCode::Char('a') => self.open_add_task(),
225            KeyCode::Char('e') => self.open_edit_task(),
226            KeyCode::Char('d') => self.open_confirm_delete(),
227            KeyCode::Enter => {
228                if let Some(id) = self.selected_task_id() {
229                    self.persist_data(|db, data| storage::cycle_task_status(db, data, id));
230                    self.bump_data();
231                }
232            }
233            KeyCode::Char(' ') => {
234                if let Some(id) = self.selected_task_id() {
235                    self.set_active_task(Some(id));
236                    self.set_status("Task set as active for the timer.", false);
237                }
238            }
239            KeyCode::Char('1') => {
240                if let Some(id) = self.selected_task_id() {
241                    self.persist_data(|db, data| {
242                        storage::set_priority(db, data, id, Priority::Low)
243                    });
244                    self.bump_data();
245                }
246            }
247            KeyCode::Char('2') => {
248                if let Some(id) = self.selected_task_id() {
249                    self.persist_data(|db, data| {
250                        storage::set_priority(db, data, id, Priority::Medium)
251                    });
252                    self.bump_data();
253                }
254            }
255            KeyCode::Char('3') => {
256                if let Some(id) = self.selected_task_id() {
257                    self.persist_data(|db, data| {
258                        storage::set_priority(db, data, id, Priority::High)
259                    });
260                    self.bump_data();
261                }
262            }
263            KeyCode::Down | KeyCode::Char('j') if !ctrl => self.move_task_selection(1),
264            KeyCode::Up | KeyCode::Char('k') if !ctrl => self.move_task_selection(-1),
265            KeyCode::Down | KeyCode::Char('j') if ctrl => {
266                if let Some(id) = self.selected_task_id() {
267                    self.persist_data(|db, data| storage::move_task(db, data, id, 1));
268                    self.bump_data();
269                }
270            }
271            KeyCode::Up | KeyCode::Char('k') if ctrl => {
272                if let Some(id) = self.selected_task_id() {
273                    self.persist_data(|db, data| storage::move_task(db, data, id, -1));
274                    self.bump_data();
275                }
276            }
277            KeyCode::PageDown => self.move_task_selection(8),
278            KeyCode::PageUp => self.move_task_selection(-8),
279            KeyCode::Home => {
280                let len = self.filtered_task_indices().len();
281                if len > 0 {
282                    self.task_state.select(Some(0));
283                }
284            }
285            KeyCode::End => {
286                let len = self.filtered_task_indices().len();
287                if len > 0 {
288                    self.task_state.select(Some(len - 1));
289                }
290            }
291            _ => {}
292        }
293    }
294
295    pub(crate) fn move_task_selection(&mut self, delta: i32) {
296        let len = self.filtered_task_indices().len();
297        if len == 0 {
298            return;
299        }
300        let cur = self.task_state.selected().unwrap_or(0) as i32;
301        let new = (cur + delta).clamp(0, len as i32 - 1) as usize;
302        self.task_state.select(Some(new));
303    }
304}