use egui::{Color32, Direction, FontId, Layout, RichText, Ui}; use egui_extras::{Size, StripBuilder}; use std::time::{Duration, Instant}; use eframe::App; #[derive(Debug, Clone, Copy)] pub enum CountDirection { Up, Down, } #[derive(Debug, Clone, Copy)] pub struct Timer { direction: CountDirection, duration: Duration, state: State, } #[derive(Debug, Clone, Copy)] enum State { Unstarted, Paused(Duration), // time remaining Running(RunningState), // time remaining } impl PartialEq for State { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Paused(_), Self::Paused(_)) => true, (Self::Running(_), Self::Running(_)) => true, (Self::Unstarted, Self::Unstarted) => true, _ => false, } } } impl Eq for State {} #[derive(Debug, Clone, Copy)] struct RunningState { started: Instant, remaining: Duration, } impl Default for Timer { fn default() -> Self { let dur = Duration::from_secs(30); Self { direction: CountDirection::Down, duration: dur, //state: State::Running(dur), state: State::Unstarted, } } } impl App for Timer { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { ctx.request_repaint_after(Duration::from_secs(1)); egui::CentralPanel::default().show(ctx, |ui| { let size = ctx.used_size(); let vsize = if size[1].is_normal() { size[1].abs() } else { 200.0 }; match self.state { State::Running(_) => self.running(ui, vsize), State::Paused(_) => {} State::Unstarted => { self.unstarted(ui, vsize); } } }); } } impl Timer { fn unstarted(&mut self, ui: &mut Ui, size: f32) { let start = RichText::new("START") .font(FontId::monospace(size * 0.9)) .color(Color32::WHITE) .background_color(Color32::LIGHT_GREEN); StripBuilder::new(ui) .size(Size::remainder()) .cell_layout(Layout::centered_and_justified(egui::Direction::TopDown)) .horizontal(|mut strip| { strip.cell(|ui| { if ui.button(start).clicked() { let dur = self.duration; self.state = State::Running(RunningState { started: Instant::now(), remaining: dur, }); } }); }); } fn paused(&mut self, ui: &mut Ui, size: f32) { todo!() } fn running(&mut self, ui: &mut Ui, size: f32) { let tsize = size * 0.2; let text = RichText::new("PAUSE") .font(FontId::monospace(tsize)) .color(Color32::GOLD); let elapsed; let started; if let State::Running(rs) = self.state { elapsed = Instant::now() - rs.started; started = rs.started; } else { unreachable!() } let remaining = self.duration.saturating_sub(elapsed); // StripBuilder::vertical(); StripBuilder::new(ui) .size(Size::remainder()) .cell_layout(Layout::centered_and_justified(Direction::LeftToRight)) .horizontal(|mut strip| { strip.cell(|ui| { if ui.button(text).clicked() { self.state = State::Paused(remaining); } }); }); if let State::Paused(_) = self.state { return; } self.state = State::Running(RunningState { started, remaining }); 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 hours = RichText::new(format!("{:02}", hours)).font(FontId::monospace(tsize)); let minutes = RichText::new(format!("{:02}", minutes)).font(FontId::monospace(tsize)); let seconds = RichText::new(format!("{:02}", seconds)).font(FontId::monospace(tsize)); StripBuilder::new(ui) .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); }); }); } }