Skip to content

Commit b81adf1

Browse files
Rework action keybinding (#243)
* Rework keybindings * Reorder keybinding constructor parameters
1 parent 124c1b7 commit b81adf1

File tree

4 files changed

+152
-65
lines changed

4 files changed

+152
-65
lines changed

crates/bevy_editor/src/load_gltf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl Plugin for LoadGltfPlugin {
1414
app.init_resource::<GltfFilepickerTask>()
1515
.add_systems(Update, (poll_pick_gltf, file_dropped))
1616
.register_action("load-gltf", "Load GLTF", pick_gltf_action)
17-
.register_action_binding("load-gltf", [KeyCode::ControlLeft, KeyCode::KeyL]);
17+
.register_keybinding(Keybinding::new("load-gltf", KeyCode::KeyL).ctrl());
1818
}
1919
}
2020

crates/bevy_editor_core/src/actions.rs

Lines changed: 2 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,16 @@
22
33
use bevy::{ecs::system::SystemId, prelude::*};
44

5-
/// Editor selection plugin.
5+
/// Editor actions plugin.
66
#[derive(Default)]
77
pub struct ActionsPlugin;
88

99
impl Plugin for ActionsPlugin {
1010
fn build(&self, app: &mut App) {
11-
app.init_resource::<ActionRegistry>()
12-
.init_resource::<ActionBindings>()
13-
.add_systems(Update, run_actions_on_binding);
11+
app.init_resource::<ActionRegistry>();
1412
}
1513
}
1614

17-
fn run_actions_on_binding(world: &mut World) {
18-
world.resource_scope(|world, mut bindings: Mut<ActionBindings>| {
19-
world.resource_scope(|world, input: Mut<ButtonInput<KeyCode>>| {
20-
// Sort by the invserse amount of keys in the binding so that simpler keybinds don't prevent more complex ones from triggering.
21-
bindings.list.sort_by_key(|(v, _)| usize::MAX - v.len());
22-
'outer: for (binding, action_id) in &bindings.list {
23-
if let Some(last_key) = binding.last() {
24-
for key in &binding[..(binding.len() - 1)] {
25-
if !input.pressed(*key) {
26-
continue 'outer;
27-
}
28-
}
29-
if input.just_pressed(*last_key) {
30-
world.run_action(action_id);
31-
break 'outer;
32-
}
33-
}
34-
}
35-
});
36-
});
37-
}
38-
3915
/// The registry for [`Action`]s
4016
#[derive(Resource, Default)]
4117
pub struct ActionRegistry {
@@ -68,24 +44,6 @@ impl ActionRegistry {
6844
}
6945
}
7046

71-
/// List of keybindings to [`Action`]s
72-
#[derive(Resource, Default)]
73-
pub struct ActionBindings {
74-
list: Vec<(Vec<KeyCode>, String)>,
75-
}
76-
77-
impl ActionBindings {
78-
/// Add a binding for an action.
79-
pub fn add_binding(
80-
&mut self,
81-
action_id: impl Into<String>,
82-
binding: impl IntoIterator<Item = KeyCode>,
83-
) {
84-
self.list
85-
.push((binding.into_iter().collect(), action_id.into()));
86-
}
87-
}
88-
8947
/// Defines some action with an id and a label for display.
9048
pub struct Action {
9149
id: String,
@@ -103,13 +61,6 @@ pub trait ActionAppExt {
10361
label: impl Into<String>,
10462
system: impl IntoSystem<(), (), M> + 'static,
10563
) -> &mut Self;
106-
107-
/// Register an action binding.
108-
fn register_action_binding(
109-
&mut self,
110-
action_id: impl Into<String>,
111-
binding: impl IntoIterator<Item = KeyCode>,
112-
) -> &mut Self;
11364
}
11465

11566
impl ActionAppExt for App {
@@ -125,17 +76,6 @@ impl ActionAppExt for App {
12576
.register(id, label, system_id);
12677
self
12778
}
128-
129-
fn register_action_binding(
130-
&mut self,
131-
action_id: impl Into<String>,
132-
binding: impl IntoIterator<Item = KeyCode>,
133-
) -> &mut Self {
134-
self.world_mut()
135-
.get_resource_or_init::<ActionBindings>()
136-
.add_binding(action_id, binding);
137-
self
138-
}
13979
}
14080

14181
/// [`ActionRegistry`] extension trait for [`World`].
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! Editor keybinding module.
2+
3+
use bevy::prelude::*;
4+
5+
use crate::actions::ActionWorldExt;
6+
7+
/// Editor keybinding plugin.
8+
#[derive(Default)]
9+
pub struct KeybindingPlugin;
10+
11+
impl Plugin for KeybindingPlugin {
12+
fn build(&self, app: &mut App) {
13+
app.init_resource::<Keybindings>()
14+
.add_systems(Update, process_keybindings);
15+
}
16+
}
17+
18+
/// The store of keybindings.
19+
#[derive(Resource)]
20+
pub struct Keybindings {
21+
list: Vec<Keybinding>,
22+
/// Control whether keybindings are active.
23+
pub enabled: bool,
24+
}
25+
26+
impl Keybindings {
27+
/// Add a keybinding to the list.
28+
pub fn add_keybinding(&mut self, keybinding: Keybinding) {
29+
self.list.push(keybinding);
30+
}
31+
}
32+
33+
impl Default for Keybindings {
34+
fn default() -> Self {
35+
Self {
36+
list: Default::default(),
37+
enabled: true,
38+
}
39+
}
40+
}
41+
42+
/// A keybinding for an editor [`Action`](crate::actions::Action).
43+
///
44+
/// # Example
45+
/// This example binds the "load-gltf" action to <kbd>Ctrl</kbd> + <kbd>L</kbd>.
46+
/// ```no_run
47+
/// # use bevy_editor_core::prelude::*;
48+
/// # use bevy::prelude::*;
49+
/// # let mut app = App::new();
50+
/// app.register_keybinding(Keybinding::new("load-gltf", KeyCode::KeyL).ctrl());
51+
/// ```
52+
#[derive(Clone, Debug, Reflect)]
53+
pub struct Keybinding {
54+
action_id: String,
55+
key: KeyCode,
56+
ctrl: bool,
57+
shift: bool,
58+
alt: bool,
59+
os: bool,
60+
}
61+
62+
impl Keybinding {
63+
/// Create a new keybind from the id of the action it will be bound to and a keycode.
64+
pub fn new(action_id: impl Into<String>, key: KeyCode) -> Self {
65+
Self {
66+
action_id: action_id.into(),
67+
key,
68+
ctrl: false,
69+
shift: false,
70+
alt: false,
71+
os: false,
72+
}
73+
}
74+
75+
/// Require the <kbd>Ctrl</kbd> or <kbd>Control</kbd> modifier key to be held for this keybind.
76+
pub fn ctrl(mut self) -> Self {
77+
self.ctrl = true;
78+
self
79+
}
80+
81+
/// Require the <kbd>Shift</kbd> or <kbd>⇧</kbd> modifier key to be held for this keybind.
82+
pub fn shift(mut self) -> Self {
83+
self.shift = true;
84+
self
85+
}
86+
87+
/// Require the <kbd>Alt</kbd> or <kbd>Option</kbd> modifier key to be held for this keybind.
88+
pub fn alt(mut self) -> Self {
89+
self.alt = true;
90+
self
91+
}
92+
93+
/// Require the "Windows Logo" key or the <kbd>Command</kbd> or <kbd>⌘</kbd> modifier key to be held for this keybind.
94+
pub fn os(mut self) -> Self {
95+
self.os = true;
96+
self
97+
}
98+
}
99+
100+
fn process_keybindings(world: &mut World) {
101+
world.resource_scope(|world, bindings: Mut<Keybindings>| {
102+
if !bindings.enabled {
103+
return;
104+
}
105+
world.resource_scope(|world, input: Mut<ButtonInput<KeyCode>>| {
106+
let ctrl = input.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);
107+
let shift = input.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
108+
let alt = input.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]);
109+
let os = input.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight]);
110+
for binding in &bindings.list {
111+
if (!binding.ctrl || ctrl)
112+
&& (!binding.alt || alt)
113+
&& (!binding.shift || shift)
114+
&& (!binding.os || os)
115+
&& input.just_pressed(binding.key)
116+
{
117+
world.run_action(&binding.action_id);
118+
}
119+
}
120+
});
121+
});
122+
}
123+
124+
/// [`Keybindings`] extension trait for [`App`].
125+
pub trait KeybindingAppExt {
126+
/// Register a keybinding for an action.
127+
fn register_keybinding(&mut self, binding: Keybinding) -> &mut Self;
128+
}
129+
130+
impl KeybindingAppExt for App {
131+
fn register_keybinding(&mut self, binding: Keybinding) -> &mut Self {
132+
self.world_mut()
133+
.get_resource_or_init::<Keybindings>()
134+
.add_keybinding(binding);
135+
self
136+
}
137+
}

crates/bevy_editor_core/src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
//! This crate provides core functionality for the Bevy Engine Editor.
22
33
pub mod actions;
4+
pub mod keybinding;
45
pub mod selection;
56
pub mod utils;
67

78
use bevy::prelude::*;
89

9-
use crate::{actions::ActionsPlugin, selection::SelectionPlugin, utils::CoreUtilsPlugin};
10+
use crate::{
11+
actions::ActionsPlugin, keybinding::KeybindingPlugin, selection::SelectionPlugin,
12+
utils::CoreUtilsPlugin,
13+
};
1014

1115
/// Crate prelude.
1216
pub mod prelude {
1317
pub use crate::{
1418
actions::{ActionAppExt, ActionWorldExt},
19+
keybinding::{Keybinding, KeybindingAppExt},
1520
selection::EditorSelection,
1621
utils::IntoBoxedScene,
1722
};
@@ -23,6 +28,11 @@ pub struct EditorCorePlugin;
2328

2429
impl Plugin for EditorCorePlugin {
2530
fn build(&self, app: &mut App) {
26-
app.add_plugins((ActionsPlugin, SelectionPlugin, CoreUtilsPlugin));
31+
app.add_plugins((
32+
ActionsPlugin,
33+
KeybindingPlugin,
34+
SelectionPlugin,
35+
CoreUtilsPlugin,
36+
));
2737
}
2838
}

0 commit comments

Comments
 (0)