Skip to content

Filter entities in the UI (part 9): Support / in queries #8933

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

Merged
merged 13 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from 11 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
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6713,6 +6713,7 @@ dependencies = [
"egui_tiles",
"getrandom",
"itertools 0.13.0",
"nohash-hasher",
"once_cell",
"parking_lot",
"rand",
Expand Down
243 changes: 133 additions & 110 deletions crates/viewer/re_blueprint_tree/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use re_entity_db::InstancePath;
use re_log_types::external::re_types_core::ViewClassIdentifier;
use re_log_types::EntityPath;
use re_types::blueprint::components::Visible;
use re_ui::filter_widget::FilterMatcher;
use re_ui::filter_widget::{FilterMatcher, PathRanges};
use re_viewer_context::{
CollapseScope, ContainerId, Contents, ContentsName, DataQueryResult, DataResultNode, Item,
ViewId, ViewerContext, VisitorControlFlow,
Expand Down Expand Up @@ -216,19 +216,21 @@ impl ViewData {
//

let mut hierarchy = Vec::with_capacity(10);
let mut hierarchy_highlights = PathRanges::default();
let origin_tree = DataResultData::from_data_result_and_filter(
ctx,
view_blueprint,
query_result,
&DataResultNodeOrPath::from_path_lookup(result_tree, &view_blueprint.space_origin),
false,
&mut hierarchy,
&mut hierarchy_highlights,
filter_matcher,
false,
);

debug_assert!(hierarchy.is_empty());
hierarchy.clear();
hierarchy_highlights.clear();

//
// Data results outside the view origin subtree (a.k.a projections)
Expand Down Expand Up @@ -266,12 +268,13 @@ impl ViewData {
&DataResultNodeOrPath::DataResultNode(node),
true,
&mut hierarchy,
&mut hierarchy_highlights,
filter_matcher,
false,
);

debug_assert!(hierarchy.is_empty());
hierarchy.clear();
hierarchy_highlights.clear();

projection_tree
})
Expand Down Expand Up @@ -322,7 +325,7 @@ impl ViewData {
// ---

/// The various kind of things that may be represented in a data result tree.
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "testing", derive(serde::Serialize, serde::Deserialize))]
pub enum DataResultKind {
/// This is a regular entity part of a data result (or the tree that contains it).
Expand Down Expand Up @@ -368,8 +371,8 @@ impl DataResultData {
data_result_or_path: &DataResultNodeOrPath<'_>,
projection: bool,
hierarchy: &mut Vec<String>,
hierarchy_highlights: &mut PathRanges,
filter_matcher: &FilterMatcher,
mut is_already_a_match: bool,
) -> Option<Self> {
re_tracing::profile_function!();

Expand All @@ -395,137 +398,157 @@ impl DataResultData {
};

//
// Filtering
// Gather some info about the current node…
//

// We must special case origin placeholder in projection. Although it does show up (as,
// well, a placeholder), it shouldn't be considered for matching as its entity part is not
// displayed.
let is_origin_placeholder = projection && entity_path == view_blueprint.space_origin;

if !is_already_a_match && !is_origin_placeholder {
let current_path_matches =
filter_matcher.matches_hierarchy(hierarchy.iter().map(|s| s.as_str()));
is_already_a_match |= current_path_matches;
enum LeafOrNot<'a> {
Leaf,
NotLeaf(&'a DataResultNode),
}

// here are some highlights if we end up being a match
let highlight_sections = || -> SmallVec<_> {
if is_origin_placeholder {
SmallVec::new()
} else if let Some(entity_part_ui_string) = &entity_part_ui_string {
filter_matcher
.find_ranges_for_keywords(entity_part_ui_string)
.collect()
} else {
SmallVec::new()
}
};

//
// "Nominal" data result node (extracted for deduplication).
//

let view_id = view_blueprint.id;
let mut from_data_result_node =
|data_result_node: &DataResultNode, entity_path: EntityPath, label, default_open| {
let mut children = data_result_node
.children
.iter()
.filter_map(|child_handle| {
let child_node = query_result.tree.lookup_node(*child_handle);

debug_assert!(
child_node.is_some(),
"DataResultNode {data_result_node:?} has an invalid child"
);

child_node.and_then(|child_node| {
Self::from_data_result_and_filter(
ctx,
view_blueprint,
query_result,
&DataResultNodeOrPath::DataResultNode(child_node),
projection,
hierarchy,
filter_matcher,
is_already_a_match,
)
})
})
.collect_vec();

// This is needed because `DataResultNode` stores children in a `SmallVec`, offering
// no guarantees about ordering.
children.sort_by(|a, b| a.entity_path.cmp(&b.entity_path));

(is_already_a_match || !children.is_empty()).then(|| Self {
kind: DataResultKind::EntityPart,
entity_path,
visible,
view_id,
label,
highlight_sections: highlight_sections(),
default_open,
children,
})
};

//
// Handle all situations
//

let result = if projection {
// projections are collapsed by default
let default_open = filter_matcher.is_active();
struct NodeInfo<'a> {
leaf_or_not: LeafOrNot<'a>,
kind: DataResultKind,
default_open: bool,
}

#[expect(clippy::manual_map)]
let node_info = if projection {
if entity_path == view_blueprint.space_origin {
is_already_a_match.then(|| Self {
Some(NodeInfo {
leaf_or_not: LeafOrNot::Leaf,
kind: DataResultKind::OriginProjectionPlaceholder,
entity_path,
visible,
view_id,
label,
highlight_sections: highlight_sections(),
default_open,
children: vec![],
// not hierarchical anyway
default_open: false,
})
} else if let Some(data_result_node) = data_result_node {
from_data_result_node(
data_result_node,
entity_path,
label,
filter_matcher.is_active(),
)
Some(NodeInfo {
leaf_or_not: if data_result_node.children.is_empty() {
LeafOrNot::Leaf
} else {
LeafOrNot::NotLeaf(data_result_node)
},
kind: DataResultKind::EntityPart,
// projections are collapsed by default
default_open: filter_matcher.is_active(),
})
} else {
// TODO(ab): what are the circumstances for this? Should we warn about it?
None
}
} else {
// empty origin case
if entity_path == view_blueprint.space_origin && data_result_node.is_none() {
is_already_a_match.then(|| Self {
Some(NodeInfo {
leaf_or_not: LeafOrNot::Leaf,
kind: DataResultKind::EmptyOriginPlaceholder,
entity_path,
visible,
view_id,
label,
highlight_sections: highlight_sections(),
default_open: false, // not hierarchical anyway
children: vec![],
// not hierarchical anyway
default_open: false,
})
} else if let Some(data_result_node) = data_result_node {
let default_open = filter_matcher.is_active()
|| default_open_for_data_result(data_result_node.children.len());

from_data_result_node(data_result_node, entity_path, label, default_open)
Some(NodeInfo {
leaf_or_not: if data_result_node.children.is_empty() {
LeafOrNot::Leaf
} else {
LeafOrNot::NotLeaf(data_result_node)
},
kind: DataResultKind::EntityPart,
default_open: filter_matcher.is_active()
|| default_open_for_data_result(data_result_node.children.len()),
})
} else {
// TODO(ab): what are the circumstances for this? Should we warn about it?
None
}
};

//
// …then handle the node accordingly.
//

let result = node_info.and_then(|node_info| {
let (is_this_a_match, children) = match node_info.leaf_or_not {
LeafOrNot::Leaf => {
// Key insight: we only ever need to match the hierarchy from the leaf nodes.
// Non-leaf nodes know they are a match if any child remains after walking their
// subtree.

let highlights =
filter_matcher.match_path(hierarchy.iter().map(String::as_str));

let is_this_a_match = if let Some(highlights) = highlights {
hierarchy_highlights.merge(highlights);
true
} else {
false
};

(is_this_a_match, vec![])
}

LeafOrNot::NotLeaf(data_result_node) => {
let mut children = data_result_node
.children
.iter()
.filter_map(|child_handle| {
let child_node = query_result.tree.lookup_node(*child_handle);

debug_assert!(
child_node.is_some(),
"DataResultNode {data_result_node:?} has an invalid child"
);

child_node.and_then(|child_node| {
Self::from_data_result_and_filter(
ctx,
view_blueprint,
query_result,
&DataResultNodeOrPath::DataResultNode(child_node),
projection,
hierarchy,
hierarchy_highlights,
filter_matcher,
)
})
})
.collect_vec();

// This is needed because `DataResultNode` stores children in a `SmallVec`, offering
// no guarantees about ordering.
children.sort_by(|a, b| a.entity_path.cmp(&b.entity_path));

let is_this_a_match = !children.is_empty();

(is_this_a_match, children)
}
};

let highlight_sections = hierarchy_highlights.remove(hierarchy.len().saturating_sub(1));

// never highlight the placeholder
let highlight_sections =
if node_info.kind == DataResultKind::OriginProjectionPlaceholder {
SmallVec::new()
} else {
highlight_sections
.map(Iterator::collect)
.unwrap_or_default()
};

is_this_a_match.then(|| Self {
kind: node_info.kind,
entity_path,
visible,
view_id: view_blueprint.id,
label,
highlight_sections,
default_open: node_info.default_open,
children,
})
});

if should_pop {
hierarchy_highlights.remove(hierarchy.len().saturating_sub(1));
hierarchy.pop();
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,42 @@ root_container:
highlight_sections: []
default_open: false
children:
- kind: EntityPart
entity_path:
- path
- onto
visible: true
view_id:
id: 0864e1b8-cec4-1b82-5b17-b9d628b8f4fe
label: onto
highlight_sections: []
default_open: false
children:
- kind: EntityPart
entity_path:
- path
- onto
- their
visible: true
view_id:
id: 0864e1b8-cec4-1b82-5b17-b9d628b8f4fe
label: their
highlight_sections: []
default_open: false
children:
- kind: EntityPart
entity_path:
- path
- onto
- their
- coils
visible: true
view_id:
id: 0864e1b8-cec4-1b82-5b17-b9d628b8f4fe
label: coils
highlight_sections: []
default_open: false
children: []
- kind: OriginProjectionPlaceholder
entity_path:
- path
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading