163 lines
4.9 KiB
Rust
163 lines
4.9 KiB
Rust
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<Cmd>) {
|
|
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::<Vec<_>>()
|
|
.iter()
|
|
.rev()
|
|
.map(|part| parse_empty_zero(part))
|
|
.enumerate()
|
|
.map(|(index, item)| 60i32.pow(index as u32) * item)
|
|
.sum::<i32>() 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");
|
|
}
|