Skip to content

Commit

Permalink
More context menu 7: cleanup (#5456)
Browse files Browse the repository at this point in the history
### What

NOTE: review commit by commit (first commit is pure refactoring).

Final PR in the series. This PR:
- split `actions.rs` into many sub-files
- fix capitalisation in some menu item
- split the release check list in small checks

Full series:
- #5392
- #5397
- #5407
- #5411
- #5422
- #5433
- #5456

### 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 newly built examples:
[app.rerun.io](https://app.rerun.io/pr/5456/index.html)
* Using examples from latest `main` build:
[app.rerun.io](https://app.rerun.io/pr/5456/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[app.rerun.io](https://app.rerun.io/pr/5456/index.html?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/5456)
- [Docs
preview](https://rerun.io/preview/a36360f586494f6648fdc4bbb9d806ab12911358/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/a36360f586494f6648fdc4bbb9d806ab12911358/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)
  • Loading branch information
abey79 authored Mar 12, 2024
1 parent 7d656ec commit 2af4da0
Show file tree
Hide file tree
Showing 16 changed files with 1,060 additions and 775 deletions.
665 changes: 0 additions & 665 deletions crates/re_viewport/src/context_menu/actions.rs

This file was deleted.

35 changes: 35 additions & 0 deletions crates/re_viewport/src/context_menu/actions/add_container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use re_viewer_context::{ContainerId, Item};

use crate::context_menu::{ContextMenuAction, ContextMenuContext};

/// Add a container of a specific type
pub(crate) struct AddContainerAction(pub egui_tiles::ContainerKind);

impl ContextMenuAction for AddContainerAction {
fn supports_selection(&self, ctx: &ContextMenuContext<'_>) -> bool {
if let Some(Item::Container(container_id)) = ctx.selection.single_item() {
if let Some(container) = ctx.viewport_blueprint.container(container_id) {
// same-kind linear containers cannot be nested
(container.container_kind != egui_tiles::ContainerKind::Vertical
&& container.container_kind != egui_tiles::ContainerKind::Horizontal)
|| container.container_kind != self.0
} else {
// unknown container
false
}
} else {
false
}
}

fn label(&self, _ctx: &ContextMenuContext<'_>) -> String {
format!("{:?}", self.0)
}

fn process_container(&self, ctx: &ContextMenuContext<'_>, container_id: &ContainerId) {
ctx.viewport_blueprint
.add_container(self.0, Some(*container_id));
ctx.viewport_blueprint
.mark_user_interaction(ctx.viewer_context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use egui::{Response, Ui};
use itertools::Itertools;
use nohash_hasher::IntSet;

use re_log_types::{EntityPath, EntityPathFilter, EntityPathRule, RuleEffect};
use re_space_view::{determine_visualizable_entities, SpaceViewBlueprint};
use re_viewer_context::{Item, SpaceViewClassIdentifier};

use crate::context_menu::{ContextMenuAction, ContextMenuContext};

/// Create a new space view containing the selected entities.
///
/// The space view is created next to the clicked item's parent view (if a data result was clicked).
pub(crate) struct AddEntitiesToNewSpaceViewAction;

impl ContextMenuAction for AddEntitiesToNewSpaceViewAction {
fn supports_multi_selection(&self, _ctx: &ContextMenuContext<'_>) -> bool {
true
}

fn supports_item(&self, _ctx: &ContextMenuContext<'_>, item: &Item) -> bool {
matches!(item, Item::DataResult(_, _) | Item::InstancePath(_))
}

fn ui(&self, ctx: &ContextMenuContext<'_>, ui: &mut Ui) -> Response {
let space_view_class_registry = ctx.viewer_context.space_view_class_registry;

let recommended_space_view_classes = recommended_space_views_for_selection(ctx);
let other_space_view_classes: IntSet<_> = space_view_class_registry
.iter_registry()
.map(|entry| entry.class.identifier())
.collect::<IntSet<SpaceViewClassIdentifier>>()
.difference(&recommended_space_view_classes)
.cloned()
.collect();

ui.menu_button("Add to new Space View", |ui| {
let buttons_for_space_view_classes =
|ui: &mut egui::Ui, space_view_classes: &IntSet<SpaceViewClassIdentifier>| {
for (identifier, display_name) in space_view_classes
.iter()
.map(|identifier| {
(
identifier,
space_view_class_registry
.get_class_or_log_error(identifier)
.display_name(),
)
})
.sorted_by_key(|(_, display_name)| display_name.to_owned())
{
if ui.button(display_name).clicked() {
create_space_view_for_selected_entities(ctx, *identifier);
ui.close_menu();
}
}
};

ui.label(egui::WidgetText::from("Recommended:").italics());
if recommended_space_view_classes.is_empty() {
ui.label("None");
} else {
buttons_for_space_view_classes(ui, &recommended_space_view_classes);
}

if !other_space_view_classes.is_empty() {
ui.label(egui::WidgetText::from("Others:").italics());
buttons_for_space_view_classes(ui, &other_space_view_classes);
}
})
.response
}
}

/// Builds a list of compatible space views for the provided selection.
fn recommended_space_views_for_selection(
ctx: &ContextMenuContext<'_>,
) -> IntSet<SpaceViewClassIdentifier> {
re_tracing::profile_function!();

let entities_of_interest = ctx
.selection
.iter()
.filter_map(|(item, _)| item.entity_path().cloned())
.collect::<IntSet<_>>();

let mut output: IntSet<SpaceViewClassIdentifier> = IntSet::default();

let space_view_class_registry = ctx.viewer_context.space_view_class_registry;
let entity_db = ctx.viewer_context.entity_db;
let applicable_entities_per_visualizer =
space_view_class_registry.applicable_entities_for_visualizer_systems(entity_db.store_id());

for entry in space_view_class_registry.iter_registry() {
let Some(suggested_root) = entry
.class
.recommended_root_for_entities(&entities_of_interest, entity_db)
else {
continue;
};

let visualizable_entities = determine_visualizable_entities(
&applicable_entities_per_visualizer,
entity_db,
&space_view_class_registry.new_visualizer_collection(entry.class.identifier()),
&*entry.class,
&suggested_root,
);

// We consider a space view class to be recommended if all selected entities are
// "visualizable" with it. By "visualizable" we mean that either the entity itself, or any
// of its sub-entities, are visualizable.

let covered = entities_of_interest.iter().all(|entity| {
visualizable_entities.0.iter().any(|(_, entities)| {
entities
.0
.iter()
.any(|visualizable_entity| visualizable_entity.starts_with(entity))
})
});

if covered {
output.insert(entry.class.identifier());
}
}

output
}

/// Creates a space view of the given class, with root set as origin, and a filter set to include all
/// selected entities. Then, the selection is set to the new space view.
fn create_space_view_for_selected_entities(
ctx: &ContextMenuContext<'_>,
identifier: SpaceViewClassIdentifier,
) {
let entities_of_interest = ctx
.selection
.iter()
.filter_map(|(item, _)| item.entity_path().cloned())
.collect::<IntSet<_>>();

let origin = ctx
.viewer_context
.space_view_class_registry
.get_class_or_log_error(&identifier)
.recommended_root_for_entities(&entities_of_interest, ctx.viewer_context.entity_db)
.unwrap_or_else(EntityPath::root);

let mut filter = EntityPathFilter::default();

for path in entities_of_interest {
filter.add_rule(RuleEffect::Include, EntityPathRule::including_subtree(path));
}

let target_container_id = ctx
.clicked_item_enclosing_container_id_and_position()
.map(|(id, _)| id);

let space_view = SpaceViewBlueprint::new(identifier, &origin, filter);

let new_space_view = ctx.viewport_blueprint.add_space_views(
std::iter::once(space_view),
ctx.viewer_context,
target_container_id,
None,
);
if let Some(space_view_id) = new_space_view.first() {
ctx.viewer_context
.selection_state()
.set_selection(Item::SpaceView(*space_view_id));
}
ctx.viewport_blueprint
.mark_user_interaction(ctx.viewer_context);
}
36 changes: 36 additions & 0 deletions crates/re_viewport/src/context_menu/actions/add_space_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use re_log_types::{EntityPath, EntityPathFilter};
use re_space_view::SpaceViewBlueprint;
use re_viewer_context::{ContainerId, Item, SpaceViewClassIdentifier};

use crate::context_menu::{ContextMenuAction, ContextMenuContext};

/// Add a space view of the specific class
pub(crate) struct AddSpaceViewAction(pub SpaceViewClassIdentifier);

impl ContextMenuAction for AddSpaceViewAction {
fn supports_item(&self, _ctx: &ContextMenuContext<'_>, item: &Item) -> bool {
matches!(item, Item::Container(_))
}

fn label(&self, ctx: &ContextMenuContext<'_>) -> String {
ctx.viewer_context
.space_view_class_registry
.get_class_or_log_error(&self.0)
.display_name()
.to_owned()
}

fn process_container(&self, ctx: &ContextMenuContext<'_>, container_id: &ContainerId) {
let space_view =
SpaceViewBlueprint::new(self.0, &EntityPath::root(), EntityPathFilter::default());

ctx.viewport_blueprint.add_space_views(
std::iter::once(space_view),
ctx.viewer_context,
Some(*container_id),
None,
);
ctx.viewport_blueprint
.mark_user_interaction(ctx.viewer_context);
}
}
29 changes: 29 additions & 0 deletions crates/re_viewport/src/context_menu/actions/clone_space_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use re_viewer_context::{Item, SpaceViewId};

use crate::context_menu::{ContextMenuAction, ContextMenuContext};

/// Clone a single space view
pub(crate) struct CloneSpaceViewAction;

impl ContextMenuAction for CloneSpaceViewAction {
fn supports_item(&self, _ctx: &ContextMenuContext<'_>, item: &Item) -> bool {
matches!(item, Item::SpaceView(_))
}

fn label(&self, _ctx: &ContextMenuContext<'_>) -> String {
"Clone".to_owned()
}

fn process_space_view(&self, ctx: &ContextMenuContext<'_>, space_view_id: &SpaceViewId) {
if let Some(new_space_view_id) = ctx
.viewport_blueprint
.duplicate_space_view(space_view_id, ctx.viewer_context)
{
ctx.viewer_context
.selection_state()
.set_selection(Item::SpaceView(new_space_view_id));
ctx.viewport_blueprint
.mark_user_interaction(ctx.viewer_context);
}
}
}
Loading

0 comments on commit 2af4da0

Please sign in to comment.