From cff545f3fb913355ec9f7d963529e1261c516892 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 9 Jan 2024 16:01:40 +0100 Subject: [PATCH] Add Help and Discord to command palette (#4752) ### What You can now open the help web site and visit the Rerun Discord from the command pallette. * Closes https://github.com/rerun-io/rerun/issues/4436 ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Using newly built examples: [app.rerun.io](https://app.rerun.io/pr/4752/index.html) * Using examples from latest `main` build: [app.rerun.io](https://app.rerun.io/pr/4752/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json) * Using full set of examples from `nightly` build: [app.rerun.io](https://app.rerun.io/pr/4752/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG - [PR Build Summary](https://build.rerun.io/pr/4752) - [Docs preview](https://rerun.io/preview/348636dd01a5a97f5efe7992720243d309f44948/docs) - [Examples preview](https://rerun.io/preview/348636dd01a5a97f5efe7992720243d309f44948/examples) - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html) --------- Co-authored-by: Andreas Reich --- crates/re_types/src/tensor_data.rs | 4 +- crates/re_ui/src/command.rs | 153 ++++++++++++++++---------- crates/re_ui/src/command_palette.rs | 18 ++- crates/re_viewer/src/app.rs | 36 ++++-- crates/re_viewer/src/ui/rerun_menu.rs | 46 +------- 5 files changed, 138 insertions(+), 119 deletions(-) diff --git a/crates/re_types/src/tensor_data.rs b/crates/re_types/src/tensor_data.rs index 28538087eff1..dbc9efd4521f 100644 --- a/crates/re_types/src/tensor_data.rs +++ b/crates/re_types/src/tensor_data.rs @@ -551,9 +551,7 @@ impl DecodedTensor { jpeg_bytes: &::re_types_core::ArrowBuffer, [expected_height, expected_width, expected_channels]: [u64; 3], ) -> Result { - re_tracing::profile_function!(); - - re_log::debug!("Decoding {expected_width}x{expected_height} JPEG"); + re_tracing::profile_function!(format!("{expected_width}x{expected_height}")); use zune_core::colorspace::ColorSpace; use zune_core::options::DecoderOptions; diff --git a/crates/re_ui/src/command.rs b/crates/re_ui/src/command.rs index 3b3710098c6f..394402c41ad2 100644 --- a/crates/re_ui/src/command.rs +++ b/crates/re_ui/src/command.rs @@ -22,6 +22,9 @@ pub enum UICommand { #[cfg(not(target_arch = "wasm32"))] Quit, + OpenWebHelp, + OpenRerunDiscord, + ResetViewer, #[cfg(not(target_arch = "wasm32"))] @@ -78,95 +81,98 @@ impl UICommand { pub fn text_and_tooltip(self) -> (&'static str, &'static str) { match self { #[cfg(not(target_arch = "wasm32"))] - UICommand::Save => ("Save…", "Save all data to a Rerun data file (.rrd)"), + Self::Save => ("Save…", "Save all data to a Rerun data file (.rrd)"), #[cfg(not(target_arch = "wasm32"))] - UICommand::SaveSelection => ( + Self::SaveSelection => ( "Save loop selection…", "Save data for the current loop selection to a Rerun data file (.rrd)", ), - UICommand::Open => ("Open…", "Open any supported files (.rrd, images, meshes, …)"), + Self::Open => ("Open…", "Open any supported files (.rrd, images, meshes, …)"), - UICommand::CloseCurrentRecording => ( + Self::CloseCurrentRecording => ( "Close current Recording", "Close the current Recording (unsaved data will be lost)", ), #[cfg(not(target_arch = "wasm32"))] - UICommand::Quit => ("Quit", "Close the Rerun Viewer"), + Self::Quit => ("Quit", "Close the Rerun Viewer"), + + Self::OpenWebHelp => ("Help", "Visit the help page on our website, with troubleshooting tips and more"), + Self::OpenRerunDiscord => ("Rerun Discord", "Visit the Rerun Discord server, where you can ask questions and get help"), - UICommand::ResetViewer => ( + Self::ResetViewer => ( "Reset Viewer", "Reset the Viewer to how it looked the first time you ran it", ), #[cfg(not(target_arch = "wasm32"))] - UICommand::OpenProfiler => ( + Self::OpenProfiler => ( "Open profiler", "Starts a profiler, showing what makes the viewer run slow", ), - UICommand::ToggleMemoryPanel => ( + Self::ToggleMemoryPanel => ( "Toggle Memory Panel", "View and track current RAM usage inside Rerun Viewer", ), - UICommand::ToggleBlueprintPanel => ("Toggle Blueprint Panel", "Toggle the left panel"), - UICommand::ToggleSelectionPanel => ("Toggle Selection Panel", "Toggle the right panel"), - UICommand::ToggleTimePanel => ("Toggle Time Panel", "Toggle the bottom panel"), + Self::ToggleBlueprintPanel => ("Toggle Blueprint Panel", "Toggle the left panel"), + Self::ToggleSelectionPanel => ("Toggle Selection Panel", "Toggle the right panel"), + Self::ToggleTimePanel => ("Toggle Time Panel", "Toggle the bottom panel"), #[cfg(debug_assertions)] - UICommand::ToggleStylePanel => ( + Self::ToggleStylePanel => ( "Toggle Style Panel", "View and change global egui style settings", ), #[cfg(not(target_arch = "wasm32"))] - UICommand::ToggleFullscreen => ( + Self::ToggleFullscreen => ( "Toggle fullscreen", "Toggle between windowed and fullscreen viewer", ), #[cfg(not(target_arch = "wasm32"))] - UICommand::ZoomIn => ("Zoom In", "Increases the UI zoom level"), + Self::ZoomIn => ("Zoom In", "Increases the UI zoom level"), #[cfg(not(target_arch = "wasm32"))] - UICommand::ZoomOut => ("Zoom Out", "Decreases the UI zoom level"), + Self::ZoomOut => ("Zoom Out", "Decreases the UI zoom level"), #[cfg(not(target_arch = "wasm32"))] - UICommand::ZoomReset => ( + Self::ZoomReset => ( "Reset Zoom", "Resets the UI zoom level to the operating system's default value", ), - UICommand::SelectionPrevious => ("Previous selection", "Go to previous selection"), - UICommand::SelectionNext => ("Next selection", "Go to next selection"), - UICommand::ToggleCommandPalette => ("Command Palette…", "Toggle the Command Palette"), + Self::SelectionPrevious => ("Previous selection", "Go to previous selection"), + Self::SelectionNext => ("Next selection", "Go to next selection"), + Self::ToggleCommandPalette => ("Command Palette…", "Toggle the Command Palette"), - UICommand::PlaybackTogglePlayPause => { + Self::PlaybackTogglePlayPause => { ("Toggle play/pause", "Either play or pause the time") } - UICommand::PlaybackFollow => ("Follow", "Follow on from end of timeline"), - UICommand::PlaybackStepBack => ( + Self::PlaybackFollow => ("Follow", "Follow on from end of timeline"), + Self::PlaybackStepBack => ( "Step time back", "Move the time marker back to the previous point in time with any data", ), - UICommand::PlaybackStepForward => ( + Self::PlaybackStepForward => ( "Step time forward", "Move the time marker to the next point in time with any data", ), - UICommand::PlaybackRestart => ("Restart", "Restart from beginning of timeline"), + Self::PlaybackRestart => ("Restart", "Restart from beginning of timeline"), #[cfg(not(target_arch = "wasm32"))] - UICommand::ScreenshotWholeApp => ( + Self::ScreenshotWholeApp => ( "Screenshot", "Copy screenshot of the whole app to clipboard", ), #[cfg(not(target_arch = "wasm32"))] - UICommand::PrintDatastore => ( + Self::PrintDatastore => ( "Print datastore", "Prints the entire data store to the console. WARNING: this may be A LOT of text.", ), #[cfg(target_arch = "wasm32")] - UICommand::CopyDirectLink => ( + Self::CopyDirectLink => ( "Copy direct link", "Copy a link to the viewer with the URL parameter set to the current .rrd data source." ) @@ -194,58 +200,73 @@ impl UICommand { match self { #[cfg(not(target_arch = "wasm32"))] - UICommand::Save => Some(cmd(Key::S)), + Self::Save => Some(cmd(Key::S)), #[cfg(not(target_arch = "wasm32"))] - UICommand::SaveSelection => Some(cmd_alt(Key::S)), - UICommand::Open => Some(cmd(Key::O)), - UICommand::CloseCurrentRecording => None, + Self::SaveSelection => Some(cmd_alt(Key::S)), + Self::Open => Some(cmd(Key::O)), + Self::CloseCurrentRecording => None, #[cfg(all(not(target_arch = "wasm32"), target_os = "windows"))] - UICommand::Quit => Some(KeyboardShortcut::new(Modifiers::ALT, Key::F4)), + Self::Quit => Some(KeyboardShortcut::new(Modifiers::ALT, Key::F4)), + + Self::OpenWebHelp => None, + Self::OpenRerunDiscord => None, #[cfg(all(not(target_arch = "wasm32"), not(target_os = "windows")))] - UICommand::Quit => Some(cmd(Key::Q)), + Self::Quit => Some(cmd(Key::Q)), - UICommand::ResetViewer => Some(ctrl_shift(Key::R)), + Self::ResetViewer => Some(ctrl_shift(Key::R)), #[cfg(not(target_arch = "wasm32"))] - UICommand::OpenProfiler => Some(ctrl_shift(Key::P)), - UICommand::ToggleMemoryPanel => Some(ctrl_shift(Key::M)), - UICommand::ToggleBlueprintPanel => Some(ctrl_shift(Key::B)), - UICommand::ToggleSelectionPanel => Some(ctrl_shift(Key::S)), - UICommand::ToggleTimePanel => Some(ctrl_shift(Key::T)), + Self::OpenProfiler => Some(ctrl_shift(Key::P)), + Self::ToggleMemoryPanel => Some(ctrl_shift(Key::M)), + Self::ToggleBlueprintPanel => Some(ctrl_shift(Key::B)), + Self::ToggleSelectionPanel => Some(ctrl_shift(Key::S)), + Self::ToggleTimePanel => Some(ctrl_shift(Key::T)), #[cfg(debug_assertions)] - UICommand::ToggleStylePanel => Some(ctrl_shift(Key::U)), + Self::ToggleStylePanel => Some(ctrl_shift(Key::U)), #[cfg(not(target_arch = "wasm32"))] - UICommand::ToggleFullscreen => Some(key(Key::F11)), + Self::ToggleFullscreen => Some(key(Key::F11)), #[cfg(not(target_arch = "wasm32"))] - UICommand::ZoomIn => Some(egui::gui_zoom::kb_shortcuts::ZOOM_IN), + Self::ZoomIn => Some(egui::gui_zoom::kb_shortcuts::ZOOM_IN), #[cfg(not(target_arch = "wasm32"))] - UICommand::ZoomOut => Some(egui::gui_zoom::kb_shortcuts::ZOOM_OUT), + Self::ZoomOut => Some(egui::gui_zoom::kb_shortcuts::ZOOM_OUT), #[cfg(not(target_arch = "wasm32"))] - UICommand::ZoomReset => Some(egui::gui_zoom::kb_shortcuts::ZOOM_RESET), + Self::ZoomReset => Some(egui::gui_zoom::kb_shortcuts::ZOOM_RESET), - UICommand::SelectionPrevious => Some(ctrl_shift(Key::ArrowLeft)), - UICommand::SelectionNext => Some(ctrl_shift(Key::ArrowRight)), - UICommand::ToggleCommandPalette => Some(cmd(Key::P)), + Self::SelectionPrevious => Some(ctrl_shift(Key::ArrowLeft)), + Self::SelectionNext => Some(ctrl_shift(Key::ArrowRight)), + Self::ToggleCommandPalette => Some(cmd(Key::P)), - UICommand::PlaybackTogglePlayPause => Some(key(Key::Space)), - UICommand::PlaybackFollow => Some(cmd(Key::ArrowRight)), - UICommand::PlaybackStepBack => Some(key(Key::ArrowLeft)), - UICommand::PlaybackStepForward => Some(key(Key::ArrowRight)), - UICommand::PlaybackRestart => Some(cmd(Key::ArrowLeft)), + Self::PlaybackTogglePlayPause => Some(key(Key::Space)), + Self::PlaybackFollow => Some(cmd(Key::ArrowRight)), + Self::PlaybackStepBack => Some(key(Key::ArrowLeft)), + Self::PlaybackStepForward => Some(key(Key::ArrowRight)), + Self::PlaybackRestart => Some(cmd(Key::ArrowLeft)), #[cfg(not(target_arch = "wasm32"))] - UICommand::ScreenshotWholeApp => None, + Self::ScreenshotWholeApp => None, #[cfg(not(target_arch = "wasm32"))] - UICommand::PrintDatastore => None, + Self::PrintDatastore => None, #[cfg(target_arch = "wasm32")] - UICommand::CopyDirectLink => None, + Self::CopyDirectLink => None, } } + pub fn icon(self) -> Option<&'static crate::Icon> { + match self { + Self::OpenWebHelp => Some(&crate::icons::EXTERNAL_LINK), + Self::OpenRerunDiscord => Some(&crate::icons::DISCORD), + _ => None, + } + } + + pub fn is_link(self) -> bool { + matches!(self, Self::OpenWebHelp | Self::OpenRerunDiscord) + } + #[must_use = "Returns the Command that was triggered by some keyboard shortcut"] pub fn listen_for_kb_shortcut(egui_ctx: &egui::Context) -> Option { use strum::IntoEnumIterator as _; @@ -256,7 +277,7 @@ impl UICommand { } egui_ctx.input_mut(|input| { - for command in UICommand::iter() { + for command in Self::iter() { if let Some(kb_shortcut) = command.kb_shortcut() { if input.consume_shortcut(&kb_shortcut) { return Some(command); @@ -276,19 +297,35 @@ impl UICommand { command_sender: &impl UICommandSender, ) -> egui::Response { let button = self.menu_button(ui.ctx()); - let response = ui.add(button).on_hover_text(self.tooltip()); + let mut response = ui.add(button).on_hover_text(self.tooltip()); + + if self.is_link() { + response = response.on_hover_cursor(egui::CursorIcon::PointingHand); + } + if response.clicked() { command_sender.send_ui(self); ui.close_menu(); } + response } pub fn menu_button(self, egui_ctx: &egui::Context) -> egui::Button<'static> { - let mut button = egui::Button::new(self.text()); + let mut button = if let Some(icon) = self.icon() { + egui::Button::image_and_text( + icon.as_image() + .fit_to_exact_size(crate::ReUi::small_icon_size()), + self.text(), + ) + } else { + egui::Button::new(self.text()) + }; + if let Some(shortcut) = self.kb_shortcut() { button = button.shortcut_text(egui_ctx.format_shortcut(&shortcut)); } + button } diff --git a/crates/re_ui/src/command_palette.rs b/crates/re_ui/src/command_palette.rs index c4b1dea49da3..25034ac2b574 100644 --- a/crates/re_ui/src/command_palette.rs +++ b/crates/re_ui/src/command_palette.rs @@ -30,11 +30,21 @@ impl CommandPalette { let max_height = 320.0.at_most(screen_rect.height()); egui::Window::new("Command Palette") - .title_bar(false) + .fixed_pos(screen_rect.center() - 0.5 * max_height * egui::Vec2::Y) .fixed_size([width, max_height]) .pivot(egui::Align2::CENTER_TOP) - .fixed_pos(screen_rect.center() - 0.5 * max_height * egui::Vec2::Y) - .show(egui_ctx, |ui| self.window_content_ui(ui))? + .resizable(false) + .scroll2(false) + .title_bar(false) + .show(egui_ctx, |ui| { + // We need an extra egui frame here because we set clip_rect_margin to zero. + egui::Frame { + inner_margin: 2.0.into(), + ..Default::default() + } + .show(ui, |ui| self.window_content_ui(ui)) + .inner + })? .inner? } @@ -199,7 +209,7 @@ fn commands_that_match(query: &str) -> Vec { }) }) .collect(); - matches.sort_by_key(|m| -m.score); + matches.sort_by_key(|m| -m.score); // highest score first matches } } diff --git a/crates/re_viewer/src/app.rs b/crates/re_viewer/src/app.rs index ae4051e7d839..690b9d3a231d 100644 --- a/crates/re_viewer/src/app.rs +++ b/crates/re_viewer/src/app.rs @@ -405,7 +405,7 @@ impl App { fn run_ui_command( &mut self, - _egui_ctx: &egui::Context, + egui_ctx: &egui::Context, app_blueprint: &AppBlueprint<'_>, store_context: Option<&StoreContext<'_>>, cmd: UICommand, @@ -435,7 +435,7 @@ impl App { } #[cfg(target_arch = "wasm32")] UICommand::Open => { - let egui_ctx = _egui_ctx.clone(); + let egui_ctx = egui_ctx.clone(); self.open_files_promise = Some(poll_promise::Promise::spawn_local(async move { let file = async_open_rrd_dialog().await; egui_ctx.request_repaint(); // Wake ui thread @@ -454,7 +454,21 @@ impl App { #[cfg(not(target_arch = "wasm32"))] UICommand::Quit => { - _egui_ctx.send_viewport_cmd(egui::ViewportCommand::Close); + egui_ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + + UICommand::OpenWebHelp => { + egui_ctx.open_url(egui::output::OpenUrl { + url: "https://www.rerun.io/docs/getting-started/viewer-walkthrough".to_owned(), + new_tab: true, + }); + } + + UICommand::OpenRerunDiscord => { + egui_ctx.open_url(egui::output::OpenUrl { + url: "https://discord.gg/PXtCgFBSmH".to_owned(), + new_tab: true, + }); } UICommand::ResetViewer => self.command_sender.send_system(SystemCommand::ResetViewer), @@ -482,28 +496,28 @@ impl App { #[cfg(not(target_arch = "wasm32"))] UICommand::ToggleFullscreen => { - let fullscreen = _egui_ctx.input(|i| i.viewport().fullscreen.unwrap_or(false)); - _egui_ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!fullscreen)); + let fullscreen = egui_ctx.input(|i| i.viewport().fullscreen.unwrap_or(false)); + egui_ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!fullscreen)); } #[cfg(not(target_arch = "wasm32"))] UICommand::ZoomIn => { - let mut zoom_factor = _egui_ctx.zoom_factor(); + let mut zoom_factor = egui_ctx.zoom_factor(); zoom_factor += 0.1; zoom_factor = zoom_factor.clamp(MIN_ZOOM_FACTOR, MAX_ZOOM_FACTOR); zoom_factor = (zoom_factor * 10.).round() / 10.; - _egui_ctx.set_zoom_factor(zoom_factor); + egui_ctx.set_zoom_factor(zoom_factor); } #[cfg(not(target_arch = "wasm32"))] UICommand::ZoomOut => { - let mut zoom_factor = _egui_ctx.zoom_factor(); + let mut zoom_factor = egui_ctx.zoom_factor(); zoom_factor -= 0.1; zoom_factor = zoom_factor.clamp(MIN_ZOOM_FACTOR, MAX_ZOOM_FACTOR); zoom_factor = (zoom_factor * 10.).round() / 10.; - _egui_ctx.set_zoom_factor(zoom_factor); + egui_ctx.set_zoom_factor(zoom_factor); } #[cfg(not(target_arch = "wasm32"))] UICommand::ZoomReset => { - _egui_ctx.set_zoom_factor(1.0); + egui_ctx.set_zoom_factor(1.0); } UICommand::SelectionPrevious => { @@ -548,7 +562,7 @@ impl App { #[cfg(not(target_arch = "wasm32"))] UICommand::ScreenshotWholeApp => { - self.screenshotter.request_screenshot(_egui_ctx); + self.screenshotter.request_screenshot(egui_ctx); } #[cfg(not(target_arch = "wasm32"))] UICommand::PrintDatastore => { diff --git a/crates/re_viewer/src/ui/rerun_menu.rs b/crates/re_viewer/src/ui/rerun_menu.rs index 015e77bc39e1..f166f52100b6 100644 --- a/crates/re_viewer/src/ui/rerun_menu.rs +++ b/crates/re_viewer/src/ui/rerun_menu.rs @@ -1,6 +1,6 @@ //! The main Rerun drop-down menu found in the top panel. -use egui::{NumExt as _, Widget}; +use egui::NumExt as _; use re_log_types::TimeZone; use re_ui::{ReUi, UICommand}; @@ -96,48 +96,8 @@ impl App { ui.add_space(SPACING); - // dont use `hyperlink_to` for styling reasons - const HELP_URL: &str = "https://www.rerun.io/docs/getting-started/viewer-walkthrough"; - - if egui::Button::image_and_text( - re_ui::icons::EXTERNAL_LINK - .as_image() - .fit_to_exact_size(ReUi::small_icon_size()), - "Help", - ) - .ui(ui) - .on_hover_cursor(egui::CursorIcon::PointingHand) - .on_hover_text(HELP_URL) - .clicked() - { - ui.ctx().output_mut(|o| { - o.open_url = Some(egui::output::OpenUrl { - url: HELP_URL.to_owned(), - new_tab: true, - }); - }); - } - - if egui::Button::image_and_text( - re_ui::icons::DISCORD - .as_image() - .fit_to_exact_size(ReUi::small_icon_size()), - "Rerun Discord", - ) - .ui(ui) - .on_hover_cursor(egui::CursorIcon::PointingHand) - .on_hover_text( - "Join the ReRun Discord server, where you can ask questions and get help.", - ) - .clicked() - { - ui.ctx().output_mut(|o| { - o.open_url = Some(egui::output::OpenUrl { - url: "https://discord.gg/PXtCgFBSmH".to_owned(), - new_tab: true, - }); - }); - } + UICommand::OpenWebHelp.menu_button_ui(ui, &self.command_sender); + UICommand::OpenRerunDiscord.menu_button_ui(ui, &self.command_sender); #[cfg(not(target_arch = "wasm32"))] {