* 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/`.**
> [!WARNING]
> _Still WIP_
# About
`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).
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
@ -33,6 +30,7 @@ Options:
-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]
-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
```
@ -40,21 +38,21 @@ Options:
## Requirements
### Nix (recommend)
### Nix users (recommend)
`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)
- [`Clippy`](https://github.com/rust-lang/rust-clippy)
- [`rustfmt`](https://github.com/rust-lang/rustfmt)
- [`just`](https://just.systems)
### Commands to `run`, `lint`, `format` etc.
### Commands
```sh
just --list
@ -87,10 +85,20 @@ nix build .#windows
# Misc.
## Persistant app state
Stored on file system.
- `Linux`
```sh
cat ~/.local/state/timr/data/timr.data
```
## Logs
In `debug` mode only.
- `Linux`
```sh
tail -f ~/.local/state/timr/logs/timr.log
```

View File

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

View File

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

View File

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

View File

@ -12,46 +12,25 @@ use ratatui::{
#[derive(Debug, Clone)]
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 running_clock: bool,
pub selected_content: Content,
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 {
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] =
Layout::vertical([Constraint::Length(2), Constraint::Percentage(100)]).areas(area);
Layout::vertical([Constraint::Length(1), Constraint::Percentage(100)]).areas(area);
Block::new()
.borders(Borders::TOP)
.title(format! {"[m]enu {:} ", if self.show_menu {""} else {""}})
@ -59,17 +38,16 @@ impl Widget for Footer {
.render(border_area, buf);
// show menu
if self.show_menu {
let content_labels: Vec<Span> = self
.content_labels
let content_labels: Vec<Span> = content_labels
.iter()
.enumerate()
.map(|(index, (content, label))| {
let mut style = Style::default();
// 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)
} else {
label.into()
label.to_string()
};
if *content == self.selected_content {
style = style.add_modifier(Modifier::BOLD);
@ -144,6 +122,7 @@ impl Widget for Footer {
],
widths,
)
.column_spacing(1)
.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 {
fn get(&mut self, mode: &Mode) -> &mut Clock<Countdown> {
fn get_mut(&mut self, mode: &Mode) -> &mut Clock<Countdown> {
match mode {
Mode::Work => &mut self.work,
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)]
@ -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)
}
@ -127,36 +137,36 @@ impl EventHandler for Pomodoro {
let edit_mode = self.get_clock().is_edit_mode();
match event {
Event::Tick => {
self.get_clock().tick();
self.get_clock_mut().tick();
}
Event::Key(key) => match key.code {
KeyCode::Char('s') => {
self.get_clock().toggle_pause();
self.get_clock_mut().toggle_pause();
}
KeyCode::Char('e') => {
self.get_clock().toggle_edit();
self.get_clock_mut().toggle_edit();
}
KeyCode::Left if edit_mode => {
self.get_clock().edit_next();
self.get_clock_mut().edit_next();
}
KeyCode::Left => {
// `next` is acting as same as a `prev` function, we don't have
self.next();
}
KeyCode::Right if edit_mode => {
self.get_clock().edit_prev();
self.get_clock_mut().edit_prev();
}
KeyCode::Right => {
self.next();
}
KeyCode::Up if edit_mode => {
self.get_clock().edit_up();
self.get_clock_mut().edit_up();
}
KeyCode::Down if edit_mode => {
self.get_clock().edit_down();
self.get_clock_mut().edit_down();
}
KeyCode::Char('r') => {
self.get_clock().reset();
self.get_clock_mut().reset();
}
_ => return Some(event),
},
@ -176,7 +186,7 @@ impl StatefulWidget for PomodoroWidget {
(format!(
"Pomodoro {} {}",
state.mode.clone(),
state.get_clock().get_mode()
state.get_clock_mut().get_mode()
))
.to_uppercase(),
);
@ -196,7 +206,7 @@ impl StatefulWidget for PomodoroWidget {
let [v1, v2] =
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);
}
}