From 5165708a03b3654e19e9af54c5d214f06fee8157 Mon Sep 17 00:00:00 2001
From: Dane Johnson <daneallenjohnson@protonmail.com>
Date: Sun, 8 May 2022 14:39:30 -0500
Subject: [PATCH] Transformible coordinates are a board trait

---
 board.zip    | Bin 511953 -> 511975 bytes
 src/board.rs | 304 +++++++++++++++++++++++++++++----------------------
 src/main.rs  |  34 +-----
 3 files changed, 175 insertions(+), 163 deletions(-)

diff --git a/board.zip b/board.zip
index 18dead62f50f3b18c5a75650e14e7a234a3edbc8..342d503d2fcced620c98fab29c4207b28442da15 100644
GIT binary patch
delta 537
zcmcaOU;g=g`T78FW)=|!1_lm>YjakF$X(o_QpL!?aE6(IfeR>_UX)mnp_f&hpI3Xy
z-~W(-1lx!8o4)Ls@h^Y7wA5o?;foatGF_{8SnOJIMPpKJ`R<)tQ&}VxB^TPy`+m2W
zrT5m0H2zb{6<2QWGF=eU`1{(f?>QSUZ>tMcU_brMcxQaMr9pk}(st(!|9`Nl?r940
z(yHX+`z!m1!;rD_|H@mL0Zp!pzscJ!Tb)~9(t54(&*yWqO#=RXDCiYSWN~z=+z|KW
zUS{^D^kD1QRPW_;)Rk?gyG}3^+u$&#&*+&;K_}CnggKi^X9t+1Th6bx@$_O{=HVlk
z%JWly(n|ANR;5hyc;k-LR|Gk{Fm9`06<^bFXYDWPKW>ttOILj`e0nGK;?l!M-KHN>
zRI@JD*PU`MQzpYpLTiakY9GtziOG*PdgUiiR-16famt#IAH0*4L^3NngFl`&SE#;p
z?b^Dn?InW65o|LOd?v`(HVIB|bt&X556ix+5xi9O?0E+f?S9s$GVKprml*BywXmz%
zef8K?x4*%@LXUs82mP?}o&Cl0clm{NYk#er-5qlMFB3{6HP2*hpUDWqOhC*G#4JF}
z3dC&NXEL(yKg){}lhaervun$PgalHnt7o2NXZU%Joq-b=xiGM#anbbk=h<c0lFqXO
F#Q<jl=ZydW

delta 516
zcmaDpU;g5J`T78FW)=|!1_llW{=JJs?)HE1OJQVSsAFbe-~x)K7bO;C=w%h>=ha?{
z^=~#1VSBKD)0bU_x__Vf^?vjdUj8xZrmK3XP^9NBskz+$?>1d1)8T4iagsk*S>3nb
zqV(lAj+HF@Y0=xorf3&jxB9<r#~sP={TU1gH~l>^@476@w)(YaHgJ61t{<?7TV?5z
zNzMf?<{KQJ!Rc~nc~!atr=IlQ`uB#xxxbfc?`eDaJk}s{$)QINWpod5xV3!Bu${fv
z{G8sroVUJPBdXpq&wQ~{XHkp@ljfP18sc*}^a@+vuf4u5&$!E(>*gKhi^?5_M^{SN
z`U$>X&k<+&H`e_Blw+Hh)vwghn75+IsO@mK&Vk;-e=3e1&*yl}^=b^rFfUuRBSEXs
z{FnLR?A+3+tA&g-p6Pk($@-b?T6lY(Pua77Tg}r|#V0OnNmPA0MO92^${a1Z<)2Dr
zPI0Of3#3o%nxGI^sMhX0r)z%V46*)WJ8x}&H{oEq;)yM9T^$QDYMTtFdp(j7IkeXJ
znAxo`OZIf(`=$0M(b2q~v3)%w2r~gOGZ3=?F)I+WZC}sGzW*#QN}NrvKF_Yr3>2Ea
n@;tk=at|;bGjkKuQ}qh+(gVC%*?{rMzyyT(K-%X#JCFnb4|3Oj

diff --git a/src/board.rs b/src/board.rs
index 76f1830..74ca986 100644
--- a/src/board.rs
+++ b/src/board.rs
@@ -1,133 +1,171 @@
-use serde::{ Serialize, Deserialize };
-use std::collections::{ HashMap, HashSet };
-
-#[derive(Serialize, Deserialize, Debug, Default)]
-#[serde(default)]
-pub struct Node {
-    pub x: f32,
-    pub y: f32,
-
-    pub name: String,
-    pub edges: HashSet<usize>,
-    pub labels: HashMap<String, String>,
-}
-#[derive(Serialize, Deserialize, Debug, Default)]
-#[serde(default)]
-pub struct Board {
-    pub labels: HashMap<String, Vec<String>>,
-    pub nodes: HashMap<usize, Node>,
-}
-impl Board {
-    pub fn new() -> Self {
-	let nodes = HashMap::new();
-	let labels = HashMap::new();
-	Board { nodes, labels }
-    }
-
-    pub fn add_node(&mut self, x: f32, y: f32, name: String) {
-	self.nodes.insert(self.next_id(), Node {
-	    x,
-	    y,
-	    name,
-	    edges: HashSet::new(),
-	    labels: HashMap::new(),
-	});
-    }
-
-    pub fn remove_node(&mut self, id: usize) {
-	// We remove this node from the graph, then drop it from each
-	// other nodes edge.
-	self.nodes.remove(&id);
-	for node in self.nodes.values_mut() {
-	    node.edges.remove(&id);
-	}
-    }
-
-    pub fn nearest_node(&self, x: f32, y: f32) -> Option<usize> {
-	let f = |n: &Node| dist_sq((n.x, n.y), (x, y));
-	let mut iter = self.nodes.iter();
-	if let Some((id, node)) = iter.next() {
-	    let mut min = *id;
-	    let mut min_val = f(node);
-	    for (id, node) in iter {
-		let val = f(node);
-		if val < min_val {
-		    min = *id;
-		    min_val = val;
-		}
-	    }
-	    Some(min)
-	} else {
-	    None
-	}
-    }
-
-    fn next_id(&self) -> usize {
-	for i in 0 .. {
-	    if !self.nodes.contains_key(&i) {
-		return i;
-	    }
-	}
-	unreachable!();
-    }
-}
-
-fn dist_sq((x0, y0): (f32, f32), (x1, y1): (f32, f32)) -> f32 {
-    (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)
-}
-
-use std::io;
-use std::io::{ Write, Read, Cursor };
-use std::fs::File;
-use std::path::Path;
-
-use image::io::Reader as ImageReader;
-use image::{ DynamicImage };
-
-pub fn write_board_to_file(board: &Board, image: Option<&DynamicImage>, path: &Path) -> io::Result<()> {
-    let file = File::create(path)?;
-    let mut ar = zip::ZipWriter::new(file);
-    let options = zip::write::FileOptions::default();
-
-    ar.start_file("graph.json", options)?;
-    ar.write_all(&serde_json::to_vec(board)?)?;
-    if let Some(image) = image {
-	ar.start_file("image.png", options)?;
-	let data = encode_png(image);
-	ar.write_all(&data)?;
-	ar.flush()?;
-    }
-    ar.finish()?;
-    Ok(())
-}
-
-pub fn read_board_from_file(path: &Path) -> io::Result<(Board, Option<DynamicImage>)> {
-    let file = File::open(path)?;
-    let mut ar = zip::ZipArchive::new(file)?;
-
-    let json_file = ar.by_name("graph.json")?;
-    let board = serde_json::from_reader(json_file)?;
-    let image = ar.by_name("image.png").ok();
-    if image.is_none() {
-	return Ok((board, None))
-    }
-    let mut image_file = image.unwrap();
-    let mut buf = Vec::new();
-    image_file.read_to_end(&mut buf)?;
-    let image = decode_png(&buf);
-    Ok((board, Some(image)))
-}
-
-pub fn encode_png(image: &DynamicImage) -> Vec<u8> {
-    let mut cursor = Cursor::new(Vec::new());
-    image.write_to(&mut cursor, image::ImageOutputFormat::Png).unwrap();
-    cursor.into_inner()
-}
-
-pub fn decode_png(buf: &[u8]) -> DynamicImage {
-    let cursor = Cursor::new(buf);
-    let mut reader = ImageReader::new(cursor);
-    reader.set_format(image::ImageFormat::Png);
-    reader.decode().unwrap()
-}
-
+use serde::{ Serialize, Deserialize };
+use std::collections::{ HashMap, HashSet };
+
+#[derive(Serialize, Deserialize, Debug, Default)]
+#[serde(default)]
+pub struct Node {
+    pub x: f32,
+    pub y: f32,
+
+    pub name: String,
+    pub edges: HashSet<usize>,
+    pub labels: HashMap<String, String>,
+}
+#[derive(Serialize, Deserialize, Debug, Default)]
+#[serde(default)]
+pub struct Board {
+    pub labels: HashMap<String, Vec<String>>,
+    pub nodes: HashMap<usize, Node>,
+}
+impl Board {
+    pub fn new() -> Self {
+	let nodes = HashMap::new();
+	let labels = HashMap::new();
+	Board { nodes, labels }
+    }
+
+    pub fn add_node(&mut self, x: f32, y: f32, name: String) {
+	self.nodes.insert(self.next_id(), Node {
+	    x,
+	    y,
+	    name,
+	    edges: HashSet::new(),
+	    labels: HashMap::new(),
+	});
+    }
+
+    pub fn remove_node(&mut self, id: usize) {
+	// We remove this node from the graph, then drop it from each
+	// other nodes edge.
+	self.nodes.remove(&id);
+	for node in self.nodes.values_mut() {
+	    node.edges.remove(&id);
+	}
+    }
+
+    pub fn nearest_node(&self, x: f32, y: f32) -> Option<usize> {
+	let f = |n: &Node| dist_sq((n.x, n.y), (x, y));
+	let mut iter = self.nodes.iter();
+	if let Some((id, node)) = iter.next() {
+	    let mut min = *id;
+	    let mut min_val = f(node);
+	    for (id, node) in iter {
+		let val = f(node);
+		if val < min_val {
+		    min = *id;
+		    min_val = val;
+		}
+	    }
+	    Some(min)
+	} else {
+	    None
+	}
+    }
+
+    fn next_id(&self) -> usize {
+	for i in 0 .. {
+	    if !self.nodes.contains_key(&i) {
+		return i;
+	    }
+	}
+	unreachable!();
+    }
+}
+
+fn dist_sq((x0, y0): (f32, f32), (x1, y1): (f32, f32)) -> f32 {
+    (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)
+}
+
+use std::io;
+use std::io::{ Write, Read, Cursor };
+use std::fs::File;
+use std::path::Path;
+
+use image::io::Reader as ImageReader;
+use image::{ DynamicImage };
+
+pub fn write_board_to_file(board: &Board, image: Option<&DynamicImage>, path: &Path) -> io::Result<()> {
+    let file = File::create(path)?;
+    let mut ar = zip::ZipWriter::new(file);
+    let options = zip::write::FileOptions::default();
+
+    ar.start_file("graph.json", options)?;
+    ar.write_all(&serde_json::to_vec(board)?)?;
+    if let Some(image) = image {
+	ar.start_file("image.png", options)?;
+	let data = encode_png(image);
+	ar.write_all(&data)?;
+	ar.flush()?;
+    }
+    ar.finish()?;
+    Ok(())
+}
+
+pub fn read_board_from_file(path: &Path) -> io::Result<(Board, Option<DynamicImage>)> {
+    let file = File::open(path)?;
+    let mut ar = zip::ZipArchive::new(file)?;
+
+    let json_file = ar.by_name("graph.json")?;
+    let board = serde_json::from_reader(json_file)?;
+    let image = ar.by_name("image.png").ok();
+    if image.is_none() {
+	return Ok((board, None))
+    }
+    let mut image_file = image.unwrap();
+    let mut buf = Vec::new();
+    image_file.read_to_end(&mut buf)?;
+    let image = decode_png(&buf);
+    Ok((board, Some(image)))
+}
+
+pub fn encode_png(image: &DynamicImage) -> Vec<u8> {
+    let mut cursor = Cursor::new(Vec::new());
+    image.write_to(&mut cursor, image::ImageOutputFormat::Png).unwrap();
+    cursor.into_inner()
+}
+
+pub fn decode_png(buf: &[u8]) -> DynamicImage {
+    let cursor = Cursor::new(buf);
+    let mut reader = ImageReader::new(cursor);
+    reader.set_format(image::ImageFormat::Png);
+    reader.decode().unwrap()
+}
+
+fn lerp(v0: f32, v1: f32, t: f32) -> f32 {
+    v0 + t * (v1 - v0)
+}
+
+fn inv_lerp(v0: f32, v1: f32, a: f32) -> f32 {
+    (a - v0) / (v1 - v0)
+}
+
+pub trait CoordTransformer<I: CoordtypeConvertible> {
+    fn origin(&self) -> (I, I);
+    fn extremes(&self) -> (I, I);
+    fn to_coords(&self, x: I, y: I) -> (f32, f32) {
+	let (sx, sy) = self.origin();
+	let (ex, ey) = self.extremes();
+	(
+	    inv_lerp(sx.to_coordtype(), ex.to_coordtype(), x.to_coordtype()),
+	    inv_lerp(sy.to_coordtype(), ey.to_coordtype(), y.to_coordtype()),
+	)
+    }
+    fn from_coords(&self, x: f32, y: f32) -> (I, I) {
+	let (sx, sy) = self.origin();
+	let (ex, ey) = self.extremes(); 
+	(
+	    CoordtypeConvertible::from_coordtype(lerp(sx.to_coordtype(), ex.to_coordtype(), x)),
+	    CoordtypeConvertible::from_coordtype(lerp(sy.to_coordtype(), ey.to_coordtype(), y)),
+	)
+    }
+}
+
+pub trait CoordtypeConvertible {
+    fn to_coordtype(self) -> f32;
+    fn from_coordtype(coordtype: f32) -> Self;
+}
+
+impl CoordtypeConvertible for i32 {
+    fn to_coordtype(self) -> f32 { self as f32 }
+    fn from_coordtype(coordtype: f32) -> i32 { coordtype as i32 }
+}
diff --git a/src/main.rs b/src/main.rs
index 1de7d61..9b51d45 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,7 +13,7 @@ use std::sync::Mutex;
 
 mod board;
 use board::Board;
-use board::{ encode_png, write_board_to_file, read_board_from_file };
+use board::{ encode_png, write_board_to_file, read_board_from_file, CoordTransformer };
 
 //////////////////// Global State ////////////////////
 // Don't @ me...
@@ -133,35 +133,9 @@ mod dispatch {
     }
 }
 
-//////////////////// Utility Functions ////////////////////
-
-fn lerp(v0: f32, v1: f32, t: f32) -> f32 {
-    v0 + t * (v1 - v0)
-}
-
-fn inv_lerp(v0: f32, v1: f32, a: f32) -> f32 {
-    (a - v0) / (v1 - v0)
-}
-
-trait CoordTransformer {
-    fn to_coords(&self, x: i32, y: i32) -> (f32, f32);
-    fn from_coords(&self, x: f32, y: f32) -> (i32, i32);
-}
-
-impl CoordTransformer for frame::Frame {
-    fn to_coords(&self, x: i32, y: i32) -> (f32, f32) {
-	(
-	    inv_lerp(self.x() as f32, (self.x() + self.w()) as f32, x as f32),
-	    inv_lerp(self.y() as f32, (self.y() + self.h()) as f32, y as f32),
-	)
-    }
-
-    fn from_coords(&self, x: f32, y: f32) -> (i32, i32) {
-	(
-	    lerp(self.x() as f32, (self.x() + self.w()) as f32, x) as i32,
-	    lerp(self.y() as f32, (self.y() + self.h()) as f32, y) as i32,
-	)
-    }
+impl CoordTransformer<i32> for frame::Frame {
+    fn origin(&self) -> (i32, i32) { (self.x(), self.y()) }
+    fn extremes(&self) -> (i32, i32) { (self.x()+self.w(), self.y()+self.h()) }
 }
 
 //////////////////// App State ////////////////////