use adw::prelude::*; use glib::Object; use gtk::subclass::prelude::*; use std::{cell::RefCell, env, rc::Rc}; const WIDTH: i32 = 640; const HEIGHT: i32 = 480; pub struct RGB { pub r: u8, pub g: u8, pub b: u8, } pub struct DashboardLightsPrivate { lights: Rc>, } #[glib::object_subclass] impl ObjectSubclass for DashboardLightsPrivate { const NAME: &'static str = "DashboardLights"; type Type = DashboardLights; type ParentType = gtk::DrawingArea; fn new() -> Self { Self { lights: Rc::new(RefCell::new([ RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, ])), } } } impl ObjectImpl for DashboardLightsPrivate {} impl WidgetImpl for DashboardLightsPrivate {} impl DrawingAreaImpl for DashboardLightsPrivate {} glib::wrapper! { pub struct DashboardLights(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; } impl DashboardLights { pub fn new() -> Self { let s: Self = Object::builder().build(); s.set_width_request(WIDTH); s.set_height_request(100); s.set_draw_func({ let s = s.clone(); move |_, context, width, _| { let start = width as f64 / 2. - 150.; let lights = s.imp().lights.borrow(); for i in 0..3 { context.set_source_rgb( lights[i].r as f64, lights[i].g as f64, lights[i].b as f64, ); context.rectangle(start + 100. * i as f64, 10., 80., 80.); let _ = context.fill(); } } }); s } pub fn set_lights(&self, lights: [RGB; 3]) { *self.imp().lights.borrow_mut() = lights; self.queue_draw(); } } pub struct BikeLightsPrivate { lights: Rc>, } #[glib::object_subclass] impl ObjectSubclass for BikeLightsPrivate { const NAME: &'static str = "BikeLights"; type Type = BikeLights; type ParentType = gtk::DrawingArea; fn new() -> Self { Self { lights: Rc::new(RefCell::new([ RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, ])), } } } impl ObjectImpl for BikeLightsPrivate {} impl WidgetImpl for BikeLightsPrivate {} impl DrawingAreaImpl for BikeLightsPrivate {} glib::wrapper! { pub struct BikeLights(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; } impl BikeLights { pub fn new() -> Self { let s: Self = Object::builder().build(); s.set_width_request(WIDTH); s.set_height_request(200); s.set_draw_func({ let s = s.clone(); move |_, context, _, _| { let lights = s.imp().lights.borrow(); for i in 0..30 { context.set_source_rgb( lights[i].r as f64, lights[i].g as f64, lights[i].b as f64, ); context.rectangle(5. + 20. * i as f64, 5., 15., 15.); let _ = context.fill(); } for i in 0..30 { context.set_source_rgb( lights[i + 30].r as f64, lights[i + 30].g as f64, lights[i + 30].b as f64, ); context.rectangle(5. + 20. * i as f64, 30., 15., 15.); let _ = context.fill(); } } }); s } pub fn set_lights(&self, lights: [RGB; 60]) { *self.imp().lights.borrow_mut() = lights; self.queue_draw(); } } fn main() { let app = adw::Application::builder() .application_id("com.luminescent-dreams.bike-light-simulator") .build(); app.connect_activate(move |app| { let window = adw::ApplicationWindow::builder() .application(app) .default_width(WIDTH) .default_height(HEIGHT) .build(); let layout = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); let controls = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .build(); let dashboard_lights = DashboardLights::new(); let bike_lights = BikeLights::new(); let left_button = gtk::Button::builder().label("L").build(); let brake_button = gtk::Button::builder().label("Brakes").build(); let right_button = gtk::Button::builder().label("R").build(); left_button.connect_clicked({ let dashboard_lights = dashboard_lights.clone(); move |_| { dashboard_lights.set_lights([ RGB { r: 128, g: 128, b: 0, }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, ]); } }); brake_button.connect_clicked({ let dashboard_lights = dashboard_lights.clone(); move |_| { dashboard_lights.set_lights([ RGB { r: 128, g: 0, b: 0 }, RGB { r: 128, g: 0, b: 0 }, RGB { r: 128, g: 0, b: 0 }, ]); } }); right_button.connect_clicked({ let dashboard_lights = dashboard_lights.clone(); move |_| { dashboard_lights.set_lights([ RGB { r: 0, g: 0, b: 0 }, RGB { r: 0, g: 0, b: 0 }, RGB { r: 128, g: 128, b: 0, }, ]); } }); controls.append(&left_button); controls.append(&brake_button); controls.append(&right_button); layout.append(&controls); let pattern_controls = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .build(); let previous_pattern = gtk::Button::builder().label("Previous").build(); let next_pattern = gtk::Button::builder().label("Next").build(); pattern_controls.append(&previous_pattern); pattern_controls.append(&next_pattern); layout.append(&pattern_controls); layout.append(&dashboard_lights); layout.append(&bike_lights); window.set_content(Some(&layout)); window.present(); }); let args: Vec = env::args().collect(); ApplicationExtManual::run_with_args(&app, &args); }