use crate::internal::duration::duration_string;
use rusty_bubbletea::commands;
use rusty_bubbletea::model::{Cmd, Msg};
use std::fmt;
use std::sync::atomic::{AtomicI64, Ordering};
use std::time::Duration;
static LAST_ID: AtomicI64 = AtomicI64::new(0);
fn next_id() -> i32 {
(LAST_ID.fetch_add(1, Ordering::SeqCst)) as i32
}
pub type Option = Box<dyn FnOnce(&mut Model)>;
pub fn with_interval(interval: Duration) -> Option {
Box::new(move |m: &mut Model| {
m.interval = interval;
})
}
#[derive(Debug, Clone)]
pub struct StartStopMsg {
pub id: i32,
running: bool,
}
#[derive(Debug, Clone)]
pub struct TickMsg {
pub id: i32,
pub timeout: bool,
tag: i32,
}
#[derive(Debug, Clone)]
pub struct TimeoutMsg {
pub id: i32,
}
#[derive(Clone)]
pub struct Model {
pub timeout: Duration,
pub interval: Duration,
id: i32,
tag: i32,
running: bool,
}
impl fmt::Debug for Model {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("timer::Model")
.field("id", &self.id)
.field("timeout", &self.timeout)
.field("running", &self.running)
.finish()
}
}
pub fn new(timeout: Duration, opts: Vec<Option>) -> Model {
let mut m = Model {
timeout,
interval: Duration::from_secs(1),
running: true,
id: next_id(),
tag: 0,
};
for opt in opts {
opt(&mut m);
}
m
}
impl Model {
pub fn id(&self) -> i32 {
self.id
}
pub fn running(&self) -> bool {
if self.timedout() || !self.running {
return false;
}
true
}
pub fn timedout(&self) -> bool {
self.timeout <= Duration::ZERO
}
pub fn init(&mut self) -> Cmd {
self.tick()
}
pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
if let Some(m) = msg.as_any().downcast_ref::<StartStopMsg>() {
if m.id != 0 && m.id != self.id {
return None;
}
self.running = m.running;
return self.tick();
}
if let Some(m) = msg.as_any().downcast_ref::<TickMsg>() {
if !self.running() || (m.id != 0 && m.id != self.id) {
return None;
}
if m.tag > 0 && m.tag != self.tag {
return None;
}
self.timeout = self.timeout.saturating_sub(self.interval);
let tick_cmd = self.tick();
let timeout_cmd = self.timeout_msg();
return commands::batch(vec![tick_cmd, timeout_cmd]);
}
None
}
pub fn view(&self) -> String {
duration_string(self.timeout)
}
pub fn start(&mut self) -> Cmd {
self.start_stop(true)
}
pub fn stop(&mut self) -> Cmd {
self.start_stop(false)
}
pub fn toggle(&mut self) -> Cmd {
self.start_stop(!self.running())
}
fn tick(&mut self) -> Cmd {
let id = self.id;
let tag = self.tag;
let timeout = self.timedout();
let interval = self.interval;
commands::tick(interval, move |_| {
Some(Box::new(TickMsg { id, tag, timeout }))
})
}
fn timeout_msg(&self) -> Cmd {
if !self.timedout() {
return None;
}
let id = self.id;
Some(Box::new(move || Some(Box::new(TimeoutMsg { id }))))
}
fn start_stop(&mut self, v: bool) -> Cmd {
let id = self.id;
Some(Box::new(move || {
Some(Box::new(StartStopMsg { id, running: v }))
}))
}
}