1use crate::internal::duration::duration_string;
11use rusty_bubbletea::commands;
12use rusty_bubbletea::model::{Cmd, Msg};
13use std::fmt;
14use std::sync::atomic::{AtomicI64, Ordering};
15use std::time::Duration;
16
17static LAST_ID: AtomicI64 = AtomicI64::new(0);
18
19fn next_id() -> i32 {
20 (LAST_ID.fetch_add(1, Ordering::SeqCst)) as i32
21}
22
23pub type Option = Box<dyn FnOnce(&mut Model)>;
31
32pub fn with_interval(interval: Duration) -> Option {
35 Box::new(move |m: &mut Model| {
36 m.interval = interval;
37 })
38}
39
40#[derive(Debug, Clone)]
42pub struct StartStopMsg {
43 pub id: i32,
45 running: bool,
46}
47
48#[derive(Debug, Clone)]
50pub struct TickMsg {
51 pub id: i32,
59
60 pub timeout: bool,
63
64 tag: i32,
65}
66
67#[derive(Debug, Clone)]
72pub struct TimeoutMsg {
73 pub id: i32,
75}
76
77#[derive(Clone)]
79pub struct Model {
80 pub timeout: Duration,
82
83 pub interval: Duration,
85
86 id: i32,
87 tag: i32,
88 running: bool,
89}
90
91impl fmt::Debug for Model {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.debug_struct("timer::Model")
94 .field("id", &self.id)
95 .field("timeout", &self.timeout)
96 .field("running", &self.running)
97 .finish()
98 }
99}
100
101pub fn new(timeout: Duration, opts: Vec<Option>) -> Model {
103 let mut m = Model {
104 timeout,
105 interval: Duration::from_secs(1),
106 running: true,
107 id: next_id(),
108 tag: 0,
109 };
110 for opt in opts {
111 opt(&mut m);
112 }
113 m
114}
115
116impl Model {
117 pub fn id(&self) -> i32 {
120 self.id
121 }
122
123 pub fn running(&self) -> bool {
126 if self.timedout() || !self.running {
127 return false;
128 }
129 true
130 }
131
132 pub fn timedout(&self) -> bool {
134 self.timeout <= Duration::ZERO
135 }
136
137 pub fn init(&mut self) -> Cmd {
139 self.tick()
140 }
141
142 pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
144 if let Some(m) = msg.as_any().downcast_ref::<StartStopMsg>() {
145 if m.id != 0 && m.id != self.id {
146 return None;
147 }
148 self.running = m.running;
149 return self.tick();
150 }
151
152 if let Some(m) = msg.as_any().downcast_ref::<TickMsg>() {
153 if !self.running() || (m.id != 0 && m.id != self.id) {
154 return None;
155 }
156
157 if m.tag > 0 && m.tag != self.tag {
161 return None;
162 }
163
164 self.timeout = self.timeout.saturating_sub(self.interval);
165 let tick_cmd = self.tick();
166 let timeout_cmd = self.timeout_msg();
167 return commands::batch(vec![tick_cmd, timeout_cmd]);
168 }
169
170 None
171 }
172
173 pub fn view(&self) -> String {
175 duration_string(self.timeout)
176 }
177
178 pub fn start(&mut self) -> Cmd {
180 self.start_stop(true)
181 }
182
183 pub fn stop(&mut self) -> Cmd {
185 self.start_stop(false)
186 }
187
188 pub fn toggle(&mut self) -> Cmd {
190 self.start_stop(!self.running())
191 }
192
193 fn tick(&mut self) -> Cmd {
194 let id = self.id;
195 let tag = self.tag;
196 let timeout = self.timedout();
197 let interval = self.interval;
198 commands::tick(interval, move |_| {
199 Some(Box::new(TickMsg { id, tag, timeout }))
200 })
201 }
202
203 fn timeout_msg(&self) -> Cmd {
204 if !self.timedout() {
205 return None;
206 }
207 let id = self.id;
208 Some(Box::new(move || Some(Box::new(TimeoutMsg { id }))))
209 }
210
211 fn start_stop(&mut self, v: bool) -> Cmd {
212 let id = self.id;
213 Some(Box::new(move || {
214 Some(Box::new(StartStopMsg { id, running: v }))
215 }))
216 }
217}