feat(countdown): implement
This commit is contained in:
@@ -4,10 +4,10 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
atomic_enum = "0.2.0"
|
||||
chrono = "0.4.31"
|
||||
clap = { version = "4.4.18", features = ["derive", "cargo"] }
|
||||
crossterm = "0.27.0"
|
||||
dirs = "5.0.1"
|
||||
ini = "1.3.0"
|
||||
parse_duration = "2.1.1"
|
||||
|
||||
|
||||
@@ -5,10 +5,8 @@ use crossterm::style::Color;
|
||||
use ini::configparser::ini::Ini;
|
||||
|
||||
use crate::{
|
||||
get_app_mode,
|
||||
modes::debug,
|
||||
rendering::color::{generate_gradient, parse_hex_color, ComputableColor},
|
||||
AppMode,
|
||||
};
|
||||
|
||||
pub struct Config {
|
||||
@@ -21,14 +19,14 @@ pub struct 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();
|
||||
ini.load(&path.to_str().unwrap()).unwrap();
|
||||
|
||||
let config = Config {
|
||||
be_polite: ini.getbool("general", "polite").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(),
|
||||
date_format: ini.get("format", "date").unwrap(),
|
||||
};
|
||||
@@ -42,7 +40,7 @@ pub fn write_default_config(path: PathBuf) -> () {
|
||||
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();
|
||||
|
||||
match color_mode.as_str() {
|
||||
@@ -59,7 +57,7 @@ fn load_color(ini: &Ini) -> ComputableColor {
|
||||
return ComputableColor::from(load_ansi_color(color));
|
||||
}
|
||||
"gradient" => {
|
||||
return load_gradient(ini);
|
||||
return load_gradient(ini, debug_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());
|
||||
}
|
||||
|
||||
fn load_gradient(ini: &Ini) -> ComputableColor {
|
||||
fn load_gradient(ini: &Ini, debug_mode: bool) -> ComputableColor {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
let mut i = 0;
|
||||
@@ -109,9 +107,7 @@ fn load_gradient(ini: &Ini) -> ComputableColor {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if get_app_mode() != AppMode::Debug
|
||||
&& ini.getbool("gradient", "gradient_loop").unwrap().unwrap()
|
||||
{
|
||||
if !debug_mode && ini.getbool("gradient", "gradient_loop").unwrap().unwrap() {
|
||||
let mut loop_keys = keys.clone();
|
||||
loop_keys.reverse();
|
||||
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
|
||||
} else {
|
||||
ini.getuint("gradient", "gradient_steps")
|
||||
|
||||
57
src/main.rs
57
src/main.rs
@@ -2,10 +2,8 @@ use core::panic;
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
sync::atomic::Ordering,
|
||||
};
|
||||
|
||||
use atomic_enum::atomic_enum;
|
||||
use clap::{Parser, Subcommand};
|
||||
use config::write_default_config;
|
||||
use crossterm::{cursor, execute, terminal};
|
||||
@@ -39,37 +37,15 @@ struct Cli {
|
||||
enum Commands {
|
||||
Debug {},
|
||||
Chrono {},
|
||||
}
|
||||
|
||||
#[atomic_enum]
|
||||
#[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);
|
||||
Countdown {
|
||||
#[arg(required = true)]
|
||||
start: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
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
|
||||
let mut default_generated = false;
|
||||
let config_file = if let Some(custom_config) = cli.config {
|
||||
@@ -110,26 +86,31 @@ fn main() -> io::Result<()> {
|
||||
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();
|
||||
|
||||
match get_app_mode() {
|
||||
AppMode::Debug => {
|
||||
if debug_mode {
|
||||
debug::print_debug_infos(&mut config)?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Switch to alternate screen, hide the cursor and enable raw mode
|
||||
execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?;
|
||||
let _ = terminal::enable_raw_mode()?;
|
||||
|
||||
match get_app_mode() {
|
||||
AppMode::Clock => modes::clock::main_loop(&mut config)?,
|
||||
AppMode::Chrono => modes::chrono::main_loop(&mut config)?,
|
||||
AppMode::Debug => unreachable!(),
|
||||
};
|
||||
match &cli.command {
|
||||
Some(Commands::Chrono {}) => modes::chrono::main_loop(&mut config)?,
|
||||
Some(Commands::Countdown { start }) => {
|
||||
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
|
||||
let _ = terminal::disable_raw_mode().unwrap();
|
||||
|
||||
157
src/modes/countdown.rs
Normal file
157
src/modes/countdown.rs
Normal 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(());
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod chrono;
|
||||
pub mod clock;
|
||||
pub mod countdown;
|
||||
pub mod debug;
|
||||
|
||||
Reference in New Issue
Block a user