//! `cargo r -p re_ui --example cmd_palette_example` use re_log_types::EntityPath; use re_ui::{ CmdRow, CommandPalette, CommandPaletteProvider, FuzzyMatch, FuzzyQuery, MatchGroup, MatchedCmd, SyntaxHighlighting as _, UICommand, UiExt as _, }; use url::Url; fn main() -> eframe::Result { let mut state = State::default(); let set_uo = std::sync::Once::new(); let options = eframe::NativeOptions::default(); eframe::run_ui_native("My egui App", options, move |ui, _frame| { set_uo.call_once(|| { re_ui::apply_style_and_install_loaders(ui); }); state.ui(ui); }) } #[expect(dead_code, reason = "This is an example")] #[derive(Debug)] enum Command { #[expect(clippy::enum_variant_names, reason = "This is an example")] UiCommand(UICommand), SelectEntityPath(String), OpenUrl(Url), } impl Command { fn tooltip(&self) -> &'static str { match self { Self::UiCommand(command) => command.tooltip(), Self::SelectEntityPath(_) => "Select and focus on this entity", Self::OpenUrl(_) => { "Open this URL in the viewer. If the contents are already loaded, this will select them." } } } fn formatted_kb_shortcut(&self, egui_ctx: &egui::Context) -> Option { match self { Self::UiCommand(command) => command.formatted_kb_shortcut(egui_ctx), Self::SelectEntityPath(_) | Self::OpenUrl(_) => None, } } } struct RecordingInfo { entities: Vec<&'static str>, } struct CommandProvider { /// Info about the open recording, if any rec: Option, } impl CommandPaletteProvider for CommandProvider { fn initial_hint_ui(&mut self, ui: &mut egui::Ui) { if self.rec.is_some() { ui.weak("Find a command, search for an entity, or enter an URL to open"); } else { ui.weak("Find a command or enter an URL to open"); } ui.add_space(4.0); } fn all_matching(&mut self, query: &FuzzyQuery) -> Vec> { use strum::IntoEnumIterator as _; let ui_cmd_group = if query.raw_query().starts_with('/') { vec![] // The user is looking for an entity } else { UICommand::iter() .filter_map(|command| { let enabled = true; let target_text = command.text(); let command = Command::UiCommand(command); if query.is_empty() { // Nothing entered yet: show all commands: Some(MatchedCmd { command, fuzzy_match: FuzzyMatch::lowest(target_text.to_owned()), enabled, }) } else { query .try_match(target_text.to_owned()) .map(|fuzzy_match| MatchedCmd { command, fuzzy_match, enabled, }) } }) .collect() }; let entity_group = if query.is_empty() { vec![] // Nothing entered yet: only show commands, no entities } else if let Some(rec) = &self.rec { rec.entities .iter() .filter_map(|&entity_path| { query .try_match(entity_path.to_owned()) .map(|fuzzy_match| MatchedCmd { command: Command::SelectEntityPath(entity_path.to_owned()), fuzzy_match, enabled: true, }) }) .collect() } else { vec![] // no recording open }; let url_group = if let Ok(url) = query.raw_query().trim().parse::() { // The user entered a URL. Let's open it! vec![MatchedCmd { fuzzy_match: FuzzyMatch::highest(url.to_string()), command: Command::OpenUrl(url), enabled: true, }] } else { vec![] }; vec![ui_cmd_group, entity_group, url_group] } fn cmd_row(&self, ui: &egui::Ui, matched: &MatchedCmd, selected: bool) -> CmdRow { let kb_shortcut = matched .command .formatted_kb_shortcut(ui.ctx()) .unwrap_or_default(); let text_color = if !matched.enabled { ui.visuals().weak_text_color() } else if selected { ui.visuals().selection.stroke.color } else { ui.visuals().widgets.inactive.fg_stroke.color }; let job = if let Command::SelectEntityPath(ent_path) = &matched.command { let mut job = EntityPath::parse_forgiving(ent_path).syntax_highlighted(ui.style()); if selected { // The syntax colors clash with the selection background, so recolor the // whole path to the selection text color (keeping the font/size). for section in &mut job.sections { section.format.color = text_color; } } job } else { egui::text::LayoutJob::simple( matched.fuzzy_match.target().to_owned(), egui::TextStyle::Button.resolve(ui.style()), text_color, f32::INFINITY, ) }; let job = if matched.enabled { // Only highlight the matched characters on available commands; // unavailable ones stay uniformly grayed out. matched .fuzzy_match .highlight_matching_text(ui.style(), &job, selected) } else { job }; CmdRow { job, kb_shortcut, tooltip: Some(matched.command.tooltip().to_owned()), } } } struct State { cmd_palette: CommandPalette, has_recording: bool, latest_command: Option, } impl Default for State { fn default() -> Self { Self { cmd_palette: Default::default(), has_recording: true, latest_command: Default::default(), } } } impl State { fn ui(&mut self, ui: &mut egui::Ui) { egui::CentralPanel::default().show(ui, |ui| { ui.heading("Press cmd-K"); ui.re_checkbox(&mut self.has_recording, "Pretend to have a recording"); }); let mut cmd_provider = CommandProvider { rec: self.has_recording.then(|| RecordingInfo { // Fake-match a bunch of entities. entities: vec![ "/camera/pinhole", "/camera/rgb", "/world", "/world/car", "/world/car/front_camera", "/world/car/imu", "/world/road", ], }), }; if let Some(command) = self.cmd_palette.show(ui.ctx(), &mut cmd_provider) { self.on_command(ui, command); } if let Some(ui_cmd) = re_ui::UICommand::listen_for_kb_shortcut(ui) { self.on_command(ui, Command::UiCommand(ui_cmd)); } } fn on_command(&mut self, ui: &egui::Ui, command: Command) { match command { Command::UiCommand(ui_cmd) => match ui_cmd { UICommand::ToggleCommandPalette => self.cmd_palette.toggle(), UICommand::ZoomIn => { let mut zoom_factor = ui.zoom_factor(); zoom_factor += 0.1; ui.set_zoom_factor(zoom_factor); } UICommand::ZoomOut => { let mut zoom_factor = ui.zoom_factor(); zoom_factor -= 0.1; ui.set_zoom_factor(zoom_factor); } UICommand::ZoomReset => { ui.set_zoom_factor(1.0); } _ => {} }, Command::SelectEntityPath(_) | Command::OpenUrl(_) => { // } } self.latest_command = Some(command); } }