menu: appearance + controls (#22)

This commit is contained in:
Jens K. 2024-12-23 10:39:53 +01:00 committed by GitHub
parent c9b444e91a
commit 98ee2bc16b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 125 additions and 14 deletions

View File

@ -7,7 +7,7 @@ use crate::{
widgets::{ widgets::{
clock::{self, Clock, ClockArgs, Style}, clock::{self, Clock, ClockArgs, Style},
countdown::{Countdown, CountdownWidget}, countdown::{Countdown, CountdownWidget},
footer::Footer, footer::{Footer, FooterArgs},
header::Header, header::Header,
pomodoro::{Mode as PomodoroMode, Pomodoro, PomodoroArgs, PomodoroWidget}, pomodoro::{Mode as PomodoroMode, Pomodoro, PomodoroArgs, PomodoroWidget},
timer::{Timer, TimerWidget}, timer::{Timer, TimerWidget},
@ -59,6 +59,7 @@ pub struct App {
pub struct AppArgs { pub struct AppArgs {
pub style: Style, pub style: Style,
pub with_decis: bool, pub with_decis: bool,
pub show_menu: bool,
pub content: Content, pub content: Content,
pub pomodoro_mode: PomodoroMode, pub pomodoro_mode: PomodoroMode,
pub initial_value_work: Duration, pub initial_value_work: Duration,
@ -76,6 +77,7 @@ impl From<(Args, AppStorage)> for AppArgs {
fn from((args, stg): (Args, AppStorage)) -> Self { fn from((args, stg): (Args, AppStorage)) -> Self {
AppArgs { AppArgs {
with_decis: args.decis || stg.with_decis, with_decis: args.decis || stg.with_decis,
show_menu: stg.show_menu,
content: args.mode.unwrap_or(stg.content), content: args.mode.unwrap_or(stg.content),
style: args.style.unwrap_or(stg.style), style: args.style.unwrap_or(stg.style),
pomodoro_mode: stg.pomodoro_mode, pomodoro_mode: stg.pomodoro_mode,
@ -94,6 +96,7 @@ impl App {
pub fn new(args: AppArgs) -> Self { pub fn new(args: AppArgs) -> Self {
let AppArgs { let AppArgs {
style, style,
show_menu,
initial_value_work, initial_value_work,
initial_value_pause, initial_value_pause,
initial_value_countdown, initial_value_countdown,
@ -108,7 +111,7 @@ impl App {
Self { Self {
mode: Mode::Running, mode: Mode::Running,
content, content,
show_menu: false, show_menu,
style, style,
with_decis, with_decis,
countdown: Countdown::new(Clock::<clock::Countdown>::new(ClockArgs { countdown: Countdown::new(Clock::<clock::Countdown>::new(ClockArgs {
@ -163,6 +166,22 @@ impl App {
self.mode != Mode::Quit self.mode != Mode::Quit
} }
fn is_edit_mode(&mut self) -> bool {
match self.content {
Content::Countdown => self.countdown.get_clock().clone().is_edit_mode(),
Content::Timer => self.timer.get_clock().clone().is_edit_mode(),
Content::Pomodoro => self.pomodoro.get_clock().is_edit_mode(),
}
}
fn clock_is_running(&mut self) -> bool {
match self.content {
Content::Countdown => self.countdown.get_clock().clone().is_running(),
Content::Timer => self.timer.get_clock().clone().is_running(),
Content::Pomodoro => self.pomodoro.get_clock().is_running(),
}
}
fn handle_key_event(&mut self, key: KeyEvent) { fn handle_key_event(&mut self, key: KeyEvent) {
debug!("Received key {:?}", key.code); debug!("Received key {:?}", key.code);
match key.code { match key.code {
@ -234,12 +253,21 @@ impl StatefulWidget for AppWidget {
let vertical = Layout::vertical([ let vertical = Layout::vertical([
Constraint::Length(1), Constraint::Length(1),
Constraint::Percentage(100), Constraint::Percentage(100),
Constraint::Length(if state.show_menu { 2 } else { 1 }), Constraint::Length(if state.show_menu { 5 } else { 1 }),
]); ]);
let [v0, v1, v2] = vertical.areas(area); let [v0, v1, v2] = vertical.areas(area);
// header
Header::new(true).render(v0, buf); Header::new(true).render(v0, buf);
// content
self.render_content(v1, buf, state); self.render_content(v1, buf, state);
Footer::new(state.show_menu, state.content).render(v2, buf); // footer
Footer::new(FooterArgs {
show_menu: state.show_menu,
running_clock: state.clock_is_running(),
selected_content: state.content,
edit_mode: state.is_edit_mode(),
})
.render(v2, buf);
} }
} }

View File

@ -211,6 +211,10 @@ impl<T> Clock<T> {
&self.mode &self.mode
} }
pub fn is_running(&self) -> bool {
self.mode == Mode::Tick
}
pub fn is_edit_mode(&mut self) -> bool { pub fn is_edit_mode(&mut self) -> bool {
matches!(self.mode, Mode::Editable(_, _)) matches!(self.mode, Mode::Editable(_, _))
} }

View File

@ -7,21 +7,38 @@ use ratatui::{
style::{Modifier, Style}, style::{Modifier, Style},
symbols, symbols,
text::{Line, Span}, text::{Line, Span},
widgets::{Block, Borders, Widget}, widgets::{Block, Borders, Cell, Row, Table, Widget},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Footer { pub struct Footer {
show_menu: bool, show_menu: bool,
running_clock: bool,
selected_content: Content, selected_content: Content,
content_labels: BTreeMap<Content, String>, content_labels: BTreeMap<Content, String>,
edit_mode: bool,
}
pub struct FooterArgs {
pub show_menu: bool,
pub running_clock: bool,
pub selected_content: Content,
pub edit_mode: bool,
} }
impl Footer { impl Footer {
pub fn new(show_menu: bool, selected_content: Content) -> Self { pub fn new(args: FooterArgs) -> Self {
let FooterArgs {
show_menu,
running_clock,
selected_content,
edit_mode,
} = args;
Self { Self {
show_menu, show_menu,
running_clock,
selected_content, selected_content,
edit_mode,
content_labels: BTreeMap::from([ content_labels: BTreeMap::from([
(Content::Countdown, "[c]ountdown".into()), (Content::Countdown, "[c]ountdown".into()),
(Content::Timer, "[t]imer".into()), (Content::Timer, "[t]imer".into()),
@ -34,7 +51,7 @@ impl Footer {
impl Widget for Footer { impl Widget for Footer {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let [border_area, menu_area] = let [border_area, menu_area] =
Layout::vertical([Constraint::Length(1), Constraint::Fill(0)]).areas(area); Layout::vertical([Constraint::Length(2), Constraint::Percentage(100)]).areas(area);
Block::new() Block::new()
.borders(Borders::TOP) .borders(Borders::TOP)
.title(format! {"[m]enu {:} ", if self.show_menu {""} else {""}}) .title(format! {"[m]enu {:} ", if self.show_menu {""} else {""}})
@ -42,12 +59,7 @@ impl Widget for Footer {
.render(border_area, buf); .render(border_area, buf);
// show menu // show menu
if self.show_menu { if self.show_menu {
let [title_area, labels_area] = let content_labels: Vec<Span> = self
Layout::horizontal([Constraint::Length(12), Constraint::Fill(0)]).areas(menu_area);
Span::styled("screens", Style::default().add_modifier(Modifier::BOLD))
.render(title_area, buf);
let spans: Vec<Span> = self
.content_labels .content_labels
.iter() .iter()
.enumerate() .enumerate()
@ -65,7 +77,74 @@ impl Widget for Footer {
Span::styled(label, style) Span::styled(label, style)
}) })
.collect(); .collect();
Line::from(spans).render(labels_area, buf);
const SPACE: &str = " ";
let widths = [Constraint::Length(12), Constraint::Percentage(100)];
Table::new(
[
// content
Row::new(vec![
Cell::from(Span::styled(
"screens",
Style::default().add_modifier(Modifier::BOLD),
)),
Cell::from(Line::from(content_labels)),
]),
// format
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"),
])),
]),
// edit
Row::new(vec![
Cell::from(Span::styled(
"controls",
Style::default().add_modifier(Modifier::BOLD),
)),
Cell::from(Line::from({
if self.edit_mode {
vec![
Span::from("[e]dit done"),
Span::from(SPACE),
Span::from("[← →]edit selection"),
Span::from(SPACE),
Span::from("[↑]edit up"),
Span::from(SPACE),
Span::from("[↓]edit down"),
]
} else {
let mut spans = vec![
Span::from(if self.running_clock {
"[s]top"
} else {
"[s]tart"
}),
Span::from(SPACE),
Span::from("[r]eset"),
Span::from(SPACE),
Span::from("[e]dit"),
];
if self.selected_content == Content::Pomodoro {
spans.extend_from_slice(&[
Span::from(SPACE),
Span::from("[← →]switch work/pause"),
]);
}
spans
}
})),
]),
],
widths,
)
.render(menu_area, buf);
} }
} }
} }