mod de;
use std::collections::HashMap;
use std::iter;
use array_init::array_init;
use interp::interp_array;
use itertools::Itertools;
use serde::Deserialize;
use crate::error::Result;
use crate::utils::{Rect, RoundRect, Vec2};
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ProfileType {
Cylindrical { depth: f32 },
Spherical { depth: f32 },
Flat,
}
impl ProfileType {
pub(crate) fn depth(self) -> f32 {
match self {
Self::Cylindrical { depth } | Self::Spherical { depth } => depth,
Self::Flat => 0.,
}
}
}
impl Default for ProfileType {
fn default() -> Self {
Self::Cylindrical { depth: 1.0 }
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum HomingType {
#[serde(alias = "deep-dish")]
Scoop,
#[serde(alias = "line")]
Bar,
#[serde(alias = "nub", alias = "dot", alias = "nipple")]
Bump,
}
impl Default for HomingType {
fn default() -> Self {
Self::Bar
}
}
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct ScoopProps {
pub depth: f32,
}
impl Default for ScoopProps {
fn default() -> Self {
Self { depth: 2.0 }
}
}
#[derive(Debug, Clone, Copy)]
pub struct BarProps {
pub size: Vec2,
pub y_offset: f32,
}
impl Default for BarProps {
fn default() -> Self {
Self {
size: Vec2::new(3.81, 0.51), y_offset: 6.35, }
}
}
#[derive(Debug, Clone, Copy)]
pub struct BumpProps {
pub diameter: f32,
pub y_offset: f32,
}
impl Default for BumpProps {
fn default() -> Self {
Self {
diameter: 0.51, y_offset: 0.,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, Default)]
pub struct HomingProps {
pub default: HomingType,
pub scoop: ScoopProps,
pub bar: BarProps,
pub bump: BumpProps,
}
#[derive(Debug, Clone)]
pub struct TextHeight([f32; Self::NUM_HEIGHTS]);
impl TextHeight {
const NUM_HEIGHTS: usize = 10;
fn new(heights: &HashMap<usize, f32>) -> Self {
if heights.is_empty() {
Self::default()
} else {
let (index, height): (Vec<_>, Vec<_>) = {
iter::once((0., 0.))
.chain(
heights
.iter()
.sorted_by_key(|(&i, _)| i)
.map(|(&i, &h)| (i as f32, h)),
)
.unzip()
};
let all_indeces = array_init(|i| i as f32);
Self(interp_array(&index, &height, &all_indeces))
}
}
pub fn get(&self, kle_font_size: u8) -> f32 {
let font_usize = usize::from(kle_font_size);
if font_usize < self.0.len() {
self.0[font_usize]
} else {
self.0[self.0.len() - 1]
}
}
}
impl Default for TextHeight {
fn default() -> Self {
const DEFAULT_MAX: f32 = 18.;
Self(array_init(|i| {
(i as f32) * DEFAULT_MAX / (Self::NUM_HEIGHTS as f32 - 1.)
}))
}
}
#[derive(Debug, Clone)]
pub struct TextRect([Rect; Self::NUM_RECTS]);
impl TextRect {
const NUM_RECTS: usize = 10;
fn new(rects: &HashMap<usize, Rect>) -> Self {
if rects.is_empty() {
Self::default()
} else {
let max_rect = rects[rects.keys().max().unwrap()];
let rects: Vec<_> = {
let tmp = (0..Self::NUM_RECTS)
.rev()
.scan(max_rect, |prev, i| {
if let Some(&value) = rects.get(&i) {
*prev = value;
}
Some(*prev)
})
.collect_vec();
tmp.into_iter().rev().collect()
};
Self(rects.try_into().unwrap())
}
}
pub fn get(&self, kle_font_size: u8) -> Rect {
if usize::from(kle_font_size) < self.0.len() {
self.0[usize::from(kle_font_size)]
} else {
self.0[self.0.len() - 1]
}
}
}
impl Default for TextRect {
fn default() -> Self {
let rect = Rect::new(Vec2::ZERO, Vec2::from(1e3));
Self([rect; Self::NUM_RECTS])
}
}
#[derive(Debug, Clone)]
pub struct Profile {
pub profile_type: ProfileType,
pub bottom_rect: RoundRect,
pub top_rect: RoundRect,
pub text_margin: TextRect,
pub text_height: TextHeight,
pub homing: HomingProps,
}
impl Profile {
pub fn from_toml(s: &str) -> Result<Self> {
Ok(toml::from_str(s)?)
}
}
impl Default for Profile {
fn default() -> Self {
Self {
profile_type: ProfileType::default(),
bottom_rect: RoundRect::new(
Vec2::new(0.5, 0.5),
Vec2::new(18.05, 18.05),
Vec2::new(1.2, 1.2),
),
top_rect: RoundRect::new(
Vec2::new(3.2, 1.0),
Vec2::new(12.7, 13.95),
Vec2::new(1.2, 1.2),
),
text_margin: TextRect::default(),
text_height: TextHeight::default(),
homing: HomingProps::default(),
}
}
}
#[cfg(test)]
mod tests {
use assert_approx_eq::assert_approx_eq;
use maplit::hashmap;
use super::*;
#[test]
fn test_profile_type_depth() {
assert_eq!(ProfileType::Cylindrical { depth: 1. }.depth(), 1.);
assert_eq!(ProfileType::Spherical { depth: 0.5 }.depth(), 0.5);
assert_eq!(ProfileType::Flat.depth(), 0.);
}
#[test]
fn test_text_height_new() {
let expected = vec![0., 2., 4., 6., 8., 10., 12., 14., 16., 18.];
let result = TextHeight::new(&hashmap! {}).0;
assert_eq!(expected.len(), result.len());
for (e, r) in expected.iter().zip(result.iter()) {
assert_approx_eq!(e, r);
}
let expected = vec![0., 3., 6., 9., 9.5, 10.5, 11.5, 14., 16.5, 19.];
let result = TextHeight::new(&hashmap! {
1 => 3.,
3 => 9.,
4 => 9.5,
6 => 11.5,
9 => 19.
})
.0;
assert_eq!(expected.len(), result.len());
for (e, r) in expected.iter().zip(result.iter()) {
assert_approx_eq!(e, r);
}
}
#[test]
fn test_text_height_get() {
let heights = TextHeight::new(&hashmap! {
1 => 3.,
3 => 9.,
4 => 9.5,
6 => 11.5,
9 => 19.
});
assert_approx_eq!(heights.get(5), 10.5);
assert_approx_eq!(heights.get(23), 19.);
}
#[test]
fn test_text_rect_new() {
let expected = vec![Rect::new(Vec2::ZERO, Vec2::from(1e3)); 10];
let result = TextRect::new(&hashmap! {}).0;
assert_eq!(expected.len(), result.len());
for (e, r) in expected.iter().zip(result.iter()) {
assert_approx_eq!(e.position(), r.position());
assert_approx_eq!(e.size(), r.size());
}
let expected = vec![
Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)),
Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)),
Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)),
Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)),
Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)),
Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)),
Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)),
Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)),
Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)),
Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)),
];
let result = TextRect::new(&hashmap! {
2 => Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)),
5 => Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)),
7 => Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)),
})
.0;
assert_eq!(expected.len(), result.len());
for (e, r) in expected.iter().zip(result.iter()) {
assert_approx_eq!(e.position(), r.position());
assert_approx_eq!(e.size(), r.size());
}
}
#[test]
fn test_text_rect_get() {
let rects = TextRect::new(&hashmap! {
2 => Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)),
5 => Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)),
7 => Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)),
});
let r = rects.get(2);
assert_approx_eq!(r.position(), Vec2::from(200.));
assert_approx_eq!(r.size(), Vec2::from(600.));
let r = rects.get(62);
assert_approx_eq!(r.position(), Vec2::from(300.));
assert_approx_eq!(r.size(), Vec2::from(400.));
}
}