rusty_bubbles/
stopwatch.rs1use 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 TickMsg {
43 pub id: i32,
51 tag: i32,
52}
53
54#[derive(Debug, Clone)]
56pub struct StartStopMsg {
57 pub id: i32,
59 running: bool,
60}
61
62#[derive(Debug, Clone)]
64pub struct ResetMsg {
65 pub id: i32,
67}
68
69#[derive(Clone)]
71pub struct Model {
72 d: Duration,
73 id: i32,
74 tag: i32,
75 running: bool,
76
77 pub interval: Duration,
79}
80
81impl fmt::Debug for Model {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 f.debug_struct("stopwatch::Model")
84 .field("id", &self.id)
85 .field("running", &self.running)
86 .field("d", &self.d)
87 .finish()
88 }
89}
90
91pub fn new(opts: Vec<Option>) -> Model {
93 let mut m = Model {
94 id: next_id(),
95 interval: Duration::from_secs(1),
96 d: Duration::ZERO,
97 tag: 0,
98 running: false,
99 };
100
101 for opt in opts {
102 opt(&mut m);
103 }
104 m
105}
106
107impl Model {
108 pub fn id(&self) -> i32 {
110 self.id
111 }
112
113 pub fn init(&mut self) -> Cmd {
115 self.start()
116 }
117
118 pub fn start(&mut self) -> Cmd {
120 let start_msg: Box<dyn Msg> = Box::new(StartStopMsg {
121 id: self.id,
122 running: true,
123 });
124 let tick_cmd = tick(self.id, self.tag, self.interval);
125 commands::sequence(vec![Some(Box::new(move || Some(start_msg))), tick_cmd])
126 }
127
128 pub fn stop(&mut self) -> Cmd {
130 let id = self.id;
131 Some(Box::new(move || {
132 Some(Box::new(StartStopMsg { id, running: false }))
133 }))
134 }
135
136 pub fn toggle(&mut self) -> Cmd {
139 if self.running {
140 return self.stop();
141 }
142 self.start()
143 }
144
145 pub fn reset(&mut self) -> Cmd {
147 let id = self.id;
148 Some(Box::new(move || Some(Box::new(ResetMsg { id }))))
149 }
150
151 pub fn running(&self) -> bool {
154 self.running
155 }
156
157 pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
159 if let Some(m) = msg.as_any().downcast_ref::<StartStopMsg>() {
160 if m.id != self.id {
161 return None;
162 }
163 self.running = m.running;
164 return None;
165 }
166
167 if let Some(m) = msg.as_any().downcast_ref::<ResetMsg>() {
168 if m.id != self.id {
169 return None;
170 }
171 self.d = Duration::ZERO;
172 return None;
173 }
174
175 if let Some(m) = msg.as_any().downcast_ref::<TickMsg>() {
176 if !self.running || m.id != self.id {
177 return None;
178 }
179
180 if m.tag > 0 && m.tag != self.tag {
184 return None;
185 }
186
187 self.d += self.interval;
188 self.tag += 1;
189 return tick(self.id, self.tag, self.interval);
190 }
191
192 None
193 }
194
195 pub fn elapsed(&self) -> Duration {
197 self.d
198 }
199
200 pub fn view(&self) -> String {
202 duration_string(self.d)
203 }
204}
205
206fn tick(id: i32, tag: i32, d: Duration) -> Cmd {
207 commands::tick(d, move |_| Some(Box::new(TickMsg { id, tag })))
208}