Skip to content

Commit

Permalink
Add Recordings section to the left panel (#2938)
Browse files Browse the repository at this point in the history
### What

Add Recordings section to the left panel.

Fixes #2298

Also:
- [x] created a brand new `ListItem` widget to be used across the UI
  - full-span highlight
  - icon support (`Icon` or closure)
  - hover button support
- [x] created a proper "panel_title_bar" widget in ReUi
- [x] removed the recordings submenu from the rerun menu
- [x] updated re_ui_example.rs to illustrate the "proper" panel
hierarchy and demo the new widgets
- [x] `+` button in title to open new recording

Deferred to follow-up PRs:
- hierarchical display
- minus hover button to close the recording

<img width="1412" alt="image"
src="https://github.com/rerun-io/rerun/assets/49431240/dd761936-bd5d-4161-b7c2-353652cf1897">


### 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 [demo.rerun.io](https://demo.rerun.io/pr/2938) (if
applicable)~~ best tested on a native build and throwing lots of example
at it, e.g. `for run in {1..4}; do python examples/python/dna/main.py
--connect; done`

- [PR Build Summary](https://build.rerun.io/pr/2938)
- [Docs
preview](https://rerun.io/preview/pr%3Aantoine%2Fnew-recording-ui-2298/docs)
- [Examples
preview](https://rerun.io/preview/pr%3Aantoine%2Fnew-recording-ui-2298/examples)
  • Loading branch information
abey79 authored Aug 11, 2023
1 parent be236ce commit 4f2fe7d
Show file tree
Hide file tree
Showing 13 changed files with 558 additions and 155 deletions.
94 changes: 82 additions & 12 deletions crates/re_ui/examples/re_ui_example.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use egui::Widget;
use re_ui::{toasts, CommandPalette, UICommand, UICommandSender};

/// Sender that queues up the execution of a command.
Expand Down Expand Up @@ -73,6 +74,8 @@ pub struct ExampleApp {
right_panel: bool,
bottom_panel: bool,

selected_list_item: Option<usize>,

dummy_bool: bool,

cmd_palette: CommandPalette,
Expand Down Expand Up @@ -103,6 +106,8 @@ impl ExampleApp {
right_panel: true,
bottom_panel: true,

selected_list_item: None,

dummy_bool: true,

cmd_palette: CommandPalette::default(),
Expand Down Expand Up @@ -219,29 +224,94 @@ impl eframe::App for ExampleApp {
});
});

// RIGHT PANEL
//
// This is the "idiomatic" panel structure for Rerun:
// - A top-level `SidePanel` without inner margins and which sets the clip rectangle.
// - Every piece of content (title bar, lists, etc.) are wrapped in a `Frame` with inner
// margins set to `ReUi::panel_margins()`. That can be done with `ReUi::panel_content()`.
// - If/when a scroll area is used, it must be applied without margin and outside of the
// `Frame`.
//
// This way, the content (titles, etc.) is properly inset and benefits from a properly set
// clip rectangle for full-span behaviour, without interference from the scroll areas.

let panel_frame = egui::Frame {
fill: egui_ctx.style().visuals.panel_fill,
inner_margin: re_ui::ReUi::view_padding().into(),
..Default::default()
};

egui::SidePanel::right("right_panel")
.frame(panel_frame)
.show_animated(egui_ctx, self.right_panel, |ui| {
// TODO(ab): use the proper `egui::Rect` function when egui 0.23 is released
let clip_rect = egui::Rect::from_min_max(
ui.max_rect().min - panel_frame.inner_margin.left_top(),
ui.max_rect().max + panel_frame.inner_margin.right_bottom(),
);
ui.set_clip_rect(clip_rect);

ui.strong("Right panel");
selection_buttons(ui);
self.re_ui
.large_collapsing_header(ui, "Large Collapsing Header", true, |ui| {
ui.set_clip_rect(ui.max_rect());

// first section - no scroll area, so a single outer "panel_content" can be used.
self.re_ui.panel_content(ui, |re_ui, ui| {
re_ui.panel_title_bar(
ui,
"Right panel",
Some("This is the title of the right panel"),
);
re_ui.large_collapsing_header(ui, "Large Collapsing Header", true, |ui| {
ui.label("Some data here");
ui.label("Some data there");

selection_buttons(ui);
});
});

// Second section. It's a list of `list_items`, so we need to remove the default
// spacing. Also, it uses a scroll area, so we must use several "panel_content".
ui.scope(|ui| {
ui.spacing_mut().item_spacing.y = 0.0;

self.re_ui.panel_content(ui, |re_ui, ui| {
re_ui.panel_title_bar(ui, "Another section", None);
});

egui::ScrollArea::both()
.id_source("example_right_panel")
.auto_shrink([false, true])
.show(ui, |ui| {
self.re_ui.panel_content(ui, |re_ui, ui| {
for i in 0..10 {
let label = if i == 4 {
"That's one heck of a loooooooong label!".to_owned()
} else {
format!("Some item {i}")
};

let mut item = re_ui
.list_item(label)
.selected(Some(i) == self.selected_list_item)
.active(i != 3)
.with_buttons(|re_ui, ui| {
re_ui.small_icon_button(ui, &re_ui::icons::ADD)
| re_ui.small_icon_button(ui, &re_ui::icons::REMOVE)
});

// demo custom icon
item = if i == 6 {
item.with_icon_fn(|_re_ui, ui, rect, visuals| {
ui.painter().circle(
rect.center(),
rect.width() / 2.0,
visuals.fg_stroke.color,
egui::Stroke::NONE,
);
})
} else {
item.with_icon(&re_ui::icons::SPACE_VIEW_TEXT)
};

if item.ui(ui).clicked() {
self.selected_list_item = Some(i);
}
}
});
});
});
});

egui::CentralPanel::default()
Expand Down
8 changes: 8 additions & 0 deletions crates/re_ui/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ impl UICommand {
Default::default()
}
}

pub fn tooltip_with_shortcut(self, egui_ctx: &egui::Context) -> String {
format!(
"{}{}",
self.tooltip(),
self.format_shortcut_tooltip_suffix(egui_ctx)
)
}
}

#[test]
Expand Down
83 changes: 82 additions & 1 deletion crates/re_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod design_tokens;
pub mod egui_helpers;
pub mod icons;
mod layout_job_builder;
pub mod list_item;
mod static_image_cache;
pub mod toasts;
mod toggle_switch;
Expand Down Expand Up @@ -50,6 +51,7 @@ use std::{ops::RangeInclusive, sync::Arc};

use parking_lot::Mutex;

use crate::list_item::ListItem;
use egui::{pos2, Align2, Color32, Mesh, NumExt, Rect, Shape, Vec2};

#[derive(Clone)]
Expand Down Expand Up @@ -91,6 +93,10 @@ impl ReUi {
12.0
}

pub fn panel_margin() -> egui::Margin {
egui::Margin::symmetric(Self::view_padding(), 0.0)
}

pub fn window_rounding() -> f32 {
12.0
}
Expand Down Expand Up @@ -130,6 +136,10 @@ impl ReUi {
28.0 // from figma 2022-02-03
}

pub fn list_item_height() -> f32 {
24.0
}

pub fn native_window_rounding() -> f32 {
10.0
}
Expand Down Expand Up @@ -444,6 +454,72 @@ impl ReUi {
response
}

pub fn panel_content<R>(
&self,
ui: &mut egui::Ui,
add_contents: impl FnOnce(&ReUi, &mut egui::Ui) -> R,
) -> R {
egui::Frame {
inner_margin: Self::panel_margin(),
..Default::default()
}
.show(ui, |ui| add_contents(self, ui))
.inner
}

/// Static title bar used to separate panels into section.
///
/// This title bar is meant to be used in a panel with proper inner margin and clip rectangle
/// set.
///
/// Use [`ReUi::panel_title_bar_with_buttons`] to display buttons in the title bar.
pub fn panel_title_bar(&self, ui: &mut egui::Ui, label: &str, hover_text: Option<&str>) {
self.panel_title_bar_with_buttons(ui, label, hover_text, |_ui| {});
}

/// Static title bar used to separate panels into section with custom buttons when hovered.
///
/// This title bar is meant to be used in a panel with proper inner margin and clip rectangle
/// set.
#[allow(clippy::unused_self)]
pub fn panel_title_bar_with_buttons<R>(
&self,
ui: &mut egui::Ui,
label: &str,
hover_text: Option<&str>,
add_right_buttons: impl FnOnce(&mut egui::Ui) -> R,
) -> R {
ui.allocate_ui_with_layout(
egui::vec2(ui.available_width(), Self::title_bar_height()),
egui::Layout::left_to_right(egui::Align::Center),
|ui| {
// draw horizontal separator lines
let mut rect = ui.available_rect_before_wrap();
let hline_stroke = ui.style().visuals.widgets.noninteractive.bg_stroke;
rect.extend_with_x(ui.clip_rect().right());
rect.extend_with_x(ui.clip_rect().left());
ui.painter().hline(rect.x_range(), rect.top(), hline_stroke);
ui.painter()
.hline(rect.x_range(), rect.bottom(), hline_stroke);

// draw label
let resp = ui.strong(label);
if let Some(hover_text) = hover_text {
resp.on_hover_text(hover_text);
}

// draw hover buttons
ui.allocate_ui_with_layout(
ui.available_size(),
egui::Layout::right_to_left(egui::Align::Center),
add_right_buttons,
)
.inner
},
)
.inner
}

/// Show a prominent collapsing header to be used as section delimitation in side panels.
///
/// Note that a clip rect must be set (typically by the panel) to avoid any overdraw.
Expand Down Expand Up @@ -592,6 +668,11 @@ impl ReUi {
ui.painter().add(shadow);
}

/// Convenience function to create a [`ListItem`] with the given text.
pub fn list_item(&self, text: impl Into<egui::WidgetText>) -> ListItem<'_> {
ListItem::new(self, text)
}

pub fn selectable_label_with_icon(
&self,
ui: &mut egui::Ui,
Expand Down Expand Up @@ -642,7 +723,7 @@ impl ReUi {
let image_rect = egui::Rect::from_min_size(
ui.painter().round_pos_to_pixels(egui::pos2(
rect.min.x.ceil(),
((rect.min.y + rect.max.y - Self::small_icon_size().y) * 0.5).ceil(),
(rect.center().y - 0.5 * ReUi::small_icon_size().y).ceil(),
)),
Self::small_icon_size(),
);
Expand Down
Loading

0 comments on commit 4f2fe7d

Please sign in to comment.