///|
/// Input utility helpers (edge detection per tick).
///
/// Ebiten refs:
/// - inpututil/inpututil.go
/// - internal/inputstate/inputstate.go
///|
struct KeyDurationEntry {
key : Int
duration : Int
} derive(Debug)
///|
pub impl Show for KeyDurationEntry with output(self, logger) {
logger.write_object(to_repr(self))
}
///|
struct MouseButtonDurationEntry {
button : Int
duration : Int
} derive(Debug)
///|
pub impl Show for MouseButtonDurationEntry with output(self, logger) {
logger.write_object(to_repr(self))
}
///|
pub struct KeyInputState {
mut prev_pressed_keys : Array[Int]
mut pressed_keys : Array[Int]
mut just_pressed_keys : Array[Int]
mut just_released_keys : Array[Int]
mut durations : Array[KeyDurationEntry]
}
///|
pub struct MouseButtonInputState {
mut prev_pressed_buttons : Array[Int]
mut pressed_buttons : Array[Int]
mut just_pressed_buttons : Array[Int]
mut just_released_buttons : Array[Int]
mut durations : Array[MouseButtonDurationEntry]
}
///|
fn contains_key(keys : Array[Int], key : Int) -> Bool {
let mut found = false
for current in keys {
if current == key {
found = true
}
}
found
}
///|
fn normalize_pressed_keys(keys : Array[Int]) -> Array[Int] {
let out : Array[Int] = []
for key in keys {
if !contains_key(out, key) {
out.push(key)
}
}
out
}
///|
fn duration_for_key(entries : Array[KeyDurationEntry], key : Int) -> Int {
let mut out = 0
for entry in entries {
if entry.key == key {
out = entry.duration
}
}
out
}
///|
pub fn new_key_input_state() -> KeyInputState {
{
prev_pressed_keys: [],
pressed_keys: [],
just_pressed_keys: [],
just_released_keys: [],
durations: [],
}
}
///|
fn duration_for_mouse_button(
entries : Array[MouseButtonDurationEntry],
button : Int,
) -> Int {
let mut out = 0
for entry in entries {
if entry.button == button {
out = entry.duration
}
}
out
}
///|
pub fn new_mouse_input_state() -> MouseButtonInputState {
{
prev_pressed_buttons: [],
pressed_buttons: [],
just_pressed_buttons: [],
just_released_buttons: [],
durations: [],
}
}
///|
pub fn update_key_input_state(
state : KeyInputState,
snapshot : @core.InputSnapshot,
) -> Unit {
let prev_pressed = state.pressed_keys
let next_pressed = normalize_pressed_keys(snapshot.pressed_keys)
let just_pressed : Array[Int] = []
for key in next_pressed {
if !contains_key(prev_pressed, key) {
just_pressed.push(key)
}
}
let just_released : Array[Int] = []
for key in prev_pressed {
if !contains_key(next_pressed, key) {
just_released.push(key)
}
}
let next_durations : Array[KeyDurationEntry] = []
for key in next_pressed {
let duration = if contains_key(prev_pressed, key) {
duration_for_key(state.durations, key) + 1
} else {
1
}
next_durations.push({ key, duration })
}
state.prev_pressed_keys = prev_pressed
state.pressed_keys = next_pressed
state.just_pressed_keys = just_pressed
state.just_released_keys = just_released
state.durations = next_durations
}
///|
pub fn update_mouse_input_state(
state : MouseButtonInputState,
snapshot : @core.InputSnapshot,
) -> Unit {
let prev_pressed = state.pressed_buttons
let next_pressed = normalize_pressed_keys(snapshot.pressed_mouse_buttons)
let just_pressed : Array[Int] = []
for button in next_pressed {
if !contains_key(prev_pressed, button) {
just_pressed.push(button)
}
}
let just_released : Array[Int] = []
for button in prev_pressed {
if !contains_key(next_pressed, button) {
just_released.push(button)
}
}
let next_durations : Array[MouseButtonDurationEntry] = []
for button in next_pressed {
let duration = if contains_key(prev_pressed, button) {
duration_for_mouse_button(state.durations, button) + 1
} else {
1
}
next_durations.push({ button, duration })
}
state.prev_pressed_buttons = prev_pressed
state.pressed_buttons = next_pressed
state.just_pressed_buttons = just_pressed
state.just_released_buttons = just_released
state.durations = next_durations
}
///|
pub fn is_key_pressed(state : KeyInputState, key : Int) -> Bool {
contains_key(state.pressed_keys, key)
}
///|
pub fn is_key_just_pressed(state : KeyInputState, key : Int) -> Bool {
contains_key(state.just_pressed_keys, key)
}
///|
pub fn is_key_just_released(state : KeyInputState, key : Int) -> Bool {
contains_key(state.just_released_keys, key)
}
///|
pub fn key_press_duration(state : KeyInputState, key : Int) -> Int {
duration_for_key(state.durations, key)
}
///|
pub fn is_mouse_button_pressed(
state : MouseButtonInputState,
button : Int,
) -> Bool {
contains_key(state.pressed_buttons, button)
}
///|
pub fn is_mouse_button_just_pressed(
state : MouseButtonInputState,
button : Int,
) -> Bool {
contains_key(state.just_pressed_buttons, button)
}
///|
pub fn is_mouse_button_just_released(
state : MouseButtonInputState,
button : Int,
) -> Bool {
contains_key(state.just_released_buttons, button)
}
///|
pub fn mouse_button_press_duration(
state : MouseButtonInputState,
button : Int,
) -> Int {
duration_for_mouse_button(state.durations, button)
}
///|
pub fn append_pressed_keys(
state : KeyInputState,
dst : Array[Int],
) -> Array[Int] {
let out = dst
for key in state.pressed_keys {
out.push(key)
}
out
}
///|
pub fn append_just_pressed_keys(
state : KeyInputState,
dst : Array[Int],
) -> Array[Int] {
let out = dst
for key in state.just_pressed_keys {
out.push(key)
}
out
}
///|
pub fn append_just_released_keys(
state : KeyInputState,
dst : Array[Int],
) -> Array[Int] {
let out = dst
for key in state.just_released_keys {
out.push(key)
}
out
}
///|
pub fn append_pressed_mouse_buttons(
state : MouseButtonInputState,
dst : Array[Int],
) -> Array[Int] {
let out = dst
for button in state.pressed_buttons {
out.push(button)
}
out
}
///|
pub fn append_just_pressed_mouse_buttons(
state : MouseButtonInputState,
dst : Array[Int],
) -> Array[Int] {
let out = dst
for button in state.just_pressed_buttons {
out.push(button)
}
out
}
///|
pub fn append_just_released_mouse_buttons(
state : MouseButtonInputState,
dst : Array[Int],
) -> Array[Int] {
let out = dst
for button in state.just_released_buttons {
out.push(button)
}
out
}
///|
pub struct InputHelper {
key_state : KeyInputState
mouse_state : MouseButtonInputState
touch_state : TouchInputState
}
///|
pub fn new_input_helper() -> InputHelper {
{
key_state: new_key_input_state(),
mouse_state: new_mouse_input_state(),
touch_state: new_touch_input_state(),
}
}
///|
pub fn update_input_helper(
helper : InputHelper,
snapshot : @core.InputSnapshot,
) -> Unit {
update_key_input_state(helper.key_state, snapshot)
update_mouse_input_state(helper.mouse_state, snapshot)
update_touch_input_state(helper.touch_state, snapshot)
}
///|
pub fn is_any_action_just_pressed(
helper : InputHelper,
_input : @core.InputSnapshot,
) -> Bool {
helper.key_state.just_pressed_keys.length() > 0 ||
helper.mouse_state.just_pressed_buttons.length() > 0 ||
helper.touch_state.just_pressed_touch_ids.length() > 0
}
// ============================================================
// Key code constants
// ============================================================
///|
pub let key_w : Int = 87
///|
pub let key_a : Int = 65
///|
pub let key_s : Int = 83
///|
pub let key_d : Int = 68
///|
pub let key_up_web : Int = 38
///|
pub let key_down_web : Int = 40
///|
pub let key_left_web : Int = 37
///|
pub let key_right_web : Int = 39
///|
pub let key_up_glfw : Int = 265
///|
pub let key_down_glfw : Int = 264
///|
pub let key_left_glfw : Int = 263
///|
pub let key_right_glfw : Int = 262
// Dvorak layout equivalents for WASD: ,aoe
// ',' (Dvorak W position) = keyCode 188
// 'a' (Dvorak A position) = keyCode 65 (same as QWERTY)
// 'o' (Dvorak S position) = keyCode 79
// 'e' (Dvorak D position) = keyCode 69
///|
pub let key_comma : Int = 188
///|
pub let key_o : Int = 79
///|
pub let key_e : Int = 69
///|
pub let key_space : Int = 32
///|
pub let key_enter_web : Int = 13
///|
pub let key_enter_glfw : Int = 257
// ============================================================
// Direction helpers (WASD + Arrow keys for web + GLFW)
// ============================================================
///|
pub fn is_move_up(key_state : KeyInputState) -> Bool {
is_key_pressed(key_state, key_w) ||
is_key_pressed(key_state, key_comma) || // Dvorak ','
is_key_pressed(key_state, key_up_web) ||
is_key_pressed(key_state, key_up_glfw)
}
///|
pub fn is_move_down(key_state : KeyInputState) -> Bool {
is_key_pressed(key_state, key_s) ||
is_key_pressed(key_state, key_o) || // Dvorak 'o'
is_key_pressed(key_state, key_down_web) ||
is_key_pressed(key_state, key_down_glfw)
}
///|
pub fn is_move_left(key_state : KeyInputState) -> Bool {
is_key_pressed(key_state, key_a) || // same on Dvorak
is_key_pressed(key_state, key_left_web) ||
is_key_pressed(key_state, key_left_glfw)
}
///|
pub fn is_move_right(key_state : KeyInputState) -> Bool {
is_key_pressed(key_state, key_d) ||
is_key_pressed(key_state, key_e) || // Dvorak 'e'
is_key_pressed(key_state, key_right_web) ||
is_key_pressed(key_state, key_right_glfw)
}
///|
pub fn is_confirm_just_pressed(helper : InputHelper) -> Bool {
is_key_just_pressed(helper.key_state, key_space) ||
is_key_just_pressed(helper.key_state, key_enter_web) ||
is_key_just_pressed(helper.key_state, key_enter_glfw)
}
///|
pub fn is_up_just_pressed(helper : InputHelper) -> Bool {
is_key_just_pressed(helper.key_state, key_w) ||
is_key_just_pressed(helper.key_state, key_comma) || // Dvorak ','
is_key_just_pressed(helper.key_state, key_up_web) ||
is_key_just_pressed(helper.key_state, key_up_glfw)
}
///|
pub fn is_down_just_pressed(helper : InputHelper) -> Bool {
is_key_just_pressed(helper.key_state, key_s) ||
is_key_just_pressed(helper.key_state, key_o) || // Dvorak 'o'
is_key_just_pressed(helper.key_state, key_down_web) ||
is_key_just_pressed(helper.key_state, key_down_glfw)
}