joeylib2/include/joey/input.h
2026-10-19 15:46:47 -05:00

203 lines
8.4 KiB
C

// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
// See LICENSE.md for license information.
// SPDX-License-Identifier: MIT
//
// Keyboard, mouse, and joystick input polling.
//
// Call jlInputPoll() once per frame (typically right before
// drawing) to refresh keyboard, mouse, and joystick state. After
// polling, the jlKey* predicates return the current state of every
// key:
//
// jlKeyDown(k) -- is key k held down right now
// jlKeyPressed(k) -- rising edge since the previous poll
// jlKeyReleased(k) -- falling edge since the previous poll
//
// Typed-character input (text entry) is a separate path:
//
// jlInputGetChar() -- pop the next typed character, or -1
//
// The key predicates are the right shape for games (is the fire key
// held?); the character queue is the right shape for text fields
// (which character did the user type, with shift and the machine's
// keyboard layout already applied). Both are refreshed by the same
// jlInputPoll() and coexist freely.
//
// The mouse predicates return the pointer state:
//
// jlMouseX/Y() -- pointer position in surface
// coords (0..JL_SURFACE_WIDTH-1,
// 0..JL_SURFACE_HEIGHT-1)
// jlMouseDown(b) -- is button b held right now
// jlMousePressed(b) -- rising edge since last poll
// jlMouseReleased(b) -- falling edge since last poll
//
// Every port has two buttons, left and right, whatever its mouse has: a
// left click made with Ctrl held is the right button (from the press until
// the button is let go), so a one-button mouse has both.
//
// The joystick predicates return per-stick state:
//
// jlJoystickConnected(js) -- true if the platform reports a
// stick on this port
// jlJoystickX/Y(js) -- axis values, signed -127..+127.
// Digital sticks snap to the
// extremes; analog sticks return
// their offset from center, scaled,
// dead zone applied.
// jlJoyDown(js, b) -- is button b held on stick js
// jlJoyPressed(js, b) -- rising edge since last poll
// jlJoyReleased(js, b) -- falling edge since last poll
//
// Edge predicates are one-shot: they return true only in the
// frame the transition occurred and false thereafter.
#ifndef JOEYLIB_INPUT_H
#define JOEYLIB_INPUT_H
#include "platform.h"
#include "types.h"
typedef enum {
JL_KEY_NONE = 0,
JL_KEY_A, JL_KEY_B, JL_KEY_C, JL_KEY_D, JL_KEY_E, JL_KEY_F, JL_KEY_G, JL_KEY_H, JL_KEY_I,
JL_KEY_J, JL_KEY_K, JL_KEY_L, JL_KEY_M, JL_KEY_N, JL_KEY_O, JL_KEY_P, JL_KEY_Q, JL_KEY_R,
JL_KEY_S, JL_KEY_T, JL_KEY_U, JL_KEY_V, JL_KEY_W, JL_KEY_X, JL_KEY_Y, JL_KEY_Z,
JL_KEY_0, JL_KEY_1, JL_KEY_2, JL_KEY_3, JL_KEY_4,
JL_KEY_5, JL_KEY_6, JL_KEY_7, JL_KEY_8, JL_KEY_9,
JL_KEY_SPACE, JL_KEY_ESCAPE, JL_KEY_RETURN, JL_KEY_TAB, JL_KEY_BACKSPACE,
JL_KEY_UP, JL_KEY_DOWN, JL_KEY_LEFT, JL_KEY_RIGHT,
JL_KEY_LSHIFT, JL_KEY_RSHIFT, JL_KEY_LCTRL, JL_KEY_LALT,
JL_KEY_F1, JL_KEY_F2, JL_KEY_F3, JL_KEY_F4, JL_KEY_F5,
JL_KEY_F6, JL_KEY_F7, JL_KEY_F8, JL_KEY_F9, JL_KEY_F10,
JL_KEY_COUNT
} jlKeyE;
typedef enum {
JL_MOUSE_BUTTON_NONE = 0,
JL_MOUSE_BUTTON_LEFT,
JL_MOUSE_BUTTON_RIGHT,
JL_MOUSE_BUTTON_COUNT
} jlMouseButtonE;
typedef enum {
JL_JOYSTICK_0 = 0,
JL_JOYSTICK_1,
JL_JOYSTICK_COUNT
} jlJoystickE;
typedef enum {
JL_JOY_BUTTON_0 = 0,
JL_JOY_BUTTON_1,
JL_JOY_BUTTON_COUNT
} jlJoyButtonE;
#define JL_JOYSTICK_AXIS_MAX 127
#define JL_JOYSTICK_AXIS_MIN (-127)
// Control characters delivered through jlInputGetChar alongside
// printable ASCII. Everything else below 0x20, and everything above
// 0x7F, is filtered out of the queue.
#define JL_CHAR_BACKSPACE 0x08
#define JL_CHAR_TAB 0x09
#define JL_CHAR_RETURN 0x0D
#define JL_CHAR_ESCAPE 0x1B
// The FORWARD delete (the key labeled Del/Delete on a PC, ST or Amiga keyboard - not backspace). A text
// editor needs both: backspace removes the character BEFORE the caret, this one removes the character
// UNDER it. 0x7F is ASCII DEL. The IIgs never delivers it: its Delete key is backspace.
#define JL_CHAR_DELETE 0x7F
// Typed-character queue capacity (ring buffer; one slot stays empty,
// so JL_CHAR_QUEUE_SIZE - 1 characters can be pending). Must be a
// power of two.
#define JL_CHAR_QUEUE_SIZE 32
void jlInputPoll(void);
// Pop the next typed character as a 7-bit code, or -1 if the queue
// is empty. Codes: printable ASCII 0x20..0x7E, plus JL_CHAR_BACKSPACE,
// JL_CHAR_TAB, JL_CHAR_RETURN, JL_CHAR_ESCAPE, JL_CHAR_DELETE. Shift, caps lock, and
// the keyboard layout are already applied by the backend, so
// punctuation and shifted symbols arrive correctly on every port.
// (The layout is the machine's own on IIgs/Amiga/ST/X68000, DVX's on
// DVX, and the OS's on the hosted ports; DOS and the PSP's on-screen
// keyboard use a fixed US layout. On the ST a key held with Alt types
// nothing.)
//
// The queue is refilled by jlInputPoll() -- no extra bring-up is
// needed. It holds JL_CHAR_QUEUE_SIZE - 1 characters; when full,
// further characters are dropped (drop-newest) until the app pops.
// A held key repeats its character on every port (the caller manages
// repeat policy).
// A chord types nothing on any port: Ctrl+M is not Return.
// 7-bit ASCII only; no Unicode / IME. Arrow keys are not characters
// and stay on the predicate API (jlKeyDown(JL_KEY_UP) etc.).
// jlWaitForAnyKey empties the queue when it returns -- its dismissing
// keystroke is consumed by the wait, not delivered as text.
//
// Returns int (not a stdint type) deliberately: getchar()-style
// "byte or -1" is the interface contract consumers expect.
int jlInputGetChar(void);
// Block until the user presses a key: one with a jlKeyE, or one that types
// a character (punctuation, which has no jlKeyE). Mouse and joystick
// buttons do not end it, and a key held when the wait starts must be let go
// and pressed again.
// Internally polls via jlInputPoll, so per-port jlpInputPoll machinery
// (including audio-friendly IRQ-driven samplers) keeps working while
// the wait loop runs.
void jlWaitForAnyKey(void);
// A key two physical keys give (Return and the keypad's Enter, an arrow and
// the keypad's 8, 2, 4, or 6, the left and right Ctrl or Alt) is down while
// either is held.
bool jlKeyDown(jlKeyE key);
bool jlKeyPressed(jlKeyE key);
bool jlKeyReleased(jlKeyE key);
// True when the platform confirmed an attached mouse at init (DOS: the INT 33h driver reset;
// Amiga: JoeyLib's input window opened; Win32/Win16: Windows reports one; SDL3/web: SDL reports one;
// IIgs/ST/X68000/DVX/Haiku/BeOS: always; PSP: always, its analog nub, with L and R its buttons).
bool jlMousePresent(void);
int16_t jlMouseX(void);
int16_t jlMouseY(void);
bool jlMouseDown(jlMouseButtonE button);
bool jlMousePressed(jlMouseButtonE button);
bool jlMouseReleased(jlMouseButtonE button);
// Every stick has two buttons. On the ST's own joystick ports, and on
// the Amiga until a stick's second button is first pressed, a stick is
// taken to have one: a quick press of it is JL_JOY_BUTTON_0, down for
// the one poll after it is let go, and a press held for half a second
// is JL_JOY_BUTTON_1, down from then until it is let go: act on button
// 0 with jlJoyPressed, not jlJoyDown.
bool jlJoystickConnected(jlJoystickE js);
int8_t jlJoystickX(jlJoystickE js);
int8_t jlJoystickY(jlJoystickE js);
bool jlJoyDown(jlJoystickE js, jlJoyButtonE button);
bool jlJoyPressed(jlJoystickE js, jlJoyButtonE button);
bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button);
// Set the stick's dead zone (8 at jlInit): axis values within `deadZone` of 0 (on the
// -127..127 scale) read 0 on the ports with analog sticks (IIgs, DOS,
// DVX, hosted); use 0 to disable it. On the IIgs and DOS the call also
// re-centers the stick: the next reading after it is taken as the
// center, so the user must hold the stick centered when calling. The
// IIgs port stops polling after about a second (60 polls) with no stick
// seen (an absent stick costs ~1 ms a frame of busy-wait) and does NOT
// re-probe by itself: this call resumes polling. DOS probes the game
// port again; the Amiga hands the port back to lowlevel.library's own
// detection. Elsewhere only the dead zone changes.
void jlJoystickReset(jlJoystickE js, uint8_t deadZone);
#endif