Compare commits

..

4 Commits

71 changed files with 20909 additions and 23220 deletions

1038
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,6 @@
resolver = "2"
members = [
"authdb",
"bike-lights/bike",
"bike-lights/core",
"bike-lights/simulator",
"changeset",
"config",
"config-derive",
@ -22,6 +19,7 @@ members = [
"icon-test",
"ifc",
"memorycache",
"music-player/server",
"nom-training",
"otg/core",
"otg/gtk",
@ -30,5 +28,5 @@ members = [
"sgf",
"timezone-testing",
"tree",
"visions/server", "gm-dash/server",
"visions/server",
]

View File

@ -1,12 +0,0 @@
[build]
target = "thumbv6m-none-eabi"
[target.thumbv6m-none-eabi]
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "inline-threshold=5",
"-C", "no-vectorize-loops",
]
runner = "elf2uf2-rs -d"

View File

@ -1,18 +0,0 @@
[package]
name = "bike"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
az = { version = "1" }
cortex-m-rt = { version = "0.7.3" }
cortex-m = { version = "0.7.7" }
embedded-alloc = { version = "0.5.1" }
embedded-hal = { version = "0.2.7" }
fixed = { version = "1" }
fugit = { version = "0.3.7" }
lights-core = { path = "../core" }
panic-halt = { version = "0.2.0" }
rp-pico = { version = "0.8.0" }

View File

@ -1,241 +0,0 @@
#![no_main]
#![no_std]
extern crate alloc;
use alloc::boxed::Box;
use az::*;
use core::cell::RefCell;
use cortex_m::delay::Delay;
use embedded_alloc::Heap;
use embedded_hal::{blocking::spi::Write, digital::v2::InputPin, digital::v2::OutputPin};
use fixed::types::I16F16;
use fugit::RateExtU32;
use lights_core::{App, BodyPattern, DashboardPattern, Event, Instant, FPS, UI};
use panic_halt as _;
use rp_pico::{
entry,
hal::{
clocks::init_clocks_and_plls,
gpio::{FunctionSio, Pin, PinId, PullDown, PullUp, SioInput, SioOutput},
pac::{CorePeripherals, Peripherals},
spi::{Enabled, Spi, SpiDevice, ValidSpiPinout},
watchdog::Watchdog,
Clock, Sio,
},
Pins,
};
#[global_allocator]
static HEAP: Heap = Heap::empty();
const LIGHT_SCALE: I16F16 = I16F16::lit("256.0");
const DASHBOARD_BRIGHTESS: u8 = 1;
const BODY_BRIGHTNESS: u8 = 8;
struct DebouncedButton<P: PinId> {
debounce: Instant,
pin: Pin<P, FunctionSio<SioInput>, PullUp>,
}
impl<P: PinId> DebouncedButton<P> {
fn new(pin: Pin<P, FunctionSio<SioInput>, PullUp>) -> Self {
Self {
debounce: Instant((0 as u32).into()),
pin,
}
}
fn is_low(&self, time: Instant) -> bool {
if time <= self.debounce {
return false;
}
self.pin.is_low().unwrap_or(false)
}
fn set_debounce(&mut self, time: Instant) {
self.debounce = time + Instant((250 as u32).into());
}
}
struct BikeUI<
D: SpiDevice,
P: ValidSpiPinout<D>,
LeftId: PinId,
RightId: PinId,
PreviousId: PinId,
NextId: PinId,
BrakeId: PinId,
> {
spi: RefCell<Spi<Enabled, D, P, 8>>,
left_blinker_button: DebouncedButton<LeftId>,
right_blinker_button: DebouncedButton<RightId>,
previous_animation_button: DebouncedButton<PreviousId>,
next_animation_button: DebouncedButton<NextId>,
brake_sensor: Pin<BrakeId, FunctionSio<SioInput>, PullUp>,
brake_enabled: bool,
}
impl<
D: SpiDevice,
P: ValidSpiPinout<D>,
LeftId: PinId,
RightId: PinId,
PreviousId: PinId,
NextId: PinId,
BrakeId: PinId,
> BikeUI<D, P, LeftId, RightId, PreviousId, NextId, BrakeId>
{
fn new(
spi: Spi<Enabled, D, P, 8>,
left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullUp>,
right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullUp>,
previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullUp>,
next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullUp>,
brake_sensor: Pin<BrakeId, FunctionSio<SioInput>, PullUp>,
) -> Self {
Self {
spi: RefCell::new(spi),
left_blinker_button: DebouncedButton::new(left_blinker_button),
right_blinker_button: DebouncedButton::new(right_blinker_button),
previous_animation_button: DebouncedButton::new(previous_animation_button),
next_animation_button: DebouncedButton::new(next_animation_button),
brake_sensor,
brake_enabled: false,
}
}
}
impl<
D: SpiDevice,
P: ValidSpiPinout<D>,
LeftId: PinId,
RightId: PinId,
PreviousId: PinId,
NextId: PinId,
BrakeId: PinId,
> UI for BikeUI<D, P, LeftId, RightId, PreviousId, NextId, BrakeId>
{
fn check_event(&mut self, current_time: Instant) -> Option<Event> {
if self.brake_sensor.is_high().unwrap_or(true) && !self.brake_enabled {
self.brake_enabled = true;
Some(Event::Brake)
} else if self.brake_sensor.is_low().unwrap_or(false) && self.brake_enabled {
self.brake_enabled = false;
Some(Event::BrakeRelease)
} else if self.left_blinker_button.is_low(current_time) {
self.left_blinker_button.set_debounce(current_time);
Some(Event::LeftBlinker)
} else if self.right_blinker_button.is_low(current_time) {
self.right_blinker_button.set_debounce(current_time);
Some(Event::RightBlinker)
} else if self.previous_animation_button.is_low(current_time) {
self.previous_animation_button.set_debounce(current_time);
Some(Event::PreviousPattern)
} else if self.next_animation_button.is_low(current_time) {
self.next_animation_button.set_debounce(current_time);
Some(Event::NextPattern)
} else {
None
}
}
fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern) {
let mut lights: [u8; 260] = [0; 260];
lights[256] = 0xff;
lights[257] = 0xff;
lights[258] = 0xff;
lights[259] = 0xff;
for (idx, rgb) in dashboard_lights.iter().enumerate() {
lights[(idx + 1) * 4 + 0] = 0xe0 + DASHBOARD_BRIGHTESS;
lights[(idx + 1) * 4 + 1] = (I16F16::from(rgb.r) * LIGHT_SCALE).saturating_as();
lights[(idx + 1) * 4 + 2] = (I16F16::from(rgb.b) * LIGHT_SCALE).saturating_as();
lights[(idx + 1) * 4 + 3] = (I16F16::from(rgb.g) * LIGHT_SCALE).saturating_as();
}
for (idx, rgb) in body_lights.iter().enumerate() {
lights[(idx + 4) * 4 + 0] = 0xe0 + BODY_BRIGHTNESS;
lights[(idx + 4) * 4 + 1] = (I16F16::from(rgb.b) * LIGHT_SCALE).saturating_as();
lights[(idx + 4) * 4 + 2] = (I16F16::from(rgb.g) * LIGHT_SCALE).saturating_as();
lights[(idx + 4) * 4 + 3] = (I16F16::from(rgb.r) * LIGHT_SCALE).saturating_as();
}
let mut spi = self.spi.borrow_mut();
spi.write(lights.as_slice());
}
}
#[entry]
fn main() -> ! {
{
use core::mem::MaybeUninit;
const HEAP_SIZE: usize = 8096;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}
let mut pac = Peripherals::take().unwrap();
let core = CorePeripherals::take().unwrap();
let sio = Sio::new(pac.SIO);
let mut watchdog = Watchdog::new(pac.WATCHDOG);
let pins = Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
let clocks = init_clocks_and_plls(
12_000_000u32,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
let mut spi_clk = pins.gpio10.into_function();
let mut spi_sdo = pins.gpio11.into_function();
let spi = Spi::<_, _, _, 8>::new(pac.SPI1, (spi_sdo, spi_clk));
let mut spi = spi.init(
&mut pac.RESETS,
clocks.peripheral_clock.freq(),
1_u32.MHz(),
embedded_hal::spi::MODE_1,
);
let left_blinker_button = pins.gpio16.into_pull_up_input();
let right_blinker_button = pins.gpio17.into_pull_up_input();
let previous_animation_button = pins.gpio27.into_pull_up_input();
let next_animation_button = pins.gpio26.into_pull_up_input();
let brake_sensor = pins.gpio18.into_pull_up_input();
let mut led_pin = pins.led.into_push_pull_output();
let ui = BikeUI::new(
spi,
left_blinker_button,
right_blinker_button,
previous_animation_button,
next_animation_button,
brake_sensor,
);
let mut app = App::new(Box::new(ui));
led_pin.set_high();
let mut time = Instant::default();
let delay_ms = 1000 / (FPS as u32);
loop {
app.tick(time);
delay.delay_ms(delay_ms);
time = time + Instant(delay_ms.into());
}
}

View File

@ -1,10 +0,0 @@
[package]
name = "lights-core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
az = { version = "1" }
fixed = { version = "1" }

View File

@ -1,481 +0,0 @@
#![no_std]
extern crate alloc;
use alloc::boxed::Box;
use az::*;
use core::{
clone::Clone,
cmp::PartialEq,
default::Default,
ops::{Add, Sub},
option::Option,
};
use fixed::types::{I48F16, I8F8, U128F0, U16F0};
mod patterns;
pub use patterns::*;
mod types;
pub use types::{BodyPattern, DashboardPattern, RGB};
fn calculate_frames(starting_time: U128F0, now: U128F0) -> U16F0 {
let frames_128 = (now - starting_time) / U128F0::from(FPS);
(frames_128 % U128F0::from(U16F0::MAX)).cast()
}
fn calculate_slope(start: I8F8, end: I8F8, frames: U16F0) -> I8F8 {
let slope_i16f16 = (I48F16::from(end) - I48F16::from(start)) / I48F16::from(frames);
slope_i16f16.saturating_as()
}
fn linear_ease(value: I8F8, frames: U16F0, slope: I8F8) -> I8F8 {
let value_i16f16 = I48F16::from(value) + I48F16::from(frames) * I48F16::from(slope);
value_i16f16.saturating_as()
}
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
pub struct Instant(pub U128F0);
impl Default for Instant {
fn default() -> Self {
Self(U128F0::from(0 as u8))
}
}
impl Add for Instant {
type Output = Self;
fn add(self, r: Self) -> Self::Output {
Self(self.0 + r.0)
}
}
impl Sub for Instant {
type Output = Self;
fn sub(self, r: Self) -> Self::Output {
Self(self.0 - r.0)
}
}
pub const FPS: u8 = 30;
pub trait UI {
fn check_event(&mut self, current_time: Instant) -> Option<Event>;
fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern);
}
pub trait Animation {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern);
}
/*
pub struct DefaultAnimation {}
impl Animation for DefaultAnimation {
fn tick(&mut self, _: Instant) -> (DashboardPattern, BodyPattern) {
(WATER_DASHBOARD, WATER_BODY)
}
}
*/
pub struct Fade {
starting_dashboard: DashboardPattern,
starting_lights: BodyPattern,
start_time: Instant,
dashboard_slope: [RGB<I8F8>; 3],
body_slope: [RGB<I8F8>; 60],
frames: U16F0,
}
impl Fade {
fn new(
dashboard: DashboardPattern,
lights: BodyPattern,
ending_dashboard: DashboardPattern,
ending_lights: BodyPattern,
frames: U16F0,
time: Instant,
) -> Self {
let mut dashboard_slope = [Default::default(); 3];
let mut body_slope = [Default::default(); 60];
for i in 0..3 {
let slope = RGB {
r: calculate_slope(dashboard[i].r, ending_dashboard[i].r, frames),
g: calculate_slope(dashboard[i].g, ending_dashboard[i].g, frames),
b: calculate_slope(dashboard[i].b, ending_dashboard[i].b, frames),
};
dashboard_slope[i] = slope;
}
for i in 0..60 {
let slope = RGB {
r: calculate_slope(lights[i].r, ending_lights[i].r, frames),
g: calculate_slope(lights[i].g, ending_lights[i].g, frames),
b: calculate_slope(lights[i].b, ending_lights[i].b, frames),
};
body_slope[i] = slope;
}
Self {
starting_dashboard: dashboard,
starting_lights: lights,
start_time: time,
dashboard_slope,
body_slope,
frames,
}
}
}
impl Animation for Fade {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let mut frames = calculate_frames(self.start_time.0, time.0);
if frames > self.frames {
frames = self.frames
}
let mut dashboard_pattern: DashboardPattern = OFF_DASHBOARD;
let mut body_pattern: BodyPattern = OFF_BODY;
for i in 0..3 {
dashboard_pattern[i].r = linear_ease(
self.starting_dashboard[i].r,
frames,
self.dashboard_slope[i].r,
);
dashboard_pattern[i].g = linear_ease(
self.starting_dashboard[i].g,
frames,
self.dashboard_slope[i].g,
);
dashboard_pattern[i].b = linear_ease(
self.starting_dashboard[i].b,
frames,
self.dashboard_slope[i].b,
);
}
for i in 0..60 {
body_pattern[i].r =
linear_ease(self.starting_lights[i].r, frames, self.body_slope[i].r);
body_pattern[i].g =
linear_ease(self.starting_lights[i].g, frames, self.body_slope[i].g);
body_pattern[i].b =
linear_ease(self.starting_lights[i].b, frames, self.body_slope[i].b);
}
(dashboard_pattern, body_pattern)
}
}
#[derive(Debug)]
pub enum FadeDirection {
Transition,
FadeIn,
FadeOut,
}
pub enum BlinkerDirection {
Left,
Right,
}
pub struct Blinker {
transition: Fade,
fade_in: Fade,
fade_out: Fade,
direction: FadeDirection,
start_time: Instant,
frames: U16F0,
}
impl Blinker {
fn new(
starting_dashboard: DashboardPattern,
starting_body: BodyPattern,
direction: BlinkerDirection,
time: Instant,
) -> Self {
let mut ending_dashboard = OFF_DASHBOARD.clone();
match direction {
BlinkerDirection::Left => {
ending_dashboard[0].r = LEFT_BLINKER_DASHBOARD[0].r;
ending_dashboard[0].g = LEFT_BLINKER_DASHBOARD[0].g;
ending_dashboard[0].b = LEFT_BLINKER_DASHBOARD[0].b;
}
BlinkerDirection::Right => {
ending_dashboard[2].r = RIGHT_BLINKER_DASHBOARD[2].r;
ending_dashboard[2].g = RIGHT_BLINKER_DASHBOARD[2].g;
ending_dashboard[2].b = RIGHT_BLINKER_DASHBOARD[2].b;
}
}
let mut ending_body = OFF_BODY.clone();
match direction {
BlinkerDirection::Left => {
for i in 0..30 {
ending_body[i].r = LEFT_BLINKER_BODY[i].r;
ending_body[i].g = LEFT_BLINKER_BODY[i].g;
ending_body[i].b = LEFT_BLINKER_BODY[i].b;
}
}
BlinkerDirection::Right => {
for i in 30..60 {
ending_body[i].r = RIGHT_BLINKER_BODY[i].r;
ending_body[i].g = RIGHT_BLINKER_BODY[i].g;
ending_body[i].b = RIGHT_BLINKER_BODY[i].b;
}
}
}
Blinker {
transition: Fade::new(
starting_dashboard.clone(),
starting_body.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_in: Fade::new(
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_out: Fade::new(
ending_dashboard.clone(),
ending_body.clone(),
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
BLINKER_FRAMES,
time,
),
direction: FadeDirection::Transition,
start_time: time,
frames: BLINKER_FRAMES,
}
}
}
impl Animation for Blinker {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let frames = calculate_frames(self.start_time.0, time.0);
if frames > self.frames {
match self.direction {
FadeDirection::Transition => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeIn => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeOut => {
self.direction = FadeDirection::FadeIn;
self.fade_in.start_time = time;
}
}
self.start_time = time;
}
match self.direction {
FadeDirection::Transition => self.transition.tick(time),
FadeDirection::FadeIn => self.fade_in.tick(time),
FadeDirection::FadeOut => self.fade_out.tick(time),
}
}
}
#[derive(Clone, Debug)]
pub enum Event {
Brake,
BrakeRelease,
LeftBlinker,
NextPattern,
PreviousPattern,
RightBlinker,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Pattern {
Water,
GayPride,
TransPride,
}
impl Pattern {
fn previous(&self) -> Pattern {
match self {
Pattern::Water => Pattern::TransPride,
Pattern::GayPride => Pattern::Water,
Pattern::TransPride => Pattern::GayPride,
}
}
fn next(&self) -> Pattern {
match self {
Pattern::Water => Pattern::GayPride,
Pattern::GayPride => Pattern::TransPride,
Pattern::TransPride => Pattern::Water,
}
}
fn dashboard(&self) -> DashboardPattern {
match self {
Pattern::Water => WATER_DASHBOARD,
Pattern::GayPride => PRIDE_DASHBOARD,
Pattern::TransPride => TRANS_PRIDE_DASHBOARD,
}
}
fn body(&self) -> BodyPattern {
match self {
Pattern::Water => OFF_BODY,
Pattern::GayPride => PRIDE_BODY,
Pattern::TransPride => TRANS_PRIDE_BODY,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum State {
Pattern(Pattern),
Brake,
LeftBlinker,
RightBlinker,
BrakeLeftBlinker,
BrakeRightBlinker,
}
pub struct App {
ui: Box<dyn UI>,
state: State,
home_pattern: Pattern,
current_animation: Box<dyn Animation>,
dashboard_lights: DashboardPattern,
lights: BodyPattern,
}
impl App {
pub fn new(ui: Box<dyn UI>) -> Self {
let pattern = Pattern::Water;
Self {
ui,
state: State::Pattern(pattern),
home_pattern: pattern,
current_animation: Box::new(Fade::new(
OFF_DASHBOARD,
OFF_BODY,
pattern.dashboard(),
pattern.body(),
DEFAULT_FRAMES,
Instant((0 as u32).into()),
)),
dashboard_lights: OFF_DASHBOARD,
lights: OFF_BODY,
}
}
fn update_animation(&mut self, time: Instant) {
match self.state {
State::Pattern(ref pattern) => {
self.current_animation = Box::new(Fade::new(
self.dashboard_lights.clone(),
self.lights.clone(),
pattern.dashboard(),
pattern.body(),
DEFAULT_FRAMES,
time,
))
}
State::Brake => {
self.current_animation = Box::new(Fade::new(
self.dashboard_lights.clone(),
self.lights.clone(),
BRAKES_DASHBOARD,
BRAKES_BODY,
BRAKES_FRAMES,
time,
));
}
State::LeftBlinker => {
self.current_animation = Box::new(Blinker::new(
self.dashboard_lights.clone(),
self.lights.clone(),
BlinkerDirection::Left,
time,
));
}
State::RightBlinker => {
self.current_animation = Box::new(Blinker::new(
self.dashboard_lights.clone(),
self.lights.clone(),
BlinkerDirection::Right,
time,
));
}
State::BrakeLeftBlinker => (),
State::BrakeRightBlinker => (),
}
}
fn update_state(&mut self, event: Event) {
match event {
Event::Brake => {
if self.state == State::Brake {
self.state = State::Pattern(self.home_pattern);
} else {
self.state = State::Brake;
}
}
Event::BrakeRelease => self.state = State::Pattern(self.home_pattern),
Event::LeftBlinker => match self.state {
State::Brake => self.state = State::BrakeLeftBlinker,
State::BrakeLeftBlinker => self.state = State::Brake,
State::LeftBlinker => self.state = State::Pattern(self.home_pattern),
_ => self.state = State::LeftBlinker,
},
Event::NextPattern => match self.state {
State::Pattern(ref pattern) => {
self.home_pattern = pattern.next();
self.state = State::Pattern(self.home_pattern);
}
_ => (),
},
Event::PreviousPattern => match self.state {
State::Pattern(ref pattern) => {
self.home_pattern = pattern.previous();
self.state = State::Pattern(self.home_pattern);
}
_ => (),
},
Event::RightBlinker => match self.state {
State::Brake => self.state = State::BrakeRightBlinker,
State::BrakeRightBlinker => self.state = State::Brake,
State::RightBlinker => self.state = State::Pattern(self.home_pattern),
_ => self.state = State::RightBlinker,
},
}
}
pub fn tick(&mut self, time: Instant) {
match self.ui.check_event(time) {
Some(event) => {
self.update_state(event);
self.update_animation(time);
}
None => {}
};
let (dashboard, lights) = self.current_animation.tick(time);
self.dashboard_lights = dashboard.clone();
self.lights = lights.clone();
self.ui.update_lights(dashboard, lights);
}
}

View File

@ -1,333 +0,0 @@
use crate::{BodyPattern, DashboardPattern, RGB};
use fixed::types::{I8F8, U16F0};
pub const RGB_OFF: RGB<I8F8> = RGB {
r: I8F8::lit("0"),
g: I8F8::lit("0"),
b: I8F8::lit("0"),
};
pub const RGB_WHITE: RGB<I8F8> = RGB {
r: I8F8::lit("1"),
g: I8F8::lit("1"),
b: I8F8::lit("1"),
};
pub const BRAKES_RED: RGB<I8F8> = RGB {
r: I8F8::lit("1"),
g: I8F8::lit("0"),
b: I8F8::lit("0"),
};
pub const BLINKER_AMBER: RGB<I8F8> = RGB {
r: I8F8::lit("1"),
g: I8F8::lit("0.15"),
b: I8F8::lit("0"),
};
pub const PRIDE_RED: RGB<I8F8> = RGB {
r: I8F8::lit("0.95"),
g: I8F8::lit("0.00"),
b: I8F8::lit("0.00"),
};
pub const PRIDE_ORANGE: RGB<I8F8> = RGB {
r: I8F8::lit("1.0"),
g: I8F8::lit("0.25"),
b: I8F8::lit("0"),
};
pub const PRIDE_YELLOW: RGB<I8F8> = RGB {
r: I8F8::lit("1.0"),
g: I8F8::lit("0.85"),
b: I8F8::lit("0"),
};
pub const PRIDE_GREEN: RGB<I8F8> = RGB {
r: I8F8::lit("0"),
g: I8F8::lit("0.95"),
b: I8F8::lit("0.05"),
};
pub const PRIDE_INDIGO: RGB<I8F8> = RGB {
r: I8F8::lit("0.04"),
g: I8F8::lit("0.15"),
b: I8F8::lit("0.55"),
};
pub const PRIDE_VIOLET: RGB<I8F8> = RGB {
r: I8F8::lit("0.75"),
g: I8F8::lit("0.0"),
b: I8F8::lit("0.80"),
};
pub const TRANS_BLUE: RGB<I8F8> = RGB {
r: I8F8::lit("0.06"),
g: I8F8::lit("0.41"),
b: I8F8::lit("0.98"),
};
pub const TRANS_PINK: RGB<I8F8> = RGB {
r: I8F8::lit("0.96"),
g: I8F8::lit("0.16"),
b: I8F8::lit("0.32"),
};
pub const WATER_1: RGB<I8F8> = RGB {
r: I8F8::lit("0.0"),
g: I8F8::lit("0.0"),
b: I8F8::lit("0.75"),
};
pub const WATER_2: RGB<I8F8> = RGB {
r: I8F8::lit("0.8"),
g: I8F8::lit("0.8"),
b: I8F8::lit("0.8"),
};
pub const WATER_3: RGB<I8F8> = RGB {
r: I8F8::lit("0.00"),
g: I8F8::lit("0.75"),
b: I8F8::lit("0.75"),
};
pub const OFF_DASHBOARD: DashboardPattern = [RGB_OFF; 3];
pub const OFF_BODY: BodyPattern = [RGB_OFF; 60];
pub const DEFAULT_FRAMES: U16F0 = U16F0::lit("30");
pub const WATER_DASHBOARD: DashboardPattern = [WATER_1, WATER_2, WATER_3];
pub const WATER_BODY: BodyPattern = [RGB_OFF; 60];
pub const PRIDE_DASHBOARD: DashboardPattern = [PRIDE_RED, PRIDE_GREEN, PRIDE_INDIGO];
pub const PRIDE_BODY: BodyPattern = [
// Left Side
// Red
PRIDE_RED,
PRIDE_RED,
PRIDE_RED,
PRIDE_RED,
PRIDE_RED,
// Orange
PRIDE_ORANGE,
PRIDE_ORANGE,
PRIDE_ORANGE,
PRIDE_ORANGE,
PRIDE_ORANGE,
// Yellow
PRIDE_YELLOW,
PRIDE_YELLOW,
PRIDE_YELLOW,
PRIDE_YELLOW,
PRIDE_YELLOW,
// Green
PRIDE_GREEN,
PRIDE_GREEN,
PRIDE_GREEN,
PRIDE_GREEN,
PRIDE_GREEN,
// Indigo
PRIDE_INDIGO,
PRIDE_INDIGO,
PRIDE_INDIGO,
PRIDE_INDIGO,
PRIDE_INDIGO,
// Violet
PRIDE_VIOLET,
PRIDE_VIOLET,
PRIDE_VIOLET,
PRIDE_VIOLET,
PRIDE_VIOLET,
// Right Side
// Violet
PRIDE_VIOLET,
PRIDE_VIOLET,
PRIDE_VIOLET,
PRIDE_VIOLET,
PRIDE_VIOLET,
// Indigo
PRIDE_INDIGO,
PRIDE_INDIGO,
PRIDE_INDIGO,
PRIDE_INDIGO,
PRIDE_INDIGO,
// Green
PRIDE_GREEN,
PRIDE_GREEN,
PRIDE_GREEN,
PRIDE_GREEN,
PRIDE_GREEN,
// Yellow
PRIDE_YELLOW,
PRIDE_YELLOW,
PRIDE_YELLOW,
PRIDE_YELLOW,
PRIDE_YELLOW,
// Orange
PRIDE_ORANGE,
PRIDE_ORANGE,
PRIDE_ORANGE,
PRIDE_ORANGE,
PRIDE_ORANGE,
// Red
PRIDE_RED,
PRIDE_RED,
PRIDE_RED,
PRIDE_RED,
PRIDE_RED,
];
pub const TRANS_PRIDE_DASHBOARD: DashboardPattern = [TRANS_BLUE, RGB_WHITE, TRANS_PINK];
pub const TRANS_PRIDE_BODY: BodyPattern = [
// Left Side
TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_PINK, TRANS_PINK,
TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, RGB_WHITE, RGB_WHITE, RGB_WHITE, RGB_WHITE,
RGB_WHITE, RGB_WHITE, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK,
TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE,
// Right side
TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_PINK, TRANS_PINK,
TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, RGB_WHITE, RGB_WHITE, RGB_WHITE, RGB_WHITE,
RGB_WHITE, RGB_WHITE, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK, TRANS_PINK,
TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE, TRANS_BLUE,
];
pub const BRAKES_FRAMES: U16F0 = U16F0::lit("15");
pub const BRAKES_DASHBOARD: DashboardPattern = [BRAKES_RED; 3];
pub const BRAKES_BODY: BodyPattern = [BRAKES_RED; 60];
pub const BLINKER_FRAMES: U16F0 = U16F0::lit("10");
pub const LEFT_BLINKER_DASHBOARD: DashboardPattern = [BLINKER_AMBER, RGB_OFF, RGB_OFF];
pub const LEFT_BLINKER_BODY: BodyPattern = [
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
];
pub const RIGHT_BLINKER_DASHBOARD: DashboardPattern = [RGB_OFF, RGB_OFF, BLINKER_AMBER];
pub const RIGHT_BLINKER_BODY: BodyPattern = [
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
RGB_OFF,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
BLINKER_AMBER,
];

View File

@ -1,17 +0,0 @@
use core::default::Default;
use fixed::types::I8F8;
#[derive(Clone, Copy, Default, Debug)]
pub struct RGB<T> {
pub r: T,
pub g: T,
pub b: T,
}
const DASHBOARD_LIGHT_COUNT: usize = 3;
pub type DashboardPattern = [RGB<I8F8>; DASHBOARD_LIGHT_COUNT];
const BODY_LIGHT_COUNT: usize = 60;
pub type BodyPattern = [RGB<I8F8>; BODY_LIGHT_COUNT];

View File

@ -1,16 +0,0 @@
[package]
name = "simulator"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] }
cairo-rs = { version = "0.18" }
fixed = { version = "1" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] }
lights-core = { path = "../core" }
pango = { version = "*" }

View File

@ -1,288 +0,0 @@
use adw::prelude::*;
use fixed::types::{I8F8, U128F0};
use glib::{Object, Sender};
use gtk::subclass::prelude::*;
use lights_core::{
App, BodyPattern, DashboardPattern, Event, Instant, FPS, OFF_BODY, OFF_DASHBOARD, RGB, UI,
};
use std::{
cell::RefCell,
env,
rc::Rc,
sync::mpsc::{Receiver, TryRecvError},
};
const WIDTH: i32 = 640;
const HEIGHT: i32 = 480;
pub struct Update {
dashboard: DashboardPattern,
lights: BodyPattern,
}
pub struct DashboardLightsPrivate {
lights: Rc<RefCell<DashboardPattern>>,
}
#[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(OFF_DASHBOARD)),
}
}
}
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(
lights[i].r.into(),
lights[i].g.into(),
lights[i].b.into(),
);
context.rectangle(start + 100. * i as f64, 10., 80., 80.);
let _ = context.fill();
}
}
});
s
}
pub fn set_lights(&self, lights: DashboardPattern) {
*self.imp().lights.borrow_mut() = lights;
self.queue_draw();
}
}
pub struct BikeLightsPrivate {
lights: Rc<RefCell<BodyPattern>>,
}
#[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(OFF_BODY)),
}
}
}
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(640);
let center = WIDTH as f64 / 2.;
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.into(),
lights[i].g.into(),
lights[i].b.into(),
);
context.rectangle(center - 45., 5. + 20. * i as f64, 15., 15.);
let _ = context.fill();
}
for i in 0..30 {
context.set_source_rgb(
lights[i + 30].r.into(),
lights[i + 30].g.into(),
lights[i + 30].b.into(),
);
context.rectangle(center + 15., 5. + 20. * (30. - (i + 1) as f64), 15., 15.);
let _ = context.fill();
}
}
});
s
}
pub fn set_lights(&self, lights: [RGB<I8F8>; 60]) {
*self.imp().lights.borrow_mut() = lights;
self.queue_draw();
}
}
struct GTKUI {
tx: Sender<Update>,
rx: Receiver<Event>,
}
impl UI for GTKUI {
fn check_event(&mut self, _: Instant) -> Option<Event> {
match self.rx.try_recv() {
Ok(event) => Some(event),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => None,
}
}
fn update_lights(&self, dashboard_lights: DashboardPattern, lights: BodyPattern) {
self.tx
.send(Update {
dashboard: dashboard_lights,
lights,
})
.unwrap();
}
}
fn main() {
let adw_app = adw::Application::builder()
.application_id("com.luminescent-dreams.bike-light-simulator")
.build();
adw_app.connect_activate(move |adw_app| {
let (update_tx, update_rx) =
gtk::glib::MainContext::channel::<Update>(gtk::glib::Priority::DEFAULT);
let (event_tx, event_rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let mut bike_app = App::new(Box::new(GTKUI {
tx: update_tx,
rx: event_rx,
}));
loop {
bike_app.tick(Instant(U128F0::from(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis(),
)));
std::thread::sleep(std::time::Duration::from_millis(1000 / (FPS as u64)));
}
});
let window = adw::ApplicationWindow::builder()
.application(adw_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 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);
}
});
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();
previous_pattern.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send(Event::PreviousPattern);
}
});
next_pattern.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send(Event::NextPattern);
}
});
pattern_controls.append(&previous_pattern);
pattern_controls.append(&next_pattern);
layout.append(&pattern_controls);
layout.append(&dashboard_lights);
layout.append(&bike_lights);
update_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.present();
});
let args: Vec<String> = env::args().collect();
ApplicationExtManual::run_with_args(&adw_app, &args);
}

View File

@ -45,7 +45,6 @@
pkgs.udev
pkgs.wasm-pack
typeshare.packages."x86_64-linux".default
pkgs.nodePackages_latest.typescript-language-server
];
LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
ENV = "dev";

View File

@ -1,13 +0,0 @@
[package]
name = "gm-dash"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pipewire = "0.8.0"
serde = { version = "1.0.209", features = ["alloc", "derive"] }
serde_json = "1.0.127"
tokio = { version = "1.39.3", features = ["full"] }
warp = "0.3.7"

View File

@ -1,25 +0,0 @@
use pipewire::{context::Context, main_loop::MainLoop};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mainloop = MainLoop::new(None)?;
let context = Context::new(&mainloop)?;
let core = context.connect(None)?;
let registry = core.get_registry()?;
let _listener = registry
.add_listener_local()
.global(|global| {
if global.props.and_then(|p| p.get("media.class")) == Some("Audio/Sink"){
println!(
"\t{:?} {:?}",
global.props.and_then(|p| p.get("node.description")),
global.props.and_then(|p| p.get("media.class"))
);
}
})
.register();
mainloop.run();
Ok(())
}

View File

@ -1,109 +0,0 @@
use pipewire::{context::Context, main_loop::MainLoop};
use std::{
net::{Ipv6Addr, SocketAddrV6},
sync::{Arc, RwLock},
};
use tokio::task::spawn_blocking;
use warp::{serve, Filter};
struct State_ {
device_list: Vec<String>,
}
#[derive(Clone)]
struct State {
internal: Arc<RwLock<State_>>,
}
impl State {
fn new() -> State {
let internal = State_ {
device_list: vec![],
};
State {
internal: Arc::new(RwLock::new(internal)),
}
}
fn add_audio(&self, device: String) {
let mut st = self.internal.write().unwrap();
(*st).device_list.push(device);
}
fn audio_devices(&self) -> Vec<String> {
let st = self.internal.read().unwrap();
(*st).device_list.clone()
}
}
impl Default for State {
fn default() -> State {
State::new()
}
}
async fn server_main(state: State) {
let localhost: Ipv6Addr = "::1".parse().unwrap();
let server_addr = SocketAddrV6::new(localhost, 3001, 0, 0);
let root = warp::path!().map(|| "ok".to_string());
let list_output_devices = warp::path!("output_devices").map({
let state = state.clone();
move || {
let devices = state.audio_devices();
serde_json::to_string(&devices).unwrap()
}
});
let routes = root.or(list_output_devices);
serve(routes).run(server_addr).await;
}
fn handle_add_audio_device(state: State, props: &pipewire::spa::utils::dict::DictRef)
{
if props.get("media.class") == Some("Audio/Sink") {
if let Some(device_name) = props.get("node.description") {
state.add_audio(device_name.to_owned());
}
}
}
fn pipewire_loop(state: State) -> Result<(), Box<dyn std::error::Error>> {
let mainloop = MainLoop::new(None)?;
let context = Context::new(&mainloop)?;
let core = context.connect(None)?;
let registry = core.get_registry()?;
let _listener = registry
.add_listener_local()
.global({
let state = state.clone();
move |global_data| {
if let Some(props) = global_data.props {
handle_add_audio_device(state.clone(), props);
}
}
})
.register();
mainloop.run();
Ok(())
}
fn pipewire_main(state: State) {
pipewire_loop(state).expect("pipewire should not error");
}
#[tokio::main]
async fn main() {
let state = State::default();
spawn_blocking({
let state = state.clone();
move || pipewire_main(state)
});
server_main(state.clone()).await;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +0,0 @@
{
"name": "ui",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.105",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@ -1,5 +0,0 @@
.layout {
display: flex;
justify-content: space-between;
width: 100%;
}

View File

@ -1,77 +0,0 @@
import './Dashboard.css';
import Card from './components/Card/Card';
import Launcher from './components/Launcher/Launcher';
import Launchpad from './components/Launchpad/Launchpad';
const LightThemes = () => <Card name="Light Themes">
<Launchpad
exclusive={true}
options={[
{ title: "Dark reds" },
{ title: "Watery" },
{ title: "Sunset" },
{ title: "Darkness" },
]}
/>
</Card>
const LightSetup = () => <div> </div>
interface LightProps {
name: string,
}
const Light = ({ name }: LightProps) => <div> <p> {name} </p> </div>
const Tracks = () => <Card name="Tracks">
<Launchpad
exclusive={false}
options={[
{ title: "City BGM" },
{ title: "Chat on the streets" },
{ title: "Abandoned structure" },
{ title: "Water dripping" },
]}
/>
</Card>
interface TrackProps {
name: string,
}
const Track = ({ name }: TrackProps) => <Launcher title={name} />
const Presets = () => <Card name="Presets">
<Launchpad
exclusive={true}
options={[
{ title: "Gilcrest Falls day" },
{ title: "Gilcrest Falls night" },
{ title: "Empty colony" },
{ title: "Surk colony" },
]}
/>
</Card>
interface PresetProps {
name: string
}
// const Scene = ({ name }: PresetProps) => <Launcher title={name} activated={false} />
const SceneEditor = () => <div> </div>
const Dashboard = () => {
return (
<div className="app">
<div className="layout">
<Presets />
<div>
<LightThemes />
<Tracks />
</div>
</div>
</div>
);
}
export default Dashboard;

View File

@ -1,78 +0,0 @@
.palette {
display: flex;
}
.palette div {
width: 50px;
height: 50px;
border: 1px solid black;
border-radius: 5px;
margin: 1em;
padding: 0;
}
.palette-purple div.item-1 {
background-color: var(--purple-1);
}
.palette-purple div.item-2 {
background-color: var(--purple-2);
}
.palette-purple div.item-3 {
background-color: var(--purple-3);
}
.palette-purple div.item-4 {
background-color: var(--purple-4);
}
.palette-purple div.item-5 {
background-color: var(--purple-5);
}
.palette-blue div.item-1 {
background-color: var(--blue-1);
}
.palette-blue div.item-2 {
background-color: var(--blue-2);
}
.palette-blue div.item-3 {
background-color: var(--blue-3);
}
.palette-blue div.item-4 {
background-color: var(--blue-4);
}
.palette-blue div.item-5 {
background-color: var(--blue-5);
}
.palette-grey div.item-1 {
background-color: var(--grey-1);
}
.palette-grey div.item-2 {
background-color: var(--grey-2);
}
.palette-grey div.item-3 {
background-color: var(--grey-3);
}
.palette-grey div.item-4 {
background-color: var(--grey-4);
}
.palette-grey div.item-5 {
background-color: var(--grey-5);
}
.design_horizontal {
display: flex;
flex-flow: row;
}

View File

@ -1,57 +0,0 @@
import './Design.css'
import Launchpad from './components/Launchpad/Launchpad'
const PaletteGrey = () => <div className="palette palette-grey">
<div className="item-1" />
<div className="item-2" />
<div className="item-3" />
<div className="item-4" />
<div className="item-5" />
</div>
const PalettePurple = () => <div className="palette palette-purple">
<div className="item-1" />
<div className="item-2" />
<div className="item-3" />
<div className="item-4" />
<div className="item-5" />
</div>
const PaletteBlue = () => <div className="palette palette-blue">
<div className="item-1" />
<div className="item-2" />
<div className="item-3" />
<div className="item-4" />
<div className="item-5" />
</div>
const Launchpads = () => <div className="design_horizontal">
<Launchpad
exclusive={true}
options={[
{ title: "Grey" },
{ title: "Purple" },
{ title: "Blue" },
]}
/>
<Launchpad
exclusive={false}
flow={"vertical"}
options={[
{ title: "Grey" },
{ title: "Purple" },
{ title: "Blue" },
]}
/>
</div>
const Design = () => <div>
<PaletteGrey />
<PalettePurple />
<PaletteBlue />
<Launchpads />
</div>
export default Design;

View File

@ -1,15 +0,0 @@
.card {
border: 1px solid black;
border-radius: 5px;
margin: var(--spacer-l);
box-shadow: 4px 4px 4px 0px var(--shadow-1),
8px 8px 8px 0px var(--shadow-2);
}
.card__title {
color: var(--title-color);
}
.card__body {
}

View File

@ -1,17 +0,0 @@
import { PropsWithChildren } from 'react';
import './Card.css';
interface CardProps {
name: string,
}
const Card = ({ name, children }: PropsWithChildren<CardProps>) => (
<div className="card">
<h1 className="card__title"> {name} </h1>
<div className="card__body">
{children}
</div>
</div>
)
export default Card;

View File

@ -1,11 +0,0 @@
.activator {
border: 1px solid black;
border-radius: 5px;
margin: var(--spacer-m);
padding: var(--spacer-s);
box-shadow: var(--shadow-deep);
}
.activator_enabled {
box-shadow: var(--activator-ring);
}

View File

@ -1,20 +0,0 @@
import './Launcher.css';
import React from 'react';
export interface LauncherProps {
title: string,
icon?: string,
activated?: boolean,
onSelected?: (key: string) => void,
}
const Launcher = ({ title, activated = false, onSelected = (key) => {} }: LauncherProps) => {
const classnames = activated ? "activator activator_enabled" : "activator";
console.log("classnames ", activated, classnames);
return (
<div className={classnames} onClick={() => onSelected(title)}>
<p> {title} </p>
</div>)
}
export default Launcher;

View File

@ -1,18 +0,0 @@
.launchpad {
display: flex;
border: var(--border);
border-radius: var(--border-radius);
box-shadow: var(--shadow-depression);
margin: var(--spacer-l);
}
.launchpad_horizontal-flow {
display: flex;
flex-direction: row;
}
.launchpad_vertical-flow {
display: flex;
flex-direction: column;
}

View File

@ -1,49 +0,0 @@
import React from 'react';
import './Launchpad.css';
import Launcher, { LauncherProps } from '../Launcher/Launcher';
import classnames from 'classnames';
export interface Selectable {
onSelected?: (key: string) => void;
}
export type Flow = "horizontal" | "vertical";
interface LaunchpadProps {
exclusive: boolean;
flow?: Flow;
options: Array<LauncherProps>;
}
const exclusiveSelect = (state: { [key: string]: boolean }, targetId: string) => {
console.log("running exclusiveSelect on ", targetId);
return { [targetId]: true };
}
const multiSelect = (state: { [key: string]: boolean }, targetId: string) => {
if (state[targetId]) {
return { ...state, [targetId]: false };
} else {
return { ...state, [targetId]: true };
}
}
const Launchpad = ({ flow = "horizontal", options, exclusive }: LaunchpadProps) => {
const [selected, dispatch] = React.useReducer(exclusive ? exclusiveSelect : multiSelect, {});
let classOptions = [ "launchpad" ];
if (flow === "horizontal") {
classOptions.push("launchpad_horizontal-flow");
} else {
classOptions.push("launchpad_vertical-flow");
}
let tiedOptions = options.map(option =>
<Launcher key={option.title} title={option.title} onSelected={(key: string) => dispatch(key)} activated={selected[option.title]} />
);
return (<div className={classnames(classOptions)}> {tiedOptions} </div>)
}
export default Launchpad;

View File

@ -1,50 +0,0 @@
:root {
--purple-1: hsl(265, 50%, 25%);
--purple-2: hsl(265, 60%, 35%);
--purple-3: hsl(265, 70%, 45%);
--purple-4: hsl(265, 80%, 55%);
--purple-5: hsl(265, 90%, 60%);
--blue-1: hsl(210, 50%, 25%);
--blue-2: hsl(210, 60%, 35%);
--blue-3: hsl(210, 70%, 45%);
--blue-4: hsl(210, 80%, 55%);
--blue-5: hsl(210, 90%, 65%);
--grey-1: hsl(210, 0%, 25%);
--grey-2: hsl(210, 0%, 40%);
--grey-3: hsl(210, 0%, 55%);
--grey-4: hsl(210, 0%, 70%);
--grey-5: hsl(210, 0%, 85%);
--title-color: var(--grey-1);
--border: 1px solid var(--purple-1);
--activator-ring: 0px 0px 8px 4px var(--blue-4);
--shadow-depression: inset 1px 1px 2px 0px var(--purple-1);
--shadow-shallow: 1px 1px 2px 0px var(--purple-1);
--shadow-deep: 2px 2px 4px 0px var(--purple-1),
4px 4px 8px 0px var(--purple-2);
--border-radius: 8px;
--spacer-xs: 2px;
--spacer-s: 4px;
--spacer-m: 8px;
--spacer-l: 12px;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: var(--grey-5);
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -1,26 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
{
"name": "music-player-client",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch"
},
"author": "Savanni D'Gerinel <savanni@luminescent-dreams.com>",
"license": "GPL-3.0-or-later",
"dependencies": {
"lodash": "^4.17.21",
"systemjs": "^6.13.0"
},
"devDependencies": {
"@types/lodash": "^4.14.191",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
"style-loader": "^3.3.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"webpack": "^5.91.0",
"webpack-cli": "^5.0.1"
}
}

View File

@ -0,0 +1,13 @@
const colorGrey100 = "hsl(240 10% 95%)";
export class StyleProvider {
styles: Record<string, string> = {};
constructor() {
this.styles["backgroundColorLight"] = colorGrey100;
}
get(key: string) {
return this.styles[key]
}
}

View File

@ -1,4 +1,4 @@
import { TextField } from "./TextField";
import { TextField } from "../TextField/TextField";
export class NowPlaying extends HTMLElement {
onStop: () => void;

View File

@ -1,4 +1,15 @@
import { TextField } from "./TextField";
import { TextField } from "../TextField/TextField";
import { StyleProvider } from "../../StyleProvider";
const style = `
.track-card {
display: grid;
gap: 4px 4px;
border: 1px solid $border-color;
background-color: $background-color-light;
box-shadow: $elevation-low;
}
`;
export class TrackCard extends HTMLElement {
trackNumberContainer: TextField;
@ -6,14 +17,17 @@ export class TrackCard extends HTMLElement {
albumContainer: TextField;
artistContainer: TextField;
durationContainer: TextField;
styleProvider: StyleProvider;
static get observedAttributes() {
return ["trackId", "trackNumber", "name", "album", "artist", "duration"];
}
constructor() {
constructor(styleProvider: StyleProvider) {
super();
this.styleProvider = styleProvider
this.trackNumberContainer = document.createElement("text-field");
this.nameContainer = document.createElement("text-field");
@ -100,6 +114,11 @@ export class TrackCard extends HTMLElement {
connectedCallback() {
const container = document.createElement("data-card");
container.classList.add("track-card");
container.style.setProperty("display", "grid");
container.style.setProperty("gap", "4px 4px");
container.style.setProperty("border", "1px solid --border-color");
container.style.setProperty("background-color", this.styleProvider.get("backgroundColorLight"));
container.style.setProperty("box-shadow", "$elevation-low");
// this.appendChild(this.trackNumberContainer);
container.appendChild(this.nameContainer);

View File

@ -1,10 +1,11 @@
import * as _ from "lodash";
import { TrackInfo, getTracks, playTrack, stopPlayback } from "./client";
import { DataCard } from "./components/DataCard";
import { NowPlaying } from "./components/NowPlaying";
import { TextField } from "./components/TextField";
import { TrackCard } from "./components/TrackCard";
import { PlaylistRow } from "./components/PlaylistRow";
import { DataCard } from "./components/DataCard/DataCard";
import { NowPlaying } from "./components/NowPlaying/NowPlaying";
import { TextField } from "./components/TextField/TextField";
import { TrackCard } from "./components/TrackCard/TrackCard";
import { PlaylistRow } from "./components/PlaylistRow/PlaylistRow";
import { StyleProvider } from "./StyleProvider";
window.customElements.define("data-card", DataCard);
window.customElements.define("now-playing", NowPlaying);
@ -24,9 +25,11 @@ declare global {
const updateTrackList = (tracks: TrackInfo[]) => {
const playlist = document.querySelector(".track-list__tracks");
const styleProvider = new StyleProvider();
if (playlist) {
_.map(tracks, (info) => {
let card: TrackCard = document.createElement("track-card");
card.styleProvider = styleProvider;
let listItem: PlaylistRow = document.createElement("playlist-row");
card.trackId = info.id;

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"rootDir": "src",
"outDir": "./dist",
"lib": ["es2016", "DOM"],
"sourceMap": true,
"strict": true,
"noImplicitAny": true
},
"include": ["src"]
}

View File

@ -3,7 +3,7 @@ const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
mode: 'development',
entry: ['./src/main.ts', './styles.scss'],
entry: ['./src/main.ts'],
module: {
rules: [
{
@ -15,10 +15,6 @@ module.exports = {
test: /\.html$/i,
type: 'asset/resource',
},
{
test: /\.s[ac]ss$/i,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
],
},
resolve: {

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,43 @@
{
"name": "music-player-client",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch"
},
"author": "Savanni D'Gerinel <savanni@luminescent-dreams.com>",
"license": "GPL-3.0-or-later",
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"lodash": "^4.17.21",
"systemjs": "^6.13.0"
},
"devDependencies": {
"@types/lodash": "^4.14.191",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
"style-loader": "^3.3.1",
"ts-loader": "^9.4.2",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.97",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,9 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import Dashboard from './Dashboard';
import App from './App';
test('renders learn react link', () => {
render(<Dashboard />);
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -0,0 +1,26 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -1,27 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Dashboard from './Dashboard';
import Design from './Design';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
const router = createBrowserRouter([
{
path: "/",
element: <Dashboard />,
},
{
path: "/design",
element: <Design />,
},
]);
root.render(
<React.StrictMode>
<RouterProvider router={router} />
<App />
</React.StrictMode>
);

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,14 +1,26 @@
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"rootDir": "src",
"outDir": "./dist",
"lib": ["es2016", "DOM"],
"sourceMap": true,
"strict": true,
"noImplicitAny": true
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
"include": [
"src"
]
}

View File

@ -6,20 +6,20 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
flow = { path = "../../flow" }
gstreamer = { version = "0.19" }
id3 = { version = "1.6" }
mime_guess = { version = "2.0" }
mime = { version = "0.3" }
rusqlite = { version = "0.28" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
thiserror = { version = "1.0" }
tokio = { version = "1.24", features = ["full"] }
url = { version = "2.3" }
uuid = { version = "1", features = ["v4"] }
warp = { version = "0.3" }
urlencoding = { version = "2.1" }
result-extended = { path = "../../result-extended" }
gstreamer = { version = "0.19" }
id3 = { version = "1.6" }
mime_guess = { version = "2.0" }
mime = { version = "0.3" }
rusqlite = { version = "0.29" }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
thiserror = { version = "1.0" }
tokio = { version = "1.24", features = ["full"] }
url = { version = "2.3" }
uuid = { version = "1", features = ["v4"] }
warp = { version = "0.3" }
urlencoding = { version = "2.1" }
[target.armv7-unknown-linux-gnueabi]
linker = "arm-linux-gnueabi-gcc"

View File

@ -1,4 +1,4 @@
use flow::Flow;
use result_extended::Result;
use music_player::{
core::{ControlMsg, Core},
database::{MemoryIndex, MusicIndex},
@ -29,8 +29,8 @@ enum Response<A: Serialize> {
Fatal(String),
}
impl<A: Serialize> From<Result<A, Error>> for Response<A> {
fn from(res: Result<A, Error>) -> Self {
impl<A: Serialize> From<std::result::Result<A, Error>> for Response<A> {
fn from(res: std::result::Result<A, Error>) -> Self {
match res {
Ok(val) => Self::Success(val),
Err(err) => Self::Failure(format!("{}", err)),
@ -38,12 +38,12 @@ impl<A: Serialize> From<Result<A, Error>> for Response<A> {
}
}
impl<A: Serialize> From<Flow<A, FatalError, Error>> for Response<A> {
fn from(res: Flow<A, FatalError, Error>) -> Self {
impl<A: Serialize> From<Result<A, Error, FatalError>> for Response<A> {
fn from(res: Result<A, Error, FatalError>) -> Self {
match res {
Flow::Ok(val) => Self::Success(val),
Flow::Fatal(fatal) => Self::Fatal(format!("{}", fatal)),
Flow::Err(err) => Self::Failure(format!("{}", err)),
Result::Ok(val) => Self::Success(val),
Result::Fatal(fatal) => Self::Fatal(format!("{}", fatal)),
Result::Err(err) => Self::Failure(format!("{}", err)),
}
}
}

View File

@ -5,7 +5,7 @@ use crate::{
scanner::MusicScanner,
Error, FatalError,
};
use flow::{ok, return_error, Flow};
use result_extended::{ok, return_error, Result};
use gstreamer::{format::ClockTime, prelude::*, MessageView};
use std::{
sync::{Arc, Mutex, RwLock},
@ -52,7 +52,7 @@ impl Core {
pub fn new(
db: Arc<dyn MusicIndex>,
scanner: Arc<dyn MusicScanner>,
) -> Result<Core, FatalError> {
) -> std::result::Result<Core, FatalError> {
let db = db;
let next_scan = Arc::new(RwLock::new(Instant::now()));
@ -70,17 +70,17 @@ impl Core {
})
}
pub fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, Error> {
pub fn list_tracks<'a>(&'a self) -> Result<Vec<TrackInfo>, Error, FatalError> {
self.db.list_tracks().map_err(Error::DatabaseError)
}
pub fn play_track<'a>(&'a mut self, id: TrackId) -> Result<(), Error> {
pub fn play_track<'a>(&'a mut self, id: TrackId) -> std::result::Result<(), Error> {
self.stop_playback()?;
self.playback_controller = Some(Playback::new(id)?);
Ok(())
}
pub fn stop_playback<'a>(&'a mut self) -> Result<(), Error> {
pub fn stop_playback<'a>(&'a mut self) -> std::result::Result<(), Error> {
match self.playback_controller {
Some(ref controller) => controller.stop()?,
None => (),

View File

@ -2,7 +2,7 @@ use crate::{
media::{TrackId, TrackInfo},
FatalError,
};
use flow::{error, ok, Flow};
use result_extended::{error, ok, Result};
use rusqlite::Connection;
use serde::Serialize;
use std::collections::HashMap;
@ -21,10 +21,10 @@ pub enum DatabaseError {
}
pub trait MusicIndex: Sync + Send {
fn add_track(&self, track: TrackInfo) -> Flow<(), FatalError, DatabaseError>;
fn remove_track(&self, id: &TrackId) -> Flow<(), FatalError, DatabaseError>;
fn get_track_info(&self, id: &TrackId) -> Flow<Option<TrackInfo>, FatalError, DatabaseError>;
fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, DatabaseError>;
fn add_track(&self, track: TrackInfo) -> Result<(), DatabaseError, FatalError>;
fn remove_track(&self, id: &TrackId) -> Result<(), DatabaseError, FatalError>;
fn get_track_info(&self, id: &TrackId) -> Result<Option<TrackInfo>, DatabaseError, FatalError>;
fn list_tracks<'a>(&'a self) -> Result<Vec<TrackInfo>, DatabaseError, FatalError>;
}
pub struct MemoryIndex {
@ -40,13 +40,13 @@ impl MemoryIndex {
}
impl MusicIndex for MemoryIndex {
fn add_track(&self, info: TrackInfo) -> Flow<(), FatalError, DatabaseError> {
fn add_track(&self, info: TrackInfo) -> Result<(), DatabaseError, FatalError> {
let mut tracks = self.tracks.write().unwrap();
tracks.insert(info.id.clone(), info);
ok(())
}
fn remove_track(&self, id: &TrackId) -> Flow<(), FatalError, DatabaseError> {
fn remove_track(&self, id: &TrackId) -> Result<(), DatabaseError, FatalError> {
let mut tracks = self.tracks.write().unwrap();
tracks.remove(&id);
ok(())
@ -55,7 +55,7 @@ impl MusicIndex for MemoryIndex {
fn get_track_info<'a>(
&'a self,
id: &TrackId,
) -> Flow<Option<TrackInfo>, FatalError, DatabaseError> {
) -> Result<Option<TrackInfo>, DatabaseError, FatalError> {
let track = {
let tracks = self.tracks.read().unwrap();
tracks.get(&id).cloned()
@ -63,7 +63,7 @@ impl MusicIndex for MemoryIndex {
ok(track)
}
fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, DatabaseError> {
fn list_tracks<'a>(&'a self) -> Result<Vec<TrackInfo>, DatabaseError, FatalError> {
ok(self
.tracks
.read()
@ -92,7 +92,7 @@ pub struct Database {
}
impl Database {
pub fn new(path: PathBuf) -> Flow<Database, FatalError, DatabaseError> {
pub fn new(path: PathBuf) -> Result<Database, DatabaseError, FatalError> {
let connection = match Connection::open(path.clone()) {
Ok(connection) => connection,
Err(err) => return error(DatabaseError::UnhandledError(err.to_string())),
@ -136,7 +136,7 @@ mod test {
index.add_track(info.clone());
assert_eq!(
Flow::Ok(Some(info)),
Result::Ok(Some(info)),
index.get_track_info(&TrackId::from("track_1".to_owned()))
);
});

View File

@ -33,4 +33,4 @@ pub enum FatalError {
UnexpectedError,
}
impl flow::FatalError for FatalError {}
impl result_extended::FatalError for FatalError {}

View File

@ -1,5 +1,4 @@
use crate::{media::TrackId, Error, FatalError};
use flow::{ok, return_error, Flow};
use gstreamer::{format::ClockTime, prelude::*, MessageView, StateChangeError};
use std::{
path::PathBuf,

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "1.77.0"
targets = [ "wasm32-unknown-unknown", "thumbv6m-none-eabi" ]
targets = [ "wasm32-unknown-unknown" ]