Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_input_focus/src/lib.rs
9353 views
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")]
21
extern crate std;
22
23
extern crate alloc;
24
25
pub mod directional_navigation;
26
pub mod navigator;
27
pub mod tab_navigation;
28
29
// This module is too small / specific to be exported by the crate,
30
// but it's nice to have it separate for code organization.
31
mod autofocus;
32
pub use autofocus::*;
33
34
#[cfg(any(feature = "keyboard", feature = "gamepad", feature = "mouse"))]
35
use bevy_app::PreUpdate;
36
use bevy_app::{App, Plugin, PostStartup};
37
use bevy_ecs::{
38
entity::Entities, prelude::*, query::QueryData, system::SystemParam, traversal::Traversal,
39
};
40
#[cfg(feature = "gamepad")]
41
use bevy_input::gamepad::GamepadButtonChangedEvent;
42
#[cfg(feature = "keyboard")]
43
use bevy_input::keyboard::KeyboardInput;
44
#[cfg(feature = "mouse")]
45
use bevy_input::mouse::MouseWheel;
46
use bevy_window::{PrimaryWindow, Window};
47
use core::fmt::Debug;
48
49
#[cfg(feature = "bevy_reflect")]
50
use bevy_reflect::{prelude::*, Reflect};
51
52
/// Resource representing which entity has input focus, if any. Input events (other than pointer-like inputs) will be
53
/// dispatched to the current focus entity, or to the primary window if no entity has focus.
54
///
55
/// Changing the input focus is as easy as modifying this resource.
56
///
57
/// # Examples
58
///
59
/// From within a system:
60
///
61
/// ```rust
62
/// use bevy_ecs::prelude::*;
63
/// use bevy_input_focus::InputFocus;
64
///
65
/// fn clear_focus(mut input_focus: ResMut<InputFocus>) {
66
/// input_focus.clear();
67
/// }
68
/// ```
69
///
70
/// With exclusive (or deferred) world access:
71
///
72
/// ```rust
73
/// use bevy_ecs::prelude::*;
74
/// use bevy_input_focus::InputFocus;
75
///
76
/// fn set_focus_from_world(world: &mut World) {
77
/// let entity = world.spawn_empty().id();
78
///
79
/// // Fetch the resource from the world
80
/// let mut input_focus = world.resource_mut::<InputFocus>();
81
/// // Then mutate it!
82
/// input_focus.set(entity);
83
///
84
/// // Or you can just insert a fresh copy of the resource
85
/// // which will overwrite the existing one.
86
/// world.insert_resource(InputFocus::from_entity(entity));
87
/// }
88
/// ```
89
#[derive(Clone, Debug, Default, Resource, PartialEq)]
90
#[cfg_attr(
91
feature = "bevy_reflect",
92
derive(Reflect),
93
reflect(Debug, Default, Resource, Clone)
94
)]
95
pub struct InputFocus(pub Option<Entity>);
96
97
impl InputFocus {
98
/// Create a new [`InputFocus`] resource with the given entity.
99
///
100
/// This is mostly useful for tests.
101
pub const fn from_entity(entity: Entity) -> Self {
102
Self(Some(entity))
103
}
104
105
/// Set the entity with input focus.
106
pub const fn set(&mut self, entity: Entity) {
107
self.0 = Some(entity);
108
}
109
110
/// Returns the entity with input focus, if any.
111
pub const fn get(&self) -> Option<Entity> {
112
self.0
113
}
114
115
/// Clears input focus.
116
pub const fn clear(&mut self) {
117
self.0 = None;
118
}
119
}
120
121
/// Resource representing whether the input focus indicator should be visible on UI elements.
122
///
123
/// Note that this resource is not used by [`bevy_input_focus`](crate) itself, but is provided for
124
/// convenience to UI widgets or frameworks that want to display a focus indicator.
125
/// [`InputFocus`] may still be `Some` even if the focus indicator is not visible.
126
///
127
/// The value of this resource should be set by your focus navigation solution.
128
/// For a desktop/web style of user interface this would be set to true when the user presses the tab key,
129
/// and set to false when the user clicks on a different element.
130
/// By contrast, a console-style UI intended to be navigated with a gamepad may always have the focus indicator visible.
131
///
132
/// To easily access information about whether focus indicators should be shown for a given entity, use the [`IsFocused`] trait.
133
///
134
/// By default, this resource is set to `false`.
135
#[derive(Clone, Debug, Resource, Default)]
136
#[cfg_attr(
137
feature = "bevy_reflect",
138
derive(Reflect),
139
reflect(Debug, Resource, Clone)
140
)]
141
pub struct InputFocusVisible(pub bool);
142
143
/// A bubble-able user input event that starts at the currently focused entity.
144
///
145
/// This event is normally dispatched to the current input focus entity, if any.
146
/// If no entity has input focus, then the event is dispatched to the main window.
147
///
148
/// To set up your own bubbling input event, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
149
/// in the [`InputFocusSystems::Dispatch`] system set during [`PreUpdate`].
150
#[derive(EntityEvent, Clone, Debug, Component)]
151
#[entity_event(propagate = WindowTraversal, auto_propagate)]
152
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
153
pub struct FocusedInput<M: Message + Clone> {
154
/// The entity that has received focused input.
155
#[event_target]
156
pub focused_entity: Entity,
157
/// The underlying input message.
158
pub input: M,
159
/// The primary window entity.
160
window: Entity,
161
}
162
163
/// An event which is used to set input focus. Trigger this on an entity, and it will bubble
164
/// until it finds a focusable entity, and then set focus to it.
165
#[derive(Clone, EntityEvent)]
166
#[entity_event(propagate = WindowTraversal, auto_propagate)]
167
pub struct AcquireFocus {
168
/// The entity that has acquired focus.
169
#[event_target]
170
pub focused_entity: Entity,
171
/// The primary window entity.
172
window: Entity,
173
}
174
175
#[derive(QueryData)]
176
/// These are for accessing components defined on the targeted entity
177
pub struct WindowTraversal {
178
child_of: Option<&'static ChildOf>,
179
window: Option<&'static Window>,
180
}
181
182
impl<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
// Send event to parent, if it has one.
187
if let Some(child_of) = child_of {
188
return Some(child_of.parent());
189
};
190
191
// Otherwise, send it to the window entity (unless this is a window entity).
192
if window.is_none() {
193
return Some(event.window);
194
}
195
196
None
197
}
198
}
199
200
impl Traversal<AcquireFocus> for WindowTraversal {
201
fn traverse(item: Self::Item<'_, '_>, event: &AcquireFocus) -> Option<Entity> {
202
let WindowTraversalItem { child_of, window } = item;
203
204
// Send event to parent, if it has one.
205
if let Some(child_of) = child_of {
206
return Some(child_of.parent());
207
};
208
209
// Otherwise, send it to the window entity (unless this is a window entity).
210
if window.is_none() {
211
return Some(event.window);
212
}
213
214
None
215
}
216
}
217
218
/// Plugin which sets up systems for dispatching bubbling keyboard and gamepad button events to the focused entity.
219
///
220
/// To add bubbling to your own input events, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
221
/// as described in the docs for [`FocusedInput`].
222
pub struct InputDispatchPlugin;
223
224
impl 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
/// System sets for [`bevy_input_focus`](crate).
247
///
248
/// These systems run in the [`PreUpdate`] schedule.
249
#[derive(SystemSet, Debug, PartialEq, Eq, Hash, Clone)]
250
pub enum InputFocusSystems {
251
/// System which dispatches bubbled input events to the focused entity, or to the primary window.
252
Dispatch,
253
}
254
255
/// If no entity is focused, sets the focus to the primary window, if any.
256
pub 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
265
/// System which dispatches bubbled input events to the focused entity, or to the primary window
266
/// if no entity has focus.
267
///
268
/// If the currently focused entity no longer exists (has been despawned), this system will
269
/// automatically clear the focus and dispatch events to the primary window instead.
270
pub 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 an element has keyboard focus, then dispatch the input event to that element.
279
if let Some(focused_entity) = focus.0 {
280
// Check if the focused entity is still alive
281
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
// If the focused entity no longer exists, clear focus and dispatch to window
291
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
// If no element has input focus, then dispatch the input event to the primary window.
302
// There should be only one primary window.
303
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
314
/// Trait which defines methods to check if an entity currently has focus.
315
///
316
/// This is implemented for [`World`] and [`IsFocusedHelper`].
317
/// [`DeferredWorld`](bevy_ecs::world::DeferredWorld) indirectly implements it through [`Deref`].
318
///
319
/// For use within systems, use [`IsFocusedHelper`].
320
///
321
/// Modify the [`InputFocus`] resource to change the focused entity.
322
///
323
/// [`Deref`]: std::ops::Deref
324
pub trait IsFocused {
325
/// Returns true if the given entity has input focus.
326
fn is_focused(&self, entity: Entity) -> bool;
327
328
/// Returns true if the given entity or any of its descendants has input focus.
329
///
330
/// Note that for unusual layouts, the focus may not be within the entity's visual bounds.
331
fn is_focus_within(&self, entity: Entity) -> bool;
332
333
/// Returns true if the given entity has input focus and the focus indicator should be visible.
334
fn is_focus_visible(&self, entity: Entity) -> bool;
335
336
/// Returns true if the given entity, or any descendant, has input focus and the focus
337
/// indicator should be visible.
338
fn is_focus_within_visible(&self, entity: Entity) -> bool;
339
}
340
341
/// A system param that helps get information about the current focused entity.
342
///
343
/// When working with the entire [`World`], consider using the [`IsFocused`] instead.
344
#[derive(SystemParam)]
345
pub 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
351
impl 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
378
impl 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)]
416
mod 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
// Note that we do not insert InputFocus here!
456
457
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
// Run the world for a single frame to set up the initial focus
548
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
// entity_a should receive this event
574
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
// This event should be lost
587
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
// These events should be received by entity_b and child_of_b
607
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
// Send input event - this should clear focus instead of panicking
672
app.world_mut().write_message(key_a_message());
673
app.update();
674
675
assert_eq!(app.world().resource::<InputFocus>().0, None);
676
}
677
}
678
679