diff --git a/CHANGELOG.md b/CHANGELOG.md index dc9db95123f5..057395ff1b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ A rough time-line of major user-facing things added, removed and changed. Newest on top. +* 2022-10-28: Add buttons to collapse/expand the side panels ([#238](https://github.com/rerun-io/rerun/pull/238)). * 2022-10-26: Replace old `space=…` and `set_space_up` code with new space transform hierarchy. See [`USAGE.md`](rerun_py/USAGE.md) for details ([#224](https://github.com/rerun-io/rerun/pull/224)). * 2022-10-19: Add support for SegmentationMaps ([#187](https://github.com/rerun-io/rerun/pull/187)). * 2022-10-14: Add support for logging 3D Arrows ([#199](https://github.com/rerun-io/rerun/pull/199)). diff --git a/Cargo.lock b/Cargo.lock index 61e6f48ac511..0983b611f0cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1021,7 +1021,7 @@ dependencies = [ [[package]] name = "eframe" version = "0.19.0" -source = "git+https://github.com/emilk/egui?rev=3d36a20376a8479647557d172e1abc82abbff135#3d36a20376a8479647557d172e1abc82abbff135" +source = "git+https://github.com/emilk/egui?rev=f7a15a34f9bd17f39ae75c488b722873465c8d86#f7a15a34f9bd17f39ae75c488b722873465c8d86" dependencies = [ "bytemuck", "directories-next", @@ -1048,7 +1048,7 @@ dependencies = [ [[package]] name = "egui" version = "0.19.0" -source = "git+https://github.com/emilk/egui?rev=3d36a20376a8479647557d172e1abc82abbff135#3d36a20376a8479647557d172e1abc82abbff135" +source = "git+https://github.com/emilk/egui?rev=f7a15a34f9bd17f39ae75c488b722873465c8d86#f7a15a34f9bd17f39ae75c488b722873465c8d86" dependencies = [ "ahash 0.8.0", "epaint", @@ -1070,7 +1070,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.19.0" -source = "git+https://github.com/emilk/egui?rev=3d36a20376a8479647557d172e1abc82abbff135#3d36a20376a8479647557d172e1abc82abbff135" +source = "git+https://github.com/emilk/egui?rev=f7a15a34f9bd17f39ae75c488b722873465c8d86#f7a15a34f9bd17f39ae75c488b722873465c8d86" dependencies = [ "bytemuck", "egui", @@ -1084,7 +1084,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.19.0" -source = "git+https://github.com/emilk/egui?rev=3d36a20376a8479647557d172e1abc82abbff135#3d36a20376a8479647557d172e1abc82abbff135" +source = "git+https://github.com/emilk/egui?rev=f7a15a34f9bd17f39ae75c488b722873465c8d86#f7a15a34f9bd17f39ae75c488b722873465c8d86" dependencies = [ "arboard", "egui", @@ -1109,7 +1109,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.19.0" -source = "git+https://github.com/emilk/egui?rev=3d36a20376a8479647557d172e1abc82abbff135#3d36a20376a8479647557d172e1abc82abbff135" +source = "git+https://github.com/emilk/egui?rev=f7a15a34f9bd17f39ae75c488b722873465c8d86#f7a15a34f9bd17f39ae75c488b722873465c8d86" dependencies = [ "egui", "tracing", @@ -1118,7 +1118,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.19.0" -source = "git+https://github.com/emilk/egui?rev=3d36a20376a8479647557d172e1abc82abbff135#3d36a20376a8479647557d172e1abc82abbff135" +source = "git+https://github.com/emilk/egui?rev=f7a15a34f9bd17f39ae75c488b722873465c8d86#f7a15a34f9bd17f39ae75c488b722873465c8d86" dependencies = [ "bytemuck", "egui", @@ -1140,7 +1140,7 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "emath" version = "0.19.0" -source = "git+https://github.com/emilk/egui?rev=3d36a20376a8479647557d172e1abc82abbff135#3d36a20376a8479647557d172e1abc82abbff135" +source = "git+https://github.com/emilk/egui?rev=f7a15a34f9bd17f39ae75c488b722873465c8d86#f7a15a34f9bd17f39ae75c488b722873465c8d86" dependencies = [ "bytemuck", "serde", @@ -1162,7 +1162,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.19.0" -source = "git+https://github.com/emilk/egui?rev=3d36a20376a8479647557d172e1abc82abbff135#3d36a20376a8479647557d172e1abc82abbff135" +source = "git+https://github.com/emilk/egui?rev=f7a15a34f9bd17f39ae75c488b722873465c8d86#f7a15a34f9bd17f39ae75c488b722873465c8d86" dependencies = [ "ab_glyph", "ahash 0.8.0", diff --git a/Cargo.toml b/Cargo.toml index c864603defd2..d3ba903ccc91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,17 @@ opt-level = 2 debug = true [patch.crates-io] -# 2022-10-16 - table: fix deadlocks caused by lock fairness -eframe = { git = "https://github.com/emilk/egui", rev = "3d36a20376a8479647557d172e1abc82abbff135" } -egui = { git = "https://github.com/emilk/egui", rev = "3d36a20376a8479647557d172e1abc82abbff135" } -egui_extras = { git = "https://github.com/emilk/egui", rev = "3d36a20376a8479647557d172e1abc82abbff135" } -egui_glow = { git = "https://github.com/emilk/egui", rev = "3d36a20376a8479647557d172e1abc82abbff135" } -egui-wgpu = { git = "https://github.com/emilk/egui", rev = "3d36a20376a8479647557d172e1abc82abbff135" } +# 2022-10-28 - panel toggle animation +eframe = { git = "https://github.com/emilk/egui", rev = "f7a15a34f9bd17f39ae75c488b722873465c8d86" } +egui = { git = "https://github.com/emilk/egui", rev = "f7a15a34f9bd17f39ae75c488b722873465c8d86" } +egui_extras = { git = "https://github.com/emilk/egui", rev = "f7a15a34f9bd17f39ae75c488b722873465c8d86" } +egui_glow = { git = "https://github.com/emilk/egui", rev = "f7a15a34f9bd17f39ae75c488b722873465c8d86" } +egui-wgpu = { git = "https://github.com/emilk/egui", rev = "f7a15a34f9bd17f39ae75c488b722873465c8d86" } +# eframe = { path = "../../egui/crates/eframe" } +# egui = { path = "../../egui/crates/egui" } +# egui_extras = { path = "../../egui/crates/egui_extras" } +# egui_glow = { path = "../../egui/crates/egui_glow" } +# egui-wgpu = { path = "../../egui/crates/egui-wgpu" } # Because gltf hasn't published a new version: https://github.com/gltf-rs/gltf/issues/357 gltf = { git = "https://github.com/rerun-io/gltf", rev = "3c14ded73755d1ce9e47010edb06db63cb7e2cca" } diff --git a/crates/re_viewer/src/app.rs b/crates/re_viewer/src/app.rs index 96d626ae807b..98abe6fb1424 100644 --- a/crates/re_viewer/src/app.rs +++ b/crates/re_viewer/src/app.rs @@ -200,6 +200,21 @@ impl eframe::App for App { return; } + if egui_ctx.input_mut().consume_key( + egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, + egui::Key::R, + ) { + self.reset(egui_ctx); + } + + #[cfg(all(feature = "puffin", not(target_arch = "wasm32")))] + if egui_ctx.input_mut().consume_key( + egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, + egui::Key::P, + ) { + self.state.profiler.start(); + } + self.state.cache.new_frame(); if let Some(rx) = &mut self.rx { @@ -257,7 +272,7 @@ impl eframe::App for App { }); }); } else { - self.state.show(egui_ctx, log_db); + self.state.show(egui_ctx, log_db, &self.design_tokens); } self.handle_dropping_files(egui_ctx); @@ -277,6 +292,20 @@ impl eframe::App for App { } impl App { + /// Reset the viewer to how it looked the first time you ran it. + fn reset(&mut self, egui_ctx: &egui::Context) { + self.state = Default::default(); + + // Keep dark/light mode setting: + let is_dark_mode = egui_ctx.style().visuals.dark_mode; + *egui_ctx.memory() = Default::default(); + egui_ctx.set_visuals(if is_dark_mode { + egui::Visuals::dark() + } else { + egui::Visuals::light() + }); + } + fn log_db(&mut self) -> &mut LogDb { self.log_dbs.entry(self.state.selected_rec_id).or_default() } @@ -388,7 +417,7 @@ struct AppState { } impl AppState { - fn show(&mut self, egui_ctx: &egui::Context, log_db: &LogDb) { + fn show(&mut self, egui_ctx: &egui::Context, log_db: &LogDb, design_tokens: &DesignTokens) { crate::profile_function!(); let Self { @@ -413,21 +442,12 @@ impl AppState { cache, log_db, rec_cfg, + design_tokens, }; - if ctx.rec_cfg.selection.is_some() { - egui::SidePanel::right("selection_view").show(egui_ctx, |ui| { - let blueprint = blueprints.entry(*selected_recording_id).or_default(); - selection_panel.ui(&mut ctx, blueprint, ui); - }); - } - - egui::TopBottomPanel::bottom("time_panel") - .resizable(true) - .default_height(210.0) - .show(egui_ctx, |ui| { - time_panel.ui(&mut ctx, ui); - }); + let blueprint = blueprints.entry(*selected_recording_id).or_default(); + selection_panel.show_panel(&mut ctx, blueprint, egui_ctx); + time_panel.show_panel(&mut ctx, blueprint, egui_ctx); let central_panel_frame = egui::Frame { fill: egui_ctx.style().visuals.window_fill(), @@ -689,27 +709,19 @@ fn file_menu(ui: &mut egui::Ui, app: &mut App, _frame: &mut eframe::Frame) { ui.menu_button("Advanced", |ui| { if ui .button("Reset viewer") - .on_hover_text("Reset the viewer to how it looked the first time you ran it.") + .on_hover_text( + "Reset the viewer to how it looked the first time you ran it (⌘⇧R or ⌃⇧R)", + ) .clicked() { - app.state = Default::default(); - - // Keep dark/light mode setting: - let is_dark_mode = ui.ctx().style().visuals.dark_mode; - *ui.ctx().memory() = Default::default(); - ui.ctx().set_visuals(if is_dark_mode { - egui::Visuals::dark() - } else { - egui::Visuals::light() - }); - + app.reset(ui.ctx()); ui.close_menu(); } #[cfg(all(feature = "puffin", not(target_arch = "wasm32")))] if ui .button("Profile viewer") - .on_hover_text("Starts a profiler, showing what makes the viewer run slow") + .on_hover_text("Starts a profiler, showing what makes the viewer run slow (⌘⇧P or ⌃⇧P)") .clicked() { app.state.profiler.start(); diff --git a/crates/re_viewer/src/design_tokens.rs b/crates/re_viewer/src/design_tokens.rs index 17ade96085e5..dc143734e499 100644 --- a/crates/re_viewer/src/design_tokens.rs +++ b/crates/re_viewer/src/design_tokens.rs @@ -7,6 +7,18 @@ pub struct DesignTokens { pub top_bar_color: egui::Color32, } +impl DesignTokens { + #[allow(clippy::unused_self)] + pub fn panel_frame(&self, egui_ctx: &egui::Context) -> egui::Frame { + egui::Frame { + fill: egui_ctx.style().visuals.window_fill(), + inner_margin: egui::style::Margin::same(4.0), + stroke: egui_ctx.style().visuals.window_stroke(), + ..Default::default() + } + } +} + pub(crate) fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens { let apply_font = true; let apply_font_size = true; diff --git a/crates/re_viewer/src/misc/viewer_context.rs b/crates/re_viewer/src/misc/viewer_context.rs index 8f8aea666223..a404caece538 100644 --- a/crates/re_viewer/src/misc/viewer_context.rs +++ b/crates/re_viewer/src/misc/viewer_context.rs @@ -17,6 +17,9 @@ pub(crate) struct ViewerContext<'a> { /// UI config for the current recording (found in [`LogDb`]). pub rec_cfg: &'a mut RecordingConfig, + + /// The look and feel of the UI + pub design_tokens: &'a crate::design_tokens::DesignTokens, } impl<'a> ViewerContext<'a> { diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index 20c328ef7da1..13bf13402cea 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -12,21 +12,66 @@ pub(crate) struct SelectionPanel {} impl SelectionPanel { #[allow(clippy::unused_self)] - pub fn ui( + pub fn show_panel( &mut self, ctx: &mut ViewerContext<'_>, blueprint: &mut Blueprint, - ui: &mut egui::Ui, + egui_ctx: &egui::Context, ) { - crate::profile_function!(); + blueprint.selection_panel_expanded ^= egui_ctx.input_mut().consume_key( + egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, + egui::Key::S, + ); + + let panel_frame = ctx.design_tokens.panel_frame(egui_ctx); + + let collapsed = egui::SidePanel::right("selection_view_collapsed") + .resizable(false) + .frame(panel_frame) + .default_width(16.0); + let expanded = egui::SidePanel::right("selection_view_expanded") + .resizable(true) + .frame(panel_frame); + + egui::SidePanel::show_animated_between( + egui_ctx, + blueprint.selection_panel_expanded, + collapsed, + expanded, + |ui: &mut egui::Ui, expansion: f32| { + if expansion < 1.0 { + // Collapsed, or animating: + if ui + .small_button("⏴") + .on_hover_text("Expand Selection View (⌘⇧S or ⌃⇧S)") + .clicked() + { + blueprint.selection_panel_expanded = true; + } + } else { + // Expanded: + if ui + .small_button("⏵") + .on_hover_text("Collapse Selection View (⌘⇧S or ⌃⇧S)") + .clicked() + { + blueprint.selection_panel_expanded = false; + } - ui.horizontal(|ui| { - ui.heading("Selection"); + self.contents(ctx, blueprint, ui); + } + }, + ); + } - if ctx.rec_cfg.selection.is_some() && ui.small_button("Deselect").clicked() { - ctx.rec_cfg.selection = Selection::None; - } - }); + #[allow(clippy::unused_self)] + fn contents( + &mut self, + ctx: &mut ViewerContext<'_>, + blueprint: &mut Blueprint, + ui: &mut egui::Ui, + ) { + crate::profile_function!(); ui.separator(); diff --git a/crates/re_viewer/src/ui/time_panel.rs b/crates/re_viewer/src/ui/time_panel.rs index 45c175d25197..c7173096b7b3 100644 --- a/crates/re_viewer/src/ui/time_panel.rs +++ b/crates/re_viewer/src/ui/time_panel.rs @@ -13,6 +13,8 @@ use crate::{ TimeRange, TimeRangeF, TimeReal, TimeView, ViewerContext, }; +use super::Blueprint; + /// A panel that shows objects to the left, time on the top. /// /// This includes the timeline controls and streams view. @@ -42,11 +44,56 @@ impl Default for TimePanel { } impl TimePanel { - pub fn ui(&mut self, ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) { + pub fn show_panel( + &mut self, + ctx: &mut ViewerContext<'_>, + blueprint: &mut Blueprint, + egui_ctx: &egui::Context, + ) { + blueprint.time_panel_expanded ^= egui_ctx.input_mut().consume_key( + egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, + egui::Key::T, + ); + + let panel_frame = ctx.design_tokens.panel_frame(egui_ctx); + + let collapsed = egui::TopBottomPanel::bottom("time_panel_collapsed") + .resizable(false) + .frame(panel_frame) + .default_height(16.0); + let expanded = egui::TopBottomPanel::bottom("time_panel_expanded") + .resizable(true) + .frame(panel_frame) + .default_height(250.0); + + egui::TopBottomPanel::show_animated_between( + egui_ctx, + blueprint.time_panel_expanded, + collapsed, + expanded, + |ui: &mut egui::Ui, expansion: f32| { + if expansion < 1.0 { + // Collapsed, or animating: + if ui + .small_button("⏶") + .on_hover_text("Expand Timeline View (⌘⇧T or ⌃⇧T)") + .clicked() + { + blueprint.time_panel_expanded = true; + } + } else { + // Expanded: + self.ui(ctx, blueprint, ui); + } + }, + ); + } + + fn ui(&mut self, ctx: &mut ViewerContext<'_>, blueprint: &mut Blueprint, ui: &mut egui::Ui) { crate::profile_function!(); // play control and current time - top_row_ui(ctx, ui); + top_row_ui(ctx, blueprint, ui); self.next_col_right = ui.min_rect().left(); // this will expand during the call @@ -359,8 +406,18 @@ impl TimePanel { } } -fn top_row_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) { +fn top_row_ui(ctx: &mut ViewerContext<'_>, blueprint: &mut Blueprint, ui: &mut egui::Ui) { ui.horizontal(|ui| { + if ui + .small_button("⏷") + .on_hover_text("Collapse Timeline View (⌘⇧T or ⌃⇧T)") + .clicked() + { + blueprint.time_panel_expanded = false; + } + + ui.separator(); + ctx.rec_cfg .time_ctrl .timeline_selector_ui(&ctx.log_db.time_points, ui); diff --git a/crates/re_viewer/src/ui/viewport.rs b/crates/re_viewer/src/ui/viewport.rs index 7d91059c1ee3..a3373ce594ab 100644 --- a/crates/re_viewer/src/ui/viewport.rs +++ b/crates/re_viewer/src/ui/viewport.rs @@ -510,11 +510,27 @@ fn unknown_space_label(ui: &mut egui::Ui, space_path: &ObjPath) -> egui::Respons // ---------------------------------------------------------------------------- /// Defines the layout of the whole Viewer (or will, eventually). -#[derive(Default, serde::Deserialize, serde::Serialize)] +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(default)] pub(crate) struct Blueprint { + pub blueprint_panel_expanded: bool, + pub selection_panel_expanded: bool, + pub time_panel_expanded: bool, + pub viewport: ViewportBlueprint, } +impl Default for Blueprint { + fn default() -> Self { + Self { + blueprint_panel_expanded: true, + selection_panel_expanded: true, + time_panel_expanded: true, + viewport: Default::default(), + } + } +} + impl Blueprint { pub fn blueprint_panel_and_viewport(&mut self, ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) { crate::profile_function!(); @@ -543,29 +559,70 @@ impl Blueprint { ui: &mut egui::Ui, spaces_info: &SpacesInfo, ) { - let side_panel_frame = egui::Frame { - fill: ui.style().visuals.window_fill(), - inner_margin: egui::style::Margin::same(4.0), - stroke: ui.style().visuals.window_stroke(), - ..Default::default() - }; + self.blueprint_panel_expanded ^= ui.input_mut().consume_key( + egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, + egui::Key::B, + ); + + let panel_frame = ctx.design_tokens.panel_frame(ui.ctx()); + + let collapsed_panel = egui::SidePanel::left("blueprint_panel_collapsed") + .resizable(false) + .frame(panel_frame) + .default_width(16.0); - egui::SidePanel::left("blueprint_panel") + let expanded_panel = egui::SidePanel::left("blueprint_panel_expanded") .resizable(true) - .frame(side_panel_frame) - .default_width(200.0) - .show_inside(ui, |ui| { - ui.vertical_centered(|ui| { - if ui.button("Reset space views").clicked() { - self.viewport = ViewportBlueprint::new(&ctx.log_db.obj_db, spaces_info); + .frame(panel_frame) + .min_width(120.0) + .default_width(200.0); + + egui::SidePanel::show_animated_between_inside( + ui, + self.blueprint_panel_expanded, + collapsed_panel, + expanded_panel, + |ui: &mut egui::Ui, expansion: f32| { + if expansion < 1.0 { + // Collapsed, or animating: + if ui + .small_button("⏵") + .on_hover_text("Expand Blueprint View (⌘⇧B or ⌃⇧B)") + .clicked() + { + self.blueprint_panel_expanded = true; } - }); + } else { + // Expanded: + ui.horizontal(|ui| { + if ui + .small_button("⏴") + .on_hover_text("Collapse Blueprint View (⌘⇧B or ⌃⇧B)") + .clicked() + { + self.blueprint_panel_expanded = false; + } - ui.separator(); + ui.vertical_centered(|ui| { + ui.label("Blueprint"); + }); + }); - self.viewport - .tree_ui(ctx, ui, spaces_info, &ctx.log_db.obj_db.tree); - }); + ui.separator(); + + ui.vertical_centered(|ui| { + if ui.button("Reset space views").clicked() { + self.viewport = ViewportBlueprint::new(&ctx.log_db.obj_db, spaces_info); + } + }); + + ui.separator(); + + self.viewport + .tree_ui(ctx, ui, spaces_info, &ctx.log_db.obj_db.tree); + } + }, + ); } }