about summary refs log tree commit diff
path: root/src
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 /src
parentf3d59ae0bfe4548a0c87d56cc251b092fa393940 (diff)
downloadreally-etches-8b78b4f3102ddc828fd9d6d2f78740827ac90090.tar.gz
really-etches-8b78b4f3102ddc828fd9d6d2f78740827ac90090.zip
Galloping keypresses
Diffstat (limited to 'src')
-rw-r--r--src/main.rs191
1 files changed, 184 insertions, 7 deletions
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,