use glm::Vec2;
use hashbrown::{HashMap, HashSet};
use sdl2::controller::{Axis as SdlAxis, Button as SdlButton, GameController};
use sdl2::event::Event;
use sdl2::{GameControllerSubsystem, Sdl};
use crate::error::{Result, TetraError};
use crate::graphics;
use crate::Context;
pub use sdl2::keyboard::Keycode as Key;
pub use sdl2::mouse::MouseButton;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum GamepadButton {
A,
B,
X,
Y,
Up,
Down,
Left,
Right,
LeftShoulder,
LeftTrigger,
LeftStick,
RightShoulder,
RightTrigger,
RightStick,
Start,
Back,
Guide,
}
impl From<SdlButton> for GamepadButton {
fn from(button: SdlButton) -> GamepadButton {
match button {
SdlButton::A => GamepadButton::A,
SdlButton::B => GamepadButton::B,
SdlButton::X => GamepadButton::X,
SdlButton::Y => GamepadButton::Y,
SdlButton::DPadUp => GamepadButton::Up,
SdlButton::DPadDown => GamepadButton::Down,
SdlButton::DPadLeft => GamepadButton::Left,
SdlButton::DPadRight => GamepadButton::Right,
SdlButton::LeftShoulder => GamepadButton::LeftShoulder,
SdlButton::LeftStick => GamepadButton::LeftStick,
SdlButton::RightShoulder => GamepadButton::RightShoulder,
SdlButton::RightStick => GamepadButton::RightStick,
SdlButton::Start => GamepadButton::Start,
SdlButton::Back => GamepadButton::Back,
SdlButton::Guide => GamepadButton::Guide,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum GamepadAxis {
LeftStickX,
LeftStickY,
LeftTrigger,
RightStickX,
RightStickY,
RightTrigger,
}
impl From<GamepadAxis> for SdlAxis {
fn from(axis: GamepadAxis) -> SdlAxis {
match axis {
GamepadAxis::LeftStickX => SdlAxis::LeftX,
GamepadAxis::LeftStickY => SdlAxis::LeftY,
GamepadAxis::LeftTrigger => SdlAxis::TriggerLeft,
GamepadAxis::RightStickX => SdlAxis::RightX,
GamepadAxis::RightStickY => SdlAxis::RightY,
GamepadAxis::RightTrigger => SdlAxis::TriggerRight,
}
}
}
impl From<SdlAxis> for GamepadAxis {
fn from(axis: SdlAxis) -> GamepadAxis {
match axis {
SdlAxis::LeftX => GamepadAxis::LeftStickX,
SdlAxis::LeftY => GamepadAxis::LeftStickY,
SdlAxis::TriggerLeft => GamepadAxis::LeftTrigger,
SdlAxis::RightX => GamepadAxis::RightStickX,
SdlAxis::RightY => GamepadAxis::RightStickY,
SdlAxis::TriggerRight => GamepadAxis::RightTrigger,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum GamepadStick {
LeftStick,
RightStick,
}
struct GamepadState {
sdl_controller: GameController,
current_button_state: HashSet<GamepadButton>,
previous_button_state: HashSet<GamepadButton>,
current_axis_state: HashMap<GamepadAxis, f32>,
}
impl GamepadState {
pub(crate) fn new(sdl_controller: GameController) -> GamepadState {
GamepadState {
sdl_controller,
current_button_state: HashSet::new(),
previous_button_state: HashSet::new(),
current_axis_state: HashMap::new(),
}
}
}
pub(crate) struct InputContext {
current_key_state: HashSet<Key>,
previous_key_state: HashSet<Key>,
current_text_input: Option<String>,
current_mouse_state: HashSet<MouseButton>,
previous_mouse_state: HashSet<MouseButton>,
mouse_position: Vec2,
controller_sys: GameControllerSubsystem,
pads: Vec<Option<GamepadState>>,
sdl_pad_indexes: HashMap<i32, usize>,
}
impl InputContext {
pub(crate) fn new(sdl: &Sdl) -> Result<InputContext> {
let controller_sys = sdl.game_controller().map_err(TetraError::Sdl)?;
sdl2::hint::set("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", "1");
Ok(InputContext {
current_key_state: HashSet::new(),
previous_key_state: HashSet::new(),
current_text_input: None,
current_mouse_state: HashSet::new(),
previous_mouse_state: HashSet::new(),
mouse_position: Vec2::zeros(),
controller_sys,
pads: Vec::new(),
sdl_pad_indexes: HashMap::new(),
})
}
}
pub(crate) fn handle_event(ctx: &mut Context, event: Event) -> Result {
match event {
Event::KeyDown {
keycode: Some(k), ..
} => {
if let Key::Escape = k {
if ctx.quit_on_escape {
ctx.running = false;
}
}
ctx.input.current_key_state.insert(k);
}
Event::KeyUp {
keycode: Some(k), ..
} => {
ctx.input.current_key_state.remove(&k);
}
Event::MouseButtonDown { mouse_btn, .. } => {
ctx.input.current_mouse_state.insert(mouse_btn);
}
Event::MouseButtonUp { mouse_btn, .. } => {
ctx.input.current_mouse_state.remove(&mouse_btn);
}
Event::MouseMotion { x, y, .. } => ctx.input.mouse_position = Vec2::new(x as f32, y as f32),
Event::TextInput { text, .. } => {
ctx.input.current_text_input = Some(text);
}
Event::ControllerDeviceAdded { which, .. } => {
let controller = ctx.input.controller_sys.open(which)?;
let id = controller.instance_id();
for (i, slot) in ctx.input.pads.iter_mut().enumerate() {
if slot.is_none() {
ctx.input.sdl_pad_indexes.insert(id, i);
*slot = Some(GamepadState::new(controller));
return Ok(());
}
}
ctx.input.sdl_pad_indexes.insert(id, ctx.input.pads.len());
ctx.input.pads.push(Some(GamepadState::new(controller)));
}
Event::ControllerDeviceRemoved { which, .. } => {
let i = ctx.input.sdl_pad_indexes.remove(&which).unwrap();
ctx.input.pads[i] = None;
}
Event::ControllerButtonDown { which, button, .. } => {
let i = ctx.input.sdl_pad_indexes[&which];
let pad = ctx.input.pads[i].as_mut().unwrap();
pad.current_button_state.insert(button.into());
}
Event::ControllerButtonUp { which, button, .. } => {
let i = ctx.input.sdl_pad_indexes[&which];
let pad = ctx.input.pads[i].as_mut().unwrap();
pad.current_button_state.remove(&button.into());
}
Event::ControllerAxisMotion {
which, axis, value, ..
} => {
let i = ctx.input.sdl_pad_indexes[&which];
let pad = ctx.input.pads[i].as_mut().unwrap();
pad.current_axis_state
.insert(axis.into(), f32::from(value) / 32767.0);
match axis {
SdlAxis::TriggerLeft => {
if value > 0 {
pad.current_button_state.insert(GamepadButton::LeftTrigger);
} else {
pad.current_button_state.remove(&GamepadButton::LeftTrigger);
}
}
SdlAxis::TriggerRight => {
if value > 0 {
pad.current_button_state.insert(GamepadButton::RightTrigger);
} else {
pad.current_button_state
.remove(&GamepadButton::RightTrigger);
}
}
_ => {}
}
}
_ => {}
}
Ok(())
}
pub(crate) fn cleanup_after_state_update(ctx: &mut Context) {
ctx.input.previous_key_state = ctx.input.current_key_state.clone();
ctx.input.previous_mouse_state = ctx.input.current_mouse_state.clone();
ctx.input.current_text_input = None;
for slot in &mut ctx.input.pads {
if let Some(pad) = slot {
pad.previous_button_state = pad.current_button_state.clone();
}
}
}
pub fn get_text_input(ctx: &Context) -> Option<&str> {
ctx.input.current_text_input.as_ref().map(String::as_str)
}
pub fn is_key_down(ctx: &Context, key: Key) -> bool {
ctx.input.current_key_state.contains(&key)
}
pub fn is_key_up(ctx: &Context, key: Key) -> bool {
!ctx.input.current_key_state.contains(&key)
}
pub fn is_key_pressed(ctx: &Context, key: Key) -> bool {
!ctx.input.previous_key_state.contains(&key) && ctx.input.current_key_state.contains(&key)
}
pub fn is_key_released(ctx: &Context, key: Key) -> bool {
ctx.input.previous_key_state.contains(&key) && !ctx.input.current_key_state.contains(&key)
}
pub fn get_keys_down(ctx: &Context) -> impl Iterator<Item = &Key> {
ctx.input.current_key_state.iter()
}
pub fn get_keys_pressed(ctx: &Context) -> impl Iterator<Item = &Key> {
ctx.input
.current_key_state
.difference(&ctx.input.previous_key_state)
}
pub fn get_keys_released(ctx: &Context) -> impl Iterator<Item = &Key> {
ctx.input
.previous_key_state
.difference(&ctx.input.current_key_state)
}
pub fn is_mouse_button_down(ctx: &Context, button: MouseButton) -> bool {
ctx.input.current_mouse_state.contains(&button)
}
pub fn is_mouse_button_up(ctx: &Context, button: MouseButton) -> bool {
!ctx.input.current_mouse_state.contains(&button)
}
pub fn is_mouse_button_pressed(ctx: &Context, button: MouseButton) -> bool {
!ctx.input.previous_mouse_state.contains(&button)
&& ctx.input.current_mouse_state.contains(&button)
}
pub fn is_mouse_button_released(ctx: &Context, button: MouseButton) -> bool {
ctx.input.previous_mouse_state.contains(&button)
&& !ctx.input.current_mouse_state.contains(&button)
}
pub fn get_mouse_x(ctx: &Context) -> f32 {
let internal_width = graphics::get_internal_width(ctx) as f32;
let screen_rect = graphics::get_screen_rect(ctx);
((ctx.input.mouse_position.x - screen_rect.x) / screen_rect.width) * internal_width
}
pub fn get_mouse_y(ctx: &Context) -> f32 {
let internal_height = graphics::get_internal_height(ctx) as f32;
let screen_rect = graphics::get_screen_rect(ctx);
((ctx.input.mouse_position.y - screen_rect.y) / screen_rect.height) * internal_height
}
pub fn get_mouse_position(ctx: &Context) -> Vec2 {
Vec2::new(get_mouse_x(ctx), get_mouse_y(ctx))
}
pub fn is_gamepad_connected(ctx: &Context, gamepad_index: usize) -> bool {
if let Some(Some(_)) = ctx.input.pads.get(gamepad_index) {
true
} else {
false
}
}
pub fn get_gamepad_name(ctx: &Context, gamepad_index: usize) -> Option<String> {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
Some(pad.sdl_controller.name())
} else {
None
}
}
pub fn is_gamepad_button_down(ctx: &Context, gamepad_index: usize, button: GamepadButton) -> bool {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
pad.current_button_state.contains(&button)
} else {
false
}
}
pub fn is_gamepad_button_up(ctx: &Context, gamepad_index: usize, button: GamepadButton) -> bool {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
!pad.current_button_state.contains(&button)
} else {
true
}
}
pub fn is_gamepad_button_pressed(
ctx: &Context,
gamepad_index: usize,
button: GamepadButton,
) -> bool {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
!pad.previous_button_state.contains(&button) && pad.current_button_state.contains(&button)
} else {
false
}
}
pub fn is_gamepad_button_released(
ctx: &Context,
gamepad_index: usize,
button: GamepadButton,
) -> bool {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
pad.previous_button_state.contains(&button) && !pad.current_button_state.contains(&button)
} else {
false
}
}
enum GamepadIterator<T> {
Disconnected,
Connected(T),
}
impl<T> Iterator for GamepadIterator<T>
where
T: Iterator,
{
type Item = T::Item;
fn next(&mut self) -> Option<T::Item> {
match self {
GamepadIterator::Disconnected => None,
GamepadIterator::Connected(i) => i.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
GamepadIterator::Disconnected => (0, Some(0)),
GamepadIterator::Connected(i) => i.size_hint(),
}
}
}
pub fn get_gamepad_buttons_down(
ctx: &Context,
gamepad_index: usize,
) -> impl Iterator<Item = &GamepadButton> {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
GamepadIterator::Connected(pad.current_button_state.iter())
} else {
GamepadIterator::Disconnected
}
}
pub fn get_gamepad_buttons_pressed(
ctx: &Context,
gamepad_index: usize,
) -> impl Iterator<Item = &GamepadButton> {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
GamepadIterator::Connected(
pad.current_button_state
.difference(&pad.previous_button_state),
)
} else {
GamepadIterator::Disconnected
}
}
pub fn get_gamepad_buttons_released(
ctx: &Context,
gamepad_index: usize,
) -> impl Iterator<Item = &GamepadButton> {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
GamepadIterator::Connected(
pad.previous_button_state
.difference(&pad.current_button_state),
)
} else {
GamepadIterator::Disconnected
}
}
pub fn get_gamepad_axis_position(ctx: &Context, gamepad_index: usize, axis: GamepadAxis) -> f32 {
if let Some(Some(pad)) = ctx.input.pads.get(gamepad_index) {
if let Some(value) = pad.current_axis_state.get(&axis) {
*value
} else {
0.0
}
} else {
0.0
}
}
pub fn get_gamepad_stick_position(
ctx: &Context,
gamepad_index: usize,
stick: GamepadStick,
) -> Vec2 {
let (x_axis, y_axis) = match stick {
GamepadStick::LeftStick => (GamepadAxis::LeftStickX, GamepadAxis::LeftStickY),
GamepadStick::RightStick => (GamepadAxis::RightStickX, GamepadAxis::RightStickY),
};
Vec2::new(
get_gamepad_axis_position(ctx, gamepad_index, x_axis),
get_gamepad_axis_position(ctx, gamepad_index, y_axis),
)
}