Skip to content

Commit c811e2d

Browse files
committed
Add command palette plugin (Ctrl+P)
Fuzzy-searchable modal listing every registered tool and action. Opens via Ctrl+P, arrow keys to navigate, Enter to execute, Escape to close. - New renzora_command_palette plugin crate, consumes only existing SDK surfaces (ToolbarRegistry, ShortcutRegistry, KeyBindings). - Tools are filtered by their visible predicate so context-sensitive ones (terrain brushes, Join Selected) appear only when relevant. - All EditorAction enum entries are surfaced too, with current keybindings displayed. Hold-based camera movement actions are filtered out. To fire built-in actions without touching the 35 existing just_pressed call sites, added an Arc<Mutex<HashSet>> dispatch channel on KeyBindings: - KeyBindings::dispatch(action) / dispatch_plugin(id) — programmatic trigger, no keyboard press required. - just_pressed() now returns true if the action was dispatched this frame, transparent to existing consumers. - clear_dispatched_actions system runs in Last to drain the set each frame so dispatches don't leak. - Plugin shortcut dispatcher honors is_plugin_dispatched the same way. Any future feature (macro recording, scripting bridge, remote control) can fire editor actions through this same channel.
1 parent 9b97ee6 commit c811e2d

6 files changed

Lines changed: 488 additions & 2 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ members = [
1212
"crates/editor/renzora_inspector",
1313
"crates/editor/renzora_test_component",
1414
"crates/editor/renzora_mesh_draw",
15+
"crates/editor/renzora_command_palette",
1516
"crates/editor/renzora_viewport",
1617
"crates/editor/renzora_hierarchy",
1718
"crates/editor/renzora_asset_browser",

crates/core/renzora_core/src/keybindings.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//! editor plugin DLLs can use these types without depending on each other.
55
66
use bevy::prelude::*;
7-
use std::collections::HashMap;
7+
use std::collections::{HashMap, HashSet};
8+
use std::sync::{Arc, Mutex};
89

910
/// Actions that can be bound to keys
1011
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
@@ -273,6 +274,16 @@ pub struct KeyBindings {
273274
pub plugin_bindings: HashMap<&'static str, KeyBinding>,
274275
/// Plugin shortcut currently being rebound (if any).
275276
pub plugin_rebinding: Option<&'static str>,
277+
/// Actions dispatched programmatically this frame (e.g. by the command
278+
/// palette). Consumers see them via [`Self::just_pressed`] alongside
279+
/// real keyboard presses; a clear system drains the set each frame.
280+
/// `Arc<Mutex>` so callers can dispatch through a shared `Res<>` rather
281+
/// than needing `ResMut<>`.
282+
#[doc(hidden)]
283+
pub dispatched: Arc<Mutex<HashSet<EditorAction>>>,
284+
/// Mirror of `dispatched` for plugin shortcuts, keyed by id.
285+
#[doc(hidden)]
286+
pub dispatched_plugin: Arc<Mutex<HashSet<&'static str>>>,
276287
}
277288

278289
impl Default for KeyBindings {
@@ -343,6 +354,8 @@ impl Default for KeyBindings {
343354
rebinding: None,
344355
plugin_bindings: HashMap::new(),
345356
plugin_rebinding: None,
357+
dispatched: Arc::new(Mutex::new(HashSet::new())),
358+
dispatched_plugin: Arc::new(Mutex::new(HashSet::new())),
346359
}
347360
}
348361
}
@@ -357,8 +370,13 @@ impl KeyBindings {
357370
}
358371
}
359372

360-
/// Check if an action key was just pressed this frame (with exact modifier check)
373+
/// Check if an action key was just pressed this frame (with exact modifier check).
374+
/// Also returns true if the action was programmatically dispatched this
375+
/// frame (e.g. from the command palette) via [`Self::dispatch`].
361376
pub fn just_pressed(&self, action: EditorAction, keyboard: &ButtonInput<KeyCode>) -> bool {
377+
if self.dispatched.lock().map_or(false, |d| d.contains(&action)) {
378+
return true;
379+
}
362380
if let Some(binding) = self.bindings.get(&action) {
363381
let key_just_pressed = keyboard.just_pressed(binding.key);
364382
let ctrl_pressed = keyboard.pressed(KeyCode::ControlLeft) || keyboard.pressed(KeyCode::ControlRight);
@@ -375,6 +393,28 @@ impl KeyBindings {
375393
}
376394
}
377395

396+
/// Programmatically dispatch an action as if the bound key was pressed
397+
/// this frame. Cleared automatically at end of frame by
398+
/// [`clear_dispatched_actions`].
399+
pub fn dispatch(&self, action: EditorAction) {
400+
if let Ok(mut set) = self.dispatched.lock() {
401+
set.insert(action);
402+
}
403+
}
404+
405+
/// Programmatically dispatch a plugin shortcut by id.
406+
pub fn dispatch_plugin(&self, id: &'static str) {
407+
if let Ok(mut set) = self.dispatched_plugin.lock() {
408+
set.insert(id);
409+
}
410+
}
411+
412+
/// True if a plugin shortcut was dispatched this frame. Used by the
413+
/// plugin shortcut dispatcher in the editor SDK.
414+
pub fn is_plugin_dispatched(&self, id: &'static str) -> bool {
415+
self.dispatched_plugin.lock().map_or(false, |d| d.contains(id))
416+
}
417+
378418
/// Get the binding for an action
379419
pub fn get(&self, action: EditorAction) -> Option<&KeyBinding> {
380420
self.bindings.get(&action)
@@ -403,6 +443,17 @@ impl KeyBindings {
403443
}
404444
}
405445

446+
/// System that clears the per-frame dispatch sets. Registered by the
447+
/// keybindings plugin in `Last`.
448+
pub fn clear_dispatched_actions(bindings: Res<KeyBindings>) {
449+
if let Ok(mut set) = bindings.dispatched.lock() {
450+
set.clear();
451+
}
452+
if let Ok(mut set) = bindings.dispatched_plugin.lock() {
453+
set.clear();
454+
}
455+
}
456+
406457
/// Convert KeyCode to display name
407458
pub fn key_name(key: KeyCode) -> &'static str {
408459
match key {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "renzora_command_palette"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["dylib"]
8+
9+
[dependencies]
10+
bevy = { workspace = true }
11+
renzora = { path = "../../renzora", default-features = false, features = ["editor"] }
12+
egui-phosphor = { version = "0.11", features = ["regular"] }

0 commit comments

Comments
 (0)