try to clean it up a bit
This commit is contained in:
parent
827af8aea3
commit
5c6405d7fb
5 changed files with 260 additions and 224 deletions
33
src/cli.rs
Normal file
33
src/cli.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(author, version, about)]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Hours to count down.
|
||||||
|
#[clap(long, default_value_t = 0)]
|
||||||
|
pub hours: u64,
|
||||||
|
|
||||||
|
/// Minutes to count down.
|
||||||
|
#[clap(long, short, default_value_t = 0)]
|
||||||
|
pub minutes: u64,
|
||||||
|
|
||||||
|
/// Seconds to count down.
|
||||||
|
#[clap(long, short, default_value_t = 0)]
|
||||||
|
pub seconds: u64,
|
||||||
|
|
||||||
|
/// Audio file to play at the end of the countdown.
|
||||||
|
#[clap(long, short)]
|
||||||
|
pub alarm: Option<String>,
|
||||||
|
|
||||||
|
/// Begin countdown immediately.
|
||||||
|
#[clap(long = "immediate", long = "running", short = 'i', short = 'r')]
|
||||||
|
pub running: bool,
|
||||||
|
|
||||||
|
/// Count up from zero, actually.
|
||||||
|
#[clap(long, short = 'u')]
|
||||||
|
pub count_up: bool,
|
||||||
|
|
||||||
|
/// Use the Predator font.
|
||||||
|
#[clap(long, short)]
|
||||||
|
pub predator: bool,
|
||||||
|
}
|
|
@ -1 +1,3 @@
|
||||||
|
pub mod cli;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
|
mod util;
|
||||||
|
|
59
src/main.rs
59
src/main.rs
|
@ -1,40 +1,5 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use egui::Vec2;
|
use egui::Vec2;
|
||||||
use katabastird::timer::{CountDirection, Timer};
|
use katabastird::timer::Timer;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
#[clap(author, version, about)]
|
|
||||||
struct Cli {
|
|
||||||
/// Hours to count down.
|
|
||||||
#[clap(long, default_value_t = 0)]
|
|
||||||
hours: u64,
|
|
||||||
|
|
||||||
/// Minutes to count down.
|
|
||||||
#[clap(long, short, default_value_t = 0)]
|
|
||||||
minutes: u64,
|
|
||||||
|
|
||||||
/// Seconds to count down.
|
|
||||||
#[clap(long, short, default_value_t = 0)]
|
|
||||||
seconds: u64,
|
|
||||||
|
|
||||||
/// Audio file to play at the end of the countdown.
|
|
||||||
#[clap(long, short)]
|
|
||||||
alarm: Option<String>,
|
|
||||||
|
|
||||||
/// Begin countdown immediately.
|
|
||||||
#[clap(long = "immediate", long = "running", short = 'i', short = 'r')]
|
|
||||||
running: bool,
|
|
||||||
|
|
||||||
/// Count up from zero, actually.
|
|
||||||
#[clap(long, short = 'u')]
|
|
||||||
count_up: bool,
|
|
||||||
|
|
||||||
/// Use the Predator font.
|
|
||||||
#[clap(long, short)]
|
|
||||||
predator: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
|
@ -43,29 +8,9 @@ fn main() {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let cli = Cli::parse();
|
|
||||||
|
|
||||||
let seconds = cli.hours * 3600 + cli.minutes * 60 + cli.seconds;
|
|
||||||
let duration = Duration::from_secs(seconds);
|
|
||||||
|
|
||||||
let direction = if cli.count_up {
|
|
||||||
CountDirection::Up
|
|
||||||
} else {
|
|
||||||
CountDirection::Down
|
|
||||||
};
|
|
||||||
|
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"katabastird",
|
"katabastird",
|
||||||
options,
|
options,
|
||||||
Box::new(move |cc| {
|
Box::new(move |cc| Box::new(Timer::new(cc))),
|
||||||
Box::new(Timer::new(
|
|
||||||
duration,
|
|
||||||
direction,
|
|
||||||
cc,
|
|
||||||
!cli.running,
|
|
||||||
cli.predator,
|
|
||||||
cli.alarm,
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
336
src/timer.rs
336
src/timer.rs
|
@ -2,6 +2,9 @@ use egui::{Color32, Direction, FontId, Layout, RichText, Ui};
|
||||||
use egui_extras::{Size, StripBuilder};
|
use egui_extras::{Size, StripBuilder};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crate::{cli::Cli, util::*};
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
use eframe::{App, CreationContext};
|
use eframe::{App, CreationContext};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -14,79 +17,73 @@ pub enum CountDirection {
|
||||||
pub struct Timer {
|
pub struct Timer {
|
||||||
direction: CountDirection,
|
direction: CountDirection,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
state: State,
|
state: TimerState,
|
||||||
tstart: Instant,
|
tstart: Instant,
|
||||||
alarm: Option<String>,
|
alarm: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum State {
|
enum TimerState {
|
||||||
Unstarted,
|
Unstarted,
|
||||||
Paused(ChronoState), // time remaining
|
Paused(ChronoState), // time remaining
|
||||||
Running(ChronoState), // time remaining
|
Running(ChronoState), // time remaining
|
||||||
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for State {
|
impl PartialEq for TimerState {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
matches!(
|
||||||
(Self::Paused(_), Self::Paused(_)) => true,
|
(self, other),
|
||||||
(Self::Running(_), Self::Running(_)) => true,
|
(Self::Paused(_), Self::Paused(_))
|
||||||
(Self::Unstarted, Self::Unstarted) => true,
|
| (Self::Running(_), Self::Running(_))
|
||||||
_ => false,
|
| (Self::Unstarted, Self::Unstarted)
|
||||||
}
|
| (Self::Finished, Self::Finished)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for State {}
|
impl Eq for TimerState {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct ChronoState {
|
struct ChronoState {
|
||||||
started: Instant,
|
updated: Instant,
|
||||||
remaining: Duration,
|
remaining: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Timer {
|
|
||||||
fn default() -> Self {
|
|
||||||
let dur = Duration::from_secs(30);
|
|
||||||
Self {
|
|
||||||
direction: CountDirection::Down,
|
|
||||||
duration: dur,
|
|
||||||
state: State::Unstarted,
|
|
||||||
tstart: Instant::now(),
|
|
||||||
alarm: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App for Timer {
|
impl App for Timer {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
ctx.request_repaint_after(Duration::from_secs(1));
|
ctx.request_repaint_after(Duration::from_secs(1));
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
let size = ctx.used_size();
|
let size = ctx.used_size();
|
||||||
let vsize = if size[1].is_normal() {
|
let vsize = if size.x.is_normal() {
|
||||||
size[1].abs()
|
size.x.abs()
|
||||||
} else {
|
} else {
|
||||||
600.0
|
400.0
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Unstarted => self.unstarted(ui, vsize),
|
TimerState::Unstarted => self.unstarted(ui, vsize),
|
||||||
_ => self.running(ui, vsize),
|
TimerState::Running(_) => self.running(ui, vsize),
|
||||||
|
TimerState::Paused(_) => self.paused(ui, vsize),
|
||||||
|
TimerState::Finished => self.finished(ui, vsize),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timer {
|
impl Timer {
|
||||||
pub fn new(
|
pub fn new(ctx: &CreationContext) -> Self {
|
||||||
duration: Duration,
|
let cli = Cli::parse();
|
||||||
direction: CountDirection,
|
let predator = cli.predator;
|
||||||
ctx: &CreationContext,
|
let seconds = cli.hours * 3600 + cli.minutes * 60 + cli.seconds;
|
||||||
paused: bool,
|
let duration = Duration::from_secs(seconds);
|
||||||
predator: bool,
|
|
||||||
alarm: Option<String>,
|
let direction = if cli.count_up {
|
||||||
) -> Self {
|
CountDirection::Up
|
||||||
let tstart = Instant::now();
|
} else {
|
||||||
|
CountDirection::Down
|
||||||
|
};
|
||||||
|
|
||||||
if predator {
|
if predator {
|
||||||
let mut fonts = egui::FontDefinitions::default();
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
fonts.font_data.insert(
|
fonts.font_data.insert(
|
||||||
|
@ -101,27 +98,22 @@ impl Timer {
|
||||||
ctx.egui_ctx.set_fonts(fonts);
|
ctx.egui_ctx.set_fonts(fonts);
|
||||||
}
|
}
|
||||||
ctx.egui_ctx.request_repaint_after(Duration::from_secs(1));
|
ctx.egui_ctx.request_repaint_after(Duration::from_secs(1));
|
||||||
if paused {
|
let mut timer = Timer {
|
||||||
Timer {
|
duration,
|
||||||
duration,
|
direction,
|
||||||
direction,
|
state: TimerState::Unstarted,
|
||||||
state: State::Unstarted,
|
tstart: Instant::now(),
|
||||||
tstart,
|
alarm: cli.alarm,
|
||||||
alarm,
|
};
|
||||||
}
|
if cli.running {
|
||||||
} else {
|
|
||||||
let cs = ChronoState {
|
let cs = ChronoState {
|
||||||
remaining: duration,
|
remaining: duration,
|
||||||
started: Instant::now(),
|
updated: Instant::now(),
|
||||||
};
|
};
|
||||||
Timer {
|
|
||||||
duration,
|
timer.state = TimerState::Running(cs);
|
||||||
direction,
|
|
||||||
state: State::Running(cs),
|
|
||||||
tstart,
|
|
||||||
alarm,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
timer
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unstarted(&mut self, ui: &mut Ui, size: f32) {
|
fn unstarted(&mut self, ui: &mut Ui, size: f32) {
|
||||||
|
@ -137,8 +129,8 @@ impl Timer {
|
||||||
strip.cell(|ui| {
|
strip.cell(|ui| {
|
||||||
if ui.button(start).clicked() {
|
if ui.button(start).clicked() {
|
||||||
let dur = self.duration;
|
let dur = self.duration;
|
||||||
self.state = State::Running(ChronoState {
|
self.state = TimerState::Running(ChronoState {
|
||||||
started: Instant::now(),
|
updated: Instant::now(),
|
||||||
remaining: dur,
|
remaining: dur,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -153,111 +145,39 @@ impl Timer {
|
||||||
.color(Color32::GOLD);
|
.color(Color32::GOLD);
|
||||||
|
|
||||||
let elapsed;
|
let elapsed;
|
||||||
let started;
|
let remaining;
|
||||||
let mut is_paused;
|
let mut is_paused = false;
|
||||||
if let State::Running(rs) = self.state {
|
if let TimerState::Running(rs) = self.state {
|
||||||
elapsed = Instant::now() - rs.started;
|
elapsed = Instant::now() - rs.updated;
|
||||||
started = rs.started;
|
remaining = rs.remaining;
|
||||||
is_paused = false;
|
|
||||||
} else if let State::Paused(cs) = self.state {
|
|
||||||
elapsed = cs.remaining;
|
|
||||||
started = Instant::now() - (self.duration - elapsed);
|
|
||||||
is_paused = true;
|
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
let remaining = if is_paused {
|
let remaining = remaining.saturating_sub(elapsed);
|
||||||
elapsed
|
|
||||||
} else {
|
|
||||||
self.duration.saturating_sub(elapsed)
|
|
||||||
};
|
|
||||||
|
|
||||||
StripBuilder::new(ui)
|
StripBuilder::new(ui)
|
||||||
.size(Size::relative(0.3333))
|
.size(Size::relative(0.3333))
|
||||||
.size(Size::remainder())
|
.size(Size::remainder())
|
||||||
.cell_layout(Layout::centered_and_justified(Direction::LeftToRight))
|
.cell_layout(Layout::centered_and_justified(Direction::LeftToRight))
|
||||||
.vertical(|mut strip| {
|
.vertical(|mut strip| {
|
||||||
if is_paused {
|
// first the pause
|
||||||
strip.strip(|pstrip| {
|
strip.cell(|ui| {
|
||||||
pstrip
|
if ui.button(text).clicked() {
|
||||||
.sizes(Size::remainder(), 2)
|
is_paused = true;
|
||||||
.cell_layout(Layout::centered_and_justified(Direction::TopDown))
|
}
|
||||||
.horizontal(|mut pstrip| {
|
});
|
||||||
pstrip.cell(|ui| {
|
|
||||||
let resume = RichText::new("RESUME")
|
|
||||||
.color(Color32::GREEN)
|
|
||||||
.font(FontId::monospace(tsize));
|
|
||||||
if ui.button(resume).clicked() {
|
|
||||||
is_paused = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
pstrip.cell(|ui| {
|
|
||||||
let reset = RichText::new("RESET")
|
|
||||||
.color(Color32::RED)
|
|
||||||
.font(FontId::monospace(tsize));
|
|
||||||
if ui.button(reset).clicked() {
|
|
||||||
self.state = State::Unstarted;
|
|
||||||
is_paused = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// first the pause
|
|
||||||
strip.cell(|ui| {
|
|
||||||
if ui.button(text).clicked() {
|
|
||||||
is_paused = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we're counting up, do the right thing
|
// if we're counting up, do the right thing
|
||||||
let remaining = match self.direction {
|
let remaining = match self.direction {
|
||||||
CountDirection::Down => remaining,
|
CountDirection::Down => remaining,
|
||||||
CountDirection::Up => self.duration - remaining,
|
CountDirection::Up => self.duration - remaining,
|
||||||
};
|
};
|
||||||
|
|
||||||
// now the numbers
|
// now the numbers
|
||||||
let color = if is_paused {
|
let color = Color32::DARK_GRAY;
|
||||||
let blink = (Instant::now() - self.tstart).as_secs() % 2 == 0;
|
|
||||||
if blink {
|
|
||||||
Color32::BLACK
|
|
||||||
} else {
|
|
||||||
Color32::DARK_GRAY
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Color32::DARK_GRAY
|
|
||||||
};
|
|
||||||
let hours = remaining.as_secs() / 3600;
|
|
||||||
let minutes = (remaining.as_secs() / 60) % 60;
|
|
||||||
let seconds = remaining.as_secs() % 60;
|
|
||||||
let tsize = size * 0.7;
|
let tsize = size * 0.7;
|
||||||
let hours = RichText::new(format!("{:02}", hours))
|
|
||||||
.font(FontId::monospace(tsize))
|
display_digits(&mut strip, remaining, color, tsize);
|
||||||
.color(color);
|
|
||||||
let minutes = RichText::new(format!("{:02}", minutes))
|
|
||||||
.font(FontId::monospace(tsize))
|
|
||||||
.color(color);
|
|
||||||
let seconds = RichText::new(format!("{:02}", seconds))
|
|
||||||
.font(FontId::monospace(tsize))
|
|
||||||
.color(color);
|
|
||||||
strip.strip(|strip| {
|
|
||||||
strip
|
|
||||||
.sizes(Size::relative(0.33), 3)
|
|
||||||
.cell_layout(Layout::centered_and_justified(Direction::TopDown))
|
|
||||||
.horizontal(|mut strip| {
|
|
||||||
strip.cell(|ui| {
|
|
||||||
ui.label(hours);
|
|
||||||
});
|
|
||||||
strip.cell(|ui| {
|
|
||||||
ui.label(minutes);
|
|
||||||
});
|
|
||||||
strip.cell(|ui| {
|
|
||||||
ui.label(seconds);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if remaining.is_zero() {
|
if remaining.is_zero() {
|
||||||
|
@ -265,36 +185,118 @@ impl Timer {
|
||||||
let alarm_file = alarm_file.to_owned();
|
let alarm_file = alarm_file.to_owned();
|
||||||
std::thread::spawn(move || alarm(alarm_file));
|
std::thread::spawn(move || alarm(alarm_file));
|
||||||
}
|
}
|
||||||
self.state = State::Unstarted;
|
self.state = TimerState::Finished;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cs = ChronoState { remaining, started };
|
let cs = ChronoState {
|
||||||
|
remaining,
|
||||||
|
updated: Instant::now(),
|
||||||
|
};
|
||||||
if is_paused {
|
if is_paused {
|
||||||
if self.state == State::Unstarted {
|
// did we click the reset button?
|
||||||
|
if self.state == TimerState::Unstarted {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.state = State::Paused(cs);
|
self.state = TimerState::Paused(cs);
|
||||||
} else {
|
} else {
|
||||||
self.state = State::Running(cs);
|
self.state = TimerState::Running(cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fn paused(&mut self, ui: &mut Ui, vsize: f32) {
|
||||||
fn alarm(path: String) {
|
let elapsed;
|
||||||
use rodio::{source::Source, Decoder, OutputStream};
|
let mut is_running = false;
|
||||||
use std::fs::File;
|
let tsize = vsize * 0.3;
|
||||||
use std::io::BufReader;
|
if let TimerState::Paused(cs) = self.state {
|
||||||
|
elapsed = cs.remaining;
|
||||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
} else {
|
||||||
let file = BufReader::new(File::open(path).unwrap());
|
unreachable!()
|
||||||
let source = Decoder::new(file).unwrap();
|
}
|
||||||
let dur = if let Some(dur) = source.total_duration() {
|
|
||||||
dur
|
let remaining = elapsed;
|
||||||
} else {
|
StripBuilder::new(ui)
|
||||||
Duration::from_secs(10)
|
.size(Size::relative(0.33333))
|
||||||
};
|
.size(Size::remainder())
|
||||||
|
.cell_layout(Layout::centered_and_justified(Direction::LeftToRight))
|
||||||
let _ = stream_handle.play_raw(source.convert_samples());
|
.vertical(|mut strip| {
|
||||||
std::thread::sleep(dur);
|
strip.strip(|pstrip| {
|
||||||
|
pstrip
|
||||||
|
.sizes(Size::remainder(), 2)
|
||||||
|
.cell_layout(Layout::centered_and_justified(Direction::TopDown))
|
||||||
|
.horizontal(|mut pstrip| {
|
||||||
|
pstrip.cell(|ui| {
|
||||||
|
let resume = RichText::new("RESUME")
|
||||||
|
.color(Color32::GREEN)
|
||||||
|
.font(FontId::monospace(tsize));
|
||||||
|
if ui.button(resume).clicked() {
|
||||||
|
is_running = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pstrip.cell(|ui| {
|
||||||
|
let reset = RichText::new("RESET")
|
||||||
|
.color(Color32::RED)
|
||||||
|
.font(FontId::monospace(tsize));
|
||||||
|
if ui.button(reset).clicked() {
|
||||||
|
self.state = TimerState::Unstarted;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let color = {
|
||||||
|
let blink = (Instant::now() - self.tstart).as_secs() % 2 == 0;
|
||||||
|
if blink {
|
||||||
|
Color32::BLACK
|
||||||
|
} else {
|
||||||
|
Color32::DARK_GRAY
|
||||||
|
}
|
||||||
|
};
|
||||||
|
display_digits(&mut strip, remaining, color, vsize * 0.7);
|
||||||
|
});
|
||||||
|
|
||||||
|
if is_running {
|
||||||
|
let cs = ChronoState {
|
||||||
|
remaining,
|
||||||
|
updated: Instant::now(),
|
||||||
|
};
|
||||||
|
self.state = TimerState::Running(cs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finished(&mut self, ui: &mut Ui, vsize: f32) {
|
||||||
|
let remaining = match self.direction {
|
||||||
|
CountDirection::Up => self.duration,
|
||||||
|
CountDirection::Down => Duration::from_nanos(0),
|
||||||
|
};
|
||||||
|
StripBuilder::new(ui)
|
||||||
|
.size(Size::relative(0.33333))
|
||||||
|
.size(Size::remainder())
|
||||||
|
.cell_layout(Layout::centered_and_justified(Direction::LeftToRight))
|
||||||
|
.vertical(|mut strip| {
|
||||||
|
strip.strip(|pstrip| {
|
||||||
|
pstrip
|
||||||
|
.size(Size::remainder())
|
||||||
|
.cell_layout(Layout::centered_and_justified(Direction::TopDown))
|
||||||
|
.horizontal(|mut pstrip| {
|
||||||
|
pstrip.cell(|ui| {
|
||||||
|
let reset = RichText::new("RESET")
|
||||||
|
.color(Color32::GOLD)
|
||||||
|
.font(FontId::monospace(vsize * 0.3));
|
||||||
|
if ui.button(reset).clicked() {
|
||||||
|
self.state = TimerState::Unstarted;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let color = {
|
||||||
|
let blink = (Instant::now() - self.tstart).as_secs() % 2 == 0;
|
||||||
|
if blink {
|
||||||
|
Color32::BLACK
|
||||||
|
} else {
|
||||||
|
Color32::DARK_GRAY
|
||||||
|
}
|
||||||
|
};
|
||||||
|
display_digits(&mut strip, remaining, color, vsize * 0.7);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
54
src/util.rs
Normal file
54
src/util.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use egui::{Color32, Direction, FontId, Layout, RichText};
|
||||||
|
use egui_extras::{Size, Strip};
|
||||||
|
|
||||||
|
pub fn format_digits(digits: u64, size: f32, color: Color32) -> RichText {
|
||||||
|
RichText::new(format!("{:02}", digits))
|
||||||
|
.font(FontId::monospace(size))
|
||||||
|
.color(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alarm(path: String) {
|
||||||
|
use rodio::{source::Source, Decoder, OutputStream};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||||
|
let file = BufReader::new(File::open(path).unwrap());
|
||||||
|
let source = Decoder::new(file).unwrap();
|
||||||
|
let dur = if let Some(dur) = source.total_duration() {
|
||||||
|
dur
|
||||||
|
} else {
|
||||||
|
Duration::from_secs(10)
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = stream_handle.play_raw(source.convert_samples());
|
||||||
|
std::thread::sleep(dur);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn display_digits(strip: &mut Strip, dur: Duration, color: Color32, size: f32) {
|
||||||
|
let hours = dur.as_secs() / 3600;
|
||||||
|
let minutes = (dur.as_secs() / 60) % 60;
|
||||||
|
let seconds = dur.as_secs() % 60;
|
||||||
|
let hours = format_digits(hours, size, color);
|
||||||
|
let minutes = format_digits(minutes, size, color);
|
||||||
|
let seconds = format_digits(seconds, size, color);
|
||||||
|
|
||||||
|
strip.strip(|strip| {
|
||||||
|
strip
|
||||||
|
.sizes(Size::relative(0.33), 3)
|
||||||
|
.cell_layout(Layout::centered_and_justified(Direction::TopDown))
|
||||||
|
.horizontal(|mut strip| {
|
||||||
|
strip.cell(|ui| {
|
||||||
|
ui.label(hours);
|
||||||
|
});
|
||||||
|
strip.cell(|ui| {
|
||||||
|
ui.label(minutes);
|
||||||
|
});
|
||||||
|
strip.cell(|ui| {
|
||||||
|
ui.label(seconds);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue