summaryrefslogtreecommitdiff
path: root/platforms/tortuise-script-wasm/src/input.rs
blob: 07cbe6210584a3805dbb47d9914914bcf4b65a7b (plain)
use serde::{Deserialize, Serialize};

use crate::ScriptContext;

mod tortuise {
	#[link(wasm_import_module = "tortuise")]
	unsafe extern "C" {
		pub safe fn is_key_pressed_this_frame(context: u64, keycode: u32) -> u32;
		pub safe fn is_mouse_clicked_this_frame(context: u64) -> u32;
		pub safe fn mouse_location(context: u64) -> u32;
		pub safe fn scroll_direction_this_frame(context: u64) -> u32;
		pub safe fn is_button_pressed_this_frame(context: u64, gamepad_id: u32, button: u32)
		-> u32;
		pub safe fn is_button_released_this_frame(
			context: u64,
			gamepad_id: u32,
			button: u32,
		) -> u32;
		pub safe fn is_button_down(context: u64, gamepad_id: u32, button: u32) -> u32;
		pub safe fn axis_value(context: u64, gamepad_id: u32, axis: u32) -> f32;
		pub safe fn is_gamepad_connected_this_frame(context: u64) -> u32;
		pub safe fn is_gamepad_connected(context: u64, gamepad_id: u32) -> u32;
		pub safe fn gamepad_power_info(context: u64, gamepad_id: u32) -> u32;
		pub safe fn is_focused(context: u64) -> u32;
		pub safe fn was_focused_this_frame(context: u64) -> u32;
		pub safe fn was_unfocused_this_frame(context: u64) -> u32;
	}
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Position {
	pub x: u16,
	pub y: u16,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Direction {
	Up = 1,
	Down,
	Left,
	Right,
}

/// Represents a key.
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
pub enum KeyCode {
	/// Backspace key (Delete on macOS, Backspace on other platforms).
	Backspace,
	/// Enter key.
	Enter,
	/// Left arrow key.
	Left,
	/// Right arrow key.
	Right,
	/// Up arrow key.
	Up,
	/// Down arrow key.
	Down,
	/// Home key.
	Home,
	/// End key.
	End,
	/// Page up key.
	PageUp,
	/// Page down key.
	PageDown,
	/// Tab key.
	Tab,
	/// Shift + Tab key.
	BackTab,
	/// Delete key. (Fn+Delete on macOS, Delete on other platforms)
	Delete,
	/// Insert key.
	Insert,
	/// F key.
	///
	/// `KeyCode::F(1)` represents F1 key, etc.
	F(u8),
	/// A character.
	///
	/// `KeyCode::Char('c')` represents `c` character, etc.
	Char(char),
	/// Null.
	Null,
	/// Escape key.
	Esc,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Button {
	// Action Pad
	South = 1,
	East = 2,
	North = 4,
	West = 5,
	C = 3,
	Z = 6,
	// Triggers
	LeftTrigger = 7,
	LeftTrigger2 = 9,
	RightTrigger = 8,
	RightTrigger2 = 10,
	// Menu Pad
	Select = 11,
	Start = 12,
	Mode = 13,
	// Sticks
	LeftThumb = 14,
	RightThumb = 15,
	// D-Pad
	DPadUp = 16,
	DPadDown = 17,
	DPadLeft = 18,
	DPadRight = 19,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Axis {
	LeftStickX = 1,
	LeftStickY = 2,
	LeftZ = 3,
	RightStickX = 4,
	RightStickY = 5,
	RightZ = 6,
	DPadX = 7,
	DPadY = 8,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PowerInfo {
	Wired,
	Discharging(u8),
	Charging(u8),
	Charged,
	Unknown,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Gamepad(u32);

impl From<u32> for Position {
	fn from(value: u32) -> Self {
		Self {
			x: (value & 0xffff) as u16,
			y: ((value >> 16) & 0xffff) as u16,
		}
	}
}

impl TryFrom<u32> for Direction {
	type Error = ();

	fn try_from(value: u32) -> Result<Self, Self::Error> {
		match value {
			1 => Ok(Direction::Up),
			2 => Ok(Direction::Down),
			3 => Ok(Direction::Left),
			4 => Ok(Direction::Right),
			_ => Err(()),
		}
	}
}

fn key_code_to_number(key_code: KeyCode) -> u32 {
	match key_code {
		KeyCode::Null => 0,
		KeyCode::Backspace => 1 << 24,
		KeyCode::Enter => 2 << 24,
		KeyCode::Left => 3 << 24,
		KeyCode::Right => 4 << 24,
		KeyCode::Up => 5 << 24,
		KeyCode::Down => 6 << 24,
		KeyCode::Home => 7 << 24,
		KeyCode::End => 8 << 24,
		KeyCode::PageUp => 9 << 24,
		KeyCode::PageDown => 10 << 24,
		KeyCode::Tab => 11 << 24,
		KeyCode::BackTab => 12 << 24,
		KeyCode::Delete => 13 << 24,
		KeyCode::Insert => 14 << 24,
		KeyCode::F(f) => 15 << 24 | f as u32,
		KeyCode::Char(c) => 16 << 24 | c as u32,
		KeyCode::Esc => 17 << 24,
	}
}

impl Gamepad {
	pub fn is_button_pressed_this_frame(&self, context: &ScriptContext, button: Button) -> bool {
		tortuise::is_button_pressed_this_frame(context.0, self.0, button as u32) > 0
	}

	pub fn is_button_released_this_frame(&self, context: &ScriptContext, button: Button) -> bool {
		tortuise::is_button_released_this_frame(context.0, self.0, button as u32) > 0
	}

	pub fn is_button_down(&self, context: &ScriptContext, button: Button) -> bool {
		tortuise::is_button_down(context.0, self.0, button as u32) > 0
	}

	pub fn axis_value(&self, context: &ScriptContext, axis: Axis) -> f32 {
		tortuise::axis_value(context.0, self.0, axis as u32)
	}

	pub fn is_connected(&self, context: &ScriptContext) -> bool {
		tortuise::is_gamepad_connected(context.0, self.0) > 0
	}

	pub fn power_info(&self, context: &ScriptContext) -> PowerInfo {
		const WIRED: u32 = 1 << 16;
		const DISCHARGING: u32 = 2 << 16;
		const CHARGING: u32 = 3 << 16;
		const CHARGED: u32 = 4 << 16;

		match tortuise::gamepad_power_info(context.0, self.0) {
			WIRED => PowerInfo::Wired,
			power @ DISCHARGING..CHARGING => PowerInfo::Discharging((power & 0xff) as u8),
			power @ CHARGING..CHARGED => PowerInfo::Charging((power & 0xff) as u8),
			CHARGED => PowerInfo::Charged,
			_ => PowerInfo::Unknown,
		}
	}
}

impl ScriptContext<'_> {
	pub fn is_key_pressed_this_frame(&self, key_code: KeyCode) -> bool {
		tortuise::is_key_pressed_this_frame(self.0, key_code_to_number(key_code)) > 0
	}

	pub fn is_mouse_clicked_this_frame(&self) -> bool {
		tortuise::is_mouse_clicked_this_frame(self.0) > 0
	}

	pub fn mouse_location(&self) -> Option<Position> {
		let location = tortuise::mouse_location(self.0);
		(location != u32::MAX).then(|| location.into())
	}

	pub fn scroll_direction_this_frame(&self) -> Option<Direction> {
		tortuise::scroll_direction_this_frame(self.0)
			.try_into()
			.ok()
	}

	pub fn is_gamepad_connected_this_frame(&self) -> Option<Gamepad> {
		let gamepad_id = tortuise::is_gamepad_connected_this_frame(self.0);
		if gamepad_id == 0 {
			return None;
		}

		Some(Gamepad(gamepad_id))
	}

	pub fn is_focused(&self) -> bool {
		tortuise::is_focused(self.0) > 0
	}

	pub fn was_focused_this_frame(&self) -> bool {
		tortuise::was_focused_this_frame(self.0) > 0
	}

	pub fn was_unfocused_this_frame(&self) -> bool {
		tortuise::was_unfocused_this_frame(self.0) > 0
	}
}