Rewrite Domasi
This commit is contained in:
parent
68ec6883b7
commit
6954e3dfd8
File diff suppressed because it is too large
Load Diff
|
@ -7,12 +7,3 @@ version = "0.1.0"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
clap = "2.33"
|
||||
crossterm = "0.17"
|
||||
directories = "3.0"
|
||||
notify-rust = "4.0"
|
||||
rodio = "0.11"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.5"
|
||||
crossbeam = "0.7"
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use directories::ProjectDirs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
const SETTINGS_FILE: &str = "settings.toml";
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
pub work_time: Duration,
|
||||
pub short_break: Duration,
|
||||
pub long_break: Duration,
|
||||
pub sound_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(work: u64, short_break: u64, long_break: u64) -> Self {
|
||||
Config {
|
||||
work_time: Duration::from_secs(work),
|
||||
short_break: Duration::from_secs(short_break),
|
||||
long_break: Duration::from_secs(long_break),
|
||||
sound_file: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(config: &Config) -> Result<()> {
|
||||
let config_directory = Self::get_config_directory();
|
||||
Self::write_to_file(&config_directory.join(SETTINGS_FILE), config)
|
||||
}
|
||||
|
||||
pub fn load() -> Option<Self> {
|
||||
let config_file = Self::get_config_directory().join(SETTINGS_FILE);
|
||||
Self::read_from_file(&config_file).ok()
|
||||
}
|
||||
|
||||
fn write_to_file<P: AsRef<Path>>(path: &P, cfg: &Self) -> Result<()> {
|
||||
let mut new_file = File::create(path.as_ref())?;
|
||||
let serialized = toml::to_string(cfg)?;
|
||||
|
||||
Ok(new_file.write_all(serialized.as_bytes())?)
|
||||
}
|
||||
|
||||
fn read_from_file<P: AsRef<Path>>(path: &P) -> Result<Self> {
|
||||
let mut toml_buf = vec![];
|
||||
let mut file = File::open(path.as_ref())?;
|
||||
file.read_to_end(&mut toml_buf)?;
|
||||
|
||||
Ok(toml::from_slice(&toml_buf)?)
|
||||
}
|
||||
|
||||
pub fn get_data_directory() -> PathBuf {
|
||||
ProjectDirs::from("moe", "paoda", "domasi")
|
||||
.unwrap()
|
||||
.data_dir()
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
pub fn get_config_directory() -> PathBuf {
|
||||
ProjectDirs::from("moe", "paoda", "domasi")
|
||||
.unwrap()
|
||||
.config_dir()
|
||||
.to_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config::new(1500, 300, 600)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
pub use config::Config;
|
||||
pub use pomodoro::Pomodoro;
|
||||
|
||||
pub mod config;
|
||||
pub mod pomodoro;
|
||||
mod pomodoro;
|
||||
|
|
203
src/main.rs
203
src/main.rs
|
@ -1,204 +1,7 @@
|
|||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use crossbeam::channel::{unbounded, Receiver, Sender};
|
||||
use crossterm::{
|
||||
event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers},
|
||||
terminal::{disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use domasi::pomodoro::{Alert, Remaining, Status};
|
||||
use domasi::{Config, Pomodoro};
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::thread;
|
||||
use domasi::Pomodoro;
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("domasi")
|
||||
.version("0.1.0")
|
||||
.author("Rekai Musuka <rekai@musuka.dev>")
|
||||
.about("Yet another pomodoro timer.")
|
||||
.arg(
|
||||
Arg::with_name("create-config")
|
||||
.short("C")
|
||||
.long("create-config")
|
||||
.help("Creates a Settings.toml and an alert directory"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("start")
|
||||
.about("Start the Pomodoro Timer")
|
||||
.arg(
|
||||
Arg::with_name("alert")
|
||||
.short("a")
|
||||
.long("alert")
|
||||
.value_name("FILE")
|
||||
.takes_value(true)
|
||||
.help("Aloud Sound. (Supports WAV, MP3, Vorbis, FLAC)"),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
let mut timer: Pomodoro = Default::default();
|
||||
|
||||
// match matches.subcommand() {
|
||||
// ("start", Some(sub_matches)) => start(sub_matches),
|
||||
// _ => {}
|
||||
// }
|
||||
|
||||
if matches.is_present("create-config") {
|
||||
create_config()
|
||||
}
|
||||
|
||||
if let ("start", Some(sub_matches)) = matches.subcommand() {
|
||||
start(sub_matches);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_config() {
|
||||
let config_dir = Config::get_config_directory();
|
||||
let data_dir = Config::get_data_directory().join("alert");
|
||||
|
||||
if !config_dir.exists() {
|
||||
fs::create_dir_all(&config_dir).unwrap_or_else(|err| {
|
||||
panic!("Failed to create {}: {}", config_dir.to_string_lossy(), err)
|
||||
})
|
||||
}
|
||||
|
||||
if !data_dir.exists() {
|
||||
fs::create_dir_all(&data_dir).unwrap_or_else(|err| {
|
||||
panic!("Failed to create {}: {}", data_dir.to_string_lossy(), err)
|
||||
});
|
||||
}
|
||||
|
||||
let config: Config = Default::default();
|
||||
|
||||
Config::save(&config).unwrap_or_else(|err| {
|
||||
let cfg_path = config_dir.to_string_lossy();
|
||||
panic!("Error while writing settings.toml to {}: {}", cfg_path, err);
|
||||
});
|
||||
|
||||
let data_path = data_dir.to_string_lossy();
|
||||
let settings_path = config_dir.join("settings.toml");
|
||||
let settings_path = settings_path.to_string_lossy();
|
||||
|
||||
println!(
|
||||
"Successfully created \"{}\" and \"{}\"",
|
||||
settings_path, data_path
|
||||
);
|
||||
}
|
||||
|
||||
pub fn start(args: &ArgMatches) {
|
||||
let mut pomodoro = Pomodoro::new();
|
||||
let (tx, rx): (Sender<Status>, Receiver<Status>) = unbounded();
|
||||
|
||||
// UI Thread
|
||||
thread::spawn(move || loop {
|
||||
if let Ok(status) = rx.recv() {
|
||||
loop {
|
||||
let (text, remain) = match Remaining::from_status(status) {
|
||||
Some(seconds) => (seconds.to_string(), seconds.remaining_secs()),
|
||||
None => ("??? Status: ??:??".to_string(), 0),
|
||||
};
|
||||
|
||||
let out = io::stdout();
|
||||
let mut handle = out.lock();
|
||||
handle.write_all(text.as_bytes()).unwrap();
|
||||
handle.flush().unwrap();
|
||||
|
||||
// TODO: Make sure this isn't an issue.
|
||||
if remain < 1 {
|
||||
break;
|
||||
}
|
||||
|
||||
thread::sleep(Remaining::polling_interval());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// User Input Thread
|
||||
thread::spawn(|| setup_user_input().unwrap());
|
||||
|
||||
let config = {
|
||||
match Config::load() {
|
||||
Some(cfg) => cfg,
|
||||
None => Default::default(),
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_audio: Option<PathBuf>;
|
||||
|
||||
match args.value_of("alert") {
|
||||
Some(path) => maybe_audio = Some(Path::new(path).to_path_buf()),
|
||||
None => {
|
||||
match &config.sound_file {
|
||||
Some(path) => maybe_audio = Some(path.clone()),
|
||||
None => {
|
||||
// Look in the default locations
|
||||
// check for .mp3, .wav, .ogg, .flac
|
||||
let data_dir = Config::get_data_directory().join("alert");
|
||||
let data_dir_str = data_dir.to_string_lossy();
|
||||
|
||||
let items = fs::read_dir(&data_dir).unwrap_or_else(|_err| {
|
||||
panic!("Unable to read the contents of {}", data_dir_str);
|
||||
});
|
||||
|
||||
maybe_audio = get_audio_file(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match maybe_audio {
|
||||
Some(audio_path) => {
|
||||
let default_device = rodio::default_output_device().unwrap();
|
||||
let alert = Alert::new(&audio_path, &default_device);
|
||||
|
||||
pomodoro.start(config, tx, Some(&alert));
|
||||
}
|
||||
None => pomodoro.start(config, tx, None),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_audio_file(items: std::fs::ReadDir) -> Option<PathBuf> {
|
||||
for maybe_entry in items {
|
||||
if let Ok(entry) = maybe_entry {
|
||||
if let Some(ext) = entry.path().extension() {
|
||||
if let Some("mp3") | Some("wav") | Some("ogg") | Some("flac") = ext.to_str() {
|
||||
return Some(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn setup_user_input() -> crossterm::Result<()> {
|
||||
enable_raw_mode()?;
|
||||
get_user_input()?;
|
||||
disable_raw_mode()
|
||||
}
|
||||
|
||||
fn get_user_input() -> crossterm::Result<()> {
|
||||
loop {
|
||||
if poll(Remaining::polling_interval())? {
|
||||
if let Event::Key(event) = read()? {
|
||||
handle_key_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_key_event(event: KeyEvent) {
|
||||
match event.code {
|
||||
KeyCode::Char('c') => {
|
||||
if let KeyModifiers::CONTROL = event.modifiers {
|
||||
exit_domasi(0)
|
||||
}
|
||||
}
|
||||
KeyCode::Esc | KeyCode::Char('q') => exit_domasi(0),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn exit_domasi(code: i32) {
|
||||
disable_raw_mode().unwrap();
|
||||
|
||||
std::process::exit(code);
|
||||
timer.start();
|
||||
}
|
||||
|
|
357
src/pomodoro.rs
357
src/pomodoro.rs
|
@ -1,220 +1,166 @@
|
|||
use super::Config;
|
||||
use crossbeam::channel::Sender;
|
||||
use rodio::{Decoder, Device, Source};
|
||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use std::io::{self, Write};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct Alert<'a> {
|
||||
path: &'a Path,
|
||||
device: &'a Device,
|
||||
}
|
||||
|
||||
impl<'a> Alert<'a> {
|
||||
pub fn new<P: AsRef<Path>>(path: &'a P, device: &'a Device) -> Alert<'a> {
|
||||
Alert {
|
||||
path: path.as_ref(),
|
||||
device,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play(&self) {
|
||||
if self.path.exists() {
|
||||
let file = File::open(self.path).unwrap();
|
||||
let source = Decoder::new(BufReader::new(file)).unwrap();
|
||||
rodio::play_raw(&self.device, source.convert_samples());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load<P: AsRef<Path>>(mut self, new_path: &'a P) -> Self {
|
||||
self.path = new_path.as_ref();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum State {
|
||||
Work,
|
||||
ShortBreak,
|
||||
LongBreak,
|
||||
Inactive,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for State {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
State::Work => f.write_str("Working: "),
|
||||
State::ShortBreak => f.write_str("Resting: "),
|
||||
State::LongBreak => f.write_str("REALLY Resting: "),
|
||||
State::Inactive => f.write_str("Inactive: "),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Pomodoro {
|
||||
count: u64,
|
||||
state: State,
|
||||
count: u64,
|
||||
wait_start: Option<Instant>,
|
||||
paused: PausedState,
|
||||
}
|
||||
|
||||
impl Default for Pomodoro {
|
||||
fn default() -> Self {
|
||||
Pomodoro {
|
||||
count: 0,
|
||||
state: State::Inactive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Pomodoro {
|
||||
pub fn start(&mut self, config: Config, tx: Sender<Status>, maybe_alert: Option<&Alert<'a>>) {
|
||||
loop {
|
||||
self.next();
|
||||
|
||||
if let Some(alert) = maybe_alert {
|
||||
alert.play();
|
||||
}
|
||||
|
||||
let _ = Self::notify(&self.state);
|
||||
|
||||
match self.state {
|
||||
State::Work => {
|
||||
Self::send_to_clock(&tx, self.state, config.work_time);
|
||||
Self::wait(config.work_time);
|
||||
}
|
||||
State::ShortBreak => {
|
||||
Self::send_to_clock(&tx, self.state, config.short_break);
|
||||
Self::wait(config.short_break);
|
||||
}
|
||||
State::LongBreak => {
|
||||
Self::send_to_clock(&tx, self.state, config.long_break);
|
||||
Self::wait(config.long_break);
|
||||
}
|
||||
State::Inactive => {
|
||||
println!("Pomodoro Cycle is complete");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static POLLING_RATE: Duration = Duration::from_millis(300);
|
||||
static WORK_TIME: u64 = 2;
|
||||
static SBREAK_TIME: u64 = 2;
|
||||
static LBREAK_TIME: u64 = 2;
|
||||
|
||||
impl Pomodoro {
|
||||
pub fn new() -> Pomodoro {
|
||||
Pomodoro {
|
||||
count: 0,
|
||||
state: State::Inactive,
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn next(&mut self) {
|
||||
match self.state {
|
||||
State::Work => {
|
||||
self.count += 1;
|
||||
|
||||
if (self.count % 4) == 0 {
|
||||
self.state = State::LongBreak;
|
||||
} else {
|
||||
self.state = State::ShortBreak;
|
||||
}
|
||||
pub fn start(&mut self) {
|
||||
loop {
|
||||
if let Status::Complete = self.poll() {
|
||||
break;
|
||||
}
|
||||
State::LongBreak => self.state = State::Inactive,
|
||||
State::ShortBreak => self.state = State::Work,
|
||||
State::Inactive => self.state = State::Work,
|
||||
thread::sleep(POLLING_RATE);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait(duration: Duration) {
|
||||
std::thread::sleep(duration);
|
||||
}
|
||||
fn poll(&mut self) -> Status {
|
||||
let status = self.check();
|
||||
|
||||
fn send_to_clock(tx: &Sender<Status>, state: State, length: Duration) {
|
||||
let status = Status {
|
||||
start: Instant::now(),
|
||||
length,
|
||||
state,
|
||||
match status {
|
||||
Status::Paused => {
|
||||
assert!(self.paused.value);
|
||||
let now = Instant::now();
|
||||
|
||||
self.paused.duration += match self.paused.previous {
|
||||
Some(earlier) => now - earlier,
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
self.paused.previous = Some(now);
|
||||
}
|
||||
Status::Active => self.display_time(),
|
||||
Status::NextState => {
|
||||
let (update_count, new_state) = self.next();
|
||||
|
||||
if let Count::Increase = update_count {
|
||||
self.count += 1;
|
||||
}
|
||||
|
||||
self.state = new_state;
|
||||
self.wait_start = match new_state {
|
||||
State::Inactive => None,
|
||||
State::Work | State::ShortBreak | State::LongBreak => Some(Instant::now()),
|
||||
};
|
||||
|
||||
self.display_time();
|
||||
}
|
||||
Status::Complete => {
|
||||
println!("\rPomodoro Cycle Complete!");
|
||||
}
|
||||
Status::Inactive => {}
|
||||
};
|
||||
|
||||
tx.send(status).unwrap();
|
||||
status
|
||||
}
|
||||
|
||||
fn notify(state: &State) -> Result<(), anyhow::Error> {
|
||||
let mut toast = notify_rust::Notification::new();
|
||||
match state {
|
||||
State::Work => toast
|
||||
.summary("Time to Work!")
|
||||
.body("Remember to stay focused!")
|
||||
.show()?,
|
||||
State::ShortBreak | State::LongBreak => toast
|
||||
.summary("Break Time!")
|
||||
.body("Enjoy your well deserved rest!")
|
||||
.show()?,
|
||||
State::Inactive => toast
|
||||
.summary("Pomodoro Cycle Complete.")
|
||||
.body("Now waiting for user input....")
|
||||
.show()?,
|
||||
};
|
||||
fn display_time(&self) {
|
||||
if let Some(earlier) = self.wait_start {
|
||||
let wait = Instant::now() - earlier;
|
||||
let left: Clock = (Self::wait_times(self.state) - wait).into();
|
||||
let _ = Self::print(&format!("\r{} {}", self.state, left));
|
||||
}
|
||||
}
|
||||
|
||||
fn print(text: &str) -> io::Result<()> {
|
||||
let out = io::stdout();
|
||||
let mut handle = out.lock();
|
||||
handle.write_all(text.as_bytes())?;
|
||||
handle.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Status {
|
||||
pub start: Instant,
|
||||
pub length: Duration,
|
||||
pub state: State,
|
||||
}
|
||||
fn next(&self) -> (Count, State) {
|
||||
match self.state {
|
||||
State::Work => {
|
||||
let state: State = if (self.count + 1) % 4 == 0 {
|
||||
State::LongBreak
|
||||
} else {
|
||||
State::ShortBreak
|
||||
};
|
||||
|
||||
pub struct Remaining {
|
||||
state: State,
|
||||
remaining: u64,
|
||||
seconds: u64,
|
||||
hours: u64,
|
||||
minutes: u64,
|
||||
}
|
||||
|
||||
impl Remaining {
|
||||
pub fn from_status(status: Status) -> Option<Self> {
|
||||
let now = Instant::now();
|
||||
let maybe_elapsed = now.checked_duration_since(status.start);
|
||||
|
||||
match maybe_elapsed {
|
||||
Some(duration) => {
|
||||
let remaining = status.length.as_secs() - duration.as_secs();
|
||||
|
||||
let hours = remaining / 3600;
|
||||
let minutes = (remaining - (hours * 3600)) / 60;
|
||||
let seconds = remaining - (hours * 3600) - (minutes * 60);
|
||||
|
||||
Some(Self {
|
||||
state: status.state,
|
||||
remaining,
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
})
|
||||
(Count::Increase, state)
|
||||
}
|
||||
None => None,
|
||||
State::ShortBreak | State::Inactive => (Count::Remain, State::Work),
|
||||
State::LongBreak => (Count::Remain, State::Inactive),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remaining_secs(&self) -> u64 {
|
||||
self.remaining
|
||||
fn check(&self) -> Status {
|
||||
if self.paused.value {
|
||||
return Status::Paused;
|
||||
}
|
||||
|
||||
match self.wait_start {
|
||||
Some(earlier) => {
|
||||
let diff: Duration = (Instant::now() - self.paused.duration) - earlier;
|
||||
|
||||
if diff > Self::wait_times(self.state) {
|
||||
Status::NextState
|
||||
} else {
|
||||
Status::Active
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if self.count == 0 {
|
||||
Status::NextState
|
||||
} else {
|
||||
Status::Complete
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn polling_interval() -> Duration {
|
||||
Duration::from_millis(500)
|
||||
fn wait_times(state: State) -> Duration {
|
||||
match state {
|
||||
State::Work => Duration::from_secs(WORK_TIME),
|
||||
State::ShortBreak => Duration::from_secs(SBREAK_TIME),
|
||||
State::LongBreak => Duration::from_secs(LBREAK_TIME),
|
||||
_ => unreachable!("Can not have Pomodoro state = State::Inactive and wait_start = Some(...) at the same time.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Remaining {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
f.write_str("\r")?;
|
||||
write!(f, "{}", self.state)?;
|
||||
struct Clock {
|
||||
hours: u64,
|
||||
minutes: u64,
|
||||
seconds: u64,
|
||||
}
|
||||
|
||||
impl From<Duration> for Clock {
|
||||
fn from(dur: Duration) -> Self {
|
||||
let dur = dur.as_secs();
|
||||
|
||||
let hours = dur / 3600;
|
||||
let minutes = (dur - (hours * 3600)) / 60;
|
||||
let seconds = dur - (hours * 3600) - (minutes * 60);
|
||||
|
||||
Self {
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Clock {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
if self.hours > 0 {
|
||||
// No need to print the hours if there are none.
|
||||
if self.hours < 10 {
|
||||
|
@ -237,3 +183,50 @@ impl Display for Remaining {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
struct PausedState {
|
||||
value: bool,
|
||||
duration: Duration,
|
||||
previous: Option<Instant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Count {
|
||||
Increase,
|
||||
Remain,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Status {
|
||||
Paused,
|
||||
Active,
|
||||
NextState,
|
||||
Inactive,
|
||||
Complete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum State {
|
||||
Inactive,
|
||||
Work,
|
||||
ShortBreak,
|
||||
LongBreak,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
State::Inactive
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for State {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
match *self {
|
||||
State::Work => f.write_str("Working: "),
|
||||
State::ShortBreak => f.write_str("Resting: "),
|
||||
State::LongBreak => f.write_str("REALLY Resting: "),
|
||||
State::Inactive => f.write_str("Inactive: "),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue