-
Notifications
You must be signed in to change notification settings - Fork 239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add new example: stopwatch #503
Merged
Merged
Changes from 14 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
4121212
new example: autocomplete search box
TianyiShi2001 6578e31
add new example: stopwatch
TianyiShi2001 586e614
better match function
TianyiShi2001 5bf442d
fixed size and scrollable
TianyiShi2001 1a85b73
submit without moving into the `matches` view
TianyiShi2001 1bd2a64
remove unsed imports
TianyiShi2001 f1f6bab
isolate the search function
TianyiShi2001 71d0669
no need to own
TianyiShi2001 258daca
remove the fixed width
TianyiShi2001 96a0fbb
working timer
TianyiShi2001 2fd22a6
this won't work
TianyiShi2001 bad48c4
laps
TianyiShi2001 8fd04b6
improve precision
TianyiShi2001 8262a7f
on_stop is working
TianyiShi2001 9cff334
fix https://github.com/gyscos/cursive/pull/503#discussion_r492864443
TianyiShi2001 1521803
show laps optionally
TianyiShi2001 00c2166
Merge remote-tracking branch 'upstream/main' into stopwatch
TianyiShi2001 5888427
refactor
TianyiShi2001 f8f3a8f
more docs
TianyiShi2001 cc13465
return full data when stop
TianyiShi2001 03b4f23
reduce funtionality
TianyiShi2001 3c54ac5
more comments
TianyiShi2001 2f90337
remove cloning
TianyiShi2001 8fc4faf
however, this won't work
TianyiShi2001 ad14207
fixed
TianyiShi2001 761d738
.
TianyiShi2001 f954e87
add info of my crate
TianyiShi2001 da2731e
minimize
TianyiShi2001 f7ce122
simplify
TianyiShi2001 8875418
add new example: stopwatch
TianyiShi2001 4b86afc
Improved the `select` example. (#501)
LunarEclipse363 7a3d906
Merge branch 'stopwatch' of https://github.com/TianyiShi2001/cursive …
TianyiShi2001 4d90e36
remove dependency on chrono
TianyiShi2001 7712beb
Merge branch 'main' of https://github.com/gyscos/cursive into main
TianyiShi2001 2ff936a
Merge branch 'main' into stopwatch
TianyiShi2001 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
use chrono::Duration; | ||
use cursive::{traits::*, views::Dialog, Cursive}; | ||
use stopwatch::{StopWatch, StopWatchView}; | ||
|
||
// A simple stopwatch without 'lap time' function is implemented in this example. | ||
// Press "Space" to start/pause/resume the stopwatch. Press "Enter" to stop and | ||
// get all data: moments at which the stopwatch is started/resumed, moments at which | ||
// the stopwatch is paused/stopped; elapsed time. | ||
|
||
fn main() { | ||
let mut siv = cursive::default(); | ||
let stopwatch = StopWatchView::new(); | ||
siv.add_layer( | ||
stopwatch | ||
// On stop, get all data and summarize them in an info box. | ||
.on_stop(|s: &mut Cursive, stopwatch| { | ||
s.add_layer(Dialog::info(summarize(stopwatch))) | ||
}), | ||
); | ||
siv.add_layer(Dialog::info( | ||
"Press 'Space' to start/pause/resume the stopwatch\nPress 'Enter' to stop", | ||
)); | ||
// the stopwatch is redrawn 15 times per second | ||
siv.set_fps(15); | ||
siv.run(); | ||
} | ||
|
||
fn summarize(stopwatch: &StopWatch) -> String { | ||
let elapsed = stopwatch.elapsed; | ||
let n = stopwatch.pause_moments.len(); | ||
let total_elapsed = | ||
stopwatch.pause_moments[n - 1] - stopwatch.start_moments[0]; | ||
format!( | ||
"Elapsed time: {}\nTotal elapsed: {}\nPaused {} times", | ||
elapsed.pretty(), | ||
total_elapsed.pretty(), | ||
n - 1, | ||
) | ||
} | ||
|
||
pub trait PrettyDuration { | ||
fn pretty(&self) -> String; | ||
} | ||
impl PrettyDuration for Duration { | ||
/// Pretty-prints a chrono::Duration in the form `HH:MM:SS.xxx` | ||
/// A custom trait is used because `std::fmt::Diaplay` cannot be implemented | ||
/// for a struct coming from another external crate, due to the orphan rule | ||
fn pretty(&self) -> String { | ||
let s = self.num_seconds(); | ||
let ms = self.num_milliseconds() - 1000 * s; | ||
let (h, s) = (s / 3600, s % 3600); | ||
let (m, s) = (s / 60, s % 60); | ||
format!("{:02}:{:02}:{:02}.{:03}", h, m, s, ms) | ||
} | ||
} | ||
|
||
mod stopwatch { | ||
use super::PrettyDuration; | ||
use chrono::{DateTime, Duration, Local}; | ||
use cursive::{ | ||
event::{Event, EventResult, Key}, | ||
view::View, | ||
Cursive, Printer, Vec2, With, | ||
}; | ||
use std::rc::Rc; | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct StopWatch { | ||
// These data might be useful to the user | ||
pub elapsed: Duration, // total elapsed time | ||
pub pause_moments: Vec<DateTime<Local>>, // moments at which the stopwatch is paused | ||
pub start_moments: Vec<DateTime<Local>>, // moments at which the stopwatch resumes | ||
paused: bool, | ||
} | ||
|
||
impl StopWatch { | ||
/// Returns a stopwatch that is reset to zero | ||
pub fn new() -> Self { | ||
Self { | ||
elapsed: Duration::zero(), | ||
start_moments: Vec::new(), | ||
pause_moments: Vec::new(), | ||
paused: true, // stopped by default; start by explicitly calling `.resume()` | ||
} | ||
} | ||
|
||
fn last_start(&self) -> DateTime<Local> { | ||
self.start_moments[self.start_moments.len() - 1] | ||
} | ||
fn pause(&mut self) { | ||
assert!(self.paused == false, "Already paused!"); | ||
let moment = Local::now(); | ||
self.pause_moments.push(moment); | ||
self.elapsed = self.elapsed + (moment - self.last_start()); | ||
self.paused = true; | ||
} | ||
fn resume(&mut self) { | ||
assert!(self.paused == true, "Already running!"); | ||
self.start_moments.push(Local::now()); | ||
self.paused = false; | ||
} | ||
fn pause_or_resume(&mut self) { | ||
if self.paused { | ||
self.resume(); | ||
} else { | ||
self.pause(); | ||
} | ||
} | ||
/// Read the total time elapsed | ||
fn read(&self) -> Duration { | ||
if self.paused { | ||
self.elapsed | ||
} else { | ||
self.elapsed + (Local::now() - self.last_start()) | ||
} | ||
} | ||
} | ||
|
||
/// Separating the `StopWatch` 'core' and the `StopWatchView` improves reusability | ||
/// and flexibility. The user may implement their own `View`s, i.e. layouts, based | ||
/// on the same `StopWatch` logic. | ||
pub struct StopWatchView { | ||
stopwatch: StopWatch, | ||
on_stop: Option<Rc<dyn Fn(&mut Cursive, &StopWatch)>>, | ||
} | ||
|
||
impl StopWatchView { | ||
pub fn new() -> Self { | ||
Self { | ||
stopwatch: StopWatch::new(), | ||
on_stop: None, | ||
} | ||
} | ||
|
||
/// Sets a callback to be used when `<Enter>` is pressed. | ||
/// | ||
/// The elapsed time will be given to the callback. | ||
/// | ||
/// See also cursive::views::select_view::SelectView::set_on_submit | ||
pub fn set_on_stop<F, R>(&mut self, cb: F) | ||
where | ||
F: 'static + Fn(&mut Cursive, &StopWatch) -> R, | ||
{ | ||
self.on_stop = Some(Rc::new(move |s, t| { | ||
cb(s, t); | ||
})); | ||
} | ||
|
||
pub fn on_stop<F, R>(self, cb: F) -> Self | ||
where | ||
F: 'static + Fn(&mut Cursive, &StopWatch) -> R, | ||
{ | ||
self.with(|s| s.set_on_stop(cb)) | ||
} | ||
|
||
fn stop(&mut self) -> EventResult { | ||
let stopwatch = &mut self.stopwatch; | ||
if !stopwatch.paused { | ||
stopwatch.pause(); | ||
} | ||
let result = if self.on_stop.is_some() { | ||
let cb = self.on_stop.clone().unwrap(); | ||
let stopwatch_data = self.stopwatch.clone(); // TODO: remove clone | ||
EventResult::with_cb(move |s| cb(s, &stopwatch_data)) | ||
} else { | ||
EventResult::Consumed(None) | ||
}; | ||
// reset the stopwatch data, but not other configurations related to the `View` | ||
self.stopwatch = StopWatch::new(); | ||
// return result | ||
result | ||
} | ||
} | ||
impl View for StopWatchView { | ||
fn draw(&self, printer: &Printer) { | ||
printer.print((0, 0), &self.stopwatch.read().pretty()); | ||
} | ||
|
||
fn required_size(&mut self, _constraint: Vec2) -> Vec2 { | ||
Vec2::new(12, 1) // columns, rows (width, height) | ||
} | ||
|
||
fn on_event(&mut self, event: Event) -> EventResult { | ||
match event { | ||
// pause/resume the stopwatch when pressing "Space" | ||
Event::Char(' ') => { | ||
self.stopwatch.pause_or_resume(); | ||
} | ||
// stop (reset) the stopwatch when pressing "Enter" | ||
Event::Key(Key::Enter) => { | ||
return self.stop(); | ||
} | ||
_ => return EventResult::Ignored, | ||
} | ||
EventResult::Consumed(None) | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost done... Except in line 163 where I haven't figured out how not to clone
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeaah... the issue is that the callback will be called later, possibly after removing this view. So we can't depend on any data in the view itself; the callback must be self-supported.
The solution is to either clone the data as you are doing (making it standalone), or wrap it in a
Rc
shared by both the view and the callback.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just came up with another approach: use
mem::replace
to replaceself.stopwatch
with a new, zeroedStopWatch
, while getting the ownership of the 'freshly' produced data, which can then passed to the closure.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use
std::mem::take
which does this automatically for "default" types.