use crate::utils::PrettyDuration;
use chrono::Duration;
use clock_core::timer::{Timer, TimerData};
use cursive::{
event::{Callback, Event, EventResult, Key},
theme::ColorStyle,
view::View,
Cursive, Printer, Vec2, With,
};
use std::rc::Rc;
#[derive(Copy, Clone)]
enum TimerViewState {
Config,
Running,
Finished,
}
struct TimerViewConfig {
h: u8,
m: u8,
s: u8,
focus: u8,
input_buffer: Vec<u8>,
}
pub struct TimerView {
timer: Timer,
remaining: Duration,
state: TimerViewState,
config: TimerViewConfig,
on_finish: Option<Rc<dyn Fn(&mut Cursive, TimerData)>>,
}
impl TimerView {
pub fn new(h: u8, m: u8, s: u8) -> Self {
let config = TimerViewConfig {
h,
m,
s,
focus: 1,
input_buffer: Vec::new(),
};
Self {
timer: Timer::new(Duration::zero()),
remaining: Duration::zero(),
config,
state: TimerViewState::Config,
on_finish: None,
}
}
pub fn start(&mut self) {
let seconds =
self.config.h as i64 * 3600 + self.config.m as i64 * 60 + self.config.s as i64;
self.timer = Timer::new(Duration::seconds(seconds));
self.state = TimerViewState::Running;
self.timer.pause_or_resume();
}
pub fn set_on_finish<F, R>(&mut self, cb: F)
where
F: 'static + Fn(&mut Cursive, TimerData) -> R,
{
self.on_finish = Some(Rc::new(move |s, t| {
cb(s, t);
}));
}
pub fn on_finish<F, R>(self, cb: F) -> Self
where
F: 'static + Fn(&mut Cursive, TimerData) -> R,
{
self.with(|s| s.set_on_finish(cb))
}
fn finish(&mut self) -> EventResult {
self.state = TimerViewState::Finished;
let data = self.timer.stop();
if self.on_finish.is_some() {
let cb = self.on_finish.clone().unwrap();
EventResult::Consumed(Some(Callback::from_fn_once(move |s| cb(s, data))))
} else {
EventResult::Consumed(None)
}
}
fn draw_running(&self, printer: &Printer) {
printer.print((0, 0), &self.remaining.pretty());
}
fn draw_finished(&self, printer: &Printer) {
printer.print((0, 0), "FINISHED!");
}
fn draw_config(&self, printer: &Printer) {
fn format(n: u8) -> String {
format!("{:02}", n)
}
let (h, m, s) = (
format(self.config.h),
format(self.config.m),
format(self.config.s),
);
if self.config.focus % 3 == 0 {
printer.with_color(ColorStyle::highlight(), |printer| printer.print((0, 0), &h));
} else {
printer.print((0, 0), &h);
}
printer.print((2, 0), ":");
if self.config.focus % 3 == 1 {
printer.with_color(ColorStyle::highlight(), |printer| printer.print((3, 0), &m));
} else {
printer.print((3, 0), &m);
}
printer.print((5, 0), ":");
if self.config.focus % 3 == 2 {
printer.with_color(ColorStyle::highlight(), |printer| printer.print((6, 0), &s));
} else {
printer.print((6, 0), &s);
}
}
fn set_selection(&mut self, v: u8) {
match self.config.focus % 3 {
0 => self.config.h = v,
1 => self.config.m = v,
2 => self.config.s = v,
_ => unreachable!(),
}
}
fn move_focus_right(&mut self) {
self.config.focus += 1;
self.config.input_buffer.clear();
}
fn move_focus_left(&mut self) {
self.config.focus -= 1;
self.config.input_buffer.clear();
}
fn read_buffer(&self) -> u8 {
let buffer = &self.config.input_buffer;
let n = match buffer.len() {
0 => 0,
1 => buffer[0],
2 => buffer[0] * 10 + buffer[1],
_ => unreachable!(),
};
match self.config.focus % 3 {
0 => n,
1 | 2 => {
if n < 60 {
n
} else {
59
}
}
_ => unreachable!(),
}
}
}
impl View for TimerView {
fn draw(&self, printer: &Printer) {
match self.state {
TimerViewState::Running => self.draw_running(printer),
TimerViewState::Config => self.draw_config(printer),
TimerViewState::Finished => self.draw_finished(printer),
}
}
fn required_size(&mut self, _constraint: Vec2) -> Vec2 {
Vec2::new(12, 1)
}
fn on_event(&mut self, event: Event) -> EventResult {
match self.state {
TimerViewState::Running => {
match event {
Event::Char(' ') => {
self.timer.pause_or_resume();
}
Event::Refresh => {
self.remaining = self.timer.read();
if self.remaining.num_milliseconds() < 10 {
return self.finish();
}
}
Event::Key(Key::Enter) => {
return self.finish();
}
_ => {
if self.timer.data.remaining.num_milliseconds() < 10 {
self.state = TimerViewState::Finished;
return self.finish();
}
}
}
}
TimerViewState::Finished => match event {
Event::Char(' ') | Event::Key(Key::Enter) => {
self.state = TimerViewState::Config;
}
_ => return EventResult::Ignored,
},
TimerViewState::Config => match event {
Event::Char(c) => {
if c.is_numeric() {
self.config.input_buffer.push(c.to_digit(10).unwrap() as u8);
}
self.set_selection(self.read_buffer());
if self.config.input_buffer.len() == 2 {
self.move_focus_right();
}
}
Event::Key(Key::Right) | Event::Key(Key::Tab) => {
self.move_focus_right();
}
Event::Key(Key::Left) => self.move_focus_left(),
Event::Key(Key::Enter) => {
self.start();
}
_ => return EventResult::Ignored,
},
}
EventResult::Consumed(None)
}
}