use std::ops::Mul; use std::sync::mpsc::{self, Sender}; use std::thread; use std::time::Duration; use glib::clone; use gtk::prelude::*; use gtk::{glib, Application, ApplicationWindow, Button, Entry, Label, Orientation}; use state::{Cmd, State}; fn main() -> glib::ExitCode { // Create a new application let app = Application::builder() .application_id("org.gtk_rs.HelloWorld2") .build(); let (send, rec) = mpsc::channel(); thread::spawn(move || { State::initial().spin(rec); }); app.connect_activate(move |app| build_ui(app, send.clone())); app.run() } mod state; fn build_ui(app: &Application, cmd: Sender) { let label = Label::builder().label("-").build(); // entry let entry = Entry::builder().text("0:00:10").build(); let set_button = Button::builder().label("set").build(); let entry_container = gtk::Box::builder() .orientation(Orientation::Horizontal) .spacing(8) .build(); entry_container.append(&entry); entry_container.append(&set_button); let passed = cmd.clone(); set_button.connect_clicked(move |_| { let cmd = &passed; let inputted = entry.text().to_string(); let inputted = parse_input_time(&inputted); cmd.send(Cmd::SetInput(inputted)).unwrap(); cmd.send(Cmd::Reset).unwrap(); }); // pause & reset let pause_button = Button::builder().label("start").build(); let reset_button = Button::builder().label("reset").build(); let button_container = gtk::Box::builder() .orientation(Orientation::Horizontal) .spacing(8) .build(); button_container.append(&pause_button); button_container.append(&reset_button); let passed = cmd.clone(); pause_button.connect_clicked(move |button| { let cmd = &passed; cmd.send(Cmd::TogglePaused).unwrap(); let (send, rec) = oneshot::channel(); cmd.send(Cmd::GetIsPaused(send)).unwrap(); let is_paused = rec.recv().unwrap(); button.set_label(if is_paused { "start" } else { "pause" }); }); let passed = cmd.clone(); reset_button.connect_clicked(move |_| { let cmd = &passed; pause_button.set_label("start"); cmd.send(Cmd::Reset).unwrap(); }); // container let container = gtk::Box::builder() .orientation(Orientation::Vertical) .spacing(8) .margin_top(12) .margin_bottom(12) .margin_start(12) .margin_end(12) .build(); container.append(&label); container.append(&entry_container); container.append(&button_container); let passed = cmd.clone(); glib::spawn_future_local(clone!(@weak label => async move { let cmd = &passed; loop { glib::timeout_future(Duration::from_millis(10)).await; let (send, rec) = oneshot::channel(); cmd.send(Cmd::GetTime(send)).unwrap(); let time = rec.recv().unwrap(); label.set_text(&format_time(time)); } })); let window = ApplicationWindow::builder() .application(app) .title("My GTK App") .child(&container) .build(); window.present(); } fn parse_input_time(inputted: &str) -> f32 { fn parse_empty_zero(input: &str) -> i32 { if input.is_empty() { return 0; } input.parse().unwrap() } inputted .split(':') .collect::>() .iter() .rev() .map(|part| parse_empty_zero(part)) .enumerate() .map(|(index, item)| 60i32.pow(index as u32) * item) .sum::() as f32 } #[test] fn test_parse_input_time() { assert_eq!(parse_input_time(""), 0f32); assert_eq!(parse_input_time(":"), 0f32); assert_eq!(parse_input_time("3"), 3f32); assert_eq!(parse_input_time(":3"), 3f32); assert_eq!(parse_input_time("3:"), 180f32); assert_eq!(parse_input_time(":3:"), 180f32); assert_eq!(parse_input_time("3::"), 10800f32); } fn format_time(input: f32) -> String { // les maths 🧙 let decimal = input.rem_euclid(1.).mul(100.) as i32; let rest = input.div_euclid(1.); let secs = rest.rem_euclid(60.) as i32; let rest = rest.div_euclid(60.); let min = rest.rem_euclid(60.) as i32; let hours = rest.div_euclid(60.) as i32; match (hours == 0, min == 0) { (true, true) => format!("{secs}.{decimal:02}"), (true, _) => format!("{min}:{secs:02}.{decimal:02}"), _ => format!("{hours}:{min:02}:{secs:02}.{decimal:02}"), } } #[test] fn test_format_time() { assert_eq!(format_time(0.), "0.00"); assert_eq!(format_time(0.5), "0.50"); assert_eq!(format_time(0.25), "0.25"); assert_eq!(format_time(10.), "10.00"); assert_eq!(format_time(60.), "1:00.00"); assert_eq!(format_time(600.), "10:00.00"); assert_eq!(format_time(3600.), "1:00:00.00"); assert_eq!(format_time(3600.5), "1:00:00.50"); assert_eq!(format_time(3660.5), "1:01:00.50"); }