Skip to content

Commit

Permalink
item selections now are associated with an optional space context
Browse files Browse the repository at this point in the history
  • Loading branch information
Wumpf committed Dec 20, 2023
1 parent fac9eda commit ecdadae
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 286 deletions.
37 changes: 12 additions & 25 deletions crates/re_data_ui/src/item_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use egui::Ui;
use re_data_store::{EntityTree, InstancePath};
use re_log_types::{ComponentPath, EntityPath, TimeInt, Timeline};
use re_viewer_context::{
DataQueryId, HoverHighlight, Item, SelectedSpaceContext, Selection, SpaceViewId, UiVerbosity,
ViewerContext,
DataQueryId, HoverHighlight, Item, Selection, SpaceViewId, UiVerbosity, ViewerContext,
};

use super::DataUi;
Expand Down Expand Up @@ -98,7 +97,7 @@ pub fn instance_path_button_to(
let item = Item::InstancePath(space_view_id, instance_path.clone());

let response = ui
.selectable_label(ctx.selection().contains(&item), text)
.selectable_label(ctx.selection().contains_item(&item), text)
.on_hover_ui(|ui| {
instance_hover_card_ui(ui, ctx, instance_path);
});
Expand Down Expand Up @@ -206,7 +205,7 @@ pub fn component_path_button_to(
component_path: &ComponentPath,
) -> egui::Response {
let item = Item::ComponentPath(component_path.clone());
let response = ui.selectable_label(ctx.selection().contains(&item), text);
let response = ui.selectable_label(ctx.selection().contains_item(&item), text);
cursor_interact_with_selectable(ctx, response, item)
}

Expand All @@ -225,7 +224,7 @@ pub fn data_blueprint_group_button_to(
ui,
&re_ui::icons::CONTAINER,
text,
ctx.selection().contains(&item),
ctx.selection().contains_item(&item),
)
.on_hover_text("Group");
cursor_interact_with_selectable(ctx, response, item)
Expand All @@ -243,7 +242,7 @@ pub fn data_blueprint_button_to(
InstancePath::entity_splat(entity_path.clone()),
);
let response = ui
.selectable_label(ctx.selection().contains(&item), text)
.selectable_label(ctx.selection().contains_item(&item), text)
.on_hover_ui(|ui| {
entity_hover_card_ui(ui, ctx, entity_path);
});
Expand Down Expand Up @@ -314,7 +313,7 @@ pub fn cursor_interact_with_selectable(
let is_item_hovered =
ctx.selection_state().highlight_for_ui_element(&item) == HoverHighlight::Hovered;

select_hovered_on_click(ctx, &response, &[item], None);
select_hovered_on_click(ctx, &response, item);
// TODO(andreas): How to deal with shift click for selecting ranges?

if is_item_hovered {
Expand All @@ -328,35 +327,23 @@ pub fn cursor_interact_with_selectable(
pub fn select_hovered_on_click(
ctx: &ViewerContext<'_>,
response: &egui::Response,
items: &[Item],
space_context: Option<SelectedSpaceContext>,
selection: impl Into<Selection>,
) {
re_tracing::profile_function!();

let items = ctx
.resolve_mono_instance_path_items(items.iter())
.collect::<Vec<_>>();
let mut selection = selection.into();
selection.resolve_mono_instance_path_items(ctx);
let selection_state = ctx.selection_state();

if response.hovered() {
selection_state.set_hovered(Selection {
items: items.clone().into_iter().into(),
space_context: space_context.clone().into_iter().collect(),
});
selection_state.set_hovered(selection.clone());
}

if response.clicked() {
if response.ctx.input(|i| i.modifiers.command) {
dbg!("click with added context {:?}", &space_context);
selection_state.toggle_selection(items);
if let Some(space_context) = space_context {
selection_state.add_selected_space_context(space_context);
}
selection_state.toggle_selection(selection);
} else {
selection_state.set_selection(Selection {
items: items.into_iter().into(),
space_context: space_context.into_iter().collect(),
});
selection_state.set_selection(selection);
}
}
}
Expand Down
62 changes: 36 additions & 26 deletions crates/re_space_view_spatial/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,35 +624,45 @@ pub fn picking(
};
}

let hovered_space = match spatial_kind {
SpatialSpaceViewKind::TwoD => SelectedSpaceContext::TwoD {
space_2d: query.space_origin.clone(),
pos: picking_context
.pointer_in_space2d
.extend(depth_at_pointer.unwrap_or(f32::INFINITY)),
},
SpatialSpaceViewKind::ThreeD => {
let hovered_point = picking_result.space_position();
SelectedSpaceContext::ThreeD {
space_3d: query.space_origin.clone(),
pos: hovered_point,
tracked_space_camera: state.state_3d.tracked_camera.clone(),
point_in_space_cameras: parts
.get::<CamerasPart>()?
.space_cameras
.iter()
.map(|cam| {
(
cam.ent_path.clone(),
hovered_point.and_then(|pos| cam.project_onto_2d(pos)),
)
})
.collect(),
// Associate the hovered space with the first item in the hovered item list.
// If we were to add several, space views might render unnecessary additional hints.
// TODO(andreas): Should there be context if no item is hovered at all? There's no usecase for that today it seems.
let mut hovered_items = hovered_items
.into_iter()
.map(|item| (item, None))
.collect::<Vec<_>>();

if let Some((_, context)) = hovered_items.first_mut() {
*context = Some(match spatial_kind {
SpatialSpaceViewKind::TwoD => SelectedSpaceContext::TwoD {
space_2d: query.space_origin.clone(),
pos: picking_context
.pointer_in_space2d
.extend(depth_at_pointer.unwrap_or(f32::INFINITY)),
},
SpatialSpaceViewKind::ThreeD => {
let hovered_point = picking_result.space_position();
SelectedSpaceContext::ThreeD {
space_3d: query.space_origin.clone(),
pos: hovered_point,
tracked_space_camera: state.state_3d.tracked_camera.clone(),
point_in_space_cameras: parts
.get::<CamerasPart>()?
.space_cameras
.iter()
.map(|cam| {
(
cam.ent_path.clone(),
hovered_point.and_then(|pos| cam.project_onto_2d(pos)),
)
})
.collect(),
}
}
}
});
};

item_ui::select_hovered_on_click(ctx, &response, &hovered_items, Some(hovered_space));
item_ui::select_hovered_on_click(ctx, &response, re_viewer_context::Selection(hovered_items));

Ok(response)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/re_space_view_spatial/src/ui_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ pub fn view_3d(
state.state_3d.camera_before_tracked_camera = None;

// While hovering an entity, focuses the camera on it.
if let Some(Item::InstancePath(_, instance_path)) = ctx.hovered().first() {
if let Some((Item::InstancePath(_, instance_path), _)) = ctx.hovered().first() {
if let Some(camera) = find_camera(space_cameras, &instance_path.entity_path) {
state.state_3d.camera_before_tracked_camera =
state.state_3d.orbit_eye.map(|eye| eye.to_eye());
Expand Down
8 changes: 3 additions & 5 deletions crates/re_time_panel/src/data_density_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,12 +481,10 @@ pub fn data_density_graph_ui(
);

if 0 < num_hovered_messages {
ctx.rec_cfg
.selection_state
.set_hovered(std::iter::once(item.clone()));
ctx.selection_state().set_hovered(item.clone());

if time_area_response.clicked_by(egui::PointerButton::Primary) {
ctx.set_single_selection(item);
ctx.selection_state().set_selection(item.clone());
time_ctrl.set_time(hovered_time_range.min);
time_ctrl.pause();
} else if !ui.ctx().memory(|mem| mem.is_anything_being_dragged()) {
Expand All @@ -503,7 +501,7 @@ pub fn data_density_graph_ui(
}

fn graph_color(ctx: &ViewerContext<'_>, item: &Item, ui: &egui::Ui) -> Color32 {
let is_selected = ctx.selection().contains(item);
let is_selected = ctx.selection().contains_item(item);
if is_selected {
make_brighter(ui.visuals().widgets.active.fg_stroke.color)
} else {
Expand Down
16 changes: 5 additions & 11 deletions crates/re_time_panel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ mod time_selection_ui;

use egui::emath::Rangef;
use egui::{pos2, Color32, CursorIcon, NumExt, Painter, PointerButton, Rect, Shape, Ui, Vec2};
use std::slice;

use re_data_store::{EntityTree, InstancePath, TimeHistogram};
use re_data_ui::item_ui;
Expand Down Expand Up @@ -484,7 +483,7 @@ impl TimePanel {
let default_open = tree.path.len() <= 1 && !tree.is_leaf();

let item = Item::InstancePath(None, InstancePath::entity_splat(tree.path.clone()));
let is_selected = ctx.selection().contains(&item);
let is_selected = ctx.selection().contains_item(&item);
let is_item_hovered =
ctx.selection_state().highlight_for_ui_element(&item) == HoverHighlight::Hovered;

Expand Down Expand Up @@ -517,7 +516,7 @@ impl TimePanel {
let response = response
.on_hover_ui(|ui| re_data_ui::item_ui::entity_hover_card_ui(ui, ctx, &tree.path));

item_ui::select_hovered_on_click(ctx, &response, slice::from_ref(&item), None);
item_ui::select_hovered_on_click(ctx, &response, item.clone());

let is_closed = body_response.is_none();
let response_rect = response.rect;
Expand Down Expand Up @@ -611,7 +610,7 @@ impl TimePanel {
let response =
re_data_ui::temporary_style_ui_for_component(ui, component_name, |ui| {
ListItem::new(ctx.re_ui, short_component_name)
.selected(ctx.selection().contains(&item))
.selected(ctx.selection().contains_item(&item))
.width_allocation_mode(WidthAllocationMode::Compact)
.force_hovered(
ctx.selection_state().highlight_for_ui_element(&item)
Expand All @@ -626,12 +625,7 @@ impl TimePanel {

ui.set_clip_rect(clip_rect_save);

re_data_ui::item_ui::select_hovered_on_click(
ctx,
&response,
slice::from_ref(&item),
None,
);
re_data_ui::item_ui::select_hovered_on_click(ctx, &response, item.clone());

let response_rect = response.rect;

Expand Down Expand Up @@ -752,7 +746,7 @@ fn highlight_timeline_row(
) {
let item_hovered =
ctx.selection_state().highlight_for_ui_element(item) == HoverHighlight::Hovered;
let item_selected = ctx.selection().contains(item);
let item_selected = ctx.selection().contains_item(item);
let bg_color = if item_selected {
Some(ui.visuals().selection.bg_fill.gamma_multiply(0.4))
} else if item_hovered {
Expand Down
32 changes: 18 additions & 14 deletions crates/re_viewer/src/ui/selection_history_ui.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use egui::RichText;
use egui_tiles::Tile;
use re_ui::UICommand;
use re_viewer_context::{Item, ItemCollection, Selection, SelectionHistory};
use re_viewer_context::{Item, Selection, SelectionHistory};
use re_viewport::ViewportBlueprint;

// ---
Expand Down Expand Up @@ -40,7 +40,7 @@ impl SelectionHistoryUi {
\n\
Right-click for more.",
UICommand::SelectionPrevious.format_shortcut_tooltip_suffix(ui.ctx()),
item_collection_to_string(blueprint, &previous.selection.items),
selection_to_string(blueprint, &previous.selection),
));

let mut return_current = false;
Expand Down Expand Up @@ -90,7 +90,7 @@ impl SelectionHistoryUi {
\n\
Right-click for more.",
UICommand::SelectionNext.format_shortcut_tooltip_suffix(ui.ctx()),
item_collection_to_string(blueprint, &next.selection.items),
selection_to_string(blueprint, &next.selection),
));

let mut return_current = false;
Expand Down Expand Up @@ -135,16 +135,16 @@ impl SelectionHistoryUi {
ui.horizontal(|ui| {
{
// borrow checker workaround
let sel = item_collection_to_string(blueprint, &sel.items);
let sel = selection_to_string(blueprint, sel);
if ui
.selectable_value(&mut history.current, index, sel)
.clicked()
{
ui.close_menu();
}
}
if sel.items.len() == 1 {
item_kind_ui(ui, sel.items.iter().next().unwrap());
if sel.iter_items().count() == 1 {
item_kind_ui(ui, sel.iter_items().next().unwrap());
}
});
}
Expand All @@ -157,14 +157,18 @@ fn item_kind_ui(ui: &mut egui::Ui, sel: &Item) {
ui.weak(RichText::new(format!("({})", sel.kind())));
}

fn item_collection_to_string(blueprint: &ViewportBlueprint, items: &ItemCollection) -> String {
if items.is_empty() {
// Can happen if no items are selected but the space context changed.
"<space context>".to_owned()
} else if items.len() == 1 {
item_to_string(blueprint, items.iter().next().unwrap())
} else if let Some(kind) = items.are_all_same_kind() {
format!("{}x {}s", items.len(), kind)
fn selection_to_string(blueprint: &ViewportBlueprint, selection: &Selection) -> String {
debug_assert!(!selection.is_empty()); // history never contains empty selections.
if selection.len() == 1 {
if let Some(item) = selection.iter_items().next() {
item_to_string(blueprint, item)
} else {
// All items got removed or weren't there to begin with.
debug_assert!(selection.iter_space_context().next().is_some()); // Should never keep both empty item & context list.
"<space context>".to_owned()
}
} else if let Some(kind) = selection.are_all_items_same_kind() {
format!("{}x {}s", selection.len(), kind)
} else {
"<multiple selections>".to_owned()
}
Expand Down
6 changes: 3 additions & 3 deletions crates/re_viewer/src/ui/selection_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ impl SelectionPanel {
// no gap before the first item title
ui.add_space(-ui.spacing().item_spacing.y);

let selection = ctx.selection().to_vec();
let selection = ctx.selection();
let multi_selection_verbosity = if selection.len() > 1 {
UiVerbosity::LimitHeight
} else {
UiVerbosity::Full
};
for (i, item) in selection.iter().enumerate() {
for (i, item) in selection.iter_items().enumerate() {
ui.push_id(i, |ui| {
what_is_selected_ui(ui, ctx, viewport.blueprint, item);

Expand Down Expand Up @@ -171,7 +171,7 @@ fn space_view_button(
space_view: &re_viewport::SpaceViewBlueprint,
) -> egui::Response {
let item = Item::SpaceView(space_view.id);
let is_selected = ctx.selection().contains(&item);
let is_selected = ctx.selection().contains_item(&item);

let response = ctx
.re_ui
Expand Down
Loading

0 comments on commit ecdadae

Please sign in to comment.