Skip to main content

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