use bevy::input::ButtonState;
use bevy::input::gamepad::{
GamepadAxisChangedEvent, GamepadButton, GamepadButtonStateChangedEvent,
};
use bevy::input::keyboard::{KeyCode, KeyboardInput};
use bevy::input::mouse::MouseButtonInput;
use bevy::prelude::*;
use crate::binding::{Binding, BindingSettings};
use crate::model::STICK_PUSH;
use crate::skin::Role;
#[derive(Resource, Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Reflect)]
#[reflect(Resource, Default, Debug, PartialEq, Hash, Clone)]
pub enum InputDevice {
#[default]
Keyboard,
Pad,
}
#[derive(Component, Clone, Copy, Debug, PartialEq, Eq, Reflect)]
#[reflect(Component, Debug, PartialEq, Clone)]
pub struct Prompt {
pub keyboard: Binding,
pub pad: Binding,
}
impl Prompt {
pub const SELECT: Self = Self::new(
Binding::Key(KeyCode::Enter),
Binding::Pad(GamepadButton::South),
);
pub const BACK: Self = Self::new(
Binding::Key(KeyCode::Escape),
Binding::Pad(GamepadButton::East),
);
pub const fn new(keyboard: Binding, pad: Binding) -> Self {
Self { keyboard, pad }
}
pub fn binding(&self, device: InputDevice) -> Binding {
match device {
InputDevice::Keyboard => self.keyboard,
InputDevice::Pad => self.pad,
}
}
}
pub fn track_input_device(
mut keys: MessageReader<KeyboardInput>,
mut clicks: MessageReader<MouseButtonInput>,
mut buttons: MessageReader<GamepadButtonStateChangedEvent>,
mut sticks: MessageReader<GamepadAxisChangedEvent>,
mut device: ResMut<InputDevice>,
) {
let by_keyboard = keys
.read()
.filter(|key| key.state == ButtonState::Pressed)
.count()
+ clicks
.read()
.filter(|click| click.state == ButtonState::Pressed)
.count();
let by_pad = buttons
.read()
.filter(|button| button.state == ButtonState::Pressed)
.count()
+ sticks
.read()
.filter(|stick| stick.value.abs() >= STICK_PUSH)
.count();
if by_pad > 0 {
device.set_if_neq(InputDevice::Pad);
} else if by_keyboard > 0 {
device.set_if_neq(InputDevice::Keyboard);
}
}
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Hash, Clone)]
pub enum PromptsAnchor {
#[default]
BottomLeft,
BottomCenter,
BottomRight,
TopLeft,
TopCenter,
TopRight,
}
impl PromptsAnchor {
pub const fn at_bottom(self) -> bool {
matches!(
self,
Self::BottomLeft | Self::BottomCenter | Self::BottomRight
)
}
pub const fn is_centred(self) -> bool {
matches!(self, Self::BottomCenter | Self::TopCenter)
}
pub const fn at_right(self) -> bool {
matches!(self, Self::BottomRight | Self::TopRight)
}
fn translation(self) -> UiTransform {
if self.is_centred() {
UiTransform::from_translation(Val2::percent(-50.0, 0.0))
} else {
UiTransform::IDENTITY
}
}
}
pub fn place_prompts(
mut legends: Query<(&PromptsAnchor, &mut UiTransform), Changed<PromptsAnchor>>,
) {
for (anchor, mut transform) in &mut legends {
let wanted = anchor.translation();
if *transform != wanted {
*transform = wanted;
}
}
}
pub fn update_prompts(
device: Res<InputDevice>,
settings: Res<BindingSettings>,
prompts: Query<(Entity, &Prompt)>,
changed: Query<Entity, Changed<Prompt>>,
children: Query<&Children>,
mut keycaps: Query<(&mut Role, &mut Text)>,
) {
let dirty: Vec<Entity> = if device.is_changed() || settings.is_changed() {
prompts.iter().map(|(entity, _)| entity).collect()
} else {
changed.iter().collect()
};
for entity in dirty {
let (Ok((_, keys)), Ok(parts)) = (prompts.get(entity), children.get(entity)) else {
continue;
};
let input = keys.binding(*device);
let shape = match input {
Binding::Pad(_) => Role::PromptButton,
Binding::Key(_) | Binding::Mouse(_) | Binding::Chord(_) => Role::PromptKey,
};
let name = input.label(settings.pad_style);
for &part in parts {
let Ok((mut role, mut text)) = keycaps.get_mut(part) else {
continue;
};
if !matches!(*role, Role::PromptKey | Role::PromptButton) {
continue;
}
role.set_if_neq(shape);
if text.0 != name {
text.0.clone_from(&name);
}
}
}
}