Skip to content

Commit

Permalink
Selecting/hovering components now highlights their parent entity (#4748)
Browse files Browse the repository at this point in the history
### What

* Fixes #2574

Note that this is implemented internally using what we call a "sibling
selection" - we preserve the information that the entity itself within
any space view isn't selected directly but only indirectly. Different ui
elements can react different to sibling selections versus "real"
selections - e.g. the blueprint view does not react to sibling
selections. We do however show the full outline in the space view.

Took the opportunity to make the space view highlights determination
code a bit more readable. Tested it a bit to make sure that the behavior
is still the same.


----

<img width="1153" alt="image"
src="https://github.com/rerun-io/rerun/assets/1220815/6acd79ac-e3ed-48cf-aa34-c2d98e900d96">
Note the highlight of the points in the space view - this didn't happen
before.

----


<img width="1158" alt="image"
src="https://github.com/rerun-io/rerun/assets/1220815/739ba24b-9282-472d-9224-98793a3357af">
Similar, hovering highlights are even more generous now, making all
entities hover-highlighted whenever an entity component path with the
same entity is hovered (subltey: the 0/1/2 on the selection panel in the
picture are *not* hover highlighted since they are specific instance
paths)

---

### 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/4748/index.html)
* Using examples from latest `main` build:
[app.rerun.io](https://app.rerun.io/pr/4748/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/4748/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

- [PR Build Summary](https://build.rerun.io/pr/4748)
- [Docs
preview](https://rerun.io/preview/1f0b646d7d7df95d6ec1512667079bce5759e0f4/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/1f0b646d7d7df95d6ec1512667079bce5759e0f4/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
Wumpf authored Jan 10, 2024
1 parent aaa8028 commit 7a921a8
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 97 deletions.
17 changes: 16 additions & 1 deletion crates/re_viewer_context/src/selection_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,26 @@ impl ApplicationSelectionState {
.iter_items()
.any(|current| match current {
Item::StoreId(_)
| Item::ComponentPath(_)
| Item::SpaceView(_)
| Item::DataBlueprintGroup(_, _, _)
| Item::Container(_) => current == test,

Item::ComponentPath(component_path) => match test {
Item::StoreId(_)
| Item::SpaceView(_)
| Item::DataBlueprintGroup(_, _, _)
| Item::Container(_) => false,

Item::ComponentPath(test_component_path) => {
test_component_path == component_path
}

Item::InstancePath(_, test_instance_path) => {
!test_instance_path.instance_key.is_specific()
&& test_instance_path.entity_path == component_path.entity_path
}
},

Item::InstancePath(current_space_view_id, current_instance_path) => {
if let Item::InstancePath(test_space_view_id, test_instance_path) = test {
// For both space view id and instance index we want to be inclusive,
Expand Down
60 changes: 57 additions & 3 deletions crates/re_viewer_context/src/space_view/highlights.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,64 @@
use nohash_hasher::IntMap;

use re_entity_db::InstancePath;
use re_log_types::EntityPathHash;
use re_renderer::OutlineMaskPreference;
use re_types::components::InstanceKey;

use crate::InteractionHighlight;
use crate::{HoverHighlight, InteractionHighlight, SelectionHighlight};

/// Highlights of a specific entity path in a specific space view.
///
/// Using this in bulk on many instances is faster than querying single objects.
#[derive(Default)]
pub struct SpaceViewEntityHighlight {
pub overall: InteractionHighlight,
pub instances: ahash::HashMap<InstanceKey, InteractionHighlight>,
overall: InteractionHighlight,
instances: ahash::HashMap<InstanceKey, InteractionHighlight>,
}

impl SpaceViewEntityHighlight {
/// Adds a new highlight to the entity highlight, combining it with existing highlights.
#[inline]
pub fn add(&mut self, instance: &InstancePath, highlight: InteractionHighlight) {
let highlight_target = if let Some(selected_index) = instance.instance_key.specific_index()
{
self.instances.entry(selected_index).or_default()
} else {
&mut self.overall
};
*highlight_target = (*highlight_target).max(highlight);
}

/// Adds a new selection highlight to the entity highlight, combining it with existing highlights.
#[inline]
pub fn add_selection(&mut self, instance: &InstancePath, selection: SelectionHighlight) {
self.add(
instance,
InteractionHighlight {
selection,
hover: HoverHighlight::None,
},
);
}

/// Adds a new hover highlight to the entity highlight, combining it with existing highlights.
#[inline]
pub fn add_hover(&mut self, instance: &InstancePath, hover: HoverHighlight) {
self.add(
instance,
InteractionHighlight {
selection: SelectionHighlight::None,
hover,
},
);
}
}

#[derive(Copy, Clone)]
pub struct OptionalSpaceViewEntityHighlight<'a>(Option<&'a SpaceViewEntityHighlight>);

impl<'a> OptionalSpaceViewEntityHighlight<'a> {
#[inline]
pub fn index_highlight(&self, instance_key: InstanceKey) -> InteractionHighlight {
match self.0 {
Some(entity_highlight) => entity_highlight
Expand Down Expand Up @@ -46,6 +86,17 @@ impl SpaceViewOutlineMasks {
.unwrap_or_default()
.with_fallback_to(self.overall)
}

/// Add a new outline mask to this entity path, combining it with existing masks.
pub fn add(&mut self, instance: &InstancePath, preference: OutlineMaskPreference) {
let outline_mask_target =
if let Some(selected_index) = instance.instance_key.specific_index() {
self.instances.entry(selected_index).or_default()
} else {
&mut self.overall
};
*outline_mask_target = preference.with_fallback_to(*outline_mask_target);
}
}

/// Highlights in a specific space view.
Expand All @@ -58,13 +109,15 @@ pub struct SpaceViewHighlights {
}

impl SpaceViewHighlights {
#[inline]
pub fn entity_highlight(
&self,
entity_path_hash: EntityPathHash,
) -> OptionalSpaceViewEntityHighlight<'_> {
OptionalSpaceViewEntityHighlight(self.highlighted_entity_paths.get(&entity_path_hash))
}

#[inline]
pub fn entity_outline_mask(&self, entity_path_hash: EntityPathHash) -> &SpaceViewOutlineMasks {
use std::sync::OnceLock;
static CELL: OnceLock<SpaceViewOutlineMasks> = OnceLock::new();
Expand All @@ -74,6 +127,7 @@ impl SpaceViewHighlights {
.unwrap_or_else(|| CELL.get_or_init(SpaceViewOutlineMasks::default))
}

#[inline]
pub fn any_outlines(&self) -> bool {
!self.outlines_masks.is_empty()
}
Expand Down
173 changes: 80 additions & 93 deletions crates/re_viewport/src/space_view_highlights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ use re_viewer_context::{
SpaceViewId, SpaceViewOutlineMasks,
};

/// Computes which things in a space view should received highlighting.
///
/// This method makes decisions which entities & instances should which kind of highlighting
/// based on the entities in a space view and the current selection/hover state.
pub fn highlights_for_space_view(
ctx: &re_viewer_context::ViewerContext<'_>,
space_view_id: SpaceViewId,
Expand All @@ -19,22 +23,15 @@ pub fn highlights_for_space_view(
let mut outlines_masks = IntMap::<EntityPathHash, SpaceViewOutlineMasks>::default();

let mut selection_mask_index: u8 = 0;
let mut hover_mask_index: u8 = 0;
let mut next_selection_mask = || {
// We don't expect to overflow u8, but if we do, don't use the "background mask".
selection_mask_index = selection_mask_index.wrapping_add(1).at_least(1);
OutlineMaskPreference::some(0, selection_mask_index)
};
let mut next_hover_mask = || {
// We don't expect to overflow u8, but if we do, don't use the "background mask".
hover_mask_index = hover_mask_index.wrapping_add(1).at_least(1);
OutlineMaskPreference::some(hover_mask_index, 0)
};

for current_selection in ctx.selection_state().current().iter_items() {
match current_selection {
Item::StoreId(_) | Item::ComponentPath(_) | Item::SpaceView(_) | Item::Container(_) => {
}
Item::StoreId(_) | Item::SpaceView(_) | Item::Container(_) => {}

Item::DataBlueprintGroup(group_space_view_id, query_id, group_entity_path) => {
// Unlike for selected objects/data we are more picky for data blueprints with our hover highlights
Expand All @@ -50,69 +47,66 @@ pub fn highlights_for_space_view(
.tree
.visit_group(group_entity_path, &mut |handle| {
if let Some(result) = query_result.tree.lookup_result(handle) {
let entity_hash = result.entity_path.hash();
let instance = result.entity_path.clone().into();

highlighted_entity_paths
.entry(result.entity_path.hash())
.entry(entity_hash)
.or_default()
.overall
.selection = SelectionHighlight::SiblingSelection;
let outline_mask_ids =
outlines_masks.entry(result.entity_path.hash()).or_default();
outline_mask_ids.overall =
selection_mask.with_fallback_to(outline_mask_ids.overall);
.add_selection(&instance, SelectionHighlight::SiblingSelection);
outlines_masks
.entry(entity_hash)
.or_default()
.add(&instance, selection_mask);
}
});
}
}

Item::ComponentPath(component_path) => {
let entity_hash = component_path.entity_path.hash();
let instance = component_path.entity_path.clone().into();

highlighted_entity_paths
.entry(entity_hash)
.or_default()
.add_selection(&instance, SelectionHighlight::SiblingSelection);
outlines_masks
.entry(entity_hash)
.or_default()
.add(&instance, next_selection_mask());
}

Item::InstancePath(selected_space_view_context, selected_instance) => {
{
let highlight = if *selected_space_view_context == Some(space_view_id) {
SelectionHighlight::Selection
} else {
SelectionHighlight::SiblingSelection
};

let highlighted_entity = highlighted_entity_paths
.entry(selected_instance.entity_path.hash())
.or_default();
let highlight_target = if let Some(selected_index) =
selected_instance.instance_key.specific_index()
{
&mut highlighted_entity
.instances
.entry(selected_index)
.or_default()
.selection
} else {
&mut highlighted_entity.overall.selection
};
*highlight_target = (*highlight_target).max(highlight);
}
{
let outline_mask_ids = outlines_masks
.entry(selected_instance.entity_path.hash())
.or_default();
let outline_mask_target = if let Some(selected_index) =
selected_instance.instance_key.specific_index()
{
outline_mask_ids
.instances
.entry(selected_index)
.or_default()
} else {
&mut outline_mask_ids.overall
};
*outline_mask_target =
next_selection_mask().with_fallback_to(*outline_mask_target);
}
let entity_hash = selected_instance.entity_path.hash();

let highlight = if *selected_space_view_context == Some(space_view_id) {
SelectionHighlight::Selection
} else {
SelectionHighlight::SiblingSelection
};
highlighted_entity_paths
.entry(entity_hash)
.or_default()
.add_selection(selected_instance, highlight);
outlines_masks
.entry(entity_hash)
.or_default()
.add(selected_instance, next_selection_mask());
}
};
}

let mut hover_mask_index: u8 = 0;
let mut next_hover_mask = || {
// We don't expect to overflow u8, but if we do, don't use the "background mask".
hover_mask_index = hover_mask_index.wrapping_add(1).at_least(1);
OutlineMaskPreference::some(hover_mask_index, 0)
};

for current_hover in ctx.selection_state().hovered().iter_items() {
match current_hover {
Item::StoreId(_) | Item::ComponentPath(_) | Item::SpaceView(_) | Item::Container(_) => {
}
Item::StoreId(_) | Item::SpaceView(_) | Item::Container(_) => {}

Item::DataBlueprintGroup(group_space_view_id, query_id, group_entity_path) => {
// Unlike for selected objects/data we are more picky for data blueprints with our hover highlights
Expand All @@ -127,51 +121,44 @@ pub fn highlights_for_space_view(
.tree
.visit_group(group_entity_path, &mut |handle| {
if let Some(result) = query_result.tree.lookup_result(handle) {
let instance = result.entity_path.clone().into();
highlighted_entity_paths
.entry(result.entity_path.hash())
.or_default()
.overall
.hover = HoverHighlight::Hovered;
let mask =
outlines_masks.entry(result.entity_path.hash()).or_default();
mask.overall = hover_mask.with_fallback_to(mask.overall);
.add_hover(&instance, HoverHighlight::Hovered);
outlines_masks
.entry(result.entity_path.hash())
.or_default()
.add(&instance, hover_mask);
}
});
}
}

Item::ComponentPath(component_path) => {
let entity_hash = component_path.entity_path.hash();
let instance = component_path.entity_path.clone().into();

highlighted_entity_paths
.entry(entity_hash)
.or_default()
.add_hover(&instance, HoverHighlight::Hovered);
outlines_masks
.entry(entity_hash)
.or_default()
.add(&instance, next_hover_mask());
}

Item::InstancePath(_, selected_instance) => {
{
let highlighted_entity = highlighted_entity_paths
.entry(selected_instance.entity_path.hash())
.or_default();

let highlight_target = if let Some(selected_index) =
selected_instance.instance_key.specific_index()
{
&mut highlighted_entity
.instances
.entry(selected_index)
.or_default()
.hover
} else {
&mut highlighted_entity.overall.hover
};
*highlight_target = HoverHighlight::Hovered;
}
{
let outlined_entity = outlines_masks
.entry(selected_instance.entity_path.hash())
.or_default();
let outline_mask_target = if let Some(selected_index) =
selected_instance.instance_key.specific_index()
{
outlined_entity.instances.entry(selected_index).or_default()
} else {
&mut outlined_entity.overall
};
*outline_mask_target = next_hover_mask().with_fallback_to(*outline_mask_target);
}
let entity_hash = selected_instance.entity_path.hash();
highlighted_entity_paths
.entry(entity_hash)
.or_default()
.add_hover(selected_instance, HoverHighlight::Hovered);
outlines_masks
.entry(entity_hash)
.or_default()
.add(selected_instance, next_hover_mask());
}
};
}
Expand Down

0 comments on commit 7a921a8

Please sign in to comment.