Persist app state (#21)

This commit is contained in:
Jens K. 2024-12-22 18:56:55 +01:00 committed by GitHub
parent 2cf411e2ae
commit c9b444e91a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 336 additions and 112 deletions

13
Cargo.lock generated
View File

@ -893,6 +893,18 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@ -1049,6 +1061,7 @@ dependencies = [
"futures", "futures",
"ratatui", "ratatui",
"serde", "serde",
"serde_json",
"strum", "strum",
"tokio", "tokio",
"tokio-stream", "tokio-stream",

View File

@ -9,6 +9,7 @@ crossterm = {version = "0.28.1", features = ["event-stream", "serde"] }
color-eyre = "0.6.2" color-eyre = "0.6.2"
futures = "0.3" futures = "0.3"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
strum = { version = "0.26.3", features = ["derive"] } strum = { version = "0.26.3", features = ["derive"] }
tokio = { version = "1.41.1", features = ["full"] } tokio = { version = "1.41.1", features = ["full"] }
tokio-stream = "0.1.16" tokio-stream = "0.1.16"

View File

@ -1,17 +1,19 @@
use crate::{ use crate::{
args::{Args, ClockStyle, Content}, args::Args,
constants::TICK_VALUE_MS, constants::TICK_VALUE_MS,
events::{Event, EventHandler, Events}, events::{Event, EventHandler, Events},
storage::AppStorage,
terminal::Terminal, terminal::Terminal,
widgets::{ widgets::{
clock::{self, Clock, ClockArgs}, clock::{self, Clock, ClockArgs, Style},
countdown::{Countdown, CountdownWidget}, countdown::{Countdown, CountdownWidget},
footer::Footer, footer::Footer,
header::Header, header::Header,
pomodoro::{Pomodoro, PomodoroArgs, PomodoroWidget}, pomodoro::{Mode as PomodoroMode, Pomodoro, PomodoroArgs, PomodoroWidget},
timer::{Timer, TimerWidget}, timer::{Timer, TimerWidget},
}, },
}; };
use clap::ValueEnum;
use color_eyre::Result; use color_eyre::Result;
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
@ -19,9 +21,23 @@ use ratatui::{
layout::{Constraint, Layout, Rect}, layout::{Constraint, Layout, Rect},
widgets::{StatefulWidget, Widget}, widgets::{StatefulWidget, Widget},
}; };
use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
use tracing::debug; use tracing::debug;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default, Serialize, Deserialize,
)]
pub enum Content {
#[default]
#[value(name = "countdown", alias = "c")]
Countdown,
#[value(name = "timer", alias = "t")]
Timer,
#[value(name = "pomodoro", alias = "p")]
Pomodoro,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode { enum Mode {
Running, Running,
@ -36,49 +52,92 @@ pub struct App {
countdown: Countdown, countdown: Countdown,
timer: Timer, timer: Timer,
pomodoro: Pomodoro, pomodoro: Pomodoro,
clock_style: ClockStyle, style: Style,
with_decis: bool, with_decis: bool,
} }
pub struct AppArgs {
pub style: Style,
pub with_decis: bool,
pub content: Content,
pub pomodoro_mode: PomodoroMode,
pub initial_value_work: Duration,
pub current_value_work: Duration,
pub initial_value_pause: Duration,
pub current_value_pause: Duration,
pub initial_value_countdown: Duration,
pub current_value_countdown: Duration,
pub current_value_timer: Duration,
}
/// Getting `AppArgs` by merging `Args` and `AppStorage`.
/// `Args` wins btw.
impl From<(Args, AppStorage)> for AppArgs {
fn from((args, stg): (Args, AppStorage)) -> Self {
AppArgs {
with_decis: args.decis || stg.with_decis,
content: args.mode.unwrap_or(stg.content),
style: args.style.unwrap_or(stg.style),
pomodoro_mode: stg.pomodoro_mode,
initial_value_work: args.work.unwrap_or(stg.inital_value_work),
current_value_work: stg.current_value_work,
initial_value_pause: args.pause,
current_value_pause: stg.current_value_pause,
initial_value_countdown: args.countdown,
current_value_countdown: stg.current_value_countdown,
current_value_timer: stg.current_value_timer,
}
}
}
impl App { impl App {
pub fn new(args: Args) -> Self { pub fn new(args: AppArgs) -> Self {
let Args { let AppArgs {
style, style,
work: work_initial_value, initial_value_work,
pause: pause_initial_value, initial_value_pause,
mode: content, initial_value_countdown,
countdown: countdown_initial_value, current_value_work,
decis: with_decis, current_value_pause,
.. current_value_countdown,
current_value_timer,
content,
with_decis,
pomodoro_mode,
} = args; } = args;
Self { Self {
mode: Mode::Running, mode: Mode::Running,
content, content,
show_menu: false, show_menu: false,
clock_style: style, style,
with_decis, with_decis,
countdown: Countdown::new(Clock::<clock::Countdown>::new(ClockArgs { countdown: Countdown::new(Clock::<clock::Countdown>::new(ClockArgs {
initial_value: countdown_initial_value, initial_value: initial_value_countdown,
current_value: current_value_countdown,
tick_value: Duration::from_millis(TICK_VALUE_MS), tick_value: Duration::from_millis(TICK_VALUE_MS),
style, style,
with_decis, with_decis,
})), })),
timer: Timer::new(Clock::<clock::Timer>::new(ClockArgs { timer: Timer::new(Clock::<clock::Timer>::new(ClockArgs {
initial_value: Duration::ZERO, initial_value: Duration::ZERO,
current_value: current_value_timer,
tick_value: Duration::from_millis(TICK_VALUE_MS), tick_value: Duration::from_millis(TICK_VALUE_MS),
style, style,
with_decis, with_decis,
})), })),
pomodoro: Pomodoro::new(PomodoroArgs { pomodoro: Pomodoro::new(PomodoroArgs {
work: work_initial_value, mode: pomodoro_mode,
pause: pause_initial_value, initial_value_work,
current_value_work,
initial_value_pause,
current_value_pause,
style, style,
with_decis, with_decis,
}), }),
} }
} }
pub async fn run(&mut self, mut terminal: Terminal, mut events: Events) -> Result<()> { pub async fn run(mut self, mut terminal: Terminal, mut events: Events) -> Result<Self> {
while self.is_running() { while self.is_running() {
if let Some(event) = events.next().await { if let Some(event) = events.next().await {
// Pipe events into subviews and handle only 'unhandled' events afterwards // Pipe events into subviews and handle only 'unhandled' events afterwards
@ -97,7 +156,7 @@ impl App {
} }
} }
} }
Ok(()) Ok(self)
} }
fn is_running(&self) -> bool { fn is_running(&self) -> bool {
@ -113,11 +172,11 @@ impl App {
KeyCode::Char('p') => self.content = Content::Pomodoro, KeyCode::Char('p') => self.content = Content::Pomodoro,
KeyCode::Char('m') => self.show_menu = !self.show_menu, KeyCode::Char('m') => self.show_menu = !self.show_menu,
KeyCode::Char(',') => { KeyCode::Char(',') => {
self.clock_style = self.clock_style.next(); self.style = self.style.next();
// update clocks // update clocks
self.timer.set_style(self.clock_style); self.timer.set_style(self.style);
self.countdown.set_style(self.clock_style); self.countdown.set_style(self.style);
self.pomodoro.set_style(self.clock_style); self.pomodoro.set_style(self.style);
} }
KeyCode::Char('.') => { KeyCode::Char('.') => {
self.with_decis = !self.with_decis; self.with_decis = !self.with_decis;
@ -138,6 +197,23 @@ impl App {
})?; })?;
Ok(()) Ok(())
} }
pub fn to_storage(&self) -> AppStorage {
AppStorage {
content: self.content,
show_menu: self.show_menu,
style: self.style,
with_decis: self.with_decis,
pomodoro_mode: self.pomodoro.get_mode().clone(),
inital_value_work: self.pomodoro.get_clock_work().initial_value,
current_value_work: self.pomodoro.get_clock_work().current_value,
inital_value_pause: self.pomodoro.get_clock_pause().initial_value,
current_value_pause: self.pomodoro.get_clock_pause().current_value,
inital_value_countdown: self.countdown.get_clock().initial_value,
current_value_countdown: self.countdown.get_clock().current_value,
current_value_timer: self.timer.get_clock().current_value,
}
}
} }
struct AppWidget; struct AppWidget;

View File

@ -1,42 +1,11 @@
use clap::{Parser, ValueEnum}; use clap::Parser;
use color_eyre::{ use color_eyre::{
eyre::{ensure, eyre}, eyre::{ensure, eyre},
Report, Report,
}; };
use std::time::Duration; use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] use crate::{app::Content, widgets::clock::Style};
pub enum Content {
#[value(name = "countdown", alias = "c")]
Countdown,
#[value(name = "timer", alias = "t")]
Timer,
#[value(name = "pomodoro", alias = "p")]
Pomodoro,
}
#[derive(Debug, Copy, Clone, ValueEnum)]
pub enum ClockStyle {
#[value(name = "bold", alias = "b")]
Bold,
#[value(name = "empty", alias = "e")]
Empty,
#[value(name = "thick", alias = "t")]
Thick,
#[value(name = "cross", alias = "c")]
Cross,
}
impl ClockStyle {
pub fn next(&self) -> Self {
match self {
ClockStyle::Bold => ClockStyle::Empty,
ClockStyle::Empty => ClockStyle::Thick,
ClockStyle::Thick => ClockStyle::Cross,
ClockStyle::Cross => ClockStyle::Bold,
}
}
}
#[derive(Parser)] #[derive(Parser)]
pub struct Args { pub struct Args {
@ -45,41 +14,38 @@ pub struct Args {
help = "Countdown time to start from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'" help = "Countdown time to start from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
)] )]
pub countdown: Duration, pub countdown: Duration,
#[arg(long, short, value_parser = parse_duration, #[arg(long, short, value_parser = parse_duration,
default_value="25:00" /* 25min */,
help = "Work time to count down from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'" help = "Work time to count down from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
)] )]
pub work: Duration, pub work: Option<Duration>,
#[arg(long, short, value_parser = parse_duration, #[arg(long, short, value_parser = parse_duration,
default_value="5:00" /* 5min */, default_value="5:00" /* 5min */,
help = "Pause time to count down from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'" help = "Pause time to count down from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
)] )]
pub pause: Duration, pub pause: Duration,
#[arg( #[arg(long, short = 'd', help = "Whether to show deciseconds or not")]
long,
short = 'd',
default_value = "false",
help = "Wether to show deciseconds or not"
)]
pub decis: bool, pub decis: bool,
#[arg( #[arg(
short = 'm', short = 'm',
value_enum, value_enum,
default_value = "timer",
help = "Mode to start with: [t]imer, [c]ountdown, [p]omodoro" help = "Mode to start with: [t]imer, [c]ountdown, [p]omodoro"
)] )]
pub mode: Content, pub mode: Option<Content>,
#[arg( #[arg(
long, long,
short = 's', short = 's',
value_enum, value_enum,
default_value = "bold",
help = "Style to display time with: [b]old, [t]hick, [c]ross, [e]mpty" help = "Style to display time with: [b]old, [t]hick, [c]ross, [e]mpty"
)] )]
pub style: ClockStyle, pub style: Option<Style>,
#[arg(long, short = 'r', help = "Reset stored values to default")]
pub reset: bool,
} }
fn parse_duration(arg: &str) -> Result<Duration, Report> { fn parse_duration(arg: &str) -> Result<Duration, Report> {
@ -130,19 +96,16 @@ mod tests {
fn test_parse_duration() { fn test_parse_duration() {
// ss // ss
assert_eq!(parse_duration("50").unwrap(), Duration::from_secs(50)); assert_eq!(parse_duration("50").unwrap(), Duration::from_secs(50));
// mm:ss // mm:ss
assert_eq!( assert_eq!(
parse_duration("01:30").unwrap(), parse_duration("01:30").unwrap(),
Duration::from_secs(60 + 30) Duration::from_secs(60 + 30)
); );
// hh:mm:ss // hh:mm:ss
assert_eq!( assert_eq!(
parse_duration("01:30:00").unwrap(), parse_duration("01:30:00").unwrap(),
Duration::from_secs(60 * 60 + 30 * 60) Duration::from_secs(60 * 60 + 30 * 60)
); );
// errors // errors
assert!(parse_duration("1:60").is_err()); // invalid seconds assert!(parse_duration("1:60").is_err()); // invalid seconds
assert!(parse_duration("60:00").is_err()); // invalid minutes assert!(parse_duration("60:00").is_err()); // invalid minutes

View File

@ -5,18 +5,20 @@ use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
pub struct Config { pub struct Config {
pub log_dir: PathBuf, pub log_dir: PathBuf,
pub data_dir: PathBuf,
} }
impl Config { impl Config {
pub fn init() -> Result<Self> { pub fn init() -> Result<Self> {
let log_dir = get_default_state_dir()?.join("logs"); let log_dir = get_default_state_dir()?.join("logs");
fs::create_dir_all(&log_dir)?; fs::create_dir_all(&log_dir)?;
let data_dir = get_default_state_dir()?.join("data");
fs::create_dir_all(&data_dir)?;
Ok(Self { log_dir }) Ok(Self { log_dir, data_dir })
} }
} }
// fn new
pub fn get_project_dir() -> Result<ProjectDirs> { pub fn get_project_dir() -> Result<ProjectDirs> {
let dirs = ProjectDirs::from("", "", APP_NAME) let dirs = ProjectDirs::from("", "", APP_NAME)
.ok_or_else(|| eyre!("Failed to get project directories"))?; .ok_or_else(|| eyre!("Failed to get project directories"))?;

View File

@ -6,6 +6,7 @@ mod events;
mod logging; mod logging;
mod args; mod args;
mod storage;
mod terminal; mod terminal;
mod utils; mod utils;
mod widgets; mod widgets;
@ -14,20 +15,38 @@ use app::App;
use args::Args; use args::Args;
use clap::Parser; use clap::Parser;
use color_eyre::Result; use color_eyre::Result;
use config::Config;
use storage::{AppStorage, Storage};
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let config = config::Config::init()?; let Config { log_dir, data_dir } = Config::init()?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
logging::Logger::new(config.log_dir).init()?; logging::Logger::new(log_dir).init()?;
color_eyre::install()?; color_eyre::install()?;
let args = Args::parse();
let terminal = terminal::setup()?; let terminal = terminal::setup()?;
let events = events::Events::new(); let events = events::Events::new();
App::new(args).run(terminal, events).await?;
// get args given by CLI
let args = Args::parse();
// check persistant storage
let storage = Storage::new(data_dir);
// option to reset previous stored data to `default`
let stg = if args.reset {
AppStorage::default()
} else {
storage.load().unwrap_or_default()
};
// merge `Args` and `AppStorage`.
let app_args = (args, stg).into();
let app_storage = App::new(app_args).run(terminal, events).await?.to_storage();
// store app state persistantly
storage.save(app_storage)?;
terminal::teardown()?; terminal::teardown()?;
Ok(()) Ok(())

82
src/storage.rs Normal file
View File

@ -0,0 +1,82 @@
use crate::{
app::Content,
constants::APP_NAME,
widgets::{clock::Style, pomodoro::Mode as PomodoroMode},
};
use color_eyre::eyre::Result;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug, Serialize, Deserialize)]
pub struct AppStorage {
pub content: Content,
pub show_menu: bool,
pub style: Style,
pub with_decis: bool,
pub pomodoro_mode: PomodoroMode,
// pomodoro -> work
pub inital_value_work: Duration,
pub current_value_work: Duration,
// pomodoro -> pause
pub inital_value_pause: Duration,
pub current_value_pause: Duration,
// countdown
pub inital_value_countdown: Duration,
pub current_value_countdown: Duration,
// timer
pub current_value_timer: Duration,
}
impl Default for AppStorage {
fn default() -> Self {
const DEFAULT_WORK: Duration = Duration::from_secs(60 * 25); /* 25min */
const DEFAULT_PAUSE: Duration = Duration::from_secs(60 * 5); /* 5min */
const DEFAULT_COUNTDOWN: Duration = Duration::from_secs(60 * 10); /* 10min */
AppStorage {
content: Content::default(),
show_menu: false,
style: Style::default(),
with_decis: false,
pomodoro_mode: PomodoroMode::Work,
// pomodoro -> work
inital_value_work: DEFAULT_WORK,
current_value_work: DEFAULT_WORK,
// pomodoro -> pause
inital_value_pause: DEFAULT_PAUSE,
current_value_pause: DEFAULT_PAUSE,
// countdown
inital_value_countdown: DEFAULT_COUNTDOWN,
current_value_countdown: DEFAULT_COUNTDOWN,
// timer
current_value_timer: Duration::ZERO,
}
}
}
pub struct Storage {
data_dir: PathBuf,
}
impl Storage {
pub fn new(data_dir: PathBuf) -> Self {
Self { data_dir }
}
fn get_storage_path(&self) -> PathBuf {
self.data_dir.join(format!("{}.data", APP_NAME))
}
pub fn save(&self, data: AppStorage) -> Result<()> {
let file = fs::File::create(self.get_storage_path())?;
serde_json::to_writer(file, &data)?;
Ok(())
}
pub fn load(&self) -> Result<AppStorage> {
let file = fs::File::open(self.get_storage_path())?;
let data = serde_json::from_reader(file)?;
Ok(data)
}
}

View File

@ -1,3 +1,5 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::Duration; use std::time::Duration;
@ -9,7 +11,7 @@ use ratatui::{
widgets::StatefulWidget, widgets::StatefulWidget,
}; };
use crate::{args::ClockStyle, utils::center_horizontal}; use crate::utils::center_horizontal;
#[derive(Debug, Copy, Clone, Display, PartialEq, Eq)] #[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
pub enum Time { pub enum Time {
@ -74,22 +76,47 @@ pub enum Format {
HhMmSs, HhMmSs,
} }
#[derive(Debug, Copy, Clone, ValueEnum, Default, Serialize, Deserialize)]
pub enum Style {
#[default]
#[value(name = "bold", alias = "b")]
Bold,
#[value(name = "empty", alias = "e")]
Empty,
#[value(name = "thick", alias = "t")]
Thick,
#[value(name = "cross", alias = "c")]
Cross,
}
impl Style {
pub fn next(&self) -> Self {
match self {
Style::Bold => Style::Empty,
Style::Empty => Style::Thick,
Style::Thick => Style::Cross,
Style::Cross => Style::Bold,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Clock<T> { pub struct Clock<T> {
initial_value: Duration, pub initial_value: Duration,
pub current_value: Duration,
tick_value: Duration, tick_value: Duration,
current_value: Duration,
mode: Mode, mode: Mode,
format: Format, format: Format,
pub style: ClockStyle, pub style: Style,
pub with_decis: bool, pub with_decis: bool,
phantom: PhantomData<T>, phantom: PhantomData<T>,
} }
pub struct ClockArgs { pub struct ClockArgs {
pub initial_value: Duration, pub initial_value: Duration,
pub current_value: Duration,
pub tick_value: Duration, pub tick_value: Duration,
pub style: ClockStyle, pub style: Style,
pub with_decis: bool, pub with_decis: bool,
} }
@ -315,15 +342,22 @@ impl Clock<Countdown> {
pub fn new(args: ClockArgs) -> Self { pub fn new(args: ClockArgs) -> Self {
let ClockArgs { let ClockArgs {
initial_value, initial_value,
current_value,
tick_value, tick_value,
style, style,
with_decis, with_decis,
} = args; } = args;
let mut instance = Self { let mut instance = Self {
initial_value, initial_value,
current_value,
tick_value, tick_value,
current_value: initial_value, mode: if current_value == Duration::ZERO {
mode: Mode::Initial, Mode::Done
} else if current_value == initial_value {
Mode::Initial
} else {
Mode::Pause
},
format: Format::S, format: Format::S,
style, style,
with_decis, with_decis,
@ -380,15 +414,22 @@ impl Clock<Timer> {
pub fn new(args: ClockArgs) -> Self { pub fn new(args: ClockArgs) -> Self {
let ClockArgs { let ClockArgs {
initial_value, initial_value,
current_value,
tick_value, tick_value,
style, style,
with_decis, with_decis,
} = args; } = args;
let mut instance = Self { let mut instance = Self {
initial_value, initial_value,
current_value,
tick_value, tick_value,
current_value: Duration::ZERO, mode: if current_value == initial_value {
mode: Mode::Initial, Mode::Initial
} else if current_value >= MAX_DURATION {
Mode::Done
} else {
Mode::Pause
},
format: Format::S, format: Format::S,
phantom: PhantomData, phantom: PhantomData,
style, style,
@ -552,12 +593,12 @@ where
} }
} }
fn get_digit_symbol(&self, style: &ClockStyle) -> &str { fn get_digit_symbol(&self, style: &Style) -> &str {
match &style { match &style {
ClockStyle::Bold => "", Style::Bold => "",
ClockStyle::Empty => "", Style::Empty => "",
ClockStyle::Cross => "", Style::Cross => "",
ClockStyle::Thick => "", Style::Thick => "",
} }
} }

View File

@ -8,10 +8,9 @@ use ratatui::{
use std::cmp::max; use std::cmp::max;
use crate::{ use crate::{
args::ClockStyle,
events::{Event, EventHandler}, events::{Event, EventHandler},
utils::center, utils::center,
widgets::clock::{self, Clock, ClockWidget}, widgets::clock::{self, Clock, ClockWidget, Style},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -24,13 +23,17 @@ impl Countdown {
Self { clock } Self { clock }
} }
pub fn set_style(&mut self, style: ClockStyle) { pub fn set_style(&mut self, style: Style) {
self.clock.style = style; self.clock.style = style;
} }
pub fn set_with_decis(&mut self, with_decis: bool) { pub fn set_with_decis(&mut self, with_decis: bool) {
self.clock.with_decis = with_decis; self.clock.with_decis = with_decis;
} }
pub fn get_clock(&self) -> &Clock<clock::Countdown> {
&self.clock
}
} }
impl EventHandler for Countdown { impl EventHandler for Countdown {

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::args::Content; use crate::app::Content;
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Constraint, Layout, Rect}, layout::{Constraint, Layout, Rect},

View File

@ -1,9 +1,8 @@
use crate::{ use crate::{
args::ClockStyle,
constants::TICK_VALUE_MS, constants::TICK_VALUE_MS,
events::{Event, EventHandler}, events::{Event, EventHandler},
utils::center, utils::center,
widgets::clock::{Clock, ClockWidget, Countdown}, widgets::clock::{Clock, ClockWidget, Countdown, Style},
}; };
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
@ -16,10 +15,12 @@ use std::{cmp::max, time::Duration};
use strum::Display; use strum::Display;
use serde::{Deserialize, Serialize};
use super::clock::ClockArgs; use super::clock::ClockArgs;
#[derive(Debug, Clone, Display, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Display, Hash, Eq, PartialEq, Deserialize, Serialize)]
enum Mode { pub enum Mode {
Work, Work,
Pause, Pause,
} }
@ -46,31 +47,39 @@ pub struct Pomodoro {
} }
pub struct PomodoroArgs { pub struct PomodoroArgs {
pub work: Duration, pub mode: Mode,
pub pause: Duration, pub initial_value_work: Duration,
pub style: ClockStyle, pub current_value_work: Duration,
pub initial_value_pause: Duration,
pub current_value_pause: Duration,
pub style: Style,
pub with_decis: bool, pub with_decis: bool,
} }
impl Pomodoro { impl Pomodoro {
pub fn new(args: PomodoroArgs) -> Self { pub fn new(args: PomodoroArgs) -> Self {
let PomodoroArgs { let PomodoroArgs {
work, mode,
pause, initial_value_work,
current_value_work,
initial_value_pause,
current_value_pause,
style, style,
with_decis, with_decis,
} = args; } = args;
Self { Self {
mode: Mode::Work, mode,
clock_map: ClockMap { clock_map: ClockMap {
work: Clock::<Countdown>::new(ClockArgs { work: Clock::<Countdown>::new(ClockArgs {
initial_value: work, initial_value: initial_value_work,
current_value: current_value_work,
tick_value: Duration::from_millis(TICK_VALUE_MS), tick_value: Duration::from_millis(TICK_VALUE_MS),
style, style,
with_decis, with_decis,
}), }),
pause: Clock::<Countdown>::new(ClockArgs { pause: Clock::<Countdown>::new(ClockArgs {
initial_value: pause, initial_value: initial_value_pause,
current_value: current_value_pause,
tick_value: Duration::from_millis(TICK_VALUE_MS), tick_value: Duration::from_millis(TICK_VALUE_MS),
style, style,
with_decis, with_decis,
@ -79,11 +88,23 @@ impl Pomodoro {
} }
} }
fn get_clock(&mut self) -> &mut Clock<Countdown> { pub fn get_clock(&mut self) -> &mut Clock<Countdown> {
self.clock_map.get(&self.mode) self.clock_map.get(&self.mode)
} }
pub fn set_style(&mut self, style: crate::args::ClockStyle) { pub fn get_clock_work(&self) -> &Clock<Countdown> {
&self.clock_map.work
}
pub fn get_clock_pause(&self) -> &Clock<Countdown> {
&self.clock_map.pause
}
pub fn get_mode(&self) -> &Mode {
&self.mode
}
pub fn set_style(&mut self, style: Style) {
self.clock_map.work.style = style; self.clock_map.work.style = style;
self.clock_map.pause.style = style; self.clock_map.pause.style = style;
} }

View File

@ -1,8 +1,7 @@
use crate::{ use crate::{
args::ClockStyle,
events::{Event, EventHandler}, events::{Event, EventHandler},
utils::center, utils::center,
widgets::clock::{self, Clock, ClockWidget}, widgets::clock::{self, Clock, ClockWidget, Style},
}; };
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
@ -23,13 +22,17 @@ impl Timer {
Self { clock } Self { clock }
} }
pub fn set_style(&mut self, style: ClockStyle) { pub fn set_style(&mut self, style: Style) {
self.clock.style = style; self.clock.style = style;
} }
pub fn set_with_decis(&mut self, with_decis: bool) { pub fn set_with_decis(&mut self, with_decis: bool) {
self.clock.with_decis = with_decis; self.clock.with_decis = with_decis;
} }
pub fn get_clock(&self) -> &Clock<clock::Timer> {
&self.clock
}
} }
impl EventHandler for Timer { impl EventHandler for Timer {