Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ListItem in blueprint tree UI #3118

Merged
merged 8 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions crates/re_ui/examples/re_ui_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,20 @@ impl eframe::App for ExampleApp {
self.re_ui
.list_item("Collapsing list item with icon")
.with_icon(&re_ui::icons::SPACE_VIEW_2D)
.show_collapsing(ui, true, |_re_ui, ui| {
self.re_ui.list_item("Sub-item").show(ui);
self.re_ui.list_item("Sub-item").show(ui);
self.re_ui
.list_item("Sub-item with icon")
.with_icon(&re_ui::icons::SPACE_VIEW_TEXT)
.show(ui);
self.re_ui.list_item("Sub-item").show(ui);
});
.show_collapsing(
ui,
"collapsing example".into(),
true,
|_re_ui, ui| {
self.re_ui.list_item("Sub-item").show(ui);
self.re_ui.list_item("Sub-item").show(ui);
self.re_ui
.list_item("Sub-item with icon")
.with_icon(&re_ui::icons::SPACE_VIEW_TEXT)
.show(ui);
self.re_ui.list_item("Sub-item").show(ui);
},
);
});
});
});
Expand Down
109 changes: 85 additions & 24 deletions crates/re_ui/src/list_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ struct ListItemResponse {
collapse_response: Option<Response>,
}

/// Responses returned by [`ListItem::show_collapsing`].
pub struct ShowCollapsingResponse<R> {
/// Response from the item itself.
pub item_response: Response,

/// Response from the body, if it was displayed.
pub body_response: Option<R>,
}

/// Generic widget for use in lists.
///
/// Layout:
Expand Down Expand Up @@ -45,6 +54,8 @@ pub struct ListItem<'a> {
re_ui: &'a ReUi,
active: bool,
selected: bool,
subdued: bool,
override_hover: bool,
collapse_openness: Option<f32>,
icon_fn:
Option<Box<dyn FnOnce(&ReUi, &mut egui::Ui, egui::Rect, egui::style::WidgetVisuals) + 'a>>,
Expand All @@ -59,6 +70,8 @@ impl<'a> ListItem<'a> {
re_ui,
active: true,
selected: false,
subdued: false,
override_hover: false,
collapse_openness: None,
icon_fn: None,
buttons_fn: None,
Expand All @@ -77,6 +90,26 @@ impl<'a> ListItem<'a> {
self
}

/// Set the subdued state of the item.
// TODO(ab): this is a hack to implement the behavior of the blueprint tree UI, where active
// widget are displayed in a subdued state (container, hidden space views/entities). One
// slightly more correct way would be to override the color using a (color, index) pair
// related to the design system table.
pub fn subdued(mut self, subdued: bool) -> Self {
self.subdued = subdued;
self
}

/// Override the hovered state even if the item is not actually hovered.
///
/// Used to highlight items representing things that are hovered elsewhere in the UI. Note that
/// the [`egui::Response`] returned by [`Self::show`] and ]`Self::show_collapsing`] will still
/// reflect the actual hover state.
pub fn override_hover(mut self, override_hover: bool) -> Self {
self.override_hover = override_hover;
abey79 marked this conversation as resolved.
Show resolved Hide resolved
self
}

/// Provide an [`Icon`] to be displayed on the left of the item.
pub fn with_icon(self, icon: &'a Icon) -> Self {
self.with_icon_fn(|re_ui, ui, rect, visuals| {
Expand Down Expand Up @@ -115,40 +148,45 @@ impl<'a> ListItem<'a> {

/// Draw the item.
pub fn show(self, ui: &mut Ui) -> Response {
self.ui(ui).response
self.ui(ui, None).response
}

/// Draw the item as a collapsing header.
pub fn show_collapsing<R>(
mut self,
ui: &mut Ui,
id: egui::Id,
default_open: bool,
add_body: impl FnOnce(&ReUi, &mut egui::Ui) -> R,
) {
) -> ShowCollapsingResponse<R> {
let mut state = egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(ui.id().with(self.text.text())),
id,
default_open,
);

// enable collapsing arrow
self.collapse_openness = Some(state.openness(ui.ctx()));

let re_ui = self.re_ui;
let response = self.ui(ui);
let response = self.ui(ui, Some(id));

if let Some(collapse_response) = response.collapse_response {
if collapse_response.clicked() {
state.toggle(ui);
}
}

state.show_body_indented(&response.response, ui, |ui| {
add_body(re_ui, ui);
});
let body_response =
state.show_body_indented(&response.response, ui, |ui| add_body(re_ui, ui));

ShowCollapsingResponse {
item_response: response.response,
body_response: body_response.map(|r| r.inner),
}
}

fn ui(self, ui: &mut Ui) -> ListItemResponse {
fn ui(self, ui: &mut Ui, id: Option<egui::Id>) -> ListItemResponse {
let collapse_extra = if self.collapse_openness.is_some() {
ReUi::collapsing_triangle_size().x + ReUi::text_to_icon_padding()
} else {
Expand All @@ -163,15 +201,27 @@ impl<'a> ListItem<'a> {
let desired_size = egui::vec2(ui.available_width(), ReUi::list_item_height());
let (rect, response) = ui.allocate_at_least(desired_size, egui::Sense::click());

// override_hover should not affect the returned response
let mut style_response = response.clone();
if self.override_hover {
style_response.hovered = true;
}

let mut collapse_response = None;

if ui.is_rect_visible(rect) {
let visuals = if self.active {
ui.style().interact_selectable(&response, self.selected)
let mut visuals = if self.active {
ui.style()
.interact_selectable(&style_response, self.selected)
} else {
ui.visuals().widgets.inactive
};

if self.subdued {
//TODO(ab): hack, see ['ListItem::subdued']
visuals.fg_stroke.color = visuals.fg_stroke.color.gamma_multiply(0.5);
}

let mut bg_rect = rect;
bg_rect.extend_with_x(ui.clip_rect().right());
bg_rect.extend_with_x(ui.clip_rect().left());
Expand All @@ -185,7 +235,11 @@ impl<'a> ListItem<'a> {
));
let triangle_rect =
egui::Rect::from_min_size(triangle_pos, ReUi::collapsing_triangle_size());
let resp = ui.interact(triangle_rect, ui.id(), egui::Sense::click());
let resp = ui.interact(
triangle_rect,
id.unwrap_or(ui.id()).with("collapsing_triangle"),
egui::Sense::click(),
);
ReUi::paint_collapsing_triangle(ui, openness, &resp);
collapse_response = Some(resp);
}
Expand All @@ -201,18 +255,25 @@ impl<'a> ListItem<'a> {
}

// Handle buttons
let button_response =
if self.active && ui.interact(rect, ui.id(), egui::Sense::hover()).hovered() {
if let Some(buttons) = self.buttons_fn {
let mut ui =
ui.child_ui(rect, egui::Layout::right_to_left(egui::Align::Center));
Some(buttons(self.re_ui, &mut ui))
} else {
None
}
let button_response = if self.active
&& ui
.interact(
rect,
id.unwrap_or(ui.id()).with("buttons"),
egui::Sense::hover(),
)
.hovered()
{
if let Some(buttons) = self.buttons_fn {
let mut ui =
ui.child_ui(rect, egui::Layout::right_to_left(egui::Align::Center));
Some(buttons(self.re_ui, &mut ui))
} else {
None
};
}
} else {
None
};

// Draw text next to the icon.
let mut text_rect = rect;
Expand Down Expand Up @@ -247,9 +308,9 @@ impl<'a> ListItem<'a> {
let bg_fill = if button_response.map_or(false, |r| r.hovered()) {
Some(visuals.bg_fill)
} else if self.selected
|| response.hovered()
|| response.highlighted()
|| response.has_focus()
|| style_response.hovered()
|| style_response.highlighted()
|| style_response.has_focus()
{
Some(visuals.weak_bg_fill)
} else {
Expand Down
16 changes: 5 additions & 11 deletions crates/re_viewer/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,15 @@ impl AppState {

// ListItem don't need vertical spacing so we disable it, but restore it
// before drawing the blueprint panel.
// TODO(ab): remove this once the blueprint tree uses list items
let v_space = ui.spacing().item_spacing.y;
ui.spacing_mut().item_spacing.y = 0.0;

recordings_panel_ui(&mut ctx, ui);
let recording_shown = recordings_panel_ui(&mut ctx, ui);

// TODO(ab): remove this frame once the blueprint tree uses list items
egui::Frame {
inner_margin: re_ui::ReUi::panel_margin(),
..Default::default()
if recording_shown {
ui.add_space(4.0);
}
.show(ui, |ui| {
ui.spacing_mut().item_spacing.y = v_space;
blueprint_panel_ui(&mut viewport.blueprint, &mut ctx, ui, &spaces_info);
});

blueprint_panel_ui(&mut viewport.blueprint, &mut ctx, ui, &spaces_info);
},
);

Expand Down
22 changes: 12 additions & 10 deletions crates/re_viewer/src/ui/blueprint_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ pub fn blueprint_panel_ui(
ui: &mut egui::Ui,
spaces_info: &SpaceInfoCollection,
) {
ctx.re_ui.panel_title_bar_with_buttons(
ui,
"Blueprint",
Some("The Blueprint is where you can configure the Rerun Viewer"),
|ui| {
blueprint.add_new_spaceview_button_ui(ctx, ui, spaces_info);
reset_button_ui(blueprint, ctx, ui, spaces_info);
},
);
ctx.re_ui.panel_content(ui, |_, ui| {
ctx.re_ui.panel_title_bar_with_buttons(
ui,
"Blueprint",
Some("The Blueprint is where you can configure the Rerun Viewer"),
|ui| {
blueprint.add_new_spaceview_button_ui(ctx, ui, spaces_info);
reset_button_ui(blueprint, ctx, ui, spaces_info);
},
);

blueprint.tree_ui(ctx, ui);
blueprint.tree_ui(ctx, ui);
});
}

fn reset_button_ui(
Expand Down
30 changes: 20 additions & 10 deletions crates/re_viewer/src/ui/recordings_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ static TIME_FORMAT_DESCRIPTION: once_cell::sync::Lazy<
> = once_cell::sync::Lazy::new(|| format_description!(version = 2, "[hour]:[minute]:[second]Z"));

/// Show the currently open Recordings in a selectable list.
pub fn recordings_panel_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) {
///
/// Returns `true` if any recordings were shown.
pub fn recordings_panel_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) -> bool {
ctx.re_ui.panel_content(ui, |re_ui, ui| {
re_ui.panel_title_bar_with_buttons(
ui,
Expand All @@ -25,12 +27,16 @@ pub fn recordings_panel_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) {
.max_height(300.)
.show(ui, |ui| {
ctx.re_ui
.panel_content(ui, |_re_ui, ui| recording_list_ui(ctx, ui));
});
.panel_content(ui, |_re_ui, ui| recording_list_ui(ctx, ui))
})
.inner
}

/// Draw the recording list.
///
/// Returns `true` if any recordings were shown.
#[allow(clippy::blocks_in_if_conditions)]
fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) {
fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) -> bool {
let ViewerContext {
store_context,
command_sender,
Expand All @@ -46,7 +52,7 @@ fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) {
}

if store_dbs_map.is_empty() {
return;
return false;
}

for store_dbs in store_dbs_map.values_mut() {
Expand All @@ -72,10 +78,11 @@ fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) {
.send_system(SystemCommand::SetRecordingId(store_db.store_id().clone()));
}
} else {
ctx.re_ui
.list_item(app_id)
.active(false)
.show_collapsing(ui, true, |_, ui| {
ctx.re_ui.list_item(app_id).active(false).show_collapsing(
ui,
ui.id().with(app_id),
true,
|_, ui| {
for store_db in store_dbs {
if recording_ui(
ctx.re_ui,
Expand All @@ -92,9 +99,12 @@ fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) {
));
}
}
});
},
);
}
}

true
}

/// Show the UI for a single recording.
Expand Down
Loading