feat(comp/timer): add rest duration

This commit is contained in:
Dmitriy Pleshevskiy 2022-02-12 16:59:07 +03:00
parent 7618fcb8a8
commit 82d75caee1
7 changed files with 74 additions and 26 deletions

View File

@ -1,6 +1,7 @@
use druid::{Selector, WidgetId};
pub const OPEN_NOTIFIER_WINDOW: Selector<WidgetId> = Selector::new("hwt.cmd.win.notifier.open");
pub const OPEN_NOTIFIER_WINDOW: Selector<(WidgetId, f64)> =
Selector::new("hwt.cmd.win.notifier.open");
pub const OPEN_IDLE_WINDOW: Selector<(WidgetId, f64)> = Selector::new("hwt.cmd.win.idle.open");
pub const DEINIT_COMP: Selector = Selector::new("hwt.cmd.comp.deinit");

View File

@ -1,6 +1,5 @@
use crate::cmd;
use crate::comp;
use crate::env;
use crate::state;
use druid::widget::{Flex, Label};
use druid::{Key, Widget, WidgetExt};
@ -9,17 +8,21 @@ pub fn build(
name: &str,
duration_env_key: Key<f64>,
postpone_duration_env_key: Key<f64>,
rest_duration_env_key: Key<f64>,
) -> impl Widget<state::BreakTimer> {
let name_label = Label::new(name);
Flex::row().with_child(name_label).with_child(
comp::timer::build()
.controller(
comp::timer::TimerController::new(|ctx| {
comp::timer::TimerController::new(|ctx, rest_duration_secs| {
ctx.submit_command(cmd::PAUSE_ALL_TIMER_COMP);
ctx.submit_command(cmd::OPEN_NOTIFIER_WINDOW.with(ctx.widget_id()))
ctx.submit_command(
cmd::OPEN_NOTIFIER_WINDOW.with((ctx.widget_id(), rest_duration_secs)),
)
})
.with_duration_env(duration_env_key.clone())
.with_postpone_duration_env(postpone_duration_env_key.clone()),
.with_postpone_duration_env(postpone_duration_env_key.clone())
.with_rest_duration_env(rest_duration_env_key.clone()),
)
.lens(state::BreakTimer::work_timer),
)

View File

@ -18,18 +18,19 @@ pub struct TimerController {
env_duration: Key<f64>,
env_init_duration: Option<Key<f64>>,
env_postpone_duration: Option<Key<f64>>,
env_rest_duration: Option<Key<f64>>,
start_time: Instant,
pause_time: Option<Instant>,
render_timer_id: TimerToken,
finish_timer_id: TimerToken,
finish_handler: Option<Box<dyn Fn(&mut EventCtx)>>,
finish_handler: Option<Box<dyn Fn(&mut EventCtx, f64)>>,
postpone_times: u32,
}
impl TimerController {
pub fn new<Handler>(finish_handler: Handler) -> Self
where
Handler: Fn(&mut EventCtx) + 'static,
Handler: Fn(&mut EventCtx, f64) + 'static,
{
Self {
finish_handler: Some(Box::new(finish_handler)),
@ -44,6 +45,7 @@ impl Default for TimerController {
env_duration: env::TIMER_DURATION,
env_init_duration: None,
env_postpone_duration: None,
env_rest_duration: None,
start_time: Instant::now(),
pause_time: None,
render_timer_id: TimerToken::INVALID,
@ -69,6 +71,11 @@ impl TimerController {
self.env_postpone_duration = Some(key);
self
}
pub fn with_rest_duration_env(mut self, key: Key<f64>) -> Self {
self.env_rest_duration = Some(key);
self
}
}
impl<W> Controller<state::Timer, W> for TimerController
@ -108,7 +115,7 @@ where
}
Event::Timer(id) if *id == self.finish_timer_id => {
if let Some(finish_handler) = &self.finish_handler {
finish_handler(ctx);
finish_handler(ctx, self.full_rest_duration(env).as_secs_f64());
}
}
Event::Command(cmd) if cmd.is(cmd::PAUSE_ALL_TIMER_COMP) => {
@ -154,6 +161,31 @@ where
}
impl TimerController {
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.env_postpone_duration, &self.env_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.env_rest_duration {
None => Duration::ZERO,
Some(key) => Duration::from_secs_f64(env.get(key)),
}
}
fn full_duration(&self, env: &Env) -> Duration {
self.duration(env) + self.postpone_times * self.postpone_duration(env)
}

View File

@ -17,13 +17,13 @@ impl AppDelegate<state::App> for Delegate {
) -> Handled {
match cmd {
_ if cmd.is(cmd::OPEN_NOTIFIER_WINDOW) => {
let widget_id = *cmd.get_unchecked(cmd::OPEN_NOTIFIER_WINDOW);
ctx.new_window(win::notifier::create(widget_id));
let (widget_id, rest_duration_secs) = *cmd.get_unchecked(cmd::OPEN_NOTIFIER_WINDOW);
ctx.new_window(win::notifier::create(widget_id, rest_duration_secs));
Handled::Yes
}
_ if cmd.is(cmd::OPEN_IDLE_WINDOW) => {
let (widget_id, rest_duration) = *cmd.get_unchecked(cmd::OPEN_IDLE_WINDOW);
ctx.new_window(win::rest::create(widget_id, rest_duration));
let (widget_id, rest_duration_secs) = *cmd.get_unchecked(cmd::OPEN_IDLE_WINDOW);
ctx.new_window(win::rest::create(widget_id, rest_duration_secs));
Handled::Yes
}
_ => Handled::No,

View File

@ -5,7 +5,7 @@ use crate::state;
use druid::widget::{Button, Flex};
use druid::{MenuDesc, Target, Widget, WidgetExt, WidgetId, WindowDesc};
pub fn create(parent_widget_id: WidgetId) -> WindowDesc<state::App> {
pub fn create(parent_widget_id: WidgetId, rest_duration_secs: f64) -> WindowDesc<state::App> {
let win_width = 200.0;
let win_height = 100.0;
@ -13,7 +13,7 @@ pub fn create(parent_widget_id: WidgetId) -> WindowDesc<state::App> {
let x = (rect.width() - win_width) / 2.0;
let y = 0.0;
return WindowDesc::new(move || build(parent_widget_id))
return WindowDesc::new(move || build(parent_widget_id, rest_duration_secs))
.show_titlebar(false)
.menu(MenuDesc::empty())
.set_position((x, y))
@ -21,22 +21,27 @@ pub fn create(parent_widget_id: WidgetId) -> WindowDesc<state::App> {
.window_size((win_width, win_height));
}
fn build(parent_widget_id: WidgetId) -> impl Widget<state::App> {
fn build(parent_widget_id: WidgetId, rest_duration_secs: f64) -> impl Widget<state::App> {
Flex::column()
.with_child(build_notifier_timer(parent_widget_id).lens(state::App::notifier))
.with_child(
build_notifier_timer(parent_widget_id, rest_duration_secs).lens(state::App::notifier),
)
.with_default_spacer()
.with_child(build_postpone_btn(parent_widget_id))
.padding((8.0, 8.0))
}
fn build_notifier_timer(parent_widget_id: WidgetId) -> impl Widget<state::Timer> {
fn build_notifier_timer(
parent_widget_id: WidgetId,
rest_duration_secs: f64,
) -> impl Widget<state::Timer> {
comp::timer::build()
.controller(
comp::timer::TimerController::new(move |ctx| {
comp::timer::TimerController::new(move |ctx, _| {
ctx.submit_command(cmd::DEINIT_COMP.to(Target::Widget(ctx.widget_id())));
ctx.submit_command(
cmd::OPEN_IDLE_WINDOW
.with((parent_widget_id, 30.0))
.with((parent_widget_id, rest_duration_secs))
.to(Target::Global),
);
ctx.submit_command(druid::commands::CLOSE_WINDOW);

View File

@ -5,7 +5,7 @@ use crate::state;
use druid::widget::Flex;
use druid::{MenuDesc, Target, Widget, WidgetExt, WidgetId, WindowDesc};
pub fn create(parent_widget_id: WidgetId, rest_duration: f64) -> WindowDesc<state::App> {
pub fn create(parent_widget_id: WidgetId, rest_duration_secs: f64) -> WindowDesc<state::App> {
let win_width = 450.0;
let win_height = 200.0;
@ -13,7 +13,7 @@ pub fn create(parent_widget_id: WidgetId, rest_duration: f64) -> WindowDesc<stat
let x = (rect.width() - win_width) / 2.0;
let y = (rect.height() - win_height) / 2.0;
return WindowDesc::new(move || build(parent_widget_id, rest_duration))
return WindowDesc::new(move || build(parent_widget_id, rest_duration_secs))
.show_titlebar(false)
.menu(MenuDesc::empty())
.set_position((x, y))
@ -21,16 +21,21 @@ pub fn create(parent_widget_id: WidgetId, rest_duration: f64) -> WindowDesc<stat
.window_size((win_width, win_height));
}
fn build(parent_widget_id: WidgetId, rest_duration: f64) -> impl Widget<state::App> {
fn build(parent_widget_id: WidgetId, rest_duration_secs: f64) -> impl Widget<state::App> {
Flex::column()
.with_child(build_idle_timer(parent_widget_id, rest_duration).lens(state::App::notifier))
.with_child(
build_idle_timer(parent_widget_id, rest_duration_secs).lens(state::App::notifier),
)
.padding((8.0, 8.0))
}
fn build_idle_timer(parent_widget_id: WidgetId, rest_duration: f64) -> impl Widget<state::Timer> {
fn build_idle_timer(
parent_widget_id: WidgetId,
rest_duration_secs: f64,
) -> impl Widget<state::Timer> {
comp::timer::build()
.controller(
comp::timer::TimerController::new(move |ctx| {
comp::timer::TimerController::new(move |ctx, _| {
ctx.submit_command(cmd::DEINIT_COMP.to(Target::Widget(ctx.widget_id())));
ctx.submit_command(cmd::UNPAUSE_ALL_TIMER_COMP.with(false).to(Target::Global));
ctx.submit_command(cmd::RESTART_TIMER_COMP.to(Target::Widget(parent_widget_id)));
@ -39,5 +44,5 @@ fn build_idle_timer(parent_widget_id: WidgetId, rest_duration: f64) -> impl Widg
.with_init_duration_env(env::BREAK_NOTIFIER_TIMER_DURATION),
)
.controller(comp::deinit::DeinitController::default())
.env_scope(move |env, _| env.set(env::TIMER_DURATION, rest_duration))
.env_scope(move |env, _| env.set(env::TIMER_DURATION, rest_duration_secs))
}

View File

@ -23,6 +23,7 @@ fn build() -> impl Widget<state::App> {
"Micro",
env::MICRO_BREAK_TIMER_DURATION,
env::MICRO_BREAK_TIMER_POSTPONE_DURATION,
env::MICRO_BREAK_TIMER_REST_DURATION,
)
.lens(state::App::micro_break),
)
@ -32,6 +33,7 @@ fn build() -> impl Widget<state::App> {
"Rest",
env::REST_BREAK_TIMER_DURATION,
env::REST_BREAK_TIMER_POSTPONE_DURATION,
env::REST_BREAK_TIMER_REST_DURATION,
)
.lens(state::App::rest_break),
)