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

Selecting/hovering components now highlights their parent entity #4748

Merged
merged 4 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>,
Copy link
Member

Choose a reason for hiding this comment

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

Not added in this PR but surely an InstanceKey should be NoHash and therefore usable with a IntMap, right? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

thought you're right at first but actually InstanceKey is not NoHash and it makes sense: Instance keys are usually ordered and low entropy, they are terrible hashes as is which is why we want to apply a hash function to them

}

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
Loading