Skip to main content

command_palette_hotkeys/
command_palette_hotkeys.rs

1use 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        &registry,
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}