2023-11-26 18:03:19 +00:00
|
|
|
use adw::prelude::*;
|
2023-11-27 01:57:28 +00:00
|
|
|
use glib::{Object, Sender};
|
2023-11-26 18:03:19 +00:00
|
|
|
use gtk::subclass::prelude::*;
|
2023-11-27 02:19:09 +00:00
|
|
|
use lights_core::{App, DashboardPattern, Event, Pattern, RGB, UI};
|
|
|
|
use std::{
|
|
|
|
cell::RefCell,
|
|
|
|
env,
|
|
|
|
rc::Rc,
|
|
|
|
sync::mpsc::{Receiver, TryRecvError},
|
|
|
|
};
|
2023-11-26 18:03:19 +00:00
|
|
|
|
|
|
|
const WIDTH: i32 = 640;
|
|
|
|
const HEIGHT: i32 = 480;
|
|
|
|
|
2023-11-27 01:57:28 +00:00
|
|
|
pub struct Update {
|
|
|
|
dashboard: DashboardPattern,
|
|
|
|
lights: Pattern,
|
2023-11-26 18:03:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct DashboardLightsPrivate {
|
2023-11-27 01:57:28 +00:00
|
|
|
lights: Rc<RefCell<DashboardPattern>>,
|
2023-11-26 18:03:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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<DashboardLightsPrivate>) @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(
|
2023-11-27 01:57:28 +00:00
|
|
|
lights[i].r as f64 / 256.,
|
|
|
|
lights[i].g as f64 / 256.,
|
|
|
|
lights[i].b as f64 / 256.,
|
2023-11-26 18:03:19 +00:00
|
|
|
);
|
|
|
|
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 {
|
2023-11-27 01:57:28 +00:00
|
|
|
lights: Rc<RefCell<Pattern>>,
|
2023-11-26 18:03:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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<BikeLightsPrivate>) @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(
|
2023-11-27 01:57:28 +00:00
|
|
|
lights[i].r as f64 / 256.,
|
|
|
|
lights[i].g as f64 / 256.,
|
|
|
|
lights[i].b as f64 / 256.,
|
2023-11-26 18:03:19 +00:00
|
|
|
);
|
|
|
|
context.rectangle(5. + 20. * i as f64, 5., 15., 15.);
|
|
|
|
let _ = context.fill();
|
|
|
|
}
|
|
|
|
for i in 0..30 {
|
|
|
|
context.set_source_rgb(
|
2023-11-27 01:57:28 +00:00
|
|
|
lights[i + 30].r as f64 / 256.,
|
|
|
|
lights[i + 30].g as f64 / 256.,
|
|
|
|
lights[i + 30].b as f64 / 256.,
|
2023-11-26 18:03:19 +00:00
|
|
|
);
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-27 01:57:28 +00:00
|
|
|
struct GTKUI {
|
2023-11-27 02:19:09 +00:00
|
|
|
tx: Sender<Update>,
|
|
|
|
rx: Receiver<Event>,
|
2023-11-27 01:57:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UI for GTKUI {
|
2023-11-27 02:19:09 +00:00
|
|
|
fn check_event(&self) -> Option<Event> {
|
|
|
|
match self.rx.try_recv() {
|
|
|
|
Ok(event) => Some(event),
|
|
|
|
Err(TryRecvError::Empty) => None,
|
|
|
|
Err(TryRecvError::Disconnected) => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-27 01:57:28 +00:00
|
|
|
fn update_lights(&self, dashboard_lights: DashboardPattern, lights: Pattern) {
|
2023-11-27 02:19:09 +00:00
|
|
|
self.tx
|
|
|
|
.send(Update {
|
2023-11-27 01:57:28 +00:00
|
|
|
dashboard: dashboard_lights,
|
|
|
|
lights,
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-26 18:03:19 +00:00
|
|
|
fn main() {
|
2023-11-27 01:57:28 +00:00
|
|
|
let adw_app = adw::Application::builder()
|
2023-11-26 18:03:19 +00:00
|
|
|
.application_id("com.luminescent-dreams.bike-light-simulator")
|
|
|
|
.build();
|
|
|
|
|
2023-11-27 01:57:28 +00:00
|
|
|
adw_app.connect_activate(move |adw_app| {
|
2023-11-27 02:19:09 +00:00
|
|
|
let (update_tx, update_rx) =
|
|
|
|
gtk::glib::MainContext::channel::<Update>(gtk::glib::Priority::DEFAULT);
|
|
|
|
let (event_tx, event_rx) = std::sync::mpsc::channel();
|
2023-11-27 01:57:28 +00:00
|
|
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
let mut bike_app = App::new(Box::new(GTKUI {
|
2023-11-27 02:19:09 +00:00
|
|
|
tx: update_tx,
|
|
|
|
rx: event_rx,
|
2023-11-27 01:57:28 +00:00
|
|
|
}));
|
|
|
|
loop {
|
2023-11-27 02:19:09 +00:00
|
|
|
bike_app.tick(std::time::Instant::now());
|
2023-11-27 01:57:28 +00:00
|
|
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-11-26 18:03:19 +00:00
|
|
|
let window = adw::ApplicationWindow::builder()
|
2023-11-27 01:57:28 +00:00
|
|
|
.application(adw_app)
|
2023-11-26 18:03:19 +00:00
|
|
|
.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();
|
|
|
|
|
2023-11-27 02:19:09 +00:00
|
|
|
left_button.connect_clicked({
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
move |_| {
|
|
|
|
let _ = event_tx.send(Event::LeftBlinker);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
brake_button.connect_clicked({
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
move |_| {
|
|
|
|
let _ = event_tx.send(Event::Brake);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
right_button.connect_clicked({
|
|
|
|
let event_tx = event_tx.clone();
|
|
|
|
move |_| {
|
|
|
|
let _ = event_tx.send(Event::RightBlinker);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-11-26 18:03:19 +00:00
|
|
|
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);
|
|
|
|
|
2023-11-27 02:19:09 +00:00
|
|
|
update_rx.attach(None, {
|
2023-11-27 01:57:28 +00:00
|
|
|
let dashboard_lights = dashboard_lights.clone();
|
|
|
|
let bike_lights = bike_lights.clone();
|
|
|
|
move |Update { dashboard, lights }| {
|
|
|
|
dashboard_lights.set_lights(dashboard);
|
|
|
|
bike_lights.set_lights(lights);
|
|
|
|
glib::ControlFlow::Continue
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-11-26 18:03:19 +00:00
|
|
|
window.set_content(Some(&layout));
|
|
|
|
window.present();
|
|
|
|
});
|
|
|
|
|
|
|
|
let args: Vec<String> = env::args().collect();
|
2023-11-27 01:57:28 +00:00
|
|
|
ApplicationExtManual::run_with_args(&adw_app, &args);
|
2023-11-26 18:03:19 +00:00
|
|
|
}
|