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"
|
||||
|
||||
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::comp;
|
||||
use crate::sound;
|
||||
use crate::state;
|
||||
use druid::widget::Label;
|
||||
use druid::{Key, Widget, WidgetExt};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn build(
|
||||
name: &str,
|
||||
duration_env_key: Key<f64>,
|
||||
postpone_duration_env_key: Key<f64>,
|
||||
rest_duration_env_key: Key<f64>,
|
||||
sound_sender: Rc<sound::Sender>,
|
||||
) -> impl Widget<state::BreakTimer> {
|
||||
comp::flex::row_sta_sta()
|
||||
.with_child(Label::new(name).align_right().fix_width(50.0))
|
||||
.with_child(
|
||||
comp::timer::build()
|
||||
.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::OPEN_NOTIFIER_WINDOW.with((ctx.widget_id(), rest_duration_secs)),
|
||||
)
|
||||
);
|
||||
})
|
||||
.with_duration(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,
|
||||
_target: Target,
|
||||
cmd: &Command,
|
||||
_data: &mut state::App,
|
||||
data: &mut state::App,
|
||||
_env: &Env,
|
||||
) -> Handled {
|
||||
match cmd {
|
||||
_ if cmd.is(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
|
||||
}
|
||||
_ if cmd.is(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::No,
|
||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -2,23 +2,38 @@ mod cmd;
|
|||
mod comp;
|
||||
mod delegate;
|
||||
mod env;
|
||||
mod sound;
|
||||
mod state;
|
||||
mod win;
|
||||
|
||||
use delegate::Delegate;
|
||||
use druid::AppLauncher;
|
||||
|
||||
use std::sync::mpsc::channel;
|
||||
use std::thread;
|
||||
|
||||
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 {
|
||||
paused: false,
|
||||
micro_break: state::BreakTimer::new(),
|
||||
rest_break: state::BreakTimer::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)
|
||||
.configure_env(env::configure)
|
||||
.launch(initial_state)
|
||||
.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 std::ops::Div;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Data, Lens)]
|
||||
pub struct App {
|
||||
pub sound_sender: Rc<sound::Sender>,
|
||||
pub paused: bool,
|
||||
pub micro_break: BreakTimer,
|
||||
pub rest_break: BreakTimer,
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
use crate::cmd;
|
||||
use crate::comp;
|
||||
use crate::env;
|
||||
use crate::sound;
|
||||
use crate::state;
|
||||
use druid::widget::Button;
|
||||
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_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 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)
|
||||
.menu(MenuDesc::empty())
|
||||
.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));
|
||||
}
|
||||
|
||||
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()
|
||||
.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_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(
|
||||
parent_widget_id: WidgetId,
|
||||
rest_duration_secs: f64,
|
||||
sound_sender: Rc<sound::Sender>,
|
||||
) -> impl Widget<state::Timer> {
|
||||
comp::timer::build()
|
||||
.controller(
|
||||
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::OPEN_IDLE_WINDOW
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
use crate::cmd;
|
||||
use crate::comp;
|
||||
use crate::env;
|
||||
use crate::sound;
|
||||
use crate::state;
|
||||
use druid::widget::Button;
|
||||
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_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 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)
|
||||
.menu(MenuDesc::empty())
|
||||
.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));
|
||||
}
|
||||
|
||||
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()
|
||||
.with_child(
|
||||
comp::flex::col_sta_end()
|
||||
.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),
|
||||
)
|
||||
.with_default_spacer()
|
||||
|
@ -38,10 +48,13 @@ fn build(parent_widget_id: WidgetId, rest_duration_secs: f64) -> impl Widget<sta
|
|||
fn build_idle_timer(
|
||||
parent_widget_id: WidgetId,
|
||||
rest_duration_secs: f64,
|
||||
sound_sender: Rc<sound::Sender>,
|
||||
) -> impl Widget<state::Timer> {
|
||||
comp::timer::build()
|
||||
.controller(
|
||||
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::UNPAUSE_ALL_TIMER_COMP.with(false).to(Target::Global));
|
||||
ctx.submit_command(cmd::RESTART_TIMER_COMP.to(Target::Widget(parent_widget_id)));
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
use crate::cmd;
|
||||
use crate::comp;
|
||||
use crate::env;
|
||||
use crate::sound;
|
||||
use crate::state;
|
||||
use druid::widget::{Button, Either};
|
||||
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_height = 100.0;
|
||||
return WindowDesc::new(build)
|
||||
WindowDesc::new(|| build(sender))
|
||||
.title(LocalizedString::new("HWT Status"))
|
||||
.menu(MenuDesc::empty())
|
||||
.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()
|
||||
.with_child(build_timers())
|
||||
.with_child(build_timers(sender))
|
||||
.with_default_spacer()
|
||||
.with_child(build_pause_btn())
|
||||
.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()
|
||||
.with_child(
|
||||
comp::break_timer::build(
|
||||
|
@ -31,6 +33,7 @@ fn build_timers() -> impl Widget<state::App> {
|
|||
env::MICRO_BREAK_TIMER_DURATION,
|
||||
env::MICRO_BREAK_TIMER_POSTPONE_DURATION,
|
||||
env::MICRO_BREAK_TIMER_REST_DURATION,
|
||||
sender.clone(),
|
||||
)
|
||||
.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_POSTPONE_DURATION,
|
||||
env::REST_BREAK_TIMER_REST_DURATION,
|
||||
sender.clone(),
|
||||
)
|
||||
.lens(state::App::rest_break),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue