Update keybindings (#76)

* (pomodoro) reset both clocks at once

* quit app by pressing `q` only

* (countdown) enter/esc keybindings

* (timer) enter/esc keybindings

* (pomodoro) enter/esc keybindings

* update footer label

* fix(coundown): don't reset elapsed clock

while skipping editing changes

* fix(clock): order of actions matters for ESC

handling. Set `pause` instead of `initial` mode while toggeling back.

* fix(timer): order of actions matters (ESC key)

* (footer) update order, lowercase standard keys
This commit is contained in:
Jens Krause 2025-04-30 11:11:33 +02:00 committed by GitHub
parent 90d9988e7a
commit e6291a3131
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 117 additions and 60 deletions

View File

@ -202,7 +202,7 @@ impl App {
let handle_key_event = |app: &mut Self, key: KeyEvent| { let handle_key_event = |app: &mut Self, key: KeyEvent| {
debug!("Received key {:?}", key.code); debug!("Received key {:?}", key.code);
match key.code { match key.code {
KeyCode::Char('q') | KeyCode::Esc => app.mode = Mode::Quit, KeyCode::Char('q') => app.mode = Mode::Quit,
KeyCode::Char('c') => app.content = Content::Countdown, KeyCode::Char('c') => app.content = Content::Countdown,
KeyCode::Char('t') => app.content = Content::Timer, KeyCode::Char('t') => app.content = Content::Timer,
KeyCode::Char('p') => app.content = Content::Pomodoro, KeyCode::Char('p') => app.content = Content::Pomodoro,

View File

@ -74,6 +74,7 @@ pub struct ClockState<T> {
name: Option<String>, name: Option<String>,
initial_value: DurationEx, initial_value: DurationEx,
current_value: DurationEx, current_value: DurationEx,
prev_value: DurationEx,
tick_value: DurationEx, tick_value: DurationEx,
mode: Mode, mode: Mode,
format: Format, format: Format,
@ -151,14 +152,18 @@ impl<T> ClockState<T> {
self.update_format(); self.update_format();
} }
pub fn get_prev_value(&self) -> &DurationEx {
&self.prev_value
}
pub fn toggle_edit(&mut self) { pub fn toggle_edit(&mut self) {
self.mode = match self.mode.clone() { self.mode = match self.mode.clone() {
Mode::Editable(_, prev) => { Mode::Editable(_, prev) => {
let p = *prev; let p = *prev;
// special cases: Should `Mode` be updated? // Update `Mode`
// 1. `Done` -> `Initial` ? // 1. `Done` -> `Pause`
if p == Mode::Done && self.current_value.gt(&Duration::ZERO.into()) { if p == Mode::Done && self.current_value.gt(&Duration::ZERO.into()) {
Mode::Initial Mode::Pause
} }
// 2. `_` -> `Done` ? // 2. `_` -> `Done` ?
else if p != Mode::Done && self.current_value.eq(&Duration::ZERO.into()) { else if p != Mode::Done && self.current_value.eq(&Duration::ZERO.into()) {
@ -170,6 +175,8 @@ impl<T> ClockState<T> {
} }
} }
mode => { mode => {
// store prev. value
self.prev_value = self.current_value;
if self.format <= Format::Ss { if self.format <= Format::Ss {
Mode::Editable(Time::Seconds, Box::new(mode)) Mode::Editable(Time::Seconds, Box::new(mode))
} else { } else {
@ -402,6 +409,7 @@ impl ClockState<Countdown> {
name: None, name: None,
initial_value: initial_value.into(), initial_value: initial_value.into(),
current_value: current_value.into(), current_value: current_value.into(),
prev_value: current_value.into(),
tick_value: tick_value.into(), tick_value: tick_value.into(),
mode: if current_value == Duration::ZERO { mode: if current_value == Duration::ZERO {
Mode::Done Mode::Done
@ -475,6 +483,7 @@ impl ClockState<Timer> {
name: None, name: None,
initial_value: initial_value.into(), initial_value: initial_value.into(),
current_value: current_value.into(), current_value: current_value.into(),
prev_value: current_value.into(),
tick_value: tick_value.into(), tick_value: tick_value.into(),
mode: if current_value == initial_value { mode: if current_value == initial_value {
Mode::Initial Mode::Initial

View File

@ -187,33 +187,65 @@ impl TuiEventHandler for CountdownState {
self.edit_time_done(edit_time); self.edit_time_done(edit_time);
} }
} }
// STRG + e => toggle edit time // skip editing clock
KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => { KeyCode::Esc if self.is_clock_edit_mode() => {
// reset both clocks // Important: set current value first
self.clock.reset(); self.clock.set_current_value(*self.clock.get_prev_value());
self.elapsed_clock.reset(); // before toggling back to non-edit mode
self.clock.toggle_edit();
}
// skip editing by local time
KeyCode::Esc if self.is_time_edit_mode() => {
self.edit_time = None;
}
if let Some(edit_time) = &mut self.edit_time.clone() { // Enter edit by local time mode
self.edit_time_done(edit_time) KeyCode::Char('e')
} else { if key.modifiers.contains(KeyModifiers::CONTROL)
// update `edit_time` && !self.is_time_edit_mode() =>
{
// set `edit_time`
self.edit_time = Some(EditTimeState::new(EditTimeStateArgs { self.edit_time = Some(EditTimeState::new(EditTimeStateArgs {
time: self.time_to_edit(), time: self.time_to_edit(),
min: self.min_time_to_edit(), min: self.min_time_to_edit(),
max: self.max_time_to_edit(), max: self.max_time_to_edit(),
})); }));
}
}
// e => toggle edit clock
KeyCode::Char('e') => {
// toggle edit mode
self.clock.toggle_edit();
// stop `elapsed_clock` // pause `elapsed_clock`
if self.elapsed_clock.is_running() { if self.elapsed_clock.is_running() {
self.elapsed_clock.toggle_pause(); self.elapsed_clock.toggle_pause();
} }
} }
// Enter edit clock
KeyCode::Char('e') if !self.is_clock_edit_mode() => {
// toggle edit mode
self.clock.toggle_edit();
// pause `elapsed_clock`
if self.elapsed_clock.is_running() {
self.elapsed_clock.toggle_pause();
}
}
// Apply changes of editing by local time
KeyCode::Enter if self.is_time_edit_mode() => {
if let Some(edit_time) = &mut self.edit_time.clone() {
self.edit_time_done(edit_time)
}
// always reset `elapsed_clock`
self.elapsed_clock.reset();
}
// Apply changes of editing clock
// Note: Using Ctrl+e is deprecated, use Enter instead
KeyCode::Enter if self.is_clock_edit_mode() => {
// toggle edit mode
self.clock.toggle_edit();
// always reset `elapsed_clock`
self.elapsed_clock.reset();
}
KeyCode::Left if self.is_clock_edit_mode() => { KeyCode::Left if self.is_clock_edit_mode() => {
self.clock.edit_next(); self.clock.edit_next();
} }
@ -230,8 +262,6 @@ impl TuiEventHandler for CountdownState {
} }
KeyCode::Up if self.is_clock_edit_mode() => { KeyCode::Up if self.is_clock_edit_mode() => {
self.clock.edit_up(); self.clock.edit_up();
// whenever `clock`'s value is changed, reset `elapsed_clock`
self.elapsed_clock.reset();
} }
KeyCode::Up if self.is_time_edit_mode() => { KeyCode::Up if self.is_time_edit_mode() => {
// safe unwrap because of previous check in `is_time_edit_mode` // safe unwrap because of previous check in `is_time_edit_mode`
@ -239,8 +269,6 @@ impl TuiEventHandler for CountdownState {
} }
KeyCode::Down if self.is_clock_edit_mode() => { KeyCode::Down if self.is_clock_edit_mode() => {
self.clock.edit_down(); self.clock.edit_down();
// whenever clock value is changed, reset timer
self.elapsed_clock.reset();
} }
KeyCode::Down if self.is_time_edit_mode() => { KeyCode::Down if self.is_time_edit_mode() => {
// safe unwrap because of previous check in `is_time_edit_mode` // safe unwrap because of previous check in `is_time_edit_mode`

View File

@ -104,7 +104,7 @@ impl StatefulWidget for Footer {
let widths = [Constraint::Length(12), Constraint::Percentage(100)]; let widths = [Constraint::Length(12), Constraint::Percentage(100)];
let table = Table::new( let table = Table::new(
[ [
// content // screens
Row::new(vec![ Row::new(vec![
Cell::from(Span::styled( Cell::from(Span::styled(
"screens", "screens",
@ -112,27 +112,7 @@ impl StatefulWidget for Footer {
)), )),
Cell::from(Line::from(content_labels)), Cell::from(Line::from(content_labels)),
]), ]),
// format // controls
Row::new(vec![
Cell::from(Span::styled(
"appearance",
Style::default().add_modifier(Modifier::BOLD),
)),
Cell::from(Line::from(vec![
Span::from("[,]change style"),
Span::from(SPACE),
Span::from("[.]toggle deciseconds"),
Span::from(SPACE),
Span::from(format!(
"[:]toggle {} time",
match self.app_time {
AppTime::Local(_) => "local",
AppTime::Utc(_) => "utc",
}
)),
])),
]),
// edit
Row::new(vec![ Row::new(vec![
Cell::from(Span::styled( Cell::from(Span::styled(
"controls", "controls",
@ -170,12 +150,10 @@ impl StatefulWidget for Footer {
} }
spans spans
} }
others => vec![ _ => vec![
Span::from(match others { Span::from("[enter]apply changes"),
AppEditMode::Clock => "[e]dit done", Span::from(SPACE),
AppEditMode::Time => "[^e]dit done", Span::from("[esc]skip changes"),
_ => "",
}),
Span::from(SPACE), Span::from(SPACE),
Span::from(format!( Span::from(format!(
"[{} {}]edit selection", "[{} {}]edit selection",
@ -190,6 +168,26 @@ impl StatefulWidget for Footer {
} }
})), })),
]), ]),
// appearance
Row::new(vec![
Cell::from(Span::styled(
"appearance",
Style::default().add_modifier(Modifier::BOLD),
)),
Cell::from(Line::from(vec![
Span::from("[,]change style"),
Span::from(SPACE),
Span::from("[.]toggle deciseconds"),
Span::from(SPACE),
Span::from(format!(
"[:]toggle {} time",
match self.app_time {
AppTime::Local(_) => "local",
AppTime::Utc(_) => "utc",
}
)),
])),
]),
], ],
widths, widths,
) )

View File

@ -145,7 +145,18 @@ impl TuiEventHandler for PomodoroState {
KeyCode::Char('s') => { KeyCode::Char('s') => {
self.get_clock_mut().toggle_pause(); self.get_clock_mut().toggle_pause();
} }
KeyCode::Char('e') => { // Skip changes
KeyCode::Esc if edit_mode => {
let clock = self.get_clock_mut();
clock.toggle_edit();
clock.set_current_value(*clock.get_prev_value());
}
// Apply changes
KeyCode::Enter if edit_mode => {
self.get_clock_mut().toggle_edit();
}
// Enter edit mode
KeyCode::Char('e') if !edit_mode => {
self.get_clock_mut().toggle_edit(); self.get_clock_mut().toggle_edit();
} }
KeyCode::Left if edit_mode => { KeyCode::Left if edit_mode => {
@ -175,7 +186,9 @@ impl TuiEventHandler for PomodoroState {
if self.get_mode() == &Mode::Work && self.get_clock().is_done() { if self.get_mode() == &Mode::Work && self.get_clock().is_done() {
self.round += 1; self.round += 1;
} }
self.get_clock_mut().reset(); // reset both clocks
self.clock_map.pause.reset();
self.clock_map.work.reset();
} }
_ => return Some(event), _ => return Some(event),
}, },

View File

@ -46,7 +46,16 @@ impl TuiEventHandler for TimerState {
KeyCode::Char('r') => { KeyCode::Char('r') => {
self.clock.reset(); self.clock.reset();
} }
KeyCode::Char('e') => { KeyCode::Esc if edit_mode => {
// Important: set current value first
self.clock.set_current_value(*self.clock.get_prev_value());
// before toggling back to non-edit mode
self.clock.toggle_edit();
}
KeyCode::Enter if edit_mode => {
self.clock.toggle_edit();
}
KeyCode::Char('e') if !edit_mode => {
self.clock.toggle_edit(); self.clock.toggle_edit();
} }
KeyCode::Left if edit_mode => { KeyCode::Left if edit_mode => {