feat(sound): add sounds for each timer
chore(deps): add rodio 0.15 feat(sound): add free resources Closes #1
This commit is contained in:
parent
d9c9733bcf
commit
94ea30a142
|
@ -19,3 +19,4 @@ path = "src/main.rs"
|
||||||
druid = "0.7.0"
|
druid = "0.7.0"
|
||||||
|
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
|
rodio = "0.15.0"
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,25 +1,30 @@
|
||||||
use crate::cmd;
|
use crate::cmd;
|
||||||
use crate::comp;
|
use crate::comp;
|
||||||
|
use crate::sound;
|
||||||
use crate::state;
|
use crate::state;
|
||||||
use druid::widget::Label;
|
use druid::widget::Label;
|
||||||
use druid::{Key, Widget, WidgetExt};
|
use druid::{Key, Widget, WidgetExt};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub fn build(
|
pub fn build(
|
||||||
name: &str,
|
name: &str,
|
||||||
duration_env_key: Key<f64>,
|
duration_env_key: Key<f64>,
|
||||||
postpone_duration_env_key: Key<f64>,
|
postpone_duration_env_key: Key<f64>,
|
||||||
rest_duration_env_key: Key<f64>,
|
rest_duration_env_key: Key<f64>,
|
||||||
|
sound_sender: Rc<sound::Sender>,
|
||||||
) -> impl Widget<state::BreakTimer> {
|
) -> impl Widget<state::BreakTimer> {
|
||||||
comp::flex::row_sta_sta()
|
comp::flex::row_sta_sta()
|
||||||
.with_child(Label::new(name).align_right().fix_width(50.0))
|
.with_child(Label::new(name).align_right().fix_width(50.0))
|
||||||
.with_child(
|
.with_child(
|
||||||
comp::timer::build()
|
comp::timer::build()
|
||||||
.controller(
|
.controller(
|
||||||
comp::timer::TimerController::new(|ctx, rest_duration_secs| {
|
comp::timer::TimerController::new(move |ctx, rest_duration_secs| {
|
||||||
|
sound_sender.send(sound::Type::EndBreakTimer).ok();
|
||||||
|
|
||||||
ctx.submit_command(cmd::PAUSE_ALL_TIMER_COMP);
|
ctx.submit_command(cmd::PAUSE_ALL_TIMER_COMP);
|
||||||
ctx.submit_command(
|
ctx.submit_command(
|
||||||
cmd::OPEN_NOTIFIER_WINDOW.with((ctx.widget_id(), rest_duration_secs)),
|
cmd::OPEN_NOTIFIER_WINDOW.with((ctx.widget_id(), rest_duration_secs)),
|
||||||
)
|
);
|
||||||
})
|
})
|
||||||
.with_duration(duration_env_key.clone())
|
.with_duration(duration_env_key.clone())
|
||||||
.with_postpone_duration(postpone_duration_env_key.clone())
|
.with_postpone_duration(postpone_duration_env_key.clone())
|
||||||
|
|
|
@ -12,18 +12,26 @@ impl AppDelegate<state::App> for Delegate {
|
||||||
ctx: &mut DelegateCtx,
|
ctx: &mut DelegateCtx,
|
||||||
_target: Target,
|
_target: Target,
|
||||||
cmd: &Command,
|
cmd: &Command,
|
||||||
_data: &mut state::App,
|
data: &mut state::App,
|
||||||
_env: &Env,
|
_env: &Env,
|
||||||
) -> Handled {
|
) -> Handled {
|
||||||
match cmd {
|
match cmd {
|
||||||
_ if cmd.is(cmd::OPEN_NOTIFIER_WINDOW) => {
|
_ if cmd.is(cmd::OPEN_NOTIFIER_WINDOW) => {
|
||||||
let (widget_id, rest_duration_secs) = *cmd.get_unchecked(cmd::OPEN_NOTIFIER_WINDOW);
|
let (widget_id, rest_duration_secs) = *cmd.get_unchecked(cmd::OPEN_NOTIFIER_WINDOW);
|
||||||
ctx.new_window(win::notifier::create(widget_id, rest_duration_secs));
|
ctx.new_window(win::notifier::create(
|
||||||
|
widget_id,
|
||||||
|
rest_duration_secs,
|
||||||
|
data.sound_sender.clone(),
|
||||||
|
));
|
||||||
Handled::Yes
|
Handled::Yes
|
||||||
}
|
}
|
||||||
_ if cmd.is(cmd::OPEN_IDLE_WINDOW) => {
|
_ if cmd.is(cmd::OPEN_IDLE_WINDOW) => {
|
||||||
let (widget_id, rest_duration_secs) = *cmd.get_unchecked(cmd::OPEN_IDLE_WINDOW);
|
let (widget_id, rest_duration_secs) = *cmd.get_unchecked(cmd::OPEN_IDLE_WINDOW);
|
||||||
ctx.new_window(win::rest::create(widget_id, rest_duration_secs));
|
ctx.new_window(win::rest::create(
|
||||||
|
widget_id,
|
||||||
|
rest_duration_secs,
|
||||||
|
data.sound_sender.clone(),
|
||||||
|
));
|
||||||
Handled::Yes
|
Handled::Yes
|
||||||
}
|
}
|
||||||
_ => Handled::No,
|
_ => Handled::No,
|
||||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -2,23 +2,38 @@ mod cmd;
|
||||||
mod comp;
|
mod comp;
|
||||||
mod delegate;
|
mod delegate;
|
||||||
mod env;
|
mod env;
|
||||||
|
mod sound;
|
||||||
mod state;
|
mod state;
|
||||||
mod win;
|
mod win;
|
||||||
|
|
||||||
use delegate::Delegate;
|
use delegate::Delegate;
|
||||||
use druid::AppLauncher;
|
use druid::AppLauncher;
|
||||||
|
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let (tx, rx) = channel::<sound::Type>();
|
||||||
|
|
||||||
|
let boombox = thread::spawn(move || loop {
|
||||||
|
rx.recv()
|
||||||
|
.map_err(From::from)
|
||||||
|
.and_then(|sound_type| sound::try_play(sound_type.into()))
|
||||||
|
.ok();
|
||||||
|
});
|
||||||
|
|
||||||
let initial_state = state::App {
|
let initial_state = state::App {
|
||||||
paused: false,
|
paused: false,
|
||||||
micro_break: state::BreakTimer::new(),
|
micro_break: state::BreakTimer::new(),
|
||||||
rest_break: state::BreakTimer::new(),
|
rest_break: state::BreakTimer::new(),
|
||||||
notifier: state::Timer::new(),
|
notifier: state::Timer::new(),
|
||||||
|
sound_sender: std::rc::Rc::new(tx.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
AppLauncher::with_window(win::status::create())
|
AppLauncher::with_window(win::status::create(initial_state.sound_sender.clone()))
|
||||||
.delegate(Delegate)
|
.delegate(Delegate)
|
||||||
.configure_env(env::configure)
|
.configure_env(env::configure)
|
||||||
.launch(initial_state)
|
.launch(initial_state)
|
||||||
.expect("Failed to launch application");
|
.expect("Failed to launch application");
|
||||||
|
boombox.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
pub type Sender = std::sync::mpsc::Sender<Type>;
|
||||||
|
|
||||||
|
pub enum Type {
|
||||||
|
EndBreakTimer,
|
||||||
|
EndNotifier,
|
||||||
|
EndRest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Type> for &'static [u8] {
|
||||||
|
fn from(sound_type: Type) -> Self {
|
||||||
|
match sound_type {
|
||||||
|
Type::EndBreakTimer => ALERT_CLEAR_ANNOUNCE_TONES,
|
||||||
|
Type::EndRest => ALERT_BELLS_ECHO,
|
||||||
|
Type::EndNotifier => ALERT_QUICK_CHIME,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALERT_BELLS_ECHO: &[u8] =
|
||||||
|
include_bytes!("../resources/sounds/mixkit-alert-bells-echo-765.wav");
|
||||||
|
const ALERT_QUICK_CHIME: &[u8] =
|
||||||
|
include_bytes!("../resources/sounds/mixkit-alert-quick-chime-766.wav");
|
||||||
|
const ALERT_CLEAR_ANNOUNCE_TONES: &[u8] =
|
||||||
|
include_bytes!("../resources/sounds/mixkit-clear-announce-tones-2861.wav");
|
||||||
|
|
||||||
|
pub fn try_play(bytes: &'static [u8]) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let (_stream, stream_handle) = rodio::OutputStream::try_default()?;
|
||||||
|
let sink = stream_handle.play_once(std::io::Cursor::new(bytes))?;
|
||||||
|
sink.sleep_until_end();
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
|
use crate::sound;
|
||||||
use druid::{Data, Lens};
|
use druid::{Data, Lens};
|
||||||
use std::ops::Div;
|
use std::ops::Div;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Clone, Data, Lens)]
|
#[derive(Clone, Data, Lens)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
pub sound_sender: Rc<sound::Sender>,
|
||||||
pub paused: bool,
|
pub paused: bool,
|
||||||
pub micro_break: BreakTimer,
|
pub micro_break: BreakTimer,
|
||||||
pub rest_break: BreakTimer,
|
pub rest_break: BreakTimer,
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
use crate::cmd;
|
use crate::cmd;
|
||||||
use crate::comp;
|
use crate::comp;
|
||||||
use crate::env;
|
use crate::env;
|
||||||
|
use crate::sound;
|
||||||
use crate::state;
|
use crate::state;
|
||||||
use druid::widget::Button;
|
use druid::widget::Button;
|
||||||
use druid::{MenuDesc, Target, Widget, WidgetExt, WidgetId, WindowDesc};
|
use druid::{MenuDesc, Target, Widget, WidgetExt, WidgetId, WindowDesc};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub fn create(parent_widget_id: WidgetId, rest_duration_secs: f64) -> WindowDesc<state::App> {
|
pub fn create(
|
||||||
|
parent_widget_id: WidgetId,
|
||||||
|
rest_duration_secs: f64,
|
||||||
|
sound_sender: Rc<sound::Sender>,
|
||||||
|
) -> WindowDesc<state::App> {
|
||||||
let win_width = 200.0;
|
let win_width = 200.0;
|
||||||
let win_height = 100.0;
|
let win_height = 100.0;
|
||||||
|
|
||||||
|
@ -13,7 +19,7 @@ pub fn create(parent_widget_id: WidgetId, rest_duration_secs: f64) -> WindowDesc
|
||||||
let x = (rect.width() - win_width) / 2.0;
|
let x = (rect.width() - win_width) / 2.0;
|
||||||
let y = 0.0;
|
let y = 0.0;
|
||||||
|
|
||||||
return WindowDesc::new(move || build(parent_widget_id, rest_duration_secs))
|
return WindowDesc::new(move || build(parent_widget_id, rest_duration_secs, sound_sender))
|
||||||
.show_titlebar(false)
|
.show_titlebar(false)
|
||||||
.menu(MenuDesc::empty())
|
.menu(MenuDesc::empty())
|
||||||
.set_position((x, y))
|
.set_position((x, y))
|
||||||
|
@ -21,10 +27,15 @@ pub fn create(parent_widget_id: WidgetId, rest_duration_secs: f64) -> WindowDesc
|
||||||
.window_size((win_width, win_height));
|
.window_size((win_width, win_height));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(parent_widget_id: WidgetId, rest_duration_secs: f64) -> impl Widget<state::App> {
|
fn build(
|
||||||
|
parent_widget_id: WidgetId,
|
||||||
|
rest_duration_secs: f64,
|
||||||
|
sound_sender: Rc<sound::Sender>,
|
||||||
|
) -> impl Widget<state::App> {
|
||||||
comp::flex::col_cen_cen()
|
comp::flex::col_cen_cen()
|
||||||
.with_child(
|
.with_child(
|
||||||
build_notifier_timer(parent_widget_id, rest_duration_secs).lens(state::App::notifier),
|
build_notifier_timer(parent_widget_id, rest_duration_secs, sound_sender)
|
||||||
|
.lens(state::App::notifier),
|
||||||
)
|
)
|
||||||
.with_default_spacer()
|
.with_default_spacer()
|
||||||
.with_child(build_postpone_btn(parent_widget_id))
|
.with_child(build_postpone_btn(parent_widget_id))
|
||||||
|
@ -34,10 +45,13 @@ fn build(parent_widget_id: WidgetId, rest_duration_secs: f64) -> impl Widget<sta
|
||||||
fn build_notifier_timer(
|
fn build_notifier_timer(
|
||||||
parent_widget_id: WidgetId,
|
parent_widget_id: WidgetId,
|
||||||
rest_duration_secs: f64,
|
rest_duration_secs: f64,
|
||||||
|
sound_sender: Rc<sound::Sender>,
|
||||||
) -> impl Widget<state::Timer> {
|
) -> impl Widget<state::Timer> {
|
||||||
comp::timer::build()
|
comp::timer::build()
|
||||||
.controller(
|
.controller(
|
||||||
comp::timer::TimerController::new(move |ctx, _| {
|
comp::timer::TimerController::new(move |ctx, _| {
|
||||||
|
sound_sender.send(sound::Type::EndNotifier).ok();
|
||||||
|
|
||||||
ctx.submit_command(cmd::DEINIT_COMP.to(Target::Widget(ctx.widget_id())));
|
ctx.submit_command(cmd::DEINIT_COMP.to(Target::Widget(ctx.widget_id())));
|
||||||
ctx.submit_command(
|
ctx.submit_command(
|
||||||
cmd::OPEN_IDLE_WINDOW
|
cmd::OPEN_IDLE_WINDOW
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
use crate::cmd;
|
use crate::cmd;
|
||||||
use crate::comp;
|
use crate::comp;
|
||||||
use crate::env;
|
use crate::env;
|
||||||
|
use crate::sound;
|
||||||
use crate::state;
|
use crate::state;
|
||||||
use druid::widget::Button;
|
use druid::widget::Button;
|
||||||
use druid::{MenuDesc, Target, Widget, WidgetExt, WidgetId, WindowDesc};
|
use druid::{MenuDesc, Target, Widget, WidgetExt, WidgetId, WindowDesc};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub fn create(parent_widget_id: WidgetId, rest_duration_secs: f64) -> WindowDesc<state::App> {
|
pub fn create(
|
||||||
|
parent_widget_id: WidgetId,
|
||||||
|
rest_duration_secs: f64,
|
||||||
|
sound_sender: Rc<sound::Sender>,
|
||||||
|
) -> WindowDesc<state::App> {
|
||||||
let win_width = 450.0;
|
let win_width = 450.0;
|
||||||
let win_height = 200.0;
|
let win_height = 200.0;
|
||||||
|
|
||||||
|
@ -13,7 +19,7 @@ pub fn create(parent_widget_id: WidgetId, rest_duration_secs: f64) -> WindowDesc
|
||||||
let x = (rect.width() - win_width) / 2.0;
|
let x = (rect.width() - win_width) / 2.0;
|
||||||
let y = (rect.height() - win_height) / 2.0;
|
let y = (rect.height() - win_height) / 2.0;
|
||||||
|
|
||||||
return WindowDesc::new(move || build(parent_widget_id, rest_duration_secs))
|
return WindowDesc::new(move || build(parent_widget_id, rest_duration_secs, sound_sender))
|
||||||
.show_titlebar(false)
|
.show_titlebar(false)
|
||||||
.menu(MenuDesc::empty())
|
.menu(MenuDesc::empty())
|
||||||
.set_position((x, y))
|
.set_position((x, y))
|
||||||
|
@ -21,12 +27,16 @@ pub fn create(parent_widget_id: WidgetId, rest_duration_secs: f64) -> WindowDesc
|
||||||
.window_size((win_width, win_height));
|
.window_size((win_width, win_height));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(parent_widget_id: WidgetId, rest_duration_secs: f64) -> impl Widget<state::App> {
|
fn build(
|
||||||
|
parent_widget_id: WidgetId,
|
||||||
|
rest_duration_secs: f64,
|
||||||
|
sound_sender: Rc<sound::Sender>,
|
||||||
|
) -> impl Widget<state::App> {
|
||||||
comp::flex::col_cen_cen()
|
comp::flex::col_cen_cen()
|
||||||
.with_child(
|
.with_child(
|
||||||
comp::flex::col_sta_end()
|
comp::flex::col_sta_end()
|
||||||
.with_child(
|
.with_child(
|
||||||
build_idle_timer(parent_widget_id, rest_duration_secs)
|
build_idle_timer(parent_widget_id, rest_duration_secs, sound_sender)
|
||||||
.lens(state::App::notifier),
|
.lens(state::App::notifier),
|
||||||
)
|
)
|
||||||
.with_default_spacer()
|
.with_default_spacer()
|
||||||
|
@ -38,10 +48,13 @@ fn build(parent_widget_id: WidgetId, rest_duration_secs: f64) -> impl Widget<sta
|
||||||
fn build_idle_timer(
|
fn build_idle_timer(
|
||||||
parent_widget_id: WidgetId,
|
parent_widget_id: WidgetId,
|
||||||
rest_duration_secs: f64,
|
rest_duration_secs: f64,
|
||||||
|
sound_sender: Rc<sound::Sender>,
|
||||||
) -> impl Widget<state::Timer> {
|
) -> impl Widget<state::Timer> {
|
||||||
comp::timer::build()
|
comp::timer::build()
|
||||||
.controller(
|
.controller(
|
||||||
comp::timer::TimerController::new(move |ctx, _| {
|
comp::timer::TimerController::new(move |ctx, _| {
|
||||||
|
sound_sender.send(sound::Type::EndRest).ok();
|
||||||
|
|
||||||
ctx.submit_command(cmd::DEINIT_COMP.to(Target::Widget(ctx.widget_id())));
|
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::UNPAUSE_ALL_TIMER_COMP.with(false).to(Target::Global));
|
||||||
ctx.submit_command(cmd::RESTART_TIMER_COMP.to(Target::Widget(parent_widget_id)));
|
ctx.submit_command(cmd::RESTART_TIMER_COMP.to(Target::Widget(parent_widget_id)));
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
use crate::cmd;
|
use crate::cmd;
|
||||||
use crate::comp;
|
use crate::comp;
|
||||||
use crate::env;
|
use crate::env;
|
||||||
|
use crate::sound;
|
||||||
use crate::state;
|
use crate::state;
|
||||||
use druid::widget::{Button, Either};
|
use druid::widget::{Button, Either};
|
||||||
use druid::{LocalizedString, MenuDesc, Widget, WidgetExt, WindowDesc};
|
use druid::{LocalizedString, MenuDesc, Widget, WidgetExt, WindowDesc};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub fn create() -> WindowDesc<state::App> {
|
pub fn create(sender: Rc<sound::Sender>) -> WindowDesc<state::App> {
|
||||||
let win_width = 220.0;
|
let win_width = 220.0;
|
||||||
let win_height = 100.0;
|
let win_height = 100.0;
|
||||||
return WindowDesc::new(build)
|
WindowDesc::new(|| build(sender))
|
||||||
.title(LocalizedString::new("HWT Status"))
|
.title(LocalizedString::new("HWT Status"))
|
||||||
.menu(MenuDesc::empty())
|
.menu(MenuDesc::empty())
|
||||||
.with_min_size((win_width, win_height))
|
.with_min_size((win_width, win_height))
|
||||||
.window_size((win_width, win_height));
|
.window_size((win_width, win_height))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build() -> impl Widget<state::App> {
|
fn build(sender: Rc<sound::Sender>) -> impl Widget<state::App> {
|
||||||
comp::flex::col_sta_sta()
|
comp::flex::col_sta_sta()
|
||||||
.with_child(build_timers())
|
.with_child(build_timers(sender))
|
||||||
.with_default_spacer()
|
.with_default_spacer()
|
||||||
.with_child(build_pause_btn())
|
.with_child(build_pause_btn())
|
||||||
.padding((8.0, 8.0))
|
.padding((8.0, 8.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_timers() -> impl Widget<state::App> {
|
fn build_timers(sender: Rc<sound::Sender>) -> impl Widget<state::App> {
|
||||||
comp::flex::col_sta_sta()
|
comp::flex::col_sta_sta()
|
||||||
.with_child(
|
.with_child(
|
||||||
comp::break_timer::build(
|
comp::break_timer::build(
|
||||||
|
@ -31,6 +33,7 @@ fn build_timers() -> impl Widget<state::App> {
|
||||||
env::MICRO_BREAK_TIMER_DURATION,
|
env::MICRO_BREAK_TIMER_DURATION,
|
||||||
env::MICRO_BREAK_TIMER_POSTPONE_DURATION,
|
env::MICRO_BREAK_TIMER_POSTPONE_DURATION,
|
||||||
env::MICRO_BREAK_TIMER_REST_DURATION,
|
env::MICRO_BREAK_TIMER_REST_DURATION,
|
||||||
|
sender.clone(),
|
||||||
)
|
)
|
||||||
.lens(state::App::micro_break),
|
.lens(state::App::micro_break),
|
||||||
)
|
)
|
||||||
|
@ -41,6 +44,7 @@ fn build_timers() -> impl Widget<state::App> {
|
||||||
env::REST_BREAK_TIMER_DURATION,
|
env::REST_BREAK_TIMER_DURATION,
|
||||||
env::REST_BREAK_TIMER_POSTPONE_DURATION,
|
env::REST_BREAK_TIMER_POSTPONE_DURATION,
|
||||||
env::REST_BREAK_TIMER_REST_DURATION,
|
env::REST_BREAK_TIMER_REST_DURATION,
|
||||||
|
sender.clone(),
|
||||||
)
|
)
|
||||||
.lens(state::App::rest_break),
|
.lens(state::App::rest_break),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue