(pomodoro/countdown) Change initial value (#79)

- While editing, an user can apply changes as a new initial value (pomodoro/countdown only).
- New keybinding: `[^s]save initial value` 
- Update keybinding: `[s]ave changes` (instead of `[Enter]`)
- Refactor event handling to re-structure `edit` / `non-edit` modes.
- Refactor footer to reflect latest keybindings
This commit is contained in:
Jens Krause 2025-05-02 12:39:26 +02:00 committed by GitHub
parent 6b068bbd09
commit 52ed8267be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 263 additions and 167 deletions

View File

@ -129,10 +129,12 @@ Extra option (if `--features sound` is enabled by local build only):
| Key | Description |
| --- | --- |
| <kbd>Enter</kbd> | apply changes |
| <kbd>s</kbd> | save changes |
| <kbd>^s</kbd> | save initial value |
| <kbd>Esc</kbd> | skip changes |
| <kbd></kbd> or <kbd></kbd> | change selection |
| <kbd></kbd> or <kbd></kbd> | change values to go up or down |
| <kbd></kbd> | edit to go up |
| <kbd></kbd> | edit to go down |
**In `Pomodoro` screen only**

View File

@ -414,7 +414,7 @@ impl StatefulWidget for AppWidget {
let [v0, v1, v2] = Layout::vertical([
Constraint::Length(1),
Constraint::Percentage(100),
Constraint::Length(if state.footer.get_show_menu() { 4 } else { 1 }),
Constraint::Length(if state.footer.get_show_menu() { 5 } else { 1 }),
])
.areas(area);

View File

@ -143,6 +143,10 @@ impl<T> ClockState<T> {
&self.initial_value
}
pub fn set_initial_value(&mut self, duration: DurationEx) {
self.initial_value = duration;
}
pub fn get_current_value(&self) -> &DurationEx {
&self.current_value
}

View File

@ -162,6 +162,96 @@ impl TuiEventHandler for CountdownState {
edit_time.set_max_time(max_time);
}
}
// EDIT CLOCK mode
TuiEvent::Key(key) if self.is_clock_edit_mode() => match key.code {
// skip editing
KeyCode::Esc => {
// 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();
}
// Apply changes and set new initial value
KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => {
// toggle edit mode
self.clock.toggle_edit();
// set initial value
self.clock
.set_initial_value(*self.clock.get_current_value());
// always reset `elapsed_clock`
self.elapsed_clock.reset();
}
// Apply changes
KeyCode::Char('s') => {
// toggle edit mode
self.clock.toggle_edit();
// always reset `elapsed_clock`
self.elapsed_clock.reset();
}
KeyCode::Right => {
self.clock.edit_prev();
}
KeyCode::Left => {
self.clock.edit_next();
}
KeyCode::Up => {
self.clock.edit_up();
}
KeyCode::Down => {
self.clock.edit_down();
}
_ => return Some(event),
},
// EDIT LOCAL TIME mode
TuiEvent::Key(key) if self.is_time_edit_mode() => match key.code {
// skip editing
KeyCode::Esc => {
self.edit_time = None;
}
// Apply changes and set new initial value
KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => {
if let Some(edit_time) = &mut self.edit_time.clone() {
// Order matters:
// 1. update current value
self.edit_time_done(edit_time);
// 2. set initial value
self.clock
.set_initial_value(*self.clock.get_current_value());
}
// always reset `elapsed_clock`
self.elapsed_clock.reset();
}
// Apply changes of editing by local time
KeyCode::Char('s') => {
if let Some(edit_time) = &mut self.edit_time.clone() {
self.edit_time_done(edit_time)
}
// always reset `elapsed_clock`
self.elapsed_clock.reset();
}
// move edit position to the left
KeyCode::Left => {
// safe unwrap because we are in `is_time_edit_mode`
self.edit_time.as_mut().unwrap().next();
}
// move edit position to the right
KeyCode::Right => {
// safe unwrap because we are in `is_time_edit_mode`
self.edit_time.as_mut().unwrap().prev();
}
// Value up
KeyCode::Up => {
// safe unwrap because of previous check in `is_time_edit_mode`
self.edit_time.as_mut().unwrap().up();
}
// Value down
KeyCode::Down => {
// safe unwrap because of previous check in `is_time_edit_mode`
self.edit_time.as_mut().unwrap().down();
}
_ => return Some(event),
},
// default mode
TuiEvent::Key(key) => match key.code {
KeyCode::Char('r') => {
// reset both clocks to use intial values
@ -187,23 +277,8 @@ impl TuiEventHandler for CountdownState {
self.edit_time_done(edit_time);
}
}
// skip editing clock
KeyCode::Esc if self.is_clock_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();
}
// skip editing by local time
KeyCode::Esc if self.is_time_edit_mode() => {
self.edit_time = None;
}
// Enter edit by local time mode
KeyCode::Char('e')
if key.modifiers.contains(KeyModifiers::CONTROL)
&& !self.is_time_edit_mode() =>
{
KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
// set `edit_time`
self.edit_time = Some(EditTimeState::new(EditTimeStateArgs {
time: self.time_to_edit(),
@ -216,9 +291,8 @@ impl TuiEventHandler for CountdownState {
self.elapsed_clock.toggle_pause();
}
}
// Enter edit clock
KeyCode::Char('e') if !self.is_clock_edit_mode() => {
// Enter edit clock mode
KeyCode::Char('e') => {
// toggle edit mode
self.clock.toggle_edit();
@ -227,53 +301,6 @@ impl TuiEventHandler for CountdownState {
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() => {
self.clock.edit_next();
}
KeyCode::Left if self.is_time_edit_mode() => {
// safe unwrap because of previous check in `is_time_edit_mode`
self.edit_time.as_mut().unwrap().next();
}
KeyCode::Right if self.is_clock_edit_mode() => {
self.clock.edit_prev();
}
KeyCode::Right if self.is_time_edit_mode() => {
// safe unwrap because of previous check in `is_time_edit_mode`
self.edit_time.as_mut().unwrap().prev();
}
KeyCode::Up if self.is_clock_edit_mode() => {
self.clock.edit_up();
}
KeyCode::Up if self.is_time_edit_mode() => {
// safe unwrap because of previous check in `is_time_edit_mode`
self.edit_time.as_mut().unwrap().up();
}
KeyCode::Down if self.is_clock_edit_mode() => {
self.clock.edit_down();
}
KeyCode::Down if self.is_time_edit_mode() => {
// safe unwrap because of previous check in `is_time_edit_mode`
self.edit_time.as_mut().unwrap().down();
}
_ => return Some(event),
},
_ => return Some(event),

View File

@ -112,62 +112,6 @@ impl StatefulWidget for Footer {
)),
Cell::from(Line::from(content_labels)),
]),
// controls
Row::new(vec![
Cell::from(Span::styled(
"controls",
Style::default().add_modifier(Modifier::BOLD),
)),
Cell::from(Line::from({
match self.app_edit_mode {
AppEditMode::None => {
let mut spans = vec![
Span::from(if self.running_clock {
"[s]top"
} else {
"[s]tart"
}),
Span::from(SPACE),
Span::from("[r]eset"),
];
if self.selected_content == Content::Pomodoro {
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[^r]eset round"),
Span::from(SPACE),
Span::from("[← →]switch work/pause"),
]);
}
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[e]dit"),
]);
if self.selected_content == Content::Countdown {
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[^e]dit by local time"),
]);
}
spans
}
_ => vec![
Span::from("[enter]apply changes"),
Span::from(SPACE),
Span::from("[esc]skip changes"),
Span::from(SPACE),
Span::from(format!(
"[{} {}]edit selection",
scrollbar::HORIZONTAL.begin,
scrollbar::HORIZONTAL.end
)), // ← →,
Span::from(SPACE),
Span::from(format!("[{}]edit up", scrollbar::VERTICAL.begin)), // ↑
Span::from(SPACE),
Span::from(format!("[{}]edit up", scrollbar::VERTICAL.end)), // ↓,
],
}
})),
]),
// appearance
Row::new(vec![
Cell::from(Span::styled(
@ -188,6 +132,89 @@ impl StatefulWidget for Footer {
)),
])),
]),
// controls - 1. row
Row::new(vec![
Cell::from(Span::styled(
"controls",
Style::default().add_modifier(Modifier::BOLD),
)),
Cell::from(Line::from({
match self.app_edit_mode {
AppEditMode::None => {
let mut spans = vec![Span::from(if self.running_clock {
"[s]top"
} else {
"[s]tart"
})];
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[e]dit"),
]);
if self.selected_content == Content::Countdown {
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[^e]dit by local time"),
]);
}
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[r]eset"),
]);
if self.selected_content == Content::Pomodoro {
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[^r]eset round"),
]);
}
spans
}
_ => vec![
Span::from("[s]ave changes"),
Span::from(SPACE),
Span::from("[^s]ave initial value"),
Span::from(SPACE),
Span::from("[esc]skip changes"),
],
}
})),
]),
// controls - 2. row
Row::new(vec![
Cell::from(Line::from("")),
Cell::from(Line::from({
match self.app_edit_mode {
AppEditMode::None => {
let mut spans = vec![];
if self.selected_content == Content::Pomodoro {
spans.extend_from_slice(&[Span::from(
"[← →]switch work/pause",
)]);
}
spans
}
_ => vec![
Span::from(format!(
// ← →,
"[{} {}]change selection",
scrollbar::HORIZONTAL.begin,
scrollbar::HORIZONTAL.end
)),
Span::from(SPACE),
Span::from(format!(
// ↑
"[{}]edit up",
scrollbar::VERTICAL.begin
)),
Span::from(SPACE),
Span::from(format!(
// ↓
"[{}]edit up",
scrollbar::VERTICAL.end
)),
],
}
})),
]),
],
widths,
)

View File

@ -5,10 +5,9 @@ use crate::{
utils::center,
widgets::clock::{ClockState, ClockStateArgs, ClockWidget, Countdown},
};
use crossterm::event::KeyModifiers;
use crossterm::event::{KeyCode, KeyModifiers};
use ratatui::{
buffer::Buffer,
crossterm::event::KeyCode,
layout::{Constraint, Layout, Rect},
text::Line,
widgets::{StatefulWidget, Widget},
@ -141,46 +140,69 @@ impl TuiEventHandler for PomodoroState {
self.get_clock_mut().tick();
self.get_clock_mut().update_done_count();
}
// EDIT mode
TuiEvent::Key(key) if edit_mode => match key.code {
// Skip changes
KeyCode::Esc => {
let clock = self.get_clock_mut();
// Important: set current value first
clock.set_current_value(*clock.get_prev_value());
// before toggling back to non-edit mode
clock.toggle_edit();
}
// Apply changes and update initial value
KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => {
self.get_clock_mut().toggle_edit();
// update initial value
let c = *self.get_clock().get_current_value();
self.get_clock_mut().set_initial_value(c);
}
// Apply changes
KeyCode::Char('s') => {
self.get_clock_mut().toggle_edit();
}
// Value up
KeyCode::Up => {
self.get_clock_mut().edit_up();
}
// Value down
KeyCode::Down => {
self.get_clock_mut().edit_down();
}
// move edit position to the left
KeyCode::Left => {
self.get_clock_mut().edit_next();
}
// move edit position to the right
KeyCode::Right => {
self.get_clock_mut().edit_prev();
}
_ => return Some(event),
},
// default mode
TuiEvent::Key(key) => match key.code {
// Toggle run/pause
KeyCode::Char('s') => {
self.get_clock_mut().toggle_pause();
}
// 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 => {
KeyCode::Char('e') => {
self.get_clock_mut().toggle_edit();
}
KeyCode::Left if edit_mode => {
self.get_clock_mut().edit_next();
}
// toggle WORK/PAUSE
KeyCode::Left => {
// `next` is acting as same as a `prev` function, we don't have
// `next` is acting as same as a "prev" function we don't have
self.next();
}
KeyCode::Right if edit_mode => {
self.get_clock_mut().edit_prev();
}
// toggle WORK/PAUSE
KeyCode::Right => {
self.next();
}
KeyCode::Up if edit_mode => {
self.get_clock_mut().edit_up();
}
KeyCode::Down if edit_mode => {
self.get_clock_mut().edit_down();
}
// reset round
KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
self.round = 1;
}
// reset values
KeyCode::Char('r') => {
// count number of finished rounds of WORK before resetting the clock
if self.get_mode() == &Mode::Work && self.get_clock().is_done() {

View File

@ -39,39 +39,53 @@ impl TuiEventHandler for TimerState {
self.clock.tick();
self.clock.update_done_count();
}
TuiEvent::Key(key) => match key.code {
KeyCode::Char('s') => {
self.clock.toggle_pause();
}
KeyCode::Char('r') => {
self.clock.reset();
}
KeyCode::Esc if edit_mode => {
// EDIT mode
TuiEvent::Key(key) if edit_mode => match key.code {
// Skip changes
KeyCode::Esc => {
// 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 => {
// Apply changes
KeyCode::Char('s') => {
self.clock.toggle_edit();
}
KeyCode::Char('e') if !edit_mode => {
self.clock.toggle_edit();
}
KeyCode::Left if edit_mode => {
// move change position to the left
KeyCode::Left => {
self.clock.edit_next();
}
KeyCode::Right if edit_mode => {
// move change position to the right
KeyCode::Right => {
self.clock.edit_prev();
}
KeyCode::Up if edit_mode => {
// change value up
KeyCode::Up => {
self.clock.edit_up();
}
KeyCode::Down if edit_mode => {
// change value down
KeyCode::Down => {
self.clock.edit_down();
}
_ => return Some(event),
},
// default mode
TuiEvent::Key(key) => match key.code {
// Toggle run/pause
KeyCode::Char('s') => {
self.clock.toggle_pause();
}
// reset clock
KeyCode::Char('r') => {
self.clock.reset();
}
// enter edit mode
KeyCode::Char('e') => {
self.clock.toggle_edit();
}
_ => return Some(event),
},
_ => return Some(event),
}
None