1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![forbid(unsafe_code)]
3#![doc(
4 html_logo_url = "https://bevy.org/assets/icon.png",
5 html_favicon_url = "https://bevy.org/assets/icon.png"
6)]
7#![no_std]
8
9#[cfg(feature = "std")]
21extern crate std;
22
23extern crate alloc;
24
25pub mod directional_navigation;
26pub mod navigator;
27pub mod tab_navigation;
28
29mod autofocus;
32pub use autofocus::*;
33
34#[cfg(any(feature = "keyboard", feature = "gamepad", feature = "mouse"))]
35use bevy_app::PreUpdate;
36use bevy_app::{App, Plugin, PostStartup};
37use bevy_ecs::{
38 entity::Entities, prelude::*, query::QueryData, system::SystemParam, traversal::Traversal,
39};
40#[cfg(feature = "gamepad")]
41use bevy_input::gamepad::GamepadButtonChangedEvent;
42#[cfg(feature = "keyboard")]
43use bevy_input::keyboard::KeyboardInput;
44#[cfg(feature = "mouse")]
45use bevy_input::mouse::MouseWheel;
46use bevy_window::{PrimaryWindow, Window};
47use core::fmt::Debug;
48
49#[cfg(feature = "bevy_reflect")]
50use bevy_reflect::{prelude::*, Reflect};
51
52#[derive(Clone, Debug, Default, Resource, PartialEq)]
90#[cfg_attr(
91 feature = "bevy_reflect",
92 derive(Reflect),
93 reflect(Debug, Default, Resource, Clone)
94)]
95pub struct InputFocus(pub Option<Entity>);
96
97impl InputFocus {
98 pub const fn from_entity(entity: Entity) -> Self {
102 Self(Some(entity))
103 }
104
105 pub const fn set(&mut self, entity: Entity) {
107 self.0 = Some(entity);
108 }
109
110 pub const fn get(&self) -> Option<Entity> {
112 self.0
113 }
114
115 pub const fn clear(&mut self) {
117 self.0 = None;
118 }
119}
120
121#[derive(Clone, Debug, Resource, Default)]
136#[cfg_attr(
137 feature = "bevy_reflect",
138 derive(Reflect),
139 reflect(Debug, Resource, Clone)
140)]
141pub struct InputFocusVisible(pub bool);
142
143#[derive(EntityEvent, Clone, Debug, Component)]
151#[entity_event(propagate = WindowTraversal, auto_propagate)]
152#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
153pub struct FocusedInput<M: Message + Clone> {
154 #[event_target]
156 pub focused_entity: Entity,
157 pub input: M,
159 window: Entity,
161}
162
163#[derive(Clone, EntityEvent)]
166#[entity_event(propagate = WindowTraversal, auto_propagate)]
167pub struct AcquireFocus {
168 #[event_target]
170 pub focused_entity: Entity,
171 window: Entity,
173}
174
175#[derive(QueryData)]
176pub struct WindowTraversal {
178 child_of: Option<&'static ChildOf>,
179 window: Option<&'static Window>,
180}
181
182impl<M: Message + Clone> Traversal<FocusedInput<M>> for WindowTraversal {
183 fn traverse(item: Self::Item<'_, '_>, event: &FocusedInput<M>) -> Option<Entity> {
184 let WindowTraversalItem { child_of, window } = item;
185
186 if let Some(child_of) = child_of {
188 return Some(child_of.parent());
189 };
190
191 if window.is_none() {
193 return Some(event.window);
194 }
195
196 None
197 }
198}
199
200impl Traversal<AcquireFocus> for WindowTraversal {
201 fn traverse(item: Self::Item<'_, '_>, event: &AcquireFocus) -> Option<Entity> {
202 let WindowTraversalItem { child_of, window } = item;
203
204 if let Some(child_of) = child_of {
206 return Some(child_of.parent());
207 };
208
209 if window.is_none() {
211 return Some(event.window);
212 }
213
214 None
215 }
216}
217
218pub struct InputDispatchPlugin;
223
224impl Plugin for InputDispatchPlugin {
225 fn build(&self, app: &mut App) {
226 app.add_systems(PostStartup, set_initial_focus)
227 .init_resource::<InputFocus>()
228 .init_resource::<InputFocusVisible>();
229
230 #[cfg(any(feature = "keyboard", feature = "gamepad", feature = "mouse"))]
231 app.add_systems(
232 PreUpdate,
233 (
234 #[cfg(feature = "keyboard")]
235 dispatch_focused_input::<KeyboardInput>,
236 #[cfg(feature = "gamepad")]
237 dispatch_focused_input::<GamepadButtonChangedEvent>,
238 #[cfg(feature = "mouse")]
239 dispatch_focused_input::<MouseWheel>,
240 )
241 .in_set(InputFocusSystems::Dispatch),
242 );
243 }
244}
245
246#[derive(SystemSet, Debug, PartialEq, Eq, Hash, Clone)]
250pub enum InputFocusSystems {
251 Dispatch,
253}
254
255pub fn set_initial_focus(
257 mut input_focus: ResMut<InputFocus>,
258 window: Single<Entity, With<PrimaryWindow>>,
259) {
260 if input_focus.0.is_none() {
261 input_focus.0 = Some(*window);
262 }
263}
264
265pub fn dispatch_focused_input<M: Message + Clone>(
271 mut input_reader: MessageReader<M>,
272 mut focus: ResMut<InputFocus>,
273 windows: Query<Entity, With<PrimaryWindow>>,
274 entities: &Entities,
275 mut commands: Commands,
276) {
277 if let Ok(window) = windows.single() {
278 if let Some(focused_entity) = focus.0 {
280 if entities.contains(focused_entity) {
282 for ev in input_reader.read() {
283 commands.trigger(FocusedInput {
284 focused_entity,
285 input: ev.clone(),
286 window,
287 });
288 }
289 } else {
290 focus.0 = None;
292 for ev in input_reader.read() {
293 commands.trigger(FocusedInput {
294 focused_entity: window,
295 input: ev.clone(),
296 window,
297 });
298 }
299 }
300 } else {
301 for ev in input_reader.read() {
304 commands.trigger(FocusedInput {
305 focused_entity: window,
306 input: ev.clone(),
307 window,
308 });
309 }
310 }
311 }
312}
313
314pub trait IsFocused {
325 fn is_focused(&self, entity: Entity) -> bool;
327
328 fn is_focus_within(&self, entity: Entity) -> bool;
332
333 fn is_focus_visible(&self, entity: Entity) -> bool;
335
336 fn is_focus_within_visible(&self, entity: Entity) -> bool;
339}
340
341#[derive(SystemParam)]
345pub struct IsFocusedHelper<'w, 's> {
346 parent_query: Query<'w, 's, &'static ChildOf>,
347 input_focus: Option<Res<'w, InputFocus>>,
348 input_focus_visible: Option<Res<'w, InputFocusVisible>>,
349}
350
351impl IsFocused for IsFocusedHelper<'_, '_> {
352 fn is_focused(&self, entity: Entity) -> bool {
353 self.input_focus
354 .as_deref()
355 .and_then(|f| f.0)
356 .is_some_and(|e| e == entity)
357 }
358
359 fn is_focus_within(&self, entity: Entity) -> bool {
360 let Some(focus) = self.input_focus.as_deref().and_then(|f| f.0) else {
361 return false;
362 };
363 if focus == entity {
364 return true;
365 }
366 self.parent_query.iter_ancestors(focus).any(|e| e == entity)
367 }
368
369 fn is_focus_visible(&self, entity: Entity) -> bool {
370 self.input_focus_visible.as_deref().is_some_and(|vis| vis.0) && self.is_focused(entity)
371 }
372
373 fn is_focus_within_visible(&self, entity: Entity) -> bool {
374 self.input_focus_visible.as_deref().is_some_and(|vis| vis.0) && self.is_focus_within(entity)
375 }
376}
377
378impl IsFocused for World {
379 fn is_focused(&self, entity: Entity) -> bool {
380 self.get_resource::<InputFocus>()
381 .and_then(|f| f.0)
382 .is_some_and(|f| f == entity)
383 }
384
385 fn is_focus_within(&self, entity: Entity) -> bool {
386 let Some(focus) = self.get_resource::<InputFocus>().and_then(|f| f.0) else {
387 return false;
388 };
389 let mut e = focus;
390 loop {
391 if e == entity {
392 return true;
393 }
394 if let Some(parent) = self.entity(e).get::<ChildOf>().map(ChildOf::parent) {
395 e = parent;
396 } else {
397 return false;
398 }
399 }
400 }
401
402 fn is_focus_visible(&self, entity: Entity) -> bool {
403 self.get_resource::<InputFocusVisible>()
404 .is_some_and(|vis| vis.0)
405 && self.is_focused(entity)
406 }
407
408 fn is_focus_within_visible(&self, entity: Entity) -> bool {
409 self.get_resource::<InputFocusVisible>()
410 .is_some_and(|vis| vis.0)
411 && self.is_focus_within(entity)
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418
419 use alloc::string::String;
420 use bevy_app::Startup;
421 use bevy_ecs::{observer::On, system::RunSystemOnce, world::DeferredWorld};
422 use bevy_input::{
423 keyboard::{Key, KeyCode},
424 ButtonState, InputPlugin,
425 };
426
427 #[derive(Component, Default)]
428 struct GatherKeyboardEvents(String);
429
430 fn gather_keyboard_events(
431 event: On<FocusedInput<KeyboardInput>>,
432 mut query: Query<&mut GatherKeyboardEvents>,
433 ) {
434 if let Ok(mut gather) = query.get_mut(event.focused_entity) {
435 if let Key::Character(c) = &event.input.logical_key {
436 gather.0.push_str(c.as_str());
437 }
438 }
439 }
440
441 fn key_a_message() -> KeyboardInput {
442 KeyboardInput {
443 key_code: KeyCode::KeyA,
444 logical_key: Key::Character("A".into()),
445 state: ButtonState::Pressed,
446 text: Some("A".into()),
447 repeat: false,
448 window: Entity::PLACEHOLDER,
449 }
450 }
451
452 #[test]
453 fn test_no_panics_if_resource_missing() {
454 let mut app = App::new();
455 let entity = app.world_mut().spawn_empty().id();
458
459 assert!(!app.world().is_focused(entity));
460
461 app.world_mut()
462 .run_system_once(move |helper: IsFocusedHelper| {
463 assert!(!helper.is_focused(entity));
464 assert!(!helper.is_focus_within(entity));
465 assert!(!helper.is_focus_visible(entity));
466 assert!(!helper.is_focus_within_visible(entity));
467 })
468 .unwrap();
469
470 app.world_mut()
471 .run_system_once(move |world: DeferredWorld| {
472 assert!(!world.is_focused(entity));
473 assert!(!world.is_focus_within(entity));
474 assert!(!world.is_focus_visible(entity));
475 assert!(!world.is_focus_within_visible(entity));
476 })
477 .unwrap();
478 }
479
480 #[test]
481 fn initial_focus_unset_if_no_primary_window() {
482 let mut app = App::new();
483 app.add_plugins((InputPlugin, InputDispatchPlugin));
484
485 app.update();
486
487 assert_eq!(app.world().resource::<InputFocus>().0, None);
488 }
489
490 #[test]
491 fn initial_focus_set_to_primary_window() {
492 let mut app = App::new();
493 app.add_plugins((InputPlugin, InputDispatchPlugin));
494
495 let entity_window = app
496 .world_mut()
497 .spawn((Window::default(), PrimaryWindow))
498 .id();
499 app.update();
500
501 assert_eq!(app.world().resource::<InputFocus>().0, Some(entity_window));
502 }
503
504 #[test]
505 fn initial_focus_not_overridden() {
506 let mut app = App::new();
507 app.add_plugins((InputPlugin, InputDispatchPlugin));
508
509 app.world_mut().spawn((Window::default(), PrimaryWindow));
510
511 app.add_systems(Startup, |mut commands: Commands| {
512 commands.spawn(AutoFocus);
513 });
514
515 app.update();
516
517 let autofocus_entity = app
518 .world_mut()
519 .query_filtered::<Entity, With<AutoFocus>>()
520 .single(app.world())
521 .unwrap();
522
523 assert_eq!(
524 app.world().resource::<InputFocus>().0,
525 Some(autofocus_entity)
526 );
527 }
528
529 #[test]
530 fn test_keyboard_events() {
531 fn get_gathered(app: &App, entity: Entity) -> &str {
532 app.world()
533 .entity(entity)
534 .get::<GatherKeyboardEvents>()
535 .unwrap()
536 .0
537 .as_str()
538 }
539
540 let mut app = App::new();
541
542 app.add_plugins((InputPlugin, InputDispatchPlugin))
543 .add_observer(gather_keyboard_events);
544
545 app.world_mut().spawn((Window::default(), PrimaryWindow));
546
547 app.update();
549
550 let entity_a = app
551 .world_mut()
552 .spawn((GatherKeyboardEvents::default(), AutoFocus))
553 .id();
554
555 let child_of_b = app
556 .world_mut()
557 .spawn((GatherKeyboardEvents::default(),))
558 .id();
559
560 let entity_b = app
561 .world_mut()
562 .spawn((GatherKeyboardEvents::default(),))
563 .add_child(child_of_b)
564 .id();
565
566 assert!(app.world().is_focused(entity_a));
567 assert!(!app.world().is_focused(entity_b));
568 assert!(!app.world().is_focused(child_of_b));
569 assert!(!app.world().is_focus_visible(entity_a));
570 assert!(!app.world().is_focus_visible(entity_b));
571 assert!(!app.world().is_focus_visible(child_of_b));
572
573 app.world_mut().write_message(key_a_message());
575 app.update();
576
577 assert_eq!(get_gathered(&app, entity_a), "A");
578 assert_eq!(get_gathered(&app, entity_b), "");
579 assert_eq!(get_gathered(&app, child_of_b), "");
580
581 app.world_mut().insert_resource(InputFocus(None));
582
583 assert!(!app.world().is_focused(entity_a));
584 assert!(!app.world().is_focus_visible(entity_a));
585
586 app.world_mut().write_message(key_a_message());
588 app.update();
589
590 assert_eq!(get_gathered(&app, entity_a), "A");
591 assert_eq!(get_gathered(&app, entity_b), "");
592 assert_eq!(get_gathered(&app, child_of_b), "");
593
594 app.world_mut()
595 .insert_resource(InputFocus::from_entity(entity_b));
596 assert!(app.world().is_focused(entity_b));
597 assert!(!app.world().is_focused(child_of_b));
598
599 app.world_mut()
600 .run_system_once(move |mut input_focus: ResMut<InputFocus>| {
601 input_focus.set(child_of_b);
602 })
603 .unwrap();
604 assert!(app.world().is_focus_within(entity_b));
605
606 app.world_mut()
608 .write_message_batch(core::iter::repeat_n(key_a_message(), 4));
609 app.update();
610
611 assert_eq!(get_gathered(&app, entity_a), "A");
612 assert_eq!(get_gathered(&app, entity_b), "AAAA");
613 assert_eq!(get_gathered(&app, child_of_b), "AAAA");
614
615 app.world_mut().resource_mut::<InputFocusVisible>().0 = true;
616
617 app.world_mut()
618 .run_system_once(move |helper: IsFocusedHelper| {
619 assert!(!helper.is_focused(entity_a));
620 assert!(!helper.is_focus_within(entity_a));
621 assert!(!helper.is_focus_visible(entity_a));
622 assert!(!helper.is_focus_within_visible(entity_a));
623
624 assert!(!helper.is_focused(entity_b));
625 assert!(helper.is_focus_within(entity_b));
626 assert!(!helper.is_focus_visible(entity_b));
627 assert!(helper.is_focus_within_visible(entity_b));
628
629 assert!(helper.is_focused(child_of_b));
630 assert!(helper.is_focus_within(child_of_b));
631 assert!(helper.is_focus_visible(child_of_b));
632 assert!(helper.is_focus_within_visible(child_of_b));
633 })
634 .unwrap();
635
636 app.world_mut()
637 .run_system_once(move |world: DeferredWorld| {
638 assert!(!world.is_focused(entity_a));
639 assert!(!world.is_focus_within(entity_a));
640 assert!(!world.is_focus_visible(entity_a));
641 assert!(!world.is_focus_within_visible(entity_a));
642
643 assert!(!world.is_focused(entity_b));
644 assert!(world.is_focus_within(entity_b));
645 assert!(!world.is_focus_visible(entity_b));
646 assert!(world.is_focus_within_visible(entity_b));
647
648 assert!(world.is_focused(child_of_b));
649 assert!(world.is_focus_within(child_of_b));
650 assert!(world.is_focus_visible(child_of_b));
651 assert!(world.is_focus_within_visible(child_of_b));
652 })
653 .unwrap();
654 }
655
656 #[test]
657 fn dispatch_clears_focus_when_focused_entity_despawned() {
658 let mut app = App::new();
659 app.add_plugins((InputPlugin, InputDispatchPlugin));
660
661 app.world_mut().spawn((Window::default(), PrimaryWindow));
662 app.update();
663
664 let entity = app.world_mut().spawn_empty().id();
665 app.world_mut()
666 .insert_resource(InputFocus::from_entity(entity));
667 app.world_mut().entity_mut(entity).despawn();
668
669 assert_eq!(app.world().resource::<InputFocus>().0, Some(entity));
670
671 app.world_mut().write_message(key_a_message());
673 app.update();
674
675 assert_eq!(app.world().resource::<InputFocus>().0, None);
676 }
677}