-
Notifications
You must be signed in to change notification settings - Fork 373
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
More context menu 6: Add "Expand/Collapse all" actions #5433
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ use nohash_hasher::IntSet; | |
use re_entity_db::InstancePath; | ||
use re_log_types::{EntityPath, EntityPathFilter, EntityPathRule, RuleEffect}; | ||
use re_space_view::{determine_visualizable_entities, SpaceViewBlueprint}; | ||
use re_viewer_context::{ContainerId, Item, SpaceViewClassIdentifier, SpaceViewId}; | ||
use re_viewer_context::{CollapseScope, ContainerId, Item, SpaceViewClassIdentifier, SpaceViewId}; | ||
|
||
use super::{ContextMenuAction, ContextMenuContext}; | ||
use crate::Contents; | ||
|
@@ -544,3 +544,122 @@ fn create_space_view_for_selected_entities( | |
ctx.viewport_blueprint | ||
.mark_user_interaction(ctx.viewer_context); | ||
} | ||
|
||
// --- | ||
|
||
/// Collapse or expand all items in the selection. | ||
// TODO(ab): the current implementation makes strong assumptions of which CollapseScope to use based | ||
// on the item type. This is brittle and will not scale if/when we add more trees to the UI. When | ||
// that happens, we will have to pass the scope to `context_menu_ui_for_item` and use it here. | ||
pub(super) enum CollapseExpandAllAction { | ||
CollapseAll, | ||
ExpandAll, | ||
} | ||
|
||
impl ContextMenuAction for CollapseExpandAllAction { | ||
fn supports_selection(&self, ctx: &ContextMenuContext<'_>) -> bool { | ||
// let's allow this action if at least one item supports it | ||
ctx.selection | ||
.iter() | ||
.any(|(item, _)| self.supports_item(ctx, item)) | ||
} | ||
|
||
fn supports_item(&self, ctx: &ContextMenuContext<'_>, item: &Item) -> bool { | ||
// TODO(ab): in an ideal world, we'd check the fully expended/collapsed state of the item to | ||
// avoid showing a command that wouldn't have an effect but that's lots of added complexity. | ||
match item { | ||
Item::StoreId(_) | Item::ComponentPath(_) => false, | ||
Item::SpaceView(_) | Item::Container(_) | Item::InstancePath(_) => true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ~could we check if either of these are empty like we do with data result? 🤔 ~ Right, spaceview and container are never empty. InstancePath might, you could check the global path tree for that, might get too involved quickly though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, containers can be empty, but they always have the triangle icon (which is kinda weird, if you think of it, but I guess more natural given that they are meant to contain something). As for Instance paths, they are never empty, since they always have at least some components. When they show up in the blueprint tree, then they are |
||
//TODO(ab): for DataResult, walk the data result tree instead! | ||
Item::DataResult(_, instance_path) => ctx | ||
.viewer_context | ||
.entity_db | ||
.tree() | ||
.subtree(&instance_path.entity_path) | ||
.is_some_and(|subtree| !subtree.is_leaf()), | ||
} | ||
} | ||
|
||
fn label(&self, _ctx: &ContextMenuContext<'_>) -> String { | ||
match self { | ||
CollapseExpandAllAction::CollapseAll => "Collapse all".to_owned(), | ||
CollapseExpandAllAction::ExpandAll => "Expand all".to_owned(), | ||
} | ||
} | ||
|
||
fn process_container(&self, ctx: &ContextMenuContext<'_>, container_id: &ContainerId) { | ||
ctx.viewport_blueprint | ||
.visit_contents_in_container(container_id, &mut |contents| match contents { | ||
Contents::Container(container_id) => CollapseScope::BlueprintTree | ||
.container(*container_id) | ||
.set_open(&ctx.egui_context, self.open()), | ||
Contents::SpaceView(space_view_id) => self.process_space_view(ctx, space_view_id), | ||
}); | ||
} | ||
|
||
fn process_space_view(&self, ctx: &ContextMenuContext<'_>, space_view_id: &SpaceViewId) { | ||
CollapseScope::BlueprintTree | ||
.space_view(*space_view_id) | ||
.set_open(&ctx.egui_context, self.open()); | ||
|
||
let query_result = ctx.viewer_context.lookup_query_result(*space_view_id); | ||
let result_tree = &query_result.tree; | ||
if let Some(root_node) = result_tree.root_node() { | ||
self.process_data_result( | ||
ctx, | ||
space_view_id, | ||
&InstancePath::entity_splat(root_node.data_result.entity_path.clone()), | ||
); | ||
} | ||
} | ||
|
||
fn process_data_result( | ||
&self, | ||
ctx: &ContextMenuContext<'_>, | ||
space_view_id: &SpaceViewId, | ||
instance_path: &InstancePath, | ||
) { | ||
//TODO(ab): here we should in principle walk the DataResult tree instead of the entity tree | ||
// but the current API isn't super ergonomic. | ||
let Some(subtree) = ctx | ||
.viewer_context | ||
.entity_db | ||
.tree() | ||
.subtree(&instance_path.entity_path) | ||
else { | ||
return; | ||
}; | ||
|
||
subtree.visit_children_recursively(&mut |entity_path, _| { | ||
CollapseScope::BlueprintTree | ||
.data_result(*space_view_id, entity_path.clone()) | ||
.set_open(&ctx.egui_context, self.open()); | ||
}); | ||
} | ||
|
||
fn process_instance_path(&self, ctx: &ContextMenuContext<'_>, instance_path: &InstancePath) { | ||
let Some(subtree) = ctx | ||
.viewer_context | ||
.entity_db | ||
.tree() | ||
.subtree(&instance_path.entity_path) | ||
else { | ||
return; | ||
}; | ||
|
||
subtree.visit_children_recursively(&mut |entity_path, _| { | ||
CollapseScope::StreamsTree | ||
.entity(entity_path.clone()) | ||
.set_open(&ctx.egui_context, self.open()); | ||
}); | ||
} | ||
} | ||
|
||
impl CollapseExpandAllAction { | ||
fn open(&self) -> bool { | ||
match self { | ||
CollapseExpandAllAction::CollapseAll => false, | ||
CollapseExpandAllAction::ExpandAll => true, | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
import random | ||
from argparse import Namespace | ||
from uuid import uuid4 | ||
|
||
import numpy as np | ||
import rerun as rr | ||
|
||
README = """ | ||
# Context Menu - Add entity to new space view | ||
|
||
## Blueprint tree | ||
|
||
* Reset the blueprint. | ||
* Right-click on Viewport and select "Collapse all". Check everything is collapsed by manually expending everything. | ||
* Right-click on Viewport and select "Collapse all" and then "Expend all". Check everything is expanded. | ||
|
||
## Streams tree | ||
|
||
* Same as above, with the `world/` entity. | ||
|
||
|
||
## Multi-selection | ||
|
||
* Same as above, with both the viewport (blueprint tree) and `world/` (streams tree) selected. | ||
""" | ||
|
||
|
||
def log_readme() -> None: | ||
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), timeless=True) | ||
|
||
|
||
def log_some_space_views() -> None: | ||
# TODO(ab): add a deep-ish container hierarchy blueprint for more collapse/expand fun | ||
|
||
rr.set_time_sequence("frame_nr", 0) | ||
|
||
rr.log("/", rr.Boxes3D(centers=[0, 0, 0], half_sizes=[1, 1, 1])) | ||
rr.log("/world/robot/arm/actuator/thing", rr.Boxes3D(centers=[0.5, 0, 0], half_sizes=[0.1, 0.1, 0.1])) | ||
|
||
|
||
def run(args: Namespace) -> None: | ||
# TODO(cmc): I have no idea why this works without specifying a `recording_id`, but | ||
# I'm not gonna rely on it anyway. | ||
rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4()) | ||
|
||
log_readme() | ||
log_some_space_views() | ||
|
||
|
||
if __name__ == "__main__": | ||
import argparse | ||
|
||
parser = argparse.ArgumentParser(description="Interactive release checklist") | ||
rr.script_add_args(parser) | ||
args = parser.parse_args() | ||
run(args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah whatever, not a big issue I think