Compare commits
10 Commits
main
...
editor-cha
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 77bc77cd20 | |
Savanni D'Gerinel | 02af9e4ea7 | |
Savanni D'Gerinel | 24d266ab34 | |
Savanni D'Gerinel | a2146a0168 | |
Savanni D'Gerinel | 54b34d81ec | |
Savanni D'Gerinel | 8f4d424d1d | |
Savanni D'Gerinel | 4a62372fd3 | |
Savanni D'Gerinel | 61127339bc | |
Savanni D'Gerinel | 612713ab1b | |
Savanni D'Gerinel | fd444a620d |
File diff suppressed because it is too large
Load Diff
|
@ -2,17 +2,13 @@
|
|||
resolver = "2"
|
||||
members = [
|
||||
"authdb",
|
||||
# "bike-lights/bike",
|
||||
"bike-lights/core",
|
||||
"bike-lights/simulator",
|
||||
"changeset",
|
||||
"config",
|
||||
"config-derive",
|
||||
"coordinates",
|
||||
"cyberpunk",
|
||||
"cyber-slides",
|
||||
"cyberpunk-splash",
|
||||
"dashboard",
|
||||
"editor-challenge",
|
||||
"emseries",
|
||||
"file-service",
|
||||
"fitnesstrax/core",
|
||||
|
@ -33,5 +29,4 @@ members = [
|
|||
"timezone-testing",
|
||||
"tree",
|
||||
"visions/server",
|
||||
"gm-dash/server"
|
||||
]
|
||||
|
|
|
@ -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,10 +0,0 @@
|
|||
[package]
|
||||
name = "lights-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
az = { version = "1" }
|
||||
fixed = { version = "1" }
|
|
@ -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>,
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "editor-challenge"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1" }
|
||||
crossterm = { version = "0.19", features = [ "serde" ] }
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
serde_yml = { version = "*" }
|
||||
thiserror = { version = "1" }
|
||||
tui = { version = "0.19", default-features = false, features = [ "crossterm", "serde" ] }
|
||||
|
||||
[[bin]]
|
||||
name = "bench"
|
||||
# main = "bin/bench.rs"
|
||||
# features = [ "bench" ]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
use editor_challenge::types::bench::*;
|
||||
|
||||
fn main() {
|
||||
bench_insert_lines();
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
use std::{mem, ops::Deref};
|
||||
|
||||
const CHUNK_SIZE: usize = 10;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct NodeId(usize);
|
||||
|
||||
impl Deref for NodeId {
|
||||
type Target = usize;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Node {
|
||||
Interior {
|
||||
// the number of characters to the left of this node
|
||||
// the number of lines to the left of this node
|
||||
char_count: usize,
|
||||
|
||||
left: Option<NodeId>,
|
||||
right: Option<NodeId>,
|
||||
},
|
||||
Leaf(LeafNode),
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
Node::Interior { char_count, .. } => 0,
|
||||
Node::Leaf(ln) => ln.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LeafNode(String);
|
||||
|
||||
impl LeafNode {
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
fn take(&mut self) -> String {
|
||||
let content = mem::replace(&mut self.0, "".to_owned());
|
||||
content
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for LeafNode {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Rope is a regular tree which adds on some extra behavior for dealing with a continuous data
|
||||
/// structure. In this case, the nodes all contain strings, and the rope is arranged such that a
|
||||
/// depth-first traversal will yield the entire contents of the rope in proper order.
|
||||
#[derive(Debug)]
|
||||
pub struct Rope {
|
||||
node_count: usize,
|
||||
contents: Vec<Option<Node>>,
|
||||
}
|
||||
|
||||
impl Rope {
|
||||
/// Insert text at an index within the document. loc is the number of characters from the
|
||||
/// beginning.
|
||||
pub fn insert_at(&mut self, loc: usize, text: String) {
|
||||
match self.find_insertion_node_id(loc) {
|
||||
None => {
|
||||
let node = Node::Leaf(LeafNode::from(text));
|
||||
self.node_count += 1;
|
||||
self.contents.push(Some(node));
|
||||
}
|
||||
Some(id) => {
|
||||
self.insert_at_node(id, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Append text to the end of the document.
|
||||
pub fn append(&mut self, text: String) {
|
||||
self.insert_at(self.len(), text);
|
||||
}
|
||||
|
||||
/// Convert the entire rope back to a continuous String.
|
||||
pub fn to_string(&self) -> String {
|
||||
if self.contents.is_empty() {
|
||||
return "".to_owned();
|
||||
}
|
||||
|
||||
let mut r = String::new();
|
||||
let mut stack = vec![NodeId(0)];
|
||||
|
||||
while let Some(current_id) = stack.pop() {
|
||||
let node = &self.contents[*current_id];
|
||||
match node {
|
||||
Some(Node::Interior { left, right, .. }) => {
|
||||
if let Some(right_id) = *right {
|
||||
stack.push(right_id);
|
||||
}
|
||||
if let Some(left_id) = *left {
|
||||
stack.push(left_id);
|
||||
}
|
||||
}
|
||||
Some(Node::Leaf(ln)) => r.push_str(ln.as_str()),
|
||||
None => panic!("Should never leave an empty space in the node list"),
|
||||
}
|
||||
}
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
/// Calculate the length of the stored string.
|
||||
pub fn len(&self) -> usize {
|
||||
// This can be optimized later. Do a traversal of each right node. We already have
|
||||
// character counts of each left tree. Only count the length of the final right leaf.
|
||||
self.contents.iter().fold(0, |acc, node| {
|
||||
if let Some(Node::Leaf(s)) = node {
|
||||
acc + s.len()
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn max_depth(&self) -> usize {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn node_count(&self) -> usize {
|
||||
self.node_count
|
||||
}
|
||||
|
||||
// Find the node ID of the insertion point. This is not fully implemented, in that this
|
||||
// function ignores the offset from the beginning. Because of that, it is also always inserting
|
||||
// onto the right side, and never traversing down the left.
|
||||
fn find_insertion_node_id(&self, _loc: usize) -> Option<NodeId> {
|
||||
let mut current_id = NodeId(0);
|
||||
loop {
|
||||
match self.contents.get(*current_id) {
|
||||
Some(Some(Node::Interior { ref right, .. })) => match right {
|
||||
Some(id) => current_id = *id,
|
||||
None => return Some(current_id),
|
||||
},
|
||||
Some(Some(Node::Leaf(_))) => return Some(current_id),
|
||||
Some(None) => panic!("There should never be an empty node in the tree"),
|
||||
// This only happens when the list is empty. Otherwise, we're detecting the None in
|
||||
// advance.
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert text at a particular node location.
|
||||
//
|
||||
// This is not a self-balancing operation (yet). Once we know where text needs to be inserted,
|
||||
// based on the offset from the beginning, we can grab that node and either replace it (if it
|
||||
// is a Leaf node) or update it (if it is an Interior node).
|
||||
//
|
||||
// This function is currently naive, in that it will always assume that text needs to be added
|
||||
// to the right side, which may not be correct.
|
||||
fn insert_at_node(&mut self, id: NodeId, text: String) {
|
||||
match self.contents[*id] {
|
||||
Some(Node::Interior { ref mut right, .. }) => {
|
||||
let new_node = Node::Leaf(LeafNode::from(text));
|
||||
let new_node_id = NodeId(self.node_count + 1);
|
||||
|
||||
*right = Some(new_node_id);
|
||||
self.contents.push(Some(new_node));
|
||||
|
||||
self.node_count += 1;
|
||||
}
|
||||
Some(Node::Leaf(_)) => {
|
||||
let Some(Node::Leaf(mut ln)) = mem::replace(&mut self.contents[*id], None) else {
|
||||
panic!("Should never leave an empty space in the node list")
|
||||
};
|
||||
let contents = ln.take();
|
||||
|
||||
let lnode = Node::Leaf(LeafNode::from(contents));
|
||||
let rnode = Node::Leaf(LeafNode::from(text));
|
||||
|
||||
let lnode_id = self.node_count;
|
||||
let rnode_id = self.node_count + 1;
|
||||
|
||||
let interior_node = Node::Interior {
|
||||
char_count: lnode.len(),
|
||||
left: Some(NodeId(lnode_id)),
|
||||
right: Some(NodeId(rnode_id)),
|
||||
};
|
||||
|
||||
let _ = mem::replace(&mut self.contents[*id], Some(interior_node));
|
||||
|
||||
self.node_count += 2;
|
||||
self.contents.push(Some(lnode));
|
||||
self.contents.push(Some(rnode));
|
||||
|
||||
}
|
||||
None => panic!("Should never leave an empty space in the node list"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Rope {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
node_count: 0,
|
||||
contents: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the initial rope. The simplest way is to split along lines and turn each line into its
|
||||
// own leaf node.
|
||||
impl From<String> for Rope {
|
||||
fn from(s: String) -> Self {
|
||||
let mut rope = Rope::default();
|
||||
#[allow(unused_assignments)]
|
||||
let mut first = s.as_str();
|
||||
let mut lst = s.as_str();
|
||||
|
||||
while lst.len() > CHUNK_SIZE {
|
||||
(first, lst) = lst.split_at(CHUNK_SIZE);
|
||||
rope.append(first.to_owned());
|
||||
}
|
||||
rope.append(lst.to_owned());
|
||||
rope
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
struct TestCase {
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_creates_a_rope_from_a_string() {
|
||||
let test_cases = vec![
|
||||
TestCase{ content: "".to_owned() },
|
||||
TestCase{ content: "This".to_owned() },
|
||||
TestCase{ content: "This is some basic".to_owned() },
|
||||
TestCase{ content: "This is some basic context which is much smaller".to_owned() },
|
||||
TestCase{ content:
|
||||
"This is some basic context which is much smaller than the rope is designed for."
|
||||
.to_owned()
|
||||
},
|
||||
];
|
||||
|
||||
for case in test_cases {
|
||||
let rope = Rope::from(case.content.clone());
|
||||
|
||||
for (idx, node) in rope.contents.iter().enumerate() {
|
||||
}
|
||||
|
||||
assert_eq!(rope.len(), case.content.len(), "{}", case.content);
|
||||
assert_eq!(rope.to_string(), case.content, "{:?}", case.content);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
pub mod doc_rope;
|
||||
pub mod ui;
|
||||
pub mod state;
|
||||
pub mod types;
|
||||
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers};
|
||||
use editor_challenge::*;
|
||||
use state::AppState;
|
||||
use std::{
|
||||
env, io,
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
use ui::Canvas;
|
||||
|
||||
// const TITLE: &str = "Text Editor Challenge";
|
||||
// const COPYRIGHT: &str = "(c) Savanni D'Gerinel - all rights reserved";
|
||||
const TICK_RATE_MS: u64 = 200;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Event<I> {
|
||||
Input(I),
|
||||
Tick,
|
||||
}
|
||||
|
||||
fn handle_input(tx: mpsc::Sender<Event<KeyEvent>>, tick_rate: Duration) {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
let timeout = tick_rate
|
||||
.checked_sub(last_tick.elapsed())
|
||||
.unwrap_or_else(|| Duration::from_secs(0));
|
||||
|
||||
if event::poll(timeout).expect("poll works") {
|
||||
if let event::Event::Key(key) = event::read().expect("can read events") {
|
||||
tx.send(Event::Input(key)).expect("can send events");
|
||||
}
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate && tx.send(Event::Tick).is_ok() {
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn app_loop<T>(
|
||||
mut app_state: AppState,
|
||||
mut screen: Canvas<T>,
|
||||
rx: mpsc::Receiver<Event<KeyEvent>>,
|
||||
) -> Result<(), anyhow::Error>
|
||||
where
|
||||
T: tui::backend::Backend,
|
||||
{
|
||||
loop {
|
||||
screen.render(&app_state)?;
|
||||
|
||||
match rx.recv()? {
|
||||
Event::Input(event)
|
||||
if event.code == KeyCode::Char('x') && event.modifiers == KeyModifiers::CONTROL =>
|
||||
{
|
||||
break;
|
||||
}
|
||||
Event::Input(event) => app_state.handle_event(event),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
let args = env::args().collect::<Vec<String>>();
|
||||
|
||||
let file_name = if args.len() > 1 { Some(&args[1]) } else { None };
|
||||
|
||||
let app_state = match file_name {
|
||||
Some(name) => AppState::open(name.into()),
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let tick_rate = Duration::from_millis(TICK_RATE_MS);
|
||||
thread::spawn(move || {
|
||||
handle_input(tx, tick_rate);
|
||||
});
|
||||
|
||||
let stdout = io::stdout();
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.clear()?;
|
||||
|
||||
let screen = Canvas::new(terminal);
|
||||
|
||||
let result = app_loop(app_state, screen, rx);
|
||||
|
||||
result?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
|
||||
use crate::types::{Cursor, Document};
|
||||
use std::{fs::File, io::{BufRead, BufReader}, path::PathBuf};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AppState {
|
||||
pub path: Option<PathBuf>,
|
||||
pub cursor: Cursor,
|
||||
pub contents: Document, // Obviously this is bad, but it's also only temporary.
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn open(path: PathBuf) -> Self {
|
||||
let file = File::open(path.clone()).unwrap();
|
||||
let reader = BufReader::new(file);
|
||||
let contents = reader
|
||||
.lines()
|
||||
.collect::<Result<Vec<String>, std::io::Error>>()
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
path: Some(path),
|
||||
cursor: Default::default(),
|
||||
contents: Document::new(contents),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_up(&mut self) {
|
||||
self.cursor.cursor_up(&self.contents);
|
||||
}
|
||||
|
||||
pub fn cursor_down(&mut self) {
|
||||
self.cursor.cursor_down(&self.contents);
|
||||
}
|
||||
|
||||
pub fn cursor_right(&mut self) {
|
||||
self.cursor.cursor_right(&self.contents);
|
||||
}
|
||||
|
||||
pub fn cursor_left(&mut self) {
|
||||
self.cursor.cursor_left();
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: KeyEvent) {
|
||||
let KeyEvent { code, .. }: KeyEvent = event;
|
||||
match code {
|
||||
KeyCode::Down => {
|
||||
self.cursor_down();
|
||||
}
|
||||
KeyCode::Up => {
|
||||
self.cursor_up();
|
||||
}
|
||||
KeyCode::Right => {
|
||||
self.cursor_right();
|
||||
}
|
||||
KeyCode::Left => {
|
||||
self.cursor_left();
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
self.contents.backspace(&mut self.cursor);
|
||||
}
|
||||
KeyCode::Delete => {
|
||||
self.contents.delete_at(&mut self.cursor);
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
self.contents.new_line(&mut self.cursor);
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
self.contents.insert_at(&mut self.cursor, c);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
// TODO: I'm increasingly feeling that cursors are per-document, not per-application. So I think I
|
||||
// want to move the cursor into here, and then rendering requires asking for the cursor for the
|
||||
// current document.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Document {
|
||||
rows: Vec<String>,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
pub fn new(contents: Vec<String>) -> Self {
|
||||
if contents.len() > (u16::MAX.into()) {
|
||||
panic!("Document row count exceeds u16::MAX. The current scrolling code cannot handle that.");
|
||||
}
|
||||
Self { rows: contents }
|
||||
}
|
||||
|
||||
pub fn line(&self, id: usize) -> Option<&str> {
|
||||
self.rows.get(id).map(|x| x.as_str())
|
||||
}
|
||||
|
||||
pub fn contents(&self) -> String {
|
||||
self.rows.join("\n")
|
||||
}
|
||||
|
||||
pub fn row_length(&self, idx: usize) -> usize {
|
||||
self.rows[idx].len()
|
||||
}
|
||||
|
||||
pub fn row_count(&self) -> usize {
|
||||
self.rows.len()
|
||||
}
|
||||
|
||||
pub fn insert_at(&mut self, cursor: &mut Cursor, c: char) {
|
||||
let (row, column) = cursor.addr();
|
||||
|
||||
self.rows[row].insert(column, c);
|
||||
cursor.cursor_right(self);
|
||||
}
|
||||
|
||||
pub fn backspace(&mut self, cursor: &mut Cursor) {
|
||||
let (row, column) = cursor.addr();
|
||||
if cursor.column > 0 {
|
||||
let _ = self.rows[row].remove(column - 1);
|
||||
cursor.cursor_left();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_at(&mut self, cursor: &mut Cursor) {
|
||||
let (row, column) = cursor.addr();
|
||||
if cursor.column < self.rows[row].len() {
|
||||
self.rows[row].remove(column);
|
||||
cursor.correct_columns(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_line(&mut self, cursor: &mut Cursor) {
|
||||
// when doing a newline, take everything to the right of the cursor from the current line
|
||||
// and move it to the next line.
|
||||
let (row, _) = cursor.addr();
|
||||
|
||||
self.rows.insert(row, String::new());
|
||||
cursor.cursor_down(&self);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cursor {
|
||||
row: usize,
|
||||
column: usize,
|
||||
desired_column: usize,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn addr(&self) -> (usize, usize) {
|
||||
(self.row, self.column)
|
||||
}
|
||||
|
||||
pub fn cursor_up(&mut self, doc: &Document) {
|
||||
if self.row > 0 {
|
||||
self.row -= 1;
|
||||
}
|
||||
self.correct_columns(doc);
|
||||
}
|
||||
|
||||
pub fn cursor_down(&mut self, doc: &Document) {
|
||||
if self.row < doc.row_count() - 1 {
|
||||
self.row += 1;
|
||||
}
|
||||
self.correct_columns(doc);
|
||||
}
|
||||
|
||||
pub fn correct_columns(&mut self, doc: &Document) {
|
||||
let row_len = doc.row_length(self.row);
|
||||
if self.desired_column < row_len {
|
||||
self.column = self.desired_column;
|
||||
} else {
|
||||
self.column = row_len;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_right(&mut self, doc: &Document) {
|
||||
if self.column < doc.row_length(self.row) {
|
||||
self.column += 1;
|
||||
}
|
||||
self.desired_column = self.column;
|
||||
}
|
||||
|
||||
pub fn cursor_left(&mut self) {
|
||||
if self.column > 0 {
|
||||
self.column -= 1;
|
||||
}
|
||||
self.desired_column = self.column;
|
||||
}
|
||||
}
|
||||
|
||||
mod test_utils {
|
||||
use super::*;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub fn with_moby_dick<F>(test: F)
|
||||
where
|
||||
F: FnOnce(Document),
|
||||
{
|
||||
let f = File::open("fixtures/moby-dick.txt").unwrap();
|
||||
let reader = BufReader::new(f);
|
||||
let contents = reader
|
||||
.lines()
|
||||
.collect::<Result<Vec<String>, std::io::Error>>()
|
||||
.unwrap();
|
||||
let doc = Document::new(contents);
|
||||
test(doc);
|
||||
}
|
||||
|
||||
pub fn measure<F>(test: F) -> Duration
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
let start = Instant::now();
|
||||
test();
|
||||
let end = Instant::now();
|
||||
end - start
|
||||
}
|
||||
|
||||
pub fn benchmark<A>(
|
||||
num_iterations: usize,
|
||||
setup: impl Fn() -> A,
|
||||
test: impl FnOnce(A) + Copy,
|
||||
) -> Duration {
|
||||
let mut measurements: Duration = Duration::from_millis(0);
|
||||
|
||||
for _i in 0..num_iterations {
|
||||
let data = setup();
|
||||
measurements += measure(move || test(data))
|
||||
}
|
||||
|
||||
measurements / (num_iterations as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{test_utils::*, *};
|
||||
|
||||
#[test]
|
||||
fn it_inserts_a_line() {
|
||||
with_moby_dick(|mut doc| {
|
||||
let mut cursor = Cursor::default();
|
||||
|
||||
let num_lines = doc.row_count();
|
||||
assert_eq!(
|
||||
doc.line(num_lines - 3),
|
||||
Some("subscribe to our email newsletter to hear about new eBooks.")
|
||||
);
|
||||
|
||||
doc.new_line(&mut cursor);
|
||||
assert_eq!(doc.row_count(), num_lines + 1);
|
||||
assert_eq!(doc.line(0), Some(""));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bench {
|
||||
use super::{test_utils::*, *};
|
||||
|
||||
pub fn bench_insert_lines() {
|
||||
with_moby_dick(|doc| {
|
||||
let performance = benchmark(
|
||||
1000,
|
||||
|| doc.clone(),
|
||||
|mut doc| {
|
||||
let mut cursor = Cursor::default();
|
||||
doc.new_line(&mut cursor);
|
||||
},
|
||||
);
|
||||
println!("[bench_insert_lines] {:?}", performance);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tui::{layout::{Alignment, Constraint, Direction, Layout}, style::{Color, Style}, widgets::{Block, BorderType, Borders, Paragraph}, Terminal};
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
pub struct Canvas<T: tui::backend::Backend> {
|
||||
top_row: usize,
|
||||
terminal: Terminal<T>,
|
||||
}
|
||||
|
||||
impl<T: tui::backend::Backend> Canvas<T> {
|
||||
pub fn new(terminal: Terminal<T>) -> Self {
|
||||
enable_raw_mode().unwrap();
|
||||
Self {
|
||||
top_row: 0,
|
||||
terminal
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, app_state: &AppState) -> Result<(), anyhow::Error>
|
||||
{
|
||||
self.terminal.draw(|rect| {
|
||||
let size = rect.size();
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Min(2),
|
||||
Constraint::Length(3),
|
||||
// Constraint::Length(3),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(size);
|
||||
|
||||
let title = Paragraph::new(
|
||||
app_state
|
||||
.path
|
||||
.clone()
|
||||
.map(|path| path.to_string_lossy().into_owned())
|
||||
.unwrap_or("No file opened".to_owned()),
|
||||
)
|
||||
.style(Style::default().fg(Color::LightCyan))
|
||||
.alignment(Alignment::Center)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.border_type(BorderType::Plain),
|
||||
);
|
||||
rect.render_widget(title, chunks[1]);
|
||||
|
||||
/*
|
||||
let cp = Paragraph::new(COPYRIGHT)
|
||||
.style(Style::default().fg(Color::LightCyan))
|
||||
.alignment(Alignment::Center)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.title("Copyright")
|
||||
.border_type(BorderType::Plain),
|
||||
);
|
||||
rect.render_widget(cp, chunks[2]);
|
||||
*/
|
||||
|
||||
let (row, column) = app_state.cursor.addr();
|
||||
if row == self.top_row && row >= 1 {
|
||||
self.top_row -= 1;
|
||||
} else if row - self.top_row == (chunks[0].height - 1).into() {
|
||||
self.top_row += 1;
|
||||
}
|
||||
|
||||
let contents = Paragraph::new(app_state.contents.contents()).scroll((self.top_row as u16, 0));
|
||||
rect.render_widget(contents, chunks[0]);
|
||||
|
||||
rect.set_cursor(chunks[0].x + column as u16, chunks[0].y + (row - self.top_row) as u16);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: tui::backend::Backend> Drop for Canvas<T> {
|
||||
fn drop(&mut self) {
|
||||
let _ = disable_raw_mode();
|
||||
let _ = self.terminal.show_cursor();
|
||||
}
|
||||
}
|
||||
|
||||
|
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"
|
||||
]
|
||||
}
|
|
@ -115,6 +115,8 @@ pub struct Core {
|
|||
|
||||
impl Core {
|
||||
pub fn new(config: Config) -> Self {
|
||||
println!("config: {:?}", config);
|
||||
|
||||
let library = match config.get::<LibraryPath>() {
|
||||
Some(ref path) if path.to_path_buf().exists() => {
|
||||
Some(Database::open_path(path.to_path_buf()).unwrap())
|
||||
|
|
|
@ -213,7 +213,7 @@ impl Goban {
|
|||
///
|
||||
/// assert_eq!(goban.stone(&Coordinate{ row: 3, column: 3 }), Some(Color::Black));
|
||||
/// assert_eq!(goban.stone(&Coordinate{ row: 15, column: 15 }), Some(Color::White));
|
||||
/// assert_eq!(goban.stone(&Coordinate{ row: 15, column: 3 }), Some(Color::Black));
|
||||
/// assert_eq!(goban.stone(&Coordinate{ row: 3, column: 15 }), Some(Color::Black));
|
||||
/// ```
|
||||
pub fn apply_moves<'a>(
|
||||
self,
|
||||
|
@ -611,6 +611,7 @@ mod test {
|
|||
),
|
||||
];
|
||||
|
||||
println!("{}", board);
|
||||
for (board, coordinate, group, liberties) in test_cases {
|
||||
assert_eq!(board.group(&coordinate), group.as_ref());
|
||||
assert_eq!(
|
||||
|
|
|
@ -32,6 +32,3 @@ mod types;
|
|||
pub use types::{
|
||||
BoardError, Color, Config, ConfigOption, DepthTree, LibraryPath, Player, Rank, Size,
|
||||
};
|
||||
|
||||
mod view_models;
|
||||
pub use view_models::GameReviewViewModel;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::goban::{Coordinate, Goban};
|
||||
use config::define_config;
|
||||
use config_derive::ConfigOption;
|
||||
use nary_tree::NodeRef;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sgf::GameTree;
|
||||
use std::{
|
||||
|
@ -243,39 +242,6 @@ pub struct Tree<T> {
|
|||
}
|
||||
*/
|
||||
|
||||
// https://llimllib.github.io/pymag-trees/
|
||||
// I want to take advantage of the Wetherell Shannon algorithm, but I want some variations. In
|
||||
// their diagram, they got a tree that looks like this.
|
||||
//
|
||||
// O
|
||||
// |\
|
||||
// O O
|
||||
// |\ \ \
|
||||
// O O O O
|
||||
// |\ |\
|
||||
// O O O O
|
||||
//
|
||||
// In the same circumstance, what I want is this:
|
||||
//
|
||||
// O--
|
||||
// | \
|
||||
// O O
|
||||
// |\ |\
|
||||
// O O O O
|
||||
// |\
|
||||
// O O
|
||||
//
|
||||
// In order to keep things from being overly smooshed, I want to ensure that if a branch overlaps
|
||||
// with another branch, there is some extra drawing space. This might actually be similar to adding
|
||||
// the principal that "A parent should be centered over its children".
|
||||
//
|
||||
// So, given a tree, I need to know how many children exist at each level. Then I build parents
|
||||
// atop the children. At level 3, I have four children, and that happens to be the maximum width of
|
||||
// the graph.
|
||||
//
|
||||
// A bottom-up traversal:
|
||||
// - Figure out the number of nodes at each depth
|
||||
|
||||
pub struct DepthTree(nary_tree::Tree<SizeNode>);
|
||||
|
||||
impl Deref for DepthTree {
|
||||
|
@ -286,17 +252,12 @@ impl Deref for DepthTree {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for DepthTree {
|
||||
fn default() -> Self {
|
||||
Self(nary_tree::Tree::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SizeNode {
|
||||
/// Use this to map back to the node in the original game tree. This way we know how to
|
||||
/// correspond from a node in the review tree back to there.
|
||||
pub game_node_id: nary_tree::NodeId,
|
||||
#[allow(dead_code)]
|
||||
game_node_id: nary_tree::NodeId,
|
||||
|
||||
/// How deep into the tree is this node?
|
||||
depth: usize,
|
||||
|
@ -312,17 +273,32 @@ impl SizeNode {
|
|||
}
|
||||
|
||||
impl DepthTree {
|
||||
/*
|
||||
pub fn node(&self, idx: usize) -> &T {
|
||||
&self.nodes[idx].content
|
||||
// My previous work to convert from a node tree to this tree-with-width dependend on the node tree
|
||||
// being a recursive data structure. Now I need to find a way to convert a slab tree to this width
|
||||
// tree.
|
||||
//
|
||||
// It all feels like a lot of custom weirdness. I shouldn't need a bunch of custom data structures,
|
||||
// so I want to eliminate the "Tree" above and keep using the slab tree. I think I should be able
|
||||
// to build these Node objects without needing a custom data structure.
|
||||
fn new() -> Self {
|
||||
Self(nary_tree::Tree::new())
|
||||
/*
|
||||
Tree {
|
||||
nodes: vec![Node {
|
||||
id: 0,
|
||||
node: root,
|
||||
parent: None,
|
||||
depth: 0,
|
||||
width: RefCell::new(None),
|
||||
children: vec![],
|
||||
}],
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn parent(&self, node: &Node<T>) -> Option<&Node<T>> {
|
||||
if let Some(parent_idx) = node.parent {
|
||||
self.nodes.get(parent_idx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
/*
|
||||
pub fn node(&self, idx: usize) -> &T {
|
||||
&self.nodes[idx].node
|
||||
}
|
||||
|
||||
// Add a node to the parent specified by parent_idx. Return the new index. This cannot be used
|
||||
|
@ -333,7 +309,7 @@ impl DepthTree {
|
|||
|
||||
self.nodes.push(Node {
|
||||
id: next_idx,
|
||||
content: node,
|
||||
node,
|
||||
parent: Some(parent_idx),
|
||||
depth: parent.depth + 1,
|
||||
width: RefCell::new(None),
|
||||
|
@ -352,6 +328,7 @@ impl DepthTree {
|
|||
.unwrap()
|
||||
.traverse_pre_order()
|
||||
.fold(0, |max, node| {
|
||||
println!("node depth: {}", node.data().depth);
|
||||
if node.data().depth > max {
|
||||
node.data().depth
|
||||
} else {
|
||||
|
@ -510,7 +487,7 @@ impl<'a> From<&'a GameTree> for DepthTree {
|
|||
|
||||
Self(tree)
|
||||
}
|
||||
None => Self::default(),
|
||||
None => Self::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -552,7 +529,7 @@ pub struct BFSIter<'a, T> {
|
|||
}
|
||||
|
||||
impl<'a, T> Iterator for BFSIter<'a, T> {
|
||||
type Item = NodeRef<'a, T>;
|
||||
type Item = &'a T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let retval = self.queue.pop_front();
|
||||
|
@ -561,7 +538,7 @@ impl<'a, T> Iterator for BFSIter<'a, T> {
|
|||
.children()
|
||||
.for_each(|noderef| self.queue.push_back(noderef));
|
||||
}
|
||||
retval
|
||||
retval.map(|retval| retval.data())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -653,7 +630,7 @@ mod test {
|
|||
)))
|
||||
.node_id();
|
||||
|
||||
let _node_d = game_tree
|
||||
let node_d = game_tree
|
||||
.get_mut(node_c)
|
||||
.unwrap()
|
||||
.append(GameNode::MoveNode(MoveNode::new(
|
||||
|
@ -662,7 +639,7 @@ mod test {
|
|||
)))
|
||||
.node_id();
|
||||
|
||||
let _node_e = game_tree
|
||||
let node_e = game_tree
|
||||
.get_mut(node_c)
|
||||
.unwrap()
|
||||
.append(GameNode::MoveNode(MoveNode::new(
|
||||
|
@ -671,7 +648,7 @@ mod test {
|
|||
)))
|
||||
.node_id();
|
||||
|
||||
let _node_f = game_tree
|
||||
let node_f = game_tree
|
||||
.get_mut(node_c)
|
||||
.unwrap()
|
||||
.append(GameNode::MoveNode(MoveNode::new(
|
||||
|
@ -680,7 +657,7 @@ mod test {
|
|||
)))
|
||||
.node_id();
|
||||
|
||||
let _node_g = game_tree
|
||||
let node_g = game_tree
|
||||
.get_mut(node_a)
|
||||
.unwrap()
|
||||
.append(GameNode::MoveNode(MoveNode::new(
|
||||
|
@ -755,4 +732,66 @@ mod test {
|
|||
assert_eq!(tree.position(test_tree.node_g), (1, 4));
|
||||
*/
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn breadth_first_iter() {
|
||||
/*
|
||||
let mut node_a = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
let mut node_b = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
let mut node_c = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_d = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_e = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_f = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_g = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
let mut node_h = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_i = MoveNode::new(sgf::Color::Black, Move::Move("dp".to_owned()));
|
||||
|
||||
let game = GameRecord::new(
|
||||
GameType::Go,
|
||||
Size {
|
||||
width: 19,
|
||||
height: 19,
|
||||
},
|
||||
Player {
|
||||
name: Some("Black".to_owned()),
|
||||
rank: None,
|
||||
team: None,
|
||||
},
|
||||
Player {
|
||||
name: Some("White".to_owned()),
|
||||
rank: None,
|
||||
team: None,
|
||||
},
|
||||
);
|
||||
|
||||
node_c.children.push(GameNode::MoveNode(node_d.clone()));
|
||||
node_c.children.push(GameNode::MoveNode(node_e.clone()));
|
||||
node_c.children.push(GameNode::MoveNode(node_f.clone()));
|
||||
|
||||
node_b.children.push(GameNode::MoveNode(node_c.clone()));
|
||||
|
||||
node_h.children.push(GameNode::MoveNode(node_i.clone()));
|
||||
|
||||
node_a.children.push(GameNode::MoveNode(node_b.clone()));
|
||||
node_a.children.push(GameNode::MoveNode(node_g.clone()));
|
||||
node_a.children.push(GameNode::MoveNode(node_h.clone()));
|
||||
|
||||
let game_tree = GameNode::MoveNode(node_a.clone());
|
||||
|
||||
let tree = Tree::from(&game_tree);
|
||||
|
||||
let mut iter = tree.bfs_iter();
|
||||
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_a.id));
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_b.id));
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_g.id));
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_h.id));
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_c.id));
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_i.id));
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_d.id));
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_e.id));
|
||||
assert_matches!(iter.next(), Some(Node { node: uuid, .. }) => assert_eq!(*uuid, node_f.id));
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,281 +0,0 @@
|
|||
/*
|
||||
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
||||
|
||||
This file is part of On the Grid.
|
||||
|
||||
On the Grid is free software: you can redistribute it and/or modify it under the terms of
|
||||
the GNU General Public License as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
On the Grid is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with On the Grid. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Currenty my game review is able to show the current game and its tree. Now, I want to start
|
||||
// tracking where in the tree that I am. This should be a combination of the abstract Tree and the
|
||||
// gameTree. Chances are, if I just keep track of where I am in the abstract tree, I can find the
|
||||
// relevant node in the game tree and then reproduce the line to get to that node.
|
||||
//
|
||||
// Moving through the game review tree shouldn't require a full invocatian. This object, and most
|
||||
// other view models, should be exported to the UI.
|
||||
|
||||
use crate::{types::SizeNode, DepthTree, Goban};
|
||||
use nary_tree::{NodeId, NodeRef};
|
||||
use sgf::{GameRecord, Player};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
struct GameReviewViewModelPrivate {
|
||||
// This is the ID of the current position in the game. The ID is specific to the GameRecord,
|
||||
// not the ReviewTree.
|
||||
current_position: Option<NodeId>,
|
||||
game: GameRecord,
|
||||
review_tree: DepthTree,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GameReviewViewModel {
|
||||
inner: Arc<RwLock<GameReviewViewModelPrivate>>,
|
||||
}
|
||||
|
||||
impl GameReviewViewModel {
|
||||
pub fn new(game: GameRecord) -> Self {
|
||||
let (review_tree, current_position) = if !game.trees.is_empty() {
|
||||
let review_tree = DepthTree::from(&game.trees[0]);
|
||||
let current_position = game.mainline().unwrap().last().map(|nr| nr.node_id());
|
||||
(review_tree, current_position)
|
||||
} else {
|
||||
(DepthTree::default(), None)
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(GameReviewViewModelPrivate {
|
||||
current_position,
|
||||
game,
|
||||
review_tree,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn black_player(&self) -> Player {
|
||||
self.inner.read().unwrap().game.black_player.clone()
|
||||
}
|
||||
|
||||
pub fn white_player(&self) -> Player {
|
||||
self.inner.read().unwrap().game.white_player.clone()
|
||||
}
|
||||
|
||||
pub fn game_view(&self) -> Goban {
|
||||
let inner = self.inner.read().unwrap();
|
||||
|
||||
let mut path: Vec<NodeId> = vec![];
|
||||
let mut current_id = inner.current_position;
|
||||
while current_id.is_some() {
|
||||
let current = current_id.unwrap();
|
||||
path.push(current);
|
||||
current_id = inner.game.trees[0]
|
||||
.get(current)
|
||||
.unwrap()
|
||||
.parent()
|
||||
.map(|parent| parent.node_id());
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
Goban::default()
|
||||
.apply_moves(
|
||||
path.into_iter()
|
||||
.map(|node_id| inner.game.trees[0].get(node_id).unwrap().data()),
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
/*
|
||||
if let Some(start) = inner.current_position {
|
||||
let mut current_id = start;
|
||||
let mut path = vec![current_id.clone()];
|
||||
|
||||
while let
|
||||
/*
|
||||
let mut current_node = inner.game.trees[0].get(current_position).unwrap();
|
||||
let mut path = vec![current_node.data()];
|
||||
while let Some(parent) = current_node.parent() {
|
||||
path.push(parent.data());
|
||||
current_node = parent;
|
||||
}
|
||||
*/
|
||||
|
||||
path.reverse();
|
||||
Goban::default().apply_moves(path).unwrap()
|
||||
} else {
|
||||
Goban::default()
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn map_tree<F>(&self, f: F)
|
||||
where
|
||||
F: Fn(NodeRef<'_, SizeNode>, Option<NodeId>),
|
||||
{
|
||||
let inner = self.inner.read().unwrap();
|
||||
|
||||
for node in inner.review_tree.bfs_iter() {
|
||||
f(node, inner.current_position);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tree_max_depth(&self) -> usize {
|
||||
self.inner.read().unwrap().review_tree.max_depth()
|
||||
}
|
||||
|
||||
// When moving forward on the tree, I grab the first child by default. I can then just advance
|
||||
// the board state by applying the child.
|
||||
pub fn next_move(&self) {
|
||||
let mut inner = self.inner.write().unwrap();
|
||||
let current_position = inner.current_position.clone();
|
||||
match current_position {
|
||||
Some(current_position) => {
|
||||
let current_id = current_position.clone();
|
||||
let node = inner.game.trees[0].get(current_id).unwrap();
|
||||
if let Some(next_id) = node.first_child().map(|child| child.node_id()) {
|
||||
inner.current_position = Some(next_id);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
inner.current_position = inner.game.trees[0].root().map(|node| node.node_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When moving backwards, I jump up to the parent. I'll then rebuild the board state from the
|
||||
// root.
|
||||
pub fn previous_move(&mut self) {
|
||||
let mut inner = self.inner.write().unwrap();
|
||||
if let Some(current_position) = inner.current_position {
|
||||
let current_node = inner.game.trees[0]
|
||||
.get(current_position)
|
||||
.expect("current_position should always correspond to a node in the tree");
|
||||
if let Some(parent_node) = current_node.parent() {
|
||||
inner.current_position = Some(parent_node.node_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_variant(&self) {
|
||||
println!("move to the next variant amongst the options available");
|
||||
}
|
||||
|
||||
pub fn previous_variant(&self) {
|
||||
println!("move to the previous variant amongst the options available");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{Color, Coordinate};
|
||||
use std::path::Path;
|
||||
|
||||
fn with_game_record<F>(test: F)
|
||||
where
|
||||
F: FnOnce(GameReviewViewModel),
|
||||
{
|
||||
let records = sgf::parse_sgf_file(&Path::new("../../sgf/test_data/branch_test.sgf"))
|
||||
.expect("to successfully load the test file");
|
||||
let record = records[0]
|
||||
.as_ref()
|
||||
.expect("to have successfully loaded the test record");
|
||||
|
||||
let view_model = GameReviewViewModel::new(record.clone());
|
||||
|
||||
test(view_model);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_generates_a_mainline_board() {
|
||||
with_game_record(|view| {
|
||||
let goban = view.game_view();
|
||||
|
||||
for row in 0..18 {
|
||||
for column in 0..18 {
|
||||
if row == 3 && column == 3 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
|
||||
} else if row == 15 && column == 3 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
|
||||
} else if row == 3 && column == 15 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
|
||||
} else if row == 15 && column == 14 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
|
||||
} else {
|
||||
assert_eq!(
|
||||
goban.stone(&Coordinate { row, column }),
|
||||
None,
|
||||
"{} {}",
|
||||
row,
|
||||
column
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_moves_to_the_previous_mainline_move() {
|
||||
with_game_record(|mut view| {
|
||||
view.previous_move();
|
||||
let goban = view.game_view();
|
||||
|
||||
for row in 0..18 {
|
||||
for column in 0..18 {
|
||||
if row == 3 && column == 3 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
|
||||
} else if row == 3 && column == 15 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
|
||||
} else if row == 15 && column == 14 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
|
||||
} else {
|
||||
assert_eq!(
|
||||
goban.stone(&Coordinate { row, column }),
|
||||
None,
|
||||
"{} {}",
|
||||
row,
|
||||
column
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_moves_to_the_next_node() {
|
||||
with_game_record(|mut view| {
|
||||
view.previous_move();
|
||||
view.next_move();
|
||||
let goban = view.game_view();
|
||||
|
||||
for row in 0..18 {
|
||||
for column in 0..18 {
|
||||
if row == 3 && column == 3 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
|
||||
} else if row == 15 && column == 3 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::White));
|
||||
} else if row == 3 && column == 15 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
|
||||
} else if row == 15 && column == 14 {
|
||||
assert_eq!(goban.stone(&Coordinate { row, column }), Some(Color::Black));
|
||||
} else {
|
||||
assert_eq!(
|
||||
goban.stone(&Coordinate { row, column }),
|
||||
None,
|
||||
"{} {}",
|
||||
row,
|
||||
column
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
||||
|
||||
This file is part of On the Grid.
|
||||
|
||||
On the Grid is free software: you can redistribute it and/or modify it under the terms of
|
||||
the GNU General Public License as published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
On the Grid is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with On the Grid. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
mod game_review;
|
||||
pub use game_review::GameReviewViewModel;
|
||||
|
|
@ -17,14 +17,12 @@ You should have received a copy of the GNU General Public License along with On
|
|||
use crate::{CoreApi, ResourceManager};
|
||||
use adw::prelude::*;
|
||||
|
||||
use glib::Propagation;
|
||||
use gtk::{gdk::Key, EventControllerKey};
|
||||
use otg_core::{
|
||||
settings::{SettingsRequest, SettingsResponse},
|
||||
CoreRequest, CoreResponse, GameReviewViewModel,
|
||||
CoreRequest, CoreResponse,
|
||||
};
|
||||
use sgf::GameRecord;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::{rc::Rc, sync::{Arc, RwLock}};
|
||||
|
||||
use crate::views::{GameReview, HomeView, SettingsView};
|
||||
|
||||
|
@ -93,32 +91,13 @@ impl AppWindow {
|
|||
|
||||
pub fn open_game_review(&self, game_record: GameRecord) {
|
||||
let header = adw::HeaderBar::new();
|
||||
let game_review = GameReview::new(
|
||||
GameReviewViewModel::new(game_record),
|
||||
self.resources.clone(),
|
||||
);
|
||||
let game_review = GameReview::new(self.core.clone(), game_record, self.resources.clone());
|
||||
|
||||
let layout = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.build();
|
||||
layout.append(&header);
|
||||
layout.append(&game_review.widget());
|
||||
|
||||
// This controller ensures that navigational keypresses get sent to the game review so that
|
||||
// they're not changing the cursor focus in the app.
|
||||
let keypress_controller = EventControllerKey::new();
|
||||
keypress_controller.connect_key_pressed({
|
||||
move |s, key, _, _| {
|
||||
println!("layout keypress: {}", key);
|
||||
if s.forward(&game_review.widget()) {
|
||||
Propagation::Stop
|
||||
} else {
|
||||
Propagation::Proceed
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
layout.add_controller(keypress_controller);
|
||||
layout.append(&game_review);
|
||||
|
||||
let page = adw::NavigationPage::builder()
|
||||
.can_pop(true)
|
||||
|
|
|
@ -37,10 +37,16 @@ You should have received a copy of the GNU General Public License along with On
|
|||
|
||||
use crate::{perftrace, Resource, ResourceManager};
|
||||
|
||||
use gio::resources_lookup_data;
|
||||
use glib::Object;
|
||||
use gtk::{gdk_pixbuf::Pixbuf, prelude::*, subclass::prelude::*};
|
||||
use gtk::{
|
||||
gdk_pixbuf::{Colorspace, InterpType, Pixbuf},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use image::{io::Reader as ImageReader, ImageError};
|
||||
use otg_core::{Color, Coordinate};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, io::Cursor, rc::Rc};
|
||||
|
||||
const WIDTH: i32 = 800;
|
||||
const HEIGHT: i32 = 800;
|
||||
|
@ -101,12 +107,17 @@ impl Goban {
|
|||
s
|
||||
}
|
||||
|
||||
pub fn set_board_state(&mut self, board_state: otg_core::Goban) {
|
||||
*self.imp().board_state.borrow_mut() = board_state;
|
||||
self.queue_draw();
|
||||
}
|
||||
|
||||
fn redraw(&self, ctx: &cairo::Context, width: i32, height: i32) {
|
||||
println!("{} x {}", width, height);
|
||||
/*
|
||||
let background = load_pixbuf(
|
||||
"/com/luminescent-dreams/otg-gtk/wood_texture.jpg",
|
||||
false,
|
||||
WIDTH + 40,
|
||||
HEIGHT + 40,
|
||||
);
|
||||
*/
|
||||
|
||||
let background = self
|
||||
.imp()
|
||||
.resource_manager
|
||||
|
@ -246,11 +257,11 @@ impl Pen {
|
|||
let (x_loc, y_loc) = self.stone_location(row, col);
|
||||
match color {
|
||||
Color::White => match self.white_stone {
|
||||
Some(ref white_stone) => ctx.set_source_pixbuf(white_stone, x_loc, y_loc),
|
||||
Some(ref white_stone) => ctx.set_source_pixbuf(&white_stone, x_loc, y_loc),
|
||||
None => ctx.set_source_rgb(0.9, 0.9, 0.9),
|
||||
},
|
||||
Color::Black => match self.black_stone {
|
||||
Some(ref black_stone) => ctx.set_source_pixbuf(black_stone, x_loc, y_loc),
|
||||
Some(ref black_stone) => ctx.set_source_pixbuf(&black_stone, x_loc, y_loc),
|
||||
None => ctx.set_source_rgb(0.0, 0.0, 0.0),
|
||||
},
|
||||
}
|
||||
|
@ -298,3 +309,34 @@ impl Pen {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_pixbuf(
|
||||
path: &str,
|
||||
transparency: bool,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> Result<Option<Pixbuf>, ImageError> {
|
||||
let image_bytes = resources_lookup_data(path, gio::ResourceLookupFlags::NONE).unwrap();
|
||||
|
||||
let image = ImageReader::new(Cursor::new(image_bytes))
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode();
|
||||
image.map(|image| {
|
||||
let stride = if transparency {
|
||||
image.to_rgba8().sample_layout().height_stride
|
||||
} else {
|
||||
image.to_rgb8().sample_layout().height_stride
|
||||
};
|
||||
Pixbuf::from_bytes(
|
||||
&glib::Bytes::from(image.as_bytes()),
|
||||
Colorspace::Rgb,
|
||||
transparency,
|
||||
8,
|
||||
image.width() as i32,
|
||||
image.height() as i32,
|
||||
stride as i32,
|
||||
)
|
||||
.scale_simple(width, height, InterpType::Nearest)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,42 +15,50 @@ You should have received a copy of the GNU General Public License along with On
|
|||
*/
|
||||
|
||||
use cairo::Context;
|
||||
use gtk::prelude::*;
|
||||
use otg_core::GameReviewViewModel;
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use otg_core::DepthTree;
|
||||
use sgf::GameRecord;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use uuid::Uuid;
|
||||
|
||||
const WIDTH: i32 = 200;
|
||||
const HEIGHT: i32 = 800;
|
||||
|
||||
const RADIUS: f64 = 7.5;
|
||||
const HIGHLIGHT_WIDTH: f64 = 4.;
|
||||
const SPACING: f64 = 30.;
|
||||
#[derive(Default)]
|
||||
pub struct ReviewTreePrivate {
|
||||
record: Rc<RefCell<Option<GameRecord>>>,
|
||||
tree: Rc<RefCell<Option<DepthTree>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReviewTree {
|
||||
widget: gtk::ScrolledWindow,
|
||||
drawing_area: gtk::DrawingArea,
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ReviewTreePrivate {
|
||||
const NAME: &'static str = "ReviewTree";
|
||||
type Type = ReviewTree;
|
||||
type ParentType = gtk::DrawingArea;
|
||||
}
|
||||
|
||||
view: GameReviewViewModel,
|
||||
impl ObjectImpl for ReviewTreePrivate {}
|
||||
impl WidgetImpl for ReviewTreePrivate {}
|
||||
impl DrawingAreaImpl for ReviewTreePrivate {}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ReviewTree(ObjectSubclass<ReviewTreePrivate>) @extends gtk::Widget, gtk::DrawingArea;
|
||||
}
|
||||
|
||||
impl ReviewTree {
|
||||
pub fn new(view: GameReviewViewModel) -> ReviewTree {
|
||||
let drawing_area = gtk::DrawingArea::new();
|
||||
let widget = gtk::ScrolledWindow::builder().child(&drawing_area).build();
|
||||
pub fn new(record: GameRecord) -> Self {
|
||||
let s: Self = Object::new();
|
||||
|
||||
widget.set_width_request(WIDTH);
|
||||
widget.set_height_request(HEIGHT);
|
||||
// TODO: there can be more than one tree, especially in instructional files. Either unify
|
||||
// them into a single tree in the GameTree, or draw all of them here.
|
||||
*s.imp().tree.borrow_mut() = Some(DepthTree::from(&record.trees[0]));
|
||||
*s.imp().record.borrow_mut() = Some(record);
|
||||
|
||||
// TODO: figure out the maximum width of the tree so that we can also set a width request
|
||||
drawing_area.set_height_request(view.tree_max_depth() as i32 * SPACING as i32);
|
||||
s.set_width_request(WIDTH);
|
||||
s.set_height_request(HEIGHT);
|
||||
|
||||
let s = Self {
|
||||
widget,
|
||||
drawing_area,
|
||||
view,
|
||||
};
|
||||
|
||||
s.drawing_area.set_draw_func({
|
||||
s.set_draw_func({
|
||||
let s = s.clone();
|
||||
move |_, ctx, width, height| {
|
||||
s.redraw(ctx, width, height);
|
||||
|
@ -60,63 +68,168 @@ impl ReviewTree {
|
|||
s
|
||||
}
|
||||
|
||||
pub fn queue_draw(&self) {
|
||||
self.drawing_area.queue_draw();
|
||||
}
|
||||
|
||||
fn redraw(&self, ctx: &Context, _width: i32, _height: i32) {
|
||||
#[allow(deprecated)]
|
||||
let context = WidgetExt::style_context(&self.widget);
|
||||
#[allow(deprecated)]
|
||||
let foreground_color = context.lookup_color("sidebar_fg_color").unwrap();
|
||||
#[allow(deprecated)]
|
||||
let accent_color = context.lookup_color("accent_color").unwrap();
|
||||
|
||||
self.view.map_tree(move |node, current| {
|
||||
let parent = node.parent();
|
||||
ctx.set_source_rgb(
|
||||
foreground_color.red().into(),
|
||||
foreground_color.green().into(),
|
||||
foreground_color.blue().into(),
|
||||
);
|
||||
let (row, column) = node.data().position();
|
||||
let y = (row as f64) * SPACING + RADIUS * 2.;
|
||||
let x = (column as f64) * SPACING + RADIUS * 2.;
|
||||
ctx.arc(x, y, RADIUS, 0., 2. * std::f64::consts::PI);
|
||||
let _ = ctx.fill();
|
||||
|
||||
if let Some(parent) = parent {
|
||||
ctx.set_line_width(1.);
|
||||
let (row, column) = parent.data().position();
|
||||
let py = (row as f64) * SPACING + RADIUS * 2.;
|
||||
let px = (column as f64) * SPACING + RADIUS * 2.;
|
||||
ctx.move_to(px, py);
|
||||
ctx.line_to(x, y);
|
||||
let _ = ctx.stroke();
|
||||
pub fn redraw(&self, ctx: &Context, _width: i32, _height: i32) {
|
||||
let tree: &Option<DepthTree> = &self.imp().tree.borrow();
|
||||
match tree {
|
||||
Some(ref tree) => {
|
||||
for node in tree.bfs_iter() {
|
||||
// draw a circle given the coordinates of the nodes
|
||||
// I don't know the indent. How do I keep track of that? Do I track the position of
|
||||
// the parent? do I need to just make it more intrinsically a part of the position
|
||||
// code?
|
||||
ctx.set_source_rgb(0.7, 0.7, 0.7);
|
||||
let (row, column) = node.position();
|
||||
let y = (row as f64) * 20. + 10.;
|
||||
let x = (column as f64) * 20. + 10.;
|
||||
ctx.arc(x, y, 5., 0., 2. * std::f64::consts::PI);
|
||||
let _ = ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
if current == Some(node.data().game_node_id) {
|
||||
ctx.set_line_width(HIGHLIGHT_WIDTH);
|
||||
ctx.set_source_rgb(
|
||||
accent_color.red().into(),
|
||||
accent_color.green().into(),
|
||||
accent_color.blue().into(),
|
||||
);
|
||||
ctx.arc(
|
||||
x,
|
||||
y,
|
||||
RADIUS + HIGHLIGHT_WIDTH / 2.,
|
||||
0.,
|
||||
2. * std::f64::consts::PI,
|
||||
);
|
||||
let _ = ctx.stroke();
|
||||
ctx.set_line_width(2.);
|
||||
None => {
|
||||
// if there is no tree present, then there's nothing to draw!
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast::<gtk::Widget>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://llimllib.github.io/pymag-trees/
|
||||
// I want to take advantage of the Wetherell Shannon algorithm, but I want some variations. In
|
||||
// their diagram, they got a tree that looks like this.
|
||||
//
|
||||
// O
|
||||
// |\
|
||||
// O O
|
||||
// |\ \ \
|
||||
// O O O O
|
||||
// |\ |\
|
||||
// O O O O
|
||||
//
|
||||
// In the same circumstance, what I want is this:
|
||||
//
|
||||
// O--
|
||||
// | \
|
||||
// O O
|
||||
// |\ |\
|
||||
// O O O O
|
||||
// |\
|
||||
// O O
|
||||
//
|
||||
// In order to keep things from being overly smooshed, I want to ensure that if a branch overlaps
|
||||
// with another branch, there is some extra drawing space. This might actually be similar to adding
|
||||
// the principal that "A parent should be centered over its children".
|
||||
//
|
||||
// So, given a tree, I need to know how many children exist at each level. Then I build parents
|
||||
// atop the children. At level 3, I have four children, and that happens to be the maximum width of
|
||||
// the graph.
|
||||
//
|
||||
// A bottom-up traversal:
|
||||
// - Figure out the number of nodes at each depth
|
||||
|
||||
/*
|
||||
struct Tree {
|
||||
width: Vec<usize>, // the total width of the tree at each depth
|
||||
}
|
||||
*/
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use sgf::{Color, GameNode, Move, MoveNode};
|
||||
|
||||
#[test]
|
||||
fn it_calculates_width_for_single_node() {
|
||||
let node = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||
|
||||
assert_eq!(node_width(&node), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_calculates_width_for_node_with_children() {
|
||||
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_b = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||
let node_c = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||
let node_d = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||
|
||||
node_a.children.push(node_b);
|
||||
node_a.children.push(node_c);
|
||||
node_a.children.push(node_d);
|
||||
|
||||
assert_eq!(node_width(&GameNode::MoveNode(node_a)), 3);
|
||||
}
|
||||
|
||||
// A
|
||||
// B E
|
||||
// C D
|
||||
#[test]
|
||||
fn it_calculates_width_with_one_deep_child() {
|
||||
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let mut node_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
|
||||
node_b.children.push(GameNode::MoveNode(node_c));
|
||||
node_b.children.push(GameNode::MoveNode(node_d));
|
||||
assert_eq!(node_width(&GameNode::MoveNode(node_b.clone())), 2);
|
||||
|
||||
node_a.children.push(GameNode::MoveNode(node_b));
|
||||
node_a.children.push(GameNode::MoveNode(node_e));
|
||||
assert_eq!(node_width(&GameNode::MoveNode(node_a)), 3);
|
||||
}
|
||||
|
||||
// A
|
||||
// B G H
|
||||
// C I
|
||||
// D E F
|
||||
#[test]
|
||||
fn it_calculates_a_complex_tree() {
|
||||
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let mut node_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let mut node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_f = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_g = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let mut node_h = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_i = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
|
||||
node_c.children.push(GameNode::MoveNode(node_d));
|
||||
node_c.children.push(GameNode::MoveNode(node_e));
|
||||
node_c.children.push(GameNode::MoveNode(node_f));
|
||||
assert_eq!(node_width(&GameNode::MoveNode(node_c.clone())), 3);
|
||||
|
||||
node_b.children.push(GameNode::MoveNode(node_c));
|
||||
assert_eq!(node_width(&GameNode::MoveNode(node_b.clone())), 3);
|
||||
|
||||
node_h.children.push(GameNode::MoveNode(node_i));
|
||||
|
||||
node_a.children.push(GameNode::MoveNode(node_b));
|
||||
node_a.children.push(GameNode::MoveNode(node_g));
|
||||
node_a.children.push(GameNode::MoveNode(node_h));
|
||||
// This should be 4 if I were collapsing levels correctly, but it is 5 until I return to
|
||||
// figure that step out.
|
||||
assert_eq!(node_width(&GameNode::MoveNode(node_a.clone())), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_nodes_children_get_separate_columns() {
|
||||
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||
let node_b = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||
let node_c = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||
let node_d = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||
|
||||
node_a.children.push(node_b.clone());
|
||||
node_a.children.push(node_c.clone());
|
||||
node_a.children.push(node_d.clone());
|
||||
|
||||
assert_eq!(
|
||||
node_children_columns(&GameNode::MoveNode(node_a)),
|
||||
vec![0, 1, 2]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_renderer() {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,8 +49,8 @@ pub struct ResourceManager {
|
|||
resources: Rc<RefCell<HashMap<String, Resource>>>,
|
||||
}
|
||||
|
||||
impl Default for ResourceManager {
|
||||
fn default() -> Self {
|
||||
impl ResourceManager {
|
||||
pub fn new() -> Self {
|
||||
let mut resources = HashMap::new();
|
||||
|
||||
for (path, xres, yres, transparency) in [
|
||||
|
@ -88,9 +88,7 @@ impl Default for ResourceManager {
|
|||
resources: Rc::new(RefCell::new(resources)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceManager {
|
||||
pub fn resource(&self, path: &str) -> Option<Resource> {
|
||||
self.resources.borrow().get(path).cloned()
|
||||
}
|
||||
|
@ -125,6 +123,7 @@ impl ResourceManager {
|
|||
.scale_simple(width, height, InterpType::Nearest)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn perftrace<F, A>(trace_name: &str, f: F) -> A
|
||||
|
|
|
@ -122,7 +122,7 @@ fn main() {
|
|||
|
||||
app.connect_activate({
|
||||
move |app| {
|
||||
let resources = ResourceManager::default();
|
||||
let resources = ResourceManager::new();
|
||||
let core_api = CoreApi { core: core.clone() };
|
||||
let app_window = AppWindow::new(app, core_api, resources);
|
||||
|
||||
|
|
|
@ -22,21 +22,16 @@ You should have received a copy of the GNU General Public License along with On
|
|||
// I'll get all of the information about the game from the core, and then render everything in the
|
||||
// UI. So this will be a heavy lift on the UI side.
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
components::{Goban, PlayerCard, ReviewTree},
|
||||
ResourceManager,
|
||||
components::{Goban, PlayerCard, ReviewTree}, CoreApi, ResourceManager
|
||||
};
|
||||
use glib::Propagation;
|
||||
use gtk::{gdk::Key, prelude::*, EventControllerKey};
|
||||
use otg_core::{Color, GameReviewViewModel};
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use otg_core::Color;
|
||||
use sgf::GameRecord;
|
||||
|
||||
/*
|
||||
#[derive(Default)]
|
||||
pub struct GameReviewPrivate {
|
||||
model: Rc<RefCell<Option<GameReviewViewModel>>>,
|
||||
}
|
||||
pub struct GameReviewPrivate {}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for GameReviewPrivate {
|
||||
|
@ -57,76 +52,14 @@ impl GameReview {
|
|||
pub fn new(_api: CoreApi, record: GameRecord, resources: ResourceManager) -> Self {
|
||||
let s: Self = Object::builder().build();
|
||||
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GameReview {
|
||||
widget: gtk::Box,
|
||||
goban: Rc<RefCell<Option<Goban>>>,
|
||||
review_tree: Rc<RefCell<Option<ReviewTree>>>,
|
||||
|
||||
resources: ResourceManager,
|
||||
view: Rc<RefCell<GameReviewViewModel>>,
|
||||
}
|
||||
|
||||
impl GameReview {
|
||||
pub fn new(view: GameReviewViewModel, resources: ResourceManager) -> Self {
|
||||
let widget = gtk::Box::builder().build();
|
||||
|
||||
let view = Rc::new(RefCell::new(view));
|
||||
|
||||
let s = Self {
|
||||
widget,
|
||||
goban: Default::default(),
|
||||
review_tree: Default::default(),
|
||||
resources,
|
||||
view,
|
||||
};
|
||||
|
||||
let keypress_controller = EventControllerKey::new();
|
||||
keypress_controller.connect_key_pressed({
|
||||
let s = s.clone();
|
||||
move |_, key, _, _| {
|
||||
let mut view = s.view.borrow_mut();
|
||||
match key {
|
||||
Key::Down => view.next_move(),
|
||||
Key::Up => view.previous_move(),
|
||||
Key::Left => view.previous_variant(),
|
||||
Key::Right => view.next_variant(),
|
||||
_ => {
|
||||
return Propagation::Proceed;
|
||||
}
|
||||
}
|
||||
|
||||
match *s.goban.borrow_mut() {
|
||||
Some(ref mut goban) => goban.set_board_state(view.game_view()),
|
||||
None => {}
|
||||
};
|
||||
match *s.review_tree.borrow() {
|
||||
Some(ref tree) => tree.queue_draw(),
|
||||
None => {}
|
||||
}
|
||||
Propagation::Stop
|
||||
}
|
||||
});
|
||||
|
||||
s.widget.add_controller(keypress_controller);
|
||||
|
||||
s.render();
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn render(&self) {
|
||||
// It's actually really bad to be just throwing away errors. Panics make everyone unhappy.
|
||||
// This is not a fatal error, so I'll replace this `unwrap` call with something that
|
||||
// renders the board and notifies the user of a problem that cannot be resolved.
|
||||
let board_repr = self.view.borrow().game_view();
|
||||
let board = Goban::new(board_repr, self.resources.clone());
|
||||
let board_repr = match record.mainline() {
|
||||
Some(iter) => otg_core::Goban::default().apply_moves(iter).unwrap(),
|
||||
None => otg_core::Goban::default(),
|
||||
};
|
||||
let board = Goban::new(board_repr, resources);
|
||||
|
||||
/*
|
||||
s.attach(&board, 0, 0, 2, 2);
|
||||
|
@ -143,7 +76,7 @@ impl GameReview {
|
|||
// The review tree needs to know the record for being able to render all of the nodes. Once
|
||||
// keyboard input is being handled, the tree will have to be updated on each keystroke in
|
||||
// order to show the user where they are within the game record.
|
||||
let review_tree = ReviewTree::new(self.view.borrow().clone());
|
||||
let review_tree = ReviewTree::new(record.clone());
|
||||
|
||||
// I think most keyboard focus is going to end up being handled here in GameReview, as
|
||||
// keystrokes need to affect both the goban and the review tree simultanesouly. Possibly
|
||||
|
@ -155,24 +88,14 @@ impl GameReview {
|
|||
.spacing(4)
|
||||
.build();
|
||||
|
||||
player_information_section
|
||||
.append(&PlayerCard::new(Color::Black, &self.view.borrow().black_player()));
|
||||
player_information_section
|
||||
.append(&PlayerCard::new(Color::White, &self.view.borrow().white_player()));
|
||||
player_information_section.append(&PlayerCard::new(Color::Black, &record.black_player));
|
||||
player_information_section.append(&PlayerCard::new(Color::White, &record.white_player));
|
||||
|
||||
self.widget.append(&board);
|
||||
s.append(&board);
|
||||
sidebar.append(&player_information_section);
|
||||
sidebar.append(&review_tree.widget());
|
||||
self.widget.append(&sidebar);
|
||||
sidebar.append(&review_tree);
|
||||
s.append(&sidebar);
|
||||
|
||||
*self.goban.borrow_mut() = Some(board);
|
||||
*self.review_tree.borrow_mut() = Some(review_tree);
|
||||
}
|
||||
|
||||
fn redraw(&self) {
|
||||
}
|
||||
|
||||
pub fn widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast::<gtk::Widget>()
|
||||
s
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,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
|
||||
|
|
@ -2,8 +2,8 @@ use crate::{
|
|||
parser::{self, Annotation, Evaluation, Move, SetupInstr, Size, UnknownProperty},
|
||||
Color, Date, GameResult, GameType,
|
||||
};
|
||||
use nary_tree::{NodeId, NodeMut, NodeRef, Tree};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use nary_tree::{NodeId, NodeMut, NodeRef, Tree};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
fmt,
|
||||
|
@ -136,7 +136,7 @@ impl GameRecord {
|
|||
/// Generate a list of moves which constitute the main line of the game. This is the game as it
|
||||
/// was actually played out, and by convention consists of the first node in each list of
|
||||
/// children.
|
||||
pub fn mainline(&self) -> Option<impl Iterator<Item = NodeRef<'_, GameNode>>> {
|
||||
pub fn mainline(&self) -> Option<impl Iterator<Item = &'_ GameNode>> {
|
||||
if !self.trees.is_empty() {
|
||||
Some(MainlineIter {
|
||||
next: self.trees[0].root(),
|
||||
|
@ -405,7 +405,7 @@ pub struct MainlineIter<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for MainlineIter<'a> {
|
||||
type Item = NodeRef<'a, GameNode>;
|
||||
type Item = &'a GameNode;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(next) = self.next.take() {
|
||||
|
@ -413,7 +413,7 @@ impl<'a> Iterator for MainlineIter<'a> {
|
|||
self.next = next
|
||||
.first_child()
|
||||
.and_then(|child| self.tree.get(child.node_id()));
|
||||
Some(ret)
|
||||
Some(ret.data())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -634,7 +634,6 @@ mod test {
|
|||
assert_eq!(tree.nodes().len(), 0);
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn it_can_add_moves_to_a_game() {
|
||||
let mut game = GameRecord::new(
|
||||
|
@ -661,7 +660,6 @@ mod test {
|
|||
assert_eq!(nodes[1].id(), second_move.id);
|
||||
*/
|
||||
}
|
||||
*/
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
|
@ -822,7 +820,6 @@ mod path_test {
|
|||
let moves = game
|
||||
.mainline()
|
||||
.expect("there should be a mainline in this file")
|
||||
.map(|nr| nr.data())
|
||||
.collect::<Vec<&GameNode>>();
|
||||
assert_matches!(moves[0], GameNode::MoveNode(node) => {
|
||||
assert_eq!(node.color, Color::Black);
|
||||
|
@ -848,7 +845,6 @@ mod path_test {
|
|||
let moves = game
|
||||
.mainline()
|
||||
.expect("there should be a mainline in this file")
|
||||
.map(|nr| nr.data())
|
||||
.collect::<Vec<&GameNode>>();
|
||||
assert_matches!(moves[1], GameNode::MoveNode(node) => {
|
||||
assert_eq!(node.color, Color::White);
|
||||
|
|
|
@ -4,7 +4,7 @@ mod game;
|
|||
pub use game::{GameNode, GameRecord, GameTree, MoveNode, Player};
|
||||
|
||||
mod parser;
|
||||
pub use parser::{parse_collection, Move, Size};
|
||||
pub use parser::{parse_collection, Move};
|
||||
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
|
|
@ -302,10 +302,10 @@ impl Move {
|
|||
Move::Move(s) => {
|
||||
if s.len() == 2 {
|
||||
let mut parts = s.chars();
|
||||
let column_char = parts.next().unwrap();
|
||||
let column = column_char as u8 - b'a';
|
||||
let row_char = parts.next().unwrap();
|
||||
let row = row_char as u8 - b'a';
|
||||
let column_char = parts.next().unwrap();
|
||||
let column = column_char as u8 - b'a';
|
||||
Some((row, column))
|
||||
} else {
|
||||
unimplemented!("moves must contain exactly two characters");
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue