about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2024-12-05 04:48:11 -0600
committergennyble <gen@nyble.dev>2024-12-05 04:48:11 -0600
commit8b78b4f3102ddc828fd9d6d2f78740827ac90090 (patch)
treea4f16ac8b242b1d858f2c3cb3374310cdec44bb2
parentf3d59ae0bfe4548a0c87d56cc251b092fa393940 (diff)
downloadreally-etches-8b78b4f3102ddc828fd9d6d2f78740827ac90090.tar.gz
really-etches-8b78b4f3102ddc828fd9d6d2f78740827ac90090.zip
Galloping keypresses
-rw-r--r--Cargo.lock51
-rw-r--r--src/main.rs191
2 files changed, 234 insertions, 8 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4693496..f3148bf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -116,6 +116,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
 
 [[package]]
+name = "bitvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
+[[package]]
 name = "block2"
 version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -417,6 +429,7 @@ dependencies = [
 name = "etch"
 version = "0.1.0"
 dependencies = [
+ "gifed 0.3.0",
  "gilrs",
  "neam",
  "softbuffer",
@@ -484,6 +497,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
 
 [[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
+[[package]]
 name = "gethostname"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -513,6 +532,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "gifed"
+version = "0.3.0"
+source = "git+https://git.nyble.dev/multimedia/gifed#7a3a4b5f86b17015e174737fde6c4867682b17f8"
+dependencies = [
+ "bitvec",
+ "weezl",
+]
+
+[[package]]
 name = "gilrs"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -777,7 +805,7 @@ name = "neam"
 version = "0.1.0"
 source = "git+https://github.com/gennyble/neam?rev=5efc1761866283537fd254a8fb3582f50631cf0f#5efc1761866283537fd254a8fb3582f50631cf0f"
 dependencies = [
- "gifed",
+ "gifed 0.1.0",
  "png",
 ]
 
@@ -1160,6 +1188,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "radium"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+
+[[package]]
 name = "raw-window-handle"
 version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1408,6 +1442,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+[[package]]
 name = "thiserror"
 version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2175,6 +2215,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
+
+[[package]]
 name = "x11-dl"
 version = "2.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src/main.rs b/src/main.rs
index c1bb344..fdf3e37 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
 use std::{
+	cmp::Ordering,
 	num::NonZeroU32,
 	ops::DerefMut,
 	rc::Rc,
@@ -13,8 +14,9 @@ use tracing_subscriber::EnvFilter;
 use winit::{
 	application::ApplicationHandler,
 	dpi::LogicalSize,
-	event::WindowEvent,
+	event::{DeviceEvent, KeyEvent, WindowEvent},
 	event_loop::{ControlFlow, EventLoop},
+	keyboard::{Key, NamedKey},
 	window::Window,
 };
 
@@ -40,6 +42,10 @@ fn main() {
 		left_angle: 0.0,
 		right_angle: 0.0,
 		next_check: Instant::now(),
+
+		gallop_x: Gallop::default(),
+		gallop_y: Gallop::default(),
+		next_gallop_check: Instant::now(),
 	};
 
 	el.run_app(&mut etch).unwrap();
@@ -52,6 +58,106 @@ fn setup_logging() {
 	tracing_subscriber::fmt().with_env_filter(env_filter).init();
 }
 
+#[derive(Clone, Debug, Default)]
+struct Gallop {
+	keys: [Option<Instant>; 4],
+}
+
+impl Gallop {
+	pub fn push(&mut self, idx: usize) {
+		if idx >= self.keys.len() {
+			panic!("gallop index out of bound");
+		}
+
+		self.keys[idx] = Some(Instant::now());
+	}
+
+	pub fn event(&mut self) -> Option<GallopEvent> {
+		let mut sorted: Vec<(usize, Option<Instant>)> = self.keys.into_iter().enumerate().collect();
+		sorted.sort_by(|(_, a_inst), (_, b_inst)| {
+			if a_inst.is_none() {
+				Ordering::Greater
+			} else if b_inst.is_none() {
+				Ordering::Less
+			} else {
+				a_inst.cmp(&b_inst)
+			}
+		});
+
+		let (early2_idx, Some(early2)) = sorted[0] else {
+			return None;
+		};
+
+		let (early_idx, Some(early)) = sorted[1] else {
+			return None;
+		};
+
+		tracing::info!(
+			"early = [{}] {}us // early2 = [{}] {}us",
+			early_idx,
+			early.elapsed().as_micros(),
+			early2_idx,
+			early2.elapsed().as_micros()
+		);
+
+		let high = self.keys.len() - 1;
+		if early_idx == 0 && early2_idx == high {
+			tracing::trace!("gallop (+) wrap special case");
+			self.keys[early2_idx] = None;
+			return Some(GallopEvent::Positive(early.duration_since(early2)));
+		} else if early_idx == high && early2_idx == 0 {
+			tracing::trace!("gallop (-) wrap special case");
+		}
+
+		if early_idx > early2_idx {
+			let delta = early_idx - early2_idx;
+			self.keys[early2_idx] = None;
+
+			if delta > 1 {
+				tracing::info!("gallop (+) delta>1");
+				None
+			} else {
+				Some(GallopEvent::Positive(early.duration_since(early2)))
+			}
+		} else if early_idx < early2_idx {
+			let delta = early2_idx - early_idx;
+			self.keys[early2_idx] = None;
+
+			if delta > 1 {
+				tracing::info!("gallop (-) delta>1");
+				None
+			} else {
+				Some(GallopEvent::Negative(early.duration_since(early2)))
+			}
+		} else {
+			None
+		}
+	}
+}
+
+#[derive(Clone, Debug)]
+enum GallopEvent {
+	Positive(Duration),
+	Negative(Duration),
+}
+
+impl GallopEvent {
+	pub fn value(&self) -> f32 {
+		match self {
+			Self::Positive(gdur) => {
+				let delta = GALLOP_TOLERANCE - *gdur;
+				let units = delta.as_millis() as f32 / GALLOP_SENSETIVITY.as_millis() as f32;
+				units
+			}
+			Self::Negative(gdur) => {
+				let delta = GALLOP_TOLERANCE - *gdur;
+				let units = delta.as_millis() as f32 / GALLOP_SENSETIVITY.as_millis() as f32;
+				-units
+			}
+		}
+	}
+}
+
 #[derive(Copy, Clone, Debug, Default)]
 struct DialState {
 	left: Vec2<f32>,
@@ -73,6 +179,10 @@ struct Etch {
 	left_angle: f32,
 	right_angle: f32,
 	next_check: Instant,
+
+	gallop_x: Gallop,
+	gallop_y: Gallop,
+	next_gallop_check: Instant,
 }
 
 impl Etch {
@@ -115,6 +225,8 @@ impl Etch {
 
 // Why are my consts HERE of all places
 const DIAL_SENSITIVITY: f32 = 10.0;
+const GALLOP_SENSETIVITY: Duration = Duration::from_millis(25);
+const GALLOP_TOLERANCE: Duration = Duration::from_millis(250);
 const WIDTH: f32 = 640.0;
 const HEIGHT: f32 = 480.0;
 
@@ -146,9 +258,40 @@ impl ApplicationHandler for Etch {
 				tracing::info!("close requested! shutting down.");
 				event_loop.exit();
 			}
+			WindowEvent::KeyboardInput {
+				device_id,
+				event,
+				is_synthetic,
+			} => {
+				let KeyEvent {
+					logical_key,
+					state,
+					repeat,
+					..
+				} = event;
+
+				// We only track keys down and non-repeat
+				if repeat || !state.is_pressed() {
+					return;
+				}
+
+				match logical_key.as_ref() {
+					Key::Character("a") => self.gallop_x.push(0),
+					Key::Character("s") => self.gallop_x.push(1),
+					Key::Character("d") => self.gallop_x.push(2),
+					Key::Character("f") => self.gallop_x.push(3),
+					Key::Character("j") => self.gallop_y.push(0),
+					Key::Character("k") => self.gallop_y.push(1),
+					Key::Character("l") => self.gallop_y.push(2),
+					Key::Character(";") => self.gallop_y.push(3),
+					_ => (),
+				}
+			}
 			WindowEvent::RedrawRequested => {
 				self.process_gamepad_events();
 
+				let stylus_prev = self.stylus;
+
 				// We check the state of the joystick at 40fps
 				if self.next_check.elapsed() > Duration::from_millis(25) {
 					let left_angle = xy_to_deg(self.dial.left.x, self.dial.left.y);
@@ -177,12 +320,11 @@ impl ApplicationHandler for Etch {
 					};
 					self.right_angle = right_angle;
 
-					tracing::info!(
+					tracing::trace!(
 						"ANGLE ({}) {left_angle} // {left_delta}v -=- ({}) {right_angle} // {right_delta}v",
 						self.dial.left.mag(), self.dial.right.mag()
 					);
 
-					let stylus_prev = self.stylus;
 					let movement_x = left_delta / DIAL_SENSITIVITY;
 					let movement_y = right_delta / DIAL_SENSITIVITY;
 					self.stylus.x =
@@ -190,6 +332,43 @@ impl ApplicationHandler for Etch {
 					self.stylus.y =
 						(self.stylus.y - movement_y).clamp(0.0, self.img.height() as f32);
 
+					self.next_check = Instant::now();
+				}
+
+				if self.next_gallop_check.elapsed() > Duration::from_millis(100) {
+					while let Some(ge) = self.gallop_x.event() {
+						match ge {
+							GallopEvent::Positive(gdur) => {
+								if gdur <= GALLOP_TOLERANCE {
+									self.stylus.x += ge.value();
+								}
+							}
+							GallopEvent::Negative(gdur) => {
+								if gdur <= GALLOP_TOLERANCE {
+									self.stylus.x += ge.value();
+								}
+							}
+						}
+					}
+
+					while let Some(ge) = self.gallop_y.event() {
+						match ge {
+							GallopEvent::Positive(gdur) => {
+								if gdur <= GALLOP_TOLERANCE {
+									self.stylus.y += ge.value();
+								}
+							}
+							GallopEvent::Negative(gdur) => {
+								if gdur <= GALLOP_TOLERANCE {
+									self.stylus.y += ge.value();
+								}
+							}
+						}
+					}
+				}
+
+				// If the stylus moved, we should draw
+				if stylus_prev != self.stylus {
 					self.img.line(
 						self.stylus.as_u32(),
 						stylus_prev.as_u32(),
@@ -205,9 +384,7 @@ impl ApplicationHandler for Etch {
 					// And then draw the stylus
 					self.img.rect(self.stylus.as_u32(), Vec2::new(2, 2), STYLUS_COLOUR.into());
 
-					tracing::info!("STYLUS: ({},{})", self.stylus.x, self.stylus.y);
-
-					self.next_check = Instant::now();
+					tracing::trace!("STYLUS: ({},{})", self.stylus.x, self.stylus.y);
 				}
 
 				let Some(surfaced) = self.window.as_mut() else {
@@ -289,7 +466,7 @@ fn angle_delta(lhs: f32, rhs: f32) -> f32 {
 	}
 }
 
-#[derive(Copy, Clone, Debug, Default)]
+#[derive(Copy, Clone, Debug, Default, PartialEq)]
 pub struct Vec2<T> {
 	pub x: T,
 	pub y: T,