An applicaiton and simulator for a bike lighting system #245

Merged
savanni merged 16 commits from bike-lights into main 2024-08-01 01:32:29 +00:00
6 changed files with 807 additions and 70 deletions
Showing only changes of commit 84b077e20c - Show all commits

1
Cargo.lock generated
View File

@ -3783,6 +3783,7 @@ dependencies = [
"glib", "glib",
"gtk4", "gtk4",
"libadwaita", "libadwaita",
"lights-core",
"pango", "pango",
] ]

View File

@ -1,14 +1,72 @@
pub fn add(left: usize, right: usize) -> usize { use std::time::Duration;
left + right
mod patterns;
pub use patterns::*;
mod types;
pub use types::{DashboardPattern, Pattern, RGB};
pub trait UI {
fn update_lights(&self, dashboard_lights: DashboardPattern, lights: Pattern);
} }
#[cfg(test)] pub trait Animation {
mod tests { fn tick(&self, frame: Duration) -> (DashboardPattern, Pattern);
use super::*; }
#[test] pub struct DefaultAnimation {}
fn it_works() {
let result = add(2, 2); impl Animation for DefaultAnimation {
assert_eq!(result, 4); fn tick(&self, _: Duration) -> (DashboardPattern, Pattern) {
(PRIDE_DASHBOARD, PRIDE)
}
}
#[derive(Clone)]
pub enum Event {
Brake,
BrakeRelease,
LeftBlinker,
LeftBlinkerRelease,
NextPattern,
PreviousPattern,
RightBlinker,
RightBlinkerRelease,
}
#[derive(Clone)]
pub enum State {
Pattern(u8),
Brake,
LeftBlinker,
RightBlinker,
BrakeLeftBlinker,
BrakeRightBlinker,
}
pub struct App {
ui: Box<dyn UI>,
state: State,
current_animation: Box<dyn Animation>,
dashboard_lights: DashboardPattern,
lights: Pattern,
}
impl App {
pub fn new(ui: Box<dyn UI>) -> Self {
Self {
ui,
state: State::Pattern(0),
current_animation: Box::new(DefaultAnimation {}),
dashboard_lights: OFF_DASHBOARD,
lights: OFF,
}
}
pub fn tick(&mut self, frame: Duration) {
let (dashboard, lights) = self.current_animation.tick(frame);
self.dashboard_lights = dashboard.clone();
self.lights = lights.clone();
self.ui.update_lights(dashboard, lights);
} }
} }

View File

@ -0,0 +1,671 @@
use crate::RGB;
pub type DashboardPattern = [RGB; 3];
pub type Pattern = [RGB; 60];
pub const OFF_DASHBOARD: DashboardPattern = [
RGB { r: 0, g: 0, b: 0 },
RGB { r: 0, g: 0, b: 0 },
RGB { r: 0, g: 0, b: 0 },
];
pub const OFF: Pattern = [
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 },
];
pub const PRIDE_DASHBOARD: DashboardPattern = [
RGB { r: 228, g: 3, b: 3 },
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 115,
g: 41,
b: 130,
},
];
pub const PRIDE: Pattern = [
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB { r: 228, g: 3, b: 3 },
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 140,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 255,
g: 237,
b: 0,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 0,
g: 128,
b: 38,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 36,
g: 64,
b: 142,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
RGB {
r: 115,
g: 41,
b: 130,
},
];
pub const TRANS_PRIDE_DASHBOARD: DashboardPattern = [
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 245,
g: 169,
b: 184,
},
];
pub const TRANS_PRIDE: Pattern = [
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 255,
g: 255,
b: 255,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 245,
g: 169,
b: 184,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
RGB {
r: 91,
g: 206,
b: 250,
},
];

View File

@ -0,0 +1,9 @@
#[derive(Clone)]
pub struct RGB {
pub r: u8,
pub g: u8,
pub b: u8,
}
pub type DashboardPattern = [RGB; 3];
pub type Pattern = [RGB; 60];

View File

@ -12,4 +12,5 @@ fixed = { version = "1" }
gio = { version = "0.18" } gio = { version = "0.18" }
glib = { version = "0.18" } glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] } gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] }
lights-core = { path = "../core" }
pango = { version = "*" } pango = { version = "*" }

View File

@ -1,19 +1,19 @@
use adw::prelude::*; use adw::prelude::*;
use glib::Object; use glib::{Object, Sender};
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use lights_core::{App, DashboardPattern, Pattern, RGB, UI};
use std::{cell::RefCell, env, rc::Rc}; use std::{cell::RefCell, env, rc::Rc};
const WIDTH: i32 = 640; const WIDTH: i32 = 640;
const HEIGHT: i32 = 480; const HEIGHT: i32 = 480;
pub struct RGB { pub struct Update {
pub r: u8, dashboard: DashboardPattern,
pub g: u8, lights: Pattern,
pub b: u8,
} }
pub struct DashboardLightsPrivate { pub struct DashboardLightsPrivate {
lights: Rc<RefCell<[RGB; 3]>>, lights: Rc<RefCell<DashboardPattern>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -55,9 +55,9 @@ impl DashboardLights {
let lights = s.imp().lights.borrow(); let lights = s.imp().lights.borrow();
for i in 0..3 { for i in 0..3 {
context.set_source_rgb( context.set_source_rgb(
lights[i].r as f64, lights[i].r as f64 / 256.,
lights[i].g as f64, lights[i].g as f64 / 256.,
lights[i].b as f64, lights[i].b as f64 / 256.,
); );
context.rectangle(start + 100. * i as f64, 10., 80., 80.); context.rectangle(start + 100. * i as f64, 10., 80., 80.);
let _ = context.fill(); let _ = context.fill();
@ -75,7 +75,7 @@ impl DashboardLights {
} }
pub struct BikeLightsPrivate { pub struct BikeLightsPrivate {
lights: Rc<RefCell<[RGB; 60]>>, lights: Rc<RefCell<Pattern>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -173,18 +173,18 @@ impl BikeLights {
let lights = s.imp().lights.borrow(); let lights = s.imp().lights.borrow();
for i in 0..30 { for i in 0..30 {
context.set_source_rgb( context.set_source_rgb(
lights[i].r as f64, lights[i].r as f64 / 256.,
lights[i].g as f64, lights[i].g as f64 / 256.,
lights[i].b as f64, lights[i].b as f64 / 256.,
); );
context.rectangle(5. + 20. * i as f64, 5., 15., 15.); context.rectangle(5. + 20. * i as f64, 5., 15., 15.);
let _ = context.fill(); let _ = context.fill();
} }
for i in 0..30 { for i in 0..30 {
context.set_source_rgb( context.set_source_rgb(
lights[i + 30].r as f64, lights[i + 30].r as f64 / 256.,
lights[i + 30].g as f64, lights[i + 30].g as f64 / 256.,
lights[i + 30].b as f64, lights[i + 30].b as f64 / 256.,
); );
context.rectangle(5. + 20. * i as f64, 30., 15., 15.); context.rectangle(5. + 20. * i as f64, 30., 15., 15.);
let _ = context.fill(); let _ = context.fill();
@ -201,14 +201,42 @@ impl BikeLights {
} }
} }
struct GTKUI {
tx: Rc<RefCell<Option<Sender<Update>>>>,
}
impl UI for GTKUI {
fn update_lights(&self, dashboard_lights: DashboardPattern, lights: Pattern) {
if let Some(tx) = self.tx.borrow().as_ref() {
tx.send(Update {
dashboard: dashboard_lights,
lights,
})
.unwrap();
}
}
}
fn main() { fn main() {
let app = adw::Application::builder() let adw_app = adw::Application::builder()
.application_id("com.luminescent-dreams.bike-light-simulator") .application_id("com.luminescent-dreams.bike-light-simulator")
.build(); .build();
app.connect_activate(move |app| { adw_app.connect_activate(move |adw_app| {
let (tx, rx) = gtk::glib::MainContext::channel::<Update>(gtk::glib::Priority::DEFAULT);
std::thread::spawn(move || {
let mut bike_app = App::new(Box::new(GTKUI {
tx: Rc::new(RefCell::new(Some(tx))),
}));
loop {
bike_app.tick(std::time::Duration::from_millis(0));
std::thread::sleep(std::time::Duration::from_millis(100));
}
});
let window = adw::ApplicationWindow::builder() let window = adw::ApplicationWindow::builder()
.application(app) .application(adw_app)
.default_width(WIDTH) .default_width(WIDTH)
.default_height(HEIGHT) .default_height(HEIGHT)
.build(); .build();
@ -226,47 +254,6 @@ fn main() {
let brake_button = gtk::Button::builder().label("Brakes").build(); let brake_button = gtk::Button::builder().label("Brakes").build();
let right_button = gtk::Button::builder().label("R").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(&left_button);
controls.append(&brake_button); controls.append(&brake_button);
controls.append(&right_button); controls.append(&right_button);
@ -286,10 +273,20 @@ fn main() {
layout.append(&dashboard_lights); layout.append(&dashboard_lights);
layout.append(&bike_lights); layout.append(&bike_lights);
rx.attach(None, {
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
}
});
window.set_content(Some(&layout)); window.set_content(Some(&layout));
window.present(); window.present();
}); });
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
ApplicationExtManual::run_with_args(&app, &args); ApplicationExtManual::run_with_args(&adw_app, &args);
} }