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

Filter entities in the UI (part 3): Move action to a menu in the blueprint panel and keep default blueprint when using heuristics #8672

Merged
merged 8 commits into from
Jan 14, 2025
148 changes: 92 additions & 56 deletions crates/viewer/re_blueprint_tree/src/blueprint_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,25 @@ impl BlueprintTree {
ui: &mut egui::Ui,
) {
ui.panel_content(|ui| {
ui.panel_title_bar_with_buttons(
"Blueprint",
Some("The blueprint is where you can configure the Rerun Viewer"),
|ui| {
self.add_new_view_button_ui(ctx, viewport, ui);
reset_blueprint_button_ui(ctx, ui);
},
);
ui.full_span_separator();
ui.add_space(-1.);

ui.list_item_scope("blueprint_section_title", |ui| {
ui.list_item_flat_noninteractive(
list_item::CustomContent::new(|ui, _| {
ui.strong("Blueprint").on_hover_text(
"The blueprint is where you can configure the Rerun Viewer",
);
})
.menu_button(&re_ui::icons::MORE, |ui| {
add_new_view_or_container_menu_button(ctx, viewport, ui);
set_blueprint_to_default_menu_buttons(ctx, ui);
set_blueprint_to_auto_menu_button(ctx, viewport, ui);
}),
);
});

ui.full_span_separator();
});

// This call is excluded from `panel_content` because it has a ScrollArea, which should not be
Expand Down Expand Up @@ -602,32 +613,6 @@ impl BlueprintTree {
ctx.handle_select_hover_drag_interactions(&response, item, true);
}

/// Add a button to trigger the addition of a new view or container.
#[allow(clippy::unused_self)]
fn add_new_view_button_ui(
&self,
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &mut egui::Ui,
) {
if ui
.small_icon_button(&re_ui::icons::ADD)
.on_hover_text("Add a new view or container")
.clicked()
{
// If a single container is selected, we use it as target. Otherwise, we target the
// root container.
let target_container_id =
if let Some(Item::Container(container_id)) = ctx.selection().single_item() {
*container_id
} else {
viewport.root_container
};

show_add_view_or_container_modal(target_container_id);
}
}

// ----------------------------------------------------------------------------
// drag and drop support

Expand Down Expand Up @@ -868,46 +853,97 @@ impl BlueprintTree {

// ----------------------------------------------------------------------------

fn reset_blueprint_button_ui(ctx: &ViewerContext<'_>, ui: &mut egui::Ui) {
/// Add a button to trigger the addition of a new view or container.
fn add_new_view_or_container_menu_button(
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &mut egui::Ui,
) {
if ui
.add(egui::Button::image_and_text(
&re_ui::icons::ADD,
"Add view or container…",
))
.clicked()
{
ui.close_menu();

// If a single container is selected, we use it as target. Otherwise, we target the
// root container.
let target_container_id =
if let Some(Item::Container(container_id)) = ctx.selection().single_item() {
*container_id
} else {
viewport.root_container
};

show_add_view_or_container_modal(target_container_id);
}
}

fn set_blueprint_to_default_menu_buttons(ctx: &ViewerContext<'_>, ui: &mut egui::Ui) {
let default_blueprint_id = ctx
.store_context
.hub
.default_blueprint_id_for_app(&ctx.store_context.app_id);

let default_blueprint = default_blueprint_id.and_then(|id| ctx.store_context.bundle.get(id));

let mut disabled_reason = None;

if let Some(default_blueprint) = default_blueprint {
let active_is_clone_of_default = Some(default_blueprint.store_id()).as_ref()
== ctx.store_context.blueprint.cloned_from();
let last_modified_at_the_same_time =
default_blueprint.latest_row_id() == ctx.store_context.blueprint.latest_row_id();
if active_is_clone_of_default && last_modified_at_the_same_time {
disabled_reason = Some("No modifications have been made");
let disabled_reason = match default_blueprint {
None => Some("No default blueprint is set for this app"),
Some(default_blueprint) => {
let active_is_clone_of_default = Some(default_blueprint.store_id()).as_ref()
== ctx.store_context.blueprint.cloned_from();
let last_modified_at_the_same_time =
default_blueprint.latest_row_id() == ctx.store_context.blueprint.latest_row_id();
if active_is_clone_of_default && last_modified_at_the_same_time {
Some("No modifications have been made")
} else {
None // it is valid to reset to default
}
}
}
};

let enabled = disabled_reason.is_none();
let response = ui.add_enabled(enabled, ui.small_icon_button_widget(&re_ui::icons::RESET));

let response = if let Some(disabled_reason) = disabled_reason {
response.on_disabled_hover_text(disabled_reason)
} else {
let hover_text = if default_blueprint_id.is_some() {
"Reset to the default blueprint for this app"
} else {
"Re-populate viewport with automatically chosen views"
};
response.on_hover_text(hover_text)
let mut response = ui
.add_enabled(
enabled,
egui::Button::image_and_text(&re_ui::icons::RESET, "Reset to default blueprint"),
)
.on_hover_text("Reset to the default blueprint for this app");

if let Some(disabled_reason) = disabled_reason {
response = response.on_disabled_hover_text(disabled_reason);
};

if response.clicked() {
ui.close_menu();
ctx.command_sender
.send_system(re_viewer_context::SystemCommand::ClearActiveBlueprint);
}
}

fn set_blueprint_to_auto_menu_button(
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &mut egui::Ui,
) {
let enabled = !viewport.auto_layout() || !viewport.auto_views();

if ui
.add_enabled(
enabled,
egui::Button::image_and_text(&re_ui::icons::RESET, "Reset to heuristic blueprint"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can come up with better copy here. "Create automatic blueprint"? Maybe @gavrelina has ideas

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed that and decided to stick with "heuristics" for now.

)
.on_hover_text("Re-populate viewport with automatically chosen views")
.clicked()
{
ui.close_menu();
ctx.command_sender
.send_system(re_viewer_context::SystemCommand::ClearActiveBlueprintAndEnableHeuristics);
}
}

/// Expand all required items and compute which item we should scroll to.
fn handle_focused_item(
ctx: &ViewerContext<'_>,
Expand Down
48 changes: 1 addition & 47 deletions crates/viewer/re_data_ui/src/app_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use itertools::Itertools as _;

use re_entity_db::EntityDb;
use re_log_types::ApplicationId;
use re_viewer_context::{SystemCommandSender as _, UiLayout, ViewerContext};
use re_viewer_context::{UiLayout, ViewerContext};

use crate::item_ui::entity_db_button_ui;

Expand Down Expand Up @@ -54,51 +54,5 @@ impl crate::DataUi for ApplicationId {
}
});
}

// ---------------------------------------------------------------------
// do not show UI code in tooltips

if ui_layout != UiLayout::Tooltip {
ui.add_space(8.0);

// ---------------------------------------------------------------------

// Blueprint section.
let active_blueprint = ctx.store_context.blueprint;
let default_blueprint = ctx.store_context.hub.default_blueprint_for_app(self);

let button =
egui::Button::image_and_text(&re_ui::icons::RESET, "Reset to default blueprint");

let is_same_as_default = default_blueprint.is_some_and(|default_blueprint| {
default_blueprint.latest_row_id() == active_blueprint.latest_row_id()
});

if is_same_as_default {
ui.add_enabled(false, button)
.on_disabled_hover_text("No modifications have been made");
} else if default_blueprint.is_none() {
ui.add_enabled(false, button)
.on_disabled_hover_text("There's no default blueprint");
} else {
// The active blueprint is different from the default blueprint
if ui
.add(button)
.on_hover_text("Reset to the default blueprint for this app")
.clicked()
{
ctx.command_sender
.send_system(re_viewer_context::SystemCommand::ClearActiveBlueprint);
}
}

if ui.add(egui::Button::image_and_text(
&re_ui::icons::RESET,
"Reset to heuristic blueprint",
)).on_hover_text("Clear both active and default blueprint, and auto-generate a new blueprint based on heuristics").clicked() {
ctx.command_sender
.send_system(re_viewer_context::SystemCommand::ClearAndGenerateBlueprint);
}
}
}
}
15 changes: 6 additions & 9 deletions crates/viewer/re_ui/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub enum UICommand {
OpenRerunDiscord,

ResetViewer,
ClearAndGenerateBlueprint,
ClearActiveBlueprintAndEnableHeuristics,

#[cfg(not(target_arch = "wasm32"))]
OpenProfiler,
Expand Down Expand Up @@ -121,7 +121,7 @@ impl UICommand {
),

Self::CloseAllRecordings => ("Close all recordings",
"Close all open current recording (unsaved data will be lost)"),
"Close all open current recording (unsaved data will be lost)"),

Self::Undo => ("Undo", "Undo the last blueprint edit for the open recording"),
Self::Redo => ("Redo", "Redo the last undone thing"),
Expand All @@ -137,12 +137,11 @@ impl UICommand {
"Reset the Viewer to how it looked the first time you ran it, forgetting all stored blueprints and UI state",
),

Self::ClearAndGenerateBlueprint => (
"Clear and generate new blueprint",
"Clear the current blueprint and generate a new one based on heuristics."
Self::ClearActiveBlueprintAndEnableHeuristics => (
"Reset to heuristic blueprint",
"Re-populate viewport with automatically chosen views"
),


#[cfg(not(target_arch = "wasm32"))]
Self::OpenProfiler => (
"Open profiler",
Expand Down Expand Up @@ -174,7 +173,6 @@ impl UICommand {
"View and change global egui style settings",
),


#[cfg(not(target_arch = "wasm32"))]
Self::ToggleFullscreen => (
"Toggle fullscreen",
Expand Down Expand Up @@ -256,7 +254,6 @@ impl UICommand {
"Restart with WebGPU",
"Reloads the webpage and force WebGPU for rendering. All data will be lost."
),

}
}

Expand Down Expand Up @@ -314,7 +311,7 @@ impl UICommand {
Self::Quit => smallvec![cmd(Key::Q)],

Self::ResetViewer => smallvec![ctrl_shift(Key::R)],
Self::ClearAndGenerateBlueprint => smallvec![],
Self::ClearActiveBlueprintAndEnableHeuristics => smallvec![],

#[cfg(not(target_arch = "wasm32"))]
Self::OpenProfiler => smallvec![ctrl_shift(Key::P)],
Expand Down
10 changes: 10 additions & 0 deletions crates/viewer/re_ui/src/ui_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,16 @@ pub trait UiExt {
self.ui().painter().add(shadow);
}

/// Convenience function to create a [`list_item::list_item_scope`].
#[inline]
fn list_item_scope<R>(
&mut self,
id_salt: impl std::hash::Hash,
content: impl FnOnce(&mut egui::Ui) -> R,
) -> R {
list_item::list_item_scope(self.ui_mut(), id_salt, content)
}

/// Convenience function to create a [`list_item::ListItem`].
#[allow(clippy::unused_self)]
fn list_item(&self) -> list_item::ListItem {
Expand Down
1 change: 1 addition & 0 deletions crates/viewer/re_view_graph/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ fn run_graph_view_and_save_snapshot(
bundle: &Default::default(),
caches: &Default::default(),
hub: &Default::default(),
should_enable_heuristics: false,
};

// Execute the queries for every `View`
Expand Down
12 changes: 5 additions & 7 deletions crates/viewer/re_viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,12 +524,10 @@ impl App {
}

SystemCommand::ResetViewer => self.reset_viewer(store_hub, egui_ctx),
SystemCommand::ClearAndGenerateBlueprint => {
SystemCommand::ClearActiveBlueprintAndEnableHeuristics => {
re_log::debug!("Clear and generate new blueprint");
// By clearing the default blueprint and the active blueprint
// it will be re-generated based on the default auto behavior.
store_hub.clear_default_blueprint();
store_hub.clear_active_blueprint();
store_hub.clear_active_blueprint_and_generate();
egui_ctx.request_repaint(); // Many changes take a frame delay to show up.
}
SystemCommand::ClearActiveBlueprint => {
// By clearing the blueprint the default blueprint will be restored
Expand Down Expand Up @@ -779,9 +777,9 @@ impl App {
}

UICommand::ResetViewer => self.command_sender.send_system(SystemCommand::ResetViewer),
UICommand::ClearAndGenerateBlueprint => {
UICommand::ClearActiveBlueprintAndEnableHeuristics => {
self.command_sender
.send_system(SystemCommand::ClearAndGenerateBlueprint);
.send_system(SystemCommand::ClearActiveBlueprintAndEnableHeuristics);
}

#[cfg(not(target_arch = "wasm32"))]
Expand Down
7 changes: 7 additions & 0 deletions crates/viewer/re_viewer/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ impl AppState {
drag_and_drop_manager: &drag_and_drop_manager,
};

// enable the heuristics if we must this frame
if store_context.should_enable_heuristics {
viewport_ui.blueprint.set_auto_layout(true, &ctx);
viewport_ui.blueprint.set_auto_views(true, &ctx);
egui_ctx.request_repaint();
}

// We move the time at the very start of the frame,
// so that we always show the latest data when we're in "follow" mode.
move_time(&ctx, recording, rx);
Expand Down
Loading
Loading