command_palette_hotkeys/
command_palette_hotkeys.rs1use operad::native::{NativeWindowOptions, NativeWindowResult};
2use operad::tooltips::ShortcutFormatter;
3use operad::widgets;
4use operad::widgets::ext::{self as ext_widgets, CommandPaletteItem, CommandPaletteState};
5use operad::{
6 root_style, ColorRgba, CommandMeta, CommandRegistry, CommandScope, LayoutStyle, Shortcut,
7 TextStyle, UiDocument, UiNode, UiSize, UiVisual, WidgetAction, WidgetActionKind,
8};
9
10fn main() -> NativeWindowResult {
11 operad::native::run_app_with(
12 NativeWindowOptions::new("Command palette").with_min_size(620.0, 420.0),
13 CommandApp::default(),
14 CommandApp::update,
15 CommandApp::view,
16 )
17}
18
19struct CommandApp {
20 palette: CommandPaletteState,
21 last_command: String,
22}
23
24impl Default for CommandApp {
25 fn default() -> Self {
26 Self {
27 palette: CommandPaletteState::new(),
28 last_command: "None".to_string(),
29 }
30 }
31}
32
33impl CommandApp {
34 fn update(&mut self, action: WidgetAction) {
35 let Some(action_id) = action.binding.action_id().map(|id| id.as_str()) else {
36 return;
37 };
38 let items = command_items();
39 if action_id == "commands.search" {
40 if let WidgetActionKind::TextEdit(edit) = &action.kind {
41 if edit.local_position.is_none() {
42 if let Some(selection) = self.palette.handle_event(&items, &edit.event).selected
43 {
44 self.last_command = selection.id;
45 }
46 }
47 }
48 } else if let Some(command) = action_id.strip_prefix("commands.item.") {
49 self.last_command = command.to_string();
50 }
51 }
52
53 fn view(&self, viewport: UiSize) -> UiDocument {
54 let mut ui = UiDocument::new(root_style(viewport.width, viewport.height));
55 let panel = ui.add_child(
56 ui.root(),
57 UiNode::container(
58 "commands.panel",
59 LayoutStyle::column()
60 .with_size(560.0, 360.0)
61 .with_padding(16.0)
62 .with_gap(10.0),
63 )
64 .with_visual(UiVisual::panel(ColorRgba::new(24, 29, 36, 255), None, 6.0)),
65 );
66 widgets::label(
67 &mut ui,
68 panel,
69 "commands.title",
70 "Command palette and shortcuts",
71 heading(),
72 LayoutStyle::new().with_width_percent(1.0).with_height(32.0),
73 );
74 let mut options =
75 ext_widgets::CommandPaletteOptions::default().with_action_prefix("commands");
76 options.width = 520.0;
77 options.max_visible_rows = 5;
78 ext_widgets::command_palette(
79 &mut ui,
80 panel,
81 "commands.palette",
82 &command_items(),
83 &self.palette,
84 None,
85 options,
86 );
87 widgets::label(
88 &mut ui,
89 panel,
90 "commands.last",
91 format!("Last command: {}", self.last_command),
92 muted(),
93 LayoutStyle::new().with_width_percent(1.0).with_height(28.0),
94 );
95 ui
96 }
97}
98
99fn command_items() -> Vec<CommandPaletteItem> {
100 let mut registry = CommandRegistry::new();
101 registry
102 .register(
103 CommandMeta::new("app.open", "Open project")
104 .description("Open an existing project")
105 .category("File"),
106 )
107 .expect("register command");
108 registry
109 .register(
110 CommandMeta::new("app.save", "Save project")
111 .description("Write current changes")
112 .category("File"),
113 )
114 .expect("register command");
115 registry
116 .register(
117 CommandMeta::new("app.toggle_sidebar", "Toggle sidebar")
118 .description("Show or hide the left navigation")
119 .category("View"),
120 )
121 .expect("register command");
122 registry
123 .bind_shortcut(CommandScope::Global, Shortcut::ctrl('o'), "app.open")
124 .expect("bind shortcut");
125 registry
126 .bind_shortcut(CommandScope::Global, Shortcut::ctrl('s'), "app.save")
127 .expect("bind shortcut");
128 registry
129 .bind_shortcut(
130 CommandScope::Global,
131 Shortcut::ctrl('b'),
132 "app.toggle_sidebar",
133 )
134 .expect("bind shortcut");
135 ext_widgets::command_palette::command_palette_items_from_registry(
136 ®istry,
137 &[CommandScope::Global],
138 &ShortcutFormatter::default(),
139 )
140}
141
142fn heading() -> TextStyle {
143 TextStyle {
144 font_size: 22.0,
145 line_height: 30.0,
146 color: ColorRgba::WHITE,
147 ..TextStyle::default()
148 }
149}
150
151fn muted() -> TextStyle {
152 TextStyle {
153 color: ColorRgba::new(166, 178, 196, 255),
154 ..TextStyle::default()
155 }
156}