diff --git a/Cargo.lock b/Cargo.lock index 891729bc94d9..7afe824cf6bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1261,7 +1261,7 @@ checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "ecolor" version = "0.21.0" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "bytemuck", "serde", @@ -1270,7 +1270,7 @@ dependencies = [ [[package]] name = "eframe" version = "0.21.3" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "bytemuck", "cocoa", @@ -1301,7 +1301,7 @@ dependencies = [ [[package]] name = "egui" version = "0.21.0" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "accesskit", "ahash 0.8.2", @@ -1315,7 +1315,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.21.0" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "bytemuck", "epaint", @@ -1330,7 +1330,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.21.1" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "arboard", "egui", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.21.0" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "egui", "log", @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.21.0" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "bytemuck", "egui", @@ -1406,7 +1406,7 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "emath" version = "0.21.0" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "bytemuck", "serde", @@ -1487,7 +1487,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.21.0" -source = "git+https://github.com/emilk/egui?rev=e9fa6c8ff68a0257ce0f2801446ba701917b7ae9#e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" +source = "git+https://github.com/emilk/egui?rev=ff8e482#ff8e4826b393506ba3f77d135c1698dde444c9f7" dependencies = [ "ab_glyph", "ahash 0.8.2", diff --git a/Cargo.toml b/Cargo.toml index 146cde6d9f31..a0e07f62a5bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,13 +125,13 @@ debug = true # ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk. # TODO(andreas/emilk): Update to a stable egui version -# wgpu 0.16 support, device configuration dependent on adapter, and some additions to help egui_tiles -ecolor = { git = "https://github.com/emilk/egui", rev = "e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" } -eframe = { git = "https://github.com/emilk/egui", rev = "e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" } -egui = { git = "https://github.com/emilk/egui", rev = "e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" } -egui-wgpu = { git = "https://github.com/emilk/egui", rev = "e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" } -egui_extras = { git = "https://github.com/emilk/egui", rev = "e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" } -emath = { git = "https://github.com/emilk/egui", rev = "e9fa6c8ff68a0257ce0f2801446ba701917b7ae9" } +# wgpu 0.16 support, device configuration dependent on adapter, and some additions to help egui_tiles, egui::Modifiers::contains +ecolor = { git = "https://github.com/emilk/egui", rev = "ff8e482" } +eframe = { git = "https://github.com/emilk/egui", rev = "ff8e482" } +egui = { git = "https://github.com/emilk/egui", rev = "ff8e482" } +egui-wgpu = { git = "https://github.com/emilk/egui", rev = "ff8e482" } +egui_extras = { git = "https://github.com/emilk/egui", rev = "ff8e482" } +emath = { git = "https://github.com/emilk/egui", rev = "ff8e482" } # TODO(andreas): Either work around this issue in wgpu-egui (never discard command buffers) or wait for wgpu patch release. # Fix for command buffer dropping crash https://github.com/gfx-rs/wgpu/pull/3726 diff --git a/crates/re_ui/src/layout_job_builder.rs b/crates/re_ui/src/layout_job_builder.rs new file mode 100644 index 000000000000..db66dee09cc9 --- /dev/null +++ b/crates/re_ui/src/layout_job_builder.rs @@ -0,0 +1,97 @@ +/// Utility for building layout jobs. +pub struct LayoutJobBuilder<'a> { + pub layout_job: egui::text::LayoutJob, + pub re_ui: &'a crate::ReUi, +} + +impl<'a> LayoutJobBuilder<'a> { + pub fn new(re_ui: &'a crate::ReUi) -> Self { + Self { + layout_job: egui::text::LayoutJob::default(), + re_ui, + } + } + + /// Append a generic text block. + pub fn add<'b, T: Into>>(&mut self, text_block: T) { + let text_block: LayoutJobBuilderBuildingBlock<'_> = text_block.into(); + match text_block { + LayoutJobBuilderBuildingBlock::Body(text) => self.add_body(text), + LayoutJobBuilderBuildingBlock::Key(key) => self.add_key(key), + LayoutJobBuilderBuildingBlock::Modifier(modifier) => self.add_modifier(modifier), + LayoutJobBuilderBuildingBlock::MouseButton(button) => self.add_mouse_button(button), + }; + } + + /// Append body text. + pub fn add_body(&mut self, text: &str) { + self.layout_job + .append(text, 0.0, self.re_ui.text_format_body()); + } + + /// Append text that has special formatting for a button. + pub fn add_button_text(&mut self, text: &str) { + self.layout_job + .append(&text.to_lowercase(), 0.0, self.re_ui.text_format_key()); + } + + /// Append text for a keyboard key. + pub fn add_key(&mut self, key: egui::Key) { + self.add_button_text(key.name()); + } + + /// Append text for one or more modifier keys. + pub fn add_modifier(&mut self, modifier: egui::Modifiers) { + let is_mac = matches!( + self.re_ui.egui_ctx.os(), + egui::os::OperatingSystem::Mac | egui::os::OperatingSystem::IOS + ); + let text = egui::ModifierNames::NAMES.format(&modifier, is_mac); + self.add_button_text(&text); + } + + /// Append text for a mouse button. + pub fn add_mouse_button(&mut self, button: egui::PointerButton) { + self.add_button_text(match button { + egui::PointerButton::Primary => "left mouse button", + egui::PointerButton::Secondary => "right mouse button", + egui::PointerButton::Middle => "middle mouse button", + egui::PointerButton::Extra1 => "extra mouse button 1", + egui::PointerButton::Extra2 => "extra mouse button 2", + }); + } +} + +/// Generic building block that the layout job builder can consume. +/// +/// Not meant to be used directly, use [`LayoutJobBuilder::add`] instead. +pub enum LayoutJobBuilderBuildingBlock<'a> { + Body(&'a str), + Key(egui::Key), + Modifier(egui::Modifiers), + MouseButton(egui::PointerButton), +} + +impl<'a> From<&'a str> for LayoutJobBuilderBuildingBlock<'a> { + fn from(text: &'a str) -> Self { + Self::Body(text) + } +} + +impl From for LayoutJobBuilderBuildingBlock<'_> { + fn from(key: egui::Key) -> Self { + Self::Key(key) + } +} + +impl From for LayoutJobBuilderBuildingBlock<'_> { + fn from(modifier: egui::Modifiers) -> Self { + Self::Modifier(modifier) + } +} + +impl From for LayoutJobBuilderBuildingBlock<'_> { + fn from(button: egui::PointerButton) -> Self { + Self::MouseButton(button) + } +} diff --git a/crates/re_ui/src/lib.rs b/crates/re_ui/src/lib.rs index 95dc069bb9e4..1dbb38ccffa8 100644 --- a/crates/re_ui/src/lib.rs +++ b/crates/re_ui/src/lib.rs @@ -5,6 +5,7 @@ mod command_palette; mod design_tokens; pub mod egui_helpers; pub mod icons; +mod layout_job_builder; mod static_image_cache; pub mod toasts; mod toggle_switch; @@ -13,6 +14,7 @@ pub use command::Command; pub use command_palette::CommandPalette; pub use design_tokens::DesignTokens; pub use icons::Icon; +pub use layout_job_builder::LayoutJobBuilder; pub use static_image_cache::StaticImageCache; pub use toggle_switch::toggle_switch; @@ -606,6 +608,24 @@ impl ReUi { response } + + /// Text format used for regular body. + pub fn text_format_body(&self) -> egui::TextFormat { + egui::TextFormat::simple( + egui::TextStyle::Body.resolve(&self.egui_ctx.style()), + self.egui_ctx.style().visuals.text_color(), + ) + } + + /// Text format used for labels referring to keys and buttons. + pub fn text_format_key(&self) -> egui::TextFormat { + let mut style = egui::TextFormat::simple( + egui::TextStyle::Monospace.resolve(&self.egui_ctx.style()), + self.egui_ctx.style().visuals.text_color(), + ); + style.background = self.egui_ctx.style().visuals.widgets.noninteractive.bg_fill; + style + } } // ---------------------------------------------------------------------------- diff --git a/crates/re_viewer/src/ui/mod.rs b/crates/re_viewer/src/ui/mod.rs index e422287fdd0f..c3ad6b9867f8 100644 --- a/crates/re_viewer/src/ui/mod.rs +++ b/crates/re_viewer/src/ui/mod.rs @@ -5,6 +5,7 @@ mod selection_history_ui; mod space_view; mod space_view_entity_picker; mod space_view_heuristics; +mod spaceview_controls; mod view_bar_chart; mod view_category; mod view_tensor; diff --git a/crates/re_viewer/src/ui/spaceview_controls.rs b/crates/re_viewer/src/ui/spaceview_controls.rs new file mode 100644 index 000000000000..2d291a2d16c4 --- /dev/null +++ b/crates/re_viewer/src/ui/spaceview_controls.rs @@ -0,0 +1,41 @@ +/// Modifier to press for scroll to zoom. +pub const ZOOM_SCROLL_MODIFIER: egui::Modifiers = egui::Modifiers::COMMAND; + +/// Modifier to press for scroll to pan horizontally. +pub const HORIZONTAL_SCROLL_MODIFIER: egui::Modifiers = egui::Modifiers::SHIFT; + +/// Which mouse button to drag for panning a 2D view. +pub const DRAG_PAN2D_BUTTON: egui::PointerButton = egui::PointerButton::Primary; + +/// Rectangles drawn with this mouse button zoom in 2D views. +pub const SELECTION_RECT_ZOOM_BUTTON: egui::PointerButton = egui::PointerButton::Secondary; + +/// Clicking this button moves the timeline to where the cursor is. +pub const MOVE_TIME_CURSOR_BUTTON: egui::PointerButton = egui::PointerButton::Secondary; + +/// Which mouse button to drag for panning a 2D view. +pub const DRAG_PAN3D_BUTTON: egui::PointerButton = egui::PointerButton::Secondary; + +/// Which mouse button to drag for rotating a 3D view. +pub const ROTATE3D_BUTTON: egui::PointerButton = egui::PointerButton::Primary; + +/// Which mouse button rolls the camera. +pub const ROLL_MOUSE: egui::PointerButton = egui::PointerButton::Middle; + +/// Which mouse button rolls the camera if the roll modifier is pressed. +pub const ROLL_MOUSE_ALT: egui::PointerButton = egui::PointerButton::Primary; + +/// See [`ROLL_MOUSE_ALT`]. +pub const ROLL_MOUSE_MODIFIER: egui::Modifiers = egui::Modifiers::ALT; + +/// Which modifier speeds up the 3D camera movement. +pub const SPEED_UP_3D_MODIFIER: egui::Modifiers = egui::Modifiers::SHIFT; + +/// Which modifier slows down the 3D camera movement. +pub const SLOW_DOWN_3D_MODIFIER: egui::Modifiers = egui::Modifiers::CTRL; + +/// Key to restore the camera. +pub const TRACKED_CAMERA_RESTORE_KEY: egui::Key = egui::Key::Escape; + +/// Description text for which action resets a space view. +pub const RESET_VIEW_BUTTON_TEXT: &str = "double click"; diff --git a/crates/re_viewer/src/ui/view_bar_chart/mod.rs b/crates/re_viewer/src/ui/view_bar_chart/mod.rs index 5a9ca311776f..165adf971f8d 100644 --- a/crates/re_viewer/src/ui/view_bar_chart/mod.rs +++ b/crates/re_viewer/src/ui/view_bar_chart/mod.rs @@ -2,4 +2,4 @@ mod scene; pub(crate) use self::scene::SceneBarChart; mod ui; -pub(crate) use self::ui::{view_bar_chart, BarChartState, HELP_TEXT}; +pub(crate) use self::ui::{help_text, view_bar_chart, BarChartState}; diff --git a/crates/re_viewer/src/ui/view_bar_chart/ui.rs b/crates/re_viewer/src/ui/view_bar_chart/ui.rs index 943601e4327d..a71298f36fa8 100644 --- a/crates/re_viewer/src/ui/view_bar_chart/ui.rs +++ b/crates/re_viewer/src/ui/view_bar_chart/ui.rs @@ -7,14 +7,34 @@ use re_log::warn_once; use re_log_types::component_types::{self, InstanceKey}; use re_viewer_context::{auto_color, ViewerContext}; +use crate::ui::spaceview_controls::{ + HORIZONTAL_SCROLL_MODIFIER, SELECTION_RECT_ZOOM_BUTTON, ZOOM_SCROLL_MODIFIER, +}; + use super::SceneBarChart; // --- -pub(crate) const HELP_TEXT: &str = "\ - Pan by dragging, or scroll (+ shift = horizontal).\n\ - Box zooming: Right click to zoom in and zoom out using a selection.\n\ - Reset view with double-click."; +pub fn help_text(re_ui: &re_ui::ReUi) -> egui::WidgetText { + let mut layout = re_ui::LayoutJobBuilder::new(re_ui); + + layout.add("Pan by dragging, or scroll (+ "); + layout.add(HORIZONTAL_SCROLL_MODIFIER); + layout.add(" for horizontal).\n"); + + layout.add("Zoom with pinch gesture or scroll + "); + layout.add(ZOOM_SCROLL_MODIFIER); + layout.add(".\n"); + + layout.add("Drag "); + layout.add(SELECTION_RECT_ZOOM_BUTTON); + layout.add(" to zoom in/out using a selection.\n\n"); + + layout.add_button_text("double-click"); + layout.add(" to reset the view."); + + layout.layout_job.into() +} #[derive(Clone, Default, serde::Deserialize, serde::Serialize)] pub struct BarChartState; diff --git a/crates/re_viewer/src/ui/view_spatial/eye.rs b/crates/re_viewer/src/ui/view_spatial/eye.rs index 789849d36765..dca5fb6dcb2f 100644 --- a/crates/re_viewer/src/ui/view_spatial/eye.rs +++ b/crates/re_viewer/src/ui/view_spatial/eye.rs @@ -2,6 +2,11 @@ use egui::{lerp, NumExt as _, Rect}; use glam::Affine3A; use macaw::{vec3, IsoTransform, Mat4, Quat, Vec3}; +use crate::ui::spaceview_controls::{ + DRAG_PAN3D_BUTTON, ROLL_MOUSE, ROLL_MOUSE_ALT, ROLL_MOUSE_MODIFIER, ROTATE3D_BUTTON, + SLOW_DOWN_3D_MODIFIER, SPEED_UP_3D_MODIFIER, +}; + use super::SpaceCamera3D; /// An eye in a 3D view. @@ -299,18 +304,20 @@ impl OrbitEye { let mut did_interact = false; if response.drag_delta().length() > drag_threshold { - if response.dragged_by(egui::PointerButton::Middle) - || (response.dragged_by(egui::PointerButton::Primary) - && response.ctx.input(|i| i.modifiers.alt)) + if response.dragged_by(ROLL_MOUSE) + || (response.dragged_by(ROLL_MOUSE_ALT) + && response + .ctx + .input(|i| i.modifiers.contains(ROLL_MOUSE_MODIFIER))) { if let Some(pointer_pos) = response.ctx.pointer_latest_pos() { self.roll(&response.rect, pointer_pos, response.drag_delta()); did_interact = true; } - } else if response.dragged_by(egui::PointerButton::Primary) { + } else if response.dragged_by(ROTATE3D_BUTTON) { self.rotate(response.drag_delta()); did_interact = true; - } else if response.dragged_by(egui::PointerButton::Secondary) { + } else if response.dragged_by(DRAG_PAN3D_BUTTON) { self.translate(response.drag_delta()); did_interact = true; } @@ -379,8 +386,16 @@ impl OrbitEye { local_movement = local_movement.normalize_or_zero(); let speed = self.orbit_radius - * (if input.modifiers.shift { 10.0 } else { 1.0 }) - * (if input.modifiers.ctrl { 0.1 } else { 1.0 }); + * (if input.modifiers.contains(SPEED_UP_3D_MODIFIER) { + 10.0 + } else { + 1.0 + }) + * (if input.modifiers.contains(SLOW_DOWN_3D_MODIFIER) { + 0.1 + } else { + 1.0 + }); let world_movement = self.world_from_view_rot * (speed * local_movement); self.velocity = egui::lerp( diff --git a/crates/re_viewer/src/ui/view_spatial/ui.rs b/crates/re_viewer/src/ui/view_spatial/ui.rs index 88e68cbe3be0..08b6d0198952 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui.rs @@ -431,10 +431,10 @@ impl ViewSpatialState { } } - pub fn help_text(&self) -> &str { + pub fn help_text(&self, re_ui: &re_ui::ReUi) -> egui::WidgetText { match *self.nav_mode.get() { - SpatialNavigationMode::TwoD => super::ui_2d::HELP_TEXT_2D, - SpatialNavigationMode::ThreeD => super::ui_3d::HELP_TEXT_3D, + SpatialNavigationMode::TwoD => super::ui_2d::help_text(re_ui), + SpatialNavigationMode::ThreeD => super::ui_3d::help_text(re_ui), } } } diff --git a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs index 23379f5f4865..55cbc4b243a0 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs @@ -13,10 +13,13 @@ use super::{ }; use crate::{ misc::SpaceViewHighlights, - ui::view_spatial::{ - ui::outline_config, - ui_renderer_bridge::{fill_view_builder, ScreenBackground}, - SceneSpatial, + ui::{ + spaceview_controls::{DRAG_PAN2D_BUTTON, RESET_VIEW_BUTTON_TEXT, ZOOM_SCROLL_MODIFIER}, + view_spatial::{ + ui::outline_config, + ui_renderer_bridge::{fill_view_builder, ScreenBackground}, + SceneSpatial, + }, }, }; @@ -151,7 +154,7 @@ impl View2DState { } // If we are dragging, adjust the center accordingly - if response.dragged_by(egui::PointerButton::Primary) { + if response.dragged_by(DRAG_PAN2D_BUTTON) { // Adjust center based on drag center -= response.drag_delta() / scale; accepting_scroll = false; @@ -203,9 +206,21 @@ impl View2DState { } } -pub const HELP_TEXT_2D: &str = "Ctrl-scroll to zoom (⌘-scroll or Mac).\n\ - Drag to pan.\n\ - Double-click to reset the view."; +pub fn help_text(re_ui: &re_ui::ReUi) -> egui::WidgetText { + let mut layout = re_ui::LayoutJobBuilder::new(re_ui); + + layout.add(ZOOM_SCROLL_MODIFIER); + layout.add(" + scroll to zoom.\n"); + + layout.add("Click and drag with "); + layout.add(DRAG_PAN2D_BUTTON); + layout.add(" to pan.\n"); + + layout.add_button_text(RESET_VIEW_BUTTON_TEXT); + layout.add(" to reset the view."); + + layout.layout_job.into() +} /// Create the outer 2D view, which consists of a scrollable region /// TODO(andreas): Split into smaller parts, more re-use with `ui_3d` diff --git a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs index 6c4a391c77d9..17b429a474a5 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs @@ -13,10 +13,17 @@ use re_viewer_context::{gpu_bridge, HoveredSpace, Item, SpaceViewId, ViewerConte use crate::{ misc::SpaceViewHighlights, - ui::view_spatial::{ - ui::{create_labels, outline_config, picking, screenshot_context_menu}, - ui_renderer_bridge::{fill_view_builder, ScreenBackground}, - SceneSpatial, SpaceCamera3D, SpatialNavigationMode, + ui::{ + spaceview_controls::{ + DRAG_PAN3D_BUTTON, RESET_VIEW_BUTTON_TEXT, ROLL_MOUSE, ROLL_MOUSE_ALT, + ROLL_MOUSE_MODIFIER, ROTATE3D_BUTTON, SLOW_DOWN_3D_MODIFIER, SPEED_UP_3D_MODIFIER, + TRACKED_CAMERA_RESTORE_KEY, + }, + view_spatial::{ + ui::{create_labels, outline_config, picking, screenshot_context_menu}, + ui_renderer_bridge::{fill_view_builder, ScreenBackground}, + SceneSpatial, SpaceCamera3D, SpatialNavigationMode, + }, }, }; @@ -238,18 +245,49 @@ fn find_camera(space_cameras: &[SpaceCamera3D], needle: &EntityPath) -> Option egui::WidgetText { + let mut layout = re_ui::LayoutJobBuilder::new(re_ui); + + layout.add("Click and drag "); + layout.add(ROTATE3D_BUTTON); + layout.add(" to rotate.\n"); + + layout.add("Click and drag with "); + layout.add(DRAG_PAN3D_BUTTON); + layout.add(" to pan.\n"); + + layout.add("Drag with "); + layout.add(ROLL_MOUSE); + layout.add(" ( "); + layout.add(ROLL_MOUSE_ALT); + layout.add(" + holding "); + layout.add(ROLL_MOUSE_MODIFIER); + layout.add(" ) to roll the view.\n"); + + layout.add("Scroll or pinch to zoom.\n\n"); + + layout.add("While hovering the 3D view, navigate with "); + layout.add_button_text("WASD"); + layout.add(" and "); + layout.add_button_text("QE"); + layout.add("\n"); + + layout.add(SPEED_UP_3D_MODIFIER); + layout.add(" slows down, "); + layout.add(SLOW_DOWN_3D_MODIFIER); + layout.add(" speeds up\n\n"); + + layout.add_button_text("double-click"); + layout.add(" an object to focus the view on it.\n"); + layout.add("For cameras, you can restore the view again with "); + layout.add(TRACKED_CAMERA_RESTORE_KEY); + layout.add(" .\n\n"); + + layout.add_button_text(RESET_VIEW_BUTTON_TEXT); + layout.add(" on empty space to reset the view."); + + layout.layout_job.into() +} /// TODO(andreas): Split into smaller parts, more re-use with `ui_2d` #[allow(clippy::too_many_arguments)] @@ -397,7 +435,7 @@ pub fn view_3d( } // Allow to restore the camera state with escape if a camera was tracked before. - if response.hovered() && ui.input(|i| i.key_pressed(egui::Key::Escape)) { + if response.hovered() && ui.input(|i| i.key_pressed(TRACKED_CAMERA_RESTORE_KEY)) { if let Some(camera_before_changing_tracked_state) = state.state_3d.camera_before_tracked_camera { diff --git a/crates/re_viewer/src/ui/view_time_series/mod.rs b/crates/re_viewer/src/ui/view_time_series/mod.rs index 51606c3807b5..83dd3d6ceb16 100644 --- a/crates/re_viewer/src/ui/view_time_series/mod.rs +++ b/crates/re_viewer/src/ui/view_time_series/mod.rs @@ -2,4 +2,4 @@ mod scene; pub(crate) use self::scene::SceneTimeSeries; mod ui; -pub(crate) use self::ui::{view_time_series, ViewTimeSeriesState, HELP_TEXT}; +pub(crate) use self::ui::{help_text, view_time_series, ViewTimeSeriesState}; diff --git a/crates/re_viewer/src/ui/view_time_series/ui.rs b/crates/re_viewer/src/ui/view_time_series/ui.rs index 0b71877b2ac6..024af4a947e7 100644 --- a/crates/re_viewer/src/ui/view_time_series/ui.rs +++ b/crates/re_viewer/src/ui/view_time_series/ui.rs @@ -8,18 +8,42 @@ use re_viewer_context::ViewerContext; use super::SceneTimeSeries; use crate::{ - misc::format_time::next_grid_tick_magnitude_ns, ui::view_time_series::scene::PlotSeriesKind, + misc::format_time::next_grid_tick_magnitude_ns, + ui::{ + spaceview_controls::{ + HORIZONTAL_SCROLL_MODIFIER, MOVE_TIME_CURSOR_BUTTON, RESET_VIEW_BUTTON_TEXT, + SELECTION_RECT_ZOOM_BUTTON, ZOOM_SCROLL_MODIFIER, + }, + view_time_series::scene::PlotSeriesKind, + }, }; // --- -pub(crate) const HELP_TEXT: &str = "Pan by dragging, or scroll (+ shift = horizontal).\n\ - Box zooming: Right click to zoom in and zoom out using a selection.\n\ - Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture.\n\ - You can also zoom by dragging a rectangle with the right mouse button.\n\ - \n\ - Reset view with double-click.\n\ - Right click to move the time cursor to the current position."; +pub fn help_text(re_ui: &re_ui::ReUi) -> egui::WidgetText { + let mut layout = re_ui::LayoutJobBuilder::new(re_ui); + + layout.add("Pan by dragging, or scroll (+ "); + layout.add(HORIZONTAL_SCROLL_MODIFIER); + layout.add(" for horizontal).\n"); + + layout.add("Zoom with pinch gesture or scroll + "); + layout.add(ZOOM_SCROLL_MODIFIER); + layout.add(".\n"); + + layout.add("Drag "); + layout.add(SELECTION_RECT_ZOOM_BUTTON); + layout.add(" to zoom in/out using a selection.\n"); + + layout.add("Click "); + layout.add(MOVE_TIME_CURSOR_BUTTON); + layout.add(" to move the time cursor.\n\n"); + + layout.add_button_text(RESET_VIEW_BUTTON_TEXT); + layout.add(" to reset the view."); + + layout.layout_job.into() +} #[derive(Clone, Default, serde::Deserialize, serde::Serialize)] pub struct ViewTimeSeriesState; diff --git a/crates/re_viewer/src/ui/viewport.rs b/crates/re_viewer/src/ui/viewport.rs index 0b488536c189..62e3f174c1a0 100644 --- a/crates/re_viewer/src/ui/viewport.rs +++ b/crates/re_viewer/src/ui/viewport.rs @@ -721,7 +721,7 @@ impl<'a, 'b> egui_tiles::Behavior for TabViewer<'a, 'b> { } // Show help last, since not all space views have help text - help_text_ui(ui, space_view); + help_text_ui(ui, self.ctx.re_ui, space_view); } // Styling: @@ -749,12 +749,12 @@ impl<'a, 'b> egui_tiles::Behavior for TabViewer<'a, 'b> { } } -fn help_text_ui(ui: &mut egui::Ui, space_view: &SpaceView) { +fn help_text_ui(ui: &mut egui::Ui, re_ui: &re_ui::ReUi, space_view: &SpaceView) { let help_text = match space_view.category { - ViewCategory::TimeSeries => Some(crate::ui::view_time_series::HELP_TEXT), - ViewCategory::BarChart => Some(crate::ui::view_bar_chart::HELP_TEXT), - ViewCategory::Spatial => Some(space_view.view_state.state_spatial.help_text()), - ViewCategory::Text | ViewCategory::TextBox | ViewCategory::Tensor => None, + ViewCategory::TimeSeries => Some(crate::ui::view_time_series::help_text(re_ui)), + ViewCategory::BarChart => Some(crate::ui::view_bar_chart::help_text(re_ui)), + ViewCategory::Spatial => Some(space_view.view_state.state_spatial.help_text(re_ui)), + ViewCategory::TextBox | ViewCategory::Text | ViewCategory::Tensor => None, }; if let Some(help_text) = help_text {