226 lines
7.8 KiB
Rust
226 lines
7.8 KiB
Rust
use crate::cmd;
|
|
use crate::comp;
|
|
use crate::env;
|
|
use crate::state;
|
|
use druid::widget::{Label, ProgressBar};
|
|
use druid::{Env, Event, EventCtx, KeyOrValue, TimerToken, Widget, WidgetExt};
|
|
use std::time::{Duration, Instant};
|
|
|
|
const TIMER_INTERVAL: Duration = Duration::from_millis(50);
|
|
|
|
pub fn build() -> impl Widget<state::Timer> {
|
|
let time_label = Label::dynamic(|data: &String, _: &Env| data.clone()).lens(state::Timer::time);
|
|
let progress_bar = ProgressBar::new().lens(state::Timer::progress);
|
|
|
|
comp::flex::row_sta_sta()
|
|
.with_child(time_label.align_right().fix_width(55.0))
|
|
.with_child(progress_bar)
|
|
}
|
|
|
|
type FinishHandler = Box<dyn Fn(&mut EventCtx, &Env, f64)>;
|
|
|
|
pub struct Controller {
|
|
duration: KeyOrValue<f64>,
|
|
init_duration: Option<KeyOrValue<f64>>,
|
|
postpone_duration: Option<KeyOrValue<f64>>,
|
|
rest_duration: Option<KeyOrValue<f64>>,
|
|
start_time: Instant,
|
|
pause_time: Option<Instant>,
|
|
render_timer_id: TimerToken,
|
|
finish_timer_id: TimerToken,
|
|
finish_handler: Option<FinishHandler>,
|
|
postpone_times: u32,
|
|
}
|
|
|
|
impl Controller {
|
|
pub fn new<Handler>(finish_handler: Handler) -> Self
|
|
where
|
|
Handler: Fn(&mut EventCtx, &Env, f64) + 'static,
|
|
{
|
|
Controller {
|
|
finish_handler: Some(Box::new(finish_handler)),
|
|
..Controller::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Controller {
|
|
fn default() -> Self {
|
|
Controller {
|
|
duration: env::TIMER_DURATION.into(),
|
|
init_duration: None,
|
|
postpone_duration: None,
|
|
rest_duration: None,
|
|
start_time: Instant::now(),
|
|
pause_time: None,
|
|
render_timer_id: TimerToken::INVALID,
|
|
finish_timer_id: TimerToken::INVALID,
|
|
finish_handler: None,
|
|
postpone_times: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Controller {
|
|
pub fn with_duration(mut self, secs: impl Into<KeyOrValue<f64>>) -> Self {
|
|
self.duration = secs.into();
|
|
self
|
|
}
|
|
|
|
pub fn with_init_duration(mut self, secs: impl Into<KeyOrValue<f64>>) -> Self {
|
|
self.init_duration = Some(secs.into());
|
|
self
|
|
}
|
|
|
|
pub fn with_postpone_duration(mut self, secs: impl Into<KeyOrValue<f64>>) -> Self {
|
|
self.postpone_duration = Some(secs.into());
|
|
self
|
|
}
|
|
|
|
pub fn with_rest_duration_env(mut self, secs: impl Into<KeyOrValue<f64>>) -> Self {
|
|
self.rest_duration = Some(secs.into());
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<W> druid::widget::Controller<state::Timer, W> for Controller
|
|
where
|
|
W: Widget<state::Timer>,
|
|
{
|
|
fn event(
|
|
&mut self,
|
|
child: &mut W,
|
|
ctx: &mut EventCtx,
|
|
event: &Event,
|
|
data: &mut state::Timer,
|
|
env: &Env,
|
|
) {
|
|
let duration = self.duration(env);
|
|
let full_duration = self.full_duration(env);
|
|
match event {
|
|
Event::WindowConnected => {
|
|
let shift_start_time = Duration::from_secs_f64(
|
|
self.init_duration
|
|
.as_ref()
|
|
.map(|d| d.resolve(env))
|
|
.unwrap_or_default(),
|
|
);
|
|
|
|
self.start_time = Instant::now() - shift_start_time;
|
|
data.reset(duration);
|
|
if data.paused {
|
|
self.pause_time = Some(self.start_time);
|
|
} else {
|
|
self.render_timer_id = ctx.request_timer(TIMER_INTERVAL);
|
|
self.finish_timer_id = ctx.request_timer(duration - shift_start_time);
|
|
}
|
|
|
|
child.event(ctx, event, data, env);
|
|
}
|
|
Event::Timer(id) if *id == self.render_timer_id => {
|
|
data.update_progress_and_time(self.start_time.elapsed(), full_duration);
|
|
ctx.request_paint();
|
|
|
|
self.render_timer_id = ctx.request_timer(TIMER_INTERVAL);
|
|
}
|
|
Event::Timer(id) if *id == self.finish_timer_id => {
|
|
if let Some(finish_handler) = &self.finish_handler {
|
|
finish_handler(ctx, env, self.full_rest_duration(env).as_secs_f64());
|
|
}
|
|
}
|
|
Event::Command(cmd) if cmd.is(cmd::PAUSE_ALL_TIMER_COMP) => {
|
|
data.paused = true;
|
|
self.pause_time = Some(Instant::now());
|
|
self.render_timer_id = TimerToken::INVALID;
|
|
self.finish_timer_id = TimerToken::INVALID;
|
|
}
|
|
Event::Command(cmd) if cmd.is(cmd::UNPAUSE_ALL_TIMER_COMP) => {
|
|
data.paused = false;
|
|
|
|
let skip_pause = cmd.get_unchecked(cmd::UNPAUSE_ALL_TIMER_COMP);
|
|
self.finish_timer_id =
|
|
if let (false, Some(pause_instant)) = (skip_pause, self.pause_time.take()) {
|
|
self.start_time += pause_instant.elapsed();
|
|
ctx.request_timer(duration.saturating_sub(
|
|
Instant::now().saturating_duration_since(self.start_time),
|
|
))
|
|
} else if self.postpone_times > 0 {
|
|
let postpone_duration = self.postpone_duration(env);
|
|
ctx.request_timer(postpone_duration.saturating_sub(
|
|
Instant::now().saturating_duration_since(
|
|
self.start_time + full_duration - postpone_duration,
|
|
),
|
|
))
|
|
} else {
|
|
ctx.request_timer(self.duration(env))
|
|
};
|
|
|
|
self.render_timer_id = ctx.request_timer(TIMER_INTERVAL);
|
|
}
|
|
Event::Command(cmd) if cmd.is(cmd::RESET_TIMER_COMP) => {
|
|
self.postpone_times = 0;
|
|
self.start_time = Instant::now();
|
|
if data.paused {
|
|
self.pause_time = Some(self.start_time);
|
|
} else {
|
|
self.render_timer_id = ctx.request_timer(TIMER_INTERVAL);
|
|
self.finish_timer_id = ctx.request_timer(duration);
|
|
}
|
|
|
|
data.reset(duration);
|
|
ctx.request_paint();
|
|
}
|
|
Event::Command(cmd) if cmd.is(cmd::POSTPONE_TIMER_COMP) => {
|
|
self.postpone_times += 1;
|
|
}
|
|
_ => child.event(ctx, event, data, env),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Controller {
|
|
fn full_rest_duration(&self, env: &Env) -> Duration {
|
|
self.rest_duration(env) + self.postpone_times * self.postpone_rest_duration(env)
|
|
}
|
|
|
|
fn postpone_rest_duration(&self, env: &Env) -> Duration {
|
|
match (&self.postpone_duration, &self.rest_duration) {
|
|
(Some(_), Some(_)) => {
|
|
let duration = self.duration(env).as_secs_f64();
|
|
let rest_duration = self.rest_duration(env).as_secs_f64();
|
|
let postpone_duration = self.postpone_duration(env).as_secs_f64();
|
|
|
|
let rest_per_sec = rest_duration / duration;
|
|
Duration::from_secs_f64(postpone_duration * rest_per_sec)
|
|
}
|
|
_ => Duration::ZERO,
|
|
}
|
|
}
|
|
|
|
fn rest_duration(&self, env: &Env) -> Duration {
|
|
match &self.rest_duration {
|
|
None => Duration::ZERO,
|
|
Some(d) => Duration::from_secs_f64(d.resolve(env)),
|
|
}
|
|
}
|
|
|
|
fn full_duration(&self, env: &Env) -> Duration {
|
|
self.duration(env) + self.postpone_times * self.postpone_duration(env)
|
|
}
|
|
|
|
fn duration(&self, env: &Env) -> Duration {
|
|
Duration::from_secs_f64(self.duration.resolve(env))
|
|
}
|
|
|
|
#[allow(clippy::single_match_else)]
|
|
fn postpone_duration(&self, env: &Env) -> Duration {
|
|
match self.postpone_times {
|
|
0 => Duration::ZERO,
|
|
_ => {
|
|
let d = self.postpone_duration.as_ref().unwrap_or(&self.duration);
|
|
Duration::from_secs_f64(d.resolve(env))
|
|
}
|
|
}
|
|
}
|
|
}
|