use bevy_app::{App, Plugin};
use bevy_ecs::{
entity::Entity,
observer::On,
query::With,
system::{Commands, Query, ResMut},
};
use bevy_picking::events::PointerPress;
use bevy_window::PrimaryWindow;
use crate::{tab_navigation::acquire_focus_tab_index, AcquireFocus, InputFocusVisible};
fn click_to_focus(
press: On<PointerPress>,
mut focus_visible: ResMut<InputFocusVisible>,
windows: Query<Entity, With<PrimaryWindow>>,
mut commands: Commands,
) {
if press.entity == press.original_event_target() {
if focus_visible.0 {
focus_visible.0 = false;
}
if let Ok(window) = windows.single() {
commands.trigger(AcquireFocus {
focused_entity: press.entity,
window,
});
}
}
}
pub struct PointerFocusPlugin;
impl Plugin for PointerFocusPlugin {
fn build(&self, app: &mut App) {
app.add_observer(click_to_focus);
app.add_observer(acquire_focus_tab_index);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
tab_navigation::{TabIndex, TabNavigationPlugin},
AcquireFocus, InputFocus, InputFocusPlugin,
};
use bevy_app::App;
use bevy_ecs::hierarchy::ChildOf;
use bevy_input::InputPlugin;
use bevy_window::{PrimaryWindow, Window};
fn pointer_focus_app() -> (App, Entity) {
let mut app = App::new();
app.add_plugins((InputPlugin, InputFocusPlugin, PointerFocusPlugin));
let window = app
.world_mut()
.spawn((Window::default(), PrimaryWindow))
.id();
app.update();
(app, window)
}
fn pointer_focus_app_with_tab_navigation() -> (App, Entity) {
let mut app = App::new();
app.add_plugins((
InputPlugin,
InputFocusPlugin,
PointerFocusPlugin,
TabNavigationPlugin,
));
let window = app
.world_mut()
.spawn((Window::default(), PrimaryWindow))
.id();
app.update();
(app, window)
}
#[test]
fn acquire_focus_without_tab_navigation_clears_focus_at_window() {
let (mut app, window) = pointer_focus_app();
let previously_focused = app.world_mut().spawn_empty().id();
app.world_mut()
.insert_resource(InputFocus::from_entity(previously_focused));
let non_focusable = app.world_mut().spawn(ChildOf(window)).id();
app.world_mut().trigger(AcquireFocus {
focused_entity: non_focusable,
window,
});
app.update();
assert_eq!(app.world().resource::<InputFocus>().get(), None);
}
#[test]
fn acquire_focus_focuses_tab_index_target_without_tab_navigation() {
let (mut app, window) = pointer_focus_app();
let focusable = app.world_mut().spawn((TabIndex(0), ChildOf(window))).id();
app.world_mut().trigger(AcquireFocus {
focused_entity: focusable,
window,
});
app.update();
assert_eq!(app.world().resource::<InputFocus>().get(), Some(focusable));
}
#[test]
fn acquire_focus_focuses_directly_targeted_tab_index_entity() {
let (mut app, window) = pointer_focus_app_with_tab_navigation();
let focusable = app.world_mut().spawn((TabIndex(0), ChildOf(window))).id();
app.world_mut().trigger(AcquireFocus {
focused_entity: focusable,
window,
});
app.update();
assert_eq!(app.world().resource::<InputFocus>().get(), Some(focusable));
}
#[test]
fn acquire_focus_on_child_focuses_tab_index_parent() {
let (mut app, window) = pointer_focus_app_with_tab_navigation();
let parent = app.world_mut().spawn((TabIndex(0), ChildOf(window))).id();
let child = app.world_mut().spawn(ChildOf(parent)).id();
app.world_mut().trigger(AcquireFocus {
focused_entity: child,
window,
});
app.update();
assert_eq!(app.world().resource::<InputFocus>().get(), Some(parent));
}
}