clock_cli/tui/
stopwatch.rs

1// Copyright (C) 2020 Tianyi Shi
2//
3// This file is part of clock-cli-rs.
4//
5// clock-cli-rs is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// clock-cli-rs is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with clock-cli-rs.  If not, see <http://www.gnu.org/licenses/>.
17
18use crate::utils::PrettyDuration;
19// use chrono::{DateTime, Duration, Local};
20use clock_core::stopwatch::{Stopwatch, StopwatchData};
21use cursive::{
22    event::{Callback, Event, EventResult, Key, MouseEvent},
23    view::View,
24    Cursive, Printer, Vec2, With,
25};
26use std::rc::Rc;
27
28#[derive(Default)]
29pub struct StopwatchView {
30    stopwatch: Stopwatch,
31    on_stop: Option<Rc<dyn Fn(&mut Cursive, StopwatchData)>>,
32    show_laps: usize,
33    show_laps_offset: usize,
34}
35
36impl StopwatchView {
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    pub fn with_laps(mut self, n: usize) -> Self {
42        self.show_laps = n;
43        self
44    }
45
46    pub fn set_on_stop<F, R>(&mut self, cb: F)
47    where
48        F: 'static + Fn(&mut Cursive, StopwatchData) -> R,
49    {
50        self.on_stop = Some(Rc::new(move |s, t| {
51            cb(s, t);
52        }));
53    }
54
55    pub fn on_stop<F, R>(self, cb: F) -> Self
56    where
57        F: 'static + Fn(&mut Cursive, StopwatchData) -> R,
58    {
59        self.with(|s| s.set_on_stop(cb))
60    }
61
62    fn stop(&mut self) -> EventResult {
63        let data = self.stopwatch.stop();
64        if self.on_stop.is_some() {
65            let cb = self.on_stop.clone().unwrap();
66            EventResult::Consumed(Some(Callback::from_fn_once(move |s| cb(s, data))))
67        } else {
68            EventResult::Consumed(None)
69        }
70    }
71
72    fn increment_show_lap_offset(&mut self) {
73        if self.stopwatch.data.laps.len() > self.show_laps + self.show_laps_offset {
74            self.show_laps_offset += 1;
75        }
76    }
77
78    fn decrement_show_lap_offset(&mut self) {
79        if self.show_laps_offset > 0 {
80            self.show_laps_offset -= 1;
81        }
82    }
83}
84impl View for StopwatchView {
85    fn draw(&self, printer: &Printer) {
86        printer.print((4, 0), &self.stopwatch.read().pretty());
87
88        let len = self.stopwatch.data.laps.len() - self.show_laps_offset;
89        let mut i = 0;
90        while i < std::cmp::min(len, self.show_laps) {
91            i += 1;
92
93            printer.print(
94                (0, i),
95                &[
96                    format!("Lap {:02}: ", len - i + 1),
97                    self.stopwatch.data.laps[len - i].pretty(),
98                ]
99                .concat(),
100            );
101        }
102        if len != i {
103            printer.print((0, self.show_laps), ":                           ");
104        }
105    }
106
107    fn required_size(&mut self, _constraint: Vec2) -> Vec2 {
108        // the required size depends on how many lap times the user want to diaplay
109        Vec2::new(20, self.show_laps + 1) // columns, rows (width, height)
110    }
111
112    fn on_event(&mut self, event: Event) -> EventResult {
113        match event {
114            // pause/resume the stopwatch when pressing "Space"
115            Event::Char(' ') => {
116                self.stopwatch.pause_or_resume();
117            }
118            Event::Key(Key::Enter) => {
119                self.show_laps_offset = 0; // FUTURE: maybe unneeded?
120                return self.stop();
121            }
122            Event::Char('l') => {
123                self.stopwatch.lap();
124                self.show_laps_offset = 0;
125            }
126            Event::Key(Key::Up) => {
127                self.decrement_show_lap_offset();
128            }
129            Event::Key(Key::Down) => {
130                self.increment_show_lap_offset();
131            }
132            Event::Mouse { event, .. } => {
133                match event {
134                    MouseEvent::WheelUp => {
135                        self.decrement_show_lap_offset();
136                    }
137                    MouseEvent::WheelDown => {
138                        self.increment_show_lap_offset();
139                    }
140                    _ => return EventResult::Ignored,
141                }
142                return EventResult::Consumed(None);
143            }
144            _ => return EventResult::Ignored,
145        }
146        EventResult::Consumed(None)
147    }
148}