feat(countdown): implement

This commit is contained in:
Pihkaal
2024-01-21 21:58:13 +01:00
parent 9644119758
commit 35e847841c
5 changed files with 187 additions and 52 deletions

View File

@@ -4,10 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
atomic_enum = "0.2.0"
chrono = "0.4.31" chrono = "0.4.31"
clap = { version = "4.4.18", features = ["derive", "cargo"] } clap = { version = "4.4.18", features = ["derive", "cargo"] }
crossterm = "0.27.0" crossterm = "0.27.0"
dirs = "5.0.1" dirs = "5.0.1"
ini = "1.3.0" ini = "1.3.0"
parse_duration = "2.1.1"

View File

@@ -5,10 +5,8 @@ use crossterm::style::Color;
use ini::configparser::ini::Ini; use ini::configparser::ini::Ini;
use crate::{ use crate::{
get_app_mode,
modes::debug, modes::debug,
rendering::color::{generate_gradient, parse_hex_color, ComputableColor}, rendering::color::{generate_gradient, parse_hex_color, ComputableColor},
AppMode,
}; };
pub struct Config { pub struct Config {
@@ -21,14 +19,14 @@ pub struct Config {
const DEFAULT_CONFIG: &str = include_str!("default_config"); const DEFAULT_CONFIG: &str = include_str!("default_config");
pub fn load_from_file(path: PathBuf) -> Config { pub fn load_from_file(path: PathBuf, debug_mode: bool) -> Config {
let mut ini = Ini::new(); let mut ini = Ini::new();
ini.load(&path.to_str().unwrap()).unwrap(); ini.load(&path.to_str().unwrap()).unwrap();
let config = Config { let config = Config {
be_polite: ini.getbool("general", "polite").unwrap().unwrap(), be_polite: ini.getbool("general", "polite").unwrap().unwrap(),
fps: ini.getuint("general", "fps").unwrap().unwrap(), fps: ini.getuint("general", "fps").unwrap().unwrap(),
color: load_color(&ini), color: load_color(&ini, debug_mode),
time_format: ini.get("format", "time").unwrap(), time_format: ini.get("format", "time").unwrap(),
date_format: ini.get("format", "date").unwrap(), date_format: ini.get("format", "date").unwrap(),
}; };
@@ -42,7 +40,7 @@ pub fn write_default_config(path: PathBuf) -> () {
let _ = fs::write(path, DEFAULT_CONFIG); let _ = fs::write(path, DEFAULT_CONFIG);
} }
fn load_color(ini: &Ini) -> ComputableColor { fn load_color(ini: &Ini, debug_mode: bool) -> ComputableColor {
let color_mode = ini.get("styling", "color_mode").unwrap(); let color_mode = ini.get("styling", "color_mode").unwrap();
match color_mode.as_str() { match color_mode.as_str() {
@@ -59,7 +57,7 @@ fn load_color(ini: &Ini) -> ComputableColor {
return ComputableColor::from(load_ansi_color(color)); return ComputableColor::from(load_ansi_color(color));
} }
"gradient" => { "gradient" => {
return load_gradient(ini); return load_gradient(ini, debug_mode);
} }
_ => panic!("ERROR: Invalid color mode: {}", color_mode), _ => panic!("ERROR: Invalid color mode: {}", color_mode),
} }
@@ -100,7 +98,7 @@ fn load_ansi_color(value: i64) -> Color {
return Color::AnsiValue(value.try_into().unwrap()); return Color::AnsiValue(value.try_into().unwrap());
} }
fn load_gradient(ini: &Ini) -> ComputableColor { fn load_gradient(ini: &Ini, debug_mode: bool) -> ComputableColor {
let mut keys = Vec::new(); let mut keys = Vec::new();
let mut i = 0; let mut i = 0;
@@ -109,9 +107,7 @@ fn load_gradient(ini: &Ini) -> ComputableColor {
i += 1; i += 1;
} }
if get_app_mode() != AppMode::Debug if !debug_mode && ini.getbool("gradient", "gradient_loop").unwrap().unwrap() {
&& ini.getbool("gradient", "gradient_loop").unwrap().unwrap()
{
let mut loop_keys = keys.clone(); let mut loop_keys = keys.clone();
loop_keys.reverse(); loop_keys.reverse();
for i in 1..loop_keys.len() { for i in 1..loop_keys.len() {
@@ -119,7 +115,7 @@ fn load_gradient(ini: &Ini) -> ComputableColor {
} }
} }
let steps: usize = if get_app_mode() == AppMode::Debug { let steps: usize = if debug_mode {
debug::DEBUG_COLOR_DISPLAY_SIZE * 2 debug::DEBUG_COLOR_DISPLAY_SIZE * 2
} else { } else {
ini.getuint("gradient", "gradient_steps") ini.getuint("gradient", "gradient_steps")

View File

@@ -2,10 +2,8 @@ use core::panic;
use std::{ use std::{
io::{self, Write}, io::{self, Write},
path::PathBuf, path::PathBuf,
sync::atomic::Ordering,
}; };
use atomic_enum::atomic_enum;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use config::write_default_config; use config::write_default_config;
use crossterm::{cursor, execute, terminal}; use crossterm::{cursor, execute, terminal};
@@ -39,37 +37,15 @@ struct Cli {
enum Commands { enum Commands {
Debug {}, Debug {},
Chrono {}, Chrono {},
} Countdown {
#[arg(required = true)]
#[atomic_enum] start: Vec<String>,
#[derive(PartialEq)] },
pub enum AppMode {
Clock = 0,
Debug,
Chrono,
}
static APP_MODE: AtomicAppMode = AtomicAppMode::new(AppMode::Debug);
pub fn get_app_mode() -> AppMode {
return APP_MODE.load(Ordering::Relaxed);
}
pub fn set_app_mode(mode: AppMode) {
return APP_MODE.store(mode, Ordering::Relaxed);
} }
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command {
Some(Commands::Debug {}) => {
set_app_mode(AppMode::Debug);
}
Some(Commands::Chrono {}) => set_app_mode(AppMode::Chrono),
_ => set_app_mode(AppMode::Clock),
}
// Load config // Load config
let mut default_generated = false; let mut default_generated = false;
let config_file = if let Some(custom_config) = cli.config { let config_file = if let Some(custom_config) = cli.config {
@@ -110,26 +86,31 @@ fn main() -> io::Result<()> {
panic!("ERROR: Configuration file not found"); panic!("ERROR: Configuration file not found");
} }
let mut config = config::load_from_file(config_file); let debug_mode = match &cli.command {
Some(Commands::Debug {}) => true,
_ => false,
};
let mut config = config::load_from_file(config_file, debug_mode);
let mut stdout = io::stdout(); let mut stdout = io::stdout();
match get_app_mode() { if debug_mode {
AppMode::Debug => { debug::print_debug_infos(&mut config)?;
debug::print_debug_infos(&mut config)?; return Ok(());
return Ok(());
}
_ => {}
} }
// Switch to alternate screen, hide the cursor and enable raw mode // Switch to alternate screen, hide the cursor and enable raw mode
execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?; execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?;
let _ = terminal::enable_raw_mode()?; let _ = terminal::enable_raw_mode()?;
match get_app_mode() { match &cli.command {
AppMode::Clock => modes::clock::main_loop(&mut config)?, Some(Commands::Chrono {}) => modes::chrono::main_loop(&mut config)?,
AppMode::Chrono => modes::chrono::main_loop(&mut config)?, Some(Commands::Countdown { start }) => {
AppMode::Debug => unreachable!(), let start = start.join(" ");
}; modes::countdown::main_loop(&mut config, &start)?
}
Some(Commands::Debug {}) => unreachable!(),
None => modes::clock::main_loop(&mut config)?,
}
// Disale raw mode, leave the alternate screen and show the cursor back // Disale raw mode, leave the alternate screen and show the cursor back
let _ = terminal::disable_raw_mode().unwrap(); let _ = terminal::disable_raw_mode().unwrap();

157
src/modes/countdown.rs Normal file
View File

@@ -0,0 +1,157 @@
use std::{
io::{self, Write},
thread,
time::{Duration, Instant},
};
use crossterm::{
event::{self, Event, KeyCode, KeyModifiers},
queue,
terminal::{self, ClearType},
};
use crate::utils;
use crate::{
config::Config,
rendering::{self, symbols},
};
struct Countdown {
duration: Duration,
end_time: Option<Instant>,
paused_duration: Duration,
}
impl Countdown {
fn new(duration: Duration) -> Self {
let end_time = Some(Instant::now() + duration + Duration::from_secs(1));
Countdown {
duration,
end_time,
paused_duration: Duration::ZERO,
}
}
fn toggle_pause(&mut self) {
if let Some(end_time) = self.end_time {
self.paused_duration += end_time.duration_since(Instant::now());
self.end_time = None;
} else {
self.end_time = Some(Instant::now() + self.paused_duration);
self.paused_duration = Duration::ZERO;
}
}
fn time_left(&self) -> Duration {
if let Some(end_time) = self.end_time {
let remaining_time = if Instant::now() < end_time {
end_time.duration_since(Instant::now())
} else {
Duration::ZERO
};
remaining_time - self.paused_duration
} else {
self.paused_duration
}
}
fn is_finished(&self) -> bool {
self.time_left().as_secs() == 0
}
fn is_paused(&self) -> bool {
return self.end_time.is_none();
}
fn reset(&mut self) {
self.end_time = Some(Instant::now() + self.duration + Duration::from_secs(1));
self.paused_duration = Duration::ZERO;
self.toggle_pause();
}
}
pub fn main_loop(config: &mut Config, start: &str) -> io::Result<()> {
let mut stdout = io::stdout();
let duration = parse_duration::parse(start).unwrap();
let mut countdown = Countdown::new(duration);
let mut quit = false;
while !quit {
// Handle events
while event::poll(Duration::ZERO)? {
match event::read()? {
Event::Key(e) => match e.code {
// Handle CTRL-C
KeyCode::Char('c') => {
if e.modifiers.contains(KeyModifiers::CONTROL) {
quit = true;
}
}
// Handle pause
KeyCode::Char(' ') => {
countdown.toggle_pause();
}
// Handle reset
KeyCode::Char('r') => {
countdown.reset();
}
_ => {}
},
_ => {}
}
}
// Clear frame
queue!(stdout, terminal::Clear(ClearType::All))?;
// Render
render_frame(&config, &countdown)?;
config.color.update();
stdout.flush()?;
thread::sleep(Duration::from_millis(1000 / config.fps));
}
return Ok(());
}
fn render_frame(config: &Config, countdown: &Countdown) -> io::Result<()> {
let color = config.color.get_value();
// Display time
let remaining = utils::format_duration(countdown.time_left());
rendering::draw_time(&remaining, color)?;
// Display pause state
let (width, height) = terminal::size()?;
let y = height / 2 + symbols::SYMBOL_HEIGHT as u16 / 2 + 2;
if countdown.is_paused() {
let text = "[PAUSE]";
let x = width / 2 - (text.len() as u16) / 2;
rendering::draw_text(
text,
x,
y - symbols::SYMBOL_HEIGHT as u16 - symbols::SYMBOL_HEIGHT as u16 / 2,
color,
)?;
}
// Display finish state
else if countdown.is_finished() {
let text = "[FINISHED]";
let x = width / 2 - (text.len() as u16) / 2;
rendering::draw_text(
text,
x,
y - symbols::SYMBOL_HEIGHT as u16 - symbols::SYMBOL_HEIGHT as u16 / 2,
color,
)?;
}
return Ok(());
}

View File

@@ -1,3 +1,4 @@
pub mod chrono; pub mod chrono;
pub mod clock; pub mod clock;
pub mod countdown;
pub mod debug; pub mod debug;