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

Refactor Selection using IndexMap and make it more encapsulated #5569

Merged
merged 5 commits into from
Mar 19, 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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ half = "2.3.1"
hyper = "0.14"
image = { version = "0.24", default-features = false }
indent = "0.1"
indexmap = "2.1" # Version chosen to align with other dependencies
indicatif = "0.17.7" # Progress bar
infer = "0.15" # infer MIME type by checking the magic number signaturefer MIME type by checking the magic number signature
insta = "1.23"
Expand Down
2 changes: 1 addition & 1 deletion crates/re_space_view_spatial/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ pub fn picking(
});
};

ctx.select_hovered_on_click(&response, re_viewer_context::Selection(hovered_items));
ctx.select_hovered_on_click(&response, hovered_items.into_iter());

Ok(response)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/re_viewer/src/ui/selection_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ fn show_list_item_for_container_child(
&response,
SelectionUpdateBehavior::Ignore,
);
ctx.select_hovered_on_click(&response, std::iter::once(item));
ctx.select_hovered_on_click(&response, item);

if remove_contents {
viewport.blueprint.mark_user_interaction(ctx);
Expand Down
1 change: 1 addition & 0 deletions crates/re_viewer_context/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ egui.workspace = true
egui_tiles.workspace = true
glam = { workspace = true, features = ["serde"] }
half.workspace = true
indexmap = { workspace = true, features = ["std", "serde"] }
itertools.workspace = true
macaw.workspace = true
ndarray.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/re_viewer_context/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{ContainerId, SpaceViewId};
/// One "thing" in the UI.
///
/// This is the granularity of what is selectable and hoverable.
#[derive(Clone, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Deserialize, serde::Serialize)]
pub enum Item {
/// A recording (or blueprint)
StoreId(re_log_types::StoreId),
Expand Down
2 changes: 1 addition & 1 deletion crates/re_viewer_context/src/selection_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl SelectionHistory {

let mut i = 0;
self.stack.retain_mut(|selection| {
selection.retain(f);
selection.retain(|item, _| f(item));
let retain = !selection.is_empty();
if !retain && i <= self.current {
self.current = self.current.saturating_sub(1);
Expand Down
97 changes: 62 additions & 35 deletions crates/re_viewer_context/src/selection_state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use ahash::HashMap;
use indexmap::IndexMap;
use parking_lot::Mutex;

use re_entity_db::EntityPath;
Expand Down Expand Up @@ -82,66 +83,62 @@ impl InteractionHighlight {
///
/// Used to store what is currently selected and/or hovered.
#[derive(Debug, Default, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Selection(pub Vec<(Item, Option<SelectedSpaceContext>)>);
pub struct Selection(IndexMap<Item, Option<SelectedSpaceContext>>);

impl From<Item> for Selection {
#[inline]
fn from(val: Item) -> Self {
Selection(vec![(val, None)])
Selection([(val, None)].into())
}
}

impl<T> From<T> for Selection
where
T: Iterator<Item = Item>,
T: Iterator<Item = (Item, Option<SelectedSpaceContext>)>,
{
#[inline]
fn from(value: T) -> Self {
Selection(value.map(|item| (item, None)).collect())
}
}

impl std::ops::Deref for Selection {
type Target = Vec<(Item, Option<SelectedSpaceContext>)>;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl std::ops::DerefMut for Selection {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
Selection(value.collect())
}
}

impl Selection {
/// For each item in this selection, if it refers to the first element of an instance with a single element, resolve it to a splatted entity path.
pub fn resolve_mono_instance_path_items(&mut self, ctx: &ViewerContext<'_>) {
for (item, _) in self.iter_mut() {
*item =
resolve_mono_instance_path_item(&ctx.current_query(), ctx.entity_db.store(), item);
}
/// For each item in this selection, if it refers to the first element of an instance with a
/// single element, resolve it to a splatted entity path.
pub fn into_mono_instance_path_items(self, ctx: &ViewerContext<'_>) -> Self {
Selection(
self.0
.into_iter()
.map(|(item, space_ctx)| {
(
resolve_mono_instance_path_item(
&ctx.current_query(),
ctx.entity_db.store(),
&item,
),
space_ctx,
)
})
.collect(),
)
}

/// The first selected object if any.
pub fn first_item(&self) -> Option<&Item> {
self.0.first().map(|(item, _)| item)
self.0.keys().next()
}

/// Check if the selection contains a single item and returns it if so.
pub fn single_item(&self) -> Option<&Item> {
if self.0.len() == 1 {
Some(&self.0[0].0)
if self.len() == 1 {
self.first_item()
} else {
None
}
}

pub fn iter_items(&self) -> impl Iterator<Item = &Item> {
self.0.iter().map(|(item, _)| item)
self.0.keys()
}

pub fn iter_space_context(&self) -> impl Iterator<Item = &SelectedSpaceContext> {
Expand Down Expand Up @@ -169,8 +166,36 @@ impl Selection {
}

/// Retains elements that fulfill a certain condition.
pub fn retain(&mut self, f: impl Fn(&Item) -> bool) {
self.0.retain(|(item, _)| f(item));
pub fn retain(&mut self, f: impl FnMut(&Item, &mut Option<SelectedSpaceContext>) -> bool) {
self.0.retain(f);
}

/// Returns the number of items in the selection.
pub fn len(&self) -> usize {
self.0.len()
}

/// Check if the selection is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

/// Returns an iterator over the items and their selected space context.
pub fn iter(&self) -> impl Iterator<Item = (&Item, &Option<SelectedSpaceContext>)> {
self.0.iter()
}

/// Returns a mutable iterator over the items and their selected space context.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Item, &mut Option<SelectedSpaceContext>)> {
self.0.iter_mut()
}

/// Extend the selection with more items.
pub fn extend(
&mut self,
other: impl IntoIterator<Item = (Item, Option<SelectedSpaceContext>)>,
) {
self.0.extend(other);
}
}

Expand Down Expand Up @@ -268,14 +293,16 @@ impl ApplicationSelectionState {
pub fn toggle_selection(&self, toggle_items: Selection) {
re_tracing::profile_function!();

let mut toggle_items_set: HashMap<Item, Option<SelectedSpaceContext>> =
toggle_items.iter().cloned().collect();
let mut toggle_items_set: HashMap<Item, Option<SelectedSpaceContext>> = toggle_items
.iter()
.map(|(item, ctx)| (item.clone(), ctx.clone()))
.collect();
Wumpf marked this conversation as resolved.
Show resolved Hide resolved

let mut new_selection = self.selection_previous_frame.clone();

// If an item was already selected with the exact same context remove it.
// If an item was already selected and loses its context, remove it.
new_selection.0.retain(|(item, ctx)| {
new_selection.retain(|item, ctx| {
if let Some(new_ctx) = toggle_items_set.get(item) {
if new_ctx == ctx || new_ctx.is_none() {
toggle_items_set.remove(item);
Expand Down
5 changes: 2 additions & 3 deletions crates/re_viewer_context/src/viewer_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,15 @@ impl<'a> ViewerContext<'a> {
) {
re_tracing::profile_function!();

let mut selection = selection.into();
selection.resolve_mono_instance_path_items(self);
let selection = selection.into().into_mono_instance_path_items(self);
let selection_state = self.selection_state();

if response.hovered() {
selection_state.set_hovered(selection.clone());
}

if response.double_clicked() {
if let Some((item, _)) = selection.first() {
if let Some(item) = selection.first_item() {
self.command_sender
.send_system(crate::SystemCommand::SetFocus(item.clone()));
}
Expand Down
6 changes: 2 additions & 4 deletions crates/re_viewport/src/context_menu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ pub fn context_menu_ui_for_item(
// When the context menu is triggered open, we check if we're part of the selection,
// and, if not, we update the selection to include only the item that was clicked.
if item_response.hovered() && item_response.secondary_clicked() {
ctx.selection_state()
.set_selection(std::iter::once(item.clone()));
ctx.selection_state().set_selection(item.clone());

show_context_menu(&Selection::from(item.clone()));
} else {
Expand All @@ -80,8 +79,7 @@ pub fn context_menu_ui_for_item(

SelectionUpdateBehavior::OverrideSelection => {
if item_response.secondary_clicked() {
ctx.selection_state()
.set_selection(std::iter::once(item.clone()));
ctx.selection_state().set_selection(item.clone());
}

show_context_menu(&Selection::from(item.clone()));
Expand Down
Loading