* simplify footer

* improve: &T vs. &mut T

* remove header

* adjust table space

* update README
This commit is contained in:
Jens K. 2024-12-23 14:24:15 +01:00 committed by GitHub
parent 98ee2bc16b
commit d86f8905f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 71 additions and 109 deletions

View File

@ -2,9 +2,6 @@
**Pronounced `/ˈɪmə/` or `/ˈtaɪmər/`.** **Pronounced `/ˈɪmə/` or `/ˈtaɪmər/`.**
> [!WARNING]
> _Still WIP_
# About # About
`tim:r` is a TUI app to help you to organize one of the most important thing you have in live: `time`! `tim:r` is a TUI app to help you to organize one of the most important thing you have in live: `time`!
@ -14,7 +11,7 @@
- `[p]omodoro` Organize your working time to be focused all the time by following the [Pomodoro Technique](https://en.wikipedia.org/wiki/Pomodoro_Technique). - `[p]omodoro` Organize your working time to be focused all the time by following the [Pomodoro Technique](https://en.wikipedia.org/wiki/Pomodoro_Technique).
It's built with [`ratatui`](https://ratatui.rs/) ([Rust](https://www.rust-lang.org/)) It's built with [`ratatui`](https://ratatui.rs/) ([Rust](https://www.rust-lang.org/)).
# Screens # Screens
@ -33,6 +30,7 @@ Options:
-d, --decis Wether to show deciseconds or not -d, --decis Wether to show deciseconds or not
-m, --mode <CONTENT> Mode to start with: [t]imer, [c]ountdown, [p]omodoro [default: timer] [possible values: countdown, timer, pomodoro] -m, --mode <CONTENT> Mode to start with: [t]imer, [c]ountdown, [p]omodoro [default: timer] [possible values: countdown, timer, pomodoro]
-s, --style <STYLE> Style to display time with: [b]old, [t]hick, [c]ross, [e]mpty [default: bold] [possible values: bold, empty, thick, cross] -s, --style <STYLE> Style to display time with: [b]old, [t]hick, [c]ross, [e]mpty [default: bold] [possible values: bold, empty, thick, cross]
-r, --reset Reset stored values to default
-h, --help Print help -h, --help Print help
``` ```
@ -40,21 +38,21 @@ Options:
## Requirements ## Requirements
### Nix (recommend) ### Nix users (recommend)
`cd` into root directory. `cd` into root directory.
[`direnv`](https://direnv.net) users run `direnv allow` once to install dependencies. Others run `nix develop`. If you have [`direnv`](https://direnv.net) installed, run `direnv allow` once to install dependencies. In other case run `nix develop`.
### Non Nix user ### Non Nix users
- [`Rust`](https://www.rust-lang.org/learn/get-started) - [`Rust`](https://www.rust-lang.org/learn/get-started)
- [`Clippy`](https://github.com/rust-lang/rust-clippy) - [`Clippy`](https://github.com/rust-lang/rust-clippy)
- [`rustfmt`](https://github.com/rust-lang/rustfmt) - [`rustfmt`](https://github.com/rust-lang/rustfmt)
- [`just`](https://just.systems) - [`just`](https://just.systems)
### Commands to `run`, `lint`, `format` etc. ### Commands
```sh ```sh
just --list just --list
@ -87,10 +85,20 @@ nix build .#windows
# Misc. # Misc.
## Persistant app state
Stored on file system.
- `Linux`
```sh
cat ~/.local/state/timr/data/timr.data
```
## Logs ## Logs
In `debug` mode only. In `debug` mode only.
- `Linux`
```sh ```sh
tail -f ~/.local/state/timr/logs/timr.log tail -f ~/.local/state/timr/logs/timr.log
``` ```

View File

@ -7,8 +7,7 @@ use crate::{
widgets::{ widgets::{
clock::{self, Clock, ClockArgs, Style}, clock::{self, Clock, ClockArgs, Style},
countdown::{Countdown, CountdownWidget}, countdown::{Countdown, CountdownWidget},
footer::{Footer, FooterArgs}, footer::Footer,
header::Header,
pomodoro::{Mode as PomodoroMode, Pomodoro, PomodoroArgs, PomodoroWidget}, pomodoro::{Mode as PomodoroMode, Pomodoro, PomodoroArgs, PomodoroWidget},
timer::{Timer, TimerWidget}, timer::{Timer, TimerWidget},
}, },
@ -166,10 +165,10 @@ impl App {
self.mode != Mode::Quit self.mode != Mode::Quit
} }
fn is_edit_mode(&mut self) -> bool { fn is_edit_mode(&self) -> bool {
match self.content { match self.content {
Content::Countdown => self.countdown.get_clock().clone().is_edit_mode(), Content::Countdown => self.countdown.get_clock().is_edit_mode(),
Content::Timer => self.timer.get_clock().clone().is_edit_mode(), Content::Timer => self.timer.get_clock().is_edit_mode(),
Content::Pomodoro => self.pomodoro.get_clock().is_edit_mode(), Content::Pomodoro => self.pomodoro.get_clock().is_edit_mode(),
} }
} }
@ -250,24 +249,21 @@ impl AppWidget {
impl StatefulWidget for AppWidget { impl StatefulWidget for AppWidget {
type State = App; type State = App;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let vertical = Layout::vertical([ let [v0, v1] = Layout::vertical([
Constraint::Length(1),
Constraint::Percentage(100), Constraint::Percentage(100),
Constraint::Length(if state.show_menu { 5 } else { 1 }), Constraint::Length(if state.show_menu { 4 } else { 1 }),
]); ])
let [v0, v1, v2] = vertical.areas(area); .areas(area);
// header
Header::new(true).render(v0, buf);
// content // content
self.render_content(v1, buf, state); self.render_content(v0, buf, state);
// footer // footer
Footer::new(FooterArgs { Footer {
show_menu: state.show_menu, show_menu: state.show_menu,
running_clock: state.clock_is_running(), running_clock: state.clock_is_running(),
selected_content: state.content, selected_content: state.content,
edit_mode: state.is_edit_mode(), edit_mode: state.is_edit_mode(),
}) }
.render(v2, buf); .render(v1, buf);
} }
} }

View File

@ -1,6 +1,5 @@
pub mod clock; pub mod clock;
pub mod countdown; pub mod countdown;
pub mod footer; pub mod footer;
pub mod header;
pub mod pomodoro; pub mod pomodoro;
pub mod timer; pub mod timer;

View File

@ -207,7 +207,7 @@ impl<T> Clock<T> {
self.update_mode(); self.update_mode();
} }
pub fn get_mode(&mut self) -> &Mode { pub fn get_mode(&self) -> &Mode {
&self.mode &self.mode
} }
@ -215,7 +215,7 @@ impl<T> Clock<T> {
self.mode == Mode::Tick self.mode == Mode::Tick
} }
pub fn is_edit_mode(&mut self) -> bool { pub fn is_edit_mode(&self) -> bool {
matches!(self.mode, Mode::Editable(_, _)) matches!(self.mode, Mode::Editable(_, _))
} }
@ -301,7 +301,7 @@ impl<T> Clock<T> {
(self.current_value.subsec_millis() / 100) as u64 (self.current_value.subsec_millis() / 100) as u64
} }
pub fn is_done(&mut self) -> bool { pub fn is_done(&self) -> bool {
self.mode == Mode::Done self.mode == Mode::Done
} }
@ -375,12 +375,12 @@ impl Clock<Countdown> {
pub fn tick(&mut self) { pub fn tick(&mut self) {
if self.mode == Mode::Tick { if self.mode == Mode::Tick {
self.current_value = self.current_value.saturating_sub(self.tick_value); self.current_value = self.current_value.saturating_sub(self.tick_value);
self.check_done(); self.set_done();
self.update_format(); self.update_format();
} }
} }
pub fn check_done(&mut self) { pub fn set_done(&mut self) {
if self.current_value.is_zero() { if self.current_value.is_zero() {
self.mode = Mode::Done; self.mode = Mode::Done;
} }
@ -447,12 +447,12 @@ impl Clock<Timer> {
pub fn tick(&mut self) { pub fn tick(&mut self) {
if self.mode == Mode::Tick { if self.mode == Mode::Tick {
self.current_value = self.current_value.saturating_add(self.tick_value); self.current_value = self.current_value.saturating_add(self.tick_value);
self.check_done(); self.set_done();
self.update_format(); self.update_format();
} }
} }
fn check_done(&mut self) { fn set_done(&mut self) {
if self.current_value >= MAX_DURATION { if self.current_value >= MAX_DURATION {
self.mode = Mode::Done; self.mode = Mode::Done;
} }

View File

@ -12,46 +12,25 @@ use ratatui::{
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Footer { pub struct Footer {
show_menu: bool,
running_clock: bool,
selected_content: Content,
content_labels: BTreeMap<Content, String>,
edit_mode: bool,
}
pub struct FooterArgs {
pub show_menu: bool, pub show_menu: bool,
pub running_clock: bool, pub running_clock: bool,
pub selected_content: Content, pub selected_content: Content,
pub edit_mode: bool, pub edit_mode: bool,
} }
impl Footer {
pub fn new(args: FooterArgs) -> Self {
let FooterArgs {
show_menu,
running_clock,
selected_content,
edit_mode,
} = args;
Self {
show_menu,
running_clock,
selected_content,
edit_mode,
content_labels: BTreeMap::from([
(Content::Countdown, "[c]ountdown".into()),
(Content::Timer, "[t]imer".into()),
(Content::Pomodoro, "[p]omodoro".into()),
]),
}
}
}
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 content_labels: BTreeMap<Content, &str> = BTreeMap::from([
(Content::Countdown, "[c]ountdown"),
(Content::Timer, "[t]imer"),
(Content::Pomodoro, "[p]omodoro"),
]);
let [_, area] =
Layout::horizontal([Constraint::Length(1), Constraint::Percentage(100)]).areas(area);
let [border_area, menu_area] = let [border_area, menu_area] =
Layout::vertical([Constraint::Length(2), Constraint::Percentage(100)]).areas(area); Layout::vertical([Constraint::Length(1), 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 {""}})
@ -59,17 +38,16 @@ 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 content_labels: Vec<Span> = self let content_labels: Vec<Span> = content_labels
.content_labels
.iter() .iter()
.enumerate() .enumerate()
.map(|(index, (content, label))| { .map(|(index, (content, label))| {
let mut style = Style::default(); let mut style = Style::default();
// Add space for all except last // Add space for all except last
let label = if index < self.content_labels.len() - 1 { let label = if index < content_labels.len() - 1 {
format!("{} ", label) format!("{} ", label)
} else { } else {
label.into() label.to_string()
}; };
if *content == self.selected_content { if *content == self.selected_content {
style = style.add_modifier(Modifier::BOLD); style = style.add_modifier(Modifier::BOLD);
@ -144,6 +122,7 @@ impl Widget for Footer {
], ],
widths, widths,
) )
.column_spacing(1)
.render(menu_area, buf); .render(menu_area, buf);
} }
} }

View File

@ -1,30 +0,0 @@
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
text::Span,
widgets::Widget,
};
#[derive(Debug, Clone)]
pub struct Header {
show_fps: bool,
}
impl Header {
pub fn new(show_fps: bool) -> Self {
Self { show_fps }
}
}
impl Widget for Header {
fn render(self, area: Rect, buf: &mut Buffer) {
let fps_txt = if self.show_fps { "FPS (soon)" } else { "" };
let fps_span = Span::raw(fps_txt);
let fps_width = fps_span.width().try_into().unwrap_or(0);
let [h1, h2] =
Layout::horizontal([Constraint::Fill(1), Constraint::Length(fps_width)]).areas(area);
Span::raw("tim:r").render(h1, buf);
fps_span.render(h2, buf);
}
}

View File

@ -32,12 +32,18 @@ pub struct ClockMap {
} }
impl ClockMap { impl ClockMap {
fn get(&mut self, mode: &Mode) -> &mut Clock<Countdown> { fn get_mut(&mut self, mode: &Mode) -> &mut Clock<Countdown> {
match mode { match mode {
Mode::Work => &mut self.work, Mode::Work => &mut self.work,
Mode::Pause => &mut self.pause, Mode::Pause => &mut self.pause,
} }
} }
fn get(&self, mode: &Mode) -> &Clock<Countdown> {
match mode {
Mode::Work => &self.work,
Mode::Pause => &self.pause,
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -88,7 +94,11 @@ impl Pomodoro {
} }
} }
pub fn get_clock(&mut self) -> &mut Clock<Countdown> { fn get_clock_mut(&mut self) -> &mut Clock<Countdown> {
self.clock_map.get_mut(&self.mode)
}
pub fn get_clock(&self) -> &Clock<Countdown> {
self.clock_map.get(&self.mode) self.clock_map.get(&self.mode)
} }
@ -127,36 +137,36 @@ impl EventHandler for Pomodoro {
let edit_mode = self.get_clock().is_edit_mode(); let edit_mode = self.get_clock().is_edit_mode();
match event { match event {
Event::Tick => { Event::Tick => {
self.get_clock().tick(); self.get_clock_mut().tick();
} }
Event::Key(key) => match key.code { Event::Key(key) => match key.code {
KeyCode::Char('s') => { KeyCode::Char('s') => {
self.get_clock().toggle_pause(); self.get_clock_mut().toggle_pause();
} }
KeyCode::Char('e') => { KeyCode::Char('e') => {
self.get_clock().toggle_edit(); self.get_clock_mut().toggle_edit();
} }
KeyCode::Left if edit_mode => { KeyCode::Left if edit_mode => {
self.get_clock().edit_next(); self.get_clock_mut().edit_next();
} }
KeyCode::Left => { 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(); self.next();
} }
KeyCode::Right if edit_mode => { KeyCode::Right if edit_mode => {
self.get_clock().edit_prev(); self.get_clock_mut().edit_prev();
} }
KeyCode::Right => { KeyCode::Right => {
self.next(); self.next();
} }
KeyCode::Up if edit_mode => { KeyCode::Up if edit_mode => {
self.get_clock().edit_up(); self.get_clock_mut().edit_up();
} }
KeyCode::Down if edit_mode => { KeyCode::Down if edit_mode => {
self.get_clock().edit_down(); self.get_clock_mut().edit_down();
} }
KeyCode::Char('r') => { KeyCode::Char('r') => {
self.get_clock().reset(); self.get_clock_mut().reset();
} }
_ => return Some(event), _ => return Some(event),
}, },
@ -176,7 +186,7 @@ impl StatefulWidget for PomodoroWidget {
(format!( (format!(
"Pomodoro {} {}", "Pomodoro {} {}",
state.mode.clone(), state.mode.clone(),
state.get_clock().get_mode() state.get_clock_mut().get_mode()
)) ))
.to_uppercase(), .to_uppercase(),
); );
@ -196,7 +206,7 @@ impl StatefulWidget for PomodoroWidget {
let [v1, v2] = let [v1, v2] =
Layout::vertical(Constraint::from_lengths([clock_widget.get_height(), 1])).areas(area); Layout::vertical(Constraint::from_lengths([clock_widget.get_height(), 1])).areas(area);
clock_widget.render(v1, buf, state.get_clock()); clock_widget.render(v1, buf, state.get_clock_mut());
label.centered().render(v2, buf); label.centered().render(v2, buf);
} }
} }