Compare commits

..

2 Commits

Author SHA1 Message Date
f4d990546b Remove the legacy glib channel 2024-11-08 09:51:53 -05:00
288cecc92f Extract animations into a subcrate 2024-11-08 09:47:01 -05:00
200 changed files with 6209 additions and 27372 deletions
.envrc
.gitea/workflows
Cargo.lockCargo.nixCargo.tomlTaskfile.yml
authdb
bike-lights
config/src
coordinates/src
crate-hashes.json
cyber-slides/src
cyberpunk-splash
cyberpunk/src
dashboard/src
emseries
file-service
fitnesstrax
flake.lockflake.nix
fluent-ergonomics/src
gm-control-panel/src
gm-dash/server/src
hex-grid/src
ifc
l10n-db
memorycache/src
nom-training/src
otg
pico-st7789
result-extended/src
rust-toolchain
screenplay
sgf/src
timezone-testing/src
tree/src
visions-prototype/server

1
.envrc
View File

@ -1,2 +1 @@
mkdir .direnv
use flake

View File

@ -1,33 +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: Where am I?
run: pwd
- 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

2472
Cargo.lock generated

File diff suppressed because it is too large Load Diff

2623
Cargo.nix

File diff suppressed because it is too large Load Diff

View File

@ -2,39 +2,35 @@
resolver = "2"
members = [
"authdb",
# "bike-lights/bike",
"bike-lights/core",
"bike-lights/simulator",
"changeset",
"config",
"config-derive",
"coordinates",
"cyber-slides",
"cyberpunk",
"cyber-slides",
"cyberpunk-splash",
"dashboard",
"emseries",
"file-service",
"fitnesstrax/app",
"fitnesstrax/core",
"fitnesstrax/app",
"fluent-ergonomics",
"geo-types",
"gm-control-panel",
"gm-dash/server",
"hex-grid",
"icon-test",
"l10n-db",
"ifc",
"memorycache",
"nom-training",
"otg/core",
"otg/gtk",
"pico-st7789",
"result-extended",
"screenplay",
"sgf",
"timezone-testing",
"tree",
"visions/server",
"visions/types",
"visions/ui",
# "bike-lights/bike",
"visions/server", "gm-dash/server"
]

View File

@ -1,16 +0,0 @@
version: '3'
tasks:
build:
cmds:
- cargo build --release
update:
cmds:
- task build
- crate2nix generate
- nix build
lint:
cmds:
- cargo watch -x clippy

View File

@ -18,7 +18,9 @@ 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" ] }
# sqlformat introduced a mistaken breaking change in 0.2.7
sqlformat = { version = "=0.2.6" }
thiserror = { version = "1" }
tokio = { version = "1", features = [ "full" ] }
uuid = { version = "0.4", features = [ "serde", "v4" ] }

View File

@ -0,0 +1,121 @@
use fixed::types::U16F0;
use crate::{
calculate_frames, Animation, BodyPattern, DashboardPattern, Fade, FadeDirection, Instant, BLINKER_FRAMES, LEFT_BLINKER_BODY, LEFT_BLINKER_DASHBOARD, OFF_BODY, OFF_DASHBOARD, RIGHT_BLINKER_BODY, RIGHT_BLINKER_DASHBOARD
};
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 {
pub fn new(
starting_dashboard: DashboardPattern,
starting_body: BodyPattern,
direction: BlinkerDirection,
time: Instant,
) -> Self {
let mut ending_dashboard = OFF_DASHBOARD.clone();
match direction {
BlinkerDirection::Left => {
ending_dashboard[0].r = LEFT_BLINKER_DASHBOARD[0].r;
ending_dashboard[0].g = LEFT_BLINKER_DASHBOARD[0].g;
ending_dashboard[0].b = LEFT_BLINKER_DASHBOARD[0].b;
}
BlinkerDirection::Right => {
ending_dashboard[2].r = RIGHT_BLINKER_DASHBOARD[2].r;
ending_dashboard[2].g = RIGHT_BLINKER_DASHBOARD[2].g;
ending_dashboard[2].b = RIGHT_BLINKER_DASHBOARD[2].b;
}
}
let mut ending_body = OFF_BODY.clone();
match direction {
BlinkerDirection::Left => {
for i in 0..30 {
ending_body[i].r = LEFT_BLINKER_BODY[i].r;
ending_body[i].g = LEFT_BLINKER_BODY[i].g;
ending_body[i].b = LEFT_BLINKER_BODY[i].b;
}
}
BlinkerDirection::Right => {
for i in 30..60 {
ending_body[i].r = RIGHT_BLINKER_BODY[i].r;
ending_body[i].g = RIGHT_BLINKER_BODY[i].g;
ending_body[i].b = RIGHT_BLINKER_BODY[i].b;
}
}
}
Blinker {
transition: Fade::new(
starting_dashboard.clone(),
starting_body.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_in: Fade::new(
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_out: Fade::new(
ending_dashboard.clone(),
ending_body.clone(),
OFF_DASHBOARD.clone(),
OFF_BODY.clone(),
BLINKER_FRAMES,
time,
),
direction: FadeDirection::Transition,
start_time: time,
frames: BLINKER_FRAMES,
}
}
}
impl Animation for Blinker {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let frames = calculate_frames(self.start_time.0, time.0);
if frames > self.frames {
match self.direction {
FadeDirection::Transition => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeIn => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeOut => {
self.direction = FadeDirection::FadeIn;
self.fade_in.start_time = time;
}
}
self.start_time = time;
}
match self.direction {
FadeDirection::Transition => self.transition.tick(time),
FadeDirection::FadeIn => self.fade_in.tick(time),
FadeDirection::FadeOut => self.fade_out.tick(time),
}
}
}

View File

@ -0,0 +1,100 @@
use fixed::types::{I8F8, U16F0};
use crate::{calculate_frames, calculate_slope, linear_ease, Animation, BodyPattern, DashboardPattern, Instant, OFF_BODY, OFF_DASHBOARD, RGB};
pub struct Fade {
starting_dashboard: DashboardPattern,
starting_lights: BodyPattern,
pub start_time: Instant,
dashboard_slope: [RGB<I8F8>; 3],
body_slope: [RGB<I8F8>; 60],
frames: U16F0,
}
impl Fade {
pub 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,
}

View File

@ -0,0 +1,43 @@
use crate::{Animation, BodyPattern, DashboardPattern, Instant};
pub struct FlagRipple {
dashboard: DashboardPattern,
body: BodyPattern,
centers: [usize; 12],
}
impl FlagRipple {
fn new(dashboard: DashboardPattern, body: BodyPattern) -> Self {
Self {
dashboard,
body,
centers: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60],
}
}
}
impl Animation for FlagRipple {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern, Option<Animation>) {
// How does a flag ripple work? I think have some center points that are darker and have a
// bit of a darker pattern around them.
let mut body_pattern = self.body.clone();
for i in 0..60 {
if self.centers.contains(&i) {
body_pattern[i].r = self.body[i].r / 2;
body_pattern[i].g = self.body[i].g / 2;
body_pattern[i].b = self.body[i].b / 2;
} else {
body_pattern[i].r = self.body[i].r;
body_pattern[i].g = self.body[i].g;
body_pattern[i].b = self.body[i].b;
}
}
(self.dashboard, body_pattern, None)
}
}

View File

@ -0,0 +1,31 @@
use fixed::types::{I48F16, I8F8, U128F0, U16F0};
use az::*;
use crate::{BodyPattern, DashboardPattern, Instant, FPS};
mod blinker;
pub use blinker::*;
mod fade;
pub use fade::*;
pub trait Animation {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern);
}
pub 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()
}
pub 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()
}
pub 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()
}

View File

@ -0,0 +1,95 @@
use fixed::types::{I16F0, I8F0, I8F8, U16F0, U8F0};
use az::*;
use crate::{
calculate_frames, calculate_slope, linear_ease, sin, Animation, BodyPattern, DashboardPattern, Instant, OFF_BODY, OFF_DASHBOARD, RGB, WATER_1, WATER_BODY
};
const DROPLET_DIMMING: u8 = 30;
pub struct Ripple {
dashboard: DashboardPattern,
base: BodyPattern,
focii: [usize; 2],
dimming: I8F8,
slope: [RGB<I8F8>; 2],
pub start_time: Instant,
}
impl Ripple {
pub fn new(dashboard: DashboardPattern, base: BodyPattern, time: Instant) -> Self {
let mut slope = [Default::default(); 2];
let dimming = I8F8::lit("0.5");
let focii = [15, 45];
for (idx, focus) in focii.iter().enumerate() {
slope[idx] = RGB {
r: calculate_slope(
base[*focus].r,
base[*focus].r * dimming,
U16F0::from(DROPLET_DIMMING),
),
g: calculate_slope(
base[*focus].g,
base[*focus].g * dimming,
U16F0::from(DROPLET_DIMMING),
),
b: calculate_slope(
base[*focus].b,
base[*focus].b * dimming,
U16F0::from(DROPLET_DIMMING),
),
};
}
Self {
dashboard,
base,
focii,
dimming,
slope,
start_time: time,
}
}
fn pixel(&self, pos: U8F0, frames: U16F0) -> RGB<I8F8> {
let focus: I16F0 = if pos <= 30 { I16F0::from(15 as i16) } else { I16F0::from(45 as i16) };
let distance_from_focus: I8F0 = (focus - I16F0::from(pos)).saturating_as();
let pos: usize = pos.into();
/*
let target = RGB {
r: WATER_BODY[pos].r * self.dimming,
g: WATER_BODY[pos].g * self.dimming,
b: WATER_BODY[pos].b * self.dimming,
};
let slope = RGB{
r: calculate_slope(WATER_BODY[pos].r, target.r, U16F0::from(DROPLET_DIMMING)),
g: calculate_slope(WATER_BODY[pos].g, target.g, U16F0::from(DROPLET_DIMMING)),
b: calculate_slope(WATER_BODY[pos].b, target.b, U16F0::from(DROPLET_DIMMING)),
};
*/
RGB {
r: WATER_BODY[pos].r + sin(frames * 5) * I8F8::from_num(0.5),
g: WATER_BODY[pos].g + sin(frames * 6) * I8F8::from_num(0.5),
b: WATER_BODY[pos].b + sin(frames * 7) * I8F8::from_num(0.5),
}
}
}
impl Animation for Ripple {
fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) {
let frames = calculate_frames(self.start_time.0, time.0);
let mut body_pattern: BodyPattern = OFF_BODY;
for i in 0..60 {
body_pattern[i] = self.pixel(U8F0::from(i as u8), frames);
}
(self.dashboard.clone(), body_pattern)
}
}

View File

@ -12,33 +12,21 @@ use core::{
};
use fixed::types::{I48F16, I8F8, U128F0, U16F0};
mod animations;
pub use animations::*;
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))
Self(U128F0::from(0 as u8))
}
}
@ -65,233 +53,6 @@ pub trait UI {
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,
@ -375,7 +136,7 @@ impl App {
pattern.dashboard(),
pattern.body(),
DEFAULT_FRAMES,
Instant(0_u32.into()),
Instant((0 as u32).into()),
)),
dashboard_lights: OFF_DASHBOARD,
lights: OFF_BODY,
@ -386,8 +147,8 @@ impl App {
match self.state {
State::Pattern(ref pattern) => {
self.current_animation = Box::new(Fade::new(
self.dashboard_lights,
self.lights,
self.dashboard_lights.clone(),
self.lights.clone(),
pattern.dashboard(),
pattern.body(),
DEFAULT_FRAMES,
@ -396,8 +157,8 @@ impl App {
}
State::Brake => {
self.current_animation = Box::new(Fade::new(
self.dashboard_lights,
self.lights,
self.dashboard_lights.clone(),
self.lights.clone(),
BRAKES_DASHBOARD,
BRAKES_BODY,
BRAKES_FRAMES,
@ -406,16 +167,16 @@ impl App {
}
State::LeftBlinker => {
self.current_animation = Box::new(Blinker::new(
self.dashboard_lights,
self.lights,
self.dashboard_lights.clone(),
self.lights.clone(),
BlinkerDirection::Left,
time,
));
}
State::RightBlinker => {
self.current_animation = Box::new(Blinker::new(
self.dashboard_lights,
self.lights,
self.dashboard_lights.clone(),
self.lights.clone(),
BlinkerDirection::Right,
time,
));
@ -441,13 +202,19 @@ impl App {
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::NextPattern => match self.state {
State::Pattern(ref pattern) => {
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::PreviousPattern => match self.state {
State::Pattern(ref pattern) => {
self.home_pattern = pattern.previous();
self.state = State::Pattern(self.home_pattern);
}
_ => (),
},
Event::RightBlinker => match self.state {
State::Brake => self.state = State::BrakeRightBlinker,
@ -459,14 +226,17 @@ impl App {
}
pub fn tick(&mut self, time: Instant) {
if let Some(event) = self.ui.check_event(time) {
self.update_state(event);
self.update_animation(time);
match self.ui.check_event(time) {
Some(event) => {
self.update_state(event);
self.update_animation(time);
}
None => {}
};
let (dashboard, lights) = self.current_animation.tick(time);
self.dashboard_lights = dashboard;
self.lights = lights;
self.dashboard_lights = dashboard.clone();
self.lights = lights.clone();
self.ui.update_lights(dashboard, lights);
}
}

View File

@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] }
async-std = "1.13.0"
async-channel = "2.3.1"
cairo-rs = { version = "0.18" }
fixed = { version = "1" }
gio = { version = "0.18" }

View File

@ -1,17 +1,13 @@
use std::{cell::RefCell, env, rc::Rc};
use adw::prelude::*;
use async_std::channel::Sender;
use async_channel::{unbounded, Receiver, Sender, TryRecvError};
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;
@ -46,12 +42,6 @@ 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();
@ -110,12 +100,6 @@ 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();
@ -169,20 +153,17 @@ impl UI for GTKUI {
match self.rx.try_recv() {
Ok(event) => Some(event),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => None,
Err(TryRecvError::Closed) => 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;
});
self.tx
.send_blocking(Update {
dashboard: dashboard_lights,
lights,
})
.unwrap();
}
}
@ -192,8 +173,8 @@ fn main() {
.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();
let (update_tx, update_rx) = unbounded();
let (event_tx, event_rx) = unbounded();
std::thread::spawn(move || {
let mut bike_app = App::new(Box::new(GTKUI {
@ -233,21 +214,21 @@ fn main() {
left_button.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send(Event::LeftBlinker);
let _ = event_tx.send_blocking(Event::LeftBlinker);
}
});
brake_button.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send(Event::Brake);
let _ = event_tx.send_blocking(Event::Brake);
}
});
right_button.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send(Event::RightBlinker);
let _ = event_tx.send_blocking(Event::RightBlinker);
}
});
@ -266,14 +247,14 @@ fn main() {
previous_pattern.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send(Event::PreviousPattern);
let _ = event_tx.send_blocking(Event::PreviousPattern);
}
});
next_pattern.connect_clicked({
let event_tx = event_tx.clone();
move |_| {
let _ = event_tx.send(Event::NextPattern);
let _ = event_tx.send_blocking(Event::NextPattern);
}
});
@ -284,16 +265,6 @@ fn main() {
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();
@ -306,6 +277,18 @@ fn main() {
});
*/
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);
}
}
});
window.set_content(Some(&layout));
window.present();
});

View File

@ -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 {

View File

@ -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);
}
}
}

View File

@ -3,26 +3,27 @@
"registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.0": "09r6drylvgy8vv8k20lnbvwq8gp09h7smfn6h1rxsy15pgh629si",
"registry+https://github.com/rust-lang/crates.io-index#adler32@1.2.0": "0d7jq7jsjyhsgbhnfq5fvrlh9j0i9g1fqrl2735ibv5f75yjgqda",
"registry+https://github.com/rust-lang/crates.io-index#adler@1.0.2": "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj",
"registry+https://github.com/rust-lang/crates.io-index#ahash@0.8.11": "04chdfkls5xmhp1d48gnjsmglbqibizs3bpbj6rsj604m10si7g8",
"registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3": "05mrpkvdgp5d20y2p989f187ry9diliijgwrs254fs9s1m1x6q4f",
"registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21": "08zrzs022xwndihvzdn78yqarv2b9696y67i6h78nla3ww87jgb8",
"registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.18": "0kr6lfnxvnj164j1x38g97qjlhb7akppqzvgfs0697140ixbav2w",
"registry+https://github.com/rust-lang/crates.io-index#android-tzdata@0.1.1": "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9",
"registry+https://github.com/rust-lang/crates.io-index#android_system_properties@0.1.5": "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1",
"registry+https://github.com/rust-lang/crates.io-index#annotate-snippets@0.9.2": "07p8r6jzb7nqydq0kr5pllckqcdxlyld2g275v425axnzffpxbyc",
"registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.18": "16sjk4x3ns2c3ya1x28a44kh6p47c7vhk27251i015hik1lm7k4a",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.6": "1acqayy22fwzsrvr6n0lz6a4zvjjcvgr5sm941m7m0b2fr81cb9v",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.1.2": "036nm3lkyk43xbps1yql3583fp4hg3b1600is7mcyxs1gzrpm53r",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.7": "0kmf0fq4c8yribdpdpylzz1zccpy84hizmcsac3wrac1f7kk8dfa",
"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.10": "1yai2vppmd7zlvlrp9grwll60knrmscalf8l2qpfz8b7y5lkpk2m",
"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.95": "010vd1ki8w84dzgx6c81sc8qm9n02fxic1gkpv52zp4nwrn0kb1l",
"registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.15": "09nm4qj34kiwgzczdvj14x7hgsb235g4sqsay3xsz7zqn4d5rqb4",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.5": "1jy12rvgbldflnb2x7mcww9dcffw1mx22nyv6p3n7d62h0gdwizb",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.1.1": "0aj22iy4pzk6mz745sfrm1ym14r0y892jhcrbs8nkj7nqx9gqdkd",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.4": "1y2pkvsrdxbcwircahb4wimans2pzmwwxad7ikdhj5lpdqdlxxsv",
"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.8": "1cfmkza63xpn1kkz844mgjwm9miaiz4jkyczmwxzivcsypk1vv0v",
"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.89": "1xh1vg89n56h6nqikcmgbpmkixjds33492klrp9m96xrbmhgizc6",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@1.9.0": "0dbdlkzlncbibd3ij6y6jmvjd0cmdn48ydcfdpfhw09njd93r5c1",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@2.3.1": "0skvwxj6ysfc6d7bhczz9a2550260g62bm5gl0nmjxxyn007id49",
"registry+https://github.com/rust-lang/crates.io-index#async-executor@1.13.1": "1v6w1dbvsmw6cs4dk4lxj5dvrikc6xi479wikwaab2qy3h09mjih",
"registry+https://github.com/rust-lang/crates.io-index#async-global-executor@2.4.1": "1762s45cc134d38rrv0hyp41hv4iv6nmx59vswid2p0il8rvdc85",
"registry+https://github.com/rust-lang/crates.io-index#async-io@2.4.0": "0n8h0vy53n4vdkq529scqnkzm9vcl3r73za9nj81s2nfrhiv78j3",
"registry+https://github.com/rust-lang/crates.io-index#async-io@2.3.4": "1s679l7x6ijh8zcxqn5pqgdiyshpy4xwklv86ldm1rhfjll04js4",
"registry+https://github.com/rust-lang/crates.io-index#async-lock@3.4.0": "060vh45i809wcqyxzs5g69nqiqah7ydz0hpkcjys9258vqn4fvpz",
"registry+https://github.com/rust-lang/crates.io-index#async-std@1.13.0": "059nbiyijwbndyrz0050skvlvzhds0dmnl0biwmxwbw055glfd66",
"registry+https://github.com/rust-lang/crates.io-index#async-task@4.7.1": "1pp3avr4ri2nbh7s6y9ws0397nkx1zymmcr14sq761ljarh3axcb",
"registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.85": "0mm0gwad44zs7mna4a0m1z4dhzpmydfj73w4wm23c8xpnhrli4rz",
"registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.83": "1p8q8gm4fv2fdka8hwy2w3f8df7p5inixqi7rlmbnky3wmysw73j",
"registry+https://github.com/rust-lang/crates.io-index#atoi@2.0.0": "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj",
"registry+https://github.com/rust-lang/crates.io-index#atomic-waker@1.1.2": "1h5av1lw56m0jf0fd3bchxq8a30xv0b4wv8s4zkp4s0i7mfvs18m",
"registry+https://github.com/rust-lang/crates.io-index#autocfg@0.1.8": "0y4vw4l4izdxq1v0rrhvmlbqvalrqrmk60v1z0dqlgnlbzkl7phd",
@ -30,39 +31,38 @@
"registry+https://github.com/rust-lang/crates.io-index#az@1.2.1": "0ww9k1w3al7x5qmb7f13v3s9c2pg1pdxbs8xshqy6zyrchj4qzkv",
"registry+https://github.com/rust-lang/crates.io-index#backtrace@0.3.74": "06pfif7nwx66qf2zaanc2fcq7m64i91ki9imw9xd3bnz5hrwp0ld",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.21.7": "0rw52yvsk75kar9wgqfwgb414kvil1gn7mqkrhn9zf1537mpsacx",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1": "1imqzgh7bxcikp5vx3shqvw9j09g9ly0xr0jma0q66i52r7jbcvj",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.9.3": "0hs62r35bgxslawyrn1vp9rmvrkkm76fqv0vqcwd048vs876r7a8",
"registry+https://github.com/rust-lang/crates.io-index#base64ct@1.6.0": "0nvdba4jb8aikv60az40x2w1y96sjdq8z3yp09rwzmkhiwv1lg4c",
"registry+https://github.com/rust-lang/crates.io-index#bindgen@0.69.5": "1240snlcfj663k04bjsg629g4wx6f83flgbjh5rzpgyagk3864r7",
"registry+https://github.com/rust-lang/crates.io-index#bit-set@0.8.0": "18riaa10s6n59n39vix0cr7l2dgwdhcpbcm97x1xbyfp1q47x008",
"registry+https://github.com/rust-lang/crates.io-index#bit-vec@0.8.0": "1xxa1s2cj291r7k1whbxq840jxvmdsq9xgh7bvrxl46m80fllxjy",
"registry+https://github.com/rust-lang/crates.io-index#bit-set@0.5.3": "1wcm9vxi00ma4rcxkl3pzzjli6ihrpn9cfdi0c5b4cvga2mxs007",
"registry+https://github.com/rust-lang/crates.io-index#bit-vec@0.6.3": "1ywqjnv60cdh1slhz67psnp422md6jdliji6alq0gmly2xm9p7rl",
"registry+https://github.com/rust-lang/crates.io-index#bit_field@0.10.2": "0qav5rpm4hqc33vmf4vc4r0mh51yjx5vmd9zhih26n9yjs3730nw",
"registry+https://github.com/rust-lang/crates.io-index#bitflags@1.3.2": "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy",
"registry+https://github.com/rust-lang/crates.io-index#bitflags@2.8.0": "0dixc6168i98652jxf0z9nbyn0zcis3g6hi6qdr7z5dbhcygas4g",
"registry+https://github.com/rust-lang/crates.io-index#bitflags@2.6.0": "1pkidwzn3hnxlsl8zizh0bncgbjnw7c41cx7bby26ncbzmiznj5h",
"registry+https://github.com/rust-lang/crates.io-index#block-buffer@0.10.4": "0w9sa2ypmrsqqvc20nhwr75wbb5cjr4kkyhpjm1z1lv2kdicfy1h",
"registry+https://github.com/rust-lang/crates.io-index#blocking@1.6.1": "1si99l8zp7c4zq87y35ayjgc5c9b60jb8h0k14zfcs679z2l2gvh",
"registry+https://github.com/rust-lang/crates.io-index#build_html@2.5.0": "0p4k25yk3v0wf720wl5zcghvc9ik6l7lsh3fz86cq3g7x4nbhpi2",
"registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.16.0": "0b015qb4knwanbdlp1x48pkb4pm57b8gidbhhhxr900q2wb6fabr",
"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.21.0": "18wj81x9xhqcd6985r8qxmbik6szjfjfj62q3xklw8h2p3x7srgg",
"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.18.0": "1bp2s9wn0gjsaygv21nsbfpf854vl897ll6sqpfn3naaannv1fwl",
"registry+https://github.com/rust-lang/crates.io-index#byteorder@1.5.0": "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z",
"registry+https://github.com/rust-lang/crates.io-index#bytes@1.9.0": "16ykzx24v1x4f42v2lxyvlczqhdfji3v7r4ghwckpwijzvb1hn9j",
"registry+https://github.com/rust-lang/crates.io-index#bytes@1.7.2": "1wzs7l57iwqmrszdpr2mmqf1b1hgvpxafc30imxhnry0zfl9m3a2",
"registry+https://github.com/rust-lang/crates.io-index#cairo-rs@0.18.5": "1qjfkcq3mrh3p01nnn71dy3kn99g21xx3j8xcdvzn8ll2pq6x8lc",
"registry+https://github.com/rust-lang/crates.io-index#cairo-sys-rs@0.18.2": "0lfsxl7ylw3phbnwmz3k58j1gnqi6kc2hdc7g3bb7f4hwnl9yp38",
"registry+https://github.com/rust-lang/crates.io-index#cc@1.2.10": "0aaj2ivamhfzhgb9maasnfkh03s2mzhzpzwrkghgzbkfnv5qy80k",
"registry+https://github.com/rust-lang/crates.io-index#cc@1.1.34": "1j9dh96lpkksmfvjfiqa5nrlswm5l6lj54m5jf7i0iik8l6lgfb7",
"registry+https://github.com/rust-lang/crates.io-index#cexpr@0.6.0": "0rl77bwhs5p979ih4r0202cn5jrfsrbgrksp40lkfz5vk1x3ib3g",
"registry+https://github.com/rust-lang/crates.io-index#cfg-expr@0.15.8": "00lgf717pmf5qd2qsxxzs815v6baqg38d6m5i6wlh235p14asryh",
"registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0": "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds",
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz-build@0.2.1": "03rmzd69cn7fp0fgkjr5042b3g54s2l941afjm3001ls7kqkjgj3",
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz@0.8.6": "0vlksnmpb6rd4h55245agnfhphnpslwnq9al3aw3is43dd3f16nm",
"registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.39": "09g8nf409lb184kl9j4s85k0kn8wzgjkp5ls9zid50b886fwqdky",
"registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.38": "009l8vc5p8750vn02z30mblg4pv2qhkbfizhfwmzc6vpy5nr67x2",
"registry+https://github.com/rust-lang/crates.io-index#clang-sys@1.8.1": "1x1r9yqss76z8xwpdanw313ss6fniwc1r7dzb5ycjn0ph53kj0hb",
"registry+https://github.com/rust-lang/crates.io-index#clap@4.5.30": "0vcyrn4ymq2gd56sl3xnfki8q8llg64sj3rj3qx33mgsf66v3dwj",
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.30": "0369xis2ar46icsaxqyy37976mlb62alzyx4j53k99vq2w3v4pd3",
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.28": "1vgigkhljp3r8r5lwdrn1ij93nafmjwh8cx77nppb9plqsaysk5z",
"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.7.4": "19nwfls5db269js5n822vkc8dw0wjq2h1wf0hgr06ld2g52d2spl",
"registry+https://github.com/rust-lang/crates.io-index#clap@4.5.20": "1s37v23gcxkjy4800qgnkxkpliz68vslpr5sgn1xar56hmnkfzxr",
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.20": "0m6w10l2f65h3ch0d53lql6p26xxrh20ffipra9ysjsfsjmq1g0r",
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.18": "1ardb26bvcpg72q9myr7yir3a8c83gx7vxk1cccabsd9n73s1ija",
"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.7.2": "15zcrc2fa6ycdzaihxghf48180bnvzsivhf0fmah24bnnaf76qhl",
"registry+https://github.com/rust-lang/crates.io-index#cloudabi@0.0.3": "0kxcg83jlihy0phnd2g8c2c303px3l2p3pkjz357ll6llnd5pz6x",
"registry+https://github.com/rust-lang/crates.io-index#color_quant@1.1.0": "12q1n427h2bbmmm1mnglr57jaz2dj9apk0plcxw7nwqiai7qjyrx",
"registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.3": "1439m3r3jy3xqck8aa13q658visn71ki76qa93cy55wkmalwlqsv",
"registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.2": "1h18ph538y8yjmbpaf8li98l0ifms2xmh3rax9666c5qfjfi3zfk",
"registry+https://github.com/rust-lang/crates.io-index#concurrent-queue@2.5.0": "0wrr3mzq2ijdkxwndhf79k952cp4zkz35ray8hvsxl96xrx1k82c",
"registry+https://github.com/rust-lang/crates.io-index#const-oid@0.9.6": "1y0jnqaq7p2wvspnx7qj76m7hjcqpz73qzvr9l2p9n2s51vr6if2",
"registry+https://github.com/rust-lang/crates.io-index#convert_case@0.6.0": "1jn1pq6fp3rri88zyw6jlhwwgf6qiyc08d6gjv0qypgkl862n67c",
@ -71,17 +71,17 @@
"registry+https://github.com/rust-lang/crates.io-index#cool_asserts@2.0.3": "1v18dg7ifx41k2f82j3gsnpm1fg9wk5s4zv7sf42c7pnad72b7zf",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation-sys@0.8.7": "12w8j73lazxmr1z0h98hf3z623kl8ms7g07jch7n4p8f9nwlhdkp",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation@0.9.4": "13zvbbj07yk3b61b8fhwfzhy35535a583irf23vlcg59j7h9bqci",
"registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.16": "1hy466fkhxjbb16i7na95wz8yr14d0kd578pwzj5lbkz14jh5f0n",
"registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.14": "1q3qd9qkw94vs7n5i0y3zz2cqgzcxvdgyb54ryngwmjhfbgrg1k0",
"registry+https://github.com/rust-lang/crates.io-index#crc-catalog@2.4.0": "1xg7sz82w3nxp1jfn425fvn1clvbzb3zgblmxsyqpys0dckp9lqr",
"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.4.2": "1czp7vif73b8xslr3c9yxysmh9ws2r8824qda7j47ffs9pcnjxx9",
"registry+https://github.com/rust-lang/crates.io-index#crc@3.2.1": "0dnn23x68qakzc429s1y9k9y3g8fn5v9jwi63jcz151sngby9rk9",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-deque@0.8.6": "0l9f1saqp1gn5qy0rxvkmz4m6n7fc0b3dbm6q1r5pmgpnyvi3lcx",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-deque@0.8.5": "03bp38ljx4wj6vvy4fbhx41q8f585zyqix6pncz1mkz93z08qgv1",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-epoch@0.9.18": "03j2np8llwf376m3fxqx859mgp9f83hj1w34153c7a9c7i5ar0jv",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-queue@0.3.12": "059igaxckccj6ndmg45d5yf7cm4ps46c18m21afq3pwiiz1bnn0g",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.21": "0a3aa2bmc8q35fb67432w16wvi54sfmb69rk9h5bhd18vw0c99fh",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-queue@0.3.11": "0d8y8y3z48r9javzj67v3p2yfswd278myz1j9vzc4sp7snslc0yz",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.20": "100fksq5mm1n7zj242cclkw6yf7a4a8ix3lvpfkhxvdhbda9kv12",
"registry+https://github.com/rust-lang/crates.io-index#crunchy@0.2.2": "1dx9mypwd5mpfbbajm78xcrg5lirqk7934ik980mmaffg3hdm0bs",
"registry+https://github.com/rust-lang/crates.io-index#crypto-common@0.1.6": "1cvby95a6xg7kxdz5ln3rl9xh66nz66w46mm3g56ri1z5x815yqv",
"registry+https://github.com/rust-lang/crates.io-index#data-encoding@2.7.0": "0vxdv88fnvnxw29gk84gj5662xlv0p5hmlxpwp7d60cckp8fwq0f",
"registry+https://github.com/rust-lang/crates.io-index#data-encoding@2.6.0": "1qnn68n4vragxaxlkqcb1r28d3hhj43wch67lm4rpxlw89wnjmp8",
"registry+https://github.com/rust-lang/crates.io-index#deflate@0.8.6": "0x6iqlayg129w63999kz97m279m0jj4x4sm6gkqlvmp73y70yxvk",
"registry+https://github.com/rust-lang/crates.io-index#der@0.7.9": "1h4vzjfa1lczxdf8avfj9qlwh1qianqlxdy1g5rn762qnvkzhnzm",
"registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.11": "1d1ibqqnr5qdrpw8rclwrf1myn3wf0dygl04idf4j2s49ah6yaxl",
@ -91,27 +91,26 @@
"registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5": "1q0alair462j21iiqwrr21iabkfnb13d6x5w95lkdg21q2xrqdlp",
"registry+https://github.com/rust-lang/crates.io-index#dotenvy@0.15.7": "16s3n973n5aqym02692i1npb079n5mb0fwql42ikmwn8wnrrbbqs",
"registry+https://github.com/rust-lang/crates.io-index#either@1.13.0": "1w2c1mybrd7vljyxk77y9f4w9dyjrmp3yp82mk7bcm8848fazcb0",
"registry+https://github.com/rust-lang/crates.io-index#encoding_rs@0.8.35": "1wv64xdrr9v37rqqdjsyb8l8wzlcbab80ryxhrszvnj59wy0y0vm",
"registry+https://github.com/rust-lang/crates.io-index#encoding_rs@0.8.34": "0nagpi1rjqdpvakymwmnlxzq908ncg868lml5b70n08bm82fjpdl",
"registry+https://github.com/rust-lang/crates.io-index#env_logger@0.10.2": "1005v71kay9kbz1d5907l0y7vh9qn2fqsp2yfgb8bjvin6m0bm2c",
"registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.1": "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl",
"registry+https://github.com/rust-lang/crates.io-index#errno@0.3.10": "0pgblicz1kjz9wa9m0sghkhh2zw1fhq1mxzj7ndjm746kg5m5n1k",
"registry+https://github.com/rust-lang/crates.io-index#errno@0.3.9": "1fi0m0493maq1jygcf1bya9cymz2pc1mqxj26bdv7yjd37v5qk2k",
"registry+https://github.com/rust-lang/crates.io-index#etcetera@0.8.0": "0hxrsn75dirbjhwgkdkh0pnpqrnq17ypyhjpjaypgax1hd91nv8k",
"registry+https://github.com/rust-lang/crates.io-index#event-listener-strategy@0.5.3": "1ch5gf6knllyq12jkb5zdfag573dh44307q4pwwi2g37sc6lwgiw",
"registry+https://github.com/rust-lang/crates.io-index#event-listener-strategy@0.5.2": "18f5ri227khkayhv3ndv7yl4rnasgwksl2jhwgafcxzr7324s88g",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@2.5.3": "1q4w3pndc518crld6zsqvvpy9lkzwahp2zgza9kbzmmqh9gif1h2",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@5.4.0": "1bii2gn3vaa33s0gr2zph7cagiq0ppcfxcxabs24ri9z9kgar4il",
"registry+https://github.com/rust-lang/crates.io-index#exr@1.73.0": "1q47yq78q9k210r6jy1wwrilxwwxqavik9l3l426rd17k7srfcgq",
"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0": "1ghiahsw1jd68df895cy5h3gzwk30hndidn3b682zmshpgmrx41p",
"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.7": "130ga18vyxbb5idbgi07njymdaavvk6j08yh1dfarm294ssm6s0y",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@5.3.1": "1fkm6q4hjn61wl52xyqyyxai0x9w0ngrzi0wf1qsf8vhsadvwck0",
"registry+https://github.com/rust-lang/crates.io-index#exr@1.72.0": "195iviimjnp1mdkqrq8hjrfkr0qavpp1p8pq5qvaksa30pv96zc8",
"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.1.1": "19nyzdq3ha4g173364y2wijmd6jlyms8qx40daqkxsnl458jmh78",
"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.5": "1axmgzpgf12yl3x9ymdslqza765la17j17ljv6a4kc143a90y2fq",
"registry+https://github.com/rust-lang/crates.io-index#field-offset@0.3.6": "0zq5sssaa2ckmcmxxbly8qgz3sxpb8g1lwv90sdh1z74qif2gqiq",
"registry+https://github.com/rust-lang/crates.io-index#fixed@1.28.0": "0nn85j5x8yzx10q49jdzia4yp6pnasnxpnwh0p9aqr7qkfwf1il5",
"registry+https://github.com/rust-lang/crates.io-index#flate2@1.0.35": "0z6h0wa095wncpfngx75wyhyjnqwld7wax401gsvnzjhzgdbydn9",
"registry+https://github.com/rust-lang/crates.io-index#flate2@1.0.34": "1w1nf2ap4q1sq1v6v951011wcvljk449ap7q7jnnjf8hvjs8kdd1",
"registry+https://github.com/rust-lang/crates.io-index#fluent-bundle@0.15.3": "14zl0cjn361is69pb1zry4k2zzh5nzsfv0iz05wccl00x0ga5q3z",
"registry+https://github.com/rust-lang/crates.io-index#fluent-langneg@0.13.0": "152yxplc11vmxkslvmaqak9x86xnavnhdqyhrh38ym37jscd0jic",
"registry+https://github.com/rust-lang/crates.io-index#fluent-syntax@0.11.1": "0gd3cdvsx9ymbb8hijcsc9wyf8h1pbcbpsafg4ldba56ji30qlra",
"registry+https://github.com/rust-lang/crates.io-index#fluent@0.16.1": "0njmdpwz52yjzyp55iik9k6vrixqiy7190d98pk0rgdy0x3n6x5v",
"registry+https://github.com/rust-lang/crates.io-index#flume@0.11.1": "15ch0slxa8sqsi6c73a0ky6vdnh48q8cxjf7rksa3243m394s3ns",
"registry+https://github.com/rust-lang/crates.io-index#flume@0.11.0": "10girdbqn77wi802pdh55lwbmymy437k7kklnvj12aaiwaflbb2m",
"registry+https://github.com/rust-lang/crates.io-index#fnv@1.0.7": "1hc2mcqha06aibcaza94vbi81j6pr9a1bbxrxjfhc91zin8yr7iz",
"registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.4": "0vsxw2iwpgs7yy6l7pndm7b8nllaq5vdxwnmjn1qpm5kyzhzvlm0",
"registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1": "0jxgzd04ra4imjv8jgkmdq59kj8fsz6w4zxsbmlai34h26225c00",
"registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2": "1cgk0vyd7r45cj769jym4a6s7vwshvd0z4bqrb92q1fwibmkkwzn",
"registry+https://github.com/rust-lang/crates.io-index#form_urlencoded@1.2.1": "0milh8x7nl4f450s3ddhg57a3flcv6yq8hlkyk6fyr3mcb128dp1",
@ -121,7 +120,7 @@
"registry+https://github.com/rust-lang/crates.io-index#futures-executor@0.3.31": "17vcci6mdfzx4gbk0wx64chr2f13wwwpvyf3xd5fb1gmjzcx2a0y",
"registry+https://github.com/rust-lang/crates.io-index#futures-intrusive@0.5.0": "0vwm08d1pli6bdaj0i7xhk3476qlx4pll6i0w03gzdnh7lh0r4qx",
"registry+https://github.com/rust-lang/crates.io-index#futures-io@0.3.31": "1ikmw1yfbgvsychmsihdkwa8a1knank2d9a8dk01mbjar9w1np4y",
"registry+https://github.com/rust-lang/crates.io-index#futures-lite@2.6.0": "0cmmgszlmkwsac9pyw5rfjakmshgx4wmzmlyn6mmjs0jav4axvgm",
"registry+https://github.com/rust-lang/crates.io-index#futures-lite@2.3.0": "19gk4my8zhfym6gwnpdjiyv2hw8cc098skkbkhryjdaf0yspwljj",
"registry+https://github.com/rust-lang/crates.io-index#futures-macro@0.3.31": "0l1n7kqzwwmgiznn0ywdc5i24z72zvh9q1dwps54mimppi7f6bhn",
"registry+https://github.com/rust-lang/crates.io-index#futures-sink@0.3.31": "1xyly6naq6aqm52d5rh236snm08kw8zadydwqz8bip70s6vzlxg5",
"registry+https://github.com/rust-lang/crates.io-index#futures-task@0.3.31": "124rv4n90f5xwfsm9qw6y99755y021cmi5dhzh253s920z77s3zr",
@ -134,7 +133,6 @@
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.11.2": "0a7w8w0rg47nmcinnfzv443lcyb8mplwc251p1jyr5xj2yh6wzv6",
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.14.7": "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45",
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.15": "1mzlnrb3dgyd1fb84gvw10pyr8wdqdl4ry4sr64i1s8an66pqmn4",
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.3.1": "1y154yzby383p63ndw6zpfm0fz3vf6c0zdwc7df6vkl150wrr923",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.11.4": "01hbw3isapzpzff8l6aw55jnaqx2bcscrbwyf3rglkbbfp397p9y",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.13.1": "1whrkvdg26gp1r7f95c6800y6ijqw5y0z8rgj6xihpi136dxdciz",
"registry+https://github.com/rust-lang/crates.io-index#gimli@0.31.1": "0gvqc0ramx8szv76jhfd4dms0zyamvlg4whhiz11j34hh3dqxqh7",
@ -146,7 +144,7 @@
"registry+https://github.com/rust-lang/crates.io-index#glib-macros@0.18.5": "1p5cla53fcp195zp0hkqpmnn7iwmkdswhy7xh34002bw8y7j5c0b",
"registry+https://github.com/rust-lang/crates.io-index#glib-sys@0.18.1": "164qhsfmlzd5mhyxs8123jzbdfldwxbikfpq5cysj3lddbmy4g06",
"registry+https://github.com/rust-lang/crates.io-index#glib@0.18.5": "1r8fw0627nmn19bgk3xpmcfngx3wkn7mcpq5a8ma3risx3valg93",
"registry+https://github.com/rust-lang/crates.io-index#glob@0.3.2": "1cm2w34b5w45fxr522h5b0fv1bxchfswcj560m3pnjbia7asvld8",
"registry+https://github.com/rust-lang/crates.io-index#glob@0.3.1": "16zca52nglanv23q5qrwd5jinw3d3as5ylya6y1pbx47vkxvrynj",
"registry+https://github.com/rust-lang/crates.io-index#gloo-timers@0.3.0": "1519157n7xppkk6pdw5w52vy1llzn5iljkqd7q1h5609jv7l7cdv",
"registry+https://github.com/rust-lang/crates.io-index#gobject-sys@0.18.0": "0i6fhp3m6vs3wkzyc22rk2cqj68qvgddxmpaai34l72da5xi4l08",
"registry+https://github.com/rust-lang/crates.io-index#graphene-rs@0.18.1": "00f4q1ra4haap5i7lazwhkdgnb49fs8adk2nm6ki6mjhl76jh8iv",
@ -159,8 +157,9 @@
"registry+https://github.com/rust-lang/crates.io-index#gtk4@0.7.3": "0hh8nzglmz94v1m1h6vy8z12m6fr7ia467ry0md5fa4p7sm53sss",
"registry+https://github.com/rust-lang/crates.io-index#h2@0.3.26": "1s7msnfv7xprzs6xzfj5sg6p8bjcdpcqcmjjbkd345cyi1x55zl1",
"registry+https://github.com/rust-lang/crates.io-index#half@2.4.1": "123q4zzw1x4309961i69igzd1wb7pj04aaii3kwasrz3599qrl3d",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.2": "12dj0yfn59p3kh3679ac0w1fagvzf4z2zp87a13gbbqbzw0185dz",
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.10.0": "1h8lzvnl9qxi3zyagivzz2p1hp6shgddfmccyf6jv7s1cdicz0kk",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.14.5": "1wa1vy1xs3mp11bn3z9dv0jricgr6a2j0zkf1g19yz3vw4il89z5",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.0": "1yx4xq091s7i6mw6bn77k8cp4jrpcac149xr32rg8szqsj27y20y",
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.8.4": "1xy8agkyp0llbqk9fcffc1xblayrrywlyrm2a7v93x8zygm4y2g8",
"registry+https://github.com/rust-lang/crates.io-index#headers-core@0.2.0": "0ab469xfpd411mc3dhmjhmzrhqikzyj8a17jn5bkj9zfpy0n9xp7",
"registry+https://github.com/rust-lang/crates.io-index#headers@0.3.9": "0w62gnwh2p1lml0zqdkrx9dp438881nhz32zrzdy61qa0a9kns06",
"registry+https://github.com/rust-lang/crates.io-index#heck@0.4.1": "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m",
@ -171,35 +170,24 @@
"registry+https://github.com/rust-lang/crates.io-index#hex@0.4.3": "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z",
"registry+https://github.com/rust-lang/crates.io-index#hkdf@0.12.4": "1xxxzcarz151p1b858yn5skmhyrvn8fs4ivx5km3i1kjmnr8wpvv",
"registry+https://github.com/rust-lang/crates.io-index#hmac@0.12.1": "0pmbr069sfg76z7wsssfk5ddcqd9ncp79fyz6zcm6yn115yc6jbc",
"registry+https://github.com/rust-lang/crates.io-index#home@0.5.11": "1kxb4k87a9sayr8jipr7nq9wpgmjk4hk4047hmf9kc24692k75aq",
"registry+https://github.com/rust-lang/crates.io-index#home@0.5.9": "19grxyg35rqfd802pcc9ys1q3lafzlcjcv2pl2s5q8xpyr5kblg3",
"registry+https://github.com/rust-lang/crates.io-index#http-body@0.4.6": "1lmyjfk6bqk6k9gkn1dxq770sb78pqbqshga241hr5p995bb5skw",
"registry+https://github.com/rust-lang/crates.io-index#http@0.2.12": "1w81s4bcbmcj9bjp7mllm8jlz6b31wzvirz8bgpzbqkpwmbvn730",
"registry+https://github.com/rust-lang/crates.io-index#http@1.2.0": "1skglzdf98j5nzxlii540n11is0w4l80mi5sm3xrj716asps4v7i",
"registry+https://github.com/rust-lang/crates.io-index#http@1.1.0": "0n426lmcxas6h75c2cp25m933pswlrfjz10v91vc62vib2sdvf91",
"registry+https://github.com/rust-lang/crates.io-index#httparse@1.9.5": "0ip9v8m9lvgvq1lznl31wvn0ch1v254na7lhid9p29yx9rbx6wbx",
"registry+https://github.com/rust-lang/crates.io-index#httpdate@1.0.3": "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz",
"registry+https://github.com/rust-lang/crates.io-index#humantime@2.1.0": "1r55pfkkf5v0ji1x6izrjwdq9v6sc7bv99xj6srywcar37xmnfls",
"registry+https://github.com/rust-lang/crates.io-index#hyper-tls@0.5.0": "01crgy13102iagakf6q4mb75dprzr7ps1gj0l5hxm1cvm7gks66n",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.10.16": "0wwjh9p3mzvg3fss2lqz5r7ddcgl1fh9w6my2j69d6k0lbcm41ha",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.32": "1rvcb0smz8q1i0y6p7rwxr02x5sclfg2hhxf3g0774zczn0cgps1",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.30": "1jayxag79yln1nzyzx652kcy1bikgwssn6c4zrrp5v7s3pbdslm1",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone-haiku@0.1.2": "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.61": "085jjsls330yj1fnwykfzmb2f10zp6l7w4fhq81ng81574ghhpi3",
"registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0": "09j5kskirl59mvqc8kabhy7005yyy7dp88jw9f6f3gkf419a8byv",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid@1.5.0": "0dznvd1c5b02iilqm044q4hvar0sqibq1z46prqwjzwif61vpb0k",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform@1.5.0": "0kmmi1kmj9yph6mdgkc7v3wz6995v7ly3n80vbg0zr78bp1iml81",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform_data@1.5.0": "0vkgjixm0wzp2n3v5mw4j89ly05bg3lx96jpdggbwlpqi0rzzj7x",
"registry+https://github.com/rust-lang/crates.io-index#icu_normalizer@1.5.0": "0kx8qryp8ma8fw1vijbgbnf7zz9f2j4d14rw36fmjs7cl86kxkhr",
"registry+https://github.com/rust-lang/crates.io-index#icu_normalizer_data@1.5.0": "05lmk0zf0q7nzjnj5kbmsigj3qgr0rwicnn5pqi9n7krmbvzpjpq",
"registry+https://github.com/rust-lang/crates.io-index#icu_properties@1.5.1": "1xgf584rx10xc1p7zjr78k0n4zn3g23rrg6v2ln31ingcq3h5mlk",
"registry+https://github.com/rust-lang/crates.io-index#icu_properties_data@1.5.0": "0scms7pd5a7yxx9hfl167f5qdf44as6r3bd8myhlngnxqgxyza37",
"registry+https://github.com/rust-lang/crates.io-index#icu_provider@1.5.0": "1nb8vvgw8dv2inqklvk05fs0qxzkw8xrg2n9vgid6y7gm3423m3f",
"registry+https://github.com/rust-lang/crates.io-index#icu_provider_macros@1.5.0": "1mjs0w7fcm2lcqmbakhninzrjwqs485lkps4hz0cv3k36y9rxj0y",
"registry+https://github.com/rust-lang/crates.io-index#idna@0.1.5": "0kl4gs5kaydn4v07c6ka33spm9qdh2np0x7iw7g5zd8z1c7rxw1q",
"registry+https://github.com/rust-lang/crates.io-index#idna@1.0.3": "0zlajvm2k3wy0ay8plr07w22hxkkmrxkffa6ah57ac6nci984vv8",
"registry+https://github.com/rust-lang/crates.io-index#idna_adapter@1.2.0": "0wggnkiivaj5lw0g0384ql2d7zk4ppkn3b1ry4n0ncjpr7qivjns",
"registry+https://github.com/rust-lang/crates.io-index#idna@0.5.0": "1xhjrcjqq0l5bpzvdgylvpkgk94panxgsirzhjnnqfdgc4a9nkb3",
"registry+https://github.com/rust-lang/crates.io-index#image@0.23.14": "18gn2f7xp30pf9aqka877knlq308khxqiwjvsccvzaa4f9zcpzr4",
"registry+https://github.com/rust-lang/crates.io-index#image@0.24.9": "17gnr6ifnpzvhjf6dwbl9hki8x6bji5mwcqp0048x1jm5yfi742n",
"registry+https://github.com/rust-lang/crates.io-index#indent_write@2.2.0": "1hqjp80argdskrhd66g9sh542yxy8qi77j6rc69qd0l7l52rdzhc",
"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.7.0": "07s7jmdymvd0rm4yswp0j3napx57hkjm9gs9n55lvs2g78vj5y32",
"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.6.0": "1nmrwn8lbs19gkvhxaawffzbvrpyrb5y3drcrr645x957kz0fybh",
"registry+https://github.com/rust-lang/crates.io-index#intl-memoizer@0.5.2": "1nkvql7c7b76axv4g68di1p2m9bnxq1cbn6mlqcawf72zhhf08py",
"registry+https://github.com/rust-lang/crates.io-index#intl_pluralrules@7.0.2": "0wprd3h6h8nfj62d8xk71h178q7zfn3srxm787w4sawsqavsg3h7",
"registry+https://github.com/rust-lang/crates.io-index#ipnet@2.10.1": "025p9wm94q1w2l13hbbr4cbmfygly3a2ag8g5s618l2jhq4l3hnx",
@ -207,10 +195,10 @@
"registry+https://github.com/rust-lang/crates.io-index#is-terminal@0.4.13": "0jwgjjz33kkmnwai3nsdk1pz9vb6gkqvw1d1vq7bs3q48kinh7r6",
"registry+https://github.com/rust-lang/crates.io-index#is_terminal_polyfill@1.70.1": "1kwfgglh91z33kl0w5i338mfpa3zs0hidq5j4ny4rmjwrikchhvr",
"registry+https://github.com/rust-lang/crates.io-index#itertools@0.12.1": "0s95jbb3ndj1lvfxyq5wanc0fm0r6hg6q4ngb92qlfdxvci10ads",
"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.14": "0x26kr9m062mafaxgcf2p6h2x7cmixm0zw95aipzn2hr3d5jlnnp",
"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.11": "0nv9cqjwzr3q58qz84dcz63ggc54yhf1yqar1m858m1kfd4g3wa9",
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.1.22": "1wnh0bmmswpgwhgmlizz545x8334nlbmkq8imy9k224ri3am7792",
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.3.1": "1c1k53svpdyfhibkmm0ir5w0v3qmcmca8xr8vnnmizwf6pdagm7m",
"registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.77": "13x2qcky5l22z4xgivi59xhjjx4kxir1zg7gcj0f1ijzd4yg7yhw",
"registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.70": "0yp3rz7vrn9mmqdpkds426r1p9vs6i8mkxx8ryqdfadr0s2q0s0q",
"registry+https://github.com/rust-lang/crates.io-index#kv-log-macro@1.0.7": "0zwp4bxkkp87rl7xy2dain77z977rvcry1gmr5bssdbn541v7s0d",
"registry+https://github.com/rust-lang/crates.io-index#language-tags@0.2.2": "16hrjdpa827carq5x4b8zhas24d8kg4s16m6nmmn1kb7cr5qh7d9",
"registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.5.0": "1zk6dqqni0193xg6iijh7i3i44sryglwgvx20spdvwk3r6sbrlmv",
@ -218,18 +206,17 @@
"registry+https://github.com/rust-lang/crates.io-index#lebe@0.5.2": "1j2l6chx19qpa5gqcw434j83gyskq3g2cnffrbl3842ymlmpq203",
"registry+https://github.com/rust-lang/crates.io-index#libadwaita-sys@0.5.3": "16n6xsy6jhbj0jbpz8yvql6c9b89a99v9vhdz5s37mg1inisl42y",
"registry+https://github.com/rust-lang/crates.io-index#libadwaita@0.5.3": "174pzn9dwsk8ikvrhx13vkh0zrpvb3rhg9yd2q5d2zjh0q6fgrrg",
"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.169": "02m253hs8gw0m1n8iyrsc4n15yzbqwhddi7w1l0ds7i92kdsiaxm",
"registry+https://github.com/rust-lang/crates.io-index#libloading@0.8.6": "0d2ccr88f8kv3x7va2ccjxalcjnhrci4j2kwxp7lfmbkpjs4wbzw",
"registry+https://github.com/rust-lang/crates.io-index#libm@0.2.11": "1yjgk18rk71rjbqcw9l1zaqna89p9s603k7n327nqs8dn88vwmc3",
"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.159": "1i9xpia0hn1y8dws7all8rqng6h3lc8ymlgslnljcvm376jrf7an",
"registry+https://github.com/rust-lang/crates.io-index#libloading@0.8.5": "194dvczq4sifwkzllfmw0qkgvilpha7m5xy90gd6i446vcpz4ya9",
"registry+https://github.com/rust-lang/crates.io-index#libm@0.2.8": "0n4hk1rs8pzw8hdfmwn96c4568s93kfxqgcqswr7sajd2diaihjf",
"registry+https://github.com/rust-lang/crates.io-index#libspa-sys@0.8.0": "07yh4i5grzbxkchg6dnxlwbdw2wm5jnd7ffbhl77jr0388b9f3dz",
"registry+https://github.com/rust-lang/crates.io-index#libspa@0.8.0": "044qs48yl0llp2dmrgwxj9y1pgfy09i6fhq661zqqb9a3fwa9wv5",
"registry+https://github.com/rust-lang/crates.io-index#libsqlite3-sys@0.30.1": "0jcikvgbj84xc7ikdmpc8m4y5lyqgrb9aqblphwk67kv95xgp69f",
"registry+https://github.com/rust-lang/crates.io-index#libsqlite3-sys@0.27.0": "05pp60ncrmyjlxxjj187808jkvpxm06w5lvvdwwvxd2qrmnj4kng",
"registry+https://github.com/rust-lang/crates.io-index#libyml@0.0.5": "106963pwg1gc3165bdlk8bbspmk919gk10vshhqglks3z8m700ik",
"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15": "1aq7r2g7786hyxhv40spzf2nhag5xbw2axxc1k8z5k1dsgdm4v6j",
"registry+https://github.com/rust-lang/crates.io-index#litemap@0.7.4": "012ili3vppd4952sh6y3qwcd0jkd0bq2qpr9h7cppc8sj11k7saf",
"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.14": "12gsjgbhhjwywpqcrizv80vrp7p7grsz5laqq773i33wphjsxcvq",
"registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.12": "05qvxa6g27yyva25a5ghsg85apdxkvr77yhkyhapj6r8vnf8pbq7",
"registry+https://github.com/rust-lang/crates.io-index#log@0.3.9": "0jq23hhn5h35k7pa8r7wqnsywji6x3wn1q5q7lif5q536if8v7p1",
"registry+https://github.com/rust-lang/crates.io-index#log@0.4.25": "17ydv5zhfv1zzygy458bmg3f3jx1vfziv9d74817w76yhfqgbjq4",
"registry+https://github.com/rust-lang/crates.io-index#log@0.4.22": "093vs0wkm1rgyykk7fjbqp2lwizbixac1w52gv109p5r4jh0p9x7",
"registry+https://github.com/rust-lang/crates.io-index#logger@0.4.0": "14xlxvkspcfnspjil0xi63qj5cybxn1hjmr5gq8m4v1g9k5p54bc",
"registry+https://github.com/rust-lang/crates.io-index#matches@0.1.10": "1994402fq4viys7pjhzisj4wcw894l53g798kkm2y74laxk0jci5",
"registry+https://github.com/rust-lang/crates.io-index#md-5@0.10.6": "1kvq5rnpm4fzwmyv5nmnxygdhhb2369888a06gdc9pxyrzh7x7nq",
@ -242,8 +229,9 @@
"registry+https://github.com/rust-lang/crates.io-index#minimal-lexical@0.2.1": "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.3.7": "0dblrhgbm0wa8jjl8cjp81akaj36yna92df4z1h9b26n3spal7br",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.4.4": "0jsfv00hl5rmx1nijn59sr9jmjd4rjnjhh4kdjy8d187iklih9d9",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.3": "093r1kd1r9dyf05cbvsibgmh96pxp3qhzfvpd6f15bpggamjqh5q",
"registry+https://github.com/rust-lang/crates.io-index#mio@1.0.3": "1gah0h4ia3avxbwym0b6bi6lr6rpysmj9zvw6zis5yq0z0xq91i8",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.7.4": "024wv14aa75cvik7005s5y2nfc8zfidddbd7g55g7sjgnzfl18mq",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.0": "1wadxkg6a6z4lr7kskapj5d8pxlx7cp1ifw4daqnkzqjxych5n72",
"registry+https://github.com/rust-lang/crates.io-index#mio@1.0.2": "1v1cnnn44awxbcfm4zlavwgkvbyg7gp5zzjm8mqf1apkrwflvq40",
"registry+https://github.com/rust-lang/crates.io-index#modifier@0.1.0": "0n3fmgli1nsskl0whrfzm1gk0rmwwl6pw1q4nb9sqqmn5h8wkxa1",
"registry+https://github.com/rust-lang/crates.io-index#multer@2.1.0": "1hjiphaypj3phqaj5igrzcia9xfmf4rr4ddigbh8zzb96k1bvb01",
"registry+https://github.com/rust-lang/crates.io-index#nary_tree@0.4.3": "1iqray1a716995l9mmvz5sfqrwg9a235bvrkpcn8bcqwjnwfv1pv",
@ -258,32 +246,33 @@
"registry+https://github.com/rust-lang/crates.io-index#num-rational@0.3.2": "01sgiwny9iflyxh2xz02sak71v2isc3x608hfdpwwzxi3j5l5b0j",
"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19": "0h984rhdkkqd4ny9cif7y2azl3xdfb7768hb9irhpsch4q3gq787",
"registry+https://github.com/rust-lang/crates.io-index#num_cpus@1.16.0": "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1",
"registry+https://github.com/rust-lang/crates.io-index#object@0.36.7": "11vv97djn9nc5n6w1gc6bd96d2qk2c8cg1kw5km9bsi3v4a8x532",
"registry+https://github.com/rust-lang/crates.io-index#object@0.36.5": "0gk8lhbs229c68lapq6w6qmnm4jkj48hrcw5ilfyswy514nhmpxf",
"registry+https://github.com/rust-lang/crates.io-index#once_cell@1.20.2": "0xb7rw1aqr7pa4z3b00y7786gyf8awx2gca3md73afy76dzgwq8j",
"registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1": "173xxvfc63rr5ybwqwylsir0vq6xsj4kxiv4hmg4c3vscdmncj59",
"registry+https://github.com/rust-lang/crates.io-index#openssl-probe@0.1.5": "1kq18qm48rvkwgcggfkqq6pm948190czqc94d6bm2sir5hq1l0gz",
"registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.104": "0hf712xcxmycnlc09r8d446b3mwqchsbfrjv374fp7grrc3g7as5",
"registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.68": "1xbiz2bmba2fibg70s462yk2fndp3f9vz11c7iw0ilh2y54bqx31",
"registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.103": "1mi9r5vbgqqwfa2nqlh2m0r1v5abhzjigfbi7ja0mx0xx7p8v7kz",
"registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.66": "1hfr9ffx67j455aqrmyys3c8l65ngbqrl5qi3v3fi8vhddwg8acm",
"registry+https://github.com/rust-lang/crates.io-index#pango-sys@0.18.0": "1iaxalcaaj59cl9n10svh4g50v8jrc1a36kd7n9yahx8j7ikfrs3",
"registry+https://github.com/rust-lang/crates.io-index#pango@0.18.3": "1r5ygq7036sv7w32kp8yxr6vgggd54iaavh3yckanmq4xg0px8kw",
"registry+https://github.com/rust-lang/crates.io-index#parking@2.2.1": "1fnfgmzkfpjd69v4j9x737b1k8pnn054bvzcn5dm3pkgq595d3gk",
"registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.3": "09ws9g6245iiq8z975h8ycf818a66q3c6zv4b5h8skpm7hc1igzi",
"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.10": "1y3cf9ld9ijf7i4igwzffcn0xl16dxyn4c5bwgjck1dkgabiyh0y",
"registry+https://github.com/rust-lang/crates.io-index#parse-zoneinfo@0.3.1": "093cs8slbd6kyfi6h12isz0mnaayf5ha8szri1xrbqj4inqhaahz",
"registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15": "02pxffpdqkapy292harq6asfjvadgp1s005fip9ljfsn9fvxgh2p",
"registry+https://github.com/rust-lang/crates.io-index#pem-rfc7468@0.7.0": "04l4852scl4zdva31c1z6jafbak0ni5pi0j38ml108zwzjdrrcw8",
"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@1.0.1": "0cgq08v1fvr6bs5fvy390cz830lq4fak8havdasdacxcw790s09i",
"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.1": "0gi8wgx0dcy8rnv1kywdv98lwcx67hz0a0zwpib5v2i08r88y573",
"registry+https://github.com/rust-lang/crates.io-index#phf@0.11.3": "0y6hxp1d48rx2434wgi5g8j1pr8s5jja29ha2b65435fh057imhz",
"registry+https://github.com/rust-lang/crates.io-index#phf@0.11.2": "1p03rsw66l7naqhpgr1a34r9yzi1gv9jh16g3fsk6wrwyfwdiqmd",
"registry+https://github.com/rust-lang/crates.io-index#phf@0.7.24": "066xwv4dr6056a9adlkarwp4n94kbpwngbmd47ngm3cfbyw49nmk",
"registry+https://github.com/rust-lang/crates.io-index#phf_codegen@0.11.3": "0si1n6zr93kzjs3wah04ikw8z6npsr39jw4dam8yi9czg2609y5f",
"registry+https://github.com/rust-lang/crates.io-index#phf_codegen@0.11.2": "0nia6h4qfwaypvfch3pnq1nd2qj64dif4a6kai3b7rjrsf49dlz8",
"registry+https://github.com/rust-lang/crates.io-index#phf_codegen@0.7.24": "0zjiblicfm0nrmr2xxrs6pnf6zz2394wgch6dcbd8jijkq98agmh",
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.11.3": "0gc4np7s91ynrgw73s2i7iakhb4lzdv1gcyx7yhlc0n214a2701w",
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.11.2": "1c14pjyxbcpwkdgw109f7581cc5fa3fnkzdq1ikvx7mdq9jcrr28",
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.7.24": "0qi62gxk3x3whrmw5c4i71406icqk11qmpgln438p6qm7k4lqdh9",
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.11.3": "1rallyvh28jqd9i916gk5gk2igdmzlgvv5q0l3xbf3m6y8pbrsk7",
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.11.2": "0azphb0a330ypqx3qvyffal5saqnks0xvl8rj73jlk3qxxgbkz4h",
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.7.24": "18371fla0vsj7d6d5rlfb747xbr2in11ar9vgv5qna72bnhp2kr3",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-internal@1.1.8": "1yzfhf6l27nhzv7r5hfrwj2g0x7xmfhgil19fj9am4srqp06csnm",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16": "16wzc7z7dfkf9bmjin22f5282783f6mdksnr0nv0j5ym5f9gyg1v",
"registry+https://github.com/rust-lang/crates.io-index#pin-project@1.1.8": "05jr3xfy1spgmz3q19l4mmvv46vgvkvsgphamifx7x45swxcabhy",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-internal@1.1.7": "133mxf5vmvnvw4idw2y2lb5bxsza2xlyfl6psjy7mz3l12nmy3rw",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.14": "00nx3f04agwjlsmd3mc5rx5haibj2v8q9b52b0kwn63wcv4nz9mx",
"registry+https://github.com/rust-lang/crates.io-index#pin-project@1.1.7": "15cvflrzsgp1zbl5gv37al2r62nl8lc37xkfwf70ql3fji7gcmxy",
"registry+https://github.com/rust-lang/crates.io-index#pin-utils@0.1.0": "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb",
"registry+https://github.com/rust-lang/crates.io-index#piper@0.2.4": "0rn0mjjm0cwagdkay77wgmz3sqf8fqmv9d9czm79mvr2yj8c9j4n",
"registry+https://github.com/rust-lang/crates.io-index#pipewire-sys@0.8.0": "04hiy3rl8v3j2dfzp04gr7r8l5azzqqsvqdzwa7sipdij27ii7l4",
@ -293,20 +282,20 @@
"registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.31": "1wk6yp2phl91795ia0lwkr3wl4a9xkrympvhqq8cxk4d75hwhglm",
"registry+https://github.com/rust-lang/crates.io-index#plugin@0.2.6": "1q7nghkpvxxr168y2jnzh3w7qc9vfrby9n7ygy3xpj0bj71hsshs",
"registry+https://github.com/rust-lang/crates.io-index#png@0.16.8": "1ipl44q3vy4kvx6j296vk7d4v8gvcg203lrkvvixwixq1j98fciw",
"registry+https://github.com/rust-lang/crates.io-index#png@0.17.16": "09kmkms9fmkbkarw0lnf0scqvjwwg3r7riddag0i3q39r0pil5c2",
"registry+https://github.com/rust-lang/crates.io-index#polling@3.7.4": "0bs4nhwfwsvlzlhah2gbhj3aa9ynvchv2g350wapswh26a65c156",
"registry+https://github.com/rust-lang/crates.io-index#png@0.17.14": "1w130qw3cngzppxk1yp3ls2pbw3f0spbzhkbarbnlnm06imd9yaj",
"registry+https://github.com/rust-lang/crates.io-index#polling@3.7.3": "04b5zdgz0m9ydbzcr3f9a55749gqbj0y89d0nz9nrv0x636r09yc",
"registry+https://github.com/rust-lang/crates.io-index#powerfmt@0.2.0": "14ckj2xdpkhv3h6l5sdmb9f1d57z8hbfpdldjc2vl5givq2y77j3",
"registry+https://github.com/rust-lang/crates.io-index#ppv-lite86@0.2.20": "017ax9ssdnpww7nrl1hvqh2lzncpv04nnsibmnw9nxjnaqlpp5bp",
"registry+https://github.com/rust-lang/crates.io-index#pretty_env_logger@0.5.0": "076w9dnvcpx6d3mdbkqad8nwnsynb7c8haxmscyrz7g3vga28mw6",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@1.3.1": "069r1k56bvgk0f58dm5swlssfcp79im230affwk6d9ck20g04k3z",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.0": "1s23imns07vmacn2xjd5hv2h6rr94iqq3fd2frwa6i4h2nk6d0vy",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.2": "092x5acqnic14cw6vacqap5kgknq3jn4c6jij9zi6j85839jc3xh",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error-attr@1.0.4": "0sgq6m5jfmasmwwy8x4mjygx5l7kp8s4j60bv25ckv2j1qc41gm1",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error@1.0.4": "1373bhxaf0pagd8zkyd03kkx6bchzf6g0dkwrwzsnal9z47lj9fs",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.93": "169dw9wch753if1mgyi2nfl1il77gslvh6y2q46qplprwml6m530",
"registry+https://github.com/rust-lang/crates.io-index#proptest@1.6.0": "0l4y4bb8hffv7cys7d59qwqdmvmqjfzz0x9vblc08209clqfkjhl",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.89": "0vlq56v41dsj69pnk7lil7fxvbfid50jnzdn3xnr31g05mkb0fgi",
"registry+https://github.com/rust-lang/crates.io-index#proptest@1.5.0": "13gm7mphs95cw4gbgk5qiczkmr68dvcwhp58gmiz33dq2ccm3hml",
"registry+https://github.com/rust-lang/crates.io-index#qoi@0.4.1": "00c0wkb112annn2wl72ixyd78mf56p4lxkhlmsggx65l3v3n8vbz",
"registry+https://github.com/rust-lang/crates.io-index#quick-error@1.2.3": "1q6za3v78hsspisc197bg3g7rpc989qycy8ypr8ap8igv10ikl51",
"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.38": "1k0s75w61k6ch0rs263r4j69b7vj1wadqgb9dia4ylc9mymcqk8f",
"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.37": "1brklraw2g34bxy9y4q1nbrccn7bv36ylihv12c9vlcii55x7fdm",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.3.23": "0v679h38pjjqj5h4md7v2slsvj6686qgcn7p9fbw3h43iwnk1b34",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.4.6": "14qjfv3gggzhnma20k0sc1jf8y6pplsaq7n1j9ls5c8kf2wl0a2m",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.6.5": "1jl4449jcl4wgmzld6ffwqj5gwxrp8zvx8w573g1z368qg6xlwbd",
@ -326,35 +315,34 @@
"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.12.1": "1qpwim68ai5h0j7axa8ai8z0payaawv3id0lrgkqmapx7lx8fr8l",
"registry+https://github.com/rust-lang/crates.io-index#rayon@1.10.0": "1ylgnzwgllajalr4v00y4kj22klq2jbwllm70aha232iah0sc65l",
"registry+https://github.com/rust-lang/crates.io-index#rdrand@0.4.0": "1cjq0kwx1bk7jx3kzyciiish5gqsj7620dm43dc52sr8fzmm9037",
"registry+https://github.com/rust-lang/crates.io-index#redox_syscall@0.5.8": "0d48ylyd6gsamynyp257p6n2zl4dw2fhnn5z9y3nhgpri6rn5a03",
"registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.9": "02092l8zfh3vkmk47yjc8d631zhhcd49ck2zr133prvd3z38v7l0",
"registry+https://github.com/rust-lang/crates.io-index#redox_syscall@0.5.7": "07vpgfr6a04k0x19zqr1xdlqm6fncik3zydbdi3f5g3l5k7zwvcv",
"registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.8": "18wd530ndrmygi6xnz3sp345qi0hy2kdbsa89182nwbl6br5i1rn",
"registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.5": "0p41p3hj9ww7blnbwbj9h7rwxzxg0c1hvrdycgys8rxyhqqw859b",
"registry+https://github.com/rust-lang/crates.io-index#regex@1.11.1": "148i41mzbx8bmq32hsj1q4karkzzx5m60qza6gdw4pdc9qdyyi5m",
"registry+https://github.com/rust-lang/crates.io-index#regex@1.11.0": "1n5imk7yxam409ik5nagsjpwqvbg3f0g0mznd5drf549x1g0w81q",
"registry+https://github.com/rust-lang/crates.io-index#remove_dir_all@0.5.3": "1rzqbsgkmr053bxxl04vmvsd1njyz0nxvly97aip6aa2cmb15k9s",
"registry+https://github.com/rust-lang/crates.io-index#reqwest@0.11.27": "0qjary4hpplpgdi62d2m0xvbn6lnzckwffm0rgkm2x51023m6ryx",
"registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.7": "06amqm85raq26v6zg00fbf93lbj3kx559n2lpxc3wrvbbiy5vis7",
"registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.6": "1z0d1aavfm0v4pv8jqmqhhvvhvblla1ydzlvwykpc3mkzhj523jx",
"registry+https://github.com/rust-lang/crates.io-index#rustc-demangle@0.1.24": "07zysaafgrkzy2rjgwqdj2a8qdpsm6zv6f5pgpk9x0lm40z9b6vi",
"registry+https://github.com/rust-lang/crates.io-index#rustc-hash@1.1.0": "1qkc5khrmv5pqi5l5ca9p5nl5hs742cagrndhbrlk3dhlrx3zm08",
"registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.4.1": "14lvdsmr5si5qbqzrajgb6vfn69k0sfygrvfvr2mps26xwi3mjyg",
"registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.43": "1xjfhdnmqsbwnfmm77vyh7ldhqx0g9waqm4982404d7jdgp93257",
"registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.37": "04b8f99c2g36gyggf4aphw8742k2b1vls3364n2z493whj5pijwa",
"registry+https://github.com/rust-lang/crates.io-index#rustls-pemfile@1.0.4": "1324n5bcns0rnw6vywr5agff3rwfvzphi7rmbyzwnv6glkhclx0w",
"registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.19": "1m39qd65jcd1xgqzdm3017ppimiggh2446xngwp1ngr8hjbmpi7p",
"registry+https://github.com/rust-lang/crates.io-index#rusty-fork@0.3.0": "0kxwq5c480gg6q0j3bg4zzyfh2kwmc3v2ba94jw8ncjc8mpcqgfb",
"registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.18": "17xx2s8j1lln7iackzd9p0sv546vjq71i779gphjq923vjh5pjzk",
"registry+https://github.com/rust-lang/crates.io-index#safemem@0.3.3": "0wp0d2b2284lw11xhybhaszsczpbq1jbdklkxgifldcknmy3nw7g",
"registry+https://github.com/rust-lang/crates.io-index#schannel@0.1.27": "0gbbhy28v72kd5iina0z2vcdl3vz63mk5idvkzn5r52z6jmfna8z",
"registry+https://github.com/rust-lang/crates.io-index#schannel@0.1.26": "1hfip5mdwqcfnmrnkrq9d8zwy6bssmf6rfm2441nk83ghbjpn8h1",
"registry+https://github.com/rust-lang/crates.io-index#scoped-tls@1.0.1": "15524h04mafihcvfpgxd8f4bgc3k95aclz8grjkg9a0rxcvn9kz1",
"registry+https://github.com/rust-lang/crates.io-index#scoped_threadpool@0.1.9": "1a26d3lk40s9mrf4imhbik7caahmw2jryhhb6vqv6fplbbgzal8x",
"registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0": "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l",
"registry+https://github.com/rust-lang/crates.io-index#security-framework-sys@2.14.0": "0chwn01qrnvs59i5220bymd38iddy4krbnmfnhf4k451aqfj7ns9",
"registry+https://github.com/rust-lang/crates.io-index#security-framework-sys@2.12.0": "1dml0lp9lrvvi01s011lyss5kzzsmakaamdwsxr0431jd4l2jjpa",
"registry+https://github.com/rust-lang/crates.io-index#security-framework@2.11.1": "00ldclwx78dm61v7wkach9lcx76awlrv0fdgjdwch4dmy12j4yw9",
"registry+https://github.com/rust-lang/crates.io-index#self_cell@0.10.3": "0pci3zh23b7dg6jmlxbn8k4plb7hcg5jprd1qiz0rp04p1ilskp1",
"registry+https://github.com/rust-lang/crates.io-index#self_cell@1.1.0": "1gmxk5bvnnimcif7v1jk8ai2azfvh9djki545nd86vsnphjgrzf2",
"registry+https://github.com/rust-lang/crates.io-index#semver@1.0.24": "1fmvjjkd3f64y5fqr1nakkq371mnwzv09fbz5mbmdxril63ypdiw",
"registry+https://github.com/rust-lang/crates.io-index#self_cell@1.0.4": "0jki9brixzzy032d799xspz1gikc5n2w81w8q8yyn8w6jxpsjsfk",
"registry+https://github.com/rust-lang/crates.io-index#semver@1.0.23": "12wqpxfflclbq4dv8sa6gchdh92ahhwn4ci1ls22wlby3h57wsb1",
"registry+https://github.com/rust-lang/crates.io-index#serde@0.9.15": "1bsla8l5xr9pp5sirkal6mngxcq6q961km88jvf339j5ff8j7dil",
"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.218": "0q6z4bnrwagnms0bds4886711l6mc68s979i49zd3xnvkg8wkpz8",
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.218": "0azqd74xbpb1v5vf6w1fdbgmwp39ljjfj25cib5rgrzlj7hh75gh",
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.139": "19kj3irpa22a7djz1jaf4wambzh7psiqa6zyafqnb76crhx6ry24",
"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.210": "0flc0z8wgax1k4j5bf2zyq48bgzyv425jkd5w0i6wbh7f8j5kqy8",
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.210": "07yzy4wafk79ps0hmbqmsqh5xjna4pm4q57wc847bb8gl3nh4f94",
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.128": "1n43nia50ybpcfmh3gcw4lcc627qsg9nyakzwgkk9pm10xklbxbg",
"registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.8": "1q89g70azwi4ybilz5jb8prfpa575165lmrffd49vmcf76qpqq47",
"registry+https://github.com/rust-lang/crates.io-index#serde_urlencoded@0.7.1": "1zgklbdaysj3230xivihs30qi5vkhigg323a9m62k8jwf4a1qjfk",
"registry+https://github.com/rust-lang/crates.io-index#serde_yml@0.0.12": "1p8xwz4znd6fj962y22fdvvv16gb8c0hx4iv5hjplngiidcdvqjr",
@ -365,63 +353,59 @@
"registry+https://github.com/rust-lang/crates.io-index#signature@2.2.0": "1pi9hd5vqfr3q3k49k37z06p7gs5si0in32qia4mmr1dancr6m3p",
"registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.7": "1zkq40c3iajcnr5936gjp9jjh1lpzhy44p3dq3fiw75iwr1w2vfn",
"registry+https://github.com/rust-lang/crates.io-index#siphasher@0.2.3": "1b53m53l24lyhr505lwqzrpjyq5qfnic71mynrcfvm43rybf938b",
"registry+https://github.com/rust-lang/crates.io-index#siphasher@1.0.1": "17f35782ma3fn6sh21c027kjmd227xyrx06ffi8gw4xzv9yry6an",
"registry+https://github.com/rust-lang/crates.io-index#siphasher@0.3.11": "03axamhmwsrmh0psdw3gf7c0zc4fyl5yjxfifz9qfka6yhkqid9q",
"registry+https://github.com/rust-lang/crates.io-index#slab@0.4.9": "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg",
"registry+https://github.com/rust-lang/crates.io-index#smallvec@1.13.2": "0rsw5samawl3wsw6glrsb127rx6sh89a8wyikicw6dkdcjd1lpiw",
"registry+https://github.com/rust-lang/crates.io-index#snowflake@1.3.0": "1wadr7bxdxbmkbqkqsvzan6q1h3mxqpxningi3ss3v9jaav7n817",
"registry+https://github.com/rust-lang/crates.io-index#socket2@0.5.8": "1s7vjmb5gzp3iaqi94rh9r63k9cj00kjgbfn7gn60kmnk6fjcw69",
"registry+https://github.com/rust-lang/crates.io-index#socket2@0.5.7": "070r941wbq76xpy039an4pyiy3rfj7mp7pvibf1rcri9njq5wc6f",
"registry+https://github.com/rust-lang/crates.io-index#spin@0.9.8": "0rvam5r0p3a6qhc18scqpvpgb3ckzyqxpgdfyjnghh8ja7byi039",
"registry+https://github.com/rust-lang/crates.io-index#spki@0.7.3": "17fj8k5fmx4w9mp27l970clrh5qa7r5sjdvbsln987xhb34dc7nr",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-core@0.8.3": "1q31dawr61wc6q2f12my4fw082mbv8sxwz1082msjsk76rlpn03a",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros-core@0.8.3": "1bg7sn6l8dc4pzrqx2dwc3sp7dbn97msfqahpycnl55bqnn917sf",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros@0.8.3": "047k67sylscv0gdhwwqrn0s33jy1mvq8rmqq6s8fygv4g2ny44ii",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-mysql@0.8.3": "0czjzzjm2y6lkhxvvzrzwgp0pmlhymcnym20hn9n9kh01s7jfq25",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-postgres@0.8.3": "04wnjl51kfx0qbfsfmhqdshpmw32vzz2p8dksmj6gvb3ydbqmff5",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-sqlite@0.8.3": "0h05ca26g428h4337k4nm0ww75bcdkiqzp883m7fc92v78fsfp7q",
"registry+https://github.com/rust-lang/crates.io-index#sqlx@0.8.3": "0pvlpq0plgyxf5kikcv786pf0pjv8dx5shlvz72l510d7hxyf424",
"registry+https://github.com/rust-lang/crates.io-index#stable_deref_trait@1.2.0": "1lxjr8q2n534b2lhkxd6l6wcddzjvnksi58zv11f9y0jjmr15wd8",
"registry+https://github.com/rust-lang/crates.io-index#sqlformat@0.2.6": "14470h40gn0f6jw9xxzbpwh5qy1fgvkhkfz8xjyzgi0cvf9kmfkv",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-core@0.7.4": "1xiyr35dq10sf7lq00291svcj9wbaaz1ihandjmrng9a6jlmkfi4",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros-core@0.7.4": "1j7k0fw7n6pgabqnj6cbp8s3rmd3yvqr4chjj878cvd1m99yycsq",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros@0.7.4": "09rih250868nfkax022y5dyk24a7qfw6scjy3sgalbzb8lihx92f",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-mysql@0.7.4": "066lxhb80xgb8r5m2yy3a7ydjvp0b6wsk9s7whwfa83d46817lqy",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-postgres@0.7.4": "0zjp30wj4n2f25dnb32vsg6jfpa3gw6dmfd0i5pr4kw91fw4x0kw",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-sqlite@0.7.4": "1ap0bb2hazbrdgd7mhnckdg9xcchx0k094di9gnhpnhlhh5fyi5j",
"registry+https://github.com/rust-lang/crates.io-index#sqlx@0.7.4": "1ahadprvyhjraq0c5712x3kdkp1gkwfm9nikrmcml2h03bzwr8n9",
"registry+https://github.com/rust-lang/crates.io-index#stringprep@0.1.5": "1cb3jis4h2b767csk272zw92lc6jzfzvh8d6m1cd86yqjb9z6kbv",
"registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1": "0kzvqlw8hxqb7y598w1s0hxlnmi84sg5vsipp3yg5na5d1rvba3x",
"registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1": "14ijxaymghbl1p0wql9cib5zlwiina7kall6w7g89csprkgbvhhk",
"registry+https://github.com/rust-lang/crates.io-index#syn@1.0.109": "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj",
"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.96": "102wk3cgawimi3i0q3r3xw3i858zkyingg6y7gsxfy733amsvl6m",
"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.79": "147mk4sgigmvsb9l8qzj199ygf0fgb0bphwdsghn8205pz82q4w9",
"registry+https://github.com/rust-lang/crates.io-index#sync_wrapper@0.1.2": "0q01lyj0gr9a93n10nxsn8lwbzq97jqd6b768x17c8f7v7gccir0",
"registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1": "0wc9f002ia2zqcbj0q2id5x6n7g1zjqba7qkg2mr0qvvmdk7dby8",
"registry+https://github.com/rust-lang/crates.io-index#system-configuration-sys@0.5.0": "1jckxvdr37bay3i9v52izgy52dg690x5xfg3hd394sv2xf4b2px7",
"registry+https://github.com/rust-lang/crates.io-index#system-configuration@0.5.1": "1rz0r30xn7fiyqay2dvzfy56cvaa3km74hnbz2d72p97bkf3lfms",
"registry+https://github.com/rust-lang/crates.io-index#system-deps@6.2.2": "0j93ryw031n3h8b0nfpj5xwh3ify636xmv8kxianvlyyipmkbrd3",
"registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.12.16": "1cg3bnx1gdkdr5hac1hzxy64fhw4g7dqkd0n3dxy5lfngpr1mi31",
"registry+https://github.com/rust-lang/crates.io-index#tempdir@0.3.7": "1n5n86zxpgd85y0mswrp5cfdisizq2rv3la906g6ipyc03xvbwhm",
"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.17.1": "0c52ggq5vy5mzgk5ly36cgzs1cig3cv6r1jarijmzxgkn6na1r92",
"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.13.0": "0nyagmbd4v5g6nzfydiihcn6l9j1w9bxgzyca5lyzgnhcbyckwph",
"registry+https://github.com/rust-lang/crates.io-index#termcolor@1.4.1": "0mappjh3fj3p2nmrg4y7qv94rchwi9mzmgmfflr8p2awdj7lyy86",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.69": "1h84fmn2nai41cxbhk6pqf46bxqq1b344v8yz089w1chzi76rvjg",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.11": "1hkkn7p2y4cxbffcrprybkj0qy1rl1r6waxmxqvr764axaxc3br6",
"registry+https://github.com/rust-lang/crates.io-index#thiserror@1.0.69": "0lizjay08agcr5hs9yfzzj6axs53a2rgx070a1dsi3jpkcrzbamn",
"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.11": "1z0649rpa8c2smzx129bz4qvxmdihj30r2km6vfpcv9yny2g4lnl",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.64": "1hvzmjx9iamln854l74qyhs0jl2pg3hhqzpqm9p8gszmf9v4x408",
"registry+https://github.com/rust-lang/crates.io-index#thiserror@1.0.64": "114s8lmssxl0c2480s671am88vzlasbaikxbvfv8pyqrq6mzh2nm",
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.6.1": "0ds48vs919ccxa3fv1www7788pzkvpg434ilqkq7sjb5dmqg8lws",
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.9.1": "0ghyxlz566dzc3scvgmzys11dhq2ri77kb8sznjakijlxby104xs",
"registry+https://github.com/rust-lang/crates.io-index#time-core@0.1.2": "1wx3qizcihw6z151hywfzzyd1y5dl804ydyxci6qm07vbakpr4pg",
"registry+https://github.com/rust-lang/crates.io-index#time-macros@0.2.19": "1pl558z26pp342l5y91n6dxb60xwhar975wk6jc4npiygq0ycd18",
"registry+https://github.com/rust-lang/crates.io-index#time-macros@0.2.18": "1kqwxvfh2jkpg38fy673d6danh1bhcmmbsmffww3mphgail2l99z",
"registry+https://github.com/rust-lang/crates.io-index#time@0.1.45": "0nl0pzv9yf56djy8y5dx25nka5pr2q1ivlandb3d24pksgx7ly8v",
"registry+https://github.com/rust-lang/crates.io-index#time@0.3.37": "08bvydyc14plkwhchzia5bcdbmm0mk5fzilsdpjx06w6hf48drrm",
"registry+https://github.com/rust-lang/crates.io-index#time@0.3.36": "11g8hdpahgrf1wwl2rpsg5nxq3aj7ri6xr672v4qcij6cgjqizax",
"registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6": "0bxqaw7z8r2kzngxlzlgvld1r6jbnwyylyvyjbv1q71rvgaga5wi",
"registry+https://github.com/rust-lang/crates.io-index#tinyvec@1.8.1": "1s41rv7n39sjsxz3kd3d4adw45ndkxz1d18rfbz2wd7s9n8bhb82",
"registry+https://github.com/rust-lang/crates.io-index#tinyvec@1.8.0": "0f5rf6a2wzyv6w4jmfga9iw7rp9fp5gf4d604xgjsf3d9wgqhpj4",
"registry+https://github.com/rust-lang/crates.io-index#tinyvec_macros@0.1.1": "081gag86208sc3y6sdkshgw3vysm5d34p431dzw0bshz66ncng0z",
"registry+https://github.com/rust-lang/crates.io-index#tokio-macros@2.5.0": "1f6az2xbvqp7am417b78d1za8axbvjvxnmkakz9vr8s52czx81kf",
"registry+https://github.com/rust-lang/crates.io-index#tokio-macros@2.4.0": "0lnpg14h1v3fh2jvnc8cz7cjf0m7z1xgkwfpcyy632g829imjgb9",
"registry+https://github.com/rust-lang/crates.io-index#tokio-native-tls@0.3.1": "1wkfg6zn85zckmv4im7mv20ca6b1vmlib5xwz9p7g19wjfmpdbmv",
"registry+https://github.com/rust-lang/crates.io-index#tokio-stream@0.1.17": "0ix0770hfp4x5rh5bl7vsnr3d4iz4ms43i522xw70xaap9xqv9gc",
"registry+https://github.com/rust-lang/crates.io-index#tokio-stream@0.1.16": "1wc65gprcsyzqlr0k091glswy96kph90i32gffi4ksyh03hnqkjg",
"registry+https://github.com/rust-lang/crates.io-index#tokio-tungstenite@0.21.0": "0f5wj0crsx74rlll97lhw0wk6y12nhdnqvmnjx002hjn08fmcfy8",
"registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.13": "0y0h10a52c7hrldmr3410bp7j3fadq0jn9nf7awddgd2an6smz6p",
"registry+https://github.com/rust-lang/crates.io-index#tokio@1.43.0": "17pdm49ihlhfw3rpxix3kdh2ppl1yv7nwp1kxazi5r1xz97zlq9x",
"registry+https://github.com/rust-lang/crates.io-index#toml@0.8.20": "0j012b37iz1mihksr6a928s6dzszxvblzg3l5wxp7azzsv6sb1yd",
"registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.8": "0hgv7v9g35d7y9r2afic58jvlwnf73vgd1mz2k8gihlgrf73bmqd",
"registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.12": "0spc0g4irbnf2flgag22gfii87avqzibwfm0si0d1g0k9ijw7rv1",
"registry+https://github.com/rust-lang/crates.io-index#tokio@1.40.0": "166rllhfkyqp0fs7sxn6crv74iizi4wzd3cvxkcpmlk52qip1c72",
"registry+https://github.com/rust-lang/crates.io-index#toml@0.8.2": "0g9ysjaqvm2mv8q85xpqfn7hi710hj24sd56k49wyddvvyq8lp8q",
"registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.3": "0jsy7v8bdvmzsci6imj8fzgd255fmy5fzp6zsri14yrry7i77nkw",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.19.15": "08bl7rp5g6jwmfpad9s8jpw8wjrciadpnbaswgywpr9hv9qbfnqv",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.20.2": "0f7k5svmxw98fhi28jpcyv7ldr2s3c867pjbji65bdxjpd44svir",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.24": "0x0lgp70x5cl9nla03xqs5vwwwlrwmd0djkdrp3h3lpdymgpkd0p",
"registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.3": "1hzfkvkci33ra94xjx64vv3pp0sq346w06fpkcdwjcid7zhvdycd",
"registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.28": "0v92l9cxs42rdm4m5hsa8z7ln1xsiw1zc2iil8c6k7lzq0jf2nir",
"registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.33": "170gc7cxyjx824r9kr17zc9gvzx89ypqfdzq259pr56gg5bwjwp6",
"registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.41": "1l5xrzyjfyayrwhvhldfnwdyligi1mpqm8mzbi2m1d6y6p2hlkkq",
"registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.27": "1rvb5dn9z6d0xdj14r403z0af0bbaqhg02hq4jc97g5wds6lqw1l",
"registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.32": "0m5aglin3cdwxpvbg6kz0r9r0k31j48n0kcfwsp6l49z26k3svf0",
"registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.40": "1vv48dac9zgj9650pg2b4d0j3w6f3x9gbggf43scq5hrlysklln3",
"registry+https://github.com/rust-lang/crates.io-index#traitobject@0.1.0": "0yb0n8822mr59j200fyr2fxgzzgqljyxflx9y8bdy3rlaqngilgg",
"registry+https://github.com/rust-lang/crates.io-index#try-lock@0.2.5": "0jqijrrvm1pyq34zn1jmy2vihd4jcrjlvsh4alkjahhssjnsn8g4",
"registry+https://github.com/rust-lang/crates.io-index#tungstenite@0.21.0": "1qaphb5kgwgid19p64grhv2b9kxy7f1059yy92l9kwrlx90sdwcy",
@ -430,29 +414,29 @@
"registry+https://github.com/rust-lang/crates.io-index#typemap@0.3.3": "1xm1gbvz9qisj1l6d36hrl9pw8imr8ngs6qyanjnsad3h0yfcfv5",
"registry+https://github.com/rust-lang/crates.io-index#typenum@1.17.0": "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2",
"registry+https://github.com/rust-lang/crates.io-index#typeshare-annotation@1.0.4": "0kx38ah6638pkqq5cac7nmvbg6x43v7fj5jgibla4lj8fv1dc5d6",
"registry+https://github.com/rust-lang/crates.io-index#typeshare@1.0.4": "1svc92lg35r12mqdpbs4wbkw7g72v2302niyw5v1w290250hzghr",
"registry+https://github.com/rust-lang/crates.io-index#typeshare@1.0.3": "11riglm8incm0vq7ciyd907w1sc6frfn7h7ab0yp8bkcnycp7w84",
"registry+https://github.com/rust-lang/crates.io-index#unarray@0.1.4": "154smf048k84prsdgh09nkm2n0w0336v84jd4zikyn6v6jrqbspa",
"registry+https://github.com/rust-lang/crates.io-index#unic-langid-impl@0.9.5": "1rckyn5wqd5h8jxhbzlbbagr459zkzg822r4k5n30jaryv0j4m0a",
"registry+https://github.com/rust-lang/crates.io-index#unic-langid@0.9.5": "0i2s024frmpfa68lzy8y8vnb1rz3m9v0ga13f7h2afx7f8g9vp93",
"registry+https://github.com/rust-lang/crates.io-index#unicase@1.4.2": "0cwazh4qsmm9msckjk86zc1z35xg7hjxjykrgjalzdv367w6aivz",
"registry+https://github.com/rust-lang/crates.io-index#unicase@2.8.1": "0fd5ddbhpva7wrln2iah054ar2pc1drqjcll0f493vj3fv8l9f3m",
"registry+https://github.com/rust-lang/crates.io-index#unicode-bidi@0.3.18": "1xcxwbsqa24b8vfchhzyyzgj0l6bn51ib5v8j6krha0m77dva72w",
"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.14": "10ywa1pg0glgkr4l3dppjxizr9r2b7im0ycbfa0137l69z5fdfdd",
"registry+https://github.com/rust-lang/crates.io-index#unicase@2.7.0": "12gd74j79f94k4clxpf06l99wiv4p30wjr0qm04ihqk9zgdd9lpp",
"registry+https://github.com/rust-lang/crates.io-index#unicode-bidi@0.3.17": "14vqdsnrm3y5anj6h5zz5s32w88crraycblb88d9k23k9ns7vcas",
"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.13": "1zm1xylzsdfvm2a5ib9li3g5pp7qnkv4amhspydvgbmd9k6mc6z9",
"registry+https://github.com/rust-lang/crates.io-index#unicode-normalization@0.1.24": "0mnrk809z3ix1wspcqy97ld5wxdb31f3xz6nsvg5qcv289ycjcsh",
"registry+https://github.com/rust-lang/crates.io-index#unicode-properties@0.1.3": "1l3mbgzwz8g14xcs09p4ww3hjkjcf0i1ih13nsg72bhj8n5jl3z7",
"registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0": "14qla2jfx74yyb9ds3d2mpwpa4l4lzb9z57c6d2ba511458z5k7n",
"registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14": "1bzn2zv0gp8xxbxbhifw778a7fc93pa6a1kj24jgg9msj07f7mkx",
"registry+https://github.com/rust-lang/crates.io-index#unicode_categories@0.1.1": "0kp1d7fryxxm7hqywbk88yb9d1avsam9sg76xh36k5qx2arj9v1r",
"registry+https://github.com/rust-lang/crates.io-index#unsafe-any@0.4.2": "0zwwphsqkw5qaiqmjwngnfpv9ym85qcsyj7adip9qplzjzbn00zk",
"registry+https://github.com/rust-lang/crates.io-index#url@1.7.2": "0nim1c90mxpi9wgdw2xh8dqd72vlklwlzam436akcrhjac6pqknx",
"registry+https://github.com/rust-lang/crates.io-index#url@2.5.4": "0q6sgznyy2n4l5lm16zahkisvc9nip9aa5q1pps7656xra3bdy1j",
"registry+https://github.com/rust-lang/crates.io-index#url@2.5.2": "0v2dx50mx7xzl9454cl5qmpjnhkbahmn59gd3apyipbgyyylsy12",
"registry+https://github.com/rust-lang/crates.io-index#urlencoding@2.1.3": "1nj99jp37k47n0hvaz5fvz7z6jd0sb4ppvfy3nphr1zbnyixpy6s",
"registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6": "1a9ns3fvgird0snjkd3wbdhwd3zdpc2h5gpyybrfr6ra5pkqxk09",
"registry+https://github.com/rust-lang/crates.io-index#utf16_iter@1.0.5": "0ik2krdr73hfgsdzw0218fn35fa09dg2hvbi1xp3bmdfrp9js8y8",
"registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4": "1gmna9flnj8dbyd8ba17zigrp9c4c3zclngf5lnb5yvz1ri41hdn",
"registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.2": "088807qwjq46azicqwbhlmzwrbkz7l4hpw43sdkdyyk524vdxaq6",
"registry+https://github.com/rust-lang/crates.io-index#uuid@0.4.0": "0cdj2v6v2yy3zyisij69waksd17cyir1n58kwyk1n622105wbzkw",
"registry+https://github.com/rust-lang/crates.io-index#uuid@0.8.2": "1dy4ldcp7rnzjy56dxh7d2sgrcvn4q77y0a8r0a48946h66zjp5w",
"registry+https://github.com/rust-lang/crates.io-index#uuid@1.12.0": "1i2i7ar5651d58ip1l8cghg3y60pn0rqmssvw6lm8d4s3xc1hh3l",
"registry+https://github.com/rust-lang/crates.io-index#value-bag@1.10.0": "1lnsixdpi1ldms1adxyafyx7lyrqxhhskgwrjckmml6majmc9x1y",
"registry+https://github.com/rust-lang/crates.io-index#uuid@1.10.0": "0503gvp08dh5mnm3f0ffqgisj6x3mbs53dmnn1lm19pga43a1pw1",
"registry+https://github.com/rust-lang/crates.io-index#value-bag@1.9.0": "00aij8p1n7vcggkb9nxpwx9g5nqzclrf7prd1wpi9c3sscvw312s",
"registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15": "09i4nf5y8lig6xgj3f7fyrvzd3nlaw4znrihw8psidvv5yk4xkdc",
"registry+https://github.com/rust-lang/crates.io-index#version-compare@0.2.0": "12y9262fhjm1wp0aj3mwhads7kv0jz8h168nn5fb8b43nwf9abl5",
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.1.5": "1pf91pvj8n6akh7w6j5ypka6aqz08b3qpzgs0ak2kjf4frkiljwi",
@ -462,15 +446,14 @@
"registry+https://github.com/rust-lang/crates.io-index#warp@0.3.7": "07137zd13lchy5hxpspd0hs6sl19b0fv2zc1chf02nwnzw1d4y23",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.10.0+wasi-snapshot-preview1": "07y3l8mzfzzz4cj09c8y90yak4hpsi9g7pllyzpr6xvwrabka50s",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.11.0+wasi-snapshot-preview1": "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.13.3+wasi-0.2.2": "1lnapbvdcvi3kc749wzqvwrpd483win2kicn1faa4dja38p6v096",
"registry+https://github.com/rust-lang/crates.io-index#wasite@0.1.0": "0nw5h9nmcl4fyf4j5d4mfdjfgvwi1cakpi349wc4zrr59wxxinmq",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.100": "1ihbf1hq3y81c4md9lyh6lcwbx6a5j0fw4fygd423g62lm8hc2ig",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.50": "0q8ymi6i9r3vxly551dhxcyai7nc491mspj0j1wbafxwq074fpam",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.100": "1plm8dh20jg2id0320pbmrlsv6cazfv6b6907z19ys4z1jj7xs4a",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.100": "01xls2dvzh38yj17jgrbiib1d3nyad7k2yw9s0mpklwys333zrkz",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.100": "0gffxvqgbh9r9xl36gprkfnh3w9gl8wgia6xrin7v11sjcxxf18s",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.100": "1x8ymcm6yi3i1rwj78myl1agqv2m86i648myy3lc97s9swlqkp0y",
"registry+https://github.com/rust-lang/crates.io-index#web-sys@0.3.77": "1lnmc1ffbq34qw91nndklqqm75rasaffj2g4f8h1yvqqz4pdvdik",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.93": "0yypblaf94rdgqs5xw97499xfwgs1096yx026d6h88v563d9dqwx",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.43": "1vf8kmaj95xn5893y1bdlav47y5niq85q5bms9pfj8d6cc7k1sb1",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.93": "0dp8w6jmw44srym6l752nkr3hkplyw38a2fxz5f3j1ch9p3l1hxg",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.93": "1kycd1xfx4d9xzqknvzbiqhwb5fzvjqrrn88x692q1vblj8lqp2q",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.93": "1104bny0hv40jfap3hp8jhs0q4ya244qcrvql39i38xlghq0lan6",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.93": "1dfr7pka5kwvky2fx82m9d060p842hc5fyyw8igryikcdb0xybm8",
"registry+https://github.com/rust-lang/crates.io-index#web-sys@0.3.70": "1h1jspkqnrx1iybwhwhc3qq8c8fn4hy5jcf0wxjry4mxv6pymz96",
"registry+https://github.com/rust-lang/crates.io-index#weezl@0.1.8": "10lhndjgs6y5djpg3b420xngcr6jkmv70q8rb1qcicbily35pa2k",
"registry+https://github.com/rust-lang/crates.io-index#whoami@1.5.2": "0vdvm6sga4v9515l6glqqfnmzp246nq66dd09cw5ri4fyn3mnb9p",
"registry+https://github.com/rust-lang/crates.io-index#winapi-i686-pc-windows-gnu@0.4.0": "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc",
@ -499,21 +482,10 @@
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.48.5": "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.52.6": "1v7rb5cibyzx8vak29pdrk8nx9hycsjs4w0jgms08qk49jl6v7sq",
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.5.40": "0xk8maai7gyxda673mmw3pj1hdizy5fpi7287vaywykkk19sk4zm",
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.3": "1c9bmhpdwbdmll6b4l6skabz0296dchnmnxw84hh2y3ggyllwzqf",
"registry+https://github.com/rust-lang/crates.io-index#winreg@0.50.0": "1cddmp929k882mdh6i9f2as848f13qqna6czwsqzkh1pqnr5fkjj",
"registry+https://github.com/rust-lang/crates.io-index#wit-bindgen-rt@0.33.0": "0g4lwfp9x6a2i1hgjn8k14nr4fsnpd5izxhc75zpi2s5cvcg6s1j",
"registry+https://github.com/rust-lang/crates.io-index#write16@1.0.0": "0dnryvrrbrnl7vvf5vb1zkmwldhjkf2n5znliviam7bm4900z2fi",
"registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5": "0lawr6y0bwqfyayf3z8zmqlhpnzhdx0ahs54isacbhyjwa7g778y",
"registry+https://github.com/rust-lang/crates.io-index#xml-rs@0.8.25": "1i73ajf6scni5bi1a51r19xykgrambdx5fkks0fyg5jqqbml1ff5",
"registry+https://github.com/rust-lang/crates.io-index#yansi-term@0.1.2": "1w8vjlvxba6yvidqdvxddx3crl6z66h39qxj8xi6aqayw2nk0p7y",
"registry+https://github.com/rust-lang/crates.io-index#yoke-derive@0.7.5": "0m4i4a7gy826bfvnqa9wy6sp90qf0as3wps3wb0smjaamn68g013",
"registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.5": "0h3znzrdmll0a7sglzf9ji0p5iqml11wrj1dypaf6ad6kbpnl3hj",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.7.35": "0gnf2ap2y92nwdalzz3x7142f2b83sni66l39vxp2ijd6j080kzs",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.7.35": "1w36q7b9il2flg0qskapgi9ymgg7p985vniqd09vi0mwib8lz6qv",
"registry+https://github.com/rust-lang/crates.io-index#zerofrom-derive@0.1.5": "022q55phhb44qbrcfbc48k0b741fl8gnazw3hpmmndbx5ycfspjr",
"registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.5": "0bnd8vjcllzrvr3wvn8x14k2hkrpyy1fm3crkn2y3plmr44fxwyg",
"registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.1": "1pjdrmjwmszpxfd7r860jx54cyk94qk59x13sc307cvr5256glyf",
"registry+https://github.com/rust-lang/crates.io-index#zerovec-derive@0.10.3": "1ik322dys6wnap5d3gcsn09azmssq466xryn5czfm13mn7gsdbvf",
"registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4": "0yghix7n3fjfdppwghknzvx9v8cf826h2qal5nqvy8yzg4yqjaxa",
"registry+https://github.com/rust-lang/crates.io-index#zune-inflate@0.2.54": "00kg24jh3zqa3i6rg6yksnb71bch9yi1casqydl00s7nw8pk7avk"
}

View File

@ -12,9 +12,9 @@ use std::{
use cairo::{Context, Rectangle};
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text};
use glib::Object;
use glib::{GString, Object};
use gtk::{
glib::{self},
glib::{self, Propagation},
prelude::*,
subclass::prelude::*,
EventControllerKey,
@ -40,7 +40,6 @@ struct Step {
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Default)]
struct Script(Vec<Step>);
impl Script {
@ -52,7 +51,7 @@ impl Script {
Ok(Self(script))
}
fn iter(&self) -> impl Iterator<Item = &'_ Step> {
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Step> {
self.0.iter()
}
@ -61,6 +60,11 @@ impl Script {
}
}
impl Default for Script {
fn default() -> Self {
Self(vec![])
}
}
impl Index<usize> for Script {
type Output = Step;
@ -94,11 +98,11 @@ impl Animation for Fade {
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 alpha = alpha_rate * frames as f64;
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);
let _ = context.move_to(0., text_display.extents().height());
let _ = context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha);
text_display.draw();
}
}
@ -122,16 +126,16 @@ impl Animation for CrossFade {
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 alpha = alpha_rate * frames as f64;
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);
let _ = context.move_to(0., text_display.extents().height());
let _ = context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, 1. - alpha);
text_display.draw();
let text_display = Text::new(self.new_text.clone(), context, 64., width);
context.move_to(0., text_display.extents().height());
context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha);
let _ = context.move_to(0., text_display.extents().height());
let _ = context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha);
text_display.draw();
}
}
@ -159,7 +163,9 @@ impl Default for CyberScreenState {
impl CyberScreenState {
fn new(script: Script) -> CyberScreenState {
CyberScreenState { script, ..Default::default() }
let mut s = CyberScreenState::default();
s.script = script;
s
}
fn next_page(&mut self) -> Box<dyn Animation> {
@ -254,7 +260,7 @@ impl CyberScreen {
let s = s.clone();
move |_, context, width, height| {
let now = Instant::now();
context.set_source_rgb(0., 0., 0.);
let _ = context.set_source_rgb(0., 0., 0.);
let _ = context.paint();
let pen = GlowPen::new(width, height, 2., 8., (0.7, 0., 1.));
@ -287,7 +293,7 @@ impl CyberScreen {
let _ = context.set_source(tracery);
let _ = context.paint();
let animations = s.imp().animations.borrow_mut();
let mut animations = s.imp().animations.borrow_mut();
let lr_margin = 50.;
let max_width = width as f64 - lr_margin * 2.;

View File

@ -7,7 +7,6 @@ license = "GPL-3.0-only"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std = "1.13.0"
cairo-rs = { version = "0.18" }
cyberpunk = { path = "../cyberpunk" }
gio = { version = "0.18" }

View File

@ -1,5 +1,5 @@
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};
@ -497,7 +497,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 +529,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");
splash.set_state(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());
}
}
});

View File

@ -274,7 +274,7 @@ impl<'a> Text<'a> {
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);
let _ = self.context.show_text(&line);
}
}
}
@ -294,7 +294,7 @@ fn word_wrap(content: String, context: &Context, max_width: f64) -> Vec<String>
lines.push(line.clone());
}
}
if !line.is_empty() {
if line.len() > 0 {
lines.push(line);
}
lines

View File

@ -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()

View File

@ -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)

View File

@ -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![],

View File

@ -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}}";

View File

@ -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",

View File

@ -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 {

View File

@ -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

View File

@ -1,11 +0,0 @@
version: '3'
tasks:
build:
cmds:
- cargo build
lint:
cmds:
- cargo watch -x clippy

View File

@ -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()
)
}
}

View File

@ -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,

View File

@ -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));

View File

@ -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),
}

View File

@ -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),

View File

@ -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(&gtk::NoSelection::new(Some(model))));

View File

@ -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,
};

42
flake.lock generated
View File

@ -1,30 +1,15 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1739936662,
"narHash": "sha256-x4syUjNUuRblR07nDPeLDP7DpphaBVbUaSoeZkFbGSk=",
"owner": "ipetkov",
"repo": "crane",
"rev": "19de14aaeb869287647d9461cbd389187d8ecdb7",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
@ -35,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": {
@ -65,7 +50,6 @@
},
"root": {
"inputs": {
"crane": "crane",
"nixpkgs": "nixpkgs",
"typeshare": "typeshare",
"unstable": "unstable"
@ -92,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": {

View File

@ -2,13 +2,12 @@
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";
crane.url = "github:ipetkov/crane";
};
outputs = { self, nixpkgs, unstable, typeshare, crane, ... }:
outputs = { self, nixpkgs, unstable, typeshare, ... }:
let
version = builtins.string 0 8 self.lastModifiedDate;
supportedSystems = [ "x86_64-linux" ];
@ -23,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
@ -47,7 +44,6 @@
pkgs.sqlx-cli
pkgs.udev
pkgs.wasm-pack
pkgs.go-task
typeshare.packages."x86_64-linux".default
pkgs.nodePackages_latest.typescript-language-server
];
@ -58,8 +54,6 @@
packages."x86_64-linux" =
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
craneLib = crane.mkLib pkgs;
src = craneLib.cleanCargoSource ./.;
gtkNativeInputs = [
pkgs.pkg-config
@ -90,13 +84,8 @@
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 = craneLib.buildPackage {
pname = "l10n-db";
cargoExtraArgs = "-p l10n-db";
src = ./.;
};
otg-gtk = cargo_nix.workspaceMembers.otg-gtk.build;
all = pkgs.symlinkJoin {
@ -105,9 +94,8 @@
cyber-slides
cyberpunk-splash
dashboard
# file-service
file-service
fitnesstrax
l10n-db
otg-gtk
];
};

View File

@ -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");

View File

@ -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,
*/
}
}
}

View File

@ -27,12 +27,12 @@ impl State {
fn add_audio(&self, device: String) {
let mut st = self.internal.write().unwrap();
st.device_list.push(device);
(*st).device_list.push(device);
}
fn audio_devices(&self) -> Vec<String> {
let st = self.internal.read().unwrap();
st.device_list.clone()
(*st).device_list.clone()
}
}

View File

@ -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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

34
ifc/src/static/index.html Normal file
View 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
View 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>

View 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
View 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);
}

22
ifc/src/today.rs Normal file
View File

@ -0,0 +1,22 @@
/*
Copyright 2020-2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of the Luminescent Dreams Tools.
Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/
extern crate chrono;
extern crate chrono_tz;
extern crate ifc as IFC;
use chrono::{Datelike, Utc};
fn main() {
let d = IFC::IFC::from(Utc::today());
println!("{} {}, {}", d.month(), d.day(), d.year());
}

119
ifc/src/web.rs Normal file
View File

@ -0,0 +1,119 @@
/*
Copyright 2020-2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of the Luminescent Dreams Tools.
Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/
use chrono::{Datelike, Utc};
use ifc as IFC;
use iron::headers;
use iron::middleware::Handler;
use iron::modifiers::Header;
use iron::prelude::*;
use iron::status;
use mustache::{compile_str, Template};
use router::Router;
use serde::Serialize;
pub const STYLES: &'static str = include_str!("static/styles.css");
pub const INDEX: &'static str = include_str!("static/index.html");
pub struct IndexHandler {
pub template: Template,
}
#[derive(Serialize)]
pub struct DayEntry {
day: u8,
highlight: String,
}
#[derive(Serialize)]
pub struct Week {
days: Vec<DayEntry>,
}
impl Week {
fn new(start_day: u8, today: u8) -> Week {
Week {
days: (1..8)
.map(|d| DayEntry {
day: d + start_day,
highlight: if today == (d + start_day) {
String::from("today")
} else {
String::from("")
},
})
.collect(),
}
}
}
#[derive(Serialize)]
pub struct IndexTemplateParams {
month: String,
year: i32,
weeks: Vec<Week>,
}
impl IndexTemplateParams {
fn new(date: IFC::IFC) -> IndexTemplateParams {
let day = date.day() as u8;
IndexTemplateParams {
month: String::from(IFC::Month::from(date.month())),
year: date.year(),
weeks: (0..4).map(|wn| Week::new(wn * 7, day)).collect(),
}
}
}
impl Handler for IndexHandler {
fn handle(&self, _: &mut Request) -> IronResult<Response> {
let d = IFC::IFC::from(Utc::today());
Ok(Response::with((
status::Ok,
Header(headers::ContentType(iron::mime::Mime(
iron::mime::TopLevel::Text,
iron::mime::SubLevel::Html,
vec![],
))),
self.template
.render_to_string(&IndexTemplateParams::new(d))
.expect("the template to render"),
)))
}
}
fn css(_: &mut Request) -> IronResult<Response> {
Ok(Response::with((
status::Ok,
Header(headers::ContentType(iron::mime::Mime(
iron::mime::TopLevel::Text,
iron::mime::SubLevel::Css,
vec![],
))),
STYLES,
)))
}
fn main() {
let mut router = Router::new();
router.get(
"/",
IndexHandler {
template: compile_str(INDEX).expect("the template to compile"),
},
"index",
);
router.get("/css", css, "styles");
Iron::new(router).http("127.0.0.1:3000").unwrap();
}

View File

@ -1,24 +0,0 @@
[package]
name = "l10n-db"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = { version = "0.4.39", features = ["serde"] }
clap = { version = "4.5.30", features = ["derive"] }
icu_locid = { version = "1.5.0", features = ["serde"] }
serde = { version = "1.0.218", features = ["derive"] }
serde_json = "1.0.139"
tempfile = "3.17.1"
thiserror = "2.0.11"
toml = "0.8.20"
xml-rs = "0.8.25"
# [lib]
# name = "l10n_db"
# path = "src/lib.rs"
#
# [[bin]]
# name = "l10n-db"
# path = "src/main.rs"

View File

@ -1,8 +0,0 @@
db_path = "./i18n"
base_locale = "en"
locales = [
"en",
"eo",
"de",
"es",
]

View File

@ -1,22 +0,0 @@
key = "SaveSettings"
description = "This is a label on a button which will save the settings when clicked"
[variants.eo]
locale = "eo"
content = "Konservi Agordojn"
modified = "2025-02-24T19:32:11.246639077Z"
[variants.es]
locale = "es"
content = "Guardar Configuraciones"
modified = "2025-02-24T19:33:23.861329923Z"
[variants.en]
locale = "en"
content = "Save Settings"
modified = "2025-02-22T23:44:18.874218939Z"
[variants.de]
locale = "de"
content = "Einstellungen Speichern"
modified = "2025-02-24T19:33:19.516005843Z"

View File

@ -1,22 +0,0 @@
key = "TimeDistance"
description = "A summary of a workout or many workouts that involve a time and a distance"
[variants.es]
locale = "es"
content = "{distance} de {activity} en {hours, plural, one {}=1 {{hours} hora} other {{hours} horas}} y {minutes, plural, one {}=1 {{minutes} minuto} other {{minutes} minutos}}"
modified = "2025-02-24T19:33:23.861604738Z"
[variants.eo]
locale = "eo"
content = "{distance} de {activity} en {hours, plural, =1 {{hours} horo} other {{hours} horoj}} {minutes, plural, =1 {{minutes} minuto} other {{minutes} minutoj}}"
modified = "2025-02-24T19:32:11.246943602Z"
[variants.en]
locale = "en"
content = "{distance} of {activity} in {hours, plural, =1 {{hours} hour} other {{hours} hours}} and {minutes, plural, =1 {{minutes} minute} other {{minutes} minutes}}"
modified = "2025-02-24T14:09:17.361641899Z"
[variants.de]
locale = "de"
content = "{distance} von {activity} in {hours, plural, one {}=1 {{hours} Stunde} other {{hours} Stunden}} und {minutes, plural, one {}=1 {{minutes} Minute} other {{minutes} Minuten}}"
modified = "2025-02-24T19:33:19.516210807Z"

View File

@ -1,22 +0,0 @@
key = "Welcome"
description = "This is a welcome content that will be shown on first app opening, before configuration."
[variants.en]
locale = "en"
content = "Welcome to FitnessTrax, the privacy-centered fitness tracker"
modified = "2025-02-25T02:12:25.757240004Z"
[variants.eo]
locale = "eo"
content = "Bonvenon al FitnessTrax"
modified = "2025-02-24T19:32:11.246407627Z"
[variants.es]
locale = "es"
content = "Bienvenido a FitnessTrax"
modified = "2025-02-24T19:33:23.861143003Z"
[variants.de]
locale = "de"
content = "Willkommen bei FitnessTrax"
modified = "2025-02-24T19:33:19.515861453Z"

View File

@ -1,143 +0,0 @@
use std::{fmt, path::PathBuf};
use clap::{Parser, Subcommand};
use icu_locid::{langid, LanguageIdentifier};
use l10n_db::{
self, js, read_file,
xliff::{self, import_file},
Bundle, Editor, ReadError,
};
use serde::Deserialize;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Edit, potentially creating, a key
EditKey {
#[arg(short, long)]
key: String,
#[arg(short, long)]
locale: Option<String>,
},
/// List al keys in the database
ListKeys,
// Search the database
// Search { },
Import {
#[arg(short, long)]
file: String,
},
/// Export the database
Export {
#[arg(short, long)]
format: String,
#[arg(short, long)]
file: String,
#[arg(short, long)]
locale: Option<String>,
},
Report,
}
#[derive(Debug, Deserialize)]
struct Config {
db_path: PathBuf,
base_locale: LanguageIdentifier,
}
fn edit_key(bundle: &mut Bundle, key: String, locale: LanguageIdentifier, editor: &str) {
let message = bundle.message(key);
Editor::edit(message, locale, editor);
bundle.save();
}
#[derive(Clone, Debug, Default)]
struct Report {
keys: Vec<String>,
// source_deleted: Vec<String>,
out_of_date: Vec<String>,
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{} messages in bundle", self.keys.len())?;
writeln!(f, "Out of date messages")?;
for key in self.out_of_date.iter() {
writeln!(f, "\t{}", key)?;
}
Ok(())
}
}
fn generate_report(bundle: &Bundle, base_locale: &LanguageIdentifier) -> Report {
let mut report: Report = Default::default();
for (key, message) in bundle.message_iter() {
report.keys.push(key.to_owned());
if !message.variants_out_of_date(base_locale).is_empty() {
report.out_of_date.push(key.to_owned())
}
}
report
}
fn main() {
let editor = std::env::var("EDITOR").expect("Set EDITOR to the path to your favorite editor");
let config: Config = read_file(&PathBuf::from("./config.toml"))
.and_then(|bytes| String::from_utf8(bytes).map_err(|_| ReadError::InvalidFormat))
.and_then(|content| toml::from_str(&content).map_err(|_| ReadError::InvalidFormat))
.unwrap();
let cli = Cli::parse();
let mut bundle = Bundle::load_from_disk(PathBuf::from(&config.db_path));
match &cli.command {
Some(Commands::EditKey { key, locale }) => {
let identifier = locale.as_ref()
.map(|l| l.parse::<LanguageIdentifier>().unwrap())
.unwrap_or(config.base_locale);
edit_key(&mut bundle, key.to_owned(), identifier, &editor)
}
Some(Commands::ListKeys) => {
for (key, _) in bundle.message_iter() {
println!("{}", key);
}
}
Some(Commands::Import { file }) => {
import_file(&mut bundle, &PathBuf::from(file)).unwrap();
bundle.save();
}
Some(Commands::Export {
format,
file,
locale,
}) => {
let locale = locale
.as_ref()
.map(|l| l.clone().parse::<LanguageIdentifier>().unwrap())
.unwrap_or(langid!("en"));
match format.as_ref() {
"js" => js::export_file(&bundle, locale, &PathBuf::from(file)).unwrap(),
"xliff" => {
xliff::export_file(&bundle, locale, &PathBuf::from(file)).unwrap()
}
_ => todo!(),
}
}
Some(Commands::Report) => {
let report = generate_report(&bundle, &config.base_locale);
println!("{}", report);
}
None => {}
}
}

View File

@ -1,143 +0,0 @@
use std::{collections::HashMap, fs::File, io::{BufReader, Read, Write}, path::{Path, PathBuf}};
use chrono::{DateTime, Utc};
use icu_locid::LanguageIdentifier;
use serde::{Deserialize, Serialize};
pub struct Bundle {
path: PathBuf,
messages: HashMap<String, Message>,
}
impl Bundle {
pub fn load_from_disk(path: PathBuf) -> Self {
let mut messages = HashMap::new();
if path.is_dir() {
for entry in std::fs::read_dir(&path).unwrap() {
let entry = entry.unwrap();
let path = entry.path().clone();
let key = path.file_stem().unwrap();
let message = Message::from_file(&entry.path());
messages.insert(key.to_str().unwrap().to_owned(), message);
}
}
Self { path, messages }
}
pub fn message_iter(&self) -> impl Iterator<Item = (&String, &Message)> {
self.messages.iter()
}
pub fn message(&mut self, name: String) -> &mut Message {
self.messages
.entry(name.to_owned())
.or_insert(Message::new(name.to_owned()))
}
pub fn save(&self) {
self.messages.iter().for_each(|(key, value)| {
let mut path = self.path.clone();
path.push(key);
path.set_extension("toml");
save_file(&path, toml::to_string(value).unwrap().as_bytes());
});
}
}
fn save_file(path: &PathBuf, s: &[u8]) {
let mut f = File::create(path).unwrap();
let _ = f.write(s).unwrap();
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Message {
key: String,
description: String,
variants: HashMap<LanguageIdentifier, Variant>,
}
impl Message {
pub fn new(key: String) -> Self {
Self {
key,
description: "".to_owned(),
variants: HashMap::new(),
}
}
pub fn from_file(path: &Path) -> Message {
let file = std::fs::File::open(path).unwrap();
let mut content = Vec::new();
let mut reader = BufReader::new(file);
let _ = reader.read_to_end(&mut content);
toml::from_str(&String::from_utf8(content).unwrap()).unwrap()
}
pub fn set_description(&mut self, desc: String) {
self.description = desc;
}
pub fn description(&self) -> &str {
&self.description
}
pub fn variant(&self, locale: &LanguageIdentifier) -> Option<&Variant> {
self.variants.get(locale)
}
pub fn variant_mut(&mut self, locale: LanguageIdentifier) -> &mut Variant {
self.variants.entry(locale.clone()).or_insert(Variant {
locale,
content: "".to_owned(),
modified: Utc::now(),
})
}
pub fn variants_out_of_date(
&self,
base_locale: &LanguageIdentifier,
) -> Vec<LanguageIdentifier> {
match self
.variants
.get(base_locale)
.map(|variant| variant.modified())
{
Some(base_date) => self
.variants
.iter()
.filter(|(_, value)| base_date > value.modified())
.map(|(locale, _)| locale.clone())
.collect(),
None => vec![],
}
}
// pub fn missing_variants(&self, locals: Vec<LanguageIdentifier>) -> Vec<LanguageIdentifier> {}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Variant {
locale: LanguageIdentifier,
content: String,
modified: DateTime<Utc>,
}
impl Variant {
pub fn content(&self) -> &str {
&self.content
}
pub fn set_content(&mut self, content: String) {
self.content = content;
self.modified = Utc::now();
}
pub fn modified(&self) -> DateTime<Utc> {
self.modified
}
}
#[cfg(test)]
mod test {}

View File

@ -1,43 +0,0 @@
use std::{io::Write, process::Command};
use icu_locid::{langid, LanguageIdentifier};
use serde::{Deserialize, Serialize};
use crate::{read_fh, Message};
#[derive(Serialize, Deserialize, Debug, Clone)]
struct EditorMessage {
description: String,
source: String,
content: String,
}
pub struct Editor {}
impl Editor {
pub fn edit(msg: &mut Message, locale: LanguageIdentifier, editor: &str) {
let description = msg.description().to_owned();
let source_string = msg.variant_mut(langid!("en")).content().to_owned();
let variant = msg.variant_mut(locale);
// let editable_content = EditorMessage::from_variant(description, &variant);
let editable_content = EditorMessage {
description,
source: source_string,
content: variant.content().to_owned(),
};
let mut file = tempfile::NamedTempFile::new().unwrap();
let _ = file.write(toml::to_string(&editable_content).unwrap().as_bytes());
let _ = file.flush();
let mut cmd = Command::new(editor).args([file.path()]).spawn().unwrap();
cmd.wait().unwrap();
let file = file.reopen().unwrap();
let content = read_fh(&file).unwrap();
let new_variant: EditorMessage =
toml::from_str(&String::from_utf8(content).unwrap()).unwrap();
variant.set_content(new_variant.content);
msg.set_description(new_variant.description);
}
}

View File

@ -1,22 +0,0 @@
use std::{collections::BTreeMap, fs::File, io::Write, path::Path};
use icu_locid::LanguageIdentifier;
use crate::{Bundle, WriteError};
pub fn export_file(bundle: &Bundle, locale: LanguageIdentifier, path: &Path) -> Result<(), WriteError> {
let mut file = File::create(path).unwrap();
export_fh(bundle, locale, &mut file)
}
pub fn export_fh(bundle: &Bundle, locale: LanguageIdentifier, fh: &mut File) -> Result<(), WriteError> {
let messages = bundle.message_iter().map(|(key, message)| {
let content = message.variant(&locale).unwrap().content().to_owned();
(key.to_owned(), content)
}).collect::<Vec<(String, String)>>();
let messages: BTreeMap<String, String> = messages.into_iter().collect();
let _ = fh.write(serde_json::to_string_pretty(&messages).unwrap().as_bytes()).unwrap();
Ok(())
}

View File

@ -1,4 +0,0 @@
pub mod js;
pub mod xliff;

View File

@ -1,146 +0,0 @@
use std::{
fs::File,
io::{BufReader, Read, Write},
path::Path,
};
use icu_locid::{langid, LanguageIdentifier};
use xml::{attribute::OwnedAttribute, reader, writer, EmitterConfig, EventReader, EventWriter};
use crate::{Bundle, Message, ReadError, WriteError};
pub fn export_file(
bundle: &Bundle,
locale: LanguageIdentifier,
path: &Path,
) -> Result<(), WriteError> {
let mut file = File::create(path).unwrap();
export_fh(bundle, locale, &mut file)
}
pub fn export_fh<W>(bundle: &Bundle, locale: LanguageIdentifier, fh: W) -> Result<(), WriteError>
where
W: Write,
{
let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh);
writer
.write(
writer::XmlEvent::start_element("xliff")
.attr("xmlns", "urn:oasis:names:tc:xliff:document:2.0")
.attr("version", "2.0")
.attr("srcLang", &format!("{}", locale)),
)
.unwrap();
writer
.write(writer::XmlEvent::start_element("file").attr("id", "main"))
.unwrap();
for (key, message) in bundle.message_iter() {
write_message(&mut writer, key, message, &locale);
}
writer.write(writer::XmlEvent::end_element()).unwrap();
writer.write(writer::XmlEvent::end_element()).unwrap();
Ok(())
}
pub fn import_file(bundle: &mut Bundle, path: &Path) -> Result<(), ReadError> {
let file = File::open(path).unwrap();
let file = BufReader::new(file);
import_reader(bundle, file)
}
pub fn import_reader<R>(bundle: &mut Bundle, fh: R) -> Result<(), ReadError>
where
R: Read,
{
let parser = EventReader::new(fh);
let mut locale: LanguageIdentifier = langid!("en");
let mut current_key = None;
let mut current_text: Option<String> = None;
let mut in_target = false;
for event in parser {
match event {
Ok(reader::XmlEvent::StartElement {
name, attributes, ..
}) => match name.local_name.as_ref() {
"xliff" => {
locale = find_attribute(&attributes, "trgLang")
.unwrap()
.parse::<LanguageIdentifier>()
.unwrap();
}
"unit" => current_key = find_attribute(&attributes, "id"),
"target" => in_target = true,
_ => println!("name: {}", name),
},
Ok(reader::XmlEvent::EndElement { name }) => match name.local_name.as_ref() {
"unit" => {
if let Some(key) = current_key {
let message = bundle.message(key);
let variant = message.variant_mut(locale.clone());
if let Some(ref text) = current_text {
variant.set_content(text.clone());
}
}
current_key = None;
}
"target" => in_target = false,
_ => {}
},
Ok(reader::XmlEvent::Characters(data)) => {
if in_target {
current_text = Some(data)
}
}
Err(e) => {
eprintln!("error: {e}");
break;
}
_ => {}
}
}
Ok(())
}
fn write_message<T>(
writer: &mut EventWriter<T>,
key: &str,
message: &Message,
locale: &LanguageIdentifier,
) where
T: std::io::Write,
{
[
writer::XmlEvent::start_element("unit")
.attr("id", key)
.into(),
writer::XmlEvent::start_element("notes").into(),
writer::XmlEvent::start_element("note").into(),
writer::XmlEvent::characters(message.description()),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::start_element("segment").into(),
writer::XmlEvent::start_element("source").into(),
writer::XmlEvent::characters(message.variant(locale).unwrap().content()),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
]
.into_iter()
.for_each(|elem: writer::XmlEvent| writer.write(elem).unwrap());
}
fn find_attribute(attrs: &Vec<OwnedAttribute>, name: &str) -> Option<String> {
for f in attrs {
if name == f.name.local_name {
return Some(f.value.clone());
}
}
None
}

View File

@ -1,11 +0,0 @@
mod bundle;
pub use bundle::{Bundle, Message, Variant};
mod editor;
pub use editor::Editor;
mod formats;
pub use formats::{js, xliff};
mod utils;
pub use utils::*;

View File

@ -1,37 +0,0 @@
use std::{fs::File, io::{BufReader, ErrorKind, Read}, path::Path};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ReadError {
#[error("file not found")]
FileNotFound,
#[error("invalid file format")]
InvalidFormat,
#[error("unhandled read error")]
Unhandled(ErrorKind),
}
pub fn read_file(path: &Path) -> Result<Vec<u8>, ReadError> {
let file = File::open(path).map_err(|err| {
match err.kind() {
ErrorKind::NotFound => ReadError::FileNotFound,
_ => ReadError::Unhandled( err.kind()),
}
})?;
read_fh(&file)
}
pub fn read_fh(file: &File) -> Result<Vec<u8>, ReadError> {
let mut content = Vec::new();
let mut reader = BufReader::new(file);
reader.read_to_end(&mut content).map_err(|err| ReadError::Unhandled(err.kind()))?;
Ok(content)
}
#[derive(Debug, Error)]
pub enum WriteError {
#[error("unhandled write error")]
Unhandled(ErrorKind),
}

View File

@ -78,7 +78,7 @@ mod tests {
})
.await;
assert_eq!(value, Value(16));
assert!(*run.read().unwrap());
assert_eq!(*run.read().unwrap(), true);
}
#[tokio::test]
@ -97,6 +97,6 @@ mod tests {
})
.await;
assert_eq!(value, Value(15));
assert!(!(*run.read().unwrap()));
assert_eq!(*run.read().unwrap(), false);
}
}

View File

@ -80,12 +80,12 @@ mod tests {
use super::*;
use cool_asserts::assert_matches;
const DATA: &str = "15";
const DATA: &'static str = "15";
#[test]
fn function() {
let resp = parse_number_a::<nom::error::VerboseError<&str>>()
.map(Container)
.map(|val| Container(val))
.parse(DATA);
assert_matches!(resp, Ok((_, content)) =>
assert_eq!(content, Container(15))
@ -95,7 +95,7 @@ mod tests {
#[test]
fn parser() {
let resp = parse_number_b::<nom::error::VerboseError<&str>>()
.map(Container)
.map(|val| Container(val))
.parse(DATA);
assert_matches!(resp, Ok((_, content)) =>
assert_eq!(content, Container(15))

View File

@ -396,7 +396,8 @@ mod test {
(Coordinate { column: 17, row: 0 }, Color::White),
(Coordinate { column: 17, row: 1 }, Color::White),
(Coordinate { column: 18, row: 1 }, Color::White),
],
]
.into_iter(),
)
.unwrap();
test(board);
@ -435,32 +436,33 @@ mod test {
},
Color::Black,
),
],
]
.into_iter(),
)
.unwrap();
assert!(board.group(&Coordinate { column: 18, row: 3 }).is_none());
assert_eq!(
board
.group(&Coordinate { column: 3, row: 3 })
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(4)
);
assert_eq!(
board
.group(&Coordinate { column: 0, row: 3 })
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(3)
);
assert_eq!(
board
.group(&Coordinate { column: 0, row: 0 })
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(2)
);
assert_eq!(
board
.group(&Coordinate { column: 18, row: 9 })
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(3)
);
assert_eq!(
@ -469,7 +471,7 @@ mod test {
column: 18,
row: 18
})
.map(|g| board.liberties(g)),
.map(|g| board.liberties(&g)),
Some(2)
);
}
@ -612,7 +614,7 @@ mod test {
for (board, coordinate, group, liberties) in test_cases {
assert_eq!(board.group(&coordinate), group.as_ref());
assert_eq!(
board.group(&coordinate).map(|g| board.liberties(g)),
board.group(&coordinate).map(|g| board.liberties(&g)),
liberties,
"{:?}",
coordinate
@ -686,11 +688,11 @@ mod test {
fn validate_group_comparisons() {
{
let b1 = Goban::from_coordinates(
vec![(Coordinate { column: 7, row: 9 }, Color::White)],
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
)
.unwrap();
let b2 = Goban::from_coordinates(
vec![(Coordinate { column: 7, row: 9 }, Color::White)],
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
)
.unwrap();
@ -702,14 +704,16 @@ mod test {
vec![
(Coordinate { column: 7, row: 9 }, Color::White),
(Coordinate { column: 8, row: 10 }, Color::White),
],
]
.into_iter(),
)
.unwrap();
let b2 = Goban::from_coordinates(
vec![
(Coordinate { column: 8, row: 10 }, Color::White),
(Coordinate { column: 7, row: 9 }, Color::White),
],
]
.into_iter(),
)
.unwrap();
@ -728,7 +732,8 @@ mod test {
(Coordinate { column: 10, row: 9 }, Color::Black),
(Coordinate { column: 9, row: 8 }, Color::Black),
(Coordinate { column: 9, row: 10 }, Color::Black),
],
]
.into_iter(),
)
.unwrap();

View File

@ -587,7 +587,8 @@ mod test {
(Coordinate { column: 17, row: 0 }, Color::White),
(Coordinate { column: 17, row: 1 }, Color::White),
(Coordinate { column: 18, row: 1 }, Color::White),
],
]
.into_iter(),
)
.unwrap();
state.current_player = Color::Black;
@ -611,7 +612,8 @@ mod test {
(Coordinate { column: 10, row: 9 }, Color::Black),
(Coordinate { column: 9, row: 8 }, Color::Black),
(Coordinate { column: 9, row: 10 }, Color::Black),
],
]
.into_iter(),
)
.unwrap();

View File

@ -132,10 +132,10 @@ impl GameReviewViewModel {
// the board state by applying the child.
pub fn next_move(&self) {
let mut inner = self.inner.write().unwrap();
let current_position = inner.current_position;
let current_position = inner.current_position.clone();
match current_position {
Some(current_position) => {
let current_id = current_position;
let current_id = current_position.clone();
let node = inner.game.trees[0].get(current_id).unwrap();
if let Some(next_id) = node.first_child().map(|child| child.node_id()) {
inner.current_position = Some(next_id);
@ -180,7 +180,7 @@ mod test {
where
F: FnOnce(GameReviewViewModel),
{
let records = sgf::parse_sgf_file(Path::new("../../sgf/test_data/branch_test.sgf"))
let records = sgf::parse_sgf_file(&Path::new("../../sgf/test_data/branch_test.sgf"))
.expect("to successfully load the test file");
let record = records[0]
.as_ref()

View File

@ -18,7 +18,7 @@ use crate::{CoreApi, ResourceManager};
use adw::prelude::*;
use glib::Propagation;
use gtk::EventControllerKey;
use gtk::{gdk::Key, EventControllerKey};
use otg_core::{
settings::{SettingsRequest, SettingsResponse},
CoreRequest, CoreResponse, GameReviewViewModel,

View File

@ -102,8 +102,14 @@ impl GameReview {
}
}
if let Some(ref mut goban) = *s.goban.borrow_mut() { goban.set_board_state(view.game_view()) };
if let Some(ref tree) = *s.review_tree.borrow() { tree.queue_draw() }
match *s.goban.borrow_mut() {
Some(ref mut goban) => goban.set_board_state(view.game_view()),
None => {}
};
match *s.review_tree.borrow() {
Some(ref tree) => tree.queue_draw(),
None => {}
}
Propagation::Stop
}
});
@ -163,6 +169,9 @@ impl GameReview {
*self.review_tree.borrow_mut() = Some(review_tree);
}
fn redraw(&self) {
}
pub fn widget(&self) -> gtk::Widget {
self.widget.clone().upcast::<gtk::Widget>()
}

View File

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

View File

@ -1,13 +0,0 @@
[package]
name = "pico-st7789"
version = "0.1.0"
edition = "2021"
[dependencies]
bitflags = "2.9.0"
cortex-m-rt = "0.7.3"
embedded-alloc = "0.6.0"
embedded-hal = "1.0.0"
fugit = "0.3.7"
panic-halt = "1.0.0"
rp-pico = "0.9.0"

View File

@ -1,62 +0,0 @@
// a a a
// f b
// f b
// f b
// g g g
// e c
// e c
// e c
// d d d
use crate::font::{Font, Glyph};
pub struct RGB {
pub r: u8,
pub g: u8,
pub b: u8,
}
pub trait Canvas {
fn set_pixel(&mut self, x: usize, y: usize, color: &RGB);
fn fill(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: &RGB) {
for x in x1..x2 + 1 {
for y in y1..y2 + 1 {
self.set_pixel(x, y, color);
}
}
}
fn square(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: &RGB) {
for x in x1..x2 + 1 {
self.set_pixel(x, y1, color);
}
for x in x1..x2 + 1 {
self.set_pixel(x, y2, color);
}
for y in y1..y2 + 1 {
self.set_pixel(x1, y, color);
}
for y in y1..y2 + 1 {
self.set_pixel(x2, y, color);
}
}
}
pub fn print<A>(
canvas: &mut impl Canvas,
font: &impl Font<A>,
sx: usize,
sy: usize,
text: &str,
color: &RGB,
) where
A: Glyph,
{
let mut x = sx;
for c in text.chars().map(|c| font.glyph(c)) {
c.draw(canvas, x, sy, color);
let (dx, _) = c.extents();
x = x + dx + 1;
}
}

View File

@ -1,513 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
pub struct BitmapGlyph([u8; 7]);
pub struct BitmapFont(BTreeMap<char, BitmapGlyph>);
impl BitmapFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', BitmapGlyph([
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
]));
font.insert(
':',
BitmapGlyph([
0b00000,
0b00100,
0b00100,
0b00000,
0b00100,
0b00100,
0b00000,
]),
);
font.insert(
'/',
BitmapGlyph([
0b00001,
0b00010,
0b00010,
0b00100,
0b01000,
0b01000,
0b10000,
]),
);
font.insert(
'0',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10101,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'1',
BitmapGlyph([
0b00001,
0b00011,
0b00101,
0b00001,
0b00001,
0b00001,
0b00001,
]),
);
font.insert(
'2',
BitmapGlyph([
0b01110,
0b10001,
0b00001,
0b00010,
0b00100,
0b01000,
0b11111,
]),
);
font.insert(
'3',
BitmapGlyph([
0b01110,
0b10001,
0b00001,
0b01110,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'4',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b11111,
0b00001,
0b00001,
0b00001,
]),
);
font.insert(
'5',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11111,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'6',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b11110,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'7',
BitmapGlyph([
0b11111,
0b00001,
0b00010,
0b00110,
0b00100,
0b01000,
0b01000,
]),
);
font.insert(
'8',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b01110,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'9',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b01111,
0b00001,
0b00001,
0b01110,
]),
);
font.insert(
'A',
BitmapGlyph([
0b00100,
0b01010,
0b01010,
0b10001,
0b11111,
0b10001,
0b10001,
]),
);
font.insert(
'B',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b1111 ,
0b10001,
0b10001,
0b11110,
]),
);
font.insert(
'C',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b10000,
0b10000,
0b10001,
0b01110,
]),
);
font.insert(
'D',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b11110,
]),
);
font.insert(
'E',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11111,
0b10000,
0b10000,
0b11111,
]),
);
font.insert(
'F',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11110,
0b10000,
0b10000,
0b10000,
]),
);
font.insert(
'G',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b10011,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'H',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b11111,
0b10001,
0b10001,
0b10001,
]),
);
font.insert(
'I',
BitmapGlyph([
0b11111,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
0b11111,
]),
);
font.insert(
'J',
BitmapGlyph([
0b00111,
0b00001,
0b00001,
0b00001,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'K',
BitmapGlyph([
0b10001,
0b10010,
0b10100,
0b11000,
0b10100,
0b10010,
0b10001,
]),
);
font.insert(
'L',
BitmapGlyph([
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b11111,
]),
);
font.insert(
'M',
BitmapGlyph([
0b10001,
0b11011,
0b10101,
0b10001,
0b10001,
0b10001,
0b10001,
]),
);
font.insert(
'N',
BitmapGlyph([
0b10001,
0b11001,
0b11001,
0b10101,
0b10011,
0b10011,
0b10001,
]),
);
font.insert(
'O',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'P',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b11110,
0b10000,
0b10000,
0b10000,
]),
);
font.insert(
'Q',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10001,
0b10101,
0b10011,
0b01110,
]),
);
font.insert(
'R',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b11110,
0b10100,
0b10010,
0b10001,
]),
);
font.insert(
'S',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b01110,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'T',
BitmapGlyph([
0b11111,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
]),
);
font.insert(
'U',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'V',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b01010,
0b01010,
0b00100,
]),
);
font.insert(
'W',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b10101,
0b10101,
0b01010,
]),
);
font.insert(
'X',
BitmapGlyph([
0b10001,
0b10001,
0b01010,
0b00100,
0b01010,
0b10001,
0b10001,
]),
);
font.insert(
'Y',
BitmapGlyph([
0b10001,
0b10001,
0b01010,
0b00100,
0b00100,
0b00100,
0b00100,
]),
);
font.insert(
'Z',
BitmapGlyph([
0b11111,
0b00001,
0b00010,
0b00100,
0b01000,
0b10000,
0b11111,
]),
);
Self(font)
}
}
impl Font<BitmapGlyph> for BitmapFont {
fn glyph(&self, c: char) -> &BitmapGlyph {
self.0.get(&c).unwrap_or(self.0.get(&' ').unwrap())
}
}
impl Glyph for BitmapGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
for row in 0..7 {
if self.0[row] & (1 << 4) > 0 {
canvas.set_pixel(x, y + row, color);
}
if self.0[row] & (1 << 3) > 0 {
canvas.set_pixel(x + 1, y + row, color);
}
if self.0[row] & (1 << 2) > 0 {
canvas.set_pixel(x + 2, y + row, color);
}
if self.0[row] & (1 << 1) > 0 {
canvas.set_pixel(x + 3, y + row, color);
}
if self.0[row] & 1 > 0 {
canvas.set_pixel(x + 4, y + row, color);
}
}
}
fn extents(&self) -> (usize, usize) {
(5, 7)
}
}

View File

@ -1,20 +0,0 @@
use crate::canvas::{Canvas, RGB};
mod bits_5_8;
pub use bits_5_8::BitmapFont;
mod seven_segment;
pub use seven_segment::SevenSegmentFont;
mod sixteen_segment;
pub use sixteen_segment::SixteenSegmentFont;
pub trait Font<A> {
fn glyph(&self, c: char) -> &A;
}
pub trait Glyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB);
fn extents(&self) -> (usize, usize);
}

View File

@ -1,211 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use bitflags::bitflags;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
// Seven Segments
//
// a a a
// f b
// f b
// f b
// g g g
// e c
// e c
// e c
// d d d
bitflags! {
pub struct SevenSegmentGlyph: u8 {
const NONE = 0;
const A = 0x01;
const B = 0x01 << 1;
const C = 0x01 << 2;
const D = 0x01 << 3;
const E = 0x01 << 4;
const F = 0x01 << 5;
const G = 0x01 << 6;
const DOT = 0x01 << 7;
}
}
pub struct SevenSegmentFont(BTreeMap<char, SevenSegmentGlyph>);
impl SevenSegmentFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', SevenSegmentGlyph::NONE);
font.insert(
'0',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F,
);
font.insert('1', SevenSegmentGlyph::B | SevenSegmentGlyph::C);
font.insert(
'2',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::G,
);
font.insert(
'3',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::G
| SevenSegmentGlyph::D,
);
font.insert(
'4',
SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'5',
SevenSegmentGlyph::A
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'6',
SevenSegmentGlyph::A
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D,
);
font.insert(
'7',
SevenSegmentGlyph::A | SevenSegmentGlyph::B | SevenSegmentGlyph::C,
);
font.insert(
'8',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'9',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'A',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'B',
SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'C',
SevenSegmentGlyph::A
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F,
);
font.insert(
'D',
SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::G,
);
font.insert(
'E',
SevenSegmentGlyph::A
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'F',
SevenSegmentGlyph::A
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
Self(font)
}
}
impl Font<SevenSegmentGlyph> for SevenSegmentFont {
fn glyph(&self, c: char) -> &SevenSegmentGlyph {
self.0.get(&c).unwrap()
}
}
impl Glyph for SevenSegmentGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
if self.contains(SevenSegmentGlyph::A) {
canvas.set_pixel(x + 1, y, color);
canvas.set_pixel(x + 2, y, color);
canvas.set_pixel(x + 3, y, color);
}
if self.contains(SevenSegmentGlyph::B) {
canvas.set_pixel(x + 4, y + 1, color);
canvas.set_pixel(x + 4, y + 2, color);
canvas.set_pixel(x + 4, y + 3, color);
}
if self.contains(SevenSegmentGlyph::C) {
canvas.set_pixel(x + 4, y + 5, color);
canvas.set_pixel(x + 4, y + 6, color);
canvas.set_pixel(x + 4, y + 7, color);
}
if self.contains(SevenSegmentGlyph::D) {
canvas.set_pixel(x + 1, y + 8, color);
canvas.set_pixel(x + 2, y + 8, color);
canvas.set_pixel(x + 3, y + 8, color);
}
if self.contains(SevenSegmentGlyph::E) {
canvas.set_pixel(x, y + 5, color);
canvas.set_pixel(x, y + 6, color);
canvas.set_pixel(x, y + 7, color);
}
if self.contains(SevenSegmentGlyph::F) {
canvas.set_pixel(x, y + 1, color);
canvas.set_pixel(x, y + 2, color);
canvas.set_pixel(x, y + 3, color);
}
if self.contains(SevenSegmentGlyph::G) {
canvas.set_pixel(x + 1, y + 4, color);
canvas.set_pixel(x + 2, y + 4, color);
canvas.set_pixel(x + 3, y + 4, color);
}
}
fn extents(&self) -> (usize, usize) {
(5, 8)
}
}

View File

@ -1,689 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use bitflags::bitflags;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
// Sixteen Segments
// https://www.partsnotincluded.com/segmented-led-display-ascii-library/
//
// a1 a2
// f h i jb
// f i j b
// f hij b
// g1 g2
// e klm c
// e k l m c
// ek l mc
// d1 d2
bitflags! {
pub struct SixteenSegmentGlyph: u32 {
const NONE = 0;
const A1 = 0x0001;
const A2 = 0x0001 << 1;
const B = 0x0001 << 2;
const C = 0x0001 << 3;
const D1 = 0x0001 << 4;
const D2 = 0x0001 << 5;
const E = 0x0001 << 6;
const F = 0x0001 << 7;
const G1 = 0x0001 << 8;
const G2 = 0x0001 << 9;
const H = 0x0001 << 10;
const I = 0x0001 << 11;
const J = 0x0001 << 12;
const K = 0x0001 << 13;
const L = 0x0001 << 14;
const M = 0x0001 << 15;
const DOT = 0x0001 << 16;
}
}
pub struct SixteenSegmentFont(BTreeMap<char, SixteenSegmentGlyph>);
impl SixteenSegmentFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', SixteenSegmentGlyph::NONE);
font.insert(
'!',
SixteenSegmentGlyph::B | SixteenSegmentGlyph::C | SixteenSegmentGlyph::DOT,
);
font.insert('"', SixteenSegmentGlyph::B | SixteenSegmentGlyph::I);
font.insert(
'0',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'1',
SixteenSegmentGlyph::B | SixteenSegmentGlyph::C | SixteenSegmentGlyph::J,
);
font.insert(
'2',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'3',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2,
);
font.insert(
'4',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'5',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::M,
);
font.insert(
'6',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'7',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C,
);
font.insert(
'8',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'9',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'A',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'B',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'C',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'D',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'E',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'F',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'G',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G2,
);
font.insert(
'H',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'I',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'J',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E,
);
font.insert(
'K',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::M,
);
font.insert(
'L',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'M',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J,
);
font.insert(
'N',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::H
| SixteenSegmentGlyph::M,
);
font.insert(
'O',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'P',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'Q',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::M,
);
font.insert(
'R',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::M,
);
font.insert(
'S',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'T',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'U',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'V',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'W',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'X',
SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'Y',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'Z',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'a',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'b',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'c',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::E | SixteenSegmentGlyph::G1,
);
font.insert(
'd',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::L,
);
font.insert(
'e',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::K,
);
font.insert(
'f',
SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'g',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'h',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert('i', SixteenSegmentGlyph::L);
font.insert(
'j',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'k',
SixteenSegmentGlyph::I
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::L
| SixteenSegmentGlyph::M,
);
font.insert('l', SixteenSegmentGlyph::E | SixteenSegmentGlyph::F);
font.insert(
'm',
SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::L
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'n',
SixteenSegmentGlyph::E | SixteenSegmentGlyph::L | SixteenSegmentGlyph::G1,
);
font.insert(
'o',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'p',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I,
);
font.insert(
'q',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert('r', SixteenSegmentGlyph::E | SixteenSegmentGlyph::G1);
font.insert(
's',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
't',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'u',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::E | SixteenSegmentGlyph::L,
);
font.insert('v', SixteenSegmentGlyph::E | SixteenSegmentGlyph::K);
font.insert(
'w',
SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'x',
SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'y',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2,
);
font.insert(
'z',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::G1 | SixteenSegmentGlyph::K,
);
font.insert(
'@',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G2,
);
Self(font)
}
}
impl Font<SixteenSegmentGlyph> for SixteenSegmentFont {
fn glyph(&self, c: char) -> &SixteenSegmentGlyph {
self.0.get(&c).unwrap_or(&SixteenSegmentGlyph::NONE)
}
}
impl Glyph for SixteenSegmentGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
if self.contains(SixteenSegmentGlyph::A1) {
canvas.set_pixel(x + 1, y, color);
canvas.set_pixel(x + 2, y, color);
canvas.set_pixel(x + 3, y, color);
}
if self.contains(SixteenSegmentGlyph::A2) {
canvas.set_pixel(x + 5, y, color);
canvas.set_pixel(x + 6, y, color);
canvas.set_pixel(x + 7, y, color);
}
if self.contains(SixteenSegmentGlyph::B) {
canvas.set_pixel(x + 8, y + 1, color);
canvas.set_pixel(x + 8, y + 2, color);
canvas.set_pixel(x + 8, y + 3, color);
canvas.set_pixel(x + 8, y + 4, color);
canvas.set_pixel(x + 8, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::C) {
canvas.set_pixel(x + 8, y + 7, color);
canvas.set_pixel(x + 8, y + 8, color);
canvas.set_pixel(x + 8, y + 9, color);
canvas.set_pixel(x + 8, y + 10, color);
canvas.set_pixel(x + 8, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::D1) {
canvas.set_pixel(x + 1, y + 12, color);
canvas.set_pixel(x + 2, y + 12, color);
canvas.set_pixel(x + 3, y + 12, color);
}
if self.contains(SixteenSegmentGlyph::D2) {
canvas.set_pixel(x + 5, y + 12, color);
canvas.set_pixel(x + 6, y + 12, color);
canvas.set_pixel(x + 7, y + 12, color);
}
if self.contains(SixteenSegmentGlyph::E) {
canvas.set_pixel(x, y + 7, color);
canvas.set_pixel(x, y + 8, color);
canvas.set_pixel(x, y + 9, color);
canvas.set_pixel(x, y + 10, color);
canvas.set_pixel(x, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::F) {
canvas.set_pixel(x, y + 1, color);
canvas.set_pixel(x, y + 2, color);
canvas.set_pixel(x, y + 3, color);
canvas.set_pixel(x, y + 4, color);
canvas.set_pixel(x, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::G1) {
canvas.set_pixel(x + 1, y + 6, color);
canvas.set_pixel(x + 2, y + 6, color);
canvas.set_pixel(x + 3, y + 6, color);
}
if self.contains(SixteenSegmentGlyph::G2) {
canvas.set_pixel(x + 5, y + 6, color);
canvas.set_pixel(x + 6, y + 6, color);
canvas.set_pixel(x + 7, y + 6, color);
}
if self.contains(SixteenSegmentGlyph::H) {
canvas.set_pixel(x + 1, y + 1, color);
canvas.set_pixel(x + 1, y + 2, color);
canvas.set_pixel(x + 2, y + 3, color);
canvas.set_pixel(x + 3, y + 4, color);
canvas.set_pixel(x + 3, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::I) {
canvas.set_pixel(x + 4, y + 1, color);
canvas.set_pixel(x + 4, y + 2, color);
canvas.set_pixel(x + 4, y + 3, color);
canvas.set_pixel(x + 4, y + 4, color);
canvas.set_pixel(x + 4, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::J) {
canvas.set_pixel(x + 7, y + 1, color);
canvas.set_pixel(x + 7, y + 2, color);
canvas.set_pixel(x + 6, y + 3, color);
canvas.set_pixel(x + 5, y + 4, color);
canvas.set_pixel(x + 5, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::K) {
canvas.set_pixel(x + 3, y + 7, color);
canvas.set_pixel(x + 3, y + 8, color);
canvas.set_pixel(x + 2, y + 9, color);
canvas.set_pixel(x + 1, y + 10, color);
canvas.set_pixel(x + 1, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::L) {
canvas.set_pixel(x + 4, y + 7, color);
canvas.set_pixel(x + 4, y + 8, color);
canvas.set_pixel(x + 4, y + 9, color);
canvas.set_pixel(x + 4, y + 10, color);
canvas.set_pixel(x + 4, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::M) {
canvas.set_pixel(x + 5, y + 7, color);
canvas.set_pixel(x + 5, y + 8, color);
canvas.set_pixel(x + 6, y + 9, color);
canvas.set_pixel(x + 7, y + 10, color);
canvas.set_pixel(x + 7, y + 11, color);
}
}
fn extents(&self) -> (usize, usize) {
(9, 13)
}
}

View File

@ -1,184 +0,0 @@
#![no_main]
#![no_std]
extern crate alloc;
use alloc::fmt::format;
use embedded_alloc::LlffHeap as Heap;
use embedded_hal::{delay::DelayNs, digital::OutputPin};
use fugit::RateExtU32;
use panic_halt as _;
use rp_pico::{
entry,
hal::{clocks::init_clocks_and_plls, spi::Spi, Clock, Sio, Timer, Watchdog},
pac, Pins,
};
mod canvas;
use canvas::{Canvas, RGB, print};
mod font;
use font::{BitmapFont, Font, Glyph, SevenSegmentFont, SixteenSegmentFont};
mod st7789;
use st7789::{ST7789Display, SETUP_PROGRAM};
const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // MHz, https://forums.raspberrypi.com/viewtopic.php?t=356764
const ROWS: usize = 320;
const COLUMNS: usize = 170;
const FRAMEBUF: usize = ROWS * COLUMNS * 3;
#[global_allocator]
static HEAP: Heap = Heap::empty();
static mut BUF: [u8; 163200] = [0; 163200];
pub struct FrameBuf {
pub buf: &'static mut [u8; 163200],
pub width: usize,
}
impl FrameBuf {
pub fn new() -> Self {
Self {
buf: unsafe { &mut BUF },
width: 170,
}
}
}
impl Canvas for FrameBuf {
fn set_pixel(&mut self, x: usize, y: usize, color: &RGB) {
self.buf[(y * self.width + x) * 3 + 0] = color.r << 2;
self.buf[(y * self.width + x) * 3 + 1] = color.g << 2;
self.buf[(y * self.width + x) * 3 + 2] = color.b << 2;
}
}
#[entry]
unsafe fn main() -> ! {
{
use core::mem::MaybeUninit;
const HEAP_SIZE: usize = 64 * 1024;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}
let font_bitmap = BitmapFont::new();
// rp_pico::pac::Peripherals is a reference to physical hardware defined on the Pico.
let mut peripherals = pac::Peripherals::take().unwrap();
// SIO inidcates "Single Cycle IO". I don't know what this means, but it could mean that this
// is a class of IO operations that can be run in a single clock cycle, such as switching a
// GPIO pin on or off.
let sio = Sio::new(peripherals.SIO);
// Many of the following systems require a watchdog. I do not know what this does, either, but
// it may be some failsafe software that will reset operations if the watchdog detects a lack
// of activity.
let mut watchdog = Watchdog::new(peripherals.WATCHDOG);
// Here we grab the GPIO pins in bank 0.
let pins = Pins::new(
peripherals.IO_BANK0,
peripherals.PADS_BANK0,
sio.gpio_bank0,
&mut peripherals.RESETS,
);
// Initialize an abstraction of the clock system with a batch of standard hardware clocks.
let clocks = init_clocks_and_plls(
XOSC_CRYSTAL_FREQ,
peripherals.XOSC,
peripherals.CLOCKS,
peripherals.PLL_SYS,
peripherals.PLL_USB,
&mut peripherals.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// An abstraction for a timer which we can use to delay the code.
let mut timer = Timer::new(peripherals.TIMER, &mut peripherals.RESETS, &clocks);
let mut led = pins.led.into_function();
// Grab the clock and data pins for SPI1. For Clock pins and for Data pins, there are only two
// pins each on the Pico which can function for SPI1.
let spi_clk = pins.gpio2.into_function();
let spi_sdo = pins.gpio3.into_function();
// let spi_sdi = pins.gpio4.into_function();
// Chip select 1 means the chip is not enabled
let mut board_select = pins.gpio13.into_function();
let mut data_command = pins.gpio15.into_function();
let mut reset = pins.gpio14.into_function();
let _ = reset.set_low();
let _ = board_select.set_high();
let _ = data_command.set_high();
// Now, create the SPI function abstraction for SPI1 with spi_clk and spi_sdo.
let mut spi = Spi::<_, _, _, 8>::new(peripherals.SPI0, (spi_sdo, spi_clk)).init(
&mut peripherals.RESETS,
// The SPI system uses the peripheral clock
clocks.peripheral_clock.freq(),
// Transmit data at a rate of 32Mbit.
32_u32.MHz(),
// Run with SPI Mode 1. This means that the clock line should start high and that data will
// be sampled starting at the first falling edge.
embedded_hal::spi::MODE_3,
);
let mut display = ST7789Display::new(board_select, data_command, spi);
let _ = reset.set_high();
timer.delay_ms(10);
for step in SETUP_PROGRAM {
let mut display = display.acquire();
display.send_command(&step, &mut timer);
}
timer.delay_ms(1000);
/*
let mut framebuf = [0; FRAMEBUF];
let mut canvas = Canvas::new(&mut framebuf, COLUMNS);
*/
let mut canvas = FrameBuf::new();
let white = RGB { r: 63, g: 63, b: 63 };
let mut count = 0;
loop {
canvas.fill(0, 10, 170, 20, &RGB{r: 0, g: 0, b: 0 });
print(&mut canvas, &font_bitmap, 1, 10, &format(format_args!("COUNT: {:03}", count)), &RGB{ r: 32, g: 32, b: 63 });
print(&mut canvas, &font_bitmap, 1, 200, " !\"#$%&'<>*+,-./", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 220, "0123456789|: = ?", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 240, "@ABCDEFGHIJKLMNO", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 260, "PQRSTUVWXYZ[\\]^_", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 280, "`abcdefghijklmno", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 300, "pqrstuvwxyz{|}", &RGB{ r: 63, g: 63, b: 63 });
// canvas.square(10, 70, 160, 310, &white);
{
let display = display.acquire();
let _ = led.set_high();
timer.delay_ms(100);
display.send_buf(canvas.buf);
let _ = led.set_low();
}
count = count + 1;
/*
for x in 80..90 {
for y in 155..165 {
draw_pixel(&mut frame, x, y, (0, 0, 63));
}
}
*/
timer.delay_ms(1000);
}
}

View File

@ -1,204 +0,0 @@
use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiBus};
use rp_pico::hal::{
gpio::{FunctionSio, Pin, PinId, PullDown, SioOutput},
spi::{Enabled, SpiDevice, ValidSpiPinout},
Spi, Timer,
};
pub struct Step {
param_cnt: usize,
command: u8,
params: [u8; 4],
delay: Option<u32>,
}
impl Step {
pub fn send_command<D, Pinout, P>(
&self,
spi: &mut Spi<Enabled, D, Pinout, 8>,
data_command: &mut Pin<P, FunctionSio<SioOutput>, PullDown>,
) where
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
P: PinId,
{
let _ = data_command.set_low();
let _ = spi.write(&[self.command]);
if self.param_cnt > 0 {
let _ = data_command.set_high();
let _ = spi.write(&self.params[0..self.param_cnt]);
}
}
}
const NOP: u8 = 0x00;
const SWRESET: Step = Step {
param_cnt: 0,
command: 0x01,
params: [0, 0, 0, 0],
delay: Some(150),
};
const SLPOUT: Step = Step {
param_cnt: 0,
command: 0x11,
params: [0, 0, 0, 0],
delay: Some(10),
};
const COLMOD: u8 = 0x3a;
const MADCTL: Step = Step {
param_cnt: 1,
command: 0x36,
params: [0x00, 0, 0, 0],
delay: None,
};
const CASET: u8 = 0x2a;
const RASET: u8 = 0x2b;
const INVON: Step = Step {
param_cnt: 0,
command: 0x21,
params: [0, 0, 0, 0],
delay: Some(10),
};
const NORON: Step = Step {
param_cnt: 0,
command: 0x13,
params: [0, 0, 0, 0],
delay: Some(10),
};
const DISPOFF: Step = Step {
param_cnt: 0,
command: 0x28,
params: [0, 0, 0, 0],
delay: Some(10),
};
const DISPON: Step = Step {
param_cnt: 0,
command: 0x29,
params: [0, 0, 0, 0],
delay: Some(10),
};
const RAMWR: u8 = 0x2c;
// Adafruit setup instructions
// SWRESET (0x01), 150ms delay
// SLPOUT (0x11), 10ms delay
// COLMOD (0x3a) 0x55 (65K RGB, 16bit/pixel), 10ms delay
// MADCTL (0x36) 0x00,
// memory data access control, RGB
// CASET 0x00, 0, 0, 170,
// column address set, 4 parameters
// 0x00, 0x00 indicates xstart is 0
// 0x00, 170 indicates xend is 170
// RASET 0x00, 0, 320 >> 8, 320 & 0xFF,
// row address set, 4 parameters
// 0x00, 0x00 indicates ystart is 0
// 3230 >> 8, 320 & 0xff indicates that 320 is the last y address
// INVON, 10ms delay
// invert the display
// NORON, 10ms delay
// normal display mode
// DISPON, 10ms delay
// turn the display on
pub const SETUP_PROGRAM: [Step; 8] = [
SWRESET,
SLPOUT,
Step {
param_cnt: 1,
command: COLMOD,
params: [0x66, 0, 0, 0],
delay: Some(10),
},
MADCTL,
Step {
param_cnt: 4,
command: CASET,
params: [0, 35, 0, 204],
delay: None,
},
/*
Step {
param_cnt: 4,
command: RASET,
params: [0, 0, (320 >> 8) as u8, (320 & 0xff) as u8],
delay: None,
},
*/
INVON,
NORON,
DISPON,
];
pub struct ST7789Display<
BoardSelectId: PinId,
DataCommandId: PinId,
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
> {
inner: ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>,
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>>
ST7789Display<BoardSelectId, DataCommandId, D, Pinout>
{
pub fn new(
board_select: Pin<BoardSelectId, FunctionSio<SioOutput>, PullDown>,
data_command: Pin<DataCommandId, FunctionSio<SioOutput>, PullDown>,
spi: Spi<Enabled, D, Pinout, 8>,
) -> Self {
Self {
inner: ST7789DisplayEnabled {
board_select,
data_command,
spi,
},
}
}
pub fn acquire(
&mut self,
) -> &mut ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout> {
self.inner.board_select.set_low();
&mut self.inner
}
}
pub struct ST7789DisplayEnabled<
BoardSelectId: PinId,
DataCommandId: PinId,
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
> {
board_select: Pin<BoardSelectId, FunctionSio<SioOutput>, PullDown>,
data_command: Pin<DataCommandId, FunctionSio<SioOutput>, PullDown>,
spi: Spi<Enabled, D, Pinout, 8>,
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>>
ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>
{
pub fn send_command(&mut self, step: &Step, timer: &mut Timer) {
step.send_command(&mut self.spi, &mut self.data_command);
if let Some(delay) = step.delay {
timer.delay_ms(delay);
}
}
pub fn send_buf(&mut self, frame: &[u8]) {
// let _ = DISPOFF.send_command(&mut self.spi, &mut self.data_command);
let _ = self.data_command.set_low();
let _ = self.spi.write(&[RAMWR]);
let _ = self.data_command.set_high();
let _ = self.spi.write(&frame);
// let _ = DISPON.send_command(&mut self.spi, &mut self.data_command);
}
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>> Drop
for ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>
{
fn drop(&mut self) {
self.board_select.set_high();
}
}

View File

@ -33,9 +33,9 @@ use std::{error::Error, fmt};
/// statement.
pub trait FatalError: Error {}
/// ResultExt<A, FE, E> represents a return value that might be a success, might be a fatal error, or
/// Result<A, FE, E> represents a return value that might be a success, might be a fatal error, or
/// might be a normal handleable error.
pub enum ResultExt<A, E, FE> {
pub enum Result<A, E, FE> {
/// The operation was successful
Ok(A),
/// Ordinary errors. These should be handled and the application should recover gracefully.
@ -45,72 +45,72 @@ pub enum ResultExt<A, E, FE> {
Fatal(FE),
}
impl<A, E, FE> ResultExt<A, E, FE> {
impl<A, E, FE> Result<A, E, FE> {
/// Apply an infallible function to a successful value.
pub fn map<B, O>(self, mapper: O) -> ResultExt<B, E, FE>
pub fn map<B, O>(self, mapper: O) -> Result<B, E, FE>
where
O: FnOnce(A) -> B,
{
match self {
ResultExt::Ok(val) => ResultExt::Ok(mapper(val)),
ResultExt::Err(err) => ResultExt::Err(err),
ResultExt::Fatal(err) => ResultExt::Fatal(err),
Result::Ok(val) => Result::Ok(mapper(val)),
Result::Err(err) => Result::Err(err),
Result::Fatal(err) => Result::Fatal(err),
}
}
/// Apply a potentially fallible function to a successful value.
///
/// Like `Result.and_then`, the mapping function can itself fail.
pub fn and_then<B, O>(self, handler: O) -> ResultExt<B, E, FE>
pub fn and_then<B, O>(self, handler: O) -> Result<B, E, FE>
where
O: FnOnce(A) -> ResultExt<B, E, FE>,
O: FnOnce(A) -> Result<B, E, FE>,
{
match self {
ResultExt::Ok(val) => handler(val),
ResultExt::Err(err) => ResultExt::Err(err),
ResultExt::Fatal(err) => ResultExt::Fatal(err),
Result::Ok(val) => handler(val),
Result::Err(err) => Result::Err(err),
Result::Fatal(err) => Result::Fatal(err),
}
}
/// Map a normal error from one type to another. This is useful for converting an error from
/// one type to another, especially in re-throwing an underlying error. `?` syntax does not
/// work with `Result`, so you will likely need to use this a lot.
pub fn map_err<F, O>(self, mapper: O) -> ResultExt<A, F, FE>
pub fn map_err<F, O>(self, mapper: O) -> Result<A, F, FE>
where
O: FnOnce(E) -> F,
{
match self {
ResultExt::Ok(val) => ResultExt::Ok(val),
ResultExt::Err(err) => ResultExt::Err(mapper(err)),
ResultExt::Fatal(err) => ResultExt::Fatal(err),
Result::Ok(val) => Result::Ok(val),
Result::Err(err) => Result::Err(mapper(err)),
Result::Fatal(err) => Result::Fatal(err),
}
}
/// Provide a function to use to recover from (or simply re-throw) an error.
pub fn or_else<O, F>(self, handler: O) -> ResultExt<A, F, FE>
pub fn or_else<O, F>(self, handler: O) -> Result<A, F, FE>
where
O: FnOnce(E) -> ResultExt<A, F, FE>,
O: FnOnce(E) -> Result<A, F, FE>,
{
match self {
ResultExt::Ok(val) => ResultExt::Ok(val),
ResultExt::Err(err) => handler(err),
ResultExt::Fatal(err) => ResultExt::Fatal(err),
Result::Ok(val) => Result::Ok(val),
Result::Err(err) => handler(err),
Result::Fatal(err) => Result::Fatal(err),
}
}
}
/// Convert from a normal `Result` type to a `ResultExt` type. The error condition for a `Result` will
/// Convert from a normal `Result` type to a `Result` type. The error condition for a `Result` will
/// be treated as `Result::Err`, never `Result::Fatal`.
impl<A, E, FE> From<std::result::Result<A, E>> for ResultExt<A, E, FE> {
impl<A, E, FE> From<std::result::Result<A, E>> for Result<A, E, FE> {
fn from(r: std::result::Result<A, E>) -> Self {
match r {
Ok(val) => ResultExt::Ok(val),
Err(err) => ResultExt::Err(err),
Ok(val) => Result::Ok(val),
Err(err) => Result::Err(err),
}
}
}
impl<A, E, FE> fmt::Debug for ResultExt<A, E, FE>
impl<A, E, FE> fmt::Debug for Result<A, E, FE>
where
A: fmt::Debug,
FE: fmt::Debug,
@ -118,14 +118,14 @@ where
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResultExt::Ok(val) => f.write_fmt(format_args!("Result::Ok {:?}", val)),
ResultExt::Err(err) => f.write_fmt(format_args!("Result::Err {:?}", err)),
ResultExt::Fatal(err) => f.write_fmt(format_args!("Result::Fatal {:?}", err)),
Result::Ok(val) => f.write_fmt(format_args!("Result::Ok {:?}", val)),
Result::Err(err) => f.write_fmt(format_args!("Result::Err {:?}", err)),
Result::Fatal(err) => f.write_fmt(format_args!("Result::Fatal {:?}", err)),
}
}
}
impl<A, E, FE> PartialEq for ResultExt<A, E, FE>
impl<A, E, FE> PartialEq for Result<A, E, FE>
where
A: PartialEq,
FE: PartialEq,
@ -133,34 +133,27 @@ where
{
fn eq(&self, rhs: &Self) -> bool {
match (self, rhs) {
(ResultExt::Ok(val), ResultExt::Ok(rhs)) => val == rhs,
(ResultExt::Err(_), ResultExt::Err(_)) => true,
(ResultExt::Fatal(_), ResultExt::Fatal(_)) => true,
(Result::Ok(val), Result::Ok(rhs)) => val == rhs,
(Result::Err(_), Result::Err(_)) => true,
(Result::Fatal(_), Result::Fatal(_)) => true,
_ => false,
}
}
}
/// Convenience function to create an ok value.
pub fn ok<A, E: Error, FE: FatalError>(val: A) -> ResultExt<A, E, FE> {
ResultExt::Ok(val)
pub fn ok<A, E: Error, FE: FatalError>(val: A) -> Result<A, E, FE> {
Result::Ok(val)
}
/// Convenience function to create an error value.
pub fn error<A, E: Error, FE: FatalError>(err: E) -> ResultExt<A, E, FE> {
ResultExt::Err(err)
pub fn error<A, E: Error, FE: FatalError>(err: E) -> Result<A, E, FE> {
Result::Err(err)
}
/// Convenience function to create a fatal value.
pub fn fatal<A, E: Error, FE: FatalError>(err: FE) -> ResultExt<A, E, FE> {
ResultExt::Fatal(err)
}
pub fn result_as_fatal<A, E: Error, FE: FatalError>(result: Result<A, FE>) -> ResultExt<A, E, FE> {
match result {
Ok(a) => ResultExt::Ok(a),
Err(err) => ResultExt::Fatal(err),
}
pub fn fatal<A, E: Error, FE: FatalError>(err: FE) -> Result<A, E, FE> {
Result::Fatal(err)
}
/// Return early from the current function if the value is a fatal error.
@ -168,9 +161,9 @@ pub fn result_as_fatal<A, E: Error, FE: FatalError>(result: Result<A, FE>) -> Re
macro_rules! return_fatal {
($x:expr) => {
match $x {
ResultExt::Fatal(err) => return ResultExt::Fatal(err),
ResultExt::Err(err) => Err(err),
ResultExt::Ok(val) => Ok(val),
Result::Fatal(err) => return Result::Fatal(err),
Result::Err(err) => Err(err),
Result::Ok(val) => Ok(val),
}
};
}
@ -180,9 +173,9 @@ macro_rules! return_fatal {
macro_rules! return_error {
($x:expr) => {
match $x {
ResultExt::Ok(val) => val,
ResultExt::Err(err) => return ResultExt::Err(err),
ResultExt::Fatal(err) => return ResultExt::Fatal(err),
Result::Ok(val) => val,
Result::Err(err) => return Result::Err(err),
Result::Fatal(err) => return Result::Fatal(err),
}
};
}
@ -217,19 +210,19 @@ mod test {
#[test]
fn it_can_map_things() {
let success: ResultExt<i32, Error, FatalError> = ok(15);
let success: Result<i32, Error, FatalError> = ok(15);
assert_eq!(ok(16), success.map(|v| v + 1));
}
#[test]
fn it_can_chain_success() {
let success: ResultExt<i32, Error, FatalError> = ok(15);
let success: Result<i32, Error, FatalError> = ok(15);
assert_eq!(ok(16), success.and_then(|v| ok(v + 1)));
}
#[test]
fn it_can_handle_an_error() {
let failure: ResultExt<i32, Error, FatalError> = error(Error::Error);
let failure: Result<i32, Error, FatalError> = error(Error::Error);
assert_eq!(
ok::<i32, Error, FatalError>(16),
failure.or_else(|_| ok(16))
@ -238,7 +231,7 @@ mod test {
#[test]
fn early_exit_on_fatal() {
fn ok_func() -> ResultExt<i32, Error, FatalError> {
fn ok_func() -> Result<i32, Error, FatalError> {
let value = return_fatal!(ok::<i32, Error, FatalError>(15));
match value {
Ok(_) => ok(14),
@ -246,7 +239,7 @@ mod test {
}
}
fn err_func() -> ResultExt<i32, Error, FatalError> {
fn err_func() -> Result<i32, Error, FatalError> {
let value = return_fatal!(error::<i32, Error, FatalError>(Error::Error));
match value {
Ok(_) => panic!("shouldn't have gotten here"),
@ -254,7 +247,7 @@ mod test {
}
}
fn fatal_func() -> ResultExt<i32, Error, FatalError> {
fn fatal_func() -> Result<i32, Error, FatalError> {
let _ = return_fatal!(fatal::<i32, Error, FatalError>(FatalError::FatalError));
panic!("failed to bail");
}
@ -266,18 +259,18 @@ mod test {
#[test]
fn it_can_early_exit_on_all_errors() {
fn ok_func() -> ResultExt<i32, Error, FatalError> {
fn ok_func() -> Result<i32, Error, FatalError> {
let value = return_error!(ok::<i32, Error, FatalError>(15));
assert_eq!(value, 15);
ok(14)
}
fn err_func() -> ResultExt<i32, Error, FatalError> {
fn err_func() -> Result<i32, Error, FatalError> {
return_error!(error::<i32, Error, FatalError>(Error::Error));
panic!("failed to bail");
}
fn fatal_func() -> ResultExt<i32, Error, FatalError> {
fn fatal_func() -> Result<i32, Error, FatalError> {
return_error!(fatal::<i32, Error, FatalError>(FatalError::FatalError));
panic!("failed to bail");
}

View File

@ -1,4 +1,3 @@
[toolchain]
channel = "1.85.0"
channel = "1.81.0"
targets = [ "wasm32-unknown-unknown", "thumbv6m-none-eabi" ]
components = [ "rustfmt", "rust-analyzer", "clippy" ]

View File

@ -7,6 +7,5 @@ license = "GPL-3.0-only"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std = "1.13.0"
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4" }

View File

@ -66,8 +66,9 @@ impl Screenplay {
/// This function currently returns no errors, instead panicing if anything goes wrong.
pub fn new(gtk_app: &gtk::Application, screens: Vec<Screen>) -> Result<Self, Error> {
let window = gtk::ApplicationWindow::new(gtk_app);
window.show();
let (sender, receiver) = async_std::channel::unbounded();
let (sender, receiver) = gtk::glib::MainContext::channel(gtk::glib::Priority::DEFAULT);
let layout = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
@ -89,15 +90,11 @@ impl Screenplay {
layout.append(&frame);
listbox.connect_row_activated(move |_, row| {
let sender = sender.clone();
let row = row.clone();
glib::spawn_future_local(async move {
match row.index() {
-1 => sender.send(Action::Deselect).await,
idx => sender.send(Action::SelectPage(idx as usize)).await,
}
.unwrap()
});
match row.index() {
-1 => sender.send(Action::Deselect),
idx => sender.send(Action::SelectPage(idx as usize)),
}
.unwrap()
});
screens.iter().for_each(|Screen { title, .. }| {
@ -111,14 +108,8 @@ impl Screenplay {
let storybook = Self { frame, screens };
{
glib::spawn_future_local({
let mut storybook = storybook.clone();
async move {
while let Ok(msg) = receiver.recv().await {
storybook.process_action(msg);
}
}
});
let mut storybook = storybook.clone();
receiver.attach(None, move |message| storybook.process_action(message));
}
Ok(storybook)

View File

@ -10,7 +10,7 @@ use nom::{
IResult, Parser,
};
use serde::{Deserialize, Serialize};
use std::{fmt::Display, num::ParseIntError, time::Duration};
use std::{fmt::Write, num::ParseIntError, time::Duration};
impl From<ParseSizeError> for Error {
fn from(_: ParseSizeError) -> Self {
@ -142,9 +142,9 @@ impl From<&GameType> for String {
}
}
impl Display for GameType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", String::from(self))
impl ToString for GameType {
fn to_string(&self) -> String {
String::from(self)
}
}
@ -189,15 +189,12 @@ pub struct Tree {
pub root: Node,
}
impl Display for Tree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({})", self.root)
impl ToString for Tree {
fn to_string(&self) -> String {
format!("({})", self.root.to_string())
}
}
struct Properties<'a>(&'a Vec<Property>);
struct Nodes<'a>(&'a Vec<Node>);
#[derive(Clone, Debug, PartialEq)]
pub struct Node {
pub properties: Vec<Property>,
@ -267,12 +264,8 @@ impl Node {
}
}
impl Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, ";{}", Properties(&self.properties))?;
write!(f, "{}", Nodes(&self.next))?;
Ok(())
/*
impl ToString for Node {
fn to_string(&self) -> String {
let props = self
.properties
.iter()
@ -288,36 +281,11 @@ impl Display for Node {
} else {
self.next
.iter()
.map(|node| write!(f, "({})", node.to_string()))
.map(|node| format!("({})", node.to_string()))
.collect::<Vec<String>>()
.join("")
};
write!(f, ";{}{}", props, next)
*/
}
}
impl Display for Properties<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for property in self.0.iter() {
write!(f, "{}", property)?;
}
Ok(())
}
}
impl Display for Nodes<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0.len() == 1 {
for node in self.0.iter() {
write!(f, "{}", node)?;
}
} else {
for node in self.0.iter() {
write!(f, "({})", node)?;
}
}
Ok(())
format!(";{}{}", props, next)
}
}
@ -492,96 +460,95 @@ pub struct UnknownProperty {
pub value: String,
}
impl Display for Property {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl ToString for Property {
fn to_string(&self) -> String {
match self {
Property::Move((color, Move::Move(mv))) => {
write!(f, "{}[{}]", color.abbreviation(), mv)
format!("{}[{}]", color.abbreviation(), mv)
}
Property::Move((color, Move::Pass)) => {
write!(f, "{}[]", color.abbreviation())
format!("{}[]", color.abbreviation())
}
Property::TimeLeft((color, time)) => {
write!(f, "{}[{}]", color.abbreviation(), time.as_secs())
format!("{}[{}]", color.abbreviation(), time.as_secs())
}
Property::Comment(value) => write!(f, "C[{}]", value),
Property::Annotation(Annotation::BadMove) => write!(f, "BM[]"),
Property::Annotation(Annotation::DoubtfulMove) => write!(f, "DO[]"),
Property::Annotation(Annotation::InterestingMove) => write!(f, "IT[]"),
Property::Annotation(Annotation::Tesuji) => write!(f, "TE[]"),
Property::Application(app) => write!(f, "AP[{}]", app),
Property::Charset(set) => write!(f, "CA[{}]", set),
Property::FileFormat(ff) => write!(f, "FF[{}]", ff),
Property::GameType(gt) => write!(f, "GM[{}]", gt),
Property::Comment(value) => format!("C[{}]", value),
Property::Annotation(Annotation::BadMove) => "BM[]".to_owned(),
Property::Annotation(Annotation::DoubtfulMove) => "DO[]".to_owned(),
Property::Annotation(Annotation::InterestingMove) => "IT[]".to_owned(),
Property::Annotation(Annotation::Tesuji) => "TE[]".to_owned(),
Property::Application(app) => format!("AP[{}]", app),
Property::Charset(set) => format!("CA[{}]", set),
Property::FileFormat(ff) => format!("FF[{}]", ff),
Property::GameType(gt) => format!("GM[{}]", gt.to_string()),
Property::VariationDisplay => unimplemented!(),
Property::BoardSize(Size { width, height }) => {
if width == height {
write!(f, "SZ[{}]", width)
format!("SZ[{}]", width)
} else {
write!(f, "SZ[{}:{}]", width, height)
format!("SZ[{}:{}]", width, height)
}
}
Property::SetupBlackStones(positions) => {
write!(f, "AB[{}]", positions.compressed_list(),)
format!("AB[{}]", positions.compressed_list(),)
}
Property::ClearStones(positions) => {
write!(f, "AE[{}]", positions.compressed_list(),)
format!("AE[{}]", positions.compressed_list(),)
}
Property::SetupWhiteStones(positions) => {
write!(f, "AW[{}]", positions.compressed_list(),)
format!("AW[{}]", positions.compressed_list(),)
}
Property::NextPlayer(color) => write!(f, "PL[{}]", color.abbreviation()),
Property::Evaluation(Evaluation::Even) => write!(f, "DM[]"),
Property::Evaluation(Evaluation::GoodForBlack) => write!(f, "GB[]"),
Property::Evaluation(Evaluation::GoodForWhite) => write!(f, "GW[]"),
Property::Evaluation(Evaluation::Unclear) => write!(f, "UC[]"),
Property::Hotspot => write!(f, "HO[]"),
Property::Value(value) => write!(f, "V[{}]", value),
Property::Annotator(value) => write!(f, "AN[{}]", value),
Property::BlackRank(value) => write!(f, "BR[{}]", value),
Property::BlackTeam(value) => write!(f, "BT[{}]", value),
Property::Copyright(value) => write!(f, "CP[{}]", value),
Property::NextPlayer(color) => format!("PL[{}]", color.abbreviation()),
Property::Evaluation(Evaluation::Even) => "DM[]".to_owned(),
Property::Evaluation(Evaluation::GoodForBlack) => "GB[]".to_owned(),
Property::Evaluation(Evaluation::GoodForWhite) => "GW[]".to_owned(),
Property::Evaluation(Evaluation::Unclear) => "UC[]".to_owned(),
Property::Hotspot => "HO[]".to_owned(),
Property::Value(value) => format!("V[{}]", value),
Property::Annotator(value) => format!("AN[{}]", value),
Property::BlackRank(value) => format!("BR[{}]", value),
Property::BlackTeam(value) => format!("BT[{}]", value),
Property::Copyright(value) => format!("CP[{}]", value),
Property::EventDates(_) => unimplemented!(),
Property::EventName(value) => write!(f, "EV[{}]", value),
Property::GameName(value) => write!(f, "GN[{}]", value),
Property::ExtraGameInformation(value) => write!(f, "GC[{}]", value),
Property::GameOpening(value) => write!(f, "ON[{}]", value),
Property::Overtime(value) => write!(f, "OT[{}]", value),
Property::BlackPlayer(value) => write!(f, "PB[{}]", value),
Property::GameLocation(value) => write!(f, "PC[{}]", value),
Property::WhitePlayer(value) => write!(f, "PW[{}]", value),
Property::EventName(value) => format!("EV[{}]", value),
Property::GameName(value) => format!("GN[{}]", value),
Property::ExtraGameInformation(value) => format!("GC[{}]", value),
Property::GameOpening(value) => format!("ON[{}]", value),
Property::Overtime(value) => format!("OT[{}]", value),
Property::BlackPlayer(value) => format!("PB[{}]", value),
Property::GameLocation(value) => format!("PC[{}]", value),
Property::WhitePlayer(value) => format!("PW[{}]", value),
Property::Result(_) => unimplemented!(),
Property::Round(value) => write!(f, "RO[{}]", value),
Property::Ruleset(value) => write!(f, "RU[{}]", value),
Property::Source(value) => write!(f, "SO[{}]", value),
Property::TimeLimit(value) => write!(f, "TM[{}]", value.as_secs()),
Property::User(value) => write!(f, "US[{}]", value),
Property::WhiteRank(value) => write!(f, "WR[{}]", value),
Property::WhiteTeam(value) => write!(f, "WT[{}]", value),
Property::Round(value) => format!("RO[{}]", value),
Property::Ruleset(value) => format!("RU[{}]", value),
Property::Source(value) => format!("SO[{}]", value),
Property::TimeLimit(value) => format!("TM[{}]", value.as_secs()),
Property::User(value) => format!("US[{}]", value),
Property::WhiteRank(value) => format!("WR[{}]", value),
Property::WhiteTeam(value) => format!("WT[{}]", value),
Property::Territory(Color::White, positions) => {
write!(f, "TW[{}]", Positions(positions))
positions
.iter()
.fold("TW".to_owned(), |mut output, Position(p)| {
let _ = write!(output, "{}", p);
output
})
}
Property::Territory(Color::Black, positions) => {
write!(f, "TB[{}]", Positions(positions))
positions
.iter()
.fold("TB".to_owned(), |mut output, Position(p)| {
let _ = write!(output, "{}", p);
output
})
}
Property::Unknown(UnknownProperty { ident, value }) => {
write!(f, "{}[{}]", ident, value)
format!("{}[{}]", ident, value)
}
}
}
}
struct Positions<'a>(&'a Vec<Position>);
impl Display for Positions<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for Position(p) in self.0.iter() {
write!(f, "{}", p)?;
}
Ok(())
}
}
pub fn parse_collection<'a, E: nom::error::ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, Vec<Tree>, E> {

View File

@ -53,7 +53,6 @@ pub enum Error {
// InvalidField,
// InvalidBoardSize,
Incomplete,
#[allow(dead_code)]
InvalidSgf(VerboseNomError),
}

View File

@ -1,7 +1,11 @@
#[cfg(test)]
mod tests {
use chrono::{DateTime, FixedOffset, NaiveDate, TimeZone};
use chrono_tz::America::{New_York, Phoenix};
use chrono_tz::{
America::{New_York, Phoenix},
Tz,
US::Mountain,
};
#[test]
fn it_saves_with_offset() {
@ -33,7 +37,7 @@ mod tests {
date.with_timezone(&New_York),
FixedOffset::west_opt(4 * 60 * 60)
.unwrap()
.with_ymd_and_hms(2023, 10, 14, 3, 0, 0)
.with_ymd_and_hms(2023, 10, 14, 03, 0, 0)
.unwrap()
);
assert_eq!(

View File

@ -62,7 +62,7 @@ impl<T> Tree<T> {
// Do a depth-first-search in order to get the path to a node. Start with a naive recursive
// implementation, then switch to a stack-based implementation in order to avoid exceeding the
// stack.
pub fn path_to<F>(&self, _f: F) -> Vec<Node<T>>
pub fn path_to<F>(&self, f: F) -> Vec<Node<T>>
where
F: FnOnce(&T) -> bool + Copy,
{
@ -206,20 +206,17 @@ mod tests {
assert!(tree2.find_bfs(|val| *val == "17").is_some());
}
/*
#[test]
fn path_to_on_empty_tree_returns_empty() {
let tree: Tree<&str> = Tree::default();
assert_eq!(tree.path_to(|val| *val == "i"), vec![]);
}
*/
// A
// B G H
// C I
// D E F
/*
#[test]
fn it_can_find_a_path_to_a_node() {
let (tree, a) = Tree::new("A");
@ -235,5 +232,4 @@ mod tests {
assert_eq!(tree.path_to(|val| *val == "z"), vec![]);
assert_eq!(tree.path_to(|val| *val == "i"), vec![a, h, i]);
}
*/
}

View File

@ -1,35 +0,0 @@
[package]
name = "visions"
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 = { version = "1.13.0" }
async-trait = { version = "0.1.83" }
authdb = { path = "../../authdb/" }
axum = { version = "0.7.9", features = [ "macros" ] }
chrono = { version = "0.4.39", features = ["serde"] }
futures = { version = "0.3.31" }
include_dir = { version = "0.7.4" }
lazy_static = { version = "1.5.0" }
mime = { version = "0.3.17" }
mime_guess = { version = "2.0.5" }
pretty_env_logger = { version = "0.5.0" }
result-extended = { path = "../../result-extended" }
rusqlite = { version = "0.32.1" }
rusqlite_migration = { version = "1.3.1", features = ["from-directory"] }
serde = { version = "1" }
serde_json = { version = "*" }
thiserror = { version = "2.0.3" }
tokio = { version = "1", features = [ "full" ] }
tokio-stream = { version = "0.1.16" }
tower-http = { version = "0.6.2", features = ["cors"] }
typeshare = { version = "1.0.4" }
urlencoding = { version = "2.1.3" }
uuid = { version = "1.11.0", features = ["v4"] }
[dev-dependencies]
cool_asserts = "2.0.3"
axum-test = "16.4.1"

View File

@ -1,23 +0,0 @@
version: '3'
tasks:
build:
cmds:
- cargo watch -x build
test:
cmds:
- cargo watch -x 'nextest run'
dev:
cmds:
- cargo watch -x run
lint:
cmds:
- cargo watch -x clippy
release:
cmds:
- task lint
- cargo build --release

View File

@ -1,41 +0,0 @@
CREATE TABLE users(
uuid TEXT PRIMARY KEY,
name TEXT UNIQUE,
password TEXT,
admin BOOLEAN,
state TEXT
);
CREATE TABLE sessions(
id TEXT PRIMARY KEY,
user_id TEXT,
FOREIGN KEY(user_id) REFERENCES users(uuid)
);
CREATE TABLE games(
id TEXT PRIMARY KEY,
type_ TEXT,
gm TEXT,
name TEXT,
FOREIGN KEY(gm) REFERENCES users(uuid)
);
CREATE TABLE characters(
uuid TEXT PRIMARY KEY,
game TEXT,
data TEXT,
FOREIGN KEY(game) REFERENCES games(uuid)
);
CREATE TABLE roles(
user_id TEXT,
game_id TEXT,
role TEXT,
FOREIGN KEY(user_id) REFERENCES users(uuid),
FOREIGN KEY(game_id) REFERENCES games(uuid)
);

View File

@ -1 +0,0 @@
{ "type_": "Candela", "name": "Soren Jensen", "pronouns": "he/him", "circle": "Circle of the Bluest Sky", "style": "dapper gentleman", "catalyst": "a cursed book", "question": "What were the contents of that book?", "nerve": { "type_": "nerve", "drives": { "current": 1, "max": 2 }, "resistances": { "current": 0, "max": 3 }, "move": { "gilded": false, "score": 2 }, "strike": { "gilded": false, "score": 1 }, "control": { "gilded": true, "score": 0 } }, "cunning": { "type_": "cunning", "drives": { "current": 1, "max": 1 }, "resistances": { "current": 0, "max": 3 }, "sway": { "gilded": false, "score": 0 }, "read": { "gilded": false, "score": 0 }, "hide": { "gilded": false, "score": 0 } }, "intuition": { "type_": "intuition", "drives": { "current": 0, "max": 0 }, "resistances": { "current": 0, "max": 3 }, "survey": { "gilded": false, "score": 0 }, "focus": { "gilded": false, "score": 0 }, "sense": { "gilded": false, "score": 0 } }, "role": "Slink", "role_abilities": [ "Scout: If you have time to observe a location, you can spend 1 Intuition to ask a question: What do I notice here that others do not see? What in this place might be of use to us? What path should we follow?" ], "specialty": "Detective", "specialty_abilities": [ "Mind Palace: When you want to figure out how two clues might relate or what path they should point you towards, burn 1 Intution resistance. The GM will give you the information you have deduced." ] }

View File

@ -1,156 +0,0 @@
use std::{
collections::{hash_map::Iter, HashMap}, fmt::{self, Display}, fs, io::Read, path::PathBuf
};
use mime::Mime;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use typeshare::typeshare;
#[derive(Debug, Error)]
pub enum Error {
#[error("Asset could not be found")]
NotFound,
#[error("Asset could not be opened")]
Inaccessible,
#[error("An unexpected IO error occured when retrieving an asset {0}")]
Unexpected(std::io::Error),
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
use std::io::ErrorKind::*;
match err.kind() {
NotFound => Error::NotFound,
PermissionDenied | UnexpectedEof => Error::Inaccessible,
_ => Error::Unexpected(err),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct AssetId(String);
impl AssetId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for AssetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AssetId({})", self.0)
}
}
impl From<&str> for AssetId {
fn from(s: &str) -> Self {
AssetId(s.to_owned())
}
}
impl From<String> for AssetId {
fn from(s: String) -> Self {
AssetId(s)
}
}
pub struct AssetIter<'a>(Iter<'a, AssetId, String>);
impl<'a> Iterator for AssetIter<'a> {
type Item = (&'a AssetId, &'a String);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub trait Assets {
fn assets(&self) -> AssetIter;
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error>;
}
pub struct FsAssets {
assets: HashMap<AssetId, String>,
}
impl FsAssets {
pub fn new(path: PathBuf) -> Self {
let dir = fs::read_dir(path).unwrap();
let mut assets = HashMap::new();
for dir_ent in dir {
let path = dir_ent.unwrap().path();
let file_name = path.file_name().unwrap().to_str().unwrap();
assets.insert(AssetId::from(file_name), path.to_str().unwrap().to_owned());
}
Self {
assets,
}
}
}
impl Assets for FsAssets {
fn assets(&self) -> AssetIter {
AssetIter(self.assets.iter())
}
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
let path = match self.assets.get(&asset_id) {
Some(asset) => Ok(asset),
None => Err(Error::NotFound),
}?;
let mime = mime_guess::from_path(path).first().unwrap();
let mut content: Vec<u8> = Vec::new();
let mut file = std::fs::File::open(path)?;
file.read_to_end(&mut content)?;
Ok((mime, content))
}
}
#[cfg(test)]
pub mod mocks {
use std::collections::HashMap;
use super::*;
pub struct MemoryAssets {
asset_paths: HashMap<AssetId, String>,
assets: HashMap<AssetId, Vec<u8>>,
}
impl MemoryAssets {
pub fn new(data: Vec<(AssetId, String, Vec<u8>)>) -> Self {
let mut asset_paths = HashMap::new();
let mut assets = HashMap::new();
data.into_iter().for_each(|(asset, path, data)| {
asset_paths.insert(asset.clone(), path);
assets.insert(asset, data);
});
Self {
asset_paths,
assets,
}
}
}
impl Assets for MemoryAssets {
fn assets(&self) -> AssetIter<'_> {
AssetIter(self.asset_paths.iter())
}
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
match (self.asset_paths.get(&asset_id), self.assets.get(&asset_id)) {
(Some(path), Some(data)) => {
let mime = mime_guess::from_path(path).first().unwrap();
Ok((mime, data.to_vec()))
}
_ => Err(Error::NotFound),
}
}
}
}

View File

@ -1,525 +0,0 @@
use std::{collections::HashMap, sync::Arc};
use async_std::sync::RwLock;
use chrono::{DateTime, Duration, TimeDelta, Utc};
use mime::Mime;
use result_extended::{error, fatal, ok, result_as_fatal, return_error, ResultExt};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use typeshare::typeshare;
use uuid::Uuid;
use crate::{
asset_db::{self, AssetId, Assets},
database::{CharacterId, Database, GameId, SessionId, UserId},
types::AccountState,
types::{AppError, FatalError, GameOverview, Message, Rgb, Tabletop, User, UserOverview},
};
const DEFAULT_BACKGROUND_COLOR: Rgb = Rgb {
red: 0xca,
green: 0xb9,
blue: 0xbb,
};
#[derive(Clone, Serialize)]
#[typeshare]
pub struct Status {
pub ok: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", content = "content")]
#[typeshare]
pub enum AuthResponse {
Success(SessionId),
PasswordReset(SessionId),
Locked,
}
#[derive(Debug)]
struct WebsocketClient {
sender: Option<UnboundedSender<Message>>,
}
pub struct AppState {
pub asset_store: Box<dyn Assets + Sync + Send + 'static>,
pub db: Box<dyn Database + Sync + Send + 'static>,
pub clients: HashMap<String, WebsocketClient>,
pub tabletop: Tabletop,
}
#[derive(Clone)]
pub struct Core(Arc<RwLock<AppState>>);
impl Core {
pub fn new<A, DB>(assetdb: A, db: DB) -> Self
where
A: Assets + Sync + Send + 'static,
DB: Database + Sync + Send + 'static,
{
Self(Arc::new(RwLock::new(AppState {
asset_store: Box::new(assetdb),
db: Box::new(db),
clients: HashMap::new(),
tabletop: Tabletop {
background_color: DEFAULT_BACKGROUND_COLOR,
background_image: None,
},
})))
}
pub async fn status(&self) -> ResultExt<Status, AppError, FatalError> {
/*
let state = self.0.write().await;
let admin_user = return_error!(match state.db.user(&UserId::from("admin")).await {
Ok(Some(admin_user)) => ok(admin_user),
Ok(None) => {
return ok(Status {
admin_enabled: false,
});
}
Err(err) => fatal(err),
});
ok(Status {
ok: !admin_user.password.is_empty(),
})
*/
ok(Status { ok: true })
}
pub async fn register_client(&self) -> String {
let mut state = self.0.write().await;
let uuid = Uuid::new_v4().simple().to_string();
let client = WebsocketClient { sender: None };
state.clients.insert(uuid.clone(), client);
uuid
}
pub async fn unregister_client(&self, client_id: String) {
let mut state = self.0.write().await;
let _ = state.clients.remove(&client_id);
}
pub async fn connect_client(&self, client_id: String) -> UnboundedReceiver<Message> {
let mut state = self.0.write().await;
match state.clients.get_mut(&client_id) {
Some(client) => {
let (tx, rx) = unbounded_channel();
client.sender = Some(tx);
rx
}
None => {
unimplemented!();
}
}
}
pub async fn user_by_username(
&self,
username: &str,
) -> ResultExt<Option<User>, AppError, FatalError> {
let state = self.0.read().await;
match state.db.user_by_username(username).await {
Ok(Some(user_row)) => ok(Some(User::from(user_row))),
Ok(None) => ok(None),
Err(err) => fatal(err),
}
}
pub async fn list_users(&self) -> ResultExt<Vec<UserOverview>, AppError, FatalError> {
let users = self.0.write().await.db.users().await;
match users {
Ok(users) => ok(users
.into_iter()
.map(|user| UserOverview {
id: user.id,
name: user.name,
state: user.state,
is_admin: user.admin,
})
.collect()),
Err(err) => fatal(err),
}
}
pub async fn user(
&self,
user_id: UserId,
) -> ResultExt<Option<UserOverview>, AppError, FatalError> {
let users = return_error!(self.list_users().await);
match users.into_iter().find(|user| user.id == user_id) {
Some(user) => ok(Some(user)),
None => return ok(None),
}
}
pub async fn create_user(&self, username: &str) -> ResultExt<UserId, AppError, FatalError> {
let state = self.0.read().await;
match return_error!(self.user_by_username(username).await) {
Some(_) => error(AppError::UsernameUnavailable),
None => match state
.db
.create_user(username, "", false, AccountState::PasswordReset(Utc::now() + Duration::minutes(60)))
.await
{
Ok(user_id) => ok(user_id),
Err(err) => fatal(err),
},
}
}
pub async fn disable_user(&self, _userid: UserId) -> ResultExt<(), AppError, FatalError> {
unimplemented!();
}
pub async fn list_games(&self) -> ResultExt<Vec<GameOverview>, AppError, FatalError> {
let games = self.0.read().await.db.games().await;
match games {
// Ok(games) => ok(games.into_iter().map(|g| Game::from(g)).collect()),
Ok(games) => ok(games
.into_iter()
.map(|game| GameOverview {
id: game.id,
type_: "".to_owned(),
name: game.name,
gm: game.gm,
players: game.players,
})
.collect::<Vec<GameOverview>>()),
Err(err) => fatal(err),
}
}
pub async fn create_game(
&self,
gm: &UserId,
game_type: &str,
game_name: &str,
) -> ResultExt<GameId, AppError, FatalError> {
let state = self.0.read().await;
match state.db.create_game(gm, game_type, game_name).await {
Ok(game_id) => ok(game_id),
Err(err) => fatal(err),
}
}
pub async fn tabletop(&self) -> Tabletop {
self.0.read().await.tabletop.clone()
}
pub async fn get_asset(
&self,
asset_id: AssetId,
) -> ResultExt<(Mime, Vec<u8>), AppError, FatalError> {
ResultExt::from(
self.0
.read()
.await
.asset_store
.get(asset_id.clone())
.map_err(|err| match err {
asset_db::Error::NotFound => AppError::NotFound(format!("{}", asset_id)),
asset_db::Error::Inaccessible => {
AppError::Inaccessible(format!("{}", asset_id))
}
asset_db::Error::Unexpected(err) => AppError::Inaccessible(format!("{}", err)),
}),
)
}
pub async fn available_images(&self) -> Vec<AssetId> {
self.0
.read()
.await
.asset_store
.assets()
.filter_map(
|(asset_id, value)| match mime_guess::from_path(value).first() {
Some(mime) if mime.type_() == mime::IMAGE => Some(asset_id.clone()),
_ => None,
},
)
.collect()
}
pub async fn set_background_image(
&self,
asset: AssetId,
) -> ResultExt<(), AppError, FatalError> {
let tabletop = {
let mut state = self.0.write().await;
state.tabletop.background_image = Some(asset.clone());
state.tabletop.clone()
};
self.publish(Message::UpdateTabletop(tabletop)).await;
ok(())
}
pub async fn get_charsheet(
&self,
id: CharacterId,
) -> ResultExt<Option<serde_json::Value>, AppError, FatalError> {
let state = self.0.write().await;
let cr = state.db.character(&id).await;
match cr {
Ok(Some(row)) => ok(Some(row.data)),
Ok(None) => ok(None),
Err(err) => fatal(err),
}
}
pub async fn publish(&self, message: Message) {
let state = self.0.read().await;
state.clients.values().for_each(|client| {
if let Some(ref sender) = client.sender {
let _ = sender.send(message.clone());
}
});
}
pub async fn save_user(
&self,
id: UserId,
name: &str,
password: &str,
admin: bool,
account_state: AccountState,
) -> ResultExt<UserId, AppError, FatalError> {
let state = self.0.read().await;
match state
.db
.save_user(User {
id,
name: name.to_owned(),
password: password.to_owned(),
admin,
state: account_state,
})
.await
{
Ok(uuid) => ok(uuid),
Err(err) => fatal(err),
}
}
pub async fn set_password(
&self,
uuid: UserId,
password: String,
) -> ResultExt<(), AppError, FatalError> {
let state = self.0.write().await;
let user = match state.db.user(&uuid).await {
Ok(Some(row)) => row,
Ok(None) => return error(AppError::NotFound(uuid.as_str().to_owned())),
Err(err) => return fatal(err),
};
match state
.db
.save_user(User {
password,
state: AccountState::Normal,
..user
})
.await
{
Ok(_) => ok(()),
Err(err) => fatal(err),
}
}
pub async fn auth(
&self,
username: &str,
password: &str,
) -> ResultExt<AuthResponse, AppError, FatalError> {
let now = Utc::now();
let state = self.0.read().await;
let user = state.db.user_by_username(username).await.unwrap().unwrap();
let user_info = return_error!(match state.db.user_by_username(username).await {
Ok(Some(row)) if row.password == password => ok(row),
Ok(_) => error(AppError::AuthFailed),
Err(err) => fatal(err),
});
match user_info.state {
AccountState::Normal => result_as_fatal(state.db.create_session(&user_info.id).await)
.map(|session_id| AuthResponse::Success(session_id)),
AccountState::PasswordReset(exp) => {
if exp < now {
error(AppError::AuthFailed)
} else {
result_as_fatal(state.db.create_session(&user_info.id).await)
.map(|session_id| AuthResponse::PasswordReset(session_id))
}
}
AccountState::Locked => ok(AuthResponse::Locked),
}
}
pub async fn session(
&self,
session_id: &SessionId,
) -> ResultExt<Option<User>, AppError, FatalError> {
let state = self.0.read().await;
match state.db.session(session_id).await {
Ok(Some(user_row)) => ok(Some(User::from(user_row))),
Ok(None) => ok(None),
Err(fatal_error) => fatal(fatal_error),
}
}
pub async fn delete_session(&self, session_id: &SessionId) -> ResultExt<(), AppError, FatalError> {
let state = self.0.read().await;
match state.db.delete_session(session_id).await {
Ok(_) => ok(()),
Err(err) => fatal(err),
}
}
}
fn create_expiration_date() -> DateTime<Utc> {
Utc::now() + TimeDelta::days(365)
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use super::*;
use cool_asserts::assert_matches;
use crate::{asset_db::mocks::MemoryAssets, database::DbConn};
async fn test_core() -> Core {
let assets = MemoryAssets::new(vec![
(
AssetId::from("asset_1"),
"asset_1.png".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_2"),
"asset_2.jpg".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_3"),
"asset_3".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_4"),
"asset_4".to_owned(),
String::from("abcdefg").into_bytes(),
),
(
AssetId::from("asset_5"),
"asset_5".to_owned(),
String::from("abcdefg").into_bytes(),
),
]);
let memory_db: Option<PathBuf> = None;
let conn = DbConn::new(memory_db);
conn.create_user("admin", "aoeu", true, AccountState::Normal)
.await
.unwrap();
conn.create_user(
"gm_1",
"aoeu",
false,
AccountState::PasswordReset(Utc::now()),
)
.await
.unwrap();
Core::new(assets, conn)
}
#[tokio::test]
async fn it_lists_available_images() {
let core = test_core().await;
let image_paths = core.available_images().await;
assert_eq!(image_paths.len(), 2);
}
#[tokio::test]
async fn it_retrieves_an_asset() {
let core = test_core().await;
assert_matches!(core.get_asset(AssetId::from("asset_1")).await, ResultExt::Ok((mime, data)) => {
assert_eq!(mime.type_(), mime::IMAGE);
assert_eq!(data, "abcdefg".as_bytes());
});
}
#[tokio::test]
async fn it_can_retrieve_the_default_tabletop() {
let core = test_core().await;
assert_matches!(core.tabletop().await, Tabletop{ background_color, background_image } => {
assert_eq!(background_color, DEFAULT_BACKGROUND_COLOR);
assert_eq!(background_image, None);
});
}
#[tokio::test]
async fn it_can_change_the_tabletop_background() {
let core = test_core().await;
assert_matches!(
core.set_background_image(AssetId::from("asset_1")).await,
ResultExt::Ok(())
);
assert_matches!(core.tabletop().await, Tabletop{ background_color, background_image } => {
assert_eq!(background_color, DEFAULT_BACKGROUND_COLOR);
assert_eq!(background_image, Some(AssetId::from("asset_1")));
});
}
#[tokio::test]
async fn it_sends_notices_to_clients_on_tabletop_change() {
let core = test_core().await;
let client_id = core.register_client().await;
let mut receiver = core.connect_client(client_id).await;
assert_matches!(
core.set_background_image(AssetId::from("asset_1")).await,
ResultExt::Ok(())
);
match receiver.recv().await {
Some(Message::UpdateTabletop(Tabletop {
background_color,
background_image,
})) => {
assert_eq!(background_color, DEFAULT_BACKGROUND_COLOR);
assert_eq!(background_image, Some(AssetId::from("asset_1")));
}
None => panic!("receiver did not get a message"),
}
}
#[tokio::test]
async fn it_creates_a_sessionid_on_successful_auth() {
let core = test_core().await;
match core.auth("admin", "aoeu").await {
ResultExt::Ok(AuthResponse::Success(session_id)) => {
let st = core.0.read().await;
match st.db.session(&session_id).await {
Ok(Some(user_row)) => assert_eq!(user_row.name, "admin"),
Ok(None) => panic!("no matching user row for the session id"),
Err(err) => panic!("{}", err),
}
}
ResultExt::Ok(AuthResponse::PasswordReset(_)) => panic!("user is in password reset state"),
ResultExt::Ok(AuthResponse::Locked) => panic!("user has been locked"),
ResultExt::Err(err) => panic!("{}", err),
ResultExt::Fatal(err) => panic!("{}", err),
}
}
}

View File

@ -1,398 +0,0 @@
use std::path::Path;
use async_std::channel::Receiver;
use include_dir::{include_dir, Dir};
use lazy_static::lazy_static;
use rusqlite::Connection;
use rusqlite_migration::Migrations;
use crate::{
database::{DatabaseResponse, Request},
types::{AccountState, FatalError, Game, User},
};
use super::{types::GameId, CharacterId, CharsheetRow, DatabaseRequest, SessionId, UserId};
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations");
lazy_static! {
static ref MIGRATIONS: Migrations<'static> =
Migrations::from_directory(&MIGRATIONS_DIR).unwrap();
}
pub struct DiskDb {
conn: Connection,
}
impl DiskDb {
pub fn new<P>(path: Option<P>) -> Result<Self, FatalError>
where
P: AsRef<Path>,
{
let mut conn = match path {
None => Connection::open(":memory:").expect("to create a memory connection"),
Some(path) => Connection::open(path).expect("to create connection"),
};
MIGRATIONS
.to_latest(&mut conn)
.map_err(|err| FatalError::DatabaseMigrationFailure(format!("{}", err)))?;
Ok(DiskDb { conn })
}
pub fn user(&self, id: &UserId) -> Result<Option<User>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT * FROM users WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<User> = stmt
.query_map([id.as_str()], |row| {
Ok(User {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
state: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<User>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(id.as_str().to_owned())),
}
}
pub fn user_by_username(&self, username: &str) -> Result<Option<User>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT * FROM users WHERE name=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<User> = stmt
.query_map([username], |row| {
Ok(User {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
state: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<User>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(username.to_owned())),
}
}
pub fn create_user(
&self,
name: &str,
password: &str,
admin: bool,
state: AccountState,
) -> Result<UserId, FatalError> {
let user_id = UserId::default();
let mut stmt = self
.conn
.prepare("INSERT INTO users VALUES (?, ?, ?, ?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((user_id.as_str(), name, password, admin, state))
.unwrap();
Ok(user_id)
}
pub fn save_user(&self, user: User) -> Result<UserId, FatalError> {
let mut stmt = self
.conn
.prepare("UPDATE users SET name=?, password=?, admin=?, state=? WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((
user.name,
user.password,
user.admin,
user.state,
user.id.as_str(),
))
.unwrap();
Ok(user.id)
}
pub fn users(&self) -> Result<Vec<User>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT * FROM users")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items = stmt
.query_map([], |row| {
Ok(User {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
state: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<User>, rusqlite::Error>>()
.unwrap();
Ok(items)
}
pub fn create_game(
&self,
gm: &UserId,
game_type: &str,
name: &str,
) -> Result<GameId, FatalError> {
let game_id = GameId::new();
let mut stmt = self
.conn
.prepare("INSERT INTO games VALUES (?, ?, ?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((game_id.as_str(), game_type, gm.as_str(), name))
.unwrap();
Ok(game_id)
}
pub fn save_game(&self, game: Game) -> Result<(), FatalError> {
let mut stmt = self
.conn
.prepare("UPDATE games SET gm=? type_=? name=? WHERE id=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
stmt.execute((game.gm.as_str(), game.type_, game.name, game.id.as_str()))
.unwrap();
Ok(())
}
pub fn games(&self) -> Result<Vec<Game>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT * FROM games")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items = stmt
.query_map([], |row| {
Ok(Game {
id: row.get(0).unwrap(),
type_: row.get(1).unwrap(),
gm: row.get(2).unwrap(),
name: row.get(3).unwrap(),
players: vec![],
})
})
.unwrap()
.collect::<Result<Vec<Game>, rusqlite::Error>>()
.unwrap();
Ok(items)
}
pub fn session(&self, session_id: &SessionId) -> Result<Option<User>, FatalError> {
let mut stmt = self.conn
.prepare("SELECT u.uuid, u.name, u.password, u.admin, u.state FROM sessions s INNER JOIN users u ON u.uuid = s.user_id WHERE s.id = ?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<User> = stmt
.query_map([session_id.as_str()], |row| {
Ok(User {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
password: row.get(2).unwrap(),
admin: row.get(3).unwrap(),
state: row.get(4).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<User>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(
session_id.as_str().to_owned(),
)),
}
}
fn create_session(&self, user_id: &UserId) -> Result<SessionId, FatalError> {
match self.user(user_id) {
Ok(Some(_)) => {
let mut stmt = self
.conn
.prepare("INSERT INTO sessions VALUES (?, ?)")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let session_id = SessionId::new();
stmt.execute((session_id.as_str(), user_id.as_str()))
.unwrap();
Ok(session_id)
}
Ok(None) => Err(FatalError::DatabaseKeyMissing),
Err(err) => Err(err),
}
}
fn delete_session(&self, session_id: &SessionId) -> Result<(), FatalError> {
match self.session(session_id) {
Ok(Some(_)) => {
let mut stmt = self.conn.prepare("DELETE FROM sessions WHERE id = ?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let session_id = SessionId::new();
stmt.execute((session_id.as_str(),)).unwrap();
Ok(())
}
Ok(None) => Err(FatalError::DatabaseKeyMissing),
Err(err) => Err(err),
}
}
pub fn character(&self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
let mut stmt = self
.conn
.prepare("SELECT uuid, game, data FROM characters WHERE uuid=?")
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
let items: Vec<CharsheetRow> = stmt
.query_map([id.as_str()], |row| {
let data: String = row.get(2).unwrap();
Ok(CharsheetRow {
id: row.get(0).unwrap(),
game: row.get(1).unwrap(),
data: serde_json::from_str(&data).unwrap(),
})
})
.unwrap()
.collect::<Result<Vec<CharsheetRow>, rusqlite::Error>>()
.unwrap();
match &items[..] {
[] => Ok(None),
[item] => Ok(Some(item.clone())),
_ => Err(FatalError::NonUniqueDatabaseKey(id.as_str().to_owned())),
}
}
pub fn save_character(
&self,
char_id: Option<CharacterId>,
game: GameId,
character: serde_json::Value,
) -> std::result::Result<CharacterId, FatalError> {
match char_id {
None => {
let char_id = CharacterId::new();
let mut stmt = self
.conn
.prepare("INSERT INTO characters VALUES (?, ?, ?)")
.unwrap();
stmt.execute((char_id.as_str(), game.as_str(), character.to_string()))
.unwrap();
Ok(char_id)
}
Some(char_id) => {
let mut stmt = self
.conn
.prepare("UPDATE characters SET data=? WHERE uuid=?")
.unwrap();
stmt.execute((character.to_string(), char_id.as_str()))
.unwrap();
Ok(char_id)
}
}
}
}
pub async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
while let Ok(DatabaseRequest { tx, req }) = requestor.recv().await {
match req {
Request::Charsheet(id) => {
let sheet = db.character(id);
match sheet {
Ok(sheet) => {
tx.send(DatabaseResponse::Charsheet(sheet)).await.unwrap();
}
_ => unimplemented!("errors for Charsheet"),
}
}
Request::CreateUser(username, password, admin, state) => {
let user_id = db.create_user(&username, &password, admin, state).unwrap();
tx.send(DatabaseResponse::SaveUser(user_id)).await.unwrap();
}
Request::CreateGame(_, _, _) => {}
Request::CreateSession(id) => {
let session_id = db.create_session(&id).unwrap();
tx.send(DatabaseResponse::CreateSession(session_id))
.await
.unwrap();
}
Request::DeleteSession(id) => {
db.delete_session(&id).unwrap();
tx.send(DatabaseResponse::DeleteSession).await.unwrap();
}
Request::Games => match db.games() {
Ok(games) => tx.send(DatabaseResponse::Games(games)).await.unwrap(),
_ => unimplemented!("errors for Request::Games"),
},
Request::Game(_game_id) => {
unimplemented!("Request::Game handler");
}
Request::SaveGame(game) => {
let id = game.id.clone();
let save_result = db.save_game(game);
match save_result {
Ok(_) => {
tx.send(DatabaseResponse::SaveGame(id)).await.unwrap();
}
err => panic!("{:?}", err),
}
}
Request::User(uid) => {
let user = db.user(&uid);
match user {
Ok(user) => {
tx.send(DatabaseResponse::User(user)).await.unwrap();
}
err => panic!("{:?}", err),
}
}
Request::UserByUsername(username) => {
let user = db.user_by_username(&username);
match user {
Ok(user) => tx.send(DatabaseResponse::User(user)).await.unwrap(),
err => panic!("{:?}", err),
}
}
Request::SaveUser(user) => {
let user_id = db.save_user(user);
match user_id {
Ok(user_id) => {
tx.send(DatabaseResponse::SaveUser(user_id)).await.unwrap();
}
err => panic!("{:?}", err),
}
}
Request::Session(session_id) => {
let user = db.session(&session_id);
match user {
Ok(user) => tx.send(DatabaseResponse::Session(user)).await.unwrap(),
err => panic!("{:?}", err),
}
}
Request::Users => {
let users = db.users();
match users {
Ok(users) => {
tx.send(DatabaseResponse::Users(users)).await.unwrap();
}
_ => unimplemented!("request::Users"),
}
}
}
}
}

View File

@ -1,230 +0,0 @@
mod disk_db;
mod types;
use std::path::Path;
use async_std::channel::{bounded, Sender};
use async_trait::async_trait;
use disk_db::{db_handler, DiskDb};
pub use types::{CharacterId, CharsheetRow, GameId, SessionId, UserId};
use crate::types::{AccountState, FatalError, Game, User};
#[derive(Debug)]
enum Request {
Charsheet(CharacterId),
CreateGame(UserId, String, String),
CreateSession(UserId),
CreateUser(String, String, bool, AccountState),
DeleteSession(SessionId),
Game(GameId),
Games,
SaveGame(Game),
SaveUser(User),
Session(SessionId),
User(UserId),
UserByUsername(String),
Users,
}
#[derive(Debug)]
struct DatabaseRequest {
tx: Sender<DatabaseResponse>,
req: Request,
}
#[derive(Debug)]
enum DatabaseResponse {
Charsheet(Option<CharsheetRow>),
CreateSession(SessionId),
DeleteSession,
Games(Vec<Game>),
Game(Option<Game>),
SaveGame(GameId),
SaveUser(UserId),
Session(Option<User>),
User(Option<User>),
Users(Vec<User>),
}
#[async_trait]
pub trait Database: Send + Sync {
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError>;
async fn session(&self, id: &SessionId) -> Result<Option<User>, FatalError>;
async fn delete_session(&self, id: &SessionId) -> Result<(), FatalError>;
async fn character(&self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
async fn create_game(
&self,
gm: &UserId,
game_type: &str,
name: &str,
) -> Result<GameId, FatalError>;
async fn save_game(&self, game: Game) -> Result<GameId, FatalError>;
async fn game(&self, _: &GameId) -> Result<Option<Game>, FatalError>;
async fn games(&self) -> Result<Vec<Game>, FatalError>;
async fn create_user(
&self,
name: &str,
password: &str,
admin: bool,
state: AccountState,
) -> Result<UserId, FatalError>;
async fn save_user(&self, user: User) -> Result<UserId, FatalError>;
async fn user(&self, _: &UserId) -> Result<Option<User>, FatalError>;
async fn user_by_username(&self, _: &str) -> Result<Option<User>, FatalError>;
async fn users(&self) -> Result<Vec<User>, FatalError>;
}
pub struct DbConn {
conn: Sender<DatabaseRequest>,
handle: tokio::task::JoinHandle<()>,
}
impl DbConn {
pub fn new<P>(path: Option<P>) -> Self
where
P: AsRef<Path>,
{
let (tx, rx) = bounded::<DatabaseRequest>(5);
let db = DiskDb::new(path).unwrap();
let handle = tokio::spawn(async move {
db_handler(db, rx).await;
});
Self { conn: tx, handle }
}
}
macro_rules! send_request {
($s:expr, $req:expr, $resp_h:pat => $block:expr) => {{
let (tx, rx) = bounded::<DatabaseResponse>(1);
let request = DatabaseRequest { tx, req: $req };
match $s.conn.send(request).await {
Ok(()) => (),
Err(_) => return Err(FatalError::DatabaseConnectionLost),
};
match rx
.recv()
.await
.map_err(|_| FatalError::DatabaseConnectionLost)
{
Ok($resp_h) => $block,
Ok(_) => Err(FatalError::MessageMismatch),
Err(_) => Err(FatalError::DatabaseConnectionLost),
}
}};
}
#[async_trait]
impl Database for DbConn {
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError> {
send_request!(self, Request::CreateSession(id.to_owned()), DatabaseResponse::CreateSession(session_id) => Ok(session_id))
}
async fn session(&self, id: &SessionId) -> Result<Option<User>, FatalError> {
send_request!(self, Request::Session(id.to_owned()), DatabaseResponse::Session(row) => Ok(row))
}
async fn delete_session(&self, id: &SessionId) -> Result<(), FatalError> {
send_request!(self, Request::DeleteSession(id.to_owned()), DatabaseResponse::DeleteSession => Ok(()))
}
async fn character(&self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
send_request!(self, Request::Charsheet(id.to_owned()), DatabaseResponse::Charsheet(row) => Ok(row))
}
async fn create_game(
&self,
user_id: &UserId,
game_type: &str,
game_name: &str,
) -> Result<GameId, FatalError> {
send_request!(self, Request::CreateGame(user_id.to_owned(), game_type.to_owned(), game_name.to_owned()), DatabaseResponse::SaveGame(game_id) => Ok(game_id))
}
async fn save_game(&self, game: Game) -> Result<GameId, FatalError> {
send_request!(self, Request::SaveGame(game), DatabaseResponse::SaveGame(game_id) => Ok(game_id))
}
async fn game(&self, game_id: &GameId) -> Result<Option<Game>, FatalError> {
send_request!(self, Request::Game(game_id.clone()), DatabaseResponse::Game(game) => Ok(game))
}
async fn games(&self) -> Result<Vec<Game>, FatalError> {
send_request!(self, Request::Games, DatabaseResponse::Games(lst) => Ok(lst))
}
async fn create_user(
&self,
name: &str,
password: &str,
admin: bool,
state: AccountState,
) -> Result<UserId, FatalError> {
send_request!(self,
Request::CreateUser(name.to_owned(), password.to_owned(), admin, state),
DatabaseResponse::SaveUser(user_id) => Ok(user_id))
}
async fn save_user(&self, user: User) -> Result<UserId, FatalError> {
send_request!(self,
Request::SaveUser(user),
DatabaseResponse::SaveUser(user_id) => Ok(user_id))
}
async fn user(&self, uid: &UserId) -> Result<Option<User>, FatalError> {
send_request!(self, Request::User(uid.clone()), DatabaseResponse::User(user) => Ok(user))
}
async fn user_by_username(&self, username: &str) -> Result<Option<User>, FatalError> {
send_request!(self, Request::UserByUsername(username.to_owned()), DatabaseResponse::User(user) => Ok(user))
}
async fn users(&self) -> Result<Vec<User>, FatalError> {
send_request!(self, Request::Users, DatabaseResponse::Users(lst) => Ok(lst))
}
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use cool_asserts::assert_matches;
use disk_db::DiskDb;
use types::GameId;
use super::*;
const SOREN: &str = r#"{ "type_": "Candela", "name": "Soren Jensen", "pronouns": "he/him", "circle": "Circle of the Bluest Sky", "style": "dapper gentleman", "catalyst": "a cursed book", "question": "What were the contents of that book?", "nerve": { "type_": "nerve", "drives": { "current": 1, "max": 2 }, "resistances": { "current": 0, "max": 3 }, "move": { "gilded": false, "score": 2 }, "strike": { "gilded": false, "score": 1 }, "control": { "gilded": true, "score": 0 } }, "cunning": { "type_": "cunning", "drives": { "current": 1, "max": 1 }, "resistances": { "current": 0, "max": 3 }, "sway": { "gilded": false, "score": 0 }, "read": { "gilded": false, "score": 0 }, "hide": { "gilded": false, "score": 0 } }, "intuition": { "type_": "intuition", "drives": { "current": 0, "max": 0 }, "resistances": { "current": 0, "max": 3 }, "survey": { "gilded": false, "score": 0 }, "focus": { "gilded": false, "score": 0 }, "sense": { "gilded": false, "score": 0 } }, "role": "Slink", "role_abilities": [ "Scout: If you have time to observe a location, you can spend 1 Intuition to ask a question: What do I notice here that others do not see? What in this place might be of use to us? What path should we follow?" ], "specialty": "Detective", "specialty_abilities": [ "Mind Palace: When you want to figure out how two clues might relate or what path they should point you towards, burn 1 Intution resistance. The GM will give you the information you have deduced." ] }"#;
fn setup_db() -> (DiskDb, GameId) {
let no_path: Option<PathBuf> = None;
let db = DiskDb::new(no_path).unwrap();
db.create_user("admin", "abcdefg", true, AccountState::Normal)
.unwrap();
let game_id = db
.create_game(
&UserId::from("admin"),
"Candela",
"Circle of the Winter Solstice",
)
.unwrap();
(db, game_id)
}
#[ignore]
#[test]
fn it_can_retrieve_a_character() {
let (db, game_id) = setup_db();
assert_matches!(db.character(CharacterId::from("1")), Ok(None));
let js: serde_json::Value = serde_json::from_str(SOREN).unwrap();
let soren_id = db.save_character(None, game_id, js.clone()).unwrap();
assert_matches!(db.character(soren_id).unwrap(), Some(CharsheetRow{ data, .. }) => assert_eq!(js, data));
}
#[tokio::test]
async fn it_can_retrieve_a_character_through_conn() {
let memory_db: Option<PathBuf> = None;
let conn = DbConn::new(memory_db);
assert_matches!(conn.character(&CharacterId::from("1")).await, Ok(None));
}
}

View File

@ -1,209 +0,0 @@
use std::fmt;
use chrono::{NaiveDateTime, Utc};
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ValueRef};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use uuid::Uuid;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct UserId(String);
impl UserId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for UserId {
fn default() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
}
impl From<&str> for UserId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for UserId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for UserId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
impl fmt::Display for UserId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&self.0)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct SessionId(String);
impl SessionId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for SessionId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for SessionId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for SessionId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
impl fmt::Display for SessionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&self.0)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct GameId(String);
impl GameId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for GameId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for GameId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for GameId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct CharacterId(String);
impl CharacterId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for CharacterId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for CharacterId {
fn from(s: String) -> Self {
Self(s)
}
}
impl FromSql for CharacterId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug)]
pub struct Role {
userid: UserId,
gameid: GameId,
role: String,
}
/*
#[derive(Clone, Debug)]
pub struct GameRow {
pub id: GameId,
pub gm: UserId,
pub game_type: String,
pub name: String,
}
*/
#[derive(Clone, Debug)]
pub struct CharsheetRow {
pub id: String,
pub game: GameId,
pub data: serde_json::Value,
}
#[derive(Clone, Debug)]
pub struct SessionRow {
id: SessionId,
user_id: SessionId,
}
#[derive(Clone, Debug)]
pub struct DateTime(pub chrono::DateTime<Utc>);
impl FromSql for DateTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Text(text) => String::from_utf8(text.to_vec())
.map_err(|_err| FromSqlError::InvalidType)
.and_then(|s| {
NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S")
.map_err(|_err| FromSqlError::InvalidType)
})
.and_then(|dt| Ok(DateTime(dt.and_utc()))),
_ => Err(FromSqlError::InvalidType),
}
}
}

View File

@ -1,29 +0,0 @@
use axum::http::HeaderMap;
use result_extended::ResultExt;
use serde::{Deserialize, Serialize};
use crate::{
core::Core,
database::GameId,
types::{AppError, FatalError},
};
use super::auth_required;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct CreateGameRequest {
pub game_type: String,
pub game_name: String,
}
pub async fn create_game(
core: Core,
headers: HeaderMap,
req: CreateGameRequest,
) -> ResultExt<GameId, AppError, FatalError> {
auth_required(core.clone(), headers, |user| async move {
core.create_game(&user.id, &req.game_type, &req.game_name)
.await
})
.await
}

Some files were not shown because too many files have changed in this diff Show More