Compare commits
1 Commits
main
...
sgf/improv
Author | SHA1 | Date | |
---|---|---|---|
474d648ae4 |
.envrcflake.lockflake.nix
.gitea/workflows
Cargo.lockCargo.nixCargo.tomlTaskfile.ymlauthdb
bike-lights
bike
case
battery_enclosure.scadbike-light-enclosure.scadclasp.scadcommon.scadcontrol_panel.scadcontrol_panel_case.scadcontrol_panel_lid.scadcontrol_panel_slider.scad
core
simulator
config/src
coordinates/src
crate-hashes.jsoncyber-slides
cyberpunk-splash
cyberpunk
dashboard
Cargo.toml
src
emseries
file-service
fitnesstrax
app
core
fluent-ergonomics/src
gm-control-panel/src
gm-dash
server
ui
hex-grid/src
ifc
@ -1,31 +0,0 @@
|
||||
name: Monorepo build
|
||||
run-name: ${{ gitea.actor }} is testing out Gitea Actions
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
# Explore-Gitea-Actions:
|
||||
# runs-on: native
|
||||
# steps:
|
||||
# - run: echo "The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||
# - run: echo "This job is now running on ${{ runner.os }} server hosted by Gitea!"
|
||||
# - run: echo "The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||
# - name: Check out repository code
|
||||
# uses: actions/checkout@v4
|
||||
# - run: echo "The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||
# - run: echo "The workflow is now ready to test your code on the runner."
|
||||
# - name: List files in the repository
|
||||
# run: |
|
||||
# ls ${{ gitea.workspace }}
|
||||
# - run: echo "This job's status is ${{ job.status }}."
|
||||
|
||||
build-flake:
|
||||
runs-on: nixos
|
||||
defaults.run.working-directory: ${{ gitea.workspace }}
|
||||
steps:
|
||||
- name: Checkout repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Build the apps
|
||||
run: /run/current-system/sw/bin/nix --extra-experimental-features "nix-command flakes" build .#all
|
||||
- name: Check the end of the build
|
||||
run: ls ${{ gitea.workspace }}/result/bin
|
||||
|
4263
Cargo.lock
generated
4263
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
91
Cargo.toml
91
Cargo.toml
@ -1,74 +1,31 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
# "authdb",
|
||||
# "bike-lights/core",
|
||||
# "bike-lights/simulator",
|
||||
# "changeset",
|
||||
# "config",
|
||||
# "config-derive",
|
||||
# "coordinates",
|
||||
# "cyber-slides",
|
||||
"cyberpunk",
|
||||
"authdb",
|
||||
"changeset",
|
||||
"config",
|
||||
"config-derive",
|
||||
"coordinates",
|
||||
"cyberpunk-splash",
|
||||
# "dashboard",
|
||||
# "emseries",
|
||||
# "file-service",
|
||||
"dashboard",
|
||||
"emseries",
|
||||
"file-service",
|
||||
"fitnesstrax/core",
|
||||
"fitnesstrax/app",
|
||||
# "fitnesstrax/core",
|
||||
# "fluent-ergonomics",
|
||||
# "geo-types",
|
||||
# "gm-control-panel",
|
||||
# "gm-dash/server",
|
||||
# "hex-grid",
|
||||
# "icon-test",
|
||||
"l10n-db",
|
||||
# "memorycache",
|
||||
# "nom-training",
|
||||
# "otg/core",
|
||||
# "otg/gtk",
|
||||
# "pico-st7789",
|
||||
# "result-extended",
|
||||
# "screenplay",
|
||||
# "sgf",
|
||||
# "timezone-testing",
|
||||
# "tree",
|
||||
"fluent-ergonomics",
|
||||
"geo-types",
|
||||
"gm-control-panel",
|
||||
"hex-grid",
|
||||
"icon-test",
|
||||
"ifc",
|
||||
"memorycache",
|
||||
"nom-training",
|
||||
"otg/core",
|
||||
"otg/gtk",
|
||||
"result-extended",
|
||||
"screenplay",
|
||||
"sgf",
|
||||
"timezone-testing",
|
||||
"tree",
|
||||
"visions/server",
|
||||
# "visions/types",
|
||||
"visions/ui",
|
||||
# "bike-lights/bike",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
adw = { version = "0.5", package = "libadwaita", features = [ "v1_4" ] }
|
||||
async-channel = { version = "2.1" }
|
||||
async-std = { version = "1.13" }
|
||||
async-trait = { version = "0.1" }
|
||||
axum = { version = "0.8", features = ["macros"] }
|
||||
cairo-rs = { version = "0.18" }
|
||||
chrono = { version = "0.4" }
|
||||
chrono-tz = { version = "0.8" }
|
||||
dimensioned = { version = "0.8", features = [ "serde" ] }
|
||||
gdk = { version = "0.7", package = "gdk4" }
|
||||
gio = { version = "0.18" }
|
||||
glib = { version = "0.18" }
|
||||
gloo-console = { version = "0.3.0" }
|
||||
gloo-net = { version = "0.6.0" }
|
||||
gtk = { version = "0.7", package = "gtk4", features = [ "v4_10" ] }
|
||||
serde = { version = "1.0", features = ["derive", "serde_derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.5" }
|
||||
serde_json = { version = "1.0.138" }
|
||||
thiserror = { version = "2.0" }
|
||||
tokio = { version = "1.43", features = ["full", "rt"] }
|
||||
tower-http = { version = "0.6", features = ["cors"] }
|
||||
uuid = { version = "1.13", features = ["v4"] }
|
||||
wasm-bindgen = { version = "0.2.100" }
|
||||
wasm-bindgen-futures = { version = "0.4.50" }
|
||||
web-sys = { version = "0.3.77" }
|
||||
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
|
||||
|
||||
# cairo-rs = { version = "0.18" }
|
||||
# gio = { version = "0.18" }
|
||||
# glib = { version = "0.18" }
|
||||
# gtk = { version = "0.7", package = "gtk4" }
|
||||
|
||||
|
20
Taskfile.yml
20
Taskfile.yml
@ -1,20 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- cargo build --release
|
||||
|
||||
update:
|
||||
cmds:
|
||||
- task build
|
||||
- crate2nix generate
|
||||
- nix build
|
||||
|
||||
lint:
|
||||
cmds:
|
||||
- cargo clippy
|
||||
|
||||
test:
|
||||
cmds:
|
||||
- cargo test
|
@ -18,7 +18,7 @@ base64ct = { version = "1", features = [ "alloc" ] }
|
||||
clap = { version = "4", features = [ "derive" ] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sha2 = { version = "0.10" }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite" ] }
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite" ] }
|
||||
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,472 +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_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;
|
||||
|
||||
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;
|
||||
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,
|
||||
starting_body,
|
||||
ending_dashboard,
|
||||
ending_body,
|
||||
BLINKER_FRAMES,
|
||||
time,
|
||||
),
|
||||
fade_in: Fade::new(
|
||||
OFF_DASHBOARD,
|
||||
OFF_BODY,
|
||||
ending_dashboard,
|
||||
ending_body,
|
||||
BLINKER_FRAMES,
|
||||
time,
|
||||
),
|
||||
fade_out: Fade::new(
|
||||
ending_dashboard,
|
||||
ending_body,
|
||||
OFF_DASHBOARD,
|
||||
OFF_BODY,
|
||||
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_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,
|
||||
self.lights,
|
||||
pattern.dashboard(),
|
||||
pattern.body(),
|
||||
DEFAULT_FRAMES,
|
||||
time,
|
||||
))
|
||||
}
|
||||
State::Brake => {
|
||||
self.current_animation = Box::new(Fade::new(
|
||||
self.dashboard_lights,
|
||||
self.lights,
|
||||
BRAKES_DASHBOARD,
|
||||
BRAKES_BODY,
|
||||
BRAKES_FRAMES,
|
||||
time,
|
||||
));
|
||||
}
|
||||
State::LeftBlinker => {
|
||||
self.current_animation = Box::new(Blinker::new(
|
||||
self.dashboard_lights,
|
||||
self.lights,
|
||||
BlinkerDirection::Left,
|
||||
time,
|
||||
));
|
||||
}
|
||||
State::RightBlinker => {
|
||||
self.current_animation = Box::new(Blinker::new(
|
||||
self.dashboard_lights,
|
||||
self.lights,
|
||||
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 => if let State::Pattern(ref pattern) = self.state {
|
||||
self.home_pattern = pattern.next();
|
||||
self.state = State::Pattern(self.home_pattern);
|
||||
},
|
||||
Event::PreviousPattern => if let State::Pattern(ref pattern) = self.state {
|
||||
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) {
|
||||
if let Some(event) = self.ui.check_event(time) {
|
||||
self.update_state(event);
|
||||
self.update_animation(time);
|
||||
};
|
||||
|
||||
let (dashboard, lights) = self.current_animation.tick(time);
|
||||
self.dashboard_lights = dashboard;
|
||||
self.lights = lights;
|
||||
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,17 +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" ] }
|
||||
async-std = "1.13.0"
|
||||
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,315 +0,0 @@
|
||||
use adw::prelude::*;
|
||||
use async_std::channel::Sender;
|
||||
use fixed::types::{I8F8, U128F0};
|
||||
use glib::Object;
|
||||
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 Default for DashboardLights {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
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 Default for BikeLights {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
let tx = self.tx.clone();
|
||||
glib::spawn_future(async move {
|
||||
let _ = tx
|
||||
.send(Update {
|
||||
dashboard: dashboard_lights,
|
||||
lights,
|
||||
})
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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) = async_std::channel::unbounded();
|
||||
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);
|
||||
|
||||
glib::spawn_future_local({
|
||||
let dashboard_lights = dashboard_lights.clone();
|
||||
let bike_lights = bike_lights.clone();
|
||||
async move {
|
||||
while let Ok(Update { dashboard, lights }) = update_rx.recv().await {
|
||||
dashboard_lights.set_lights(dashboard);
|
||||
bike_lights.set_lights(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);
|
||||
}
|
@ -40,12 +40,6 @@ macro_rules! define_config {
|
||||
values: std::collections::HashMap<ConfigName, ConfigOption>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
@ -145,7 +145,7 @@ mod tests {
|
||||
|
||||
let coord2 = &lst1[idx];
|
||||
assert!(coord2.is_adjacent(&coord1));
|
||||
assert!(coord1.is_adjacent(coord2));
|
||||
assert!(coord1.is_adjacent(&coord2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -166,10 +166,10 @@ mod tests {
|
||||
let hexaddr = AxialAddr::new(q, r);
|
||||
let en_distancaj_hexaddr: Vec<AxialAddr> = hexaddr.addresses(distance).collect();
|
||||
|
||||
let expected_cnt = (0..distance+1).map(|v| v * 6).fold(1, |acc, val| acc + val);
|
||||
let expected_cnt = ((0..distance+1).map(|v| v * 6).fold(1, |acc, val| acc + val)) as usize;
|
||||
assert_eq!(en_distancaj_hexaddr.len(), expected_cnt);
|
||||
for c in en_distancaj_hexaddr {
|
||||
assert!(c.distance(&hexaddr) <= distance);
|
||||
assert!(c.distance(&hexaddr) <= distance as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"git+https://github.com/yewstack/yew/#yew-macro@0.21.0": "1g47mpyzd2mib73cjrbmcivrp7kr16f6hbrmpaap56kbc518khwf",
|
||||
"git+https://github.com/yewstack/yew/#yew@0.21.0": "1g47mpyzd2mib73cjrbmcivrp7kr16f6hbrmpaap56kbc518khwf"
|
||||
}
|
@ -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,410 +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::Object;
|
||||
use gtk::{
|
||||
glib::{self},
|
||||
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)]
|
||||
#[derive(Default)]
|
||||
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(&self) -> impl Iterator<Item = &'_ Step> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
let text_display = Text::new(self.text.clone(), context, 64., width);
|
||||
context.move_to(0., text_display.extents().height());
|
||||
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;
|
||||
|
||||
let text_display = Text::new(self.old_text.clone(), context, 64., width);
|
||||
context.move_to(0., text_display.extents().height());
|
||||
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);
|
||||
context.move_to(0., text_display.extents().height());
|
||||
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 {
|
||||
CyberScreenState { script, ..Default::default() }
|
||||
}
|
||||
|
||||
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();
|
||||
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 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();
|
||||
}
|
@ -7,9 +7,7 @@ license = "GPL-3.0-only"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-std = { workspace = true }
|
||||
cairo-rs = { workspace = true }
|
||||
cyberpunk = { path = "../cyberpunk" }
|
||||
gio = { workspace = true }
|
||||
glib = { workspace = true }
|
||||
gtk = { workspace = true }
|
||||
cairo-rs = { version = "0.18" }
|
||||
gio = { version = "0.18" }
|
||||
glib = { version = "0.18" }
|
||||
gtk = { version = "0.7", package = "gtk4" }
|
||||
|
@ -1,8 +1,7 @@
|
||||
use cairo::{
|
||||
Context, FontSlant, FontWeight, Format, ImageSurface, LinearGradient, Pattern,
|
||||
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()
|
||||
@ -497,7 +702,8 @@ 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::<State>(gtk::glib::Priority::DEFAULT);
|
||||
|
||||
let window = gtk::ApplicationWindow::new(app);
|
||||
window.present();
|
||||
@ -528,25 +734,19 @@ fn main() {
|
||||
});
|
||||
window.add_controller(keyboard_events);
|
||||
|
||||
glib::spawn_future_local({
|
||||
let splash = splash.clone();
|
||||
async move {
|
||||
while let Ok(state) = gtk_rx.recv().await {
|
||||
println!("received state");
|
||||
gtk_rx.attach(None, move |state| {
|
||||
splash.set_state(state);
|
||||
}
|
||||
}
|
||||
glib::ControlFlow::Continue
|
||||
});
|
||||
|
||||
glib::spawn_future_local({
|
||||
std::thread::spawn({
|
||||
let state = state.clone();
|
||||
async move {
|
||||
move || {
|
||||
state.write().unwrap().start();
|
||||
loop {
|
||||
async_std::task::sleep(Duration::from_millis(1000 / 60)).await;
|
||||
std::thread::sleep(Duration::from_millis(1000 / 60));
|
||||
state.write().unwrap().run(Instant::now());
|
||||
println!("state: {:?}", state.read().unwrap());
|
||||
let _ = gtk_tx.send(*state.read().unwrap()).await;
|
||||
let _ = gtk_tx.send(*state.read().unwrap());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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 = { workspace = true }
|
||||
gio = { workspace = true }
|
||||
glib = { workspace = true }
|
||||
gtk = { workspace = true }
|
@ -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.is_empty() {
|
||||
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" }
|
||||
|
||||
|
@ -31,9 +31,7 @@ impl ApplicationWindow {
|
||||
|
||||
let provider = gtk::CssProvider::new();
|
||||
provider.load_from_data(&stylesheet);
|
||||
#[allow(deprecated)]
|
||||
let context = window.style_context();
|
||||
#[allow(deprecated)]
|
||||
context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER);
|
||||
|
||||
let layout = gtk::Box::builder()
|
||||
@ -43,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");
|
||||
|
@ -36,7 +36,6 @@ impl Default for TransitClock {
|
||||
s.set_draw_func({
|
||||
let s = s.clone();
|
||||
move |_, context, width, height| {
|
||||
#[allow(deprecated)]
|
||||
let style_context = WidgetExt::style_context(&s);
|
||||
let center_x = width as f64 / 2.;
|
||||
let center_y = height as f64 / 2.;
|
||||
@ -46,9 +45,7 @@ impl Default for TransitClock {
|
||||
let sunrise = info.sunrise - NaiveTime::from_hms_opt(0, 0, 0).unwrap();
|
||||
let sunset = info.sunset - NaiveTime::from_hms_opt(0, 0, 0).unwrap();
|
||||
|
||||
#[allow(deprecated)]
|
||||
let night_color = style_context.lookup_color("dark_5").unwrap();
|
||||
#[allow(deprecated)]
|
||||
let day_color = style_context.lookup_color("blue_1").unwrap();
|
||||
|
||||
PieChart::new(&style_context)
|
||||
|
@ -1,7 +1,5 @@
|
||||
use cairo::Context;
|
||||
use gtk::{gdk::RGBA, prelude::*};
|
||||
#[allow(deprecated)]
|
||||
use gtk::StyleContext;
|
||||
use gtk::{gdk::RGBA, prelude::*, StyleContext};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -29,9 +27,7 @@ pub struct PieChart {
|
||||
}
|
||||
|
||||
impl PieChart {
|
||||
#[allow(deprecated)]
|
||||
pub fn new(style_context: &StyleContext) -> Self {
|
||||
#[allow(deprecated)]
|
||||
Self {
|
||||
rotation: 0.,
|
||||
wedges: vec![],
|
||||
|
@ -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 = "
|
||||
|
@ -132,7 +132,7 @@ impl SolunaClient {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use serde_json;
|
||||
|
||||
const EXAMPLE: &str = "{\"sunRise\":\"7:15\",\"sunTransit\":\"12:30\",\"sunSet\":\"17:45\",\"moonRise\":null,\"moonTransit\":\"7:30\",\"moonUnder\":\"19:54\",\"moonSet\":\"15:02\",\"moonPhase\":\"Waning Crescent\",\"moonIllumination\":0.35889454647387764,\"sunRiseDec\":7.25,\"sunTransitDec\":12.5,\"sunSetDec\":17.75,\"moonRiseDec\":null,\"moonSetDec\":15.033333333333333,\"moonTransitDec\":7.5,\"moonUnderDec\":19.9,\"minor1Start\":null,\"minor1Stop\":null,\"minor2StartDec\":14.533333333333333,\"minor2Start\":\"14:32\",\"minor2StopDec\":15.533333333333333,\"minor2Stop\":\"15:32\",\"major1StartDec\":6.5,\"major1Start\":\"06:30\",\"major1StopDec\":8.5,\"major1Stop\":\"08:30\",\"major2StartDec\":18.9,\"major2Start\":\"18:54\",\"major2StopDec\":20.9,\"major2Stop\":\"20:54\",\"dayRating\":1,\"hourlyRating\":{\"0\":20,\"1\":20,\"2\":0,\"3\":0,\"4\":0,\"5\":0,\"6\":20,\"7\":40,\"8\":40,\"9\":20,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":20,\"16\":20,\"17\":20,\"18\":40,\"19\":20,\"20\":20,\"21\":20,\"22\":0,\"23\":0}}";
|
||||
|
||||
|
@ -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>,
|
||||
|
@ -9,7 +9,6 @@ documentation = "https://docs.rs/emseries"
|
||||
homepage = "https://github.com/luminescent-dreams/emseries"
|
||||
repository = "https://github.com/luminescent-dreams/emseries"
|
||||
categories = ["database-implementations"]
|
||||
edition = "2021"
|
||||
|
||||
include = [
|
||||
"**/*.rs",
|
||||
|
@ -10,7 +10,7 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but
|
||||
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crate::types::{Recordable, Timestamp};
|
||||
use types::{Recordable, Timestamp};
|
||||
|
||||
/// This trait is used for constructing queries for searching the database.
|
||||
pub trait Criteria {
|
||||
|
@ -10,6 +10,10 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but
|
||||
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate uuid;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::ser::Serialize;
|
||||
use std::cmp::Ordering;
|
||||
@ -20,8 +24,8 @@ use std::fs::OpenOptions;
|
||||
use std::io::{BufRead, BufReader, LineWriter, Write};
|
||||
use std::iter::Iterator;
|
||||
|
||||
use crate::criteria::Criteria;
|
||||
use crate::types::{EmseriesReadError, EmseriesWriteError, Record, RecordId, Recordable};
|
||||
use criteria::Criteria;
|
||||
use types::{EmseriesReadError, EmseriesWriteError, Record, RecordId, Recordable};
|
||||
|
||||
// A RecordOnDisk, a private data structure, is useful for handling all of the on-disk
|
||||
// representations of a record. Unlike [Record], this one can accept an empty data value to
|
||||
|
@ -1,11 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- cargo build
|
||||
|
||||
lint:
|
||||
cmds:
|
||||
- cargo watch -x clippy
|
||||
|
@ -1,5 +1,3 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use build_html::{self, Html, HtmlContainer};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@ -25,14 +23,13 @@ impl FromIterator<(&str, &str)> for Attributes {
|
||||
}
|
||||
*/
|
||||
|
||||
impl Display for Attributes {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let result = self.0
|
||||
impl ToString for Attributes {
|
||||
fn to_string(&self) -> String {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{}=\"{}\"", key, value))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
write!(f, "{}", result)
|
||||
.join(" ")
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +202,7 @@ impl Html for Button {
|
||||
"<button {ty} {name} {attrs}>{label}</button>",
|
||||
name = name,
|
||||
label = self.label,
|
||||
attrs = self.attributes
|
||||
attrs = self.attributes.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -118,21 +118,17 @@ impl Deref for FileId {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub trait FileRoot {
|
||||
fn root(&self) -> PathBuf;
|
||||
}
|
||||
*/
|
||||
|
||||
// pub struct Context(PathBuf);
|
||||
pub struct Context(PathBuf);
|
||||
|
||||
/*
|
||||
impl FileRoot for Context {
|
||||
fn root(&self) -> PathBuf {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub struct Store {
|
||||
files_root: PathBuf,
|
||||
|
@ -6,20 +6,20 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
adw = { workspace = true }
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
chrono-tz = { workspace = true }
|
||||
dimensioned = { workspace = true }
|
||||
adw = { version = "0.5", package = "libadwaita", features = [ "v1_4" ] }
|
||||
async-channel = { version = "2.1" }
|
||||
async-trait = { version = "0.1" }
|
||||
chrono = { version = "0.4" }
|
||||
chrono-tz = { version = "0.8" }
|
||||
dimensioned = { version = "0.8", features = [ "serde" ] }
|
||||
emseries = { path = "../../emseries" }
|
||||
ft-core = { path = "../core" }
|
||||
gio = { workspace = true }
|
||||
glib = { workspace = true }
|
||||
gdk = { workspace = true }
|
||||
gtk = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
gio = { version = "0.18" }
|
||||
glib = { version = "0.18" }
|
||||
gdk = { version = "0.7", package = "gdk4" }
|
||||
gtk = { version = "0.7", package = "gtk4", features = [ "v4_10" ] }
|
||||
thiserror = { version = "1.0" }
|
||||
tokio = { version = "1.34", features = [ "full" ] }
|
||||
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.18"
|
||||
|
@ -153,7 +153,7 @@ mod test {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn it_allows_valid_dates() {
|
||||
let reference = chrono::NaiveDate::from_ymd_opt(2006, 1, 2).unwrap();
|
||||
let reference = chrono::NaiveDate::from_ymd_opt(2006, 01, 02).unwrap();
|
||||
let field = DateField::new(reference);
|
||||
field.imp().year.set_value(Some(2023));
|
||||
field.imp().month.set_value(Some(10));
|
||||
|
@ -80,10 +80,10 @@ impl TimeFormatter {
|
||||
0 => Err(ParseError),
|
||||
1 => Err(ParseError),
|
||||
2 => chrono::NaiveTime::from_hms_opt(parts[0], parts[1], 0)
|
||||
.map(TimeFormatter)
|
||||
.map(|v| TimeFormatter(v))
|
||||
.ok_or(ParseError),
|
||||
3 => chrono::NaiveTime::from_hms_opt(parts[0], parts[1], parts[2])
|
||||
.map(TimeFormatter)
|
||||
.map(|v| TimeFormatter(v))
|
||||
.ok_or(ParseError),
|
||||
_ => Err(ParseError),
|
||||
}
|
||||
|
@ -443,7 +443,7 @@ mod test {
|
||||
async fn put_record(&self, record: TraxRecord) -> Result<RecordId, WriteError> {
|
||||
let id = RecordId::default();
|
||||
let record = Record {
|
||||
id,
|
||||
id: id,
|
||||
data: record,
|
||||
};
|
||||
self.put_records.write().unwrap().push(record.clone());
|
||||
@ -509,7 +509,7 @@ mod test {
|
||||
Record {
|
||||
id: RecordId::default(),
|
||||
data: TraxRecord::TimeDistance(ft_core::TimeDistance {
|
||||
datetime: oct_13_am,
|
||||
datetime: oct_13_am.clone(),
|
||||
activity: TimeDistanceActivity::Biking,
|
||||
distance: Some(15000. * si::M),
|
||||
duration: Some(3600. * si::S),
|
||||
|
@ -144,7 +144,7 @@ impl HistoricalView {
|
||||
let mut model = gio::ListStore::new::<Date>();
|
||||
let mut days = interval.days().map(Date::new).collect::<Vec<Date>>();
|
||||
days.reverse();
|
||||
model.extend(days);
|
||||
model.extend(days.into_iter());
|
||||
self.imp()
|
||||
.list_view
|
||||
.set_model(Some(>k::NoSelection::new(Some(model))));
|
||||
|
@ -6,12 +6,12 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chrono = { workspace = true }
|
||||
chrono-tz = { workspace = true }
|
||||
dimensioned = { workspace = true }
|
||||
chrono = { version = "0.4" }
|
||||
chrono-tz = { version = "0.8" }
|
||||
dimensioned = { version = "0.8", features = [ "serde" ] }
|
||||
emseries = { path = "../../emseries" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
serde_json = { version = "1" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "*"
|
||||
|
@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit
|
||||
use chrono::SecondsFormat;
|
||||
use chrono_tz::Etc::UTC;
|
||||
use dimensioned::si;
|
||||
use emseries::{Record, RecordId};
|
||||
use emseries::{Record, RecordId, Series, Timestamp};
|
||||
use ft_core::{self, DurationWorkout, DurationWorkoutActivity, SetRepActivity, TraxRecord};
|
||||
use serde::{
|
||||
de::{self, Visitor},
|
||||
@ -26,7 +26,7 @@ use serde::{
|
||||
use std::{
|
||||
fmt,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
io::{BufRead, BufReader, Read},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
|
26
flake.lock
generated
26
flake.lock
generated
@ -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": {
|
||||
@ -20,26 +20,26 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1740339700,
|
||||
"narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=",
|
||||
"lastModified": 1704732714,
|
||||
"narHash": "sha256-ABqK/HggMYA/jMUXgYyqVAcQ8QjeMyr1jcXfTpSHmps=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195",
|
||||
"rev": "6723fa4e4f1a30d42a633bef5eb01caeb281adc3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-24.11",
|
||||
"ref": "nixos-23.11",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
19
flake.nix
19
flake.nix
@ -2,7 +2,7 @@
|
||||
description = "Lumenescent Dreams Tools";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-24.11";
|
||||
nixpkgs.url = "nixpkgs/nixos-23.11";
|
||||
unstable.url = "nixpkgs/nixos-unstable";
|
||||
typeshare.url = "github:1Password/typeshare";
|
||||
};
|
||||
@ -22,10 +22,8 @@
|
||||
name = "ld-tools-devshell";
|
||||
buildInputs = [
|
||||
pkgs.cargo-nextest
|
||||
pkgs.cargo-watch
|
||||
pkgs.clang
|
||||
pkgs.crate2nix
|
||||
pkgs.trunk
|
||||
pkgs.glib
|
||||
pkgs.gst_all_1.gst-plugins-bad
|
||||
pkgs.gst_all_1.gst-plugins-base
|
||||
@ -46,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";
|
||||
@ -57,6 +53,7 @@
|
||||
packages."x86_64-linux" =
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
|
||||
gtkNativeInputs = [
|
||||
pkgs.pkg-config
|
||||
pkgs.gtk4
|
||||
@ -83,24 +80,20 @@
|
||||
};
|
||||
|
||||
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;
|
||||
file-service = cargo_nix.workspaceMembers.file-service.build;
|
||||
fitnesstrax = cargo_nix.workspaceMembers.fitnesstrax.build;
|
||||
l10n-db = cargo_nix.workspaceMembers.l10n-db.build;
|
||||
otg-gtk = cargo_nix.workspaceMembers.otg-gtk.build;
|
||||
|
||||
all = pkgs.symlinkJoin {
|
||||
name = "all";
|
||||
paths = [
|
||||
# cyber-slides
|
||||
cyberpunk-splash
|
||||
# dashboard
|
||||
# file-service
|
||||
dashboard
|
||||
file-service
|
||||
fitnesstrax
|
||||
l10n-db
|
||||
# otg-gtk
|
||||
otg-gtk
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -294,21 +294,21 @@ mod tests {
|
||||
use fluent_bundle::{FluentArgs, FluentValue};
|
||||
use unic_langid::LanguageIdentifier;
|
||||
|
||||
const EN_TRANSLATIONS: &str = "
|
||||
const EN_TRANSLATIONS: &'static str = "
|
||||
preferences = Preferences
|
||||
history = History
|
||||
time_display = {$time} during the day
|
||||
nested_display = nesting a time display: {time_display}
|
||||
";
|
||||
|
||||
const EO_TRANSLATIONS: &str = "
|
||||
const EO_TRANSLATIONS: &'static str = "
|
||||
history = Historio
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn translations() {
|
||||
let en_id = "en-US".parse::<LanguageIdentifier>().unwrap();
|
||||
let mut fluent = FluentErgo::new(&[en_id.clone()]);
|
||||
let mut fluent = FluentErgo::new(&vec![en_id.clone()]);
|
||||
fluent
|
||||
.add_from_text(en_id, String::from(EN_TRANSLATIONS))
|
||||
.expect("text should load");
|
||||
@ -322,7 +322,7 @@ history = Historio
|
||||
fn translation_fallback() {
|
||||
let eo_id = "eo".parse::<LanguageIdentifier>().unwrap();
|
||||
let en_id = "en".parse::<LanguageIdentifier>().unwrap();
|
||||
let mut fluent = FluentErgo::new(&[eo_id.clone(), en_id.clone()]);
|
||||
let mut fluent = FluentErgo::new(&vec![eo_id.clone(), en_id.clone()]);
|
||||
fluent
|
||||
.add_from_text(en_id, String::from(EN_TRANSLATIONS))
|
||||
.expect("text should load");
|
||||
@ -342,7 +342,7 @@ history = Historio
|
||||
#[test]
|
||||
fn placeholder_insertion_should_strip_placeholder_markers() {
|
||||
let en_id = "en".parse::<LanguageIdentifier>().unwrap();
|
||||
let mut fluent = FluentErgo::new(&[en_id.clone()]);
|
||||
let mut fluent = FluentErgo::new(&vec![en_id.clone()]);
|
||||
fluent
|
||||
.add_from_text(en_id, String::from(EN_TRANSLATIONS))
|
||||
.expect("text should load");
|
||||
@ -357,7 +357,7 @@ history = Historio
|
||||
#[test]
|
||||
fn placeholder_insertion_should_strip_nested_placeholder_markers() {
|
||||
let en_id = "en".parse::<LanguageIdentifier>().unwrap();
|
||||
let mut fluent = FluentErgo::new(&[en_id.clone()]);
|
||||
let mut fluent = FluentErgo::new(&vec![en_id.clone()]);
|
||||
fluent
|
||||
.add_from_text(en_id, String::from(EN_TRANSLATIONS))
|
||||
.expect("text should load");
|
||||
|
@ -7,8 +7,8 @@ use std::iter::Iterator;
|
||||
#[derive(Clone)]
|
||||
pub struct ApplicationWindow {
|
||||
pub window: adw::ApplicationWindow,
|
||||
// pub layout: gtk::FlowBox,
|
||||
// pub playlists: Vec<PlaylistCard>,
|
||||
pub layout: gtk::FlowBox,
|
||||
pub playlists: Vec<PlaylistCard>,
|
||||
}
|
||||
|
||||
impl ApplicationWindow {
|
||||
@ -31,9 +31,7 @@ impl ApplicationWindow {
|
||||
|
||||
let provider = gtk::CssProvider::new();
|
||||
provider.load_from_data(&stylesheet);
|
||||
#[allow(deprecated)]
|
||||
let context = window.style_context();
|
||||
#[allow(deprecated)]
|
||||
context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER);
|
||||
|
||||
let layout = gtk::FlowBox::new();
|
||||
@ -59,10 +57,8 @@ impl ApplicationWindow {
|
||||
|
||||
Self {
|
||||
window,
|
||||
/*
|
||||
layout,
|
||||
playlists,
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
23
gm-dash/ui/.gitignore
vendored
23
gm-dash/ui/.gitignore
vendored
@ -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/).
|
18044
gm-dash/ui/package-lock.json
generated
18044
gm-dash/ui/package-lock.json
generated
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: 64px | Height: 64px | 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 ![]() (image error) Size: 5.2 KiB |
Binary file not shown.
Before ![]() (image error) 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 (image error) Size: 2.6 KiB |
1
gm-dash/ui/src/react-app-env.d.ts
vendored
1
gm-dash/ui/src/react-app-env.d.ts
vendored
@ -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"
|
||||
]
|
||||
}
|
@ -107,6 +107,8 @@ impl ObjectSubclass for HexGridWindowPrivate {
|
||||
layout.append(&drawing_area);
|
||||
layout.append(&sidebar);
|
||||
|
||||
layout.show();
|
||||
|
||||
Self {
|
||||
drawing_area,
|
||||
hex_address,
|
||||
|
400
ifc/Cargo.lock
generated
Normal file
400
ifc/Cargo.lock
generated
Normal file
@ -0,0 +1,400 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "international-fixed-calendar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
14
ifc/Cargo.toml
Normal file
14
ifc/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "ifc"
|
||||
description = "chrono-compatible-ish date objects for the International Fixed Calendar"
|
||||
version = "0.1.0"
|
||||
authors = ["Savanni D'Gerinel <savanni@luminescent-dreams.com>"]
|
||||
edition = "2018"
|
||||
keywords = ["date", "time", "calendar"]
|
||||
categories = ["date-and-time"]
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = { version = "1" }
|
8
ifc/readme.md
Normal file
8
ifc/readme.md
Normal file
@ -0,0 +1,8 @@
|
||||
# International Fixed Calendar
|
||||
|
||||
This is a fun project implementing a library for the [International Fixed Calendar](https://en.wikipedia.org/wiki/International_Fixed_Calendar).
|
||||
|
||||
This is at least somewhat compatible with [Chrono](https://github.com/chronotope/chrono), in that I have implemented these traits:
|
||||
|
||||
* `From<NaiveDate>`
|
||||
* `Datelike`
|
1042
ifc/src/lib.rs
Normal file
1042
ifc/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
34
ifc/src/static/index.html
Normal file
34
ifc/src/static/index.html
Normal file
@ -0,0 +1,34 @@
|
||||
<html>
|
||||
<head>
|
||||
<title> {{month}} {{year}} </title>
|
||||
<link href="/css" rel="stylesheet" type="text/css" media="screen" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1> IFC Fixed Calendar: {{month}}, {{year}} years after the invention of agriculture </h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Sunday </th>
|
||||
<th> Monday </th>
|
||||
<th> Tuesday </th>
|
||||
<th> Wednesday </th>
|
||||
<th> Thursday </th>
|
||||
<th> Friday </th>
|
||||
<th> Saturday </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#weeks}}
|
||||
<tr>
|
||||
{{#days}}
|
||||
<td class="{{highlight}}"> {{day}} </td>
|
||||
{{/days}}
|
||||
</tr>
|
||||
{{/weeks}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
35
ifc/src/static/month.html
Normal file
35
ifc/src/static/month.html
Normal file
@ -0,0 +1,35 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{{month}} {{year}}</title>
|
||||
<link href="/css" rel="stylesheet" type="text/css" media="screen" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
IFC Fixed Calendar: {{month}}, {{year}} years after the invention of
|
||||
agriculture
|
||||
</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sunday</th>
|
||||
<th>Monday</th>
|
||||
<th>Tuesday</th>
|
||||
<th>Wednesday</th>
|
||||
<th>Thursday</th>
|
||||
<th>Friday</th>
|
||||
<th>Saturday</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#weeks}}
|
||||
<tr>
|
||||
{{#days}}
|
||||
<td class="{{highlight}}">{{day}}</td>
|
||||
{{/days}}
|
||||
</tr>
|
||||
{{/weeks}}
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
12
ifc/src/static/out_of_time.html
Normal file
12
ifc/src/static/out_of_time.html
Normal file
@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{{day_out_of_time}} {{year}}</title>
|
||||
<link href="/css" rel="stylesheet" type="text/css" media="screen" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
IFC Fixed Calendar: {{day_out_of_time}}, {{year}} years after the
|
||||
invention of agriculture
|
||||
</h1>
|
||||
</body>
|
||||
</html>
|
18
ifc/src/static/styles.css
Normal file
18
ifc/src/static/styles.css
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
table {
|
||||
width: 98%;
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
width: 14%;
|
||||
font-family: sans-serif;
|
||||
font-size: larger;
|
||||
border: 1px solid black;
|
||||
padding: 1em 0em 5em 1em;
|
||||
}
|
||||
|
||||
.today {
|
||||
background-color: rgb(200, 200, 255);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user