Cleanup (#23)
* simplify footer * improve: &T vs. &mut T * remove header * adjust table space * update README
This commit is contained in:
parent
98ee2bc16b
commit
d86f8905f2
24
README.md
24
README.md
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
**Pronounced `/ˈtʌɪmə/` or `/ˈtaɪmər/`.**
|
**Pronounced `/ˈtʌɪ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
|
||||||
```
|
```
|
||||||
|
|||||||
28
src/app.rs
28
src/app.rs
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user