bevy_input_focus/
lib.rs

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//! A UI-centric focus system for Bevy.
10//!
11//! This crate provides a system for managing input focus in Bevy applications, including:
12//! * [`InputFocus`], a resource for tracking which entity has input focus.
13//! * Methods for getting and setting input focus via [`InputFocus`] and [`IsFocusedHelper`].
14//! * A generic [`FocusedInput`] event for input events which bubble up from the focused entity.
15//! * Various navigation frameworks for moving input focus between entities based on user input, such as [`tab_navigation`] and [`directional_navigation`].
16//!
17//! This crate does *not* provide any integration with UI widgets: this is the responsibility of the widget crate,
18//! which should depend on [`bevy_input_focus`](crate).
19
20#[cfg(feature = "std")]
21extern crate std;
22
23extern crate alloc;
24
25pub mod directional_navigation;
26pub mod tab_navigation;
27
28// This module is too small / specific to be exported by the crate,
29// but it's nice to have it separate for code organization.
30mod autofocus;
31pub use autofocus::*;
32
33#[cfg(any(feature = "keyboard", feature = "gamepad", feature = "mouse"))]
34use bevy_app::PreUpdate;
35use bevy_app::{App, Plugin, PostStartup};
36use bevy_ecs::{
37    entity::Entities, prelude::*, query::QueryData, system::SystemParam, traversal::Traversal,
38};
39#[cfg(feature = "gamepad")]
40use bevy_input::gamepad::GamepadButtonChangedEvent;
41#[cfg(feature = "keyboard")]
42use bevy_input::keyboard::KeyboardInput;
43#[cfg(feature = "mouse")]
44use bevy_input::mouse::MouseWheel;
45use bevy_window::{PrimaryWindow, Window};
46use core::fmt::Debug;
47
48#[cfg(feature = "bevy_reflect")]
49use bevy_reflect::{prelude::*, Reflect};
50
51/// Resource representing which entity has input focus, if any. Input events (other than pointer-like inputs) will be
52/// dispatched to the current focus entity, or to the primary window if no entity has focus.
53///
54/// Changing the input focus is as easy as modifying this resource.
55///
56/// # Examples
57///
58/// From within a system:
59///
60/// ```rust
61/// use bevy_ecs::prelude::*;
62/// use bevy_input_focus::InputFocus;
63///
64/// fn clear_focus(mut input_focus: ResMut<InputFocus>) {
65///   input_focus.clear();
66/// }
67/// ```
68///
69/// With exclusive (or deferred) world access:
70///
71/// ```rust
72/// use bevy_ecs::prelude::*;
73/// use bevy_input_focus::InputFocus;
74///
75/// fn set_focus_from_world(world: &mut World) {
76///     let entity = world.spawn_empty().id();
77///
78///     // Fetch the resource from the world
79///     let mut input_focus = world.resource_mut::<InputFocus>();
80///     // Then mutate it!
81///     input_focus.set(entity);
82///
83///     // Or you can just insert a fresh copy of the resource
84///     // which will overwrite the existing one.
85///     world.insert_resource(InputFocus::from_entity(entity));
86/// }
87/// ```
88#[derive(Clone, Debug, Default, Resource)]
89#[cfg_attr(
90    feature = "bevy_reflect",
91    derive(Reflect),
92    reflect(Debug, Default, Resource, Clone)
93)]
94pub struct InputFocus(pub Option<Entity>);
95
96impl InputFocus {
97    /// Create a new [`InputFocus`] resource with the given entity.
98    ///
99    /// This is mostly useful for tests.
100    pub const fn from_entity(entity: Entity) -> Self {
101        Self(Some(entity))
102    }
103
104    /// Set the entity with input focus.
105    pub const fn set(&mut self, entity: Entity) {
106        self.0 = Some(entity);
107    }
108
109    /// Returns the entity with input focus, if any.
110    pub const fn get(&self) -> Option<Entity> {
111        self.0
112    }
113
114    /// Clears input focus.
115    pub const fn clear(&mut self) {
116        self.0 = None;
117    }
118}
119
120/// Resource representing whether the input focus indicator should be visible on UI elements.
121///
122/// Note that this resource is not used by [`bevy_input_focus`](crate) itself, but is provided for
123/// convenience to UI widgets or frameworks that want to display a focus indicator.
124/// [`InputFocus`] may still be `Some` even if the focus indicator is not visible.
125///
126/// The value of this resource should be set by your focus navigation solution.
127/// For a desktop/web style of user interface this would be set to true when the user presses the tab key,
128/// and set to false when the user clicks on a different element.
129/// By contrast, a console-style UI intended to be navigated with a gamepad may always have the focus indicator visible.
130///
131/// To easily access information about whether focus indicators should be shown for a given entity, use the [`IsFocused`] trait.
132///
133/// By default, this resource is set to `false`.
134#[derive(Clone, Debug, Resource, Default)]
135#[cfg_attr(
136    feature = "bevy_reflect",
137    derive(Reflect),
138    reflect(Debug, Resource, Clone)
139)]
140pub struct InputFocusVisible(pub bool);
141
142/// A bubble-able user input event that starts at the currently focused entity.
143///
144/// This event is normally dispatched to the current input focus entity, if any.
145/// If no entity has input focus, then the event is dispatched to the main window.
146///
147/// To set up your own bubbling input event, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
148/// in the [`InputFocusSystems::Dispatch`] system set during [`PreUpdate`].
149#[derive(EntityEvent, Clone, Debug, Component)]
150#[entity_event(propagate = WindowTraversal, auto_propagate)]
151#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
152pub struct FocusedInput<M: Message + Clone> {
153    /// The entity that has received focused input.
154    #[event_target]
155    pub focused_entity: Entity,
156    /// The underlying input message.
157    pub input: M,
158    /// The primary window entity.
159    window: Entity,
160}
161
162/// An event which is used to set input focus. Trigger this on an entity, and it will bubble
163/// until it finds a focusable entity, and then set focus to it.
164#[derive(Clone, EntityEvent)]
165#[entity_event(propagate = WindowTraversal, auto_propagate)]
166pub struct AcquireFocus {
167    /// The entity that has acquired focus.
168    #[event_target]
169    pub focused_entity: Entity,
170    /// The primary window entity.
171    window: Entity,
172}
173
174#[derive(QueryData)]
175/// These are for accessing components defined on the targeted entity
176pub struct WindowTraversal {
177    child_of: Option<&'static ChildOf>,
178    window: Option<&'static Window>,
179}
180
181impl<M: Message + Clone> Traversal<FocusedInput<M>> for WindowTraversal {
182    fn traverse(item: Self::Item<'_, '_>, event: &FocusedInput<M>) -> Option<Entity> {
183        let WindowTraversalItem { child_of, window } = item;
184
185        // Send event to parent, if it has one.
186        if let Some(child_of) = child_of {
187            return Some(child_of.parent());
188        };
189
190        // Otherwise, send it to the window entity (unless this is a window entity).
191        if window.is_none() {
192            return Some(event.window);
193        }
194
195        None
196    }
197}
198
199impl Traversal<AcquireFocus> for WindowTraversal {
200    fn traverse(item: Self::Item<'_, '_>, event: &AcquireFocus) -> Option<Entity> {
201        let WindowTraversalItem { child_of, window } = item;
202
203        // Send event to parent, if it has one.
204        if let Some(child_of) = child_of {
205            return Some(child_of.parent());
206        };
207
208        // Otherwise, send it to the window entity (unless this is a window entity).
209        if window.is_none() {
210            return Some(event.window);
211        }
212
213        None
214    }
215}
216
217/// Plugin which sets up systems for dispatching bubbling keyboard and gamepad button events to the focused entity.
218///
219/// To add bubbling to your own input events, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
220/// as described in the docs for [`FocusedInput`].
221pub struct InputDispatchPlugin;
222
223impl Plugin for InputDispatchPlugin {
224    fn build(&self, app: &mut App) {
225        app.add_systems(PostStartup, set_initial_focus)
226            .init_resource::<InputFocus>()
227            .init_resource::<InputFocusVisible>();
228
229        #[cfg(any(feature = "keyboard", feature = "gamepad", feature = "mouse"))]
230        app.add_systems(
231            PreUpdate,
232            (
233                #[cfg(feature = "keyboard")]
234                dispatch_focused_input::<KeyboardInput>,
235                #[cfg(feature = "gamepad")]
236                dispatch_focused_input::<GamepadButtonChangedEvent>,
237                #[cfg(feature = "mouse")]
238                dispatch_focused_input::<MouseWheel>,
239            )
240                .in_set(InputFocusSystems::Dispatch),
241        );
242    }
243}
244
245/// System sets for [`bevy_input_focus`](crate).
246///
247/// These systems run in the [`PreUpdate`] schedule.
248#[derive(SystemSet, Debug, PartialEq, Eq, Hash, Clone)]
249pub enum InputFocusSystems {
250    /// System which dispatches bubbled input events to the focused entity, or to the primary window.
251    Dispatch,
252}
253
254/// If no entity is focused, sets the focus to the primary window, if any.
255pub fn set_initial_focus(
256    mut input_focus: ResMut<InputFocus>,
257    window: Single<Entity, With<PrimaryWindow>>,
258) {
259    if input_focus.0.is_none() {
260        input_focus.0 = Some(*window);
261    }
262}
263
264/// System which dispatches bubbled input events to the focused entity, or to the primary window
265/// if no entity has focus.
266///
267/// If the currently focused entity no longer exists (has been despawned), this system will
268/// automatically clear the focus and dispatch events to the primary window instead.
269pub fn dispatch_focused_input<M: Message + Clone>(
270    mut input_reader: MessageReader<M>,
271    mut focus: ResMut<InputFocus>,
272    windows: Query<Entity, With<PrimaryWindow>>,
273    entities: &Entities,
274    mut commands: Commands,
275) {
276    if let Ok(window) = windows.single() {
277        // If an element has keyboard focus, then dispatch the input event to that element.
278        if let Some(focused_entity) = focus.0 {
279            // Check if the focused entity is still alive
280            if entities.contains(focused_entity) {
281                for ev in input_reader.read() {
282                    commands.trigger(FocusedInput {
283                        focused_entity,
284                        input: ev.clone(),
285                        window,
286                    });
287                }
288            } else {
289                // If the focused entity no longer exists, clear focus and dispatch to window
290                focus.0 = None;
291                for ev in input_reader.read() {
292                    commands.trigger(FocusedInput {
293                        focused_entity: window,
294                        input: ev.clone(),
295                        window,
296                    });
297                }
298            }
299        } else {
300            // If no element has input focus, then dispatch the input event to the primary window.
301            // There should be only one primary window.
302            for ev in input_reader.read() {
303                commands.trigger(FocusedInput {
304                    focused_entity: window,
305                    input: ev.clone(),
306                    window,
307                });
308            }
309        }
310    }
311}
312
313/// Trait which defines methods to check if an entity currently has focus.
314///
315/// This is implemented for [`World`] and [`IsFocusedHelper`].
316/// [`DeferredWorld`](bevy_ecs::world::DeferredWorld) indirectly implements it through [`Deref`].
317///
318/// For use within systems, use [`IsFocusedHelper`].
319///
320/// Modify the [`InputFocus`] resource to change the focused entity.
321///
322/// [`Deref`]: std::ops::Deref
323pub trait IsFocused {
324    /// Returns true if the given entity has input focus.
325    fn is_focused(&self, entity: Entity) -> bool;
326
327    /// Returns true if the given entity or any of its descendants has input focus.
328    ///
329    /// Note that for unusual layouts, the focus may not be within the entity's visual bounds.
330    fn is_focus_within(&self, entity: Entity) -> bool;
331
332    /// Returns true if the given entity has input focus and the focus indicator should be visible.
333    fn is_focus_visible(&self, entity: Entity) -> bool;
334
335    /// Returns true if the given entity, or any descendant, has input focus and the focus
336    /// indicator should be visible.
337    fn is_focus_within_visible(&self, entity: Entity) -> bool;
338}
339
340/// A system param that helps get information about the current focused entity.
341///
342/// When working with the entire [`World`], consider using the [`IsFocused`] instead.
343#[derive(SystemParam)]
344pub struct IsFocusedHelper<'w, 's> {
345    parent_query: Query<'w, 's, &'static ChildOf>,
346    input_focus: Option<Res<'w, InputFocus>>,
347    input_focus_visible: Option<Res<'w, InputFocusVisible>>,
348}
349
350impl IsFocused for IsFocusedHelper<'_, '_> {
351    fn is_focused(&self, entity: Entity) -> bool {
352        self.input_focus
353            .as_deref()
354            .and_then(|f| f.0)
355            .is_some_and(|e| e == entity)
356    }
357
358    fn is_focus_within(&self, entity: Entity) -> bool {
359        let Some(focus) = self.input_focus.as_deref().and_then(|f| f.0) else {
360            return false;
361        };
362        if focus == entity {
363            return true;
364        }
365        self.parent_query.iter_ancestors(focus).any(|e| e == entity)
366    }
367
368    fn is_focus_visible(&self, entity: Entity) -> bool {
369        self.input_focus_visible.as_deref().is_some_and(|vis| vis.0) && self.is_focused(entity)
370    }
371
372    fn is_focus_within_visible(&self, entity: Entity) -> bool {
373        self.input_focus_visible.as_deref().is_some_and(|vis| vis.0) && self.is_focus_within(entity)
374    }
375}
376
377impl IsFocused for World {
378    fn is_focused(&self, entity: Entity) -> bool {
379        self.get_resource::<InputFocus>()
380            .and_then(|f| f.0)
381            .is_some_and(|f| f == entity)
382    }
383
384    fn is_focus_within(&self, entity: Entity) -> bool {
385        let Some(focus) = self.get_resource::<InputFocus>().and_then(|f| f.0) else {
386            return false;
387        };
388        let mut e = focus;
389        loop {
390            if e == entity {
391                return true;
392            }
393            if let Some(parent) = self.entity(e).get::<ChildOf>().map(ChildOf::parent) {
394                e = parent;
395            } else {
396                return false;
397            }
398        }
399    }
400
401    fn is_focus_visible(&self, entity: Entity) -> bool {
402        self.get_resource::<InputFocusVisible>()
403            .is_some_and(|vis| vis.0)
404            && self.is_focused(entity)
405    }
406
407    fn is_focus_within_visible(&self, entity: Entity) -> bool {
408        self.get_resource::<InputFocusVisible>()
409            .is_some_and(|vis| vis.0)
410            && self.is_focus_within(entity)
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    use alloc::string::String;
419    use bevy_app::Startup;
420    use bevy_ecs::{observer::On, system::RunSystemOnce, world::DeferredWorld};
421    use bevy_input::{
422        keyboard::{Key, KeyCode},
423        ButtonState, InputPlugin,
424    };
425
426    #[derive(Component, Default)]
427    struct GatherKeyboardEvents(String);
428
429    fn gather_keyboard_events(
430        event: On<FocusedInput<KeyboardInput>>,
431        mut query: Query<&mut GatherKeyboardEvents>,
432    ) {
433        if let Ok(mut gather) = query.get_mut(event.focused_entity) {
434            if let Key::Character(c) = &event.input.logical_key {
435                gather.0.push_str(c.as_str());
436            }
437        }
438    }
439
440    fn key_a_message() -> KeyboardInput {
441        KeyboardInput {
442            key_code: KeyCode::KeyA,
443            logical_key: Key::Character("A".into()),
444            state: ButtonState::Pressed,
445            text: Some("A".into()),
446            repeat: false,
447            window: Entity::PLACEHOLDER,
448        }
449    }
450
451    #[test]
452    fn test_no_panics_if_resource_missing() {
453        let mut app = App::new();
454        // Note that we do not insert InputFocus here!
455
456        let entity = app.world_mut().spawn_empty().id();
457
458        assert!(!app.world().is_focused(entity));
459
460        app.world_mut()
461            .run_system_once(move |helper: IsFocusedHelper| {
462                assert!(!helper.is_focused(entity));
463                assert!(!helper.is_focus_within(entity));
464                assert!(!helper.is_focus_visible(entity));
465                assert!(!helper.is_focus_within_visible(entity));
466            })
467            .unwrap();
468
469        app.world_mut()
470            .run_system_once(move |world: DeferredWorld| {
471                assert!(!world.is_focused(entity));
472                assert!(!world.is_focus_within(entity));
473                assert!(!world.is_focus_visible(entity));
474                assert!(!world.is_focus_within_visible(entity));
475            })
476            .unwrap();
477    }
478
479    #[test]
480    fn initial_focus_unset_if_no_primary_window() {
481        let mut app = App::new();
482        app.add_plugins((InputPlugin, InputDispatchPlugin));
483
484        app.update();
485
486        assert_eq!(app.world().resource::<InputFocus>().0, None);
487    }
488
489    #[test]
490    fn initial_focus_set_to_primary_window() {
491        let mut app = App::new();
492        app.add_plugins((InputPlugin, InputDispatchPlugin));
493
494        let entity_window = app
495            .world_mut()
496            .spawn((Window::default(), PrimaryWindow))
497            .id();
498        app.update();
499
500        assert_eq!(app.world().resource::<InputFocus>().0, Some(entity_window));
501    }
502
503    #[test]
504    fn initial_focus_not_overridden() {
505        let mut app = App::new();
506        app.add_plugins((InputPlugin, InputDispatchPlugin));
507
508        app.world_mut().spawn((Window::default(), PrimaryWindow));
509
510        app.add_systems(Startup, |mut commands: Commands| {
511            commands.spawn(AutoFocus);
512        });
513
514        app.update();
515
516        let autofocus_entity = app
517            .world_mut()
518            .query_filtered::<Entity, With<AutoFocus>>()
519            .single(app.world())
520            .unwrap();
521
522        assert_eq!(
523            app.world().resource::<InputFocus>().0,
524            Some(autofocus_entity)
525        );
526    }
527
528    #[test]
529    fn test_keyboard_events() {
530        fn get_gathered(app: &App, entity: Entity) -> &str {
531            app.world()
532                .entity(entity)
533                .get::<GatherKeyboardEvents>()
534                .unwrap()
535                .0
536                .as_str()
537        }
538
539        let mut app = App::new();
540
541        app.add_plugins((InputPlugin, InputDispatchPlugin))
542            .add_observer(gather_keyboard_events);
543
544        app.world_mut().spawn((Window::default(), PrimaryWindow));
545
546        // Run the world for a single frame to set up the initial focus
547        app.update();
548
549        let entity_a = app
550            .world_mut()
551            .spawn((GatherKeyboardEvents::default(), AutoFocus))
552            .id();
553
554        let child_of_b = app
555            .world_mut()
556            .spawn((GatherKeyboardEvents::default(),))
557            .id();
558
559        let entity_b = app
560            .world_mut()
561            .spawn((GatherKeyboardEvents::default(),))
562            .add_child(child_of_b)
563            .id();
564
565        assert!(app.world().is_focused(entity_a));
566        assert!(!app.world().is_focused(entity_b));
567        assert!(!app.world().is_focused(child_of_b));
568        assert!(!app.world().is_focus_visible(entity_a));
569        assert!(!app.world().is_focus_visible(entity_b));
570        assert!(!app.world().is_focus_visible(child_of_b));
571
572        // entity_a should receive this event
573        app.world_mut().write_message(key_a_message());
574        app.update();
575
576        assert_eq!(get_gathered(&app, entity_a), "A");
577        assert_eq!(get_gathered(&app, entity_b), "");
578        assert_eq!(get_gathered(&app, child_of_b), "");
579
580        app.world_mut().insert_resource(InputFocus(None));
581
582        assert!(!app.world().is_focused(entity_a));
583        assert!(!app.world().is_focus_visible(entity_a));
584
585        // This event should be lost
586        app.world_mut().write_message(key_a_message());
587        app.update();
588
589        assert_eq!(get_gathered(&app, entity_a), "A");
590        assert_eq!(get_gathered(&app, entity_b), "");
591        assert_eq!(get_gathered(&app, child_of_b), "");
592
593        app.world_mut()
594            .insert_resource(InputFocus::from_entity(entity_b));
595        assert!(app.world().is_focused(entity_b));
596        assert!(!app.world().is_focused(child_of_b));
597
598        app.world_mut()
599            .run_system_once(move |mut input_focus: ResMut<InputFocus>| {
600                input_focus.set(child_of_b);
601            })
602            .unwrap();
603        assert!(app.world().is_focus_within(entity_b));
604
605        // These events should be received by entity_b and child_of_b
606        app.world_mut()
607            .write_message_batch(core::iter::repeat_n(key_a_message(), 4));
608        app.update();
609
610        assert_eq!(get_gathered(&app, entity_a), "A");
611        assert_eq!(get_gathered(&app, entity_b), "AAAA");
612        assert_eq!(get_gathered(&app, child_of_b), "AAAA");
613
614        app.world_mut().resource_mut::<InputFocusVisible>().0 = true;
615
616        app.world_mut()
617            .run_system_once(move |helper: IsFocusedHelper| {
618                assert!(!helper.is_focused(entity_a));
619                assert!(!helper.is_focus_within(entity_a));
620                assert!(!helper.is_focus_visible(entity_a));
621                assert!(!helper.is_focus_within_visible(entity_a));
622
623                assert!(!helper.is_focused(entity_b));
624                assert!(helper.is_focus_within(entity_b));
625                assert!(!helper.is_focus_visible(entity_b));
626                assert!(helper.is_focus_within_visible(entity_b));
627
628                assert!(helper.is_focused(child_of_b));
629                assert!(helper.is_focus_within(child_of_b));
630                assert!(helper.is_focus_visible(child_of_b));
631                assert!(helper.is_focus_within_visible(child_of_b));
632            })
633            .unwrap();
634
635        app.world_mut()
636            .run_system_once(move |world: DeferredWorld| {
637                assert!(!world.is_focused(entity_a));
638                assert!(!world.is_focus_within(entity_a));
639                assert!(!world.is_focus_visible(entity_a));
640                assert!(!world.is_focus_within_visible(entity_a));
641
642                assert!(!world.is_focused(entity_b));
643                assert!(world.is_focus_within(entity_b));
644                assert!(!world.is_focus_visible(entity_b));
645                assert!(world.is_focus_within_visible(entity_b));
646
647                assert!(world.is_focused(child_of_b));
648                assert!(world.is_focus_within(child_of_b));
649                assert!(world.is_focus_visible(child_of_b));
650                assert!(world.is_focus_within_visible(child_of_b));
651            })
652            .unwrap();
653    }
654
655    #[test]
656    fn dispatch_clears_focus_when_focused_entity_despawned() {
657        let mut app = App::new();
658        app.add_plugins((InputPlugin, InputDispatchPlugin));
659
660        app.world_mut().spawn((Window::default(), PrimaryWindow));
661        app.update();
662
663        let entity = app.world_mut().spawn_empty().id();
664        app.world_mut()
665            .insert_resource(InputFocus::from_entity(entity));
666        app.world_mut().entity_mut(entity).despawn();
667
668        assert_eq!(app.world().resource::<InputFocus>().0, Some(entity));
669
670        // Send input event - this should clear focus instead of panicking
671        app.world_mut().write_message(key_a_message());
672        app.update();
673
674        assert_eq!(app.world().resource::<InputFocus>().0, None);
675    }
676}