// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Stable gamepad identifier.
pub(all) struct Gamepad {
id : Int
} derive(Eq, Debug, Hash)
///|
pub fn Gamepad::Gamepad(id : Int) -> Gamepad {
{ id, }
}
///|
pub fn Gamepad::id(self : Gamepad) -> Int {
self.id
}
///|
/// Logical gamepad button layout aligned with Bevy conventions.
pub(all) enum GamepadButton {
South
East
North
West
C
Z
LeftTrigger
LeftTrigger2
RightTrigger
RightTrigger2
Select
Start
Mode
LeftThumb
RightThumb
DPadUp
DPadDown
DPadLeft
DPadRight
} derive(Eq, Debug, Hash)
///|
/// Logical gamepad axis layout aligned with Bevy conventions.
pub(all) enum GamepadAxis {
LeftStickX
LeftStickY
RightStickX
RightStickY
LeftZ
RightZ
} derive(Eq, Debug, Hash)
///|
pub(all) enum GamepadButtonInputState {
Pressed
Released
} derive(Eq, Debug)
///|
pub struct GamepadConnectionEvent {
gamepad : Gamepad
connected : Bool
}
///|
pub struct GamepadButtonEvent {
input : GamepadButtonInput
state : GamepadButtonInputState
}
///|
pub struct GamepadAxisEvent {
input : GamepadAxisInput
value : Double
delta : Double
}
///|
pub(all) struct GamepadButtonInput {
gamepad : Gamepad
button : GamepadButton
} derive(Eq, Debug, Hash)
///|
pub fn GamepadButtonInput::GamepadButtonInput(
gamepad : Gamepad,
button : GamepadButton,
) -> GamepadButtonInput {
{ gamepad, button }
}
///|
pub(all) struct GamepadAxisInput {
gamepad : Gamepad
axis : GamepadAxis
} derive(Eq, Debug, Hash)
///|
pub fn GamepadAxisInput::GamepadAxisInput(
gamepad : Gamepad,
axis : GamepadAxis,
) -> GamepadAxisInput {
{ gamepad, axis }
}
///|
priv struct GamepadWorldStore {
connected_gamepads : Set[Gamepad]
pressed_gamepad_buttons : Set[GamepadButtonInput]
gamepad_axis_values : Map[GamepadAxisInput, Double]
just_connected_gamepads : Set[Gamepad]
just_disconnected_gamepads : Set[Gamepad]
just_pressed_gamepad_buttons : Set[GamepadButtonInput]
just_released_gamepad_buttons : Set[GamepadButtonInput]
last_connected_gamepads : Set[Gamepad]
last_pressed_gamepad_buttons : Set[GamepadButtonInput]
last_gamepad_axis_values : Map[GamepadAxisInput, Double]
}
///|
let gamepad_world_stores : Map[UInt, GamepadWorldStore] = Map([])
///|
fn gamepad_world_store() -> GamepadWorldStore {
let world_id = @ecs.require_current_world().id()
gamepad_world_stores.get_or_init(world_id, () => {
connected_gamepads: Set([]),
pressed_gamepad_buttons: Set([]),
gamepad_axis_values: Map([]),
just_connected_gamepads: Set([]),
just_disconnected_gamepads: Set([]),
just_pressed_gamepad_buttons: Set([]),
just_released_gamepad_buttons: Set([]),
last_connected_gamepads: Set([]),
last_pressed_gamepad_buttons: Set([]),
last_gamepad_axis_values: Map([]),
})
}
///|
/// Connected gamepads in the current frame.
pub fn connected_gamepads() -> Set[Gamepad] {
gamepad_world_store().connected_gamepads
}
///|
/// Buttons currently pressed in the current frame.
pub fn pressed_gamepad_buttons() -> Set[GamepadButtonInput] {
gamepad_world_store().pressed_gamepad_buttons
}
///|
/// Current axis values in the current frame.
pub fn gamepad_axis_values() -> Map[GamepadAxisInput, Double] {
gamepad_world_store().gamepad_axis_values
}
///|
/// Gamepads that became connected in this frame.
pub fn just_connected_gamepads() -> Set[Gamepad] {
gamepad_world_store().just_connected_gamepads
}
///|
/// Gamepads that became disconnected in this frame.
pub fn just_disconnected_gamepads() -> Set[Gamepad] {
gamepad_world_store().just_disconnected_gamepads
}
///|
/// Buttons that transitioned to pressed in this frame.
pub fn just_pressed_gamepad_buttons() -> Set[GamepadButtonInput] {
gamepad_world_store().just_pressed_gamepad_buttons
}
///|
/// Buttons that transitioned to released in this frame.
pub fn just_released_gamepad_buttons() -> Set[GamepadButtonInput] {
gamepad_world_store().just_released_gamepad_buttons
}
///|
fn last_connected_gamepads() -> Set[Gamepad] {
gamepad_world_store().last_connected_gamepads
}
///|
fn last_pressed_gamepad_buttons() -> Set[GamepadButtonInput] {
gamepad_world_store().last_pressed_gamepad_buttons
}
///|
fn last_gamepad_axis_values() -> Map[GamepadAxisInput, Double] {
gamepad_world_store().last_gamepad_axis_values
}
///|
pub let gamepad_connection_event_bus : @event.Events[GamepadConnectionEvent] = Events()
///|
pub let gamepad_button_event_bus : @event.Events[GamepadButtonEvent] = Events()
///|
pub let gamepad_axis_event_bus : @event.Events[GamepadAxisEvent] = Events()
///|
pub fn is_gamepad_connected(gamepad : Gamepad) -> Bool {
connected_gamepads().contains(gamepad)
}
///|
pub fn is_gamepad_just_connected(gamepad : Gamepad) -> Bool {
just_connected_gamepads().contains(gamepad)
}
///|
pub fn is_gamepad_just_disconnected(gamepad : Gamepad) -> Bool {
just_disconnected_gamepads().contains(gamepad)
}
///|
pub fn is_gamepad_button_pressed(
gamepad : Gamepad,
button : GamepadButton,
) -> Bool {
pressed_gamepad_buttons().contains(GamepadButtonInput(gamepad, button))
}
///|
pub fn is_gamepad_button_released(
gamepad : Gamepad,
button : GamepadButton,
) -> Bool {
!is_gamepad_button_pressed(gamepad, button)
}
///|
pub fn is_gamepad_button_just_pressed(
gamepad : Gamepad,
button : GamepadButton,
) -> Bool {
just_pressed_gamepad_buttons().contains(GamepadButtonInput(gamepad, button))
}
///|
pub fn is_gamepad_button_just_released(
gamepad : Gamepad,
button : GamepadButton,
) -> Bool {
just_released_gamepad_buttons().contains(GamepadButtonInput(gamepad, button))
}
///|
pub fn gamepad_axis_value(gamepad : Gamepad, axis : GamepadAxis) -> Double {
gamepad_axis_values().get(GamepadAxisInput(gamepad, axis)).unwrap_or(0.0)
}
///|
pub fn gamepad_left_stick(gamepad : Gamepad) -> @math.Vec2 {
Vec2(
gamepad_axis_value(gamepad, LeftStickX),
gamepad_axis_value(gamepad, LeftStickY),
)
}
///|
pub fn gamepad_right_stick(gamepad : Gamepad) -> @math.Vec2 {
Vec2(
gamepad_axis_value(gamepad, RightStickX),
gamepad_axis_value(gamepad, RightStickY),
)
}
///|
pub fn gamepad_triggers(gamepad : Gamepad) -> @math.Vec2 {
Vec2(gamepad_axis_value(gamepad, LeftZ), gamepad_axis_value(gamepad, RightZ))
}
///|
pub fn advanced_gamepad_system(_delta : Double) -> Unit {
just_connected_gamepads().clear()
for gamepad in connected_gamepads().difference(last_connected_gamepads()) {
just_connected_gamepads().add(gamepad)
gamepad_connection_event_bus.send({ gamepad, connected: true })
}
just_disconnected_gamepads().clear()
for gamepad in last_connected_gamepads().difference(connected_gamepads()) {
just_disconnected_gamepads().add(gamepad)
gamepad_connection_event_bus.send({ gamepad, connected: false })
}
just_pressed_gamepad_buttons().clear()
for
input in pressed_gamepad_buttons().difference(
last_pressed_gamepad_buttons(),
) {
just_pressed_gamepad_buttons().add(input)
gamepad_button_event_bus.send({ input, state: Pressed })
}
just_released_gamepad_buttons().clear()
for
input in last_pressed_gamepad_buttons().difference(
pressed_gamepad_buttons(),
) {
just_released_gamepad_buttons().add(input)
gamepad_button_event_bus.send({ input, state: Released })
}
let stale_axes : Array[GamepadAxisInput] = []
for axis_input, _value in gamepad_axis_values() {
if !connected_gamepads().contains(axis_input.gamepad) {
stale_axes.push(axis_input)
}
}
for axis_input in stale_axes {
gamepad_axis_values().remove(axis_input)
last_gamepad_axis_values().remove(axis_input)
}
for axis_input, value in gamepad_axis_values() {
let previous = last_gamepad_axis_values().get(axis_input).unwrap_or(0.0)
let delta = value - previous
if delta.abs() > 0.00001 {
gamepad_axis_event_bus.send({ input: axis_input, value, delta })
}
last_gamepad_axis_values().set(axis_input, value)
}
last_connected_gamepads().clear()
for gamepad in connected_gamepads() {
last_connected_gamepads().add(gamepad)
}
last_pressed_gamepad_buttons().clear()
for input in pressed_gamepad_buttons() {
last_pressed_gamepad_buttons().add(input)
}
}