Skip to content

Commit

Permalink
Rewrite the ui.large_collapsing_header into `re_ui::SectionCollapsi…
Browse files Browse the repository at this point in the history
…ngHeader` using `re_ui::ListItem` (#6657)

### What

- Follow-up to #6637 
- Part of #6655 

This PR completely rewrites the existing `large_collapsing_header` stuff
as follows:
- Renamed `SectionCollapsingHeader`.
- Now a struct with builder pattern, can be created with convenience
`ui.section_collapsing_header("label")`.
- Supports menu button
- Support help tooltip
- Added `ListItem::show_hierarchical_with_children_unindented`.

Note: the label must now be double-clicked to collapse/uncollapse, as
per `ListItem` behaviour.

Few visible changes beyond the help tooltip. The background no longer
changes on hover.

<img width="431" alt="image"
src="https://github.com/rerun-io/rerun/assets/49431240/20be7205-59a3-40a7-9692-c9c2a89ea0b6">


### 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 examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/6657?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/6657?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
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!

- [PR Build Summary](https://build.rerun.io/pr/6657)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.
  • Loading branch information
abey79 authored Jun 27, 2024
1 parent 1a047fd commit a7c0e39
Show file tree
Hide file tree
Showing 14 changed files with 400 additions and 359 deletions.
2 changes: 2 additions & 0 deletions crates/re_blueprint_tree/src/blueprint_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ impl BlueprintTree {
let list_item::ShowCollapsingResponse {
item_response: response,
body_response,
..
} = ui
.list_item()
.selected(ctx.selection().contains_item(&item))
Expand Down Expand Up @@ -363,6 +364,7 @@ impl BlueprintTree {
let list_item::ShowCollapsingResponse {
item_response: mut response,
body_response,
..
} = ui
.list_item()
.selected(ctx.selection().contains_item(&item))
Expand Down
6 changes: 4 additions & 2 deletions crates/re_selection_panel/src/defaults_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ pub fn view_components_defaults_section_ui(
db,
);
};
ui.large_collapsing_header_with_button("Component defaults", true, body, add_button);
ui.section_collapsing_header("Component defaults")
.button(add_button)
.show(ui, body);
}

fn active_default_ui(
Expand All @@ -83,7 +85,7 @@ fn active_default_ui(
ui.spacing_mut().item_spacing.y = 0.0;

if sorted_overrides.is_empty() {
ui.list_item_flat_noninteractive(LabelContent::new("(none)").weak(true));
ui.list_item_flat_noninteractive(LabelContent::new("none").weak(true).italics(true));
}

for component_name in sorted_overrides {
Expand Down
160 changes: 79 additions & 81 deletions crates/re_selection_panel/src/selection_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl SelectionPanel {
}

if let Some(data_ui_item) = data_section_ui(item) {
ui.large_collapsing_header("Data", true, |ui| {
ui.section_collapsing_header("Data").show(ui, |ui| {
// TODO(#6075): Because `list_item_scope` changes it. Temporary until everything is `ListItem`.
ui.spacing_mut().item_spacing.y = ui.ctx().style().spacing.item_spacing.y;

Expand Down Expand Up @@ -319,74 +319,6 @@ impl SelectionPanel {
view_id: &SpaceViewId,
view_states: &mut ViewStates,
) {
clone_space_view_button_ui(ctx, ui, blueprint, *view_id);

if let Some(view) = blueprint.view(view_id) {
ui.large_collapsing_header("Entity path filter", true, |ui| {
// TODO(#6075): Because `list_item_scope` changes it. Temporary until everything is `ListItem`.
ui.spacing_mut().item_spacing.y = ui.ctx().style().spacing.item_spacing.y;

if let Some(new_entity_path_filter) = self.entity_path_filter_ui(
ctx,
ui,
*view_id,
&view.contents.entity_path_filter,
&view.space_origin,
) {
view.contents
.set_entity_path_filter(ctx, &new_entity_path_filter);
}
})
.header_response
.on_hover_text(
"The entity path query consists of a list of include/exclude rules \
that determines what entities are part of this view",
);
}

if let Some(view) = blueprint.view(view_id) {
let view_class = view.class(ctx.space_view_class_registry);
let view_state = view_states.get_mut_or_create(view.id, view_class);

ui.large_collapsing_header("View properties", true, |ui| {
// TODO(#6075): Because `list_item_scope` changes it. Temporary until everything is `ListItem`.
ui.spacing_mut().item_spacing.y = ui.ctx().style().spacing.item_spacing.y;

let cursor = ui.cursor();

if let Err(err) =
view_class.selection_ui(ctx, ui, view_state, &view.space_origin, view.id)
{
re_log::error_once!(
"Error in view selection UI (class: {}, display name: {}): {err}",
view.class_identifier(),
view_class.display_name(),
);
}

if cursor == ui.cursor() {
ui.weak("(none)");
}
});

let view_ctx = view.bundle_context_with_state(ctx, view_state);
view_components_defaults_section_ui(&view_ctx, ui, view);
}

if let Some(view) = blueprint.view(view_id) {
visible_time_range_ui_for_view(ctx, ui, view);
}
}

/// Returns a new filter when the editing is done, and there has been a change.
fn entity_path_filter_ui(
&mut self,
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
view_id: SpaceViewId,
filter: &EntityPathFilter,
origin: &EntityPath,
) -> Option<EntityPathFilter> {
fn entity_path_filter_help_ui(ui: &mut egui::Ui) {
let markdown = r#"
# Entity path query syntax
Expand Down Expand Up @@ -428,6 +360,77 @@ The last rule matching `/world/house` is `+ /world/**`, so it is included.
ui.markdown_ui(egui::Id::new("entity_path_filter_help_ui"), markdown);
}

clone_space_view_button_ui(ctx, ui, blueprint, *view_id);

if let Some(view) = blueprint.view(view_id) {
ui.section_collapsing_header("Entity path filter")
.help_ui(entity_path_filter_help_ui)
.show(ui, |ui| {
// TODO(#6075): Because `list_item_scope` changes it. Temporary until everything is `ListItem`.
ui.spacing_mut().item_spacing.y = ui.ctx().style().spacing.item_spacing.y;

if let Some(new_entity_path_filter) = self.entity_path_filter_ui(
ctx,
ui,
*view_id,
&view.contents.entity_path_filter,
&view.space_origin,
) {
view.contents
.set_entity_path_filter(ctx, &new_entity_path_filter);
}
})
.header_response
.on_hover_text(
"The entity path query consists of a list of include/exclude rules \
that determines what entities are part of this view",
);
}

if let Some(view) = blueprint.view(view_id) {
let view_class = view.class(ctx.space_view_class_registry);
let view_state = view_states.get_mut_or_create(view.id, view_class);

ui.section_collapsing_header("View properties")
.show(ui, |ui| {
// TODO(#6075): Because `list_item_scope` changes it. Temporary until everything is `ListItem`.
ui.spacing_mut().item_spacing.y = ui.ctx().style().spacing.item_spacing.y;

let cursor = ui.cursor();

if let Err(err) =
view_class.selection_ui(ctx, ui, view_state, &view.space_origin, view.id)
{
re_log::error_once!(
"Error in view selection UI (class: {}, display name: {}): {err}",
view.class_identifier(),
view_class.display_name(),
);
}

if cursor == ui.cursor() {
ui.weak("(none)");
}
});

let view_ctx = view.bundle_context_with_state(ctx, view_state);
view_components_defaults_section_ui(&view_ctx, ui, view);
}

if let Some(view) = blueprint.view(view_id) {
visible_time_range_ui_for_view(ctx, ui, view);
}
}

/// Returns a new filter when the editing is done, and there has been a change.
fn entity_path_filter_ui(
&mut self,
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
view_id: SpaceViewId,
filter: &EntityPathFilter,
origin: &EntityPath,
) -> Option<EntityPathFilter> {
fn syntax_highlight_entity_path_filter(
style: &egui::Style,
mut string: &str,
Expand Down Expand Up @@ -506,18 +509,13 @@ The last rule matching `/world/house` is `+ /world/**`, so it is included.
));
}

ui.horizontal(|ui| {
ui.help_hover_button()
.on_hover_ui(entity_path_filter_help_ui);

if ui
.button("Edit")
.on_hover_text("Modify the entity query using the editor")
.clicked()
{
self.space_view_entity_modal.open(view_id);
}
});
if ui
.button("Edit")
.on_hover_text("Modify the entity query using the editor")
.clicked()
{
self.space_view_entity_modal.open(view_id);
}

// Apply the edit.
let new_filter = EntityPathFilter::parse_forgiving(&filter_string, &Default::default());
Expand Down
3 changes: 1 addition & 2 deletions crates/re_selection_panel/src/space_view_space_origin_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,7 @@ fn space_view_space_origin_widget_editing_ui(

let excluded_count = space_view_suggestions.len() - filtered_space_view_suggestions.len();
if excluded_count > 0 {
ui.list_item().interactive(false).show_flat(
ui,
ui.list_item_flat_noninteractive(
list_item::LabelContent::new(format!("{excluded_count} hidden suggestions"))
.weak(true)
.italics(true),
Expand Down
7 changes: 4 additions & 3 deletions crates/re_selection_panel/src/visible_time_range_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,10 @@ fn query_range_ui(

let mut interacting_with_controls = false;

let default_open = false;
let collapsing_response =
ui.large_collapsing_header("Visible time range", default_open, |ui| {
let collapsing_response = ui
.section_collapsing_header("Visible time range")
.default_open(false)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.re_radio_value(has_individual_time_range, false, "Default")
.on_hover_text(if is_space_view {
Expand Down
43 changes: 24 additions & 19 deletions crates/re_selection_panel/src/visualizer_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,24 @@ pub fn visualizer_ui(
&active_visualizers,
);

ui.large_collapsing_header_with_button(
"Visualizers",
true,
|ui| {
let button = re_ui::HeaderMenuButton::new(&re_ui::icons::ADD, |ui| {
menu_add_new_visualizer(
ctx,
ui,
&data_result,
&active_visualizers,
&available_inactive_visualizers,
);
})
.enabled(!available_inactive_visualizers.is_empty())
.hover_text("Add additional visualizers")
.disabled_hover_text("No additional visualizers available");

ui.section_collapsing_header("Visualizers")
.button(button)
.show(ui, |ui| {
visualizer_ui_impl(ctx, ui, &data_result, &active_visualizers);
},
re_ui::HeaderMenuButton::new(&re_ui::icons::ADD, |ui| {
menu_add_new_visualizer(
ctx,
ui,
&data_result,
&active_visualizers,
&available_inactive_visualizers,
);
})
.enabled(!available_inactive_visualizers.is_empty())
.hover_text("Add additional visualizers")
.disabled_hover_text("No additional visualizers available"),
);
});
}

pub fn visualizer_ui_impl(
Expand Down Expand Up @@ -88,7 +87,13 @@ pub fn visualizer_ui_impl(
};

list_item::list_item_scope(ui, "visualizers", |ui| {
ui.spacing_mut().item_spacing.y = 0.0;
if active_visualizers.is_empty() {
ui.list_item_flat_noninteractive(
list_item::LabelContent::new("none")
.weak(true)
.italics(true),
);
}

for &visualizer_id in active_visualizers {
let default_open = true;
Expand Down
1 change: 1 addition & 0 deletions crates/re_time_panel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ impl TimePanel {
let list_item::ShowCollapsingResponse {
item_response: response,
body_response,
..
} = ui
.list_item()
.selected(is_selected)
Expand Down
17 changes: 7 additions & 10 deletions crates/re_ui/examples/re_ui_example/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,17 +243,14 @@ impl eframe::App for ExampleApp {

// ---

ui.large_collapsing_header_with_button(
"Data",
true,
|ui| {
ui.label("Some data here");
},
re_ui::HeaderMenuButton::new(&re_ui::icons::ADD, |ui| {
ui.section_collapsing_header("Data")
.button(re_ui::HeaderMenuButton::new(&re_ui::icons::ADD, |ui| {
ui.weak("empty");
}),
);
ui.large_collapsing_header("Blueprint", true, |ui| {
}))
.show(ui, |ui| {
ui.label("Some data here");
});
ui.section_collapsing_header("Blueprint").show(ui, |ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.label("Some blueprint stuff here, that might be wide.");
ui.re_checkbox(&mut self.dummy_bool, "Checkbox");
Expand Down
6 changes: 6 additions & 0 deletions crates/re_ui/src/design_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,12 @@ impl DesignTokens {
Self::small_icon_size()
}

/// The color for the background of [`crate::SectionCollapsingHeader`].
pub fn section_collapsing_header_color() -> egui::Color32 {
// same as visuals.widgets.inactive.bg_fill
egui::Color32::from_gray(50)
}

/// The color we use to mean "loop this selection"
pub fn loop_selection_color() -> egui::Color32 {
egui::Color32::from_rgb(1, 37, 105) // from figma 2023-02-09
Expand Down
4 changes: 3 additions & 1 deletion crates/re_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod drag_and_drop;
pub mod icons;
pub mod list_item;
pub mod modal;
mod section_collapsing_header;
pub mod toasts;
mod ui_ext;

Expand All @@ -21,8 +22,9 @@ pub use self::{
design_tokens::DesignTokens,
icons::Icon,
layout_job_builder::LayoutJobBuilder,
section_collapsing_header::{HeaderMenuButton, SectionCollapsingHeader},
syntax_highlighting::SyntaxHighlighting,
ui_ext::{HeaderMenuButton, UiExt},
ui_ext::UiExt,
};

// ---------------------------------------------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions crates/re_ui/src/list_item/label_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct LabelContent<'a> {
italics: bool,

label_style: LabelStyle,
icon_fn: Option<Box<dyn FnOnce(&egui::Ui, egui::Rect, egui::style::WidgetVisuals) + 'a>>,
icon_fn: Option<Box<dyn FnOnce(&mut egui::Ui, egui::Rect, egui::style::WidgetVisuals) + 'a>>,
buttons_fn: Option<Box<dyn FnOnce(&mut egui::Ui) -> egui::Response + 'a>>,
always_show_buttons: bool,

Expand Down Expand Up @@ -115,7 +115,7 @@ impl<'a> LabelContent<'a> {
#[inline]
pub fn with_icon_fn(
mut self,
icon_fn: impl FnOnce(&egui::Ui, egui::Rect, egui::style::WidgetVisuals) + 'a,
icon_fn: impl FnOnce(&mut egui::Ui, egui::Rect, egui::style::WidgetVisuals) + 'a,
) -> Self {
self.icon_fn = Some(Box::new(icon_fn));
self
Expand Down
Loading

0 comments on commit a7c0e39

Please sign in to comment.