use accesskit::Role;
use bevy_a11y::AccessibilityNode;
use bevy_app::{App, Plugin};
use bevy_ecs::query::Has;
use bevy_ecs::system::In;
use bevy_ecs::{
component::Component,
entity::Entity,
observer::On,
query::With,
system::{Commands, Query},
};
use bevy_input::keyboard::{KeyCode, KeyboardInput};
use bevy_input::ButtonState;
use bevy_input_focus::FocusedInput;
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
use bevy_ui::{InteractionDisabled, Pressed};
use crate::{Activate, Callback, Notify};
#[derive(Component, Default, Debug)]
#[require(AccessibilityNode(accesskit::Node::new(Role::Button)))]
pub struct Button {
pub on_activate: Callback<In<Activate>>,
}
fn button_on_key_event(
mut event: On<FocusedInput<KeyboardInput>>,
q_state: Query<(&Button, Has<InteractionDisabled>)>,
mut commands: Commands,
) {
if let Ok((bstate, disabled)) = q_state.get(event.focused_entity)
&& !disabled
{
let input_event = &event.input;
if !input_event.repeat
&& input_event.state == ButtonState::Pressed
&& (input_event.key_code == KeyCode::Enter || input_event.key_code == KeyCode::Space)
{
event.propagate(false);
commands.notify_with(&bstate.on_activate, Activate(event.focused_entity));
}
}
}
fn button_on_pointer_click(
mut click: On<Pointer<Click>>,
mut q_state: Query<(&Button, Has<Pressed>, Has<InteractionDisabled>)>,
mut commands: Commands,
) {
if let Ok((bstate, pressed, disabled)) = q_state.get_mut(click.entity) {
click.propagate(false);
if pressed && !disabled {
commands.notify_with(&bstate.on_activate, Activate(click.entity));
}
}
}
fn button_on_pointer_down(
mut press: On<Pointer<Press>>,
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<Button>>,
mut commands: Commands,
) {
if let Ok((button, disabled, pressed)) = q_state.get_mut(press.entity) {
press.propagate(false);
if !disabled && !pressed {
commands.entity(button).insert(Pressed);
}
}
}
fn button_on_pointer_up(
mut release: On<Pointer<Release>>,
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<Button>>,
mut commands: Commands,
) {
if let Ok((button, disabled, pressed)) = q_state.get_mut(release.entity) {
release.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
fn button_on_pointer_drag_end(
mut drag_end: On<Pointer<DragEnd>>,
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<Button>>,
mut commands: Commands,
) {
if let Ok((button, disabled, pressed)) = q_state.get_mut(drag_end.entity) {
drag_end.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
fn button_on_pointer_cancel(
mut cancel: On<Pointer<Cancel>>,
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<Button>>,
mut commands: Commands,
) {
if let Ok((button, disabled, pressed)) = q_state.get_mut(cancel.entity) {
cancel.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
pub struct ButtonPlugin;
impl Plugin for ButtonPlugin {
fn build(&self, app: &mut App) {
app.add_observer(button_on_key_event)
.add_observer(button_on_pointer_down)
.add_observer(button_on_pointer_up)
.add_observer(button_on_pointer_click)
.add_observer(button_on_pointer_drag_end)
.add_observer(button_on_pointer_cancel);
}
}