Compare commits
26 Commits
main
...
ray-tracer
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 4d67ea4af2 | |
Savanni D'Gerinel | f347e2e47d | |
Savanni D'Gerinel | 324d37f858 | |
Savanni D'Gerinel | 59dfaf1696 | |
Savanni D'Gerinel | 2fbb468830 | |
Savanni D'Gerinel | fa4ec059f7 | |
Savanni D'Gerinel | 4f47d65ba5 | |
Savanni D'Gerinel | f15fa9dd48 | |
Savanni D'Gerinel | af75bc20c8 | |
Savanni D'Gerinel | b07925a2c3 | |
Savanni D'Gerinel | 40bfe6d74f | |
Savanni D'Gerinel | af7d8680a0 | |
Savanni D'Gerinel | 8e4f6b06e6 | |
Savanni D'Gerinel | bd899e3a2e | |
Savanni D'Gerinel | 7be0baba53 | |
Savanni D'Gerinel | a2aa132886 | |
Savanni D'Gerinel | 971206d325 | |
Savanni D'Gerinel | c2777e2a70 | |
Savanni D'Gerinel | 2569a48792 | |
Savanni D'Gerinel | 15d87fbde6 | |
Savanni D'Gerinel | 3c8536deb6 | |
Savanni D'Gerinel | d0a8be63e9 | |
Savanni D'Gerinel | 2a38ca38e1 | |
Savanni D'Gerinel | 39c947b461 | |
Savanni D'Gerinel | e23a4aacab | |
Savanni D'Gerinel | 01f3e05235 |
File diff suppressed because it is too large
Load Diff
|
@ -2,15 +2,10 @@
|
|||
resolver = "2"
|
||||
members = [
|
||||
"authdb",
|
||||
# "bike-lights/bike",
|
||||
"bike-lights/core",
|
||||
"bike-lights/simulator",
|
||||
"changeset",
|
||||
"config",
|
||||
"config-derive",
|
||||
"coordinates",
|
||||
"cyberpunk",
|
||||
"cyber-slides",
|
||||
"cyberpunk-splash",
|
||||
"dashboard",
|
||||
"emseries",
|
||||
|
@ -32,6 +27,5 @@ members = [
|
|||
"sgf",
|
||||
"timezone-testing",
|
||||
"tree",
|
||||
"visions/server",
|
||||
"gm-dash/server"
|
||||
"visions/server", "ray-tracer",
|
||||
]
|
||||
|
|
|
@ -19,8 +19,6 @@ clap = { version = "4", features = [ "derive" ] }
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
sha2 = { version = "0.10" }
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite" ] }
|
||||
# sqlformat introduced a mistaken breaking change in 0.2.7
|
||||
sqlformat = { version = "=0.2.6" }
|
||||
thiserror = { version = "1" }
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
uuid = { version = "0.4", features = [ "serde", "v4" ] }
|
||||
|
|
|
@ -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"
|
|
@ -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" }
|
|
@ -1,244 +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) {
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
$fn = 50;
|
||||
threshold = 0.1;
|
||||
half_threshold = threshold / 2;
|
||||
bevel = 0.5;
|
||||
|
||||
wire_radius = 1;
|
||||
|
||||
wall_thickness = 2;
|
||||
cutout_threshold = 1;
|
||||
|
||||
battery_length = 71;
|
||||
battery_width = 18.75;
|
||||
|
||||
cell_holder_length = battery_length + wall_thickness * 2;
|
||||
cell_holder_width = battery_width + wall_thickness * 2;
|
||||
cell_holder_height = battery_width + wall_thickness;
|
||||
|
||||
battery_contact_thickness = .6;
|
||||
// battery_contact_thickness = 1;
|
||||
battery_contact_width = 11;
|
||||
battery_contact_length = 12.8;
|
||||
battery_contact_spring_height = 10.5;
|
||||
battery_contact_flange_height = 1.9;
|
||||
|
||||
converter_width = 11.25;
|
||||
converter_length = 22.25;
|
||||
converter_height = 5;
|
||||
|
||||
|
||||
include <./common.scad>;
|
||||
|
||||
// box(20, 10, 10);
|
||||
// color("blue", 0.5) cube([10, 20, 10], center = true);
|
||||
|
||||
module cell_cradle(width, height) {
|
||||
difference() {
|
||||
translate([0, 0, -height / 2]) cube([width,
|
||||
wall_thickness,
|
||||
height],
|
||||
center = true);
|
||||
color("red", 1) translate([0, 0, 0])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(h = wall_thickness + cutout_threshold,
|
||||
r = width / 2,
|
||||
center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module cell_box() {
|
||||
union() {
|
||||
channel(cell_holder_length, cell_holder_width, cell_holder_height);
|
||||
translate([0, -battery_length / 6, wall_thickness]) cell_cradle(cell_holder_width, cell_holder_height / 2);
|
||||
translate([0, battery_length / 6, wall_thickness]) cell_cradle(cell_holder_width, cell_holder_height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
module contact_box() {
|
||||
contact_thickness = battery_contact_flange_height * .75;
|
||||
cutout_width = battery_contact_width * .8;
|
||||
// box_thickness = contact_thickness_ + wall_thickness * 2;
|
||||
// box_height = width + wall_thickness;
|
||||
|
||||
difference() {
|
||||
box(wall_thickness * 2 + contact_thickness, cell_holder_width, cell_holder_height);
|
||||
translate([0, contact_thickness, wall_thickness * 2])
|
||||
cube([battery_contact_width,
|
||||
wall_thickness * 2,
|
||||
battery_contact_length + threshold],
|
||||
center = true);
|
||||
|
||||
color("red", 1) translate([0,
|
||||
-(wall_thickness + contact_thickness + threshold) / 2,
|
||||
cell_holder_height / 2])
|
||||
cube([5, wall_thickness + threshold * 2, cell_holder_height], center = true);
|
||||
|
||||
translate([0,
|
||||
-(wall_thickness + contact_thickness + threshold) / 2 - wire_radius,
|
||||
0])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(h = cell_holder_width, r = wire_radius, center = true);
|
||||
|
||||
color("green", 1) translate([-cell_holder_width / 2, 0, cell_holder_height / 2])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(h = 5, r = contact_thickness / 2, center = true);
|
||||
|
||||
color("green", 1) translate([cell_holder_width / 2, 0, cell_holder_height / 2])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(h = 5, r = contact_thickness / 2, center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module battery_slot() {
|
||||
difference() {
|
||||
union() {
|
||||
translate([0, -cell_holder_length / 2, 0]) contact_box();
|
||||
translate([0, wall_thickness, 0]) cell_box();
|
||||
translate([0, cell_holder_length / 2 + wall_thickness * 2, 0])
|
||||
rotate([0, 0, 180])
|
||||
contact_box();
|
||||
}
|
||||
translate([cell_holder_width / 2, 1, 0]) rotate([90, 0, 0]) cylinder(h = cell_holder_length + wall_thickness * 4 + battery_contact_flange_height * 2, r = wire_radius, center = true);
|
||||
translate([-cell_holder_width / 2, 1, 0]) rotate([90, 0, 0]) cylinder(h = cell_holder_length + wall_thickness * 4 + battery_contact_flange_height * 2, r = wire_radius, center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module converter_box() {
|
||||
box_length = wall_thickness * 2 + converter_height;
|
||||
box_width = cell_holder_width * 2 - wall_thickness;
|
||||
difference() {
|
||||
box(box_length, box_width, cell_holder_height);
|
||||
|
||||
translate([cell_holder_width - wire_radius, 0, 0])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(h = box_length, r = wire_radius, center = true);
|
||||
translate([cell_holder_width - wire_radius * 2, 0, 0])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(h = wall_thickness + threshold, r = wire_radius, center = true);
|
||||
|
||||
translate([-cell_holder_width + wire_radius, 0, 0])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(h = box_length, r = wire_radius, center = true);
|
||||
translate([-cell_holder_width + wire_radius * 2, 0, 0])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(h = wall_thickness + threshold, r = wire_radius, center = true);
|
||||
|
||||
translate([0, -box_length / 2, 0])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(h = cell_holder_width * 2 + wall_thickness, r = wire_radius, center = true);
|
||||
|
||||
translate([-cell_holder_width * .75, (-box_length + wall_thickness) / 2, 0])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(h = wall_thickness * 2, r = wire_radius, center = true);
|
||||
|
||||
translate([cell_holder_width * .75, (-box_length + wall_thickness) / 2, 0])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(h = wall_thickness * 2, r = wire_radius, center = true);
|
||||
|
||||
color("red", 1) translate([-box_width / 4, -(converter_height + wall_thickness) / 2, cell_holder_height / 2])
|
||||
cube([5, wall_thickness + threshold * 2, cell_holder_height], center = true);
|
||||
color("red", 1) translate([box_width / 4, -(converter_height + wall_thickness) / 2, cell_holder_height / 2])
|
||||
cube([5, wall_thickness + threshold * 2, cell_holder_height], center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module battery_case() {
|
||||
union() {
|
||||
translate([-cell_holder_width / 2, 0, 0]) battery_slot();
|
||||
translate([cell_holder_width / 2 - wall_thickness, 0, 0]) battery_slot();
|
||||
translate([-wall_thickness / 2,
|
||||
cell_holder_length / 2 + wall_thickness * 2 + battery_contact_flange_height + wall_thickness * 2 + wall_thickness / 2,
|
||||
0])
|
||||
converter_box();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
battery_case();
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
width = 65;
|
||||
length = 75;
|
||||
height = 16;
|
||||
wall_thickness = 2;
|
||||
guide_thickness = 1;
|
||||
power_width = 21;
|
||||
output_width = 37.5;
|
||||
half_wall_thickness = wall_thickness / 2;
|
||||
standoff_thickness = 10;
|
||||
hole_diameter = 3;
|
||||
// The radius of a nut in mm. However, based on my measurements, I'm not actually sure I have this right. The short height of a nut is 7.86mm. Derive from there.
|
||||
nut_radius = 8.5 * cos(30) / 2;
|
||||
nut_height = 2.69; // mm
|
||||
screw_radius = 2;
|
||||
handlebar_radius = 15;
|
||||
clasp_thickness = 4;
|
||||
clasp_width = 35;
|
||||
circular_face_count = 48;
|
||||
|
||||
module hexagon(r, h) {
|
||||
pi = 3.1415926;
|
||||
polyhedron(
|
||||
points=[
|
||||
[r, 0, 0],
|
||||
[r * cos(60), r * sin(60), 0],
|
||||
[r * cos(120), r * sin(120), 0],
|
||||
[r * cos(180), r * sin(180), 0],
|
||||
[r * cos(240), r * sin(240), 0],
|
||||
[r * cos(300), r * sin(300), 0],
|
||||
|
||||
[r, 0, h],
|
||||
[r * cos(60), r * sin(60), h],
|
||||
[r * cos(120), r * sin(120), h],
|
||||
[r * cos(180), r * sin(180), h],
|
||||
[r * cos(240), r * sin(240), h],
|
||||
[r * cos(300), r * sin(300), h],
|
||||
],
|
||||
faces=[
|
||||
[0, 1, 2, 3, 4, 5],
|
||||
[11, 10, 9, 8, 7, 6],
|
||||
[6, 7, 1, 0],
|
||||
[7, 8, 2, 1],
|
||||
[8, 9, 3, 2],
|
||||
[9, 10, 4, 3],
|
||||
[10, 11, 5, 4],
|
||||
[11, 6, 0, 5],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Nut holders are blocks that have a hole drilled through them and a hexagonal-shaped cavity. The idea is to
|
||||
module nut_holder() {
|
||||
difference() {
|
||||
translate([-4.5, -4.5, -2]) cube([9, 9, 4]);
|
||||
union() {
|
||||
translate([0, 0, -1]) hexagon(nut_radius, 2);
|
||||
cylinder(h = 6, r = screw_radius, center = true, $fn = 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module screw_hole() {
|
||||
union() {
|
||||
translate([0, 0, 4]) cylinder(h = 2.1, r = screw_radius * 2, center = true, $fn = 24);
|
||||
cylinder(h = 6, r = screw_radius, center = true, $fn = 24);
|
||||
}
|
||||
}
|
||||
|
||||
module base() {
|
||||
cube([width, length, wall_thickness]);
|
||||
}
|
||||
|
||||
module face() {
|
||||
union() {
|
||||
cube([width, length, wall_thickness / 2]);
|
||||
translate([wall_thickness, wall_thickness, wall_thickness / 2]) cube([width-wall_thickness*2, length-wall_thickness*2, wall_thickness / 2]);
|
||||
translate([4.5 + wall_thickness, 4.5 + wall_thickness, 4]) nut_holder();
|
||||
translate([width - 4.5 - wall_thickness, 4.5 + wall_thickness, 4]) nut_holder();
|
||||
translate([width - 4.5 - wall_thickness, length - 4.5 - wall_thickness, 4]) nut_holder();
|
||||
translate([4.5 + wall_thickness, length - 4.5 - wall_thickness, 4]) nut_holder();
|
||||
}
|
||||
}
|
||||
|
||||
module wall(length) {
|
||||
cube([length, height, wall_thickness]);
|
||||
}
|
||||
|
||||
module power_wall() {
|
||||
difference() {
|
||||
wall(65);
|
||||
translate([9, 2, -.5]) cube([power_width, height, wall_thickness + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
module output_wall() {
|
||||
difference() {
|
||||
wall(65);
|
||||
translate([9, 2, -.5]) cube([output_width, height, wall_thickness + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Use hexagons as cutouts into which I can install a hex nut. This isn't quite right yet, but close.
|
||||
// hexagon(nut_radius, 1);
|
||||
|
||||
// cube([standoff_thickness, standoff_thickness, 2]);
|
||||
|
||||
/*
|
||||
difference() {
|
||||
union() {
|
||||
base();
|
||||
rotate([90, 0, 90]) wall(75);
|
||||
// translate([width - wall_thickness, 0, 0]) rotate([90, 0, 90]) wall(length);
|
||||
// rotate([90, 0, 0]) power_wall();
|
||||
// translate([0, length, 0]) rotate([90, 0, 0]) output_wall();
|
||||
// translate([wall_thickness,
|
||||
// wall_thickness,
|
||||
// wall_thickness]) standoff();
|
||||
// translate([width - wall_thickness - standoff_thickness,
|
||||
// wall_thickness,
|
||||
// wall_thickness]) standoff();
|
||||
// translate([wall_thickness,
|
||||
// length - wall_thickness - standoff_thickness,
|
||||
// wall_thickness]) standoff();
|
||||
// translate([width - wall_thickness - standoff_thickness,
|
||||
// length - wall_thickness - standoff_thickness,
|
||||
// wall_thickness]) standoff();
|
||||
}
|
||||
// translate([-half_wall_thickness, -wall_thickness - half_wall_thickness, height - half_wall_thickness]) cube([wall_thickness, length + wall_thickness * 2, wall_thickness]);
|
||||
// translate([width - half_wall_thickness, -wall_thickness - half_wall_thickness, height - half_wall_thickness]) cube([wall_thickness, length + wall_thickness * 2, wall_thickness]);
|
||||
// translate([-half_wall_thickness, -half_wall_thickness, height - half_wall_thickness]) rotate([0, 0, 270]) cube([wall_thickness, width + wall_thickness * 2, wall_thickness]);
|
||||
// translate([-half_wall_thickness, length + half_wall_thickness, height - half_wall_thickness]) rotate([0, 0, 270]) cube([wall_thickness, width + wall_thickness * 2, wall_thickness]);
|
||||
}
|
||||
*/
|
||||
|
||||
module box() {
|
||||
difference() {
|
||||
union() {
|
||||
cube([width, length, wall_thickness * 2]);
|
||||
translate([0, 0, wall_thickness]) rotate([90, 0, 90]) wall(length);
|
||||
translate([width - wall_thickness, 0, wall_thickness]) rotate([90, 0, 90]) wall(length);
|
||||
translate([0, wall_thickness, wall_thickness]) rotate([90, 0, 0]) wall(width);
|
||||
translate([0, length, wall_thickness]) rotate([90, 0, 0]) wall(width);
|
||||
}
|
||||
translate([4.5 + wall_thickness, 4.5 + wall_thickness, 4]) rotate([180, 0, 0]) screw_hole();
|
||||
translate([width - 4.5 - wall_thickness, 4.5 + wall_thickness, 4]) rotate([180, 0, 0]) screw_hole();
|
||||
translate([width - 4.5 - wall_thickness, length - 4.5 - wall_thickness, 4]) rotate([180, 0, 0]) screw_hole();
|
||||
translate([4.5 + wall_thickness, length - 4.5 - wall_thickness, 4]) rotate([180, 0, 0]) screw_hole();
|
||||
}
|
||||
}
|
||||
|
||||
module top_clasp() {
|
||||
difference() {
|
||||
union() {
|
||||
cylinder(h = clasp_width, r = handlebar_radius + clasp_thickness, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, -clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, -clasp_width / 2 + 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, clasp_width / 2 - 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([-handlebar_radius-5, -10, -clasp_width / 2 + 6]) cube([6, 20, clasp_width - 12]);
|
||||
}
|
||||
translate([-0.5, 0, 0]) cylinder(h = clasp_width+2, r = handlebar_radius + 1, center = true, $fn = circular_face_count);
|
||||
translate([-0.5, -handlebar_radius - 10, -clasp_width / 2 - 1]) cube([handlebar_radius + 10, handlebar_radius * 2 + 20, clasp_width + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
module body() {
|
||||
union() {
|
||||
box();
|
||||
translate([width / 2, length / 2, -5 - handlebar_radius]) rotate([0, 90, 90]) top_clasp();
|
||||
}
|
||||
}
|
||||
|
||||
body();
|
||||
translate([width + 10, 0, 0]) face();
|
|
@ -1,21 +0,0 @@
|
|||
handlebar_radius = 15;
|
||||
clasp_thickness = 4;
|
||||
circular_face_count = 48;
|
||||
clasp_width = 35;
|
||||
|
||||
module top_clasp() {
|
||||
difference() {
|
||||
union() {
|
||||
cylinder(h = clasp_width, r = handlebar_radius + clasp_thickness, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, -clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, -clasp_width / 2 + 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, clasp_width / 2 - 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([-handlebar_radius-5, -10, -clasp_width / 2 + 6]) cube([6, 20, clasp_width - 12]);
|
||||
}
|
||||
translate([-0.5, 0, 0]) cylinder(h = clasp_width+2, r = handlebar_radius + 1, center = true, $fn = circular_face_count);
|
||||
translate([-0.5, -handlebar_radius - 10, -clasp_width / 2 - 1]) cube([handlebar_radius + 10, handlebar_radius * 2 + 20, clasp_width + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
top_clasp();
|
|
@ -1,92 +0,0 @@
|
|||
|
||||
module hexagon(r, h) {
|
||||
cylinder(r = r, h = h, center = 2, $fn = 6);
|
||||
}
|
||||
|
||||
module pill(length, bevel) {
|
||||
hull() {
|
||||
translate([0, 0, (-length / 2) + bevel]) sphere(r = bevel);
|
||||
translate([0, 0, (length / 2) - bevel]) sphere(r = bevel);
|
||||
}
|
||||
}
|
||||
|
||||
module rounded_cube(dimensions, bevel = 0) {
|
||||
x = dimensions[0];
|
||||
y = dimensions[1];
|
||||
z = dimensions[2];
|
||||
|
||||
if (bevel > 0) {
|
||||
hull() {
|
||||
translate([-x / 2 + bevel, -y / 2 + bevel, -z / 2 + bevel]) sphere(r = bevel);
|
||||
translate([ x / 2 - bevel, -y / 2 + bevel, -z / 2 + bevel]) sphere(r = bevel);
|
||||
translate([ x / 2 - bevel, y / 2 - bevel, -z / 2 + bevel]) sphere(r = bevel);
|
||||
translate([-x / 2 + bevel, y / 2 - bevel, -z / 2 + bevel]) sphere(r = bevel);
|
||||
translate([-x / 2 + bevel, -y / 2 + bevel, z / 2 - bevel]) sphere(r = bevel);
|
||||
translate([ x / 2 - bevel, -y / 2 + bevel, z / 2 - bevel]) sphere(r = bevel);
|
||||
translate([ x / 2 - bevel, y / 2 - bevel, z / 2 - bevel]) sphere(r = bevel);
|
||||
translate([-x / 2 + bevel, y / 2 - bevel, z / 2 - bevel]) sphere(r = bevel);
|
||||
}
|
||||
} else {
|
||||
cube(dimensions, center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module box_face(dimensions, bevel = 0) {
|
||||
x = dimensions[0];
|
||||
y = dimensions[1];
|
||||
z = dimensions[2];
|
||||
|
||||
if (bevel > 0) {
|
||||
translate([0, 0, z / 2])
|
||||
hull() {
|
||||
pill(z, bevel);
|
||||
translate([x, 0, 0])
|
||||
pill(z, bevel);
|
||||
translate([x, y, 0])
|
||||
pill(z, bevel);
|
||||
translate([0, y, 0])
|
||||
pill(z, bevel);
|
||||
}
|
||||
} else {
|
||||
cube(dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
module channel(length, width, height, bevel) {
|
||||
union() {
|
||||
box_face([length, width, wall_thickness], bevel);
|
||||
|
||||
translate([0, wall_thickness - bevel, bevel])
|
||||
rotate([90, 0, 0])
|
||||
box_face([length, height, wall_thickness], bevel);
|
||||
|
||||
translate([0, width + bevel, bevel])
|
||||
rotate([90, 0, 0])
|
||||
box_face([length, height, wall_thickness], bevel);
|
||||
}
|
||||
}
|
||||
|
||||
module box(length, width, height, bevel = 0) {
|
||||
union() {
|
||||
channel(length, width, height, bevel);
|
||||
|
||||
translate([-bevel, 0, bevel])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 90, 0])
|
||||
box_face([width, height, wall_thickness], bevel);
|
||||
|
||||
translate([length - wall_thickness + bevel, 0, bevel])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 90, 0])
|
||||
box_face([width, height, wall_thickness], bevel);
|
||||
}
|
||||
}
|
||||
|
||||
module box_side_slider(length, width, height) {
|
||||
difference() {
|
||||
box_face([width - wall_thickness * 2 + 4, height, wall_thickness], bevel);
|
||||
translate([-1, -1, 1]) cube([4-threshold, height+2, 4-threshold]);
|
||||
color("red") translate([width - wall_thickness * 2 + 1, -1, 1]) cube([4-threshold, height+2, 4-threshold]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
$fn = 50;
|
||||
threshold = 0.1;
|
||||
|
||||
board_length = 92;
|
||||
board_width = 72;
|
||||
board_height = 21.5;
|
||||
wall_thickness = 4;
|
||||
bevel = 0.5;
|
||||
|
||||
hinge_radius = 2.5;
|
||||
|
||||
case_width = board_width + wall_thickness * 2;
|
||||
case_length = board_length + wall_thickness * 2;
|
||||
case_height = board_height + wall_thickness;
|
||||
|
||||
handlebar_radius = 15;
|
||||
clasp_thickness = 4;
|
||||
circular_face_count = 48;
|
||||
clasp_width = 35;
|
||||
|
||||
include <./common.scad>;
|
||||
|
||||
module top_clasp() {
|
||||
difference() {
|
||||
union() {
|
||||
cylinder(h = clasp_width, r = handlebar_radius + clasp_thickness, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, -clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, -clasp_width / 2 + 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, clasp_width / 2]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([0, 0, clasp_width / 2 - 4]) cylinder(h = 1, r = handlebar_radius + clasp_thickness + 1, center = true, $fn = circular_face_count);
|
||||
translate([-handlebar_radius-5, -10, -clasp_width / 2 + 6]) cube([6, 20, clasp_width - 12]);
|
||||
}
|
||||
translate([-0.5, 0, 0]) cylinder(h = clasp_width+2, r = handlebar_radius + 1, center = true, $fn = circular_face_count);
|
||||
translate([-0.5, -handlebar_radius - 10, -clasp_width / 2 - 1]) cube([handlebar_radius + 10, handlebar_radius * 2 + 20, clasp_width + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
module hinge(length) {
|
||||
difference() {
|
||||
union() {
|
||||
cube([hinge_radius * 2, length, hinge_radius], center = true);
|
||||
translate([0, 0, -1.5]) rotate([90, 0, 0]) cylinder(h = length, r = hinge_radius, center = true);
|
||||
}
|
||||
translate([0, threshold / 2, -1.5]) rotate([90, 0, 0]) cylinder(h = length + threshold * 2, r = 1, center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module base_case(length, width, height, bevel = 0) {
|
||||
difference() {
|
||||
union() {
|
||||
channel(length + wall_thickness / 2, width, height, bevel);
|
||||
|
||||
translate([-bevel, 0, bevel])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 90, 0])
|
||||
box_face([width, height, wall_thickness], bevel);
|
||||
|
||||
// These are the sleds at the bottom of the case that should hold the lower of the two boards down
|
||||
color("blue") translate([0, wall_thickness - 2, wall_thickness + 4]) cube([length - 8, 4, wall_thickness / 2]);
|
||||
color("blue") translate([wall_thickness - 2, wall_thickness - 4, wall_thickness + 4]) cube([4, width, wall_thickness / 2]);
|
||||
color("blue") translate([length - 25, width - wall_thickness * 3 / 2, wall_thickness + 6]) cube([16, wall_thickness, wall_thickness / 2]);
|
||||
}
|
||||
|
||||
// This makes an indent at the bottom to accomodate solder joins
|
||||
translate([wall_thickness + 2, wall_thickness + 2, wall_thickness / 2]) cube([length, width - wall_thickness * 2 - 4, wall_thickness / 2 + threshold]);
|
||||
|
||||
// This creates a cutout that lets the power plug slide in better.
|
||||
translate([wall_thickness, width - wall_thickness, wall_thickness]) cube([length, 2, 6]);
|
||||
|
||||
// These two put in the slots that should allow the fourth wall to be slotted into place.
|
||||
color("red") translate([length - 1, wall_thickness - 2, 4]) cube([2, 2, height]);
|
||||
color("red") translate([length - 1, width - wall_thickness, 4]) cube([2, 2, height]);
|
||||
}
|
||||
}
|
||||
|
||||
module main_case() {
|
||||
hinge_length = board_length / 4;
|
||||
hinge_y_offset = board_width + wall_thickness + hinge_radius;
|
||||
hinge_z_offset = board_height;
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
base_case(case_length,
|
||||
case_width,
|
||||
case_height,
|
||||
bevel);
|
||||
|
||||
translate([-bevel, 0, bevel])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 90, 0])
|
||||
box_face([case_width, case_height, wall_thickness], bevel);
|
||||
|
||||
translate([0, -hinge_radius - bevel + threshold, hinge_z_offset + bevel])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 90, 0])
|
||||
hinge(case_length / 4);
|
||||
|
||||
translate([case_length - hinge_length, -hinge_radius - bevel + threshold, hinge_z_offset + bevel])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 90, 0])
|
||||
hinge(case_length / 4);
|
||||
|
||||
translate([43, case_width, wall_thickness + 8])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 180, 0])
|
||||
linear_extrude(1)
|
||||
text("lights", size = 3);
|
||||
|
||||
translate([67, case_width, wall_thickness + 8])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 180, 0])
|
||||
linear_extrude(1)
|
||||
text("left", size = 3);
|
||||
|
||||
translate([55, case_width, wall_thickness + 8])
|
||||
rotate([90, 0, 0])
|
||||
rotate([0, 180, 0])
|
||||
linear_extrude(1)
|
||||
text("right", size = 3);
|
||||
// translate([case_length / 2, case_width / 2, -20]) rotate([0, 90, 0]) top_clasp();
|
||||
}
|
||||
|
||||
translate([case_length / 2, case_width / 2, -threshold]) hexagon(4.5, 6);
|
||||
|
||||
# translate([8.5 + wall_thickness, case_width - wall_thickness - threshold, wall_thickness])
|
||||
# cube([60, wall_thickness * 2, 7]);
|
||||
}
|
||||
}
|
||||
|
||||
module lamp() {
|
||||
union() {
|
||||
translate([0, 0, -0.5]) cube([12.9 + threshold, 8, 4], center = true);
|
||||
translate([0, 0, .88]) cube([5 + threshold, 5 + threshold, 1.56], center = true);
|
||||
/*
|
||||
translate([0, 0, -1.56]) cube([12.9, 7.6, wall_thickness], center = true);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
module button() {
|
||||
union() {
|
||||
cube([3.5 + threshold, 6.1 + threshold, 4 + threshold], center = true);
|
||||
translate([0, 0, -0.5]) cube([1.2, 7, 3 + threshold], center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module lid() {
|
||||
lid_width = case_width + hinge_radius * 2 + wall_thickness;
|
||||
hinge_length = case_length / 4;
|
||||
union() {
|
||||
difference() {
|
||||
rounded_cube([case_length,
|
||||
lid_width,
|
||||
wall_thickness],
|
||||
bevel);
|
||||
translate([0, lid_width / 5, 0.4]) lamp();
|
||||
translate([-15, lid_width / 5, 0.4]) lamp();
|
||||
translate([15, lid_width / 5, 0.4]) lamp();
|
||||
translate([-30, lid_width / 5, 0]) button();
|
||||
translate([30, lid_width / 5, 0]) button();
|
||||
|
||||
translate([0, lid_width / 5, -2]) cube([20, 7, 3], center = true);
|
||||
|
||||
color("black") translate([-2, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
|
||||
color("black") translate([-17, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
|
||||
color("black") translate([13, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
|
||||
color("black") translate([-30, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
|
||||
color("black") translate([30, lid_width / 5 - 5, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h=5, r = 1, center = true, $fn = circular_face_count);
|
||||
color("black") translate([0, 10, -2]) rotate([0, 90, 0]) cylinder(h = 62, r = 1, center = true, $fn = circular_face_count);
|
||||
|
||||
color("red") translate([-33, 21, -2]) rotate([0, 90, 0]) cylinder(h = 5, r = 1, center = true, $fn = circular_face_count);
|
||||
color("red") translate([-35, 13, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h = 18, r = 1, center = true, $fn = circular_face_count);
|
||||
color("red") translate([33, 21, -2]) rotate([0, 90, 0]) cylinder(h = 5, r = 1, center = true, $fn = circular_face_count);
|
||||
color("red") translate([35, 13, -2]) rotate([0, 0, 90]) rotate([0, 90, 0]) cylinder(h = 18, r = 1, center = true, $fn = circular_face_count);
|
||||
color("red") translate([0, 5, -2]) rotate([0, 90, 0]) cylinder(h = 70, r = 1, center = true, $fn = circular_face_count);
|
||||
}
|
||||
|
||||
translate([case_length / 2 - hinge_length / 2, lid_width / 2 - wall_thickness / 2 - 0.5, -wall_thickness / 2]) rotate([0, 0, 90]) hinge(hinge_length);
|
||||
translate([-case_length / 2 + hinge_length / 2, lid_width / 2 - wall_thickness / 2 - 0.5, -wall_thickness / 2]) rotate([0, 0, 90]) hinge(hinge_length);
|
||||
|
||||
translate([0, -lid_width / 2 + bevel, -3]) rounded_cube([20, wall_thickness / 2, 10], bevel);
|
||||
color("blue") translate([-9, -lid_width / 2 + 1.5, -6]) rotate([90, 0, 0]) rotate([0, 90, 0]) linear_extrude(18) circle(1, $fn = 3);
|
||||
color("blue") translate([-9, -lid_width / 2 + 1.5, -7]) rotate([90, 0, 0]) rotate([0, 90, 0]) linear_extrude(18) circle(1, $fn = 3);
|
||||
}
|
||||
}
|
||||
|
||||
module box_side() {
|
||||
box_side_slider(case_length, case_width, case_height);
|
||||
}
|
||||
|
||||
module case_base() {
|
||||
difference() {
|
||||
rounded_cube([case_length, case_width, wall_thickness + 2], bevel = 0.5);
|
||||
translate([wall_thickness, 0, 2]) rounded_cube([case_length + threshold, board_width + threshold, 2 + threshold]);
|
||||
|
||||
// These give a screw-hole in the center which will allow the clamp to be attached
|
||||
translate([0, 0, -1]) hexagon(4.5, 2);
|
||||
translate([0, 0, -wall_thickness / 2]) cylinder(r = 2, h = wall_thickness + threshold, center = true);
|
||||
|
||||
// and now a bit of an indentation to help the clip remain in place
|
||||
translate([0, 0, -4.5]) cube([clasp_width + threshold, clasp_width + threshold, wall_thickness], center = true);
|
||||
|
||||
// here are some grooves along the edges that can be used to piece parts together
|
||||
translate([wall_thickness / 2, case_width / 2 - wall_thickness / 2, wall_thickness / 2])
|
||||
cube([board_length + wall_thickness, wall_thickness / 2, wall_thickness / 2 + threshold], center = true);
|
||||
translate([wall_thickness / 2, -case_width / 2 + wall_thickness / 2, wall_thickness / 2])
|
||||
cube([board_length + wall_thickness, wall_thickness / 2, wall_thickness / 2 + threshold], center = true);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
include <./control_panel.scad>
|
||||
|
||||
/*
|
||||
difference() {
|
||||
color("blue") rounded_cube([5, 5, 5], bevel = 0.5);
|
||||
translate([0, 0, 1]) rounded_cube([4, 4, 4]);
|
||||
};
|
||||
*/
|
||||
|
||||
case_base();
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
include <./control_panel.scad>
|
||||
|
||||
lid();
|
||||
// lamp();
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
include <./control_panel.scad>
|
||||
|
||||
box_side();
|
|
@ -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 => WATER_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);
|
||||
}
|
||||
}
|
|
@ -1,400 +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 = [
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
|
||||
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
WATER_3,
|
||||
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
WATER_2,
|
||||
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
WATER_1,
|
||||
];
|
||||
|
||||
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,
|
||||
];
|
|
@ -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];
|
|
@ -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 = "*" }
|
|
@ -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);
|
||||
}
|
|
@ -1,151 +1,147 @@
|
|||
{
|
||||
"registry+https://github.com/rust-lang/crates.io-index#addr2line@0.24.2": "1hd1i57zxgz08j6h5qrhsnm2fi0bcqvsh389fw400xm3arz2ggnz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.0": "09r6drylvgy8vv8k20lnbvwq8gp09h7smfn6h1rxsy15pgh629si",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#addr2line@0.21.0": "1jx0k3iwyqr8klqbzk6kjvr496yd94aspis10vwsj5wy7gib4c4a",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#adler32@1.2.0": "0d7jq7jsjyhsgbhnfq5fvrlh9j0i9g1fqrl2735ibv5f75yjgqda",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#adler@1.0.2": "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.11": "04chdfkls5xmhp1d48gnjsmglbqibizs3bpbj6rsj604m10si7g8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3": "05mrpkvdgp5d20y2p989f187ry9diliijgwrs254fs9s1m1x6q4f",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.18": "0kr6lfnxvnj164j1x38g97qjlhb7akppqzvgfs0697140ixbav2w",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.6": "0yn9i8nc6mmv28ig9w3dga571q09vg9f1f650mi5z8phx42r6hli",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.2": "1w510wnixvlgimkx1zjbvlxh6xps2vjgfqgwf5a6adlbjp5rv5mj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.16": "1iayppgq4wqbfbfcqmsbwgamj0s65012sskfvyx07pxavk3gyhh9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#android-tzdata@0.1.1": "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#android_system_properties@0.1.5": "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#annotate-snippets@0.9.2": "07p8r6jzb7nqydq0kr5pllckqcdxlyld2g275v425axnzffpxbyc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.15": "09nm4qj34kiwgzczdvj14x7hgsb235g4sqsay3xsz7zqn4d5rqb4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.5": "1jy12rvgbldflnb2x7mcww9dcffw1mx22nyv6p3n7d62h0gdwizb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.1.1": "0aj22iy4pzk6mz745sfrm1ym14r0y892jhcrbs8nkj7nqx9gqdkd",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.4": "1y2pkvsrdxbcwircahb4wimans2pzmwwxad7ikdhj5lpdqdlxxsv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.8": "1cfmkza63xpn1kkz844mgjwm9miaiz4jkyczmwxzivcsypk1vv0v",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.89": "1xh1vg89n56h6nqikcmgbpmkixjds33492klrp9m96xrbmhgizc6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.5": "1dm1mdbs1x6y3m3pz0qlamgiskb50i4q859676kx0pz8r8pajr6n",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.3": "134jhzrz89labrdwxxnjxqjdg06qvaflj1wkfnmyapwyldfwcnn7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.0.2": "0j3na4b1nma39g4x7cwvj009awxckjf3z2vkwhldgka44hqj72g2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.2": "19v0fv400bmp4niqpzxnhg83vz12mmqv7l2l8vi80qcdxj0lpm8w",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.4": "11yxw02b6parn29s757z96rgiqbn8qy0fk9a3p3bhczm85dhfybh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.75": "1rmcjkim91c5mw7h9wn8nv0k6x118yz0xg0z1q18svgn42mqqrm4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-channel@1.9.0": "0dbdlkzlncbibd3ij6y6jmvjd0cmdn48ydcfdpfhw09njd93r5c1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-channel@2.3.1": "0skvwxj6ysfc6d7bhczz9a2550260g62bm5gl0nmjxxyn007id49",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-executor@1.13.1": "1v6w1dbvsmw6cs4dk4lxj5dvrikc6xi479wikwaab2qy3h09mjih",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-channel@2.1.1": "1337ywc1paw03rdlwh100kh8pa0zyp0nrlya8bpsn6zdqi5kz8qw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-executor@1.8.0": "0z7rpayidhdqs4sdzjhh26z5155c1n94fycqni9793n4zjz5xbhp",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-global-executor@2.4.1": "1762s45cc134d38rrv0hyp41hv4iv6nmx59vswid2p0il8rvdc85",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-io@2.3.4": "1s679l7x6ijh8zcxqn5pqgdiyshpy4xwklv86ldm1rhfjll04js4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-lock@3.4.0": "060vh45i809wcqyxzs5g69nqiqah7ydz0hpkcjys9258vqn4fvpz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-std@1.13.0": "059nbiyijwbndyrz0050skvlvzhds0dmnl0biwmxwbw055glfd66",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-task@4.7.1": "1pp3avr4ri2nbh7s6y9ws0397nkx1zymmcr14sq761ljarh3axcb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.83": "1p8q8gm4fv2fdka8hwy2w3f8df7p5inixqi7rlmbnky3wmysw73j",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-io@1.13.0": "1byj7lpw0ahk6k63sbc9859v68f28hpaab41dxsjj1ggjdfv9i8g",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-io@2.3.1": "0rggn074kbqxxajci1aq14b17gp75rw9l6rpbazcv9q0bc6ap5wg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-lock@2.8.0": "0asq5xdzgp3d5m82y5rg7a0k9q0g95jy6mgc7ivl334x7qlp4wi8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-lock@3.3.0": "0yxflkfw46rad4lv86f59b5z555dlfmg1riz1n8830rgi0qb8d6h",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-std@1.12.0": "0pbgxhyb97h4n0451r26njvr20ywqsbm6y1wjllnp4if82s5nmk2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-task@4.7.0": "16975vx6aqy5yf16fs9xz5vx1zq8mwkzfmykvcilc1j7b6c6xczv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.77": "1adf1jh2yg39rkpmqjqyr9xyd6849p0d95425i6imgbhx0syx069",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#atoi@2.0.0": "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#atomic-waker@1.1.2": "1h5av1lw56m0jf0fd3bchxq8a30xv0b4wv8s4zkp4s0i7mfvs18m",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#atomic-write-file@0.1.2": "0dl4x0srdwjxm3zz3fj1c7m44i3b7mjiad550fqklj1n4bfbxkgd",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#autocfg@0.1.8": "0y4vw4l4izdxq1v0rrhvmlbqvalrqrmk60v1z0dqlgnlbzkl7phd",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#autocfg@1.4.0": "09lz3by90d2hphbq56znag9v87gfpd9gb8nr82hll8z6x2nhprdc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#az@1.2.1": "0ww9k1w3al7x5qmb7f13v3s9c2pg1pdxbs8xshqy6zyrchj4qzkv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#backtrace@0.3.74": "06pfif7nwx66qf2zaanc2fcq7m64i91ki9imw9xd3bnz5hrwp0ld",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#base64@0.21.7": "0rw52yvsk75kar9wgqfwgb414kvil1gn7mqkrhn9zf1537mpsacx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#autocfg@1.1.0": "1ylp3cb47ylzabimazvbz9ms6ap784zhb6syaz6c1jqpmcmq0s6l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#backtrace@0.3.69": "0dsq23dhw4pfndkx2nsa1ml2g31idm7ss7ljxp8d57avygivg290",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#base64@0.21.5": "1y8x2xs9nszj5ix7gg4ycn5a6wy7ca74zxwqri3bdqzdjha6lqrm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#base64@0.9.3": "0hs62r35bgxslawyrn1vp9rmvrkkm76fqv0vqcwd048vs876r7a8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#base64ct@1.6.0": "0nvdba4jb8aikv60az40x2w1y96sjdq8z3yp09rwzmkhiwv1lg4c",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bindgen@0.69.5": "1240snlcfj663k04bjsg629g4wx6f83flgbjh5rzpgyagk3864r7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bit-set@0.5.3": "1wcm9vxi00ma4rcxkl3pzzjli6ihrpn9cfdi0c5b4cvga2mxs007",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bit-vec@0.6.3": "1ywqjnv60cdh1slhz67psnp422md6jdliji6alq0gmly2xm9p7rl",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bit_field@0.10.2": "0qav5rpm4hqc33vmf4vc4r0mh51yjx5vmd9zhih26n9yjs3730nw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bitflags@1.3.2": "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bitflags@2.6.0": "1pkidwzn3hnxlsl8zizh0bncgbjnw7c41cx7bby26ncbzmiznj5h",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bitflags@2.4.1": "01ryy3kd671b0ll4bhdvhsz67vwz1lz53fz504injrd7wpv64xrj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#block-buffer@0.10.4": "0w9sa2ypmrsqqvc20nhwr75wbb5cjr4kkyhpjm1z1lv2kdicfy1h",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#blocking@1.6.1": "1si99l8zp7c4zq87y35ayjgc5c9b60jb8h0k14zfcs679z2l2gvh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#build_html@2.5.0": "0p4k25yk3v0wf720wl5zcghvc9ik6l7lsh3fz86cq3g7x4nbhpi2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.16.0": "0b015qb4knwanbdlp1x48pkb4pm57b8gidbhhhxr900q2wb6fabr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.18.0": "1bp2s9wn0gjsaygv21nsbfpf854vl897ll6sqpfn3naaannv1fwl",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#blocking@1.5.1": "064i3d6b8ln34fgdw49nmx9m36bwi3r3nv8c9xhcrpf4ilz92dva",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#build_html@2.4.0": "188nibbsv33vgjjiq9cn2irsgdb75gxfipavcavnyydcwxpzw21i",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.14.0": "1v4arnv9kwk54v5d0qqpv4vyw2sgr660nk0w3apzixi1cm3yfc3z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.14.0": "1ik1ma5n3bg700skkzhx50zjk7kj7mbsphi773if17l04pn2hk9p",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#byteorder@1.5.0": "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bytes@1.7.2": "1wzs7l57iwqmrszdpr2mmqf1b1hgvpxafc30imxhnry0zfl9m3a2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cairo-rs@0.18.5": "1qjfkcq3mrh3p01nnn71dy3kn99g21xx3j8xcdvzn8ll2pq6x8lc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#bytes@1.5.0": "08w2i8ac912l8vlvkv3q51cd4gr09pwlg3sjsjffcizlrb0i5gd2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cairo-rs@0.18.3": "18d80lk853bjhx36rjaj78clzfjrmlgi01863drnmshdgxi16dpk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cairo-sys-rs@0.18.2": "0lfsxl7ylw3phbnwmz3k58j1gnqi6kc2hdc7g3bb7f4hwnl9yp38",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cc@1.1.34": "1j9dh96lpkksmfvjfiqa5nrlswm5l6lj54m5jf7i0iik8l6lgfb7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cexpr@0.6.0": "0rl77bwhs5p979ih4r0202cn5jrfsrbgrksp40lkfz5vk1x3ib3g",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cfg-expr@0.15.8": "00lgf717pmf5qd2qsxxzs815v6baqg38d6m5i6wlh235p14asryh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cc@1.0.83": "1l643zidlb5iy1dskc5ggqs4wqa29a02f44piczqc8zcnsq4y5zi",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cfg-expr@0.15.5": "1cqicd9qi8mzzgh63dw03zhbdihqfl3lbiklrkynyzkq67s5m483",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0": "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz-build@0.2.1": "03rmzd69cn7fp0fgkjr5042b3g54s2l941afjm3001ls7kqkjgj3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz@0.8.6": "0vlksnmpb6rd4h55245agnfhphnpslwnq9al3aw3is43dd3f16nm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.38": "009l8vc5p8750vn02z30mblg4pv2qhkbfizhfwmzc6vpy5nr67x2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clang-sys@1.8.1": "1x1r9yqss76z8xwpdanw313ss6fniwc1r7dzb5ycjn0ph53kj0hb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clap@4.5.20": "1s37v23gcxkjy4800qgnkxkpliz68vslpr5sgn1xar56hmnkfzxr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.20": "0m6w10l2f65h3ch0d53lql6p26xxrh20ffipra9ysjsfsjmq1g0r",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.18": "1ardb26bvcpg72q9myr7yir3a8c83gx7vxk1cccabsd9n73s1ija",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.7.2": "15zcrc2fa6ycdzaihxghf48180bnvzsivhf0fmah24bnnaf76qhl",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz@0.8.4": "0xhd3dsfs72im0sbc7w889lfy7bxgjlbvqhj5a1yvxhxwb08acg2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.31": "0f6vg67pipm8cziad2yms6a639pssnvysk1m05dd9crymmdnhb3z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clap@4.4.11": "1wj5gb2fnqls00zfahg3490bdfc36d9cwpl80qjacb5jyrqzdbxz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.4.11": "1fxdsmw1ilgswz3lg2hjlvsdyyz04k78scjirlbd7c9bc83ba5m2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.4.7": "0hk4hcxl56qwqsf4hmf7c0gr19r9fbxk0ah2bgkr36pmmaph966g",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.6.0": "1l8bragdvim7mva9flvd159dskn2bdkpl0jqrr41wnjfn8pcfbvh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cloudabi@0.0.3": "0kxcg83jlihy0phnd2g8c2c303px3l2p3pkjz357ll6llnd5pz6x",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#color_quant@1.1.0": "12q1n427h2bbmmm1mnglr57jaz2dj9apk0plcxw7nwqiai7qjyrx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.2": "1h18ph538y8yjmbpaf8li98l0ifms2xmh3rax9666c5qfjfi3zfk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#concurrent-queue@2.5.0": "0wrr3mzq2ijdkxwndhf79k952cp4zkz35ray8hvsxl96xrx1k82c",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.0": "1ix7w85kwvyybwi2jdkl3yva2r2bvdcc3ka2grjfzfgrapqimgxc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#concurrent-queue@2.4.0": "0qvk23ynj311adb4z7v89wk3bs65blps4n24q8rgl23vjk6lhq6i",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#const-oid@0.9.6": "1y0jnqaq7p2wvspnx7qj76m7hjcqpz73qzvr9l2p9n2s51vr6if2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#convert_case@0.6.0": "1jn1pq6fp3rri88zyw6jlhwwgf6qiyc08d6gjv0qypgkl862n67c",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cookie-factory@0.3.3": "18mka6fk3843qq3jw1fdfvzyv05kx7kcmirfbs2vg2kbw9qzm1cq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cookie@0.17.0": "096c52jg9iq4lfcps2psncswv33fc30mmnaa2sbzzcfcw71kgyvy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cool_asserts@2.0.3": "1v18dg7ifx41k2f82j3gsnpm1fg9wk5s4zv7sf42c7pnad72b7zf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#core-foundation-sys@0.8.7": "12w8j73lazxmr1z0h98hf3z623kl8ms7g07jch7n4p8f9nwlhdkp",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#core-foundation-sys@0.8.6": "13w6sdf06r0hn7bx2b45zxsg1mm2phz34jikm6xc5qrbr6djpsh6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#core-foundation@0.9.4": "13zvbbj07yk3b61b8fhwfzhy35535a583irf23vlcg59j7h9bqci",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.14": "1q3qd9qkw94vs7n5i0y3zz2cqgzcxvdgyb54ryngwmjhfbgrg1k0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.11": "1l0gzsyy576n017g9bf0vkv5hhg9cpz1h1libxyfdlzcgbh0yhnf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crc-catalog@2.4.0": "1xg7sz82w3nxp1jfn425fvn1clvbzb3zgblmxsyqpys0dckp9lqr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.4.2": "1czp7vif73b8xslr3c9yxysmh9ws2r8824qda7j47ffs9pcnjxx9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crc@3.2.1": "0dnn23x68qakzc429s1y9k9y3g8fn5v9jwi63jcz151sngby9rk9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-deque@0.8.5": "03bp38ljx4wj6vvy4fbhx41q8f585zyqix6pncz1mkz93z08qgv1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-epoch@0.9.18": "03j2np8llwf376m3fxqx859mgp9f83hj1w34153c7a9c7i5ar0jv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-queue@0.3.11": "0d8y8y3z48r9javzj67v3p2yfswd278myz1j9vzc4sp7snslc0yz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.20": "100fksq5mm1n7zj242cclkw6yf7a4a8ix3lvpfkhxvdhbda9kv12",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.3.2": "03c8f29yx293yf43xar946xbls1g60c207m9drf8ilqhr25vsh5m",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crc@3.0.1": "1zkx87a5x06xfd6xm5956w4vmdfs0wcxpsn7iwj5jbp2rcapmv46",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-deque@0.8.4": "0la7fx9n1vbx3h23va0xmcy36hziql1pkik08s3j3asv4479ma7w",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-epoch@0.9.16": "1anr32r8px0vb65cgwbwp3zhqz69scz5dgq9bmx54w5qa59yjbrd",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-queue@0.3.9": "0lz17pgydh29w8brld8dysi1m4n5bxfpnj8w9bxk0q6xpyyzbg5r",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.17": "13y7wh993i7q71kg6wcfj65w3rlmizzrz7cqgz1l9whlgw9rcvf0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crunchy@0.2.2": "1dx9mypwd5mpfbbajm78xcrg5lirqk7934ik980mmaffg3hdm0bs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#crypto-common@0.1.6": "1cvby95a6xg7kxdz5ln3rl9xh66nz66w46mm3g56ri1z5x815yqv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#data-encoding@2.6.0": "1qnn68n4vragxaxlkqcb1r28d3hhj43wch67lm4rpxlw89wnjmp8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#data-encoding@2.5.0": "1rcbnwfmfxhlshzbn3r7srm3azqha3mn33yxyqxkzz2wpqcjm5ky",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#deflate@0.8.6": "0x6iqlayg129w63999kz97m279m0jj4x4sm6gkqlvmp73y70yxvk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#der@0.7.9": "1h4vzjfa1lczxdf8avfj9qlwh1qianqlxdy1g5rn762qnvkzhnzm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.11": "1d1ibqqnr5qdrpw8rclwrf1myn3wf0dygl04idf4j2s49ah6yaxl",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#der@0.7.8": "070bwiyr80800h31c5zd96ckkgagfjgnrrdmz3dzg2lccsd3dypz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.10": "1p4i64nkadamksa943d6gk39sl1kximz0xr69n408fvsl1q0vcwf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#digest@0.10.7": "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.7.0": "09ky8s3higkf677lmyqg30hmj66gpg7hx907s6hfvbk2a9av05r5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.8.0": "15s3j4ry943xqlac63bp81sgdk9s3yilysabzww35j9ibmnaic50",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5": "1q0alair462j21iiqwrr21iabkfnb13d6x5w95lkdg21q2xrqdlp",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.4": "0p8pyg10csc782qlwx3znr6qx46ni96m1qh597kmyrf6s3s8axa8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#dotenvy@0.15.7": "16s3n973n5aqym02692i1npb079n5mb0fwql42ikmwn8wnrrbbqs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#either@1.13.0": "1w2c1mybrd7vljyxk77y9f4w9dyjrmp3yp82mk7bcm8848fazcb0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#encoding_rs@0.8.34": "0nagpi1rjqdpvakymwmnlxzq908ncg868lml5b70n08bm82fjpdl",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#env_logger@0.10.2": "1005v71kay9kbz1d5907l0y7vh9qn2fqsp2yfgb8bjvin6m0bm2c",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#either@1.9.0": "01qy3anr7jal5lpc20791vxrw0nl6vksb5j7x56q2fycgcyy8sm2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#encoding_rs@0.8.33": "1qa5k4a0ipdrxq4xg9amms9r9pnnfn7nfh2i9m3mw0ka563b6s3j",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#env_logger@0.10.1": "1kmy9xmfjaqfvd4wkxr1f7d16ld3h9b487vqs2q9r0s8f3kg7cwm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.1": "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#errno@0.3.9": "1fi0m0493maq1jygcf1bya9cymz2pc1mqxj26bdv7yjd37v5qk2k",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#errno@0.3.8": "0ia28ylfsp36i27g1qih875cyyy4by2grf80ki8vhgh6vinf8n52",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#etcetera@0.8.0": "0hxrsn75dirbjhwgkdkh0pnpqrnq17ypyhjpjaypgax1hd91nv8k",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#event-listener-strategy@0.5.2": "18f5ri227khkayhv3ndv7yl4rnasgwksl2jhwgafcxzr7324s88g",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#event-listener-strategy@0.4.0": "1lwprdjqp2ibbxhgm9khw7s7y7k4xiqj5i5yprqiks6mnrq4v3lm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#event-listener@2.5.3": "1q4w3pndc518crld6zsqvvpy9lkzwahp2zgza9kbzmmqh9gif1h2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#event-listener@5.3.1": "1fkm6q4hjn61wl52xyqyyxai0x9w0ngrzi0wf1qsf8vhsadvwck0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#exr@1.72.0": "195iviimjnp1mdkqrq8hjrfkr0qavpp1p8pq5qvaksa30pv96zc8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.1.1": "19nyzdq3ha4g173364y2wijmd6jlyms8qx40daqkxsnl458jmh78",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.5": "1axmgzpgf12yl3x9ymdslqza765la17j17ljv6a4kc143a90y2fq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#event-listener@4.0.1": "04k7qbi5kgs36s905gxijj41kcr78xs2s6cp6vbg50254z7wvwl4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#exr@1.71.0": "1a58k179b0h8zpf1cfgc2vl60j2syg7cdgdzp9j6cgmb6lgpcal3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fastrand@1.9.0": "1gh12m56265ihdbzh46bhh0jf74i197wm51jg1cw75q7ggi96475",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.0.1": "19flpv5zbzpf0rk4x77z4zf25in0brg8l7m304d3yrf47qvwxjr5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.1": "0s5885wdsih2hqx3hsl7l8cl3666fgsgiwvglifzy229hpydmmk4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#field-offset@0.3.6": "0zq5sssaa2ckmcmxxbly8qgz3sxpb8g1lwv90sdh1z74qif2gqiq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fixed@1.28.0": "0nn85j5x8yzx10q49jdzia4yp6pnasnxpnwh0p9aqr7qkfwf1il5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#flate2@1.0.34": "1w1nf2ap4q1sq1v6v951011wcvljk449ap7q7jnnjf8hvjs8kdd1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fluent-bundle@0.15.3": "14zl0cjn361is69pb1zry4k2zzh5nzsfv0iz05wccl00x0ga5q3z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#finl_unicode@1.2.0": "1ipdx778849czik798sjbgk5yhwxqybydac18d2g9jb20dxdrkwg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#flate2@1.0.28": "03llhsh4gqdirnfxxb9g2w9n0721dyn4yjir3pz7z4vjaxb3yc26",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fluent-bundle@0.15.2": "1zbzm13rfz7fay7bps7jd4j1pdnlxmdzzfymyq2iawf9vq0wchp2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fluent-langneg@0.13.0": "152yxplc11vmxkslvmaqak9x86xnavnhdqyhrh38ym37jscd0jic",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fluent-syntax@0.11.1": "0gd3cdvsx9ymbb8hijcsc9wyf8h1pbcbpsafg4ldba56ji30qlra",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fluent@0.16.1": "0njmdpwz52yjzyp55iik9k6vrixqiy7190d98pk0rgdy0x3n6x5v",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fluent-syntax@0.11.0": "0y6ac7z7sbv51nsa6km5z8rkjj4nvqk91vlghq1ck5c3cjbyvay0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fluent@0.16.0": "19s7z0gw95qdsp9hhc00xcy11nwhnx93kknjmdvdnna435w97xk1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#flume@0.11.0": "10girdbqn77wi802pdh55lwbmymy437k7kklnvj12aaiwaflbb2m",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fnv@1.0.7": "1hc2mcqha06aibcaza94vbi81j6pr9a1bbxrxjfhc91zin8yr7iz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1": "0jxgzd04ra4imjv8jgkmdq59kj8fsz6w4zxsbmlai34h26225c00",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2": "1cgk0vyd7r45cj769jym4a6s7vwshvd0z4bqrb92q1fwibmkkwzn",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#form_urlencoded@1.2.1": "0milh8x7nl4f450s3ddhg57a3flcv6yq8hlkyk6fyr3mcb128dp1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#fuchsia-cprng@0.1.1": "1fnkqrbz7ixxzsb04bsz9p0zzazanma8znfdqjvh39n14vapfvx0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-channel@0.3.31": "040vpqpqlbk099razq8lyn74m0f161zd0rp36hciqrwcg2zibzrd",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-core@0.3.31": "0gk6yrxgi5ihfanm2y431jadrll00n5ifhnpx090c2f2q1cr1wh5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-executor@0.3.31": "17vcci6mdfzx4gbk0wx64chr2f13wwwpvyf3xd5fb1gmjzcx2a0y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-channel@0.3.29": "1jxsifvrbqzdadk0svbax71cba5d3qg3wgjq8i160mxmd1kdckgz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-core@0.3.29": "1308bpj0g36nhx2y6bl4mm6f1gnh9xyvvw2q2wpdgnb6dv3247gb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-executor@0.3.29": "1g4pjni0sw28djx6mlcfz584abm2lpifz86cmng0kkxh7mlvhkqg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-intrusive@0.5.0": "0vwm08d1pli6bdaj0i7xhk3476qlx4pll6i0w03gzdnh7lh0r4qx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-io@0.3.31": "1ikmw1yfbgvsychmsihdkwa8a1knank2d9a8dk01mbjar9w1np4y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-lite@2.3.0": "19gk4my8zhfym6gwnpdjiyv2hw8cc098skkbkhryjdaf0yspwljj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-macro@0.3.31": "0l1n7kqzwwmgiznn0ywdc5i24z72zvh9q1dwps54mimppi7f6bhn",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-sink@0.3.31": "1xyly6naq6aqm52d5rh236snm08kw8zadydwqz8bip70s6vzlxg5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-task@0.3.31": "124rv4n90f5xwfsm9qw6y99755y021cmi5dhzh253s920z77s3zr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-util@0.3.31": "10aa1ar8bgkgbr4wzxlidkqkcxf77gffyj8j7768h831pcaq784z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures@0.3.31": "0xh8ddbkm9jy8kc5gbvjp9a4b6rqqxvc8471yb2qaz5wm2qhgg35",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-io@0.3.29": "1ajsljgny3zfxwahba9byjzclrgvm1ypakca8z854k2w7cb4mwwb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-lite@1.13.0": "1kkbqhaib68nzmys2dc8j9fl2bwzf2s91jfk13lb2q3nwhfdbaa9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-lite@2.2.0": "1flj85i6xm0rjicxixmajrp6rhq8i4bnbzffmrd6h23ln8jshns4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-macro@0.3.29": "1nwd18i8kvpkdfwm045hddjli0n96zi7pn6f99zi9c74j7ym7cak",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-sink@0.3.29": "05q8jykqddxzp8nwf00wjk5m5mqi546d7i8hsxma7hiqxrw36vg3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-task@0.3.29": "1qmsss8rb5ppql4qvd4r70h9gpfcpd0bg2b3qilxrnhdkc397lgg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures-util@0.3.29": "0141rkqh0psj4h8x8lgsl1p29dhqr7z2wcixkcbs60z74kb2d5d1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#futures@0.3.29": "0dak2ilpcmyjrb1j54fzy9hlw6vd10vqljq9gd59pbrq9dqr00ns",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gdk-pixbuf-sys@0.18.0": "1xya543c4ffd2n7aiwwrdxsyc9casdbasafi6ixcknafckm3k61z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gdk-pixbuf@0.18.5": "1v7svvl0g7zybndmis5inaqqgi1mvcc6s1n8rkb31f5zn3qzbqah",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gdk-pixbuf@0.18.3": "0b68ssdyapvq3bgsna9frabbzhjkvvzz8jld4mxkphr29nvk4vs4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gdk4-sys@0.7.2": "1w7yvir565sjrrw828lss07749hfpfsr19jdjzwivkx36brl7ayv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gdk4@0.7.3": "1xiacc63p73apr033gjrb9dsk0y4yxnsljwfxbwfry41snd03nvy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.11.2": "0a7w8w0rg47nmcinnfzv443lcyb8mplwc251p1jyr5xj2yh6wzv6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.14.7": "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.15": "1mzlnrb3dgyd1fb84gvw10pyr8wdqdl4ry4sr64i1s8an66pqmn4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.11": "03q7120cc2kn7ry013i67zmjl2g9q73h1ks5z08hq5v9syz0d47y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gif@0.11.4": "01hbw3isapzpzff8l6aw55jnaqx2bcscrbwyf3rglkbbfp397p9y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gif@0.13.1": "1whrkvdg26gp1r7f95c6800y6ijqw5y0z8rgj6xihpi136dxdciz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gimli@0.31.1": "0gvqc0ramx8szv76jhfd4dms0zyamvlg4whhiz11j34hh3dqxqh7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gif@0.12.0": "0ibhjyrslfv9qm400gp4hd50v9ibva01j4ab9bwiq1aycy9jayc0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gimli@0.28.1": "0lv23wc8rxvmjia3mcxc6hj9vkqnv1bqq0h8nzjcgf71mrxx6wa2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gio-sys@0.18.1": "1lip8z35iy9d184x2qwjxlbxi64q9cpayy7v1p5y9xdsa3w6smip",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gio@0.18.4": "0wsc6mnx057s4ailacg99dwgna38dbqli5x7a6y9rdw75x9qzz6l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.16.3": "1z73bl10zmxwrv16v4f5wcky1f3z5a2v0hknca54al4k2p5ka695",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.17.10": "05p7ab2vn8962cbchi7a6hndhvw64nqk4w5kpg5z53iizsgdfrbs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.18.0": "0p5c2ayiam5bkp9wvq9f9ihwp06nqs5j801npjlwnhrl8rpwac9l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glib-macros@0.18.5": "1p5cla53fcp195zp0hkqpmnn7iwmkdswhy7xh34002bw8y7j5c0b",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glib-macros@0.18.3": "19crnw5a57w02njpbsmdqwbkncl6hw6g3mv554y8dqzcrri3jybj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glib-sys@0.18.1": "164qhsfmlzd5mhyxs8123jzbdfldwxbikfpq5cysj3lddbmy4g06",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glib@0.18.5": "1r8fw0627nmn19bgk3xpmcfngx3wkn7mcpq5a8ma3risx3valg93",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glob@0.3.1": "16zca52nglanv23q5qrwd5jinw3d3as5ylya6y1pbx47vkxvrynj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gloo-timers@0.3.0": "1519157n7xppkk6pdw5w52vy1llzn5iljkqd7q1h5609jv7l7cdv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#glib@0.18.4": "0kjws6ns6dym48nzxz9skhipk55flc2hy5q5kzg4w12wvizvs6wm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gloo-timers@0.2.6": "0p2yqcxw0q9kclhwpgshq1r4ijns07nmmagll3lvrgl7pdk5m6cv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gobject-sys@0.18.0": "0i6fhp3m6vs3wkzyc22rk2cqj68qvgddxmpaai34l72da5xi4l08",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#graphene-rs@0.18.1": "00f4q1ra4haap5i7lazwhkdgnb49fs8adk2nm6ki6mjhl76jh8iv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#graphene-sys@0.18.1": "0n8zlg7z26lwpnvlqp1hjlgrs671skqwagdpm7r8i1zwx3748hfc",
|
||||
|
@ -155,110 +151,103 @@
|
|||
"registry+https://github.com/rust-lang/crates.io-index#gtk4-macros@0.7.2": "0bw3cchiycf7dw1bw4p8946gv38azxy05a5w0ndgcmxnz6fc8znm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gtk4-sys@0.7.3": "1f2ylskyqkjdik9fij2m46pra4jagnif5xyalbxfk3334fmc9n2l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#gtk4@0.7.3": "0hh8nzglmz94v1m1h6vy8z12m6fr7ia467ry0md5fa4p7sm53sss",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#h2@0.3.26": "1s7msnfv7xprzs6xzfj5sg6p8bjcdpcqcmjjbkd345cyi1x55zl1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#half@2.4.1": "123q4zzw1x4309961i69igzd1wb7pj04aaii3kwasrz3599qrl3d",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.14.5": "1wa1vy1xs3mp11bn3z9dv0jricgr6a2j0zkf1g19yz3vw4il89z5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.0": "1yx4xq091s7i6mw6bn77k8cp4jrpcac149xr32rg8szqsj27y20y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#h2@0.3.22": "0y41jlflvw8niifdirgng67zdmic62cjf5m2z69hzrpn5qr50qjd",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#half@2.2.1": "1l1gdlzxgm7wc8xl5fxas20kfi1j35iyb7vfjkghbdzijcvazd02",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.14.3": "012nywlg0lj9kwanh69my5x67vjlfmzfi9a0rq4qvis2j8fil3r9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.8.4": "1xy8agkyp0llbqk9fcffc1xblayrrywlyrm2a7v93x8zygm4y2g8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#headers-core@0.2.0": "0ab469xfpd411mc3dhmjhmzrhqikzyj8a17jn5bkj9zfpy0n9xp7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#headers@0.3.9": "0w62gnwh2p1lml0zqdkrx9dp438881nhz32zrzdy61qa0a9kns06",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#heck@0.4.1": "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0": "1sjmpsdl8czyh9ywl3qcsfsq9a307dg4ni2vnlwgnzzqhc4y0113",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hermit-abi@0.3.9": "092hxjbjnq5fmz66grd9plxd0sh6ssg5fhgwwwqbrzgzkjwdycfj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hermit-abi@0.4.0": "1k1zwllx6nfq417hy38x4akw1ivlv68ymvnzyxs76ffgsqcskxpv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hermit-abi@0.3.3": "1dyc8qsjh876n74a3rcz8h43s27nj1sypdhsn2ms61bd3b47wzyp",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hex-string@0.1.0": "02sgrgrbp693jv0v5iga7z47y6aj93cq0ia39finby9x17fw53l4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hex@0.4.3": "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hkdf@0.12.4": "1xxxzcarz151p1b858yn5skmhyrvn8fs4ivx5km3i1kjmnr8wpvv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hmac@0.12.1": "0pmbr069sfg76z7wsssfk5ddcqd9ncp79fyz6zcm6yn115yc6jbc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#home@0.5.9": "19grxyg35rqfd802pcc9ys1q3lafzlcjcv2pl2s5q8xpyr5kblg3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#http-body@0.4.6": "1lmyjfk6bqk6k9gkn1dxq770sb78pqbqshga241hr5p995bb5skw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#http@0.2.12": "1w81s4bcbmcj9bjp7mllm8jlz6b31wzvirz8bgpzbqkpwmbvn730",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#http@1.1.0": "0n426lmcxas6h75c2cp25m933pswlrfjz10v91vc62vib2sdvf91",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#httparse@1.9.5": "0ip9v8m9lvgvq1lznl31wvn0ch1v254na7lhid9p29yx9rbx6wbx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#http@0.2.11": "1fwz3mhh86h5kfnr5767jlx9agpdggclq7xsqx930fflzakb2iw9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#http@1.0.0": "1sllw565jn8r5w7h928nsfqq33x586pyasdfr7vid01scwwgsamk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#httparse@1.8.0": "010rrfahm1jss3p022fqf3j3jmm72vhn4iqhykahb9ynpaag75yq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#httpdate@1.0.3": "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#humantime@2.1.0": "1r55pfkkf5v0ji1x6izrjwdq9v6sc7bv99xj6srywcar37xmnfls",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hyper-tls@0.5.0": "01crgy13102iagakf6q4mb75dprzr7ps1gj0l5hxm1cvm7gks66n",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.10.16": "0wwjh9p3mzvg3fss2lqz5r7ddcgl1fh9w6my2j69d6k0lbcm41ha",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.30": "1jayxag79yln1nzyzx652kcy1bikgwssn6c4zrrp5v7s3pbdslm1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.28": "107gkvqx4h9bl17d602zkm2dgpfq86l2dr36yzfsi8l3xcsy35mz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone-haiku@0.1.2": "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.61": "085jjsls330yj1fnwykfzmb2f10zp6l7w4fhq81ng81574ghhpi3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.58": "081vcr8z8ddhl5r1ywif6grnswk01b2ac4nks2bhn8zzdimvh9l3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#idna@0.1.5": "0kl4gs5kaydn4v07c6ka33spm9qdh2np0x7iw7g5zd8z1c7rxw1q",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#idna@0.5.0": "1xhjrcjqq0l5bpzvdgylvpkgk94panxgsirzhjnnqfdgc4a9nkb3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#image@0.23.14": "18gn2f7xp30pf9aqka877knlq308khxqiwjvsccvzaa4f9zcpzr4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#image@0.24.9": "17gnr6ifnpzvhjf6dwbl9hki8x6bji5mwcqp0048x1jm5yfi742n",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#image@0.24.7": "04d7f25b8nlszfv9a474n4a0al4m2sv9gqj3yiphhqr0syyzsgbg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#indent_write@2.2.0": "1hqjp80argdskrhd66g9sh542yxy8qi77j6rc69qd0l7l52rdzhc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.6.0": "1nmrwn8lbs19gkvhxaawffzbvrpyrb5y3drcrr645x957kz0fybh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#intl-memoizer@0.5.2": "1nkvql7c7b76axv4g68di1p2m9bnxq1cbn6mlqcawf72zhhf08py",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.1.0": "07rxrqmryr1xfnmhrjlz8ic6jw28v6h5cig3ws2c9d0wifhy2c6m",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#instant@0.1.12": "0b2bx5qdlwayriidhrag8vhy10kdfimfhmb3jnjmsz2h9j1bwnvs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#intl-memoizer@0.5.1": "0vx6cji8ifw77zrgipwmvy1i3v43dcm58hwjxpb1h29i98z46463",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#intl_pluralrules@7.0.2": "0wprd3h6h8nfj62d8xk71h178q7zfn3srxm787w4sawsqavsg3h7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#ipnet@2.10.1": "025p9wm94q1w2l13hbbr4cbmfygly3a2ag8g5s618l2jhq4l3hnx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#io-lifetimes@1.0.11": "1hph5lz4wd3drnn6saakwxr497liznpfnv70via6s0v8x6pbkrza",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#ipnet@2.9.0": "1hzrcysgwf0knf83ahb3535hrkw63mil88iqc6kjaryfblrqylcg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#iron@0.6.1": "1s4mf8395f693nhwsr0znw3j5frzn56gzllypyl50il85p50ily6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#is-terminal@0.4.13": "0jwgjjz33kkmnwai3nsdk1pz9vb6gkqvw1d1vq7bs3q48kinh7r6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#is_terminal_polyfill@1.70.1": "1kwfgglh91z33kl0w5i338mfpa3zs0hidq5j4ny4rmjwrikchhvr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#itertools@0.12.1": "0s95jbb3ndj1lvfxyq5wanc0fm0r6hg6q4ngb92qlfdxvci10ads",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.11": "0nv9cqjwzr3q58qz84dcz63ggc54yhf1yqar1m858m1kfd4g3wa9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#is-terminal@0.4.9": "12xgvc7nsrp3pn8hcxajfhbli2l5wnh3679y2fmky88nhj4qj26b",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#itertools@0.12.0": "1c07gzdlc6a1c8p8jrvvw3gs52bss3y58cs2s21d9i978l36pnr5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.10": "0k7xjfki7mnv6yzjrbnbnjllg86acmbnk4izz2jmm1hx2wd6v95i",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.1.22": "1wnh0bmmswpgwhgmlizz545x8334nlbmkq8imy9k224ri3am7792",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.3.1": "1c1k53svpdyfhibkmm0ir5w0v3qmcmca8xr8vnnmizwf6pdagm7m",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.70": "0yp3rz7vrn9mmqdpkds426r1p9vs6i8mkxx8ryqdfadr0s2q0s0q",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.3.0": "0gkv0zx95i4fr40fj1a10d70lqi6lfyia8r5q8qjxj8j4pj0005w",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.66": "1ji9la5ydg0vy17q54i7dnwc0wwb9zkx662w1583pblylm6wdsff",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#kv-log-macro@1.0.7": "0zwp4bxkkp87rl7xy2dain77z977rvcry1gmr5bssdbn541v7s0d",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#language-tags@0.2.2": "16hrjdpa827carq5x4b8zhas24d8kg4s16m6nmmn1kb7cr5qh7d9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.5.0": "1zk6dqqni0193xg6iijh7i3i44sryglwgvx20spdvwk3r6sbrlmv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#lazycell@1.3.0": "0m8gw7dn30i0zjjpjdyf6pc16c34nl71lpv461mix50x3p70h3c3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.4.0": "0in6ikhw8mgl33wjv6q6xfrb5b9jr16q8ygjy803fay4zcisvaz2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#lebe@0.5.2": "1j2l6chx19qpa5gqcw434j83gyskq3g2cnffrbl3842ymlmpq203",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libadwaita-sys@0.5.3": "16n6xsy6jhbj0jbpz8yvql6c9b89a99v9vhdz5s37mg1inisl42y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libadwaita@0.5.3": "174pzn9dwsk8ikvrhx13vkh0zrpvb3rhg9yd2q5d2zjh0q6fgrrg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.159": "1i9xpia0hn1y8dws7all8rqng6h3lc8ymlgslnljcvm376jrf7an",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libloading@0.8.5": "194dvczq4sifwkzllfmw0qkgvilpha7m5xy90gd6i446vcpz4ya9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.151": "1x28f0zgp4zcwr891p8n9ag9w371sbib30vp4y6hi2052frplb9h",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libm@0.2.8": "0n4hk1rs8pzw8hdfmwn96c4568s93kfxqgcqswr7sajd2diaihjf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libspa-sys@0.8.0": "07yh4i5grzbxkchg6dnxlwbdw2wm5jnd7ffbhl77jr0388b9f3dz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libspa@0.8.0": "044qs48yl0llp2dmrgwxj9y1pgfy09i6fhq661zqqb9a3fwa9wv5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libsqlite3-sys@0.27.0": "05pp60ncrmyjlxxjj187808jkvpxm06w5lvvdwwvxd2qrmnj4kng",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#libyml@0.0.5": "106963pwg1gc3165bdlk8bbspmk919gk10vshhqglks3z8m700ik",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.14": "12gsjgbhhjwywpqcrizv80vrp7p7grsz5laqq773i33wphjsxcvq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.12": "05qvxa6g27yyva25a5ghsg85apdxkvr77yhkyhapj6r8vnf8pbq7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.3.8": "068mbigb3frrxvbi5g61lx25kksy98f2qgkvc4xg8zxznwp98lzg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.12": "0mhlla3gk1jgn6mrq9s255rvvq8a1w3yk2vpjiwsd6hmmy1imkf4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.11": "0iggx0h4jx63xm35861106af3jkxq06fpqhpkhgw0axi2n38y5iw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#log@0.3.9": "0jq23hhn5h35k7pa8r7wqnsywji6x3wn1q5q7lif5q536if8v7p1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#log@0.4.22": "093vs0wkm1rgyykk7fjbqp2lwizbixac1w52gv109p5r4jh0p9x7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#log@0.4.20": "13rf7wphnwd61vazpxr7fiycin6cb1g8fmvgqg18i464p0y1drmm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#logger@0.4.0": "14xlxvkspcfnspjil0xi63qj5cybxn1hjmr5gq8m4v1g9k5p54bc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#matches@0.1.10": "1994402fq4viys7pjhzisj4wcw894l53g798kkm2y74laxk0jci5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#md-5@0.10.6": "1kvq5rnpm4fzwmyv5nmnxygdhhb2369888a06gdc9pxyrzh7x7nq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4": "18z32bhxrax0fnjikv475z7ii718hq457qwmaryixfxsl2qrmjkq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#memoffset@0.9.1": "12i17wh9a9plx869g7j4whf62xw68k5zd4k0k5nh6ys5mszid028",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#memchr@2.6.4": "0rq1ka8790ns41j147npvxcqcl2anxyngsdimy85ag2api0fwrgn",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#memoffset@0.9.0": "0v20ihhdzkfw1jx00a7zjpk2dcp5qjq6lz302nyqamd9c4f4nqss",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#mime@0.2.6": "1q1s1ax1gaz8ld3513nvhidfwnik5asbs1ma3hp6inp5dn56nqms",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#mime@0.3.17": "16hkibgvb9klh0w0jk5crr5xv90l3wlf77ggymzjmvl1818vnxv8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#mime_guess@1.8.8": "18qcd5aa3363mb742y7lf39j7ha88pkzbv9ff2qidlsdxsjjjs91",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#mime_guess@2.0.5": "03jmg3yx6j39mg0kayf7w4a886dl3j15y8zs119zw01ccy74zi7p",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#mime_guess@2.0.4": "1vs28rxnbfwil6f48hh58lfcx90klcvg68gxdc60spwa4cy2d4j1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#minimal-lexical@0.2.1": "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.3.7": "0dblrhgbm0wa8jjl8cjp81akaj36yna92df4z1h9b26n3spal7br",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.4.4": "0jsfv00hl5rmx1nijn59sr9jmjd4rjnjhh4kdjy8d187iklih9d9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.7.4": "024wv14aa75cvik7005s5y2nfc8zfidddbd7g55g7sjgnzfl18mq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.0": "1wadxkg6a6z4lr7kskapj5d8pxlx7cp1ifw4daqnkzqjxych5n72",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#mio@1.0.2": "1v1cnnn44awxbcfm4zlavwgkvbyg7gp5zzjm8mqf1apkrwflvq40",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.7.1": "1ivl3rbbdm53bzscrd01g60l46lz5krl270487d8lhjvwl5hx0g7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#mio@0.8.10": "02gyaxvaia9zzi4drrw59k9s0j6pa5d1y2kv7iplwjipdqlhngcg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#modifier@0.1.0": "0n3fmgli1nsskl0whrfzm1gk0rmwwl6pw1q4nb9sqqmn5h8wkxa1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#multer@2.1.0": "1hjiphaypj3phqaj5igrzcia9xfmf4rr4ddigbh8zzb96k1bvb01",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#nary_tree@0.4.3": "1iqray1a716995l9mmvz5sfqrwg9a235bvrkpcn8bcqwjnwfv1pv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#native-tls@0.2.12": "0rkl65z70n7sy4d5w0qa99klg1hr43wx6kcprk4d2n9xr2r4wqd8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#native-tls@0.2.11": "0bmrlg0fmzxaycjpkgkchi93av07v2yf9k33gc12ca9gqdrn28h7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#nix@0.27.1": "0ly0kkmij5f0sqz35lx9czlbk6zpihb7yh1bsy4irzwfd2f4xc1f",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#no-std-compat@0.4.1": "132vrf710zsdp40yp1z3kgc2ss8pi0z4gmihsz3y7hl4dpd56f5r",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#nom@7.1.3": "0jha9901wxam390jcf5pfa0qqfrgh8li787jx2ip0yk5b8y9hwyj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-bigint-dig@0.8.4": "0lb12df24wgxxbspz4gw1sf1kdqwvpdcpwq4fdlwg4gj41c1k16w",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-conv@0.1.0": "1ndiyg82q73783jq18isi71a7mjh56wxrk52rlvyx0mi5z9ibmai",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.46": "13w5g54a9184cqlbsq80rnxw4jj4s0d8wv75jsq5r2lms8gncsbr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-iter@0.1.45": "1gzm7vc5g9qsjjl3bqk9rz1h6raxhygbrcpbfl04swlh0i506a8l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.45": "1ncwavvwdmsqzxnn65phv6c6nn72pnv9xhpmjd6a429mzf4k6p92",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-iter@0.1.43": "0lp22isvzmmnidbq9n5kbdh8gj0zm3yhxv1ddsn5rp65530fc0vx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-rational@0.3.2": "01sgiwny9iflyxh2xz02sak71v2isc3x608hfdpwwzxi3j5l5b0j",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19": "0h984rhdkkqd4ny9cif7y2azl3xdfb7768hb9irhpsch4q3gq787",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-rational@0.4.1": "1c0rb8x4avxy3jvvzv764yk7afipzxncfnqlb10r3h53s34s2f06",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.17": "0z16bi5zwgfysz6765v3rd6whfbjpihx3mhsn4dg8dzj2c221qrr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#num_cpus@1.16.0": "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#object@0.36.5": "0gk8lhbs229c68lapq6w6qmnm4jkj48hrcw5ilfyswy514nhmpxf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#once_cell@1.20.2": "0xb7rw1aqr7pa4z3b00y7786gyf8awx2gca3md73afy76dzgwq8j",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#object@0.32.1": "1c02x4kvqpnl3wn7gz9idm4jrbirbycyqjgiw6lm1g9k77fzkxcw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#once_cell@1.19.0": "14kvw7px5z96dk4dwdm1r9cqhhy2cyj1l5n5b29mynbb8yr15nrz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1": "173xxvfc63rr5ybwqwylsir0vq6xsj4kxiv4hmg4c3vscdmncj59",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#openssl-probe@0.1.5": "1kq18qm48rvkwgcggfkqq6pm948190czqc94d6bm2sir5hq1l0gz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.103": "1mi9r5vbgqqwfa2nqlh2m0r1v5abhzjigfbi7ja0mx0xx7p8v7kz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.66": "1hfr9ffx67j455aqrmyys3c8l65ngbqrl5qi3v3fi8vhddwg8acm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.97": "02s670ir38fsavphdna07144y41dkvrcfkwnjzg82zfrrlsavsn3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.61": "0idv3n9n9f2sxq8cqzxvq44633vg5sx4n9q1p3g6dn66ikf1k13b",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pango-sys@0.18.0": "1iaxalcaaj59cl9n10svh4g50v8jrc1a36kd7n9yahx8j7ikfrs3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pango@0.18.3": "1r5ygq7036sv7w32kp8yxr6vgggd54iaavh3yckanmq4xg0px8kw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#parking@2.2.1": "1fnfgmzkfpjd69v4j9x737b1k8pnn054bvzcn5dm3pkgq595d3gk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.3": "09ws9g6245iiq8z975h8ycf818a66q3c6zv4b5h8skpm7hc1igzi",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.10": "1y3cf9ld9ijf7i4igwzffcn0xl16dxyn4c5bwgjck1dkgabiyh0y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#parse-zoneinfo@0.3.1": "093cs8slbd6kyfi6h12isz0mnaayf5ha8szri1xrbqj4inqhaahz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15": "02pxffpdqkapy292harq6asfjvadgp1s005fip9ljfsn9fvxgh2p",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#parking@2.2.0": "1blwbkq6im1hfxp5wlbr475mw98rsyc0bbr2d5n16m38z253p0dv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.1": "13r2xk7mnxfc5g0g6dkdxqdqad99j7s7z8zhzz4npw5r0g0v4hip",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.9": "13h0imw1aq86wj28gxkblhkzx6z1gk8q18n0v76qmmj6cliajhjc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#parse-zoneinfo@0.3.0": "0h8g6jy4kckn2gk8sd5adaws180n1ip65xhzw5jxlq4w8ibg41f7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#paste@1.0.14": "0k7d54zz8zrz0623l3xhvws61z5q2wd3hkwim6gylk8212placfy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pem-rfc7468@0.7.0": "04l4852scl4zdva31c1z6jafbak0ni5pi0j38ml108zwzjdrrcw8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@1.0.1": "0cgq08v1fvr6bs5fvy390cz830lq4fak8havdasdacxcw790s09i",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.1": "0gi8wgx0dcy8rnv1kywdv98lwcx67hz0a0zwpib5v2i08r88y573",
|
||||
|
@ -270,32 +259,31 @@
|
|||
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.7.24": "0qi62gxk3x3whrmw5c4i71406icqk11qmpgln438p6qm7k4lqdh9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.11.2": "0azphb0a330ypqx3qvyffal5saqnks0xvl8rj73jlk3qxxgbkz4h",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.7.24": "18371fla0vsj7d6d5rlfb747xbr2in11ar9vgv5qna72bnhp2kr3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pin-project-internal@1.1.7": "133mxf5vmvnvw4idw2y2lb5bxsza2xlyfl6psjy7mz3l12nmy3rw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.14": "00nx3f04agwjlsmd3mc5rx5haibj2v8q9b52b0kwn63wcv4nz9mx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pin-project@1.1.7": "15cvflrzsgp1zbl5gv37al2r62nl8lc37xkfwf70ql3fji7gcmxy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pin-project-internal@1.1.3": "01a4l3vb84brv9v7wl71chzxra2kynm6yvcjca66xv3ij6fgsna3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.13": "0n0bwr5qxlf0mhn2xkl36sy55118s9qmvx2yl5f3ixkb007lbywa",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pin-project@1.1.3": "08k4cpy8q3j93qqgnrbzkcgpn7g0a88l4a9nm33kyghpdhffv97x",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pin-utils@0.1.0": "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#piper@0.2.4": "0rn0mjjm0cwagdkay77wgmz3sqf8fqmv9d9czm79mvr2yj8c9j4n",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pipewire-sys@0.8.0": "04hiy3rl8v3j2dfzp04gr7r8l5azzqqsvqdzwa7sipdij27ii7l4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pipewire@0.8.0": "1nldg1hz4v0qr26lzdxqpvrac4zbc3pb6436sl392425bjx4brh8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#piper@0.2.1": "1m45fkdq7q5l9mv3b0ra10qwm0kb67rjp2q8y91958gbqjqk33b6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pkcs1@0.7.5": "0zz4mil3nchnxljdfs2k5ab1cjqn7kq5lqp62n9qfix01zqvkzy8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pkcs8@0.10.2": "1dx7w21gvn07azszgqd3ryjhyphsrjrmq5mmz1fbxkj5g0vv4l7r",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.31": "1wk6yp2phl91795ia0lwkr3wl4a9xkrympvhqq8cxk4d75hwhglm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.27": "0r39ryh1magcq4cz5g9x88jllsnxnhcqr753islvyk4jp9h2h1r6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#plugin@0.2.6": "1q7nghkpvxxr168y2jnzh3w7qc9vfrby9n7ygy3xpj0bj71hsshs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#png@0.16.8": "1ipl44q3vy4kvx6j296vk7d4v8gvcg203lrkvvixwixq1j98fciw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#png@0.17.14": "1w130qw3cngzppxk1yp3ls2pbw3f0spbzhkbarbnlnm06imd9yaj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#polling@3.7.3": "04b5zdgz0m9ydbzcr3f9a55749gqbj0y89d0nz9nrv0x636r09yc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#png@0.17.10": "0r5a8a25ad0jq2pkp2zbab3wwhpgp6jmdg6d0ybjnw6kilnvyxfx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#polling@2.8.0": "1kixxfq1af1k7gkmmk9yv4j2krpp4fji2r8j4cz6p6d7ihz34bab",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#polling@3.4.0": "052am20b5r03nwhpnjw86rv3dwsdabvb07anv3fqxfbs65r4w19h",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#powerfmt@0.2.0": "14ckj2xdpkhv3h6l5sdmb9f1d57z8hbfpdldjc2vl5givq2y77j3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#ppv-lite86@0.2.20": "017ax9ssdnpww7nrl1hvqh2lzncpv04nnsibmnw9nxjnaqlpp5bp",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#ppv-lite86@0.2.17": "1pp6g52aw970adv3x2310n7glqnji96z0a9wiamzw89ibf0ayh2v",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#pretty_env_logger@0.5.0": "076w9dnvcpx6d3mdbkqad8nwnsynb7c8haxmscyrz7g3vga28mw6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@1.3.1": "069r1k56bvgk0f58dm5swlssfcp79im230affwk6d9ck20g04k3z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.2": "092x5acqnic14cw6vacqap5kgknq3jn4c6jij9zi6j85839jc3xh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.1": "06jbv5w6s04dbjbwq0iv7zil12ildf3w8dvvb4pqvhig4gm5zp4p",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error-attr@1.0.4": "0sgq6m5jfmasmwwy8x4mjygx5l7kp8s4j60bv25ckv2j1qc41gm1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error@1.0.4": "1373bhxaf0pagd8zkyd03kkx6bchzf6g0dkwrwzsnal9z47lj9fs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.89": "0vlq56v41dsj69pnk7lil7fxvbfid50jnzdn3xnr31g05mkb0fgi",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proptest@1.5.0": "13gm7mphs95cw4gbgk5qiczkmr68dvcwhp58gmiz33dq2ccm3hml",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.78": "1bjak27pqdn4f4ih1c9nr3manzyavsgqmf76ygw9k76q8pb2lhp2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#proptest@1.4.0": "1gzmw40pgmwzb7x6jsyr88z5w151snv5rp1g0dlcp1iw3h9pdd1i",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#qoi@0.4.1": "00c0wkb112annn2wl72ixyd78mf56p4lxkhlmsggx65l3v3n8vbz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#quick-error@1.2.3": "1q6za3v78hsspisc197bg3g7rpc989qycy8ypr8ap8igv10ikl51",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.37": "1brklraw2g34bxy9y4q1nbrccn7bv36ylihv12c9vlcii55x7fdm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.35": "1vv8r2ncaz4pqdr78x7f138ka595sp2ncr1sa2plm4zxbsmwj7i9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rand@0.3.23": "0v679h38pjjqj5h4md7v2slsvj6686qgcn7p9fbw3h43iwnk1b34",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rand@0.4.6": "14qjfv3gggzhnma20k0sc1jf8y6pplsaq7n1j9ls5c8kf2wl0a2m",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rand@0.6.5": "1jl4449jcl4wgmzld6ffwqj5gwxrp8zvx8w573g1z368qg6xlwbd",
|
||||
|
@ -312,180 +300,175 @@
|
|||
"registry+https://github.com/rust-lang/crates.io-index#rand_pcg@0.1.2": "0i0bdla18a8x4jn1w0fxsbs3jg7ajllz6azmch1zw33r06dv1ydb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rand_xorshift@0.1.1": "0p2x8nr00hricpi2m6ca5vysiha7ybnghz79yqhhx6sl4gkfkxyb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rand_xorshift@0.3.0": "13vcag7gmqspzyabfl1gr9ykvxd2142q2agrj8dkyjmfqmgg4nyj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.12.1": "1qpwim68ai5h0j7axa8ai8z0payaawv3id0lrgkqmapx7lx8fr8l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rayon@1.10.0": "1ylgnzwgllajalr4v00y4kj22klq2jbwllm70aha232iah0sc65l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.12.0": "1vaq0q71yfvcwlmia0iqf6ixj2fibjcf2xjy92n1m1izv1mgpqsw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rayon@1.8.0": "1cfdnvchf7j4cpha5jkcrrsr61li9i9lp5ak7xdq6d3pvc1xn9ww",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rdrand@0.4.0": "1cjq0kwx1bk7jx3kzyciiish5gqsj7620dm43dc52sr8fzmm9037",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#redox_syscall@0.5.7": "07vpgfr6a04k0x19zqr1xdlqm6fncik3zydbdi3f5g3l5k7zwvcv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.8": "18wd530ndrmygi6xnz3sp345qi0hy2kdbsa89182nwbl6br5i1rn",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.5": "0p41p3hj9ww7blnbwbj9h7rwxzxg0c1hvrdycgys8rxyhqqw859b",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#regex@1.11.0": "1n5imk7yxam409ik5nagsjpwqvbg3f0g0mznd5drf549x1g0w81q",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#redox_syscall@0.4.1": "1aiifyz5dnybfvkk4cdab9p2kmphag1yad6iknc7aszlxxldf8j7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.3": "0gs8q9yhd3kcg4pr00ag4viqxnh5l7jpyb9fsfr8hzh451w4r02z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.2": "17rd2s8xbiyf6lb4aj2nfi44zqlj98g2ays8zzj2vfs743k79360",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#regex@1.10.2": "0hxkd814n4irind8im5c9am221ri6bprx49nc7yxv02ykhd9a2rq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#remove_dir_all@0.5.3": "1rzqbsgkmr053bxxl04vmvsd1njyz0nxvly97aip6aa2cmb15k9s",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#reqwest@0.11.27": "0qjary4hpplpgdi62d2m0xvbn6lnzckwffm0rgkm2x51023m6ryx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#reqwest@0.11.23": "0hgvzb7r46656r9vqhl5qk1kbr2xzjb91yr2cb321160ka6sxc9p",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.6": "1z0d1aavfm0v4pv8jqmqhhvvhvblla1ydzlvwykpc3mkzhj523jx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustc-demangle@0.1.24": "07zysaafgrkzy2rjgwqdj2a8qdpsm6zv6f5pgpk9x0lm40z9b6vi",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustc-demangle@0.1.23": "0xnbk2bmyzshacjm2g1kd4zzv2y2az14bw3sjccq5qkpmsfvn9nn",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustc-hash@1.1.0": "1qkc5khrmv5pqi5l5ca9p5nl5hs742cagrndhbrlk3dhlrx3zm08",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.4.1": "14lvdsmr5si5qbqzrajgb6vfn69k0sfygrvfvr2mps26xwi3mjyg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.37": "04b8f99c2g36gyggf4aphw8742k2b1vls3364n2z493whj5pijwa",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.4.0": "0rpk9rcdk405xhbmgclsh4pai0svn49x35aggl4nhbkd4a2zb85z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustix@0.37.27": "1lidfswa8wbg358yrrkhfvsw0hzlvl540g4lwqszw09sg8vcma7y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.28": "05m3vacvbqbg6r6ksmx9k5afpi0lppjdv712crrpsrfax2jp5rbj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rustls-pemfile@1.0.4": "1324n5bcns0rnw6vywr5agff3rwfvzphi7rmbyzwnv6glkhclx0w",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#rusty-fork@0.3.0": "0kxwq5c480gg6q0j3bg4zzyfh2kwmc3v2ba94jw8ncjc8mpcqgfb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.18": "17xx2s8j1lln7iackzd9p0sv546vjq71i779gphjq923vjh5pjzk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.16": "0k7b90xr48ag5bzmfjp82rljasw2fx28xr3bg1lrpx7b5sljm3gr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#safemem@0.3.3": "0wp0d2b2284lw11xhybhaszsczpbq1jbdklkxgifldcknmy3nw7g",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#schannel@0.1.26": "1hfip5mdwqcfnmrnkrq9d8zwy6bssmf6rfm2441nk83ghbjpn8h1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#schannel@0.1.22": "126zy5jb95fc5hvzyjwiq6lc81r08rdcn6affn00ispp9jzk6dqc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#scoped-tls@1.0.1": "15524h04mafihcvfpgxd8f4bgc3k95aclz8grjkg9a0rxcvn9kz1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#scoped_threadpool@0.1.9": "1a26d3lk40s9mrf4imhbik7caahmw2jryhhb6vqv6fplbbgzal8x",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0": "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#security-framework-sys@2.12.0": "1dml0lp9lrvvi01s011lyss5kzzsmakaamdwsxr0431jd4l2jjpa",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#security-framework@2.11.1": "00ldclwx78dm61v7wkach9lcx76awlrv0fdgjdwch4dmy12j4yw9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#security-framework-sys@2.9.1": "0yhciwlsy9dh0ps1gw3197kvyqx1bvc4knrhiznhid6kax196cp9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#security-framework@2.9.2": "1pplxk15s5yxvi2m1sz5xfmjibp96cscdcl432w9jzbk0frlzdh5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#self_cell@0.10.3": "0pci3zh23b7dg6jmlxbn8k4plb7hcg5jprd1qiz0rp04p1ilskp1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#self_cell@1.0.4": "0jki9brixzzy032d799xspz1gikc5n2w81w8q8yyn8w6jxpsjsfk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#semver@1.0.23": "12wqpxfflclbq4dv8sa6gchdh92ahhwn4ci1ls22wlby3h57wsb1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#self_cell@1.0.2": "1rmdglwnd77wcw2gv76finpgzjhkynx422d0jpahrf2fsqn37273",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#semver@1.0.20": "140hmbfa743hbmah1zjf07s8apavhvn04204qjigjiz5w6iscvw3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde@0.9.15": "1bsla8l5xr9pp5sirkal6mngxcq6q961km88jvf339j5ff8j7dil",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.210": "0flc0z8wgax1k4j5bf2zyq48bgzyv425jkd5w0i6wbh7f8j5kqy8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.210": "07yzy4wafk79ps0hmbqmsqh5xjna4pm4q57wc847bb8gl3nh4f94",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.128": "1n43nia50ybpcfmh3gcw4lcc627qsg9nyakzwgkk9pm10xklbxbg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.8": "1q89g70azwi4ybilz5jb8prfpa575165lmrffd49vmcf76qpqq47",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.193": "129b0j67594f8qg5cbyi3nyk31y97wrqihi026mba34dwrsrkp95",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.193": "1lwlx2k7wxr1v160kpyqjfabs37gm1yxqg65383rnyrm06jnqms3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.108": "0ssj59s7lpzqh1m50kfzlnrip0p0jg9lmhn4098i33a0mhz7w71x",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.5": "1hgh6s3jjwyzhfk3xwb6pnnr1misq9nflwq0f026jafi37s24dpb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde_urlencoded@0.7.1": "1zgklbdaysj3230xivihs30qi5vkhigg323a9m62k8jwf4a1qjfk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#serde_yml@0.0.12": "1p8xwz4znd6fj962y22fdvvv16gb8c0hx4iv5hjplngiidcdvqjr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sha1@0.10.6": "1fnnxlfg08xhkmwf2ahv634as30l1i3xhlhkvxflmasi5nd85gz3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sha2@0.10.8": "1j1x78zk9il95w9iv46dh9wm73r6xrgj32y6lzzw7bxws9dbfgbr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0": "0r1y6bv26c1scpxvhg2cabimrmwgbp4p3wy6syj9n0c4s3q2znhg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.2": "1cb5akgq8ajnd5spyn587srvs4n26ryq0p78nswffwhv46sf1sd9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.1": "18crkkw5k82bvcx088xlf5g4n3772m24qhzgfan80nda7d3rn8nq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#signature@2.2.0": "1pi9hd5vqfr3q3k49k37z06p7gs5si0in32qia4mmr1dancr6m3p",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.7": "1zkq40c3iajcnr5936gjp9jjh1lpzhy44p3dq3fiw75iwr1w2vfn",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#siphasher@0.2.3": "1b53m53l24lyhr505lwqzrpjyq5qfnic71mynrcfvm43rybf938b",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#siphasher@0.3.11": "03axamhmwsrmh0psdw3gf7c0zc4fyl5yjxfifz9qfka6yhkqid9q",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#slab@0.4.9": "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#smallvec@1.13.2": "0rsw5samawl3wsw6glrsb127rx6sh89a8wyikicw6dkdcjd1lpiw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#smallvec@1.11.2": "0w79x38f7c0np7hqfmzrif9zmn0avjvvm31b166zdk9d1aad1k2d",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#snowflake@1.3.0": "1wadr7bxdxbmkbqkqsvzan6q1h3mxqpxningi3ss3v9jaav7n817",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#socket2@0.5.7": "070r941wbq76xpy039an4pyiy3rfj7mp7pvibf1rcri9njq5wc6f",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#socket2@0.4.10": "03ack54dxhgfifzsj14k7qa3r5c9wqy3v6mqhlim99cc03y1cycz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#socket2@0.5.5": "1sgq315f1njky114ip7wcy83qlphv9qclprfjwvxcpfblmcsqpvv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#spin@0.5.2": "0b84m6dbzrwf2kxylnw82d3dr8w06av7rfkr8s85fb5f43rwyqvf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#spin@0.9.8": "0rvam5r0p3a6qhc18scqpvpgb3ckzyqxpgdfyjnghh8ja7byi039",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#spki@0.7.3": "17fj8k5fmx4w9mp27l970clrh5qa7r5sjdvbsln987xhb34dc7nr",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlformat@0.2.6": "14470h40gn0f6jw9xxzbpwh5qy1fgvkhkfz8xjyzgi0cvf9kmfkv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-core@0.7.4": "1xiyr35dq10sf7lq00291svcj9wbaaz1ihandjmrng9a6jlmkfi4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros-core@0.7.4": "1j7k0fw7n6pgabqnj6cbp8s3rmd3yvqr4chjj878cvd1m99yycsq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros@0.7.4": "09rih250868nfkax022y5dyk24a7qfw6scjy3sgalbzb8lihx92f",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-mysql@0.7.4": "066lxhb80xgb8r5m2yy3a7ydjvp0b6wsk9s7whwfa83d46817lqy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-postgres@0.7.4": "0zjp30wj4n2f25dnb32vsg6jfpa3gw6dmfd0i5pr4kw91fw4x0kw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-sqlite@0.7.4": "1ap0bb2hazbrdgd7mhnckdg9xcchx0k094di9gnhpnhlhh5fyi5j",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx@0.7.4": "1ahadprvyhjraq0c5712x3kdkp1gkwfm9nikrmcml2h03bzwr8n9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#stringprep@0.1.5": "1cb3jis4h2b767csk272zw92lc6jzfzvh8d6m1cd86yqjb9z6kbv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1": "0kzvqlw8hxqb7y598w1s0hxlnmi84sg5vsipp3yg5na5d1rvba3x",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1": "14ijxaymghbl1p0wql9cib5zlwiina7kall6w7g89csprkgbvhhk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlformat@0.2.3": "0v0p70wjdshj18zgjjac9xlx8hmpx33xhq7g8x9rg4s4gjyvg0ff",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-core@0.7.3": "1gdz44yb9qwxv4xl4hv6w4vbqx0zzdlzsf9j9gcj1qir6wy0ljyq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros-core@0.7.3": "0h88wahkxa6nam536lhwr1y0yxlr6la8b1x0hs0n88v790clbgfh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros@0.7.3": "19gjwisiym07q7ibkp9nkvvbywjh0r5rc572msvzyzadvh01r5l9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-mysql@0.7.3": "190ygz5a3pqcd9vvqjv2i4r1xh8vi53j4272yrld07zpblwrawg3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-postgres@0.7.3": "090wm9s6mm53ggn1xwr183cnn8yxly8rgcksdk4hrlfcnz1hmb6n",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx-sqlite@0.7.3": "143laha7wf8dmi0xwycwqmvxdcnb25dq7jnqrsgvmis8v6vpc291",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sqlx@0.7.3": "1kv3hyx7izmmsjqh3l47zrfhjlcblpg20cvnk7pr8dm7klkkr86v",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#stringprep@0.1.4": "1rkfsf7riynsmqj3hbldfrvmna0i9chx2sz39qdpl40s4d7dfhdv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#strsim@0.10.0": "08s69r4rcrahwnickvi0kq49z524ci50capybln83mg6b473qivk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#subtle@2.5.0": "1g2yjs7gffgmdvkkq0wrrh0pxds3q0dv6dhkw9cdpbib656xdkc1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#syn@1.0.109": "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.79": "147mk4sgigmvsb9l8qzj199ygf0fgb0bphwdsghn8205pz82q4w9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#sync_wrapper@0.1.2": "0q01lyj0gr9a93n10nxsn8lwbzq97jqd6b768x17c8f7v7gccir0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.48": "0gqgfygmrxmp8q32lia9p294kdd501ybn6kn2h4gqza0irik2d8g",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#system-configuration-sys@0.5.0": "1jckxvdr37bay3i9v52izgy52dg690x5xfg3hd394sv2xf4b2px7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#system-configuration@0.5.1": "1rz0r30xn7fiyqay2dvzfy56cvaa3km74hnbz2d72p97bkf3lfms",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#system-deps@6.2.2": "0j93ryw031n3h8b0nfpj5xwh3ify636xmv8kxianvlyyipmkbrd3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.12.16": "1cg3bnx1gdkdr5hac1hzxy64fhw4g7dqkd0n3dxy5lfngpr1mi31",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#system-deps@6.2.0": "0c836abhh3k8yn5ymg8wx383ay7n731gkrbbp3gma352yq7mhb9a",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.12.12": "02lk65ik5ffb8vl9qzq02v0df8kxrp16zih78a33mji49789zhql",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tempdir@0.3.7": "1n5n86zxpgd85y0mswrp5cfdisizq2rv3la906g6ipyc03xvbwhm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.13.0": "0nyagmbd4v5g6nzfydiihcn6l9j1w9bxgzyca5lyzgnhcbyckwph",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#termcolor@1.4.1": "0mappjh3fj3p2nmrg4y7qv94rchwi9mzmgmfflr8p2awdj7lyy86",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.64": "1hvzmjx9iamln854l74qyhs0jl2pg3hhqzpqm9p8gszmf9v4x408",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#thiserror@1.0.64": "114s8lmssxl0c2480s671am88vzlasbaikxbvfv8pyqrq6mzh2nm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.8.1": "1r88v07zdafzf46y63vs39rmzwl4vqd4g2c5qarz9mqa8nnavwby",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#termcolor@1.4.0": "0jfllflbxxffghlq6gx4csv0bv0qv77943dcx01h9zssy39w66zz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.51": "1ps9ylhlk2vn19fv3cxp40j3wcg1xmb117g2z2fbf4vmg2bj4x01",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#thiserror@1.0.51": "1drvyim21w5sga3izvnvivrdp06l2c24xwbhp0vg1mhn2iz2277i",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.6.1": "0ds48vs919ccxa3fv1www7788pzkvpg434ilqkq7sjb5dmqg8lws",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.9.1": "0ghyxlz566dzc3scvgmzys11dhq2ri77kb8sznjakijlxby104xs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.9.0": "04b2fd3clxm0pmdlfip8xj594zyrsfwmh641i6x1gfiz9l7jn5vd",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#time-core@0.1.2": "1wx3qizcihw6z151hywfzzyd1y5dl804ydyxci6qm07vbakpr4pg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#time-macros@0.2.18": "1kqwxvfh2jkpg38fy673d6danh1bhcmmbsmffww3mphgail2l99z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#time-macros@0.2.16": "0gx4ngf5g7ydqa8lf7kh9sy72rd4dhvpi31y1jvswi0288rpw696",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#time@0.1.45": "0nl0pzv9yf56djy8y5dx25nka5pr2q1ivlandb3d24pksgx7ly8v",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#time@0.3.36": "11g8hdpahgrf1wwl2rpsg5nxq3aj7ri6xr672v4qcij6cgjqizax",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6": "0bxqaw7z8r2kzngxlzlgvld1r6jbnwyylyvyjbv1q71rvgaga5wi",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tinyvec@1.8.0": "0f5rf6a2wzyv6w4jmfga9iw7rp9fp5gf4d604xgjsf3d9wgqhpj4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#time@0.3.31": "0gjqcdsdbh0r5vi4c2vrj5a6prdviapx731wwn07cvpqqd1blmzn",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.5": "1khf3j95bwwksj2hw76nlvwlwpwi4d1j421lj6x35arqqprjph43",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tinyvec@1.6.0": "0l6bl2h62a5m44jdnpn7lmj14rd44via8180i7121fvm73mmrk47",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tinyvec_macros@0.1.1": "081gag86208sc3y6sdkshgw3vysm5d34p431dzw0bshz66ncng0z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-macros@2.4.0": "0lnpg14h1v3fh2jvnc8cz7cjf0m7z1xgkwfpcyy632g829imjgb9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-macros@2.2.0": "0fwjy4vdx1h9pi4g2nml72wi0fr27b5m954p13ji9anyy8l1x2jv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-native-tls@0.3.1": "1wkfg6zn85zckmv4im7mv20ca6b1vmlib5xwz9p7g19wjfmpdbmv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-stream@0.1.16": "1wc65gprcsyzqlr0k091glswy96kph90i32gffi4ksyh03hnqkjg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-tungstenite@0.21.0": "0f5wj0crsx74rlll97lhw0wk6y12nhdnqvmnjx002hjn08fmcfy8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.12": "0spc0g4irbnf2flgag22gfii87avqzibwfm0si0d1g0k9ijw7rv1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio@1.40.0": "166rllhfkyqp0fs7sxn6crv74iizi4wzd3cvxkcpmlk52qip1c72",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-stream@0.1.14": "0hi8hcwavh5sdi1ivc9qc4yvyr32f153c212dpd7sb366y6rhz1r",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-tungstenite@0.20.1": "0v1v24l27hxi5hlchs7hfd5rgzi167x0ygbw220nvq0w5b5msb91",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.10": "058y6x4mf0fsqji9rfyb77qbfyc50y4pk2spqgj6xsyr693z66al",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tokio@1.35.1": "01613rkziqp812a288ga65aqygs254wgajdi57v8brivjkx4x6y8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#toml@0.8.2": "0g9ysjaqvm2mv8q85xpqfn7hi710hj24sd56k49wyddvvyq8lp8q",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.3": "0jsy7v8bdvmzsci6imj8fzgd255fmy5fzp6zsri14yrry7i77nkw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.19.15": "08bl7rp5g6jwmfpad9s8jpw8wjrciadpnbaswgywpr9hv9qbfnqv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.20.2": "0f7k5svmxw98fhi28jpcyv7ldr2s3c867pjbji65bdxjpd44svir",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.3": "1hzfkvkci33ra94xjx64vv3pp0sq346w06fpkcdwjcid7zhvdycd",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.2": "0lmfzmmvid2yp2l36mbavhmqgsvzqf7r2wiwz73ml4xmwaf1rg5n",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.27": "1rvb5dn9z6d0xdj14r403z0af0bbaqhg02hq4jc97g5wds6lqw1l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.32": "0m5aglin3cdwxpvbg6kz0r9r0k31j48n0kcfwsp6l49z26k3svf0",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.40": "1vv48dac9zgj9650pg2b4d0j3w6f3x9gbggf43scq5hrlysklln3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#traitobject@0.1.0": "0yb0n8822mr59j200fyr2fxgzzgqljyxflx9y8bdy3rlaqngilgg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#try-lock@0.2.5": "0jqijrrvm1pyq34zn1jmy2vihd4jcrjlvsh4alkjahhssjnsn8g4",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tungstenite@0.21.0": "1qaphb5kgwgid19p64grhv2b9kxy7f1059yy92l9kwrlx90sdwcy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#type-map@0.5.0": "17qaga12nkankr7hi2mv43f4lnc78hg480kz6j9zmy4g0h28ddny",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#tungstenite@0.20.1": "1fbgcv3h4h1bhhf5sqbwqsp7jnc44bi4m41sgmhzdsk2zl8aqgcy",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#type-map@0.4.0": "0ilsqq7pcl3k9ggxv2x5fbxxfd6x7ljsndrhc38jmjwnbr63dlxn",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#typeable@0.1.2": "11w8dywgnm32hb291izjvh4zjd037ccnkk77ahk63l913zwzc40l",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#typemap@0.3.3": "1xm1gbvz9qisj1l6d36hrl9pw8imr8ngs6qyanjnsad3h0yfcfv5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#typenum@1.17.0": "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#typeshare-annotation@1.0.4": "0kx38ah6638pkqq5cac7nmvbg6x43v7fj5jgibla4lj8fv1dc5d6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#typeshare@1.0.3": "11riglm8incm0vq7ciyd907w1sc6frfn7h7ab0yp8bkcnycp7w84",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#typeshare-annotation@1.0.2": "1adpfhyz3lqjjbq2ym69mv62ymqyd5651gxlqdy8aa446l70srzw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#typeshare@1.0.1": "1mi7snkx2b4g84x8vx38v1myg5r6g48c865j0nz5zcsc8lpilkgl",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unarray@0.1.4": "154smf048k84prsdgh09nkm2n0w0336v84jd4zikyn6v6jrqbspa",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unic-langid-impl@0.9.5": "1rckyn5wqd5h8jxhbzlbbagr459zkzg822r4k5n30jaryv0j4m0a",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unic-langid@0.9.5": "0i2s024frmpfa68lzy8y8vnb1rz3m9v0ga13f7h2afx7f8g9vp93",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unic-langid-impl@0.9.4": "1ijvqmsrg6qw3b1h9bh537pvwk2jn2kl6ck3z3qlxspxcch5mmab",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unic-langid@0.9.4": "05pm5p3j29c9jw9a4dr3v64g3x6g3zh37splj47i7vclszk251r3",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicase@1.4.2": "0cwazh4qsmm9msckjk86zc1z35xg7hjxjykrgjalzdv367w6aivz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicase@2.7.0": "12gd74j79f94k4clxpf06l99wiv4p30wjr0qm04ihqk9zgdd9lpp",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-bidi@0.3.17": "14vqdsnrm3y5anj6h5zz5s32w88crraycblb88d9k23k9ns7vcas",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.13": "1zm1xylzsdfvm2a5ib9li3g5pp7qnkv4amhspydvgbmd9k6mc6z9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-normalization@0.1.24": "0mnrk809z3ix1wspcqy97ld5wxdb31f3xz6nsvg5qcv289ycjcsh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-properties@0.1.3": "1l3mbgzwz8g14xcs09p4ww3hjkjcf0i1ih13nsg72bhj8n5jl3z7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0": "14qla2jfx74yyb9ds3d2mpwpa4l4lzb9z57c6d2ba511458z5k7n",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14": "1bzn2zv0gp8xxbxbhifw778a7fc93pa6a1kj24jgg9msj07f7mkx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-bidi@0.3.14": "05i4ps31vskq1wdp8yf315fxivyh1frijly9d4gb5clygbr2h9bg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.12": "0jzf1znfpb2gx8nr8mvmyqs1crnv79l57nxnbiszc7xf7ynbjm1k",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-normalization@0.1.22": "08d95g7b1irc578b2iyhzv4xhsa4pfvwsqxcl9lbcpabzkq16msw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.10.1": "0dky2hm5k51xy11hc3nk85p533rvghd462b6i0c532b7hl4j9mhx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unicode_categories@0.1.1": "0kp1d7fryxxm7hqywbk88yb9d1avsam9sg76xh36k5qx2arj9v1r",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#unsafe-any@0.4.2": "0zwwphsqkw5qaiqmjwngnfpv9ym85qcsyj7adip9qplzjzbn00zk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#url@1.7.2": "0nim1c90mxpi9wgdw2xh8dqd72vlklwlzam436akcrhjac6pqknx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#url@2.5.2": "0v2dx50mx7xzl9454cl5qmpjnhkbahmn59gd3apyipbgyyylsy12",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#url@2.5.0": "0cs65961miawncdg2z20171w0vqrmraswv2ihdpd8lxp7cp31rii",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#urlencoding@2.1.3": "1nj99jp37k47n0hvaz5fvz7z6jd0sb4ppvfy3nphr1zbnyixpy6s",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6": "1a9ns3fvgird0snjkd3wbdhwd3zdpc2h5gpyybrfr6ra5pkqxk09",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.2": "088807qwjq46azicqwbhlmzwrbkz7l4hpw43sdkdyyk524vdxaq6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.1": "02ip1a0az0qmc2786vxk2nqwsgcwf17d3a38fkf0q7hrmwh9c6vi",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#uuid@0.4.0": "0cdj2v6v2yy3zyisij69waksd17cyir1n58kwyk1n622105wbzkw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#uuid@0.8.2": "1dy4ldcp7rnzjy56dxh7d2sgrcvn4q77y0a8r0a48946h66zjp5w",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#uuid@1.10.0": "0503gvp08dh5mnm3f0ffqgisj6x3mbs53dmnn1lm19pga43a1pw1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#value-bag@1.9.0": "00aij8p1n7vcggkb9nxpwx9g5nqzclrf7prd1wpi9c3sscvw312s",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#uuid@1.6.1": "0q45jxahvysldn3iy04m8xmr8hgig80855y9gq9di8x72v7myfay",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#value-bag@1.7.0": "02r8wccrzi3bzlkrslkcfw9pwp8kwif9szif2i9arn9dzqx44vhj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15": "09i4nf5y8lig6xgj3f7fyrvzd3nlaw4znrihw8psidvv5yk4xkdc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#version-compare@0.2.0": "12y9262fhjm1wp0aj3mwhads7kv0jz8h168nn5fb8b43nwf9abl5",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#version-compare@0.1.1": "0acg4pmjdbmclg0m7yhijn979mdy66z3k8qrcnvn634f1gy456jp",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.1.5": "1pf91pvj8n6akh7w6j5ypka6aqz08b3qpzgs0ak2kjf4frkiljwi",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.5": "0nhhi4i5x89gm911azqbn7avs9mdacw2i3vcz3cnmz3mv4rqz4hb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.4": "0gs8grwdlgh0xq660d7wr80x14vxbizmd8dbp29p2pdncx8lp1s9",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wait-timeout@0.2.0": "1xpkk0j5l9pfmjfh1pi0i89invlavfrd9av5xp0zhxgb29dhy84z",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#waker-fn@1.1.1": "142n74wlmpwcazfb5v7vhnzj3lb3r97qy8mzpjdpg345aizm3i7k",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#want@0.3.1": "03hbfrnvqqdchb5kgxyavb9jabwza0dmh2vw5kg0dq8rxl57d9xz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#warp@0.3.7": "07137zd13lchy5hxpspd0hs6sl19b0fv2zc1chf02nwnzw1d4y23",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#warp@0.3.6": "0sfimrpxkyka1mavfhg5wa4x977qs8vyxa510c627w9zw0i2xsf1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.10.0+wasi-snapshot-preview1": "07y3l8mzfzzz4cj09c8y90yak4hpsi9g7pllyzpr6xvwrabka50s",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.11.0+wasi-snapshot-preview1": "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasite@0.1.0": "0nw5h9nmcl4fyf4j5d4mfdjfgvwi1cakpi349wc4zrr59wxxinmq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.93": "0yypblaf94rdgqs5xw97499xfwgs1096yx026d6h88v563d9dqwx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.43": "1vf8kmaj95xn5893y1bdlav47y5niq85q5bms9pfj8d6cc7k1sb1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.93": "0dp8w6jmw44srym6l752nkr3hkplyw38a2fxz5f3j1ch9p3l1hxg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.93": "1kycd1xfx4d9xzqknvzbiqhwb5fzvjqrrn88x692q1vblj8lqp2q",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.93": "1104bny0hv40jfap3hp8jhs0q4ya244qcrvql39i38xlghq0lan6",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.93": "1dfr7pka5kwvky2fx82m9d060p842hc5fyyw8igryikcdb0xybm8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#web-sys@0.3.70": "1h1jspkqnrx1iybwhwhc3qq8c8fn4hy5jcf0wxjry4mxv6pymz96",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#weezl@0.1.8": "10lhndjgs6y5djpg3b420xngcr6jkmv70q8rb1qcicbily35pa2k",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#whoami@1.5.2": "0vdvm6sga4v9515l6glqqfnmzp246nq66dd09cw5ri4fyn3mnb9p",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.89": "09l8lyylsdssz993h4fzja69zpvpykaw84fivs210fjgwqjzcmhv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.39": "04lsxpw4jqfwh7c0crzx0smj52nvwp1w3bh4098sq90149da2dmc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.89": "10sj1gr2naxv5q116yjb929hhpvz45dxbkvyk8hyc2lknzy85szh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.89": "1cl2w7k5jn2jbd5kx613c8k8vjvda22hfgcgx7y2mk93fbrxnqh1",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.89": "17s5rppad113c6ggkaq8c3cg7a3zz15i78wxcg6mcl1n15iv7fbs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.89": "0kh6akdldy13z9xqj0skz6b4npq1d98bjkgzb8ccq59hibvd9l0f",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#web-sys@0.3.66": "03q1z22djv5ncqkyydcvnchmdsl5gvnyzcyixkxnifw6xi24mhjh",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#weezl@0.1.7": "1frdbq6y5jn2j93i20hc80swpkj30p1wffwxj1nr4fp09m6id4wi",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#whoami@1.4.1": "0l6ca9pl92wmngsn1dh9ih716v216nmn2zvcn94k04x9p1b3gz12",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#winapi-i686-pc-windows-gnu@0.4.0": "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#winapi-util@0.1.9": "1fqhkcl9scd230cnfj8apfficpf5c9vhwnk4yy9xfc1sw69iq8ng",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#winapi-util@0.1.6": "15i5lm39wd44004i9d5qspry2cynkrpvwzghr6s2c3dsk28nz7pj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#winapi-x86_64-pc-windows-gnu@0.4.0": "0gqq64czqb64kskjryj8isp62m2sgvx25yyj3kpc2myh85w24bki",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#winapi@0.3.9": "06gl025x418lchw1wxj64ycr7gha83m44cjr5sarhynd9xkrm0sw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows-core@0.52.0": "1nc3qv7sy24x0nlnb32f7alzpd6f72l4p24vl65vydbyil669ark",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows-core@0.51.1": "0r1f57hsshsghjyc7ypp2s0i78f7b1vr93w68sdb8baxyf2czy7i",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.48.0": "1aan23v5gs7gya1lc46hqn9mdh8yph3fhxmhxlw36pn6pqc28zb7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.52.0": "0gd3v4ji88490zgb6b5mq5zgbvwv7zx1ibn8v3x83rwcdbryaar8",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.59.0": "0fw5672ziw8b3zpmnbp9pdv1famk74f1l9fcbc3zsrzdg56vqf0y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows-targets@0.48.5": "034ljxqshifs1lan89xwpcy1hp0lhdh4b5n0d2z4fwjx2piacbws",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows-targets@0.52.6": "0wwrx625nwlfp7k93r2rra568gad1mwd888h1jwnl0vfg5r4ywlv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows-targets@0.52.0": "1kg7a27ynzw8zz3krdgy6w5gbqcji27j1sz4p7xk2j5j8082064a",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_gnullvm@0.48.5": "1n05v7qblg1ci3i567inc7xrkmywczxrs1z3lj3rkkxw18py6f1b",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_gnullvm@0.52.6": "1lrcq38cr2arvmz19v32qaggvj8bh1640mdm9c2fr877h0hn591j",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_gnullvm@0.52.0": "1shmn1kbdc0bpphcxz0vlph96bxz0h1jlmh93s9agf2dbpin8xyb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_msvc@0.48.5": "1g5l4ry968p73g6bg6jgyvy9lb8fyhcs54067yzxpcpkf44k2dfw",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_msvc@0.52.6": "0sfl0nysnz32yyfh773hpi49b1q700ah6y7sacmjbqjjn5xjmv09",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_msvc@0.52.0": "1vvmy1ypvzdvxn9yf0b8ygfl85gl2gpcyvsvqppsmlpisil07amv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnu@0.48.5": "0gklnglwd9ilqx7ac3cn8hbhkraqisd0n83jxzf9837nvvkiand7",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnu@0.52.6": "02zspglbykh1jh9pi7gn8g1f97jh1rrccni9ivmrfbl0mgamm6wf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnullvm@0.52.6": "0rpdx1537mw6slcpqa0rm3qixmsb79nbhqy5fsm3q2q9ik9m5vhf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnu@0.52.0": "04zkglz4p3pjsns5gbz85v4s5aw102raz4spj4b0lmm33z5kg1m2",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_msvc@0.48.5": "01m4rik437dl9rdf0ndnm2syh10hizvq0dajdkv2fjqcywrw4mcg",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_msvc@0.52.6": "0rkcqmp4zzmfvrrrx01260q3xkpzi6fzi2x2pgdcdry50ny4h294",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_msvc@0.52.0": "16kvmbvx0vr0zbgnaz6nsks9ycvfh5xp05bjrhq65kj623iyirgz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnu@0.48.5": "13kiqqcvz2vnyxzydjh73hwgigsdr2z1xpzx313kxll34nyhmm2k",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnu@0.52.6": "0y0sifqcb56a56mvn7xjgs8g43p33mfqkd8wj1yhrgxzma05qyhl",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnu@0.52.0": "1zdy4qn178sil5sdm63lm7f0kkcjg6gvdwmcprd2yjmwn8ns6vrx",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnullvm@0.48.5": "1k24810wfbgz8k48c2yknqjmiigmql6kk3knmddkv8k8g1v54yqb",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnullvm@0.52.6": "03gda7zjx1qh8k9nnlgb7m3w3s1xkysg55hkd1wjch8pqhyv5m94",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnullvm@0.52.0": "17lllq4l2k1lqgcnw1cccphxp9vs7inq99kjlm2lfl9zklg7wr8s",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.48.5": "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.52.6": "1v7rb5cibyzx8vak29pdrk8nx9hycsjs4w0jgms08qk49jl6v7sq",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.5.40": "0xk8maai7gyxda673mmw3pj1hdizy5fpi7287vaywykkk19sk4zm",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.52.0": "012wfq37f18c09ij5m6rniw7xxn5fcvrxbqd0wd8vgnl3hfn9yfz",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.5.30": "1ifj9vnqna5qp0d7nb9mrinzf8j7zi1m0gv75870vm91jyw3sp4v",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#winreg@0.50.0": "1cddmp929k882mdh6i9f2as848f13qqna6czwsqzkh1pqnr5fkjj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#yansi-term@0.1.2": "1w8vjlvxba6yvidqdvxddx3crl6z66h39qxj8xi6aqayw2nk0p7y",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.7.35": "0gnf2ap2y92nwdalzz3x7142f2b83sni66l39vxp2ijd6j080kzs",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.7.35": "1w36q7b9il2flg0qskapgi9ymgg7p985vniqd09vi0mwib8lz6qv",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.1": "1pjdrmjwmszpxfd7r860jx54cyk94qk59x13sc307cvr5256glyf",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.7.31": "06k0zk4x4n9s1blgxmxqb1g81y8q334aayx61gyy6v9y1dajkhdk",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.7.31": "0gcfyrmlrhmsz16qxjp2qzr6vixyaw1p04zl28f08lxkvfz62h0w",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#zeroize@1.7.0": "0bfvby7k9pdp6623p98yz2irqnamcyzpn7zh20nqmdn68b0lwnsj",
|
||||
"registry+https://github.com/rust-lang/crates.io-index#zune-inflate@0.2.54": "00kg24jh3zqa3i6rg6yksnb71bch9yi1casqydl00s7nw8pk7avk"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
[package]
|
||||
name = "cyber-slides"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.13.0"
|
||||
cairo-rs = "0.18"
|
||||
cyberpunk = { path = "../cyberpunk" }
|
||||
gio = "0.18"
|
||||
glib = "0.18"
|
||||
gtk = { version = "0.7", package = "gtk4" }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_yml = "0.0.12"
|
|
@ -1,416 +0,0 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
io::Read,
|
||||
ops::Index,
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
sync::{Arc, RwLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use cairo::{Context, Rectangle};
|
||||
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text};
|
||||
use glib::{GString, Object};
|
||||
use gtk::{
|
||||
glib::{self, Propagation},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
EventControllerKey,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const FPS: u64 = 60;
|
||||
const PURPLE: (f64, f64, f64) = (0.7, 0., 1.);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum Position {
|
||||
Top,
|
||||
Middle,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Step {
|
||||
text: String,
|
||||
position: Position,
|
||||
transition: Duration,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Script(Vec<Step>);
|
||||
|
||||
impl Script {
|
||||
fn from_file(path: &Path) -> Result<Script, serde_yml::Error> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let mut f = File::open(path).unwrap();
|
||||
f.read_to_end(&mut buf).unwrap();
|
||||
let script = serde_yml::from_slice(&buf)?;
|
||||
Ok(Self(script))
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Step> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Script {
|
||||
fn default() -> Self {
|
||||
Self(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Script {
|
||||
type Output = Step;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
struct Fade {
|
||||
text: String,
|
||||
position: Position,
|
||||
duration: Duration,
|
||||
|
||||
start_time: Instant,
|
||||
}
|
||||
|
||||
trait Animation {
|
||||
fn position(&self) -> Position;
|
||||
|
||||
fn tick(&self, now: Instant, context: &Context, width: f64);
|
||||
}
|
||||
|
||||
impl Animation for Fade {
|
||||
fn position(&self) -> Position {
|
||||
self.position.clone()
|
||||
}
|
||||
|
||||
fn tick(&self, now: Instant, context: &Context, width: f64) {
|
||||
let total_frames = self.duration.as_secs() * FPS;
|
||||
let alpha_rate: f64 = 1. / total_frames as f64;
|
||||
|
||||
let frames = (now - self.start_time).as_secs_f64() * FPS as f64;
|
||||
let alpha = alpha_rate * frames as f64;
|
||||
|
||||
let text_display = Text::new(self.text.clone(), context, 64., width);
|
||||
let _ = context.move_to(0., text_display.extents().height());
|
||||
let _ = context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha);
|
||||
text_display.draw();
|
||||
}
|
||||
}
|
||||
|
||||
struct CrossFade {
|
||||
old_text: String,
|
||||
new_text: String,
|
||||
position: Position,
|
||||
duration: Duration,
|
||||
|
||||
start_time: Instant,
|
||||
}
|
||||
|
||||
impl Animation for CrossFade {
|
||||
fn position(&self) -> Position {
|
||||
self.position.clone()
|
||||
}
|
||||
|
||||
fn tick(&self, now: Instant, context: &Context, width: f64) {
|
||||
let total_frames = self.duration.as_secs() * FPS;
|
||||
let alpha_rate: f64 = 1. / total_frames as f64;
|
||||
|
||||
let frames = (now - self.start_time).as_secs_f64() * FPS as f64;
|
||||
let alpha = alpha_rate * frames as f64;
|
||||
|
||||
let text_display = Text::new(self.old_text.clone(), context, 64., width);
|
||||
let _ = context.move_to(0., text_display.extents().height());
|
||||
let _ = context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, 1. - alpha);
|
||||
text_display.draw();
|
||||
|
||||
let text_display = Text::new(self.new_text.clone(), context, 64., width);
|
||||
let _ = context.move_to(0., text_display.extents().height());
|
||||
let _ = context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha);
|
||||
text_display.draw();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CyberScreenState {
|
||||
script: Script,
|
||||
idx: Option<usize>,
|
||||
top: Option<Step>,
|
||||
middle: Option<Step>,
|
||||
bottom: Option<Step>,
|
||||
}
|
||||
|
||||
impl Default for CyberScreenState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
script: Script(vec![]),
|
||||
idx: None,
|
||||
top: None,
|
||||
middle: None,
|
||||
bottom: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CyberScreenState {
|
||||
fn new(script: Script) -> CyberScreenState {
|
||||
let mut s = CyberScreenState::default();
|
||||
s.script = script;
|
||||
s
|
||||
}
|
||||
|
||||
fn next_page(&mut self) -> Box<dyn Animation> {
|
||||
let idx = match self.idx {
|
||||
None => 0,
|
||||
Some(idx) => {
|
||||
if idx < self.script.len() {
|
||||
idx + 1
|
||||
} else {
|
||||
idx
|
||||
}
|
||||
}
|
||||
};
|
||||
self.idx = Some(idx);
|
||||
let step = self.script[idx].clone();
|
||||
|
||||
let (old, new) = match step.position {
|
||||
Position::Top => {
|
||||
let old = self.top.replace(step.clone());
|
||||
(old, step)
|
||||
}
|
||||
Position::Middle => {
|
||||
let old = self.middle.replace(step.clone());
|
||||
(old, step)
|
||||
}
|
||||
Position::Bottom => {
|
||||
let old = self.bottom.replace(step.clone());
|
||||
(old, step)
|
||||
}
|
||||
};
|
||||
|
||||
match old {
|
||||
Some(old) => Box::new(CrossFade {
|
||||
old_text: old.text.clone(),
|
||||
new_text: new.text.clone(),
|
||||
position: new.position,
|
||||
duration: new.transition,
|
||||
start_time: Instant::now(),
|
||||
}),
|
||||
None => Box::new(Fade {
|
||||
text: new.text.clone(),
|
||||
position: new.position,
|
||||
duration: new.transition,
|
||||
start_time: Instant::now(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CyberScreenPrivate {
|
||||
state: Rc<RefCell<CyberScreenState>>,
|
||||
// For crossfading to work, I have to detect that there is an old animation in a position, and
|
||||
// replace it with the new one.
|
||||
animations: Rc<RefCell<HashMap<Position, Box<dyn Animation>>>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for CyberScreenPrivate {
|
||||
const NAME: &'static str = "CyberScreen";
|
||||
type Type = CyberScreen;
|
||||
type ParentType = gtk::DrawingArea;
|
||||
}
|
||||
|
||||
impl ObjectImpl for CyberScreenPrivate {}
|
||||
impl WidgetImpl for CyberScreenPrivate {}
|
||||
impl DrawingAreaImpl for CyberScreenPrivate {}
|
||||
|
||||
impl CyberScreenPrivate {
|
||||
fn set_script(&self, script: Script) {
|
||||
*self.state.borrow_mut() = CyberScreenState::new(script);
|
||||
}
|
||||
|
||||
fn next_page(&self) {
|
||||
let transition = self.state.borrow_mut().next_page();
|
||||
self.animations
|
||||
.borrow_mut()
|
||||
.insert(transition.position(), transition);
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct CyberScreen(ObjectSubclass<CyberScreenPrivate>) @extends gtk::DrawingArea, gtk::Widget;
|
||||
}
|
||||
|
||||
impl CyberScreen {
|
||||
fn new(script: Script) -> Self {
|
||||
let s: Self = Object::builder().build();
|
||||
s.imp().set_script(script);
|
||||
|
||||
s.set_draw_func({
|
||||
let s = s.clone();
|
||||
move |_, context, width, height| {
|
||||
let now = Instant::now();
|
||||
let _ = context.set_source_rgb(0., 0., 0.);
|
||||
let _ = context.paint();
|
||||
|
||||
let pen = GlowPen::new(width, height, 2., 8., (0.7, 0., 1.));
|
||||
AsymLineCutout {
|
||||
orientation: gtk::Orientation::Horizontal,
|
||||
start_x: 25.,
|
||||
start_y: height as f64 / 7.,
|
||||
start_length: width as f64 / 3.,
|
||||
cutout_length: width as f64 / 3. - 100.,
|
||||
height: 50.,
|
||||
end_length: width as f64 / 3. - 50.,
|
||||
invert: false,
|
||||
}
|
||||
.draw(&pen);
|
||||
pen.stroke();
|
||||
|
||||
AsymLine {
|
||||
orientation: gtk::Orientation::Horizontal,
|
||||
start_x: width as f64 / 4.,
|
||||
start_y: height as f64 * 6. / 7.,
|
||||
start_length: width as f64 * 2. / 3. - 25.,
|
||||
height: 50.,
|
||||
end_length: 0.,
|
||||
invert: false,
|
||||
}
|
||||
.draw(&pen);
|
||||
pen.stroke();
|
||||
|
||||
let tracery = pen.finish();
|
||||
let _ = context.set_source(tracery);
|
||||
let _ = context.paint();
|
||||
|
||||
let mut animations = s.imp().animations.borrow_mut();
|
||||
|
||||
let lr_margin = 50.;
|
||||
let max_width = width as f64 - lr_margin * 2.;
|
||||
let region_height = height as f64 / 5.;
|
||||
|
||||
if let Some(animation) = animations.get(&Position::Top) {
|
||||
let y = height as f64 * 1. / 5.;
|
||||
let surface = context
|
||||
.target()
|
||||
.create_for_rectangle(Rectangle::new(20., y, max_width, region_height))
|
||||
.unwrap();
|
||||
let ctx = Context::new(&surface).unwrap();
|
||||
animation.tick(now, &ctx, max_width);
|
||||
}
|
||||
if let Some(animation) = animations.get(&Position::Middle) {
|
||||
let y = height as f64 * 2. / 5.;
|
||||
let surface = context
|
||||
.target()
|
||||
.create_for_rectangle(Rectangle::new(20., y, max_width, region_height))
|
||||
.unwrap();
|
||||
let ctx = Context::new(&surface).unwrap();
|
||||
animation.tick(now, &ctx, max_width);
|
||||
}
|
||||
if let Some(animation) = animations.get(&Position::Bottom) {
|
||||
let y = height as f64 * 3. / 5.;
|
||||
let surface = context
|
||||
.target()
|
||||
.create_for_rectangle(Rectangle::new(20., y, max_width, region_height))
|
||||
.unwrap();
|
||||
let ctx = Context::new(&surface).unwrap();
|
||||
animation.tick(now, &ctx, max_width);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn next_page(&self) {
|
||||
self.imp().next_page();
|
||||
self.queue_draw();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let script = Arc::new(RwLock::new(Script::default()));
|
||||
let app = gtk::Application::builder()
|
||||
.application_id("com.luminescent-dreams.cyberpunk-slideshow")
|
||||
.build();
|
||||
|
||||
app.add_main_option(
|
||||
"script",
|
||||
glib::char::Char::from(b's'),
|
||||
glib::OptionFlags::IN_MAIN,
|
||||
glib::OptionArg::String,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
|
||||
app.connect_handle_local_options({
|
||||
let script = script.clone();
|
||||
move |_, options| {
|
||||
if let Some(script_path) = options.lookup::<String>("script").unwrap() {
|
||||
let mut script = script.write().unwrap();
|
||||
*script = Script::from_file(Path::new(&script_path)).unwrap();
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.connect_activate(move |app| {
|
||||
let window = gtk::ApplicationWindow::new(app);
|
||||
let screen = CyberScreen::new(script.read().unwrap().clone());
|
||||
|
||||
let events = EventControllerKey::new();
|
||||
|
||||
events.connect_key_released({
|
||||
let app = app.clone();
|
||||
let window = window.clone();
|
||||
let screen = screen.clone();
|
||||
move |_, key, _, _| {
|
||||
let name = key
|
||||
.name()
|
||||
.map(|s| s.as_str().to_owned())
|
||||
.unwrap_or("".to_owned());
|
||||
match name.as_ref() {
|
||||
"Right" => screen.next_page(),
|
||||
"q" => app.quit(),
|
||||
"Escape" => window.unfullscreen(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.add_controller(events);
|
||||
|
||||
window.set_child(Some(&screen));
|
||||
window.set_width_request(800);
|
||||
window.set_height_request(600);
|
||||
window.present();
|
||||
|
||||
window.connect_maximized_notify(|window| {
|
||||
window.fullscreen();
|
||||
});
|
||||
|
||||
let _ = glib::spawn_future_local({
|
||||
let screen = screen.clone();
|
||||
async move {
|
||||
loop {
|
||||
screen.queue_draw();
|
||||
async_std::task::sleep(Duration::from_millis(1000 / FPS)).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.run();
|
||||
}
|
|
@ -8,7 +8,6 @@ license = "GPL-3.0-only"
|
|||
|
||||
[dependencies]
|
||||
cairo-rs = { version = "0.18" }
|
||||
cyberpunk = { path = "../cyberpunk" }
|
||||
gio = { version = "0.18" }
|
||||
glib = { version = "0.18" }
|
||||
gtk = { version = "0.7", package = "gtk4" }
|
||||
|
|
|
@ -2,7 +2,6 @@ use cairo::{
|
|||
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, LinearGradient, Pattern,
|
||||
TextExtents,
|
||||
};
|
||||
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, SlashMeter};
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*, EventControllerKey};
|
||||
use std::{
|
||||
|
@ -172,7 +171,7 @@ impl SplashPrivate {
|
|||
start_y: extents.height() + 10.,
|
||||
start_length: 0.,
|
||||
height: extents.height() / 2.,
|
||||
end_length: 0.,
|
||||
total_length: extents.width() + extents.height() / 2.,
|
||||
invert: false,
|
||||
}
|
||||
.draw(&pen);
|
||||
|
@ -184,7 +183,7 @@ impl SplashPrivate {
|
|||
start_y: extents.height() + 60.,
|
||||
start_length: extents.width(),
|
||||
height: extents.height() / 2.,
|
||||
end_length: 0.,
|
||||
total_length: extents.width() + extents.height() / 2.,
|
||||
invert: false,
|
||||
}
|
||||
.draw(&pen);
|
||||
|
@ -209,7 +208,7 @@ impl SplashPrivate {
|
|||
start_x: 20.,
|
||||
start_y: center_y - 20. - title_height / 2.,
|
||||
start_length,
|
||||
end_length: *self.width.borrow() as f64 - 120. - start_length,
|
||||
total_length: *self.width.borrow() as f64 - 120.,
|
||||
cutout_length: title_width,
|
||||
height: title_height,
|
||||
invert: false,
|
||||
|
@ -244,7 +243,7 @@ impl SplashPrivate {
|
|||
start_y: *self.height.borrow() as f64 / 2. + 100.,
|
||||
start_length: 400.,
|
||||
height: 50.,
|
||||
end_length: 0.,
|
||||
total_length: 650.,
|
||||
invert: true,
|
||||
}
|
||||
.draw(&pen);
|
||||
|
@ -259,7 +258,7 @@ impl SplashPrivate {
|
|||
start_y: *self.height.borrow() as f64 / 2. + 200.,
|
||||
start_length: 600.,
|
||||
height: 50.,
|
||||
end_length: 0.,
|
||||
total_length: 650.,
|
||||
invert: false,
|
||||
}
|
||||
.draw(&pen);
|
||||
|
@ -420,6 +419,212 @@ impl Splash {
|
|||
}
|
||||
}
|
||||
|
||||
struct AsymLineCutout {
|
||||
orientation: gtk::Orientation,
|
||||
start_x: f64,
|
||||
start_y: f64,
|
||||
start_length: f64,
|
||||
total_length: f64,
|
||||
cutout_length: f64,
|
||||
height: f64,
|
||||
invert: bool,
|
||||
}
|
||||
|
||||
impl AsymLineCutout {
|
||||
fn draw(&self, pen: &impl Pen) {
|
||||
let dodge = if self.invert {
|
||||
self.height
|
||||
} else {
|
||||
-self.height
|
||||
};
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
pen.move_to(self.start_x, self.start_y);
|
||||
pen.line_to(self.start_x + self.start_length, self.start_y);
|
||||
pen.line_to(
|
||||
self.start_x + self.start_length + self.height,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x + self.start_length + self.height + self.cutout_length,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x
|
||||
+ self.start_length
|
||||
+ self.height
|
||||
+ self.cutout_length
|
||||
+ (self.height / 2.),
|
||||
self.start_y + dodge / 2.,
|
||||
);
|
||||
pen.line_to(self.total_length, self.start_y + dodge / 2.);
|
||||
}
|
||||
gtk::Orientation::Vertical => {
|
||||
pen.move_to(self.start_x, self.start_y);
|
||||
pen.line_to(self.start_x, self.start_y + self.start_length);
|
||||
pen.line_to(
|
||||
self.start_x + dodge,
|
||||
self.start_y + self.start_length + self.height,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x + dodge,
|
||||
self.start_y + self.start_length + self.height + self.cutout_length,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x + dodge / 2.,
|
||||
self.start_y
|
||||
+ self.start_length
|
||||
+ self.height
|
||||
+ self.cutout_length
|
||||
+ (self.height / 2.),
|
||||
);
|
||||
pen.line_to(self.start_x + dodge / 2., self.total_length);
|
||||
}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AsymLine {
|
||||
orientation: gtk::Orientation,
|
||||
start_x: f64,
|
||||
start_y: f64,
|
||||
start_length: f64,
|
||||
height: f64,
|
||||
total_length: f64,
|
||||
invert: bool,
|
||||
}
|
||||
|
||||
impl AsymLine {
|
||||
fn draw(&self, pen: &impl Pen) {
|
||||
let dodge = if self.invert {
|
||||
self.height
|
||||
} else {
|
||||
-self.height
|
||||
};
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
pen.move_to(self.start_x, self.start_y);
|
||||
pen.line_to(self.start_x + self.start_length, self.start_y);
|
||||
pen.line_to(
|
||||
self.start_x + self.start_length + self.height,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
pen.line_to(self.start_x + self.total_length, self.start_y + dodge);
|
||||
}
|
||||
gtk::Orientation::Vertical => {}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SlashMeter {
|
||||
orientation: gtk::Orientation,
|
||||
start_x: f64,
|
||||
start_y: f64,
|
||||
count: u8,
|
||||
fill_count: u8,
|
||||
height: f64,
|
||||
length: f64,
|
||||
}
|
||||
|
||||
impl SlashMeter {
|
||||
fn draw(&self, context: &Context) {
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
let angle: f64 = 0.8;
|
||||
let run = self.height / angle.tan();
|
||||
let width = self.length / (self.count as f64 * 2.);
|
||||
|
||||
for c in 0..self.count {
|
||||
context.set_line_width(1.);
|
||||
|
||||
let start_x = self.start_x + c as f64 * width * 2.;
|
||||
context.move_to(start_x, self.start_y);
|
||||
context.line_to(start_x + run, self.start_y - self.height);
|
||||
context.line_to(start_x + run + width, self.start_y - self.height);
|
||||
context.line_to(start_x + width, self.start_y);
|
||||
context.line_to(start_x, self.start_y);
|
||||
if c < self.fill_count {
|
||||
let _ = context.fill();
|
||||
} else {
|
||||
let _ = context.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
gtk::Orientation::Vertical => {}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait Pen {
|
||||
fn move_to(&self, x: f64, y: f64);
|
||||
fn line_to(&self, x: f64, y: f64);
|
||||
fn stroke(&self);
|
||||
|
||||
fn finish(self) -> Pattern;
|
||||
}
|
||||
|
||||
struct GlowPen {
|
||||
blur_context: Context,
|
||||
draw_context: Context,
|
||||
}
|
||||
|
||||
impl GlowPen {
|
||||
fn new(
|
||||
width: i32,
|
||||
height: i32,
|
||||
line_width: f64,
|
||||
blur_line_width: f64,
|
||||
color: (f64, f64, f64),
|
||||
) -> Self {
|
||||
let blur_context =
|
||||
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
|
||||
blur_context.set_line_width(blur_line_width);
|
||||
blur_context.set_source_rgba(color.0, color.1, color.2, 0.5);
|
||||
blur_context.push_group();
|
||||
blur_context.set_line_cap(LineCap::Round);
|
||||
|
||||
let draw_context =
|
||||
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
|
||||
draw_context.set_line_width(line_width);
|
||||
draw_context.set_source_rgb(color.0, color.1, color.2);
|
||||
draw_context.push_group();
|
||||
draw_context.set_line_cap(LineCap::Round);
|
||||
|
||||
Self {
|
||||
blur_context,
|
||||
draw_context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pen for GlowPen {
|
||||
fn move_to(&self, x: f64, y: f64) {
|
||||
self.blur_context.move_to(x, y);
|
||||
self.draw_context.move_to(x, y);
|
||||
}
|
||||
|
||||
fn line_to(&self, x: f64, y: f64) {
|
||||
self.blur_context.line_to(x, y);
|
||||
self.draw_context.line_to(x, y);
|
||||
}
|
||||
|
||||
fn stroke(&self) {
|
||||
self.blur_context.stroke().expect("to draw the blur line");
|
||||
self.draw_context
|
||||
.stroke()
|
||||
.expect("to draw the regular line");
|
||||
}
|
||||
|
||||
fn finish(self) -> Pattern {
|
||||
let foreground = self.draw_context.pop_group().unwrap();
|
||||
self.blur_context.set_source(foreground).unwrap();
|
||||
self.blur_context.paint().unwrap();
|
||||
self.blur_context.pop_group().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = gtk::Application::builder()
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
name = "cyberpunk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cairo-rs = { version = "0.18" }
|
||||
gio = { version = "0.18" }
|
||||
glib = { version = "0.18" }
|
||||
gtk = { version = "0.7", package = "gtk4" }
|
|
@ -1,301 +0,0 @@
|
|||
use cairo::{
|
||||
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, Pattern,
|
||||
TextExtents,
|
||||
};
|
||||
|
||||
pub struct AsymLineCutout {
|
||||
pub orientation: gtk::Orientation,
|
||||
pub start_x: f64,
|
||||
pub start_y: f64,
|
||||
pub start_length: f64,
|
||||
pub cutout_length: f64,
|
||||
pub end_length: f64,
|
||||
pub height: f64,
|
||||
pub invert: bool,
|
||||
}
|
||||
|
||||
impl AsymLineCutout {
|
||||
pub fn draw(&self, pen: &impl Pen) {
|
||||
let dodge = if self.invert {
|
||||
self.height
|
||||
} else {
|
||||
-self.height
|
||||
};
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
pen.move_to(self.start_x, self.start_y);
|
||||
pen.line_to(self.start_x + self.start_length, self.start_y);
|
||||
pen.line_to(
|
||||
self.start_x + self.start_length + self.height,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x + self.start_length + self.height + self.cutout_length,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x
|
||||
+ self.start_length
|
||||
+ self.height
|
||||
+ self.cutout_length
|
||||
+ (self.height / 2.),
|
||||
self.start_y + dodge / 2.,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x
|
||||
+ self.start_length
|
||||
+ self.height
|
||||
+ self.cutout_length
|
||||
+ (self.height / 2.)
|
||||
+ self.end_length,
|
||||
self.start_y + dodge / 2.,
|
||||
);
|
||||
}
|
||||
gtk::Orientation::Vertical => {
|
||||
pen.move_to(self.start_x, self.start_y);
|
||||
pen.line_to(self.start_x, self.start_y + self.start_length);
|
||||
pen.line_to(
|
||||
self.start_x + dodge,
|
||||
self.start_y + self.start_length + self.height,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x + dodge,
|
||||
self.start_y + self.start_length + self.height + self.cutout_length,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x + dodge / 2.,
|
||||
self.start_y
|
||||
+ self.start_length
|
||||
+ self.height
|
||||
+ self.cutout_length
|
||||
+ (self.height / 2.),
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x + dodge / 2.,
|
||||
self.start_y
|
||||
+ self.start_length
|
||||
+ self.height
|
||||
+ self.cutout_length
|
||||
+ (self.height / 2.)
|
||||
+ self.end_length,
|
||||
);
|
||||
}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Represents an asymetrical line that starts at one location, then a 45-degree angle and then
|
||||
// another line afterwards.
|
||||
pub struct AsymLine {
|
||||
// Will this be drawn left-to-right or up-to-down?
|
||||
pub orientation: gtk::Orientation,
|
||||
|
||||
// Starting address
|
||||
pub start_x: f64,
|
||||
pub start_y: f64,
|
||||
|
||||
// Length of the first segment
|
||||
pub start_length: f64,
|
||||
|
||||
// Height to dodge over to the next section
|
||||
pub height: f64,
|
||||
|
||||
// Total length of the entire line.
|
||||
pub end_length: f64,
|
||||
|
||||
// When normal, the angle dodge is upwards. When inverted, the angle dodge is downwards.
|
||||
pub invert: bool,
|
||||
}
|
||||
|
||||
impl AsymLine {
|
||||
pub fn draw(&self, pen: &impl Pen) {
|
||||
let dodge = if self.invert {
|
||||
self.height
|
||||
} else {
|
||||
-self.height
|
||||
};
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
pen.move_to(self.start_x, self.start_y);
|
||||
pen.line_to(self.start_x + self.start_length, self.start_y);
|
||||
pen.line_to(
|
||||
self.start_x + self.start_length + self.height,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
pen.line_to(
|
||||
self.start_x + self.start_length + self.height + self.end_length,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
}
|
||||
gtk::Orientation::Vertical => {}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SlashMeter {
|
||||
pub orientation: gtk::Orientation,
|
||||
pub start_x: f64,
|
||||
pub start_y: f64,
|
||||
pub count: u8,
|
||||
pub fill_count: u8,
|
||||
pub height: f64,
|
||||
pub length: f64,
|
||||
}
|
||||
|
||||
impl SlashMeter {
|
||||
pub fn draw(&self, context: &Context) {
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
let angle: f64 = 0.8;
|
||||
let run = self.height / angle.tan();
|
||||
let width = self.length / (self.count as f64 * 2.);
|
||||
|
||||
for c in 0..self.count {
|
||||
context.set_line_width(1.);
|
||||
|
||||
let start_x = self.start_x + c as f64 * width * 2.;
|
||||
context.move_to(start_x, self.start_y);
|
||||
context.line_to(start_x + run, self.start_y - self.height);
|
||||
context.line_to(start_x + run + width, self.start_y - self.height);
|
||||
context.line_to(start_x + width, self.start_y);
|
||||
context.line_to(start_x, self.start_y);
|
||||
if c < self.fill_count {
|
||||
let _ = context.fill();
|
||||
} else {
|
||||
let _ = context.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
gtk::Orientation::Vertical => {}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a pen for drawing a pattern. This is good for complex patterns that may require
|
||||
/// multiple identical steps.
|
||||
pub trait Pen {
|
||||
/// Move the pen to a location.
|
||||
fn move_to(&self, x: f64, y: f64);
|
||||
|
||||
/// Draw a line from the current location to the specified destination.
|
||||
fn line_to(&self, x: f64, y: f64);
|
||||
|
||||
/// Instantiate the line.
|
||||
fn stroke(&self);
|
||||
|
||||
/// Convert all of the drawing into a pattern that can be painted to a drawing context.
|
||||
fn finish(self) -> Pattern;
|
||||
}
|
||||
|
||||
pub struct GlowPen {
|
||||
blur_context: Context,
|
||||
draw_context: Context,
|
||||
}
|
||||
|
||||
impl GlowPen {
|
||||
pub fn new(
|
||||
width: i32,
|
||||
height: i32,
|
||||
line_width: f64,
|
||||
blur_line_width: f64,
|
||||
color: (f64, f64, f64),
|
||||
) -> Self {
|
||||
let blur_context =
|
||||
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
|
||||
blur_context.set_line_width(blur_line_width);
|
||||
blur_context.set_source_rgba(color.0, color.1, color.2, 0.5);
|
||||
blur_context.push_group();
|
||||
blur_context.set_line_cap(LineCap::Round);
|
||||
|
||||
let draw_context =
|
||||
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
|
||||
draw_context.set_line_width(line_width);
|
||||
draw_context.set_source_rgb(color.0, color.1, color.2);
|
||||
draw_context.push_group();
|
||||
draw_context.set_line_cap(LineCap::Round);
|
||||
|
||||
Self {
|
||||
blur_context,
|
||||
draw_context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pen for GlowPen {
|
||||
fn move_to(&self, x: f64, y: f64) {
|
||||
self.blur_context.move_to(x, y);
|
||||
self.draw_context.move_to(x, y);
|
||||
}
|
||||
|
||||
fn line_to(&self, x: f64, y: f64) {
|
||||
self.blur_context.line_to(x, y);
|
||||
self.draw_context.line_to(x, y);
|
||||
}
|
||||
|
||||
fn stroke(&self) {
|
||||
self.blur_context.stroke().expect("to draw the blur line");
|
||||
self.draw_context
|
||||
.stroke()
|
||||
.expect("to draw the regular line");
|
||||
}
|
||||
|
||||
fn finish(self) -> Pattern {
|
||||
let foreground = self.draw_context.pop_group().unwrap();
|
||||
self.blur_context.set_source(foreground).unwrap();
|
||||
self.blur_context.paint().unwrap();
|
||||
self.blur_context.pop_group().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Text<'a> {
|
||||
content: Vec<String>,
|
||||
context: &'a Context,
|
||||
}
|
||||
|
||||
impl<'a> Text<'a> {
|
||||
pub fn new(content: String, context: &'a Context, size: f64, width: f64) -> Self {
|
||||
context.select_font_face("Alegreya Sans SC", FontSlant::Normal, FontWeight::Bold);
|
||||
context.set_font_size(size);
|
||||
|
||||
let lines = word_wrap(content, context, width);
|
||||
|
||||
Self { content: lines, context }
|
||||
}
|
||||
|
||||
pub fn extents(&self) -> TextExtents {
|
||||
self.context.text_extents(&self.content[0]).unwrap()
|
||||
}
|
||||
|
||||
pub fn draw(&self) {
|
||||
let mut baseline = 0.;
|
||||
for line in self.content.iter() {
|
||||
baseline += self.context.text_extents(line).unwrap().height() + 10.;
|
||||
self.context.move_to(0., baseline);
|
||||
let _ = self.context.show_text(&line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn word_wrap(content: String, context: &Context, max_width: f64) -> Vec<String> {
|
||||
let mut lines = vec![];
|
||||
let words: Vec<&str> = content.split_whitespace().collect();
|
||||
let mut start: usize = 0;
|
||||
let mut line = String::new();
|
||||
|
||||
for idx in 0..words.len() + 1 {
|
||||
line = words[start..idx].join(" ");
|
||||
let extents = context.text_extents(&line).unwrap();
|
||||
if extents.width() > max_width {
|
||||
let line = words[start..idx-1].join(" ");
|
||||
start = idx-1;
|
||||
lines.push(line.clone());
|
||||
}
|
||||
}
|
||||
if line.len() > 0 {
|
||||
lines.push(line);
|
||||
}
|
||||
lines
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
[package]
|
||||
name = "dashboard"
|
||||
version = "0.1.3"
|
||||
version = "0.1.2"
|
||||
edition = "2018"
|
||||
|
||||
# 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" ] }
|
||||
async-std = { version = "1.13" }
|
||||
cairo-rs = { version = "0.18" }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
fluent-ergonomics = { path = "../fluent-ergonomics/" }
|
||||
|
@ -18,11 +17,13 @@ gio = { version = "0.18" }
|
|||
glib = { version = "0.18" }
|
||||
gdk = { version = "0.7", package = "gdk4" }
|
||||
gtk = { version = "0.7", package = "gtk4" }
|
||||
ifc = { path = "../ifc/" }
|
||||
lazy_static = { version = "1.4" }
|
||||
memorycache = { path = "../memorycache/" }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
serde_derive = { version = "1" }
|
||||
serde_json = { version = "1" }
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
serde = { version = "1" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
unic-langid = { version = "0.9" }
|
||||
|
||||
|
|
|
@ -41,10 +41,7 @@ impl ApplicationWindow {
|
|||
.build();
|
||||
|
||||
let date_label = Date::default();
|
||||
let header = adw::HeaderBar::builder()
|
||||
.title_widget(&date_label)
|
||||
.build();
|
||||
layout.append(&header);
|
||||
layout.append(&date_label);
|
||||
|
||||
let events = Events::default();
|
||||
layout.append(&events);
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use chrono::Datelike;
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use ifc::IFC;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
pub struct DatePrivate {
|
||||
date: Rc<RefCell<NaiveDate>>,
|
||||
date: Rc<RefCell<IFC>>,
|
||||
label: Rc<RefCell<gtk::Label>>,
|
||||
}
|
||||
|
||||
impl Default for DatePrivate {
|
||||
fn default() -> Self {
|
||||
let date = chrono::Local::now().date_naive();
|
||||
let year = date.year();
|
||||
let date = date.with_year(year + 10000).unwrap();
|
||||
Self {
|
||||
date: Rc::new(RefCell::new(date)),
|
||||
date: Rc::new(RefCell::new(IFC::from(date))),
|
||||
label: Rc::new(RefCell::new(gtk::Label::new(None))),
|
||||
}
|
||||
}
|
||||
|
@ -50,16 +52,19 @@ impl Default for Date {
|
|||
}
|
||||
|
||||
impl Date {
|
||||
pub fn update_date(&self, date: NaiveDate) {
|
||||
pub fn update_date(&self, date: IFC) {
|
||||
*self.imp().date.borrow_mut() = date;
|
||||
self.redraw();
|
||||
}
|
||||
|
||||
fn redraw(&self) {
|
||||
let date = self.imp().date.borrow();
|
||||
self.imp()
|
||||
.label
|
||||
.borrow_mut()
|
||||
.set_text(&date.format("%Y %B %d").to_string());
|
||||
let date = self.imp().date.borrow().clone();
|
||||
self.imp().label.borrow_mut().set_text(&format!(
|
||||
"{:?}, {:?} {}, {}",
|
||||
date.weekday(),
|
||||
date.month(),
|
||||
date.day(),
|
||||
date.year()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::{
|
|||
};
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use ifc::IFC;
|
||||
|
||||
/*
|
||||
#[derive(PartialEq)]
|
||||
|
@ -58,19 +59,19 @@ impl Events {
|
|||
pub fn set_events(&self, events: YearlyEvents, next_event: solstices::Event) {
|
||||
self.imp()
|
||||
.spring_equinox
|
||||
.update_date(events.spring_equinox.date_naive());
|
||||
.update_date(IFC::from(events.spring_equinox.date_naive()));
|
||||
|
||||
self.imp()
|
||||
.summer_solstice
|
||||
.update_date(events.summer_solstice.date_naive());
|
||||
.update_date(IFC::from(events.summer_solstice.date_naive()));
|
||||
|
||||
self.imp()
|
||||
.autumn_equinox
|
||||
.update_date(events.autumn_equinox.date_naive());
|
||||
.update_date(IFC::from(events.autumn_equinox.date_naive()));
|
||||
|
||||
self.imp()
|
||||
.winter_solstice
|
||||
.update_date(events.winter_solstice.date_naive());
|
||||
.update_date(IFC::from(events.winter_solstice.date_naive()));
|
||||
|
||||
self.imp().spring_equinox.remove_css_class("highlight");
|
||||
self.imp().summer_solstice.remove_css_class("highlight");
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use chrono::{Datelike, Local, Utc};
|
||||
use geo_types::{Latitude, Longitude};
|
||||
use glib::Sender;
|
||||
use gtk::prelude::*;
|
||||
use ifc::IFC;
|
||||
use std::{
|
||||
env,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use async_std::channel::Sender;
|
||||
use chrono::{Datelike, Local, Utc};
|
||||
use geo_types::{Latitude, Longitude};
|
||||
use gtk::prelude::*;
|
||||
|
||||
mod app_window;
|
||||
use app_window::ApplicationWindow;
|
||||
|
||||
|
@ -102,17 +102,14 @@ pub fn main() {
|
|||
|
||||
let now = Local::now();
|
||||
let state = State {
|
||||
date: now.date_naive(),
|
||||
date: IFC::from(now.date_naive().with_year(now.year() + 10000).unwrap()),
|
||||
next_event: EVENTS.next_event(now.with_timezone(&Utc)).unwrap(),
|
||||
events: EVENTS.yearly_events(now.year()).unwrap(),
|
||||
transit: Some(transit),
|
||||
};
|
||||
|
||||
let gtk_tx = core.tx.read().unwrap().clone();
|
||||
|
||||
if let Some(gtk_tx) = gtk_tx {
|
||||
let state = state.clone();
|
||||
let _ = gtk_tx.send(Message::Refresh(state)).await;
|
||||
if let Some(ref gtk_tx) = *core.tx.read().unwrap() {
|
||||
let _ = gtk_tx.send(Message::Refresh(state.clone()));
|
||||
std::thread::sleep(std::time::Duration::from_secs(60));
|
||||
} else {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
@ -122,17 +119,21 @@ pub fn main() {
|
|||
});
|
||||
|
||||
app.connect_activate(move |app| {
|
||||
let (gtk_tx, gtk_rx) = async_std::channel::unbounded();
|
||||
let (gtk_tx, gtk_rx) =
|
||||
gtk::glib::MainContext::channel::<Message>(gtk::glib::Priority::DEFAULT);
|
||||
|
||||
*core.tx.write().unwrap() = Some(gtk_tx);
|
||||
|
||||
let window = ApplicationWindow::new(app);
|
||||
window.window.present();
|
||||
|
||||
glib::spawn_future_local(async move {
|
||||
loop {
|
||||
let Message::Refresh(state) = gtk_rx.recv().await.unwrap();
|
||||
window.update_state(state);
|
||||
gtk_rx.attach(None, {
|
||||
let window = window.clone();
|
||||
move |msg| {
|
||||
let Message::Refresh(state) = msg;
|
||||
ApplicationWindow::update_state(&window, state);
|
||||
|
||||
glib::ControlFlow::Continue
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// http://astropixels.com/ephemeris/soleq2001.html
|
||||
const SOLSTICE_TEXT: &str = "
|
||||
|
|
|
@ -2,11 +2,11 @@ use crate::{
|
|||
solstices::{Event, YearlyEvents},
|
||||
soluna_client::SunMoon,
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use ifc::IFC;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct State {
|
||||
pub date: NaiveDate,
|
||||
pub date: IFC,
|
||||
pub next_event: Event,
|
||||
pub events: YearlyEvents,
|
||||
pub transit: Option<SunMoon>,
|
||||
|
|
18
flake.lock
18
flake.lock
|
@ -5,11 +5,11 @@
|
|||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -35,11 +35,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1714906307,
|
||||
"narHash": "sha256-UlRZtrCnhPFSJlDQE7M0eyhgvuuHBTe1eJ9N9AQlJQ0=",
|
||||
"lastModified": 1681303793,
|
||||
"narHash": "sha256-JEdQHsYuCfRL2PICHlOiH/2ue3DwoxUX7DJ6zZxZXFk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "25865a40d14b3f9cf19f19b924e2ab4069b09588",
|
||||
"rev": "fe2ecaf706a5907b5e54d979fbde4924d84b65fc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -76,11 +76,11 @@
|
|||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731966246,
|
||||
"narHash": "sha256-e/V7Ffm5wPd9DVzCThnPZ7lFxd43bb64tSk8/oGP4Ag=",
|
||||
"lastModified": 1698205128,
|
||||
"narHash": "sha256-jP+81TkldLtda8bzmsBWahETGsyFkoDOCT244YkA+S4=",
|
||||
"owner": "1Password",
|
||||
"repo": "typeshare",
|
||||
"rev": "e0e5f27ee34d7d4da76a9dc96a11552e98be56da",
|
||||
"rev": "c3ee2ad8f27774c45db7af4f2ba746c4ae11de21",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -44,9 +44,7 @@
|
|||
pkgs.sqlx-cli
|
||||
pkgs.udev
|
||||
pkgs.wasm-pack
|
||||
pkgs.go-task
|
||||
typeshare.packages."x86_64-linux".default
|
||||
pkgs.nodePackages_latest.typescript-language-server
|
||||
];
|
||||
LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
|
||||
ENV = "dev";
|
||||
|
@ -82,7 +80,6 @@
|
|||
};
|
||||
|
||||
in rec {
|
||||
cyber-slides = cargo_nix.workspaceMembers.cyber-slides.build;
|
||||
cyberpunk-splash = cargo_nix.workspaceMembers.cyberpunk-splash.build;
|
||||
dashboard = cargo_nix.workspaceMembers.dashboard.build;
|
||||
file-service = cargo_nix.workspaceMembers.file-service.build;
|
||||
|
@ -92,7 +89,6 @@
|
|||
all = pkgs.symlinkJoin {
|
||||
name = "all";
|
||||
paths = [
|
||||
cyber-slides
|
||||
cyberpunk-splash
|
||||
dashboard
|
||||
file-service
|
||||
|
|
|
@ -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"
|
|
@ -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(())
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -1,46 +0,0 @@
|
|||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
|
@ -1,43 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
|
@ -1,5 +0,0 @@
|
|||
.layout {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import Dashboard from './Dashboard';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<Dashboard />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
|
@ -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 {
|
||||
}
|
||||
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import Dashboard from './Dashboard';
|
||||
import Design from './Design';
|
||||
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} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1 +0,0 @@
|
|||
/// <reference types="react-scripts" />
|
|
@ -1,15 +0,0 @@
|
|||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
|
@ -1,5 +0,0 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
[build]
|
||||
target = "thumbv6m-none-eabi"
|
||||
|
||||
[target.thumbv6m-none-eabi]
|
||||
rustflags = [
|
||||
"-C", "link-arg=--nmagic",
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
"-C", "llvm-args=--inline-threshold=5",
|
||||
"-C", "no-vectorize-loops",
|
||||
]
|
||||
|
||||
runner = "elf2uf2-rs -d"
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "halloween-leds"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cortex-m-rt = "0.7.3"
|
||||
embedded-hal = "1.0.0"
|
||||
embedded-io = "0.6.1"
|
||||
panic-halt = "1.0.0"
|
||||
rp-pico = "0.9.0"
|
|
@ -1,125 +0,0 @@
|
|||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
/// This application demonstrates using a Raspberry Pi Pico to control an individual SK9822 module.
|
||||
/// Keep in mind that the Pico, though it accepts 5V for power, it runs on 3.3V logic. The GPIO
|
||||
/// pins will emit only 3.3 volts, and the SK9822 needs 5V logic. So, make sure that the GPIO pins
|
||||
/// run through a transistor or a logic level lhifter to go from 3.3V logic to 5V logic.
|
||||
use embedded_hal::{delay::DelayNs, spi::SpiBus};
|
||||
use panic_halt as _;
|
||||
use rp_pico::{
|
||||
entry,
|
||||
hal::{
|
||||
clocks::init_clocks_and_plls,
|
||||
fugit::RateExtU32,
|
||||
gpio::{
|
||||
bank0::{Gpio10, Gpio11},
|
||||
FunctionSpi, Pin, PullDown,
|
||||
},
|
||||
spi::Spi,
|
||||
Clock, Sio, Timer, Watchdog,
|
||||
},
|
||||
pac, Pins,
|
||||
};
|
||||
|
||||
const FPS: u32 = 60;
|
||||
const MS_PER_FRAME: u32 = 1000 / FPS;
|
||||
const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // MHz, https://forums.raspberrypi.com/viewtopic.php?t=356764
|
||||
|
||||
#[entry]
|
||||
unsafe fn main() -> ! {
|
||||
// rp_pico::pac::Peripherals is a reference to physical hardware defined on the Pico.
|
||||
let mut peripherals = pac::Peripherals::take().unwrap();
|
||||
|
||||
// SIO inidcates "Single Cycle IO". I don't know what this means, but it could mean that this
|
||||
// is a class of IO operations that can be run in a single clock cycle, such as switching a
|
||||
// GPIO pin on or off.
|
||||
let sio = Sio::new(peripherals.SIO);
|
||||
|
||||
// Many of the following systems require a watchdog. I do not know what this does, either, but
|
||||
// it may be some failsafe software that will reset operations if the watchdog detects a lack
|
||||
// of activity.
|
||||
let mut watchdog = Watchdog::new(peripherals.WATCHDOG);
|
||||
|
||||
// Here we grab the GPIO pins in bank 0.
|
||||
let pins = Pins::new(
|
||||
peripherals.IO_BANK0,
|
||||
peripherals.PADS_BANK0,
|
||||
sio.gpio_bank0,
|
||||
&mut peripherals.RESETS,
|
||||
);
|
||||
|
||||
// Initialize an abstraction of the clock system with a batch of standard hardware clocks.
|
||||
let clocks = init_clocks_and_plls(
|
||||
XOSC_CRYSTAL_FREQ,
|
||||
peripherals.XOSC,
|
||||
peripherals.CLOCKS,
|
||||
peripherals.PLL_SYS,
|
||||
peripherals.PLL_USB,
|
||||
&mut peripherals.RESETS,
|
||||
&mut watchdog,
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
|
||||
// An abstraction for a timer which we can use to delay the code.
|
||||
let mut timer = Timer::new(peripherals.TIMER, &mut peripherals.RESETS, &clocks);
|
||||
|
||||
// Grab the clock and data pins for SPI1. For Clock pins and for Data pins, there are only two
|
||||
// pins each on the Pico which can function for SPI1.
|
||||
let spi_clk: Pin<Gpio10, FunctionSpi, PullDown> = pins.gpio10.into_function();
|
||||
let spi_sdo: Pin<Gpio11, FunctionSpi, PullDown> = pins.gpio11.into_function();
|
||||
|
||||
// Now, create the SPI function abstraction for SPI1 with spi_clk and spi_sdo.
|
||||
let mut spi = Spi::<_, _, _, 8>::new(peripherals.SPI1, (spi_sdo, spi_clk)).init(
|
||||
&mut peripherals.RESETS,
|
||||
// The SPI system uses the peripheral clock
|
||||
clocks.peripheral_clock.freq(),
|
||||
// Transmit data at a rate of 1Mbit.
|
||||
1_u32.MHz(),
|
||||
// Run with SPI Mode 1. This means that the clock line should start high and that data will
|
||||
// be sampled starting at the first falling edge.
|
||||
embedded_hal::spi::MODE_1,
|
||||
);
|
||||
|
||||
// byte count: 4 for the start frame
|
||||
// 1 * 4 for three lights with 4 bytes per light
|
||||
// 4 for the end frame
|
||||
// = 20 bytes
|
||||
let mut lights: [u8; 12] = [0; 12];
|
||||
// We just skip the first four bytes, because the start frame is four bytes of 0.
|
||||
|
||||
// Set the first byte of the one and only lamp. The first byte follows the pattern of three 1
|
||||
// bits followed by five additional bits that indicate an overall brightness level of the
|
||||
// pixel. The datasheet for the SK9822 doesn't specify the exact effect, but it does mean that
|
||||
// the higher this number is, the brighter 255 means for an given LED in the array. 1 is the
|
||||
// lowest brightness that emits light, and 31 is the highest supported brightness.
|
||||
lights[4] = 0xe0 + 1;
|
||||
// Set the Blue light of the dotstar to 255, assuming the dotstar frame format is RBG. Note
|
||||
// that the standard SK9822 datasheed indicates that the format is BGR. Your mileage may vary.
|
||||
lights[6] = 255;
|
||||
|
||||
// The end frame is four bytes of 255.
|
||||
lights[8] = 0xff;
|
||||
lights[9] = 0xff;
|
||||
lights[10] = 0xff;
|
||||
lights[11] = 0xff;
|
||||
|
||||
|
||||
// The rest of this is just a stock pulsating animation which is slightly brightening and
|
||||
// dimming the *blue* LED (on my set of dotstars).
|
||||
let mut brightness = 1;
|
||||
let mut step = 1;
|
||||
loop {
|
||||
if brightness == 64 && step == 1 {
|
||||
step = -1;
|
||||
} else if brightness == 1 && step == -1 {
|
||||
step = 1;
|
||||
};
|
||||
lights[5] = brightness as u8;
|
||||
brightness = brightness + step;
|
||||
|
||||
let _ = spi.write(lights.as_slice());
|
||||
timer.delay_ms(MS_PER_FRAME);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
[package]
|
||||
name = "lights-core"
|
||||
name = "ray-tracer"
|
||||
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" }
|
||||
rayon = "1.10.0"
|
||||
|
||||
[[bin]]
|
||||
name = "projectile"
|
|
@ -0,0 +1,53 @@
|
|||
use ray_tracer::{types::*, PPM};
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Projectile {
|
||||
position: Point,
|
||||
velocity: Vector,
|
||||
}
|
||||
|
||||
struct Environment {
|
||||
gravity: Vector,
|
||||
wind: Vector,
|
||||
}
|
||||
|
||||
fn tick(env: &Environment, proj: &Projectile) -> Projectile {
|
||||
let position = proj.position + proj.velocity;
|
||||
let velocity = proj.velocity + env.gravity + env.wind;
|
||||
Projectile { position, velocity }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut canvas = Canvas::new(900, 550);
|
||||
|
||||
let start = Projectile {
|
||||
position: Point::new(0., 1., 0.),
|
||||
velocity: Vector::new(1., 5., 0.).normalize() * 5.,
|
||||
};
|
||||
|
||||
let e = Environment {
|
||||
gravity: Vector::new(0., -0.1, 0.),
|
||||
wind: Vector::new(-0.01, 0., 0.),
|
||||
};
|
||||
|
||||
let mut p = start;
|
||||
while p.position.y() > 0. {
|
||||
p = tick(&e, &p);
|
||||
|
||||
let x = p.position.x().round() as usize;
|
||||
let y = p.position.y().round() as usize;
|
||||
if x > 1 && x < 900 && y > 1 && y < 550 {
|
||||
*canvas.pixel_mut(x, 550 - y - 1) = Color::new(1., 1., 0.);
|
||||
*canvas.pixel_mut(x+1, 550 - y - 1) = Color::new(1., 1., 0.);
|
||||
*canvas.pixel_mut(x-1, 550 - y - 1) = Color::new(1., 1., 0.);
|
||||
*canvas.pixel_mut(x, 550 - y) = Color::new(1., 1., 0.);
|
||||
*canvas.pixel_mut(x, 550 - y - 2) = Color::new(1., 1., 0.);
|
||||
}
|
||||
}
|
||||
|
||||
let ppm = PPM::from(&canvas);
|
||||
|
||||
let mut file = File::create("projectile.ppm").unwrap();
|
||||
let _ = file.write(ppm.as_bytes());
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
use ray_tracer::{types::*, PPM};
|
||||
use rayon::prelude::*;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Write,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
const SIZE: usize = 1000;
|
||||
|
||||
const EPSILON: f64 = 0.00001;
|
||||
|
||||
fn eq_f64(l: f64, r: f64) -> bool {
|
||||
(l - r).abs() < EPSILON
|
||||
}
|
||||
|
||||
|
||||
fn render(
|
||||
camera: &Point,
|
||||
light: &PointLight,
|
||||
sphere: &Sphere,
|
||||
wall_z: f64,
|
||||
wall_size: f64,
|
||||
filename: &str,
|
||||
) {
|
||||
let canvas = Arc::new(RwLock::new(Canvas::new(SIZE, SIZE)));
|
||||
|
||||
let pixel_size = wall_size / SIZE as f64;
|
||||
let half = wall_size / 2.;
|
||||
|
||||
let ray = Ray::new(camera.clone(), (Point::new(0., 0., wall_z) - camera).normalize());
|
||||
let xs = ray.intersect(sphere);
|
||||
/*
|
||||
match xs.hit() {
|
||||
Some(hit) => {
|
||||
let point = ray.position(hit.t);
|
||||
let normal = hit.object.normal_at(&point);
|
||||
let eye = -ray.direction;
|
||||
let color = hit.object.material().lighting(&light, &point, &eye, &normal);
|
||||
println!("{:?}", color);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
*/
|
||||
|
||||
(0..SIZE).into_par_iter().for_each(|x| {
|
||||
(0..SIZE).into_par_iter().for_each(|y| {
|
||||
let world_x = -half + pixel_size * x as f64;
|
||||
let world_y = half - pixel_size * y as f64;
|
||||
let position = Point::new(world_x, world_y, wall_z);
|
||||
|
||||
let ray = Ray::new(camera.clone(), (position - camera).normalize());
|
||||
let xs = ray.intersect(sphere);
|
||||
match xs.hit() {
|
||||
Some(hit) => {
|
||||
let point = ray.position(hit.t);
|
||||
let normal = hit.object.normal_at(&point);
|
||||
let eye = -ray.direction;
|
||||
|
||||
let color = hit
|
||||
.object
|
||||
.material()
|
||||
.lighting(&light, &point, &eye, &normal);
|
||||
*canvas.write().unwrap().pixel_mut(x, y) = color;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let ppm = PPM::from(&*canvas.read().unwrap());
|
||||
let mut file = File::create(filename).unwrap();
|
||||
let _ = file.write(ppm.as_bytes());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut sphere = Sphere::default();
|
||||
|
||||
let mut material = Material::default();
|
||||
material.color = Color::new(1., 0.2, 1.);
|
||||
*sphere.material_mut() = material;
|
||||
|
||||
let camera = Point::new(0., 0., -5.);
|
||||
let wall_z = 10.;
|
||||
let wall_size = 7.;
|
||||
let light = PointLight::new(Point::new(-5., 5., -10.), Color::new(1., 1., 1.));
|
||||
|
||||
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_1.ppm");
|
||||
/*
|
||||
{
|
||||
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
|
||||
println!("Light at {:0.2} {:0.2} {:0.2}", light.position.x(), light.position.y(), light.position.z());
|
||||
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_1.ppm");
|
||||
println!();
|
||||
}
|
||||
|
||||
{
|
||||
let light = PointLight::new(Point::new(-2., 0., -10.), Color::new(1., 1., 1.));
|
||||
println!("Light at {:0.2} {:0.2} {:0.2}", light.position.x(), light.position.y(), light.position.z());
|
||||
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_2.ppm");
|
||||
println!();
|
||||
}
|
||||
|
||||
{
|
||||
let light = PointLight::new(Point::new(2., 0., -10.), Color::new(1., 1., 1.));
|
||||
println!("Light at {:0.2} {:0.2} {:0.2}", light.position.x(), light.position.y(), light.position.z());
|
||||
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_3.ppm");
|
||||
println!();
|
||||
}
|
||||
|
||||
{
|
||||
let light = PointLight::new(Point::new(0., -2., -10.), Color::new(1., 1., 1.));
|
||||
println!("Light at {:0.2} {:0.2} {:0.2}", light.position.x(), light.position.y(), light.position.z());
|
||||
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_4.ppm");
|
||||
println!();
|
||||
}
|
||||
|
||||
{
|
||||
let light = PointLight::new(Point::new(0., 2., -10.), Color::new(1., 1., 1.));
|
||||
println!("Light at {:0.2} {:0.2} {:0.2}", light.position.x(), light.position.y(), light.position.z());
|
||||
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_5.ppm");
|
||||
println!();
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
mod ppm;
|
||||
pub mod transforms;
|
||||
pub mod types;
|
||||
|
||||
pub use ppm::PPM;
|
|
@ -0,0 +1,118 @@
|
|||
use crate::types::Canvas;
|
||||
|
||||
fn color_float_to_int(val: f64) -> u8 {
|
||||
(val * 256.).clamp(0., 255.) as u8
|
||||
}
|
||||
|
||||
fn join_to_line_limit(data: impl IntoIterator<Item = String>) -> Vec<String> {
|
||||
let mut lines = vec![];
|
||||
|
||||
let mut line = String::new();
|
||||
for element in data {
|
||||
if line.is_empty() {
|
||||
line = line + &element;
|
||||
} else if line.len() + 1 + element.len() < 70 {
|
||||
line = line + " " + &element;
|
||||
} else {
|
||||
lines.push(line);
|
||||
line = element;
|
||||
}
|
||||
}
|
||||
|
||||
if !line.is_empty() {
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PPM(String);
|
||||
|
||||
impl From<&Canvas> for PPM {
|
||||
fn from(c: &Canvas) -> Self {
|
||||
// let v = vec![0.; c.width() * c.height() * 3];
|
||||
let header = format!("P3\n{} {}\n255\n", c.width(), c.height());
|
||||
|
||||
let mut data = vec![];
|
||||
for y in 0..c.height() {
|
||||
let mut row = vec![];
|
||||
for x in 0..c.width() {
|
||||
let pixel = c.pixel(x, y);
|
||||
row.push(color_float_to_int(pixel.red()).to_string());
|
||||
row.push(color_float_to_int(pixel.green()).to_string());
|
||||
row.push(color_float_to_int(pixel.blue()).to_string());
|
||||
}
|
||||
let mut lines = join_to_line_limit(row);
|
||||
data.append(&mut lines);
|
||||
}
|
||||
|
||||
let data = data.join("\n");
|
||||
|
||||
Self(format!("{header}{data}\n"))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for PPM {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &String {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::Color;
|
||||
|
||||
#[test]
|
||||
fn construct_ppm_header() {
|
||||
let c = Canvas::new(5, 3);
|
||||
let ppm = PPM::from(&c);
|
||||
|
||||
assert!(ppm.starts_with(
|
||||
"P3\n\
|
||||
5 3\n\
|
||||
255\n"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_full_ppm_data() {
|
||||
let mut c = Canvas::new(5, 3);
|
||||
*c.pixel_mut(0, 0) = Color::new(1.5, 0., 0.);
|
||||
*c.pixel_mut(2, 1) = Color::new(0., 0.5, 0.);
|
||||
*c.pixel_mut(4, 2) = Color::new(-0.5, 0., 1.);
|
||||
|
||||
let expected = "P3\n\
|
||||
5 3\n\
|
||||
255\n\
|
||||
255 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n\
|
||||
0 0 0 0 0 0 0 128 0 0 0 0 0 0 0\n\
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 255\n";
|
||||
|
||||
let ppm = PPM::from(&c);
|
||||
assert_eq!(*ppm, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ppm_line_length_limit() {
|
||||
let mut c = Canvas::new(10, 2);
|
||||
for y in 0..2 {
|
||||
for x in 0..10 {
|
||||
*c.pixel_mut(x, y) = Color::new(1., 0.8, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
let expected = "P3\n\
|
||||
10 2\n\
|
||||
255\n\
|
||||
255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n\
|
||||
153 255 204 153 255 204 153 255 204 153 255 204 153\n\
|
||||
255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n\
|
||||
153 255 204 153 255 204 153 255 204 153 255 204 153\n";
|
||||
|
||||
let ppm = PPM::from(&c);
|
||||
assert_eq!(*ppm, expected);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
use crate::types::Matrix;
|
||||
|
||||
pub fn translation(x: f64, y: f64, z: f64) -> Matrix {
|
||||
Matrix::from([
|
||||
[1., 0., 0., x],
|
||||
[0., 1., 0., y],
|
||||
[0., 0., 1., z],
|
||||
[0., 0., 0., 1.],
|
||||
])
|
||||
}
|
||||
|
||||
pub fn scaling(x: f64, y: f64, z: f64) -> Matrix {
|
||||
Matrix::from([
|
||||
[x, 0., 0., 0.],
|
||||
[0., y, 0., 0.],
|
||||
[0., 0., z, 0.],
|
||||
[0., 0., 0., 1.],
|
||||
])
|
||||
}
|
||||
|
||||
pub fn rotation_x(r: f64) -> Matrix {
|
||||
Matrix::from([
|
||||
[1., 0., 0., 0.],
|
||||
[0., r.cos(), -r.sin(), 0.],
|
||||
[0., r.sin(), r.cos(), 0.],
|
||||
[0., 0., 0., 1.],
|
||||
])
|
||||
}
|
||||
|
||||
pub fn rotation_y(r: f64) -> Matrix {
|
||||
Matrix::from([
|
||||
[r.cos(), 0., r.sin(), 0.],
|
||||
[0., 1., 0., 0.],
|
||||
[-r.sin(), 0., r.cos(), 0.],
|
||||
[0., 0., 0., 1.],
|
||||
])
|
||||
}
|
||||
|
||||
pub fn rotation_z(r: f64) -> Matrix {
|
||||
Matrix::from([
|
||||
[r.cos(), -r.sin(), 0., 0.],
|
||||
[r.sin(), r.cos(), 0., 0.],
|
||||
[0., 0., 1., 0.],
|
||||
[0., 0., 0., 1.],
|
||||
])
|
||||
}
|
||||
|
||||
pub fn shearing(
|
||||
x_by_y: f64,
|
||||
x_by_z: f64,
|
||||
y_by_x: f64,
|
||||
y_by_z: f64,
|
||||
z_by_x: f64,
|
||||
z_by_y: f64,
|
||||
) -> Matrix {
|
||||
Matrix::from([
|
||||
[1., x_by_y, x_by_z, 0.],
|
||||
[y_by_x, 1., y_by_z, 0.],
|
||||
[z_by_x, z_by_y, 1., 0.],
|
||||
[0., 0., 0., 1.],
|
||||
])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::{Point, Vector};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
#[test]
|
||||
fn multiply_by_translation_matrix() {
|
||||
let tr = translation(5., -3., 2.);
|
||||
let p = Point::new(-3., 4., 5.);
|
||||
assert_eq!(&tr * p, Point::new(2., 1., 7.));
|
||||
|
||||
let inv = tr.inverse();
|
||||
assert_eq!(inv * p, Point::new(-8., 7., 3.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn translation_does_not_affect_vectors() {
|
||||
let tr = translation(5., -3., 2.);
|
||||
let v = Vector::new(-3., 4., 5.);
|
||||
assert_eq!(tr * v, v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scaling_matrix_applied_to_point() {
|
||||
let tr = scaling(2., 3., 4.);
|
||||
let p = Point::new(-4., 6., 8.);
|
||||
let v = Vector::new(-4., 6., 8.);
|
||||
|
||||
assert_eq!(&tr * p, Point::new(-8., 18., 32.));
|
||||
assert_eq!(tr * v, Vector::new(-8., 18., 32.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflection_is_scaling_by_negative_value() {
|
||||
let tr = scaling(-1., 1., 1.);
|
||||
let p = Point::new(2., 3., 4.);
|
||||
assert_eq!(tr * p, Point::new(-2., 3., 4.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_point_around_x() {
|
||||
let p = Point::new(0., 1., 0.);
|
||||
let half_quarter = rotation_x(PI / 4.);
|
||||
let full_quarter = rotation_x(PI / 2.);
|
||||
assert_eq!(
|
||||
half_quarter * p,
|
||||
Point::new(0., 2_f64.sqrt() / 2., 2_f64.sqrt() / 2.)
|
||||
);
|
||||
assert_eq!(full_quarter * p, Point::new(0., 0., 1.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inverse_rotates_opposite_direction() {
|
||||
let p = Point::new(0., 1., 0.);
|
||||
let half_quarter = rotation_x(PI / 4.);
|
||||
let inv = half_quarter.inverse();
|
||||
assert_eq!(
|
||||
inv * p,
|
||||
Point::new(0., 2_f64.sqrt() / 2., -2_f64.sqrt() / 2.)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_around_y() {
|
||||
let p = Point::new(0., 0., 1.);
|
||||
let half_quarter = rotation_y(PI / 4.);
|
||||
let full_quarter = rotation_y(PI / 2.);
|
||||
assert_eq!(
|
||||
half_quarter * p,
|
||||
Point::new(2_f64.sqrt() / 2., 0., 2_f64.sqrt() / 2.)
|
||||
);
|
||||
assert_eq!(full_quarter * p, Point::new(1., 0., 0.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_around_z() {
|
||||
let p = Point::new(0., 1., 0.);
|
||||
let half_quarter = rotation_z(PI / 4.);
|
||||
let full_quarter = rotation_z(PI / 2.);
|
||||
assert_eq!(
|
||||
half_quarter * p,
|
||||
Point::new(-2_f64.sqrt() / 2., 2_f64.sqrt() / 2., 0.)
|
||||
);
|
||||
assert_eq!(full_quarter * p, Point::new(-1., 0., 0.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shear_moves_x_proportionally_to_y() {
|
||||
let transform = shearing(1., 0., 0., 0., 0., 0.);
|
||||
let p = Point::new(2., 3., 4.);
|
||||
assert_eq!(transform * p, Point::new(5., 3., 4.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shear_moves_x_proportionally_to_z() {
|
||||
let transform = shearing(0., 1., 0., 0., 0., 0.);
|
||||
let p = Point::new(2., 3., 4.);
|
||||
assert_eq!(transform * p, Point::new(6., 3., 4.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shear_moves_y_proportionally_to_x() {
|
||||
let transform = shearing(0., 0., 1., 0., 0., 0.);
|
||||
let p = Point::new(2., 3., 4.);
|
||||
assert_eq!(transform * p, Point::new(2., 5., 4.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shear_moves_y_proportionally_to_z() {
|
||||
let transform = shearing(0., 0., 0., 1., 0., 0.);
|
||||
let p = Point::new(2., 3., 4.);
|
||||
assert_eq!(transform * p, Point::new(2., 7., 4.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shear_moves_z_proportionally_to_x() {
|
||||
let transform = shearing(0., 0., 0., 0., 1., 0.);
|
||||
let p = Point::new(2., 3., 4.);
|
||||
assert_eq!(transform * p, Point::new(2., 3., 6.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shear_moves_z_proportionally_to_y() {
|
||||
let transform = shearing(0., 0., 0., 0., 0., 1.);
|
||||
let p = Point::new(2., 3., 4.);
|
||||
assert_eq!(transform * p, Point::new(2., 3., 7.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn individual_transformations_are_applied_in_sequence() {
|
||||
let p = Point::new(1., 0., 1.);
|
||||
let rotation = rotation_x(PI / 2.);
|
||||
let scale = scaling(5., 5., 5.);
|
||||
let translate = translation(10., 5., 7.);
|
||||
|
||||
let p2 = rotation * p;
|
||||
assert_eq!(p2, Point::new(1., -1., 0.));
|
||||
|
||||
let p3 = scale * p2;
|
||||
assert_eq!(p3, Point::new(5., -5., 0.));
|
||||
|
||||
let p4 = translate * p3;
|
||||
assert_eq!(p4, Point::new(15., 0., 7.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chained_translations() {
|
||||
let p = Point::new(1., 0., 1.);
|
||||
let rotation = rotation_x(PI / 2.);
|
||||
let scale = scaling(5., 5., 5.);
|
||||
let translate = translation(10., 5., 7.);
|
||||
let tr = translate * scale * rotation;
|
||||
assert_eq!(tr * p, Point::new(15., 0., 7.));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
use crate::types::Color;
|
||||
|
||||
pub struct Canvas {
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixels: Vec<Color>,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
pixels: vec![Color::default(); width * height],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn pixel(&self, x: usize, y: usize) -> &Color {
|
||||
&self.pixels[self.addr(y, x)]
|
||||
}
|
||||
|
||||
pub fn pixel_mut(&mut self, x: usize, y: usize) -> &mut Color {
|
||||
let addr = self.addr(y, x);
|
||||
&mut self.pixels[addr]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn addr(&self, y: usize, x: usize) -> usize {
|
||||
let val = y * self.width() + x;
|
||||
if val >= self.pixels.len() {
|
||||
eprintln!("[{val}] out of range: [{x}, {y}]");
|
||||
}
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn addresses() {
|
||||
let c = Canvas::new(5, 3);
|
||||
|
||||
assert_eq!(c.addr(0, 0), 0);
|
||||
assert_eq!(c.addr(1, 0), 5);
|
||||
assert_eq!(c.addr(2, 0), 10);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
use crate::types::Tuple;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Color(Tuple);
|
||||
|
||||
impl Color {
|
||||
pub fn new(red: f64, green: f64, blue: f64) -> Self {
|
||||
Self(Tuple(red, green, blue, 0.))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn red(&self) -> f64 {
|
||||
self.0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn green(&self) -> f64 {
|
||||
self.0.1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn blue(&self) -> f64 {
|
||||
self.0.2
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Self::new(0., 0., 0.)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tuple> for Color {
|
||||
fn from(tuple: Tuple) -> Self {
|
||||
assert_eq!(tuple.3, 0.0);
|
||||
Self(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Color {
|
||||
type Target = Tuple;
|
||||
fn deref(&self) -> &Tuple {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Color {
|
||||
type Output = Color;
|
||||
fn add(self, r: Self) -> Self {
|
||||
Color::from(self.0 + r.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Color {
|
||||
type Output = Color;
|
||||
fn sub(self, r: Self) -> Self {
|
||||
Color::from(self.0 - r.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for Color {
|
||||
type Output = Color;
|
||||
fn neg(self) -> Self::Output {
|
||||
let mut t = -self.0;
|
||||
t.0 = 0.;
|
||||
Color::from(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul for Color {
|
||||
type Output = Color;
|
||||
fn mul(self, r: Color) -> Self {
|
||||
let red = self.red() * r.red();
|
||||
let green = self.green() * r.green();
|
||||
let blue = self.blue() * r.blue();
|
||||
Self::new(red, green, blue)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f64> for Color {
|
||||
type Output = Color;
|
||||
fn mul(self, r: f64) -> Self {
|
||||
Color::from(self.0 * r)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
use std::cmp::Ordering;
|
||||
use super::Sphere;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Intersection<'a> {
|
||||
pub t: f64,
|
||||
pub object: &'a Sphere,
|
||||
}
|
||||
|
||||
pub struct Intersections<'a>(Vec<Intersection<'a>>);
|
||||
|
||||
impl<'a> Intersections<'a> {
|
||||
pub fn is_empty(&'a self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&'a self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn hit(&'a self) -> Option<&Intersection<'a>> {
|
||||
self.0.iter().find(|i| i.t >= 0.)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::Index<usize> for Intersections<'a> {
|
||||
type Output = Intersection<'a>;
|
||||
fn index(&self, idx: usize) -> &Intersection<'a> {
|
||||
&self.0[idx]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<Intersection<'a>>> for Intersections<'a> {
|
||||
fn from(mut v: Vec<Intersection<'a>>) -> Self {
|
||||
v.sort_by(|l, r| l.t.partial_cmp(&r.t).unwrap_or(Ordering::Equal));
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
use super::{Color, Point};
|
||||
|
||||
pub struct PointLight {
|
||||
pub position: Point,
|
||||
pub intensity: Color,
|
||||
}
|
||||
|
||||
impl PointLight {
|
||||
pub fn new(position: Point, intensity: Color) -> Self {
|
||||
Self { position, intensity }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
use super::{eq_f64, Color, Point, PointLight, Vector};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Material {
|
||||
pub color: Color,
|
||||
pub ambient: f64,
|
||||
pub diffuse: f64,
|
||||
pub specular: f64,
|
||||
pub shininess: f64,
|
||||
}
|
||||
|
||||
impl Default for Material {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: Color::new(1., 1., 1.),
|
||||
ambient: 0.1,
|
||||
diffuse: 0.9,
|
||||
specular: 0.9,
|
||||
shininess: 200.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Material {
|
||||
pub fn lighting(
|
||||
&self,
|
||||
light: &PointLight,
|
||||
position: &Point,
|
||||
eye: &Vector,
|
||||
normal_v: &Vector,
|
||||
) -> Color {
|
||||
// let debug = eq_f64(position.x(), -0.09344)
|
||||
// || eq_f64(position.x(), 0.09344) && eq_f64(position.y(), 0.)
|
||||
// || eq_f64(position.y(), -0.09344)
|
||||
// || eq_f64(position.y(), 0.09344) && eq_f64(position.x(), 0.);
|
||||
// if debug {
|
||||
// println!(
|
||||
// "Debugging {:0.5} {:0.5} {:0.5}",
|
||||
// position.x(),
|
||||
// position.y(),
|
||||
// position.z()
|
||||
// );
|
||||
// }
|
||||
let effective_color = self.color * light.intensity;
|
||||
|
||||
let light_v = (light.position - position).normalize();
|
||||
let ambient = effective_color * self.ambient;
|
||||
|
||||
let light_dot_normal = light_v.dot(&normal_v);
|
||||
let (diffuse, specular) = if light_dot_normal < 0. {
|
||||
(Color::new(0., 0., 0.), Color::new(0., 0., 0.))
|
||||
} else {
|
||||
let diffuse = effective_color * self.diffuse * light_dot_normal;
|
||||
|
||||
let reflect_v = (-light_v).reflect(&normal_v);
|
||||
let reflect_dot_eye = reflect_v.dot(&eye);
|
||||
|
||||
let specular = if reflect_dot_eye <= 0. {
|
||||
Color::new(0., 0., 0.)
|
||||
} else {
|
||||
let factor = reflect_dot_eye.powf(self.shininess);
|
||||
light.intensity * self.specular * factor
|
||||
};
|
||||
|
||||
(diffuse, specular)
|
||||
};
|
||||
|
||||
ambient + diffuse + specular
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::{Color, Point, PointLight, Vector};
|
||||
|
||||
#[test]
|
||||
fn lighting_with_eye_between_light_and_surface() {
|
||||
let m = Material::default();
|
||||
let position = Point::default();
|
||||
|
||||
let eyev = Vector::new(0., 0., -1.);
|
||||
let normalv = Vector::new(0., 0., -1.);
|
||||
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
|
||||
|
||||
assert_eq!(
|
||||
m.lighting(&light, &position, &eyev, &normalv),
|
||||
Color::new(1.9, 1.9, 1.9)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lighting_with_eye_between_light_and_surface_eye_offset_45() {
|
||||
let m = Material::default();
|
||||
let position = Point::default();
|
||||
|
||||
let eyev = Vector::new(0., 2_f64.sqrt() / 2., -2_f64.sqrt() / 2.);
|
||||
let normalv = Vector::new(0., 0., -1.);
|
||||
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
|
||||
|
||||
assert_eq!(
|
||||
m.lighting(&light, &position, &eyev, &normalv),
|
||||
Color::new(1., 1., 1.)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lighting_with_eye_between_light_and_surface_light_offset_45() {
|
||||
let m = Material::default();
|
||||
let position = Point::default();
|
||||
|
||||
let eyev = Vector::new(0., 0., -1.);
|
||||
let normalv = Vector::new(0., 0., -1.);
|
||||
let light = PointLight::new(Point::new(0., 10., -10.), Color::new(1., 1., 1.));
|
||||
|
||||
assert_eq!(
|
||||
m.lighting(&light, &position, &eyev, &normalv),
|
||||
Color::new(0.7364, 0.7364, 0.7364)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lighting_eye_in_path_of_reflection() {
|
||||
let m = Material::default();
|
||||
let position = Point::default();
|
||||
|
||||
// let eyev = Vector::new(0., -2_f64.sqrt() / 2., -2_f64.sqrt() / 2.);
|
||||
let eyev = Vector::new(0., -10., -10.).normalize();
|
||||
let normalv = Vector::new(0., 0., -1.);
|
||||
let light = PointLight::new(Point::new(0., 10., -10.), Color::new(1., 1., 1.));
|
||||
|
||||
assert_eq!(
|
||||
m.lighting(&light, &position, &eyev, &normalv),
|
||||
Color::new(1.6364, 1.6364, 1.6364)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lighting_with_light_behind_surface() {
|
||||
let m = Material::default();
|
||||
let position = Point::default();
|
||||
|
||||
let eyev = Vector::new(0., 0., -1.);
|
||||
let normalv = Vector::new(0., 0., -1.);
|
||||
let light = PointLight::new(Point::new(0., 0., 10.), Color::new(1., 1., 1.));
|
||||
|
||||
assert_eq!(
|
||||
m.lighting(&light, &position, &eyev, &normalv),
|
||||
Color::new(0.1, 0.1, 0.1)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,544 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::types::{eq_f64, Point, Tuple, Vector};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Matrix {
|
||||
size: usize,
|
||||
values: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Matrix {
|
||||
pub fn new(size: usize) -> Self {
|
||||
Self {
|
||||
size,
|
||||
values: vec![0.; size * size],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity() -> Self {
|
||||
Self::from([
|
||||
[1., 0., 0., 0.],
|
||||
[0., 1., 0., 0.],
|
||||
[0., 0., 1., 0.],
|
||||
[0., 0., 0., 1.],
|
||||
])
|
||||
}
|
||||
|
||||
pub fn cell(&self, row: usize, column: usize) -> f64 {
|
||||
self.values[self.addr(row, column)]
|
||||
}
|
||||
|
||||
pub fn cell_mut(&mut self, row: usize, column: usize) -> &mut f64 {
|
||||
let addr = self.addr(row, column);
|
||||
&mut self.values[addr]
|
||||
}
|
||||
|
||||
pub fn transpose(&self) -> Self {
|
||||
let mut m = Self::new(self.size);
|
||||
|
||||
for row in 0..self.size {
|
||||
for column in 0..self.size {
|
||||
*m.cell_mut(row, column) = self.cell(column, row);
|
||||
}
|
||||
}
|
||||
|
||||
m
|
||||
}
|
||||
|
||||
pub fn is_invertible(&self) -> bool {
|
||||
!eq_f64(self.determinant(), 0.)
|
||||
}
|
||||
|
||||
pub fn inverse(&self) -> Self {
|
||||
let mut m = Matrix::new(self.size);
|
||||
let determinant = self.determinant();
|
||||
for row in 0..self.size {
|
||||
for column in 0..self.size {
|
||||
*m.cell_mut(row, column) = self.cofactor(row, column);
|
||||
}
|
||||
}
|
||||
|
||||
let mut m = m.transpose();
|
||||
for row in 0..self.size {
|
||||
for column in 0..self.size {
|
||||
let value = m.cell(row, column);
|
||||
*m.cell_mut(row, column) = value / determinant;
|
||||
}
|
||||
}
|
||||
|
||||
m
|
||||
}
|
||||
|
||||
fn determinant(&self) -> f64 {
|
||||
// TODO: optimization may not be necessary, but this can be optimized by memoizing
|
||||
// submatrices and cofactors.
|
||||
if self.size == 2 {
|
||||
self.cell(0, 0) * self.cell(1, 1) - self.cell(0, 1) * self.cell(1, 0)
|
||||
} else if self.size == 3 {
|
||||
self.cell(0, 0) * self.cofactor(0, 0)
|
||||
+ self.cell(0, 1) * self.cofactor(0, 1)
|
||||
+ self.cell(0, 2) * self.cofactor(0, 2)
|
||||
} else if self.size == 4 {
|
||||
self.cell(0, 0) * self.cofactor(0, 0)
|
||||
+ self.cell(0, 1) * self.cofactor(0, 1)
|
||||
+ self.cell(0, 2) * self.cofactor(0, 2)
|
||||
+ self.cell(0, 3) * self.cofactor(0, 3)
|
||||
} else {
|
||||
0.
|
||||
}
|
||||
}
|
||||
|
||||
fn submatrix(&self, row: usize, column: usize) -> Self {
|
||||
let mut m = Self::new(self.size - 1);
|
||||
|
||||
for r in 0..self.size {
|
||||
for c in 0..self.size {
|
||||
let dest_r = match r.cmp(&row) {
|
||||
Ordering::Less => r,
|
||||
Ordering::Greater => r - 1,
|
||||
Ordering::Equal => continue,
|
||||
};
|
||||
let dest_c = match c.cmp(&column) {
|
||||
Ordering::Less => c,
|
||||
Ordering::Greater => c - 1,
|
||||
Ordering::Equal => continue,
|
||||
};
|
||||
*m.cell_mut(dest_r, dest_c) = self.cell(r, c);
|
||||
}
|
||||
}
|
||||
|
||||
m
|
||||
}
|
||||
|
||||
fn minor(&self, row: usize, column: usize) -> f64 {
|
||||
self.submatrix(row, column).determinant()
|
||||
}
|
||||
|
||||
fn cofactor(&self, row: usize, column: usize) -> f64 {
|
||||
if (row + column) % 2 == 0 {
|
||||
self.minor(row, column)
|
||||
} else {
|
||||
-self.minor(row, column)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn addr(&self, row: usize, column: usize) -> usize {
|
||||
row * self.size + column
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[[f64; 2]; 2]> for Matrix {
|
||||
fn from(s: [[f64; 2]; 2]) -> Self {
|
||||
Self {
|
||||
size: 2,
|
||||
values: s.concat(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[[f64; 3]; 3]> for Matrix {
|
||||
fn from(s: [[f64; 3]; 3]) -> Self {
|
||||
Self {
|
||||
size: 3,
|
||||
values: s.concat(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[[f64; 4]; 4]> for Matrix {
|
||||
fn from(s: [[f64; 4]; 4]) -> Self {
|
||||
Self {
|
||||
size: 4,
|
||||
values: s.concat(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Matrix {
|
||||
fn eq(&self, rside: &Matrix) -> bool {
|
||||
if self.size != rside.size {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.values
|
||||
.iter()
|
||||
.zip(rside.values.iter())
|
||||
.all(|(l, r)| eq_f64(*l, *r))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<&Matrix> for &Matrix {
|
||||
type Output = Matrix;
|
||||
fn mul(self, rside: &Matrix) -> Matrix {
|
||||
assert_eq!(self.size, 4);
|
||||
assert_eq!(rside.size, 4);
|
||||
|
||||
let mut m = Matrix::new(self.size);
|
||||
for row in 0..4 {
|
||||
for column in 0..4 {
|
||||
*m.cell_mut(row, column) = self.cell(row, 0) * rside.cell(0, column)
|
||||
+ self.cell(row, 1) * rside.cell(1, column)
|
||||
+ self.cell(row, 2) * rside.cell(2, column)
|
||||
+ self.cell(row, 3) * rside.cell(3, column);
|
||||
}
|
||||
}
|
||||
|
||||
m
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Matrix> for &Matrix {
|
||||
type Output = Matrix;
|
||||
fn mul(self, rside: Matrix) -> Matrix {
|
||||
self * &rside
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<&Matrix> for Matrix {
|
||||
type Output = Matrix;
|
||||
fn mul(self, rside: &Matrix) -> Matrix {
|
||||
&self * rside
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul for Matrix {
|
||||
type Output = Matrix;
|
||||
fn mul(self, rside: Matrix) -> Matrix {
|
||||
&self * &rside
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Tuple> for &Matrix {
|
||||
type Output = Tuple;
|
||||
fn mul(self, rside: Tuple) -> Tuple {
|
||||
assert_eq!(self.size, 4);
|
||||
|
||||
let mut t = [0.; 4];
|
||||
|
||||
for (row, item) in t.iter_mut().enumerate() {
|
||||
*item = self.cell(row, 0) * rside.0
|
||||
+ self.cell(row, 1) * rside.1
|
||||
+ self.cell(row, 2) * rside.2
|
||||
+ self.cell(row, 3) * rside.3;
|
||||
}
|
||||
|
||||
Tuple::from(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Tuple> for Matrix {
|
||||
type Output = Tuple;
|
||||
fn mul(self, rside: Tuple) -> Tuple {
|
||||
&self * rside
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<&Point> for &Matrix {
|
||||
type Output = Point;
|
||||
fn mul(self, rside: &Point) -> Point {
|
||||
let t: Tuple = **rside;
|
||||
Point::from(self * t)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Point> for &Matrix {
|
||||
type Output = Point;
|
||||
fn mul(self, rside: Point) -> Point {
|
||||
self * &rside
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<&Point> for Matrix {
|
||||
type Output = Point;
|
||||
fn mul(self, rside: &Point) -> Point {
|
||||
&self * rside
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Point> for Matrix {
|
||||
type Output = Point;
|
||||
fn mul(self, rside: Point) -> Point {
|
||||
&self * rside
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Vector> for Matrix {
|
||||
type Output = Vector;
|
||||
fn mul(self, rside: Vector) -> Vector {
|
||||
Vector::from(self * *rside)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_constructs_a_matrix() {
|
||||
let m = Matrix::from([[-3., 5.], [1., -2.]]);
|
||||
assert_eq!(m.cell(0, 0), -3.);
|
||||
assert_eq!(m.cell(0, 1), 5.);
|
||||
assert_eq!(m.cell(1, 0), 1.);
|
||||
assert_eq!(m.cell(1, 1), -2.);
|
||||
|
||||
let m = Matrix::from([[-3., 5., 0.], [1., -2., -7.], [0., 1., 1.]]);
|
||||
assert_eq!(m.cell(0, 0), -3.);
|
||||
assert_eq!(m.cell(1, 1), -2.);
|
||||
assert_eq!(m.cell(2, 2), 1.);
|
||||
|
||||
let m = Matrix::from([
|
||||
[1., 2., 3., 4.],
|
||||
[5.5, 6.5, 7.5, 8.5],
|
||||
[9., 10., 11., 12.],
|
||||
[13.5, 14.5, 15.5, 16.5],
|
||||
]);
|
||||
assert_eq!(m.cell(0, 0), 1.);
|
||||
assert_eq!(m.cell(0, 3), 4.);
|
||||
assert_eq!(m.cell(1, 0), 5.5);
|
||||
assert_eq!(m.cell(1, 2), 7.5);
|
||||
assert_eq!(m.cell(2, 2), 11.);
|
||||
assert_eq!(m.cell(3, 0), 13.5);
|
||||
assert_eq!(m.cell(3, 2), 15.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_compares_two_matrices() {
|
||||
let a = Matrix::from([
|
||||
[1., 2., 3., 4.],
|
||||
[5., 6., 7., 8.],
|
||||
[9., 8., 7., 6.],
|
||||
[5., 4., 3., 2.],
|
||||
]);
|
||||
let b = Matrix::from([
|
||||
[1., 2., 3., 4.],
|
||||
[5., 6., 7., 8.],
|
||||
[9., 8., 7., 6.],
|
||||
[5., 4., 3., 2.],
|
||||
]);
|
||||
let c = Matrix::from([
|
||||
[2., 3., 4., 5.],
|
||||
[6., 7., 8., 9.],
|
||||
[8., 7., 6., 5.],
|
||||
[4., 3., 2., 1.],
|
||||
]);
|
||||
|
||||
assert_eq!(a, b);
|
||||
assert_ne!(a, c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_two_matrices() {
|
||||
let a = Matrix::from([
|
||||
[1., 2., 3., 4.],
|
||||
[5., 6., 7., 8.],
|
||||
[9., 8., 7., 6.],
|
||||
[5., 4., 3., 2.],
|
||||
]);
|
||||
|
||||
let b = Matrix::from([
|
||||
[-2., 1., 2., 3.],
|
||||
[3., 2., 1., -1.],
|
||||
[4., 3., 6., 5.],
|
||||
[1., 2., 7., 8.],
|
||||
]);
|
||||
|
||||
let expected = Matrix::from([
|
||||
[20., 22., 50., 48.],
|
||||
[44., 54., 114., 108.],
|
||||
[40., 58., 110., 102.],
|
||||
[16., 26., 46., 42.],
|
||||
]);
|
||||
|
||||
assert_eq!(a * b, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_matrix_by_tuple() {
|
||||
let a = Matrix::from([
|
||||
[1., 2., 3., 4.],
|
||||
[2., 4., 4., 2.],
|
||||
[8., 6., 4., 1.],
|
||||
[0., 0., 0., 1.],
|
||||
]);
|
||||
let b = Tuple(1., 2., 3., 1.);
|
||||
assert_eq!(a * b, Tuple(18., 24., 33., 1.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_matrix() {
|
||||
let a = Matrix::from([
|
||||
[0., 1., 2., 4.],
|
||||
[1., 2., 4., 8.],
|
||||
[2., 4., 8., 16.],
|
||||
[4., 8., 16., 32.],
|
||||
]);
|
||||
|
||||
assert_eq!(&a * Matrix::identity(), a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transpose_a_matrix() {
|
||||
let a = Matrix::from([
|
||||
[0., 9., 3., 0.],
|
||||
[9., 8., 0., 8.],
|
||||
[1., 8., 5., 3.],
|
||||
[0., 0., 5., 8.],
|
||||
]);
|
||||
|
||||
let expected = Matrix::from([
|
||||
[0., 9., 1., 0.],
|
||||
[9., 8., 8., 0.],
|
||||
[3., 0., 5., 5.],
|
||||
[0., 8., 3., 8.],
|
||||
]);
|
||||
|
||||
assert_eq!(a.transpose(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculates_2x2_determinant() {
|
||||
let m = Matrix::from([[1., 5.], [-3., 2.]]);
|
||||
assert_eq!(m.determinant(), 17.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculates_3x3_determinant() {
|
||||
let m = Matrix::from([[1., 2., 6.], [-5., 8., -4.], [2., 6., 4.]]);
|
||||
assert_eq!(m.cofactor(0, 0), 56.);
|
||||
assert_eq!(m.cofactor(0, 1), 12.);
|
||||
assert_eq!(m.cofactor(0, 2), -46.);
|
||||
assert_eq!(m.determinant(), -196.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculates_4x4_determinant() {
|
||||
let m = Matrix::from([
|
||||
[-2., -8., 3., 5.],
|
||||
[-3., 1., 7., 3.],
|
||||
[1., 2., -9., 6.],
|
||||
[-6., 7., 7., -9.],
|
||||
]);
|
||||
assert_eq!(m.cofactor(0, 0), 690.);
|
||||
assert_eq!(m.cofactor(0, 1), 447.);
|
||||
assert_eq!(m.cofactor(0, 2), 210.);
|
||||
assert_eq!(m.cofactor(0, 3), 51.);
|
||||
assert_eq!(m.determinant(), -4071.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculates_submatrix() {
|
||||
let m = Matrix::from([[1., 5., 0.], [-3., 2., 7.], [0., 6., -3.]]);
|
||||
let expected = Matrix::from([[-3., 2.], [0., 6.]]);
|
||||
assert_eq!(m.submatrix(0, 2), expected);
|
||||
|
||||
let m = Matrix::from([
|
||||
[-6., 1., 1., 6.],
|
||||
[-8., 5., 8., 6.],
|
||||
[-1., 0., 8., 2.],
|
||||
[-7., 1., -1., 1.],
|
||||
]);
|
||||
let expected = Matrix::from([[-6., 1., 6.], [-8., 8., 6.], [-7., -1., 1.]]);
|
||||
assert_eq!(m.submatrix(2, 1), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculates_minors_and_cofactors() {
|
||||
let m = Matrix::from([[3., 5., 0.], [2., -1., -7.], [6., -1., 5.]]);
|
||||
assert_eq!(m.submatrix(1, 0).determinant(), 25.);
|
||||
assert_eq!(m.minor(0, 0), -12.);
|
||||
assert_eq!(m.cofactor(0, 0), -12.);
|
||||
assert_eq!(m.minor(1, 0), 25.);
|
||||
assert_eq!(m.cofactor(1, 0), -25.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_4x4_matrix() {
|
||||
let m = Matrix::from([
|
||||
[6., 4., 4., 4.],
|
||||
[5., 5., 7., 6.],
|
||||
[4., -9., 3., -7.],
|
||||
[9., 1., 7., -6.],
|
||||
]);
|
||||
|
||||
assert_eq!(m.determinant(), -2120.);
|
||||
assert!(m.is_invertible());
|
||||
|
||||
let m = Matrix::from([
|
||||
[-4., 2., -2., -3.],
|
||||
[9., 6., 2., 6.],
|
||||
[0., -5., 1., -5.],
|
||||
[0., 0., 0., 0.],
|
||||
]);
|
||||
assert_eq!(m.determinant(), 0.);
|
||||
assert!(!m.is_invertible());
|
||||
|
||||
let m = Matrix::from([
|
||||
[-5., 2., 6., -8.],
|
||||
[1., -5., 1., 8.],
|
||||
[7., 7., -6., -7.],
|
||||
[1., -3., 7., 4.],
|
||||
]);
|
||||
let expected = Matrix::from([
|
||||
[0.21805, 0.45113, 0.24060, -0.04511],
|
||||
[-0.80827, -1.45677, -0.44361, 0.52068],
|
||||
[-0.07895, -0.22368, -0.05263, 0.19737],
|
||||
[-0.52256, -0.81391, -0.30075, 0.30639],
|
||||
]);
|
||||
|
||||
assert_eq!(m.determinant(), 532.);
|
||||
assert_eq!(m.cofactor(2, 3), -160.);
|
||||
assert!(eq_f64(expected.cell(3, 2), -160. / 532.));
|
||||
assert_eq!(m.cofactor(3, 2), 105.);
|
||||
assert!(eq_f64(expected.cell(2, 3), 105. / 532.));
|
||||
|
||||
assert_eq!(m.inverse(), expected);
|
||||
|
||||
let m = Matrix::from([
|
||||
[8., -5., 9., 2.],
|
||||
[7., 5., 6., 1.],
|
||||
[-6., 0., 9., 6.],
|
||||
[-3., 0., -9., -4.],
|
||||
]);
|
||||
let expected = Matrix::from([
|
||||
[-0.15385, -0.15385, -0.28205, -0.53846],
|
||||
[-0.07692, 0.12308, 0.02564, 0.03077],
|
||||
[0.35897, 0.35897, 0.43590, 0.92308],
|
||||
[-0.69231, -0.69231, -0.76923, -1.92308],
|
||||
]);
|
||||
assert_eq!(m.inverse(), expected);
|
||||
|
||||
let m = Matrix::from([
|
||||
[9., 3., 0., 9.],
|
||||
[-5., -2., -6., -3.],
|
||||
[-4., 9., 6., 4.],
|
||||
[-7., 6., 6., 2.],
|
||||
]);
|
||||
let expected = Matrix::from([
|
||||
[-0.04074, -0.07778, 0.14444, -0.22222],
|
||||
[-0.07778, 0.03333, 0.36667, -0.33333],
|
||||
[-0.02901, -0.14630, -0.10926, 0.12963],
|
||||
[0.17778, 0.06667, -0.26667, 0.33333],
|
||||
]);
|
||||
assert_eq!(m.inverse(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_product_by_inverse() {
|
||||
let a = Matrix::from([
|
||||
[3., -9., 7., 3.],
|
||||
[3., -8., 2., -9.],
|
||||
[-4., 4., 4., 1.],
|
||||
[-6., 5., -1., 1.],
|
||||
]);
|
||||
let b = Matrix::from([
|
||||
[8., 2., 2., 2.],
|
||||
[3., -1., 7., 0.],
|
||||
[7., 0., 5., 4.],
|
||||
[6., -2., 0., 5.],
|
||||
]);
|
||||
let c = &a * &b;
|
||||
assert_eq!(c * b.inverse(), a);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
mod canvas;
|
||||
mod color;
|
||||
mod intersections;
|
||||
mod light;
|
||||
mod material;
|
||||
mod matrix;
|
||||
mod point;
|
||||
mod ray;
|
||||
mod sphere;
|
||||
mod tuple;
|
||||
mod vector;
|
||||
|
||||
pub use canvas::Canvas;
|
||||
pub use color::Color;
|
||||
pub use intersections::{Intersections, Intersection};
|
||||
pub use light::PointLight;
|
||||
pub use material::Material;
|
||||
pub use matrix::Matrix;
|
||||
pub use point::Point;
|
||||
pub use ray::Ray;
|
||||
pub use sphere::Sphere;
|
||||
pub use tuple::Tuple;
|
||||
pub use vector::Vector;
|
||||
|
||||
const EPSILON: f64 = 0.00001;
|
||||
|
||||
fn eq_f64(l: f64, r: f64) -> bool {
|
||||
(l - r).abs() < EPSILON
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn eq_f64_compares_values() {
|
||||
assert!(eq_f64(1.0, 1.0));
|
||||
assert!(eq_f64(0.9994, 0.9994));
|
||||
assert!(eq_f64(0.9999994, 0.9999995));
|
||||
assert!(!eq_f64(0.9995, 0.9994));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_two_tuples() {
|
||||
let a = Tuple(3., -2., 5., 1.);
|
||||
let b = Tuple(-2., 3., 1., 0.);
|
||||
assert_eq!(a + b, Tuple(1., 1., 6., 1.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtracts_two_tuples() {
|
||||
let a = Tuple(3., 2., 1., 1.);
|
||||
let b = Tuple(5., 6., 7., 1.);
|
||||
assert_eq!(a - b, Tuple(-2., -4., -6., 0.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_point_and_vector() {
|
||||
let a = Point::new(3., -2., 5.);
|
||||
let b = Vector::new(-2., 3., 1.);
|
||||
assert_eq!(a + b, Point::new(1., 1., 6.,));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtracts_two_points() {
|
||||
let a = Point::new(3., 2., 1.);
|
||||
let b = Point::new(5., 6., 7.);
|
||||
assert_eq!(a - b, Vector::new(-2., -4., -6.,));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtracts_vector_from_point() {
|
||||
let a = Point::new(3., 2., 1.);
|
||||
let b = Vector::new(5., 6., 7.);
|
||||
assert_eq!(a - b, Point::new(-2., -4., -6.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtracts_two_vectors() {
|
||||
let a = Vector::new(3., 2., 1.);
|
||||
let b = Vector::new(5., 6., 7.);
|
||||
assert_eq!(a - b, Vector::new(-2., -4., -6.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_negates_primitives() {
|
||||
assert_eq!(-Tuple(1., 2., 3., 4.), Tuple(-1., -2., -3., -4.),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_tuple_by_scalar() {
|
||||
assert_eq!(Tuple(1., -2., 3., -4.) * 3.5, Tuple(3.5, -7., 10.5, -14.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn divide_tuple_by_scalar() {
|
||||
assert_eq!(Tuple(1., -2., 3., -4.) / 2., Tuple(0.5, -1., 1.5, -2.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn magnitude_of_vector() {
|
||||
assert_eq!(Vector::new(1., 0., 0.).magnitude(), 1.);
|
||||
assert_eq!(Vector::new(0., 1., 0.).magnitude(), 1.);
|
||||
assert_eq!(Vector::new(0., 0., 1.).magnitude(), 1.);
|
||||
assert_eq!(Vector::new(1., 2., 3.).magnitude(), 14_f64.sqrt());
|
||||
assert_eq!(Vector::new(-1., -2., -3.).magnitude(), 14_f64.sqrt());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_vector() {
|
||||
assert_eq!(Vector::new(4., 0., 0.).normalize(), Vector::new(1., 0., 0.));
|
||||
assert_eq!(
|
||||
Vector::new(1., 2., 3.).normalize(),
|
||||
Vector::new(0.26726, 0.53452, 0.80178)
|
||||
);
|
||||
assert_eq!(Vector::new(1., 2., 3.).normalize().magnitude(), 1.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_product() {
|
||||
assert_eq!(Vector::new(1., 2., 3.).dot(&Vector::new(2., 3., 4.)), 20.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_product() {
|
||||
assert_eq!(
|
||||
Vector::new(1., 2., 3.).cross(&Vector::new(2., 3., 4.)),
|
||||
Vector::new(-1., 2., -1.)
|
||||
);
|
||||
assert_eq!(
|
||||
Vector::new(2., 3., 4.).cross(&Vector::new(1., 2., 3.)),
|
||||
Vector::new(1., -2., 1.)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_colors() {
|
||||
let c1 = Color::new(1., 0.2, 0.4);
|
||||
let c2 = Color::new(0.9, 1., 0.1);
|
||||
|
||||
assert_eq!(c1 * c2, Color::new(0.9, 0.2, 0.04));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_creates_a_canvas() {
|
||||
let c = Canvas::new(10, 20);
|
||||
assert_eq!(c.width(), 10);
|
||||
assert_eq!(c.height(), 20);
|
||||
for row in 0..20 {
|
||||
for col in 0..10 {
|
||||
assert_eq!(*c.pixel(row, col), Color::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_write_pixel() {
|
||||
let mut c = Canvas::new(10, 20);
|
||||
let red = Color::new(1., 0., 0.);
|
||||
*c.pixel_mut(2, 3) = red;
|
||||
assert_eq!(*c.pixel(2, 3), red);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflect_vector_approaching_at_45() {
|
||||
let v = Vector::new(1., -1., 0.);
|
||||
let n = Vector::new(0., 1., 0.);
|
||||
assert_eq!(v.reflect(&n), Vector::new(1., 1., 0.));
|
||||
|
||||
let v = Vector::new(-1., -1., 0.);
|
||||
let n = Vector::new(1., 0., 0.);
|
||||
assert_eq!(v.reflect(&n), Vector::new(1., -1., 0.));
|
||||
|
||||
let v = Vector::new(1., 0., -1.);
|
||||
let n = Vector::new(0., 0., 1.);
|
||||
assert_eq!(v.reflect(&n), Vector::new(1., 0., 1.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflect_off_slanted_surface() {
|
||||
let v = Vector::new(0., -1., 0.);
|
||||
let n = Vector::new(2_f64.sqrt() / 2., 2_f64.sqrt() / 2., 0.);
|
||||
assert_eq!(v.reflect(&n), Vector::new(1., 0., 0.));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
use crate::types::{Tuple, Vector};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Point(Tuple);
|
||||
|
||||
impl Point {
|
||||
pub fn new(x: f64, y: f64, z: f64) -> Self {
|
||||
Self(Tuple(x, y, z, 1.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(&self) -> f64 {
|
||||
self.0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(&self) -> f64 {
|
||||
self.0.1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn z(&self) -> f64 {
|
||||
self.0.2
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Point {
|
||||
fn default() -> Self {
|
||||
Self::new(0., 0., 0.)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tuple> for Point {
|
||||
fn from(tuple: Tuple) -> Self {
|
||||
assert_eq!(tuple.3, 1.0);
|
||||
Self(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Point {
|
||||
type Target = Tuple;
|
||||
fn deref(&self) -> &Tuple {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Vector> for Point {
|
||||
type Output = Point;
|
||||
fn add(self, r: Vector) -> Self::Output {
|
||||
Point::from(self.0 + *r)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<&Point> for &Point {
|
||||
type Output = Vector;
|
||||
fn sub(self, r: &Point) -> Self::Output {
|
||||
Vector::from(self.0 - r.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Point> for &Point {
|
||||
type Output = Vector;
|
||||
fn sub(self, r: Point) -> Self::Output {
|
||||
self - &r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<&Point> for Point {
|
||||
type Output = Vector;
|
||||
fn sub(self, r: &Point) -> Self::Output {
|
||||
&self - r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Point> for Point {
|
||||
type Output = Vector;
|
||||
fn sub(self, r: Point) -> Self::Output {
|
||||
&self - &r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Vector> for Point {
|
||||
type Output = Point;
|
||||
fn sub(self, r: Vector) -> Self::Output {
|
||||
Point::from(self.0 - *r)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for Point {
|
||||
type Output = Point;
|
||||
fn neg(self) -> Self::Output {
|
||||
let mut t = -self.0;
|
||||
t.3 = 1.;
|
||||
Point::from(t)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
use super::{Intersection, Intersections, Matrix, Point, Sphere, Vector};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ray {
|
||||
origin: Point,
|
||||
pub direction: Vector,
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn new(origin: Point, direction: Vector) -> Self {
|
||||
Self { origin, direction }
|
||||
}
|
||||
|
||||
pub fn position(&self, t: f64) -> Point {
|
||||
self.origin + self.direction * t
|
||||
}
|
||||
|
||||
pub fn intersect<'a>(&self, s: &'a Sphere) -> Intersections<'a> {
|
||||
let r2 = self.transform(s.transformation().inverse());
|
||||
let sphere_to_ray = r2.origin - Point::new(0., 0., 0.);
|
||||
let a = r2.direction.dot(&r2.direction);
|
||||
let b = 2. * r2.direction.dot(&sphere_to_ray);
|
||||
let c = sphere_to_ray.dot(&sphere_to_ray) - 1.;
|
||||
|
||||
let discriminant = b * b - 4. * a * c;
|
||||
if discriminant < 0. {
|
||||
return vec![].into();
|
||||
}
|
||||
|
||||
let t1 = (-b - discriminant.sqrt()) / (2. * a);
|
||||
let t2 = (-b + discriminant.sqrt()) / (2. * a);
|
||||
|
||||
vec![
|
||||
Intersection { t: t1, object: s },
|
||||
Intersection { t: t2, object: s },
|
||||
]
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn transform(&self, m: Matrix) -> Self {
|
||||
Self {
|
||||
origin: &m * self.origin,
|
||||
direction: m * self.direction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::transforms::{scaling, translation};
|
||||
|
||||
#[test]
|
||||
fn computing_point_from_distance() {
|
||||
let r = Ray::new(Point::new(2., 3., 4.), Vector::new(1., 0., 0.));
|
||||
assert_eq!(r.position(0.), Point::new(2., 3., 4.));
|
||||
assert_eq!(r.position(1.), Point::new(3., 3., 4.));
|
||||
assert_eq!(r.position(-1.), Point::new(1., 3., 4.));
|
||||
assert_eq!(r.position(2.5), Point::new(4.5, 3., 4.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_intersects_sphere_at_two_points() {
|
||||
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
|
||||
let s = Sphere::default();
|
||||
let xs = r.intersect(&s);
|
||||
assert_eq!(xs.len(), 2);
|
||||
assert_eq!(xs[0].t, 4.);
|
||||
assert_eq!(xs[1].t, 6.);
|
||||
assert_eq!(*xs[0].object, s);
|
||||
assert_eq!(*xs[1].object, s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_tangents_sphere_at_one_point() {
|
||||
let r = Ray::new(Point::new(0., 1., -5.), Vector::new(0., 0., 1.));
|
||||
let s = Sphere::default();
|
||||
let xs = r.intersect(&s);
|
||||
assert_eq!(xs.len(), 2);
|
||||
assert_eq!(xs[0].t, 5.);
|
||||
assert_eq!(xs[1].t, 5.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_misses_the_sphere() {
|
||||
let r = Ray::new(Point::new(0., 2., -5.), Vector::new(0., 0., 1.));
|
||||
let s = Sphere::default();
|
||||
let xs = r.intersect(&s);
|
||||
assert_eq!(xs.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_originates_inside_the_sphere() {
|
||||
let r = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.));
|
||||
let s = Sphere::default();
|
||||
let xs = r.intersect(&s);
|
||||
assert_eq!(xs.len(), 2);
|
||||
assert_eq!(xs[0].t, -1.);
|
||||
assert_eq!(xs[1].t, 1.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sphere_is_behind_the_ray() {
|
||||
let r = Ray::new(Point::new(0., 0., 5.), Vector::new(0., 0., 1.));
|
||||
let s = Sphere::default();
|
||||
let xs = r.intersect(&s);
|
||||
assert_eq!(xs.len(), 2);
|
||||
assert_eq!(xs[0].t, -6.);
|
||||
assert_eq!(xs[1].t, -4.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_all_intersections_are_positive() {
|
||||
let s = Sphere::default();
|
||||
let i1 = Intersection { t: 1., object: &s };
|
||||
let i2 = Intersection { t: 2., object: &s };
|
||||
let xs = Intersections::from(vec![i1.clone(), i2.clone()]);
|
||||
|
||||
assert_eq!(xs.hit(), Some(&i1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_some_intersections_are_negative() {
|
||||
let s = Sphere::default();
|
||||
let i1 = Intersection { t: -1., object: &s };
|
||||
let i2 = Intersection { t: 1., object: &s };
|
||||
let xs = Intersections::from(vec![i1, i2.clone()]);
|
||||
|
||||
assert_eq!(xs.hit(), Some(&i2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_all_intersections_are_negative() {
|
||||
let s = Sphere::default();
|
||||
let i1 = Intersection { t: -2., object: &s };
|
||||
let i2 = Intersection { t: -1., object: &s };
|
||||
let xs = Intersections::from(vec![i1, i2]);
|
||||
|
||||
assert_eq!(xs.hit(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_is_always_lowest_nonnegative() {
|
||||
let s = Sphere::default();
|
||||
let i1 = Intersection { t: 5., object: &s };
|
||||
let i2 = Intersection { t: 7., object: &s };
|
||||
let i3 = Intersection { t: -3., object: &s };
|
||||
let i4 = Intersection { t: 2., object: &s };
|
||||
let xs = Intersections::from(vec![i1, i2, i3, i4.clone()]);
|
||||
|
||||
assert_eq!(xs.hit(), Some(&i4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn translate_a_ray() {
|
||||
let r = Ray::new(Point::new(1., 2., 3.), Vector::new(0., 1., 0.));
|
||||
let m = translation(3., 4., 5.);
|
||||
let r2 = r.transform(m);
|
||||
assert_eq!(r2.origin, Point::new(4., 6., 8.));
|
||||
assert_eq!(r2.direction, Vector::new(0., 1., 0.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_a_ray() {
|
||||
let r = Ray::new(Point::new(1., 2., 3.), Vector::new(0., 1., 0.));
|
||||
let m = scaling(2., 3., 4.);
|
||||
let r2 = r.transform(m);
|
||||
assert_eq!(r2.origin, Point::new(2., 6., 12.));
|
||||
assert_eq!(r2.direction, Vector::new(0., 3., 0.,));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_scaled_sphere_with_ray() {
|
||||
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
|
||||
let mut s = Sphere::default();
|
||||
*s.transformation_mut() = scaling(2., 2., 2.);
|
||||
let xs = r.intersect(&s);
|
||||
assert_eq!(xs.len(), 2);
|
||||
assert_eq!(xs[0].t, 3.);
|
||||
assert_eq!(xs[1].t, 7.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_translated_sphere_with_ray() {
|
||||
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
|
||||
let mut s = Sphere::default();
|
||||
*s.transformation_mut() = translation(5., 0., 0.);
|
||||
let xs = r.intersect(&s);
|
||||
assert_eq!(xs.len(), 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
use super::{Matrix, Point, Vector, Material };
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Sphere {
|
||||
origin: Point,
|
||||
transformation: Matrix,
|
||||
material: Material,
|
||||
}
|
||||
|
||||
impl Sphere {
|
||||
pub fn transformation(&self) -> &Matrix {
|
||||
&self.transformation
|
||||
}
|
||||
|
||||
pub fn transformation_mut(&mut self) -> &mut Matrix{
|
||||
&mut self.transformation
|
||||
}
|
||||
|
||||
pub fn material(&self) -> &Material {
|
||||
&self.material
|
||||
}
|
||||
|
||||
pub fn material_mut(&mut self) -> &mut Material {
|
||||
&mut self.material
|
||||
}
|
||||
|
||||
pub fn normal_at(&self, world_point: &Point) -> Vector {
|
||||
let inverted_transform = self.transformation.inverse();
|
||||
let object_point = &inverted_transform * world_point;
|
||||
let object_normal = (object_point - Point::new(0., 0., 0.)).normalize();
|
||||
let mut world_normal = inverted_transform.transpose() * *object_normal;
|
||||
world_normal.3 = 0.;
|
||||
Vector::from(world_normal).normalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Sphere {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
origin: Point::new(0., 0., 0.),
|
||||
transformation: Matrix::identity(),
|
||||
material: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::transforms::{rotation_z, scaling, translation};
|
||||
|
||||
#[test]
|
||||
fn sphere_has_default_transformation() {
|
||||
let s = Sphere::default();
|
||||
assert_eq!(s.transformation(), &Matrix::identity());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_a_spheres_transformation() {
|
||||
let mut s = Sphere::default();
|
||||
let t = translation(2., 3., 4.);
|
||||
*s.transformation_mut() = t.clone();
|
||||
assert_eq!(*s.transformation(), t);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_of_sphere_on_x() {
|
||||
let s = Sphere::default();
|
||||
let n = s.normal_at(&Point::new(1., 0., 0.));
|
||||
assert_eq!(n, Vector::new(1., 0., 0.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_of_sphere_on_y() {
|
||||
let s = Sphere::default();
|
||||
let n = s.normal_at(&Point::new(0., 1., 0.));
|
||||
assert_eq!(n, Vector::new(0., 1., 0.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_of_sphere_on_z() {
|
||||
let s = Sphere::default();
|
||||
let n = s.normal_at(&Point::new(0., 0., 1.));
|
||||
assert_eq!(n, Vector::new(0., 0., 1.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_of_sphere_on_nonaxial() {
|
||||
let s = Sphere::default();
|
||||
let n = s.normal_at(&Point::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
|
||||
assert_eq!(n, Vector::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_is_normalized() {
|
||||
let s = Sphere::default();
|
||||
let n = s.normal_at(&Point::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
|
||||
assert_eq!(n.normalize(), n);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_normal_of_translated_sphere() {
|
||||
let mut s = Sphere::default();
|
||||
*s.transformation_mut() = translation(0., 1., 0.);
|
||||
let n = s.normal_at(&Point::new(0., 1.70711, -0.70711));
|
||||
assert_eq!(n, Vector::new(0., 0.70711, -0.70711));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_normal_of_transformed_sphere() {
|
||||
let mut s = Sphere::default();
|
||||
*s.transformation_mut() = scaling(1., 0.5, 1.) * rotation_z(std::f64::consts::PI / 5.);
|
||||
let n = s.normal_at(&Point::new(0., 2_f64.sqrt() / 2., -2_f64.sqrt() / 2.));
|
||||
assert_eq!(n, Vector::new(0., 0.97014, -0.24254));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
use crate::types::eq_f64;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Tuple(
|
||||
pub f64, // x or red
|
||||
pub f64, // y or green
|
||||
pub f64, // z or blue
|
||||
pub f64, // w, the flag which
|
||||
// indicates point vs vec, or alpha
|
||||
);
|
||||
|
||||
impl Tuple {
|
||||
pub fn dot(&self, r: &Tuple) -> f64 {
|
||||
self.0 * r.0 + self.1 * r.1 + self.2 * r.2 + self.3 * r.3
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f64; 4]> for Tuple {
|
||||
fn from(source: [f64; 4]) -> Self {
|
||||
Self(source[0], source[1], source[2], source[3])
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Tuple {
|
||||
fn eq(&self, r: &Tuple) -> bool {
|
||||
eq_f64(self.0, r.0) && eq_f64(self.1, r.1) && eq_f64(self.2, r.2) && eq_f64(self.3, r.3)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<&Tuple> for &Tuple {
|
||||
type Output = Tuple;
|
||||
fn add(self, r: &Tuple) -> Self::Output {
|
||||
Tuple(self.0 + r.0, self.1 + r.1, self.2 + r.2, self.3 + r.3)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Tuple> for Tuple {
|
||||
type Output = Tuple;
|
||||
fn add(self, r: Tuple) -> Self::Output {
|
||||
&self + &r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<&Tuple> for &Tuple {
|
||||
type Output = Tuple;
|
||||
fn sub(self, r: &Tuple) -> Self::Output {
|
||||
Tuple(self.0 - r.0, self.1 - r.1, self.2 - r.2, self.3 - r.3)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Tuple> for Tuple {
|
||||
type Output = Tuple;
|
||||
fn sub(self, r: Tuple) -> Self::Output {
|
||||
&self - &r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for &Tuple {
|
||||
type Output = Tuple;
|
||||
fn neg(self) -> Self::Output {
|
||||
Tuple(-self.0, -self.1, -self.2, -self.3)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for Tuple {
|
||||
type Output = Tuple;
|
||||
fn neg(self) -> Self::Output {
|
||||
-&self
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f64> for &Tuple {
|
||||
type Output = Tuple;
|
||||
fn mul(self, scalar: f64) -> Self::Output {
|
||||
Tuple(
|
||||
self.0 * scalar,
|
||||
self.1 * scalar,
|
||||
self.2 * scalar,
|
||||
self.3 * scalar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f64> for Tuple {
|
||||
type Output = Tuple;
|
||||
#[allow(clippy::op_ref)]
|
||||
fn mul(self, scalar: f64) -> Self::Output {
|
||||
&self * scalar
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<f64> for &Tuple {
|
||||
type Output = Tuple;
|
||||
fn div(self, scalar: f64) -> Self::Output {
|
||||
Tuple(
|
||||
self.0 / scalar,
|
||||
self.1 / scalar,
|
||||
self.2 / scalar,
|
||||
self.3 / scalar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<f64> for Tuple {
|
||||
type Output = Tuple;
|
||||
#[allow(clippy::op_ref)]
|
||||
fn div(self, scalar: f64) -> Self::Output {
|
||||
&self / scalar
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
use crate::types::Tuple;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Vector(Tuple);
|
||||
|
||||
impl Vector {
|
||||
pub fn new(x: f64, y: f64, z: f64) -> Self {
|
||||
Self(Tuple(x, y, z, 0.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(&self) -> f64 {
|
||||
self.0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(&self) -> f64 {
|
||||
self.0.1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn z(&self) -> f64 {
|
||||
self.0.2
|
||||
}
|
||||
|
||||
pub fn magnitude(&self) -> f64 {
|
||||
(self.x() * self.x() + self.y() * self.y() + self.z() * self.z()).sqrt()
|
||||
}
|
||||
|
||||
pub fn normalize(&self) -> Self {
|
||||
let mag = self.magnitude();
|
||||
Self::new(self.x() / mag, self.y() / mag, self.z() / mag)
|
||||
}
|
||||
|
||||
pub fn dot(&self, r: &Vector) -> f64 {
|
||||
self.0.dot(r)
|
||||
}
|
||||
|
||||
pub fn cross(&self, r: &Vector) -> Self {
|
||||
let x = self.y() * r.z() - self.z() * r.y();
|
||||
let y = self.z() * r.x() - self.x() * r.z();
|
||||
let z = self.x() * r.y() - self.y() * r.x();
|
||||
Self::new(x, y, z)
|
||||
}
|
||||
|
||||
pub fn reflect(&self, n: &Vector) -> Self {
|
||||
self - (n * 2. * self.dot(n))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Vector {
|
||||
fn default() -> Self {
|
||||
Self::new(0., 0., 0.)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tuple> for Vector {
|
||||
fn from(tuple: Tuple) -> Self {
|
||||
assert_eq!(tuple.3, 0.0);
|
||||
Self(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Vector {
|
||||
type Target = Tuple;
|
||||
fn deref(&self) -> &Tuple {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Vector {
|
||||
type Output = Vector;
|
||||
fn add(self, r: Self) -> Self::Output {
|
||||
Vector::from(self.0 + r.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<&Vector> for &Vector {
|
||||
type Output = Vector;
|
||||
fn sub(self, r: &Vector) -> Self::Output {
|
||||
Vector::from(self.0 - r.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Vector> for &Vector {
|
||||
type Output = Vector;
|
||||
fn sub(self, r: Vector) -> Self::Output {
|
||||
self - &r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<&Vector> for Vector {
|
||||
type Output = Vector;
|
||||
fn sub(self, r: &Vector) -> Self::Output {
|
||||
&self - r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Vector {
|
||||
type Output = Vector;
|
||||
fn sub(self, r: Vector) -> Self::Output {
|
||||
&self - &r
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for Vector {
|
||||
type Output = Vector;
|
||||
fn neg(self) -> Self::Output {
|
||||
Vector::from(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f64> for &Vector {
|
||||
type Output = Vector;
|
||||
fn mul(self, r: f64) -> Self::Output {
|
||||
Vector::from(self.0 * r)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f64> for Vector {
|
||||
type Output = Vector;
|
||||
fn mul(self, r: f64) -> Self {
|
||||
Vector::from(self.0 * r)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "1.81.0"
|
||||
targets = [ "wasm32-unknown-unknown", "thumbv6m-none-eabi" ]
|
||||
channel = "1.77.0"
|
||||
targets = [ "wasm32-unknown-unknown" ]
|
||||
|
|
18
script.yml
18
script.yml
|
@ -1,18 +0,0 @@
|
|||
- text: The distinguishing thing about magic is that it includes some kind of personal element. The person who is performing the magic is relevant to the magic. -- Ted Chang, Marie Brennan
|
||||
position: top
|
||||
transition:
|
||||
secs: 1
|
||||
nanos: 0
|
||||
|
||||
- text: Any sufficiently advanced technology is indistinguishable from magic. -- Arthur C. Clark.
|
||||
position: middle
|
||||
transition:
|
||||
secs: 1
|
||||
nanos: 0
|
||||
|
||||
- text: Science is our Magic.
|
||||
position: bottom
|
||||
transition:
|
||||
secs: 1
|
||||
nanos: 0
|
||||
|
|
@ -12,10 +12,3 @@ serde_json = { version = "*" }
|
|||
serde = { version = "1" }
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
warp = { version = "0.3" }
|
||||
mime_guess = "2.0.5"
|
||||
mime = "0.3.17"
|
||||
uuid = { version = "1.11.0", features = ["v4"] }
|
||||
futures = "0.3.31"
|
||||
tokio-stream = "0.1.16"
|
||||
typeshare = "1.0.4"
|
||||
urlencoding = "2.1.3"
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
io::Read,
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
use urlencoding::decode;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::types::{AppError, Message, Tabletop, RGB};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct WebsocketClient {
|
||||
sender: Option<UnboundedSender<Message>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppState {
|
||||
pub image_base: PathBuf,
|
||||
pub tabletop: Tabletop,
|
||||
pub clients: HashMap<String, WebsocketClient>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Core(pub Arc<RwLock<AppState>>);
|
||||
|
||||
impl Core {
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(RwLock::new(AppState {
|
||||
image_base: PathBuf::from("/home/savanni/Pictures"),
|
||||
tabletop: Tabletop {
|
||||
background_color: RGB{ red: 0xca, green: 0xb9, blue: 0xbb },
|
||||
background_image: None,
|
||||
},
|
||||
clients: HashMap::new(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn register_client(&self) -> String {
|
||||
let mut state = self.0.write().unwrap();
|
||||
let uuid = Uuid::new_v4().simple().to_string();
|
||||
|
||||
let client = WebsocketClient { sender: None };
|
||||
|
||||
state.clients.insert(uuid.clone(), client);
|
||||
uuid
|
||||
}
|
||||
|
||||
pub fn unregister_client(&self, client_id: String) {
|
||||
let mut state = self.0.write().unwrap();
|
||||
let _ = state.clients.remove(&client_id);
|
||||
}
|
||||
|
||||
pub fn connect_client(&self, client_id: String) -> UnboundedReceiver<Message> {
|
||||
let mut state = self.0.write().unwrap();
|
||||
|
||||
match state.clients.get_mut(&client_id) {
|
||||
Some(client) => {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
client.sender = Some(tx);
|
||||
rx
|
||||
}
|
||||
None => {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_file(&self, file_name: String) -> Vec<u8> {
|
||||
let file_name = decode(&file_name).expect("UTF-8");
|
||||
|
||||
let mut full_path = self.0.read().unwrap().image_base.clone();
|
||||
full_path.push(file_name.to_string());
|
||||
|
||||
|
||||
let mut content: Vec<u8> = Vec::new();
|
||||
let mut file = std::fs::File::open(&full_path).unwrap();
|
||||
file.read_to_end(&mut content).unwrap();
|
||||
content
|
||||
}
|
||||
|
||||
pub fn available_images(&self) -> Vec<String> {
|
||||
std::fs::read_dir(&self.0.read().unwrap().image_base)
|
||||
.unwrap()
|
||||
.filter_map(|entry| match entry {
|
||||
Ok(entry_) => match mime_guess::from_path(entry_.path()).first() {
|
||||
Some(mime) if mime.type_() == mime::IMAGE => Some(
|
||||
entry_
|
||||
.path()
|
||||
.file_name()
|
||||
.and_then(|filename| filename.to_str())
|
||||
.and_then(|filename| Some(filename.to_owned()))
|
||||
.unwrap(),
|
||||
),
|
||||
_ => None,
|
||||
},
|
||||
Err(_) => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn set_background_image(&self, path: String) -> Result<(), AppError> {
|
||||
let tabletop = {
|
||||
let mut state = self.0.write().unwrap();
|
||||
state.tabletop.background_image = Some(path.clone());
|
||||
state.tabletop.clone()
|
||||
};
|
||||
self.publish(Message::UpdateTabletop(tabletop));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn publish(&self, message: Message) {
|
||||
let state = self.0.read().unwrap();
|
||||
|
||||
state.clients.values().for_each(|client| {
|
||||
if let Some(ref sender) = client.sender {
|
||||
let _ = sender.send(message.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,13 +1,6 @@
|
|||
use std::{pin::Pin, time::Duration};
|
||||
use authdb::{AuthDB, AuthToken};
|
||||
use http::{response::Response, status::StatusCode, Error};
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use warp::{http::Response, reply::Reply, ws::Message};
|
||||
|
||||
use crate::core::Core;
|
||||
|
||||
/*
|
||||
pub async fn handle_auth(
|
||||
auth_ctx: &AuthDB,
|
||||
auth_token: AuthToken,
|
||||
|
@ -29,93 +22,3 @@ pub async fn handle_auth(
|
|||
.body("".to_owned()),
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub async fn handle_file(core: Core, file_name: String) -> impl Reply {
|
||||
let mimetype = mime_guess::from_path(&file_name).first().unwrap();
|
||||
let bytes = core.get_file(file_name);
|
||||
Response::builder()
|
||||
.header("application-type", mimetype.to_string())
|
||||
.body(bytes)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn handle_available_images(core: Core) -> impl Reply {
|
||||
Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Content-Type", "application/json")
|
||||
.body(serde_json::to_string(&core.available_images()).unwrap())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct RegisterRequest {}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct RegisterResponse {
|
||||
url: String,
|
||||
}
|
||||
|
||||
pub async fn handle_register_client(core: Core, request: RegisterRequest) -> impl Reply {
|
||||
let client_id = core.register_client();
|
||||
|
||||
Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Content-Type", "application/json")
|
||||
.body(
|
||||
serde_json::to_string(&RegisterResponse {
|
||||
url: format!("ws://127.0.0.1:8001/ws/{}", client_id),
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn handle_unregister_client(core: Core, client_id: String) -> impl Reply {
|
||||
core.unregister_client(client_id);
|
||||
|
||||
warp::reply::reply()
|
||||
}
|
||||
|
||||
pub async fn handle_connect_websocket(
|
||||
core: Core,
|
||||
ws: warp::ws::Ws,
|
||||
client_id: String,
|
||||
) -> impl Reply {
|
||||
// println!("handle_connect_websocket: {}", client_id);
|
||||
ws.on_upgrade(move |socket| {
|
||||
let core = core.clone();
|
||||
async move {
|
||||
let (mut ws_sender, _) = socket.split();
|
||||
let mut receiver = core.connect_client(client_id.clone());
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let tabletop = core.0.read().unwrap().tabletop.clone();
|
||||
let _ = ws_sender
|
||||
.send(Message::text(
|
||||
serde_json::to_string(&crate::types::Message::UpdateTabletop(tabletop))
|
||||
.unwrap(),
|
||||
))
|
||||
.await;
|
||||
while let Some(msg) = receiver.recv().await {
|
||||
println!("Relaying message: {:?}", msg);
|
||||
let _ = ws_sender
|
||||
.send(Message::text(serde_json::to_string(&msg).unwrap()))
|
||||
.await;
|
||||
}
|
||||
println!("process ended for id {}", client_id);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn handle_set_background_image(core: Core, image_name: String) -> impl Reply {
|
||||
let _ = core.set_background_image(image_name);
|
||||
|
||||
Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Access-Control-Allow-Methods", "*")
|
||||
.header("Content-Type", "application/json")
|
||||
.body("")
|
||||
.unwrap()
|
||||
}
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
use authdb::AuthError;
|
||||
use handlers::{
|
||||
handle_available_images, handle_connect_websocket, handle_file, handle_register_client, handle_set_background_image, handle_unregister_client, RegisterRequest
|
||||
};
|
||||
use authdb::{AuthDB, AuthError, AuthToken, SessionToken, Username};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
use warp::{
|
||||
// header,
|
||||
http::{Response, StatusCode},
|
||||
reply::Reply,
|
||||
header,
|
||||
http::StatusCode,
|
||||
reply::{Json, Reply},
|
||||
Filter,
|
||||
};
|
||||
|
||||
mod core;
|
||||
|
||||
mod handlers;
|
||||
// use handlers::handle_auth;
|
||||
|
||||
mod types;
|
||||
use handlers::handle_auth;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Unauthorized;
|
||||
|
@ -28,7 +23,6 @@ impl warp::reject::Reject for Unauthorized {}
|
|||
struct AuthDBError(AuthError);
|
||||
impl warp::reject::Reject for AuthDBError {}
|
||||
|
||||
/*
|
||||
fn with_session(
|
||||
auth_ctx: Arc<AuthDB>,
|
||||
) -> impl Filter<Extract = (Username,), Error = warp::Rejection> + Clone {
|
||||
|
@ -77,10 +71,8 @@ fn route_echo_authenticated(
|
|||
warp::reply::json(&vec!["authenticated", username.as_str(), param.as_str()])
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible> {
|
||||
println!("handle_rejection: {:?}", err);
|
||||
if let Some(Unauthorized) = err.find() {
|
||||
Ok(warp::reply::with_status(
|
||||
"".to_owned(),
|
||||
|
@ -96,71 +88,14 @@ async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible
|
|||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
let core = core::Core::new();
|
||||
let log = warp::log("visions::api");
|
||||
let auth_db = AuthDB::new(PathBuf::from("./auth_db.sqlite"))
|
||||
.await
|
||||
.expect("AuthDB should initialize");
|
||||
let auth_ctx: Arc<AuthDB> = Arc::new(auth_db);
|
||||
|
||||
let route_image = warp::path!("api" / "v1" / "image" / String)
|
||||
.and(warp::get())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move |file_name| handle_file(core.clone(), file_name)
|
||||
});
|
||||
|
||||
let route_available_images = warp::path!("api" / "v1" / "image").and(warp::get()).then({
|
||||
let core = core.clone();
|
||||
move || handle_available_images(core.clone())
|
||||
});
|
||||
|
||||
let route_register_client = warp::path!("api" / "v1" / "client")
|
||||
.and(warp::post())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move || handle_register_client(core.clone(), RegisterRequest {})
|
||||
});
|
||||
|
||||
let route_unregister_client = warp::path!("api" / "v1" / "client" / String)
|
||||
.and(warp::delete())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move |client_id| handle_unregister_client(core.clone(), client_id)
|
||||
});
|
||||
|
||||
let route_websocket = warp::path("ws")
|
||||
.and(warp::ws())
|
||||
.and(warp::path::param())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move |ws, client_id| handle_connect_websocket(core.clone(), ws, client_id)
|
||||
});
|
||||
|
||||
let route_set_bg_image_options = warp::path!("api" / "v1" / "tabletop" / "bg_image")
|
||||
.and(warp::options())
|
||||
.map({
|
||||
move || {
|
||||
Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Access-Control-Allow-Methods", "PUT")
|
||||
.header("Access-Control-Allow-Headers", "content-type")
|
||||
.header("Content-Type", "application/json")
|
||||
.body("")
|
||||
.unwrap()
|
||||
}
|
||||
});
|
||||
let route_set_bg_image = warp::path!("api" / "v1" / "tabletop" / "bg_image")
|
||||
.and(warp::put())
|
||||
.and(warp::body::json())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move |body| handle_set_background_image(core.clone(), body)
|
||||
}).with(log);
|
||||
|
||||
let filter = route_register_client
|
||||
.or(route_unregister_client)
|
||||
.or(route_websocket)
|
||||
.or(route_image)
|
||||
.or(route_available_images)
|
||||
.or(route_set_bg_image_options)
|
||||
.or(route_set_bg_image)
|
||||
let filter = route_echo_authenticated(auth_ctx.clone())
|
||||
.or(route_authenticate(auth_ctx.clone()))
|
||||
.or(route_echo_unauthenticated())
|
||||
.recover(handle_rejection);
|
||||
|
||||
let server = warp::serve(filter);
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppError {
|
||||
JsonError(serde_json::Error),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[typeshare]
|
||||
pub struct RGB {
|
||||
pub red: u32,
|
||||
pub green: u32,
|
||||
pub blue: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[typeshare]
|
||||
pub struct Tabletop {
|
||||
pub background_color: RGB,
|
||||
pub background_image: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "type", content = "content")]
|
||||
#[typeshare]
|
||||
pub enum Message {
|
||||
UpdateTabletop(Tabletop),
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "webpack.config.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "webpack"
|
||||
},
|
||||
"author": "",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.8",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"ts-loader": "^9.4.3",
|
||||
"webpack": "^5.85.0",
|
||||
"webpack-cli": "^5.1.3"
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
type PlayingField = {
|
||||
backgroundImage: string;
|
||||
}
|
||||
|
||||
class Client {
|
||||
playingField(): PlayingField {
|
||||
return { backgroundImage: "tower-in-mist.jpg" };
|
||||
}
|
||||
|
||||
async getFile(filename: string): Promise<Blob | undefined> {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:8001/api/v1/file/${filename}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Response status: ${response.status}`);
|
||||
}
|
||||
|
||||
const body = await response.blob();
|
||||
return body;
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue