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

Entities can be dragged from the blueprint tree and streams tree to an existing view in the viewport #8431

Merged
merged 24 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cae926a
WIP: enable dragging entities from the
abey79 Dec 10, 2024
9ed096f
Fix maximized view not getting highlighted
abey79 Dec 11, 2024
56bb57f
Dont unnecessarily pass a `ui`
abey79 Dec 11, 2024
6ddf6cf
Move the EntityAddInfo to re_viewport_blueprint to make it availalble…
abey79 Dec 11, 2024
62f5f5b
well, just don't... :shrug:
abey79 Dec 12, 2024
16c3fe8
consider visualizability when highlighting target view and adding ent…
abey79 Dec 12, 2024
e5db0d5
Merge branch 'main' into antoine/dnd-entities-to-existing-views
abey79 Dec 12, 2024
9df6e8b
lint
abey79 Dec 12, 2024
5bc0148
Merge branch 'main' into antoine/dnd-entities-to-existing-views
abey79 Dec 12, 2024
b7f267b
select view on successful drop + slightly dim view with blue color wh…
abey79 Dec 12, 2024
4539d4e
make drop target frame 2px wide (+ fix display bug in the blueprint t…
abey79 Dec 12, 2024
e6d4713
have the pill feedback be controllable from the drop location code. p…
abey79 Dec 12, 2024
5638e79
Update cursor based on feedback from drop zone
abey79 Dec 12, 2024
7b85ed3
Merge branch 'main' into antoine/dnd-entities-to-existing-views
abey79 Dec 12, 2024
3738fc9
Pill opacity 50/70% depending on feedback + fixed pilled being cropped
abey79 Dec 13, 2024
12b1a36
Don't force select on drag
abey79 Dec 13, 2024
2a7ad9f
Merge branch 'main' into antoine/dnd-entities-to-existing-views
abey79 Dec 13, 2024
8559af7
doclink
abey79 Dec 13, 2024
e7522f9
Merge branch 'main' into antoine/dnd-entities-to-existing-views
abey79 Dec 16, 2024
f5a7f3d
Add two release check list test
abey79 Dec 16, 2024
b76b8b3
Merge branch 'main' into antoine/dnd-entities-to-existing-views
abey79 Dec 16, 2024
4a4712a
Merge branch 'main' into antoine/dnd-entities-to-existing-views
abey79 Dec 16, 2024
a271689
Review comment
abey79 Dec 17, 2024
925f682
Merge branch 'main' into antoine/dnd-entities-to-existing-views
abey79 Dec 17, 2024
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 @@ -6851,6 +6851,7 @@ dependencies = [
"bit-vec",
"bitflags 2.6.0",
"bytemuck",
"crossbeam",
"directories",
"egui",
"egui-wgpu",
Expand Down
24 changes: 19 additions & 5 deletions crates/viewer/re_blueprint_tree/src/blueprint_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use re_types::blueprint::components::Visible;
use re_ui::{drag_and_drop::DropTarget, list_item, ContextExt as _, DesignTokens, UiExt as _};
use re_viewer_context::{
contents_name_style, icon_for_container_kind, CollapseScope, Contents, DataResultNodeOrPath,
DragAndDropPayload, SystemCommandSender,
DragAndDropFeedback, DragAndDropPayload, SystemCommandSender,
};
use re_viewer_context::{
ContainerId, DataQueryResult, DataResultNode, HoverHighlight, Item, ViewId, ViewerContext,
Expand Down Expand Up @@ -101,6 +101,7 @@ impl BlueprintTree {

// handle drag and drop interaction on empty space
self.handle_empty_space_drag_and_drop_interaction(
ctx,
viewport,
ui,
empty_space_response.rect,
Expand Down Expand Up @@ -192,6 +193,7 @@ impl BlueprintTree {
ctx.handle_select_hover_drag_interactions(&item_response, item, true);

self.handle_root_container_drag_and_drop_interaction(
ctx,
viewport,
ui,
Contents::Container(container_id),
Expand Down Expand Up @@ -275,6 +277,7 @@ impl BlueprintTree {
viewport.set_content_visibility(ctx, &content, visible);

self.handle_drag_and_drop_interaction(
ctx,
viewport,
ui,
content,
Expand Down Expand Up @@ -411,6 +414,7 @@ impl BlueprintTree {

viewport.set_content_visibility(ctx, &content, visible);
self.handle_drag_and_drop_interaction(
ctx,
viewport,
ui,
content,
Expand Down Expand Up @@ -629,6 +633,7 @@ impl BlueprintTree {

fn handle_root_container_drag_and_drop_interaction(
&mut self,
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &egui::Ui,
contents: Contents,
Expand Down Expand Up @@ -672,12 +677,13 @@ impl BlueprintTree {
);

if let Some(drop_target) = drop_target {
self.handle_contents_drop_target(viewport, ui, dragged_contents, &drop_target);
self.handle_contents_drop_target(ctx, viewport, ui, dragged_contents, &drop_target);
}
}

fn handle_drag_and_drop_interaction(
&mut self,
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &egui::Ui,
contents: Contents,
Expand Down Expand Up @@ -751,12 +757,13 @@ impl BlueprintTree {
);

if let Some(drop_target) = drop_target {
self.handle_contents_drop_target(viewport, ui, dragged_contents, &drop_target);
self.handle_contents_drop_target(ctx, viewport, ui, dragged_contents, &drop_target);
}
}

fn handle_empty_space_drag_and_drop_interaction(
&mut self,
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &egui::Ui,
empty_space: egui::Rect,
Expand Down Expand Up @@ -792,12 +799,13 @@ impl BlueprintTree {
usize::MAX,
);

self.handle_contents_drop_target(viewport, ui, dragged_contents, &drop_target);
self.handle_contents_drop_target(ctx, viewport, ui, dragged_contents, &drop_target);
}
}

fn handle_contents_drop_target(
&mut self,
ctx: &ViewerContext<'_>,
viewport: &ViewportBlueprint,
ui: &Ui,
dragged_contents: &[Contents],
Expand All @@ -816,6 +824,8 @@ impl BlueprintTree {
false
};
if dragged_contents.iter().any(parent_contains_dragged_content) {
ctx.drag_and_drop_manager
.set_feedback(DragAndDropFeedback::Reject);
return;
}

Expand All @@ -826,7 +836,9 @@ impl BlueprintTree {
);

let Contents::Container(target_container_id) = drop_target.target_parent_id else {
// this shouldn't append
// this shouldn't happen
ctx.drag_and_drop_manager
.set_feedback(DragAndDropFeedback::Reject);
return;
};

Expand All @@ -839,6 +851,8 @@ impl BlueprintTree {

egui::DragAndDrop::clear_payload(ui.ctx());
} else {
ctx.drag_and_drop_manager
.set_feedback(DragAndDropFeedback::Accept);
self.next_candidate_drop_parent_container_id = Some(target_container_id);
}
}
Expand Down
126 changes: 7 additions & 119 deletions crates/viewer/re_selection_panel/src/view_entity_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use re_data_ui::item_ui;
use re_entity_db::{EntityPath, EntityTree, InstancePath};
use re_log_types::{EntityPathFilter, EntityPathRule};
use re_ui::{list_item, UiExt as _};
use re_viewer_context::{DataQueryResult, ViewClassExt as _, ViewId, ViewerContext};
use re_viewport_blueprint::{ViewBlueprint, ViewportBlueprint};
use re_viewer_context::{DataQueryResult, ViewId, ViewerContext};
use re_viewport_blueprint::{
create_entity_add_info, CanAddToView, EntityAddInfo, ViewBlueprint, ViewportBlueprint,
};

/// Window for adding/removing entities from a view.
///
Expand Down Expand Up @@ -73,10 +75,9 @@ fn add_entities_ui(ctx: &ViewerContext<'_>, ui: &mut egui::Ui, view: &ViewBluepr
re_tracing::profile_function!();

let tree = &ctx.recording().tree();
// TODO(jleibs): Avoid clone
let query_result = ctx.lookup_query_result(view.id).clone();
let query_result = ctx.lookup_query_result(view.id);
let entity_path_filter = &view.contents.entity_path_filter;
let entities_add_info = create_entity_add_info(ctx, tree, view, &query_result);
let entities_add_info = create_entity_add_info(ctx, tree, view, query_result);

list_item::list_item_scope(ui, "view_entity_picker", |ui| {
add_entities_tree_ui(
Expand All @@ -85,7 +86,7 @@ fn add_entities_ui(ctx: &ViewerContext<'_>, ui: &mut egui::Ui, view: &ViewBluepr
&tree.path.to_string(),
tree,
view,
&query_result,
query_result,
entity_path_filter,
&entities_add_info,
);
Expand Down Expand Up @@ -263,116 +264,3 @@ fn add_entities_line_ui(
}
});
}

/// Describes if an entity path can be added to a view.
#[derive(Clone, PartialEq, Eq)]
enum CanAddToView {
Compatible { already_added: bool },
No { reason: String },
}

impl Default for CanAddToView {
fn default() -> Self {
Self::Compatible {
already_added: false,
}
}
}

impl CanAddToView {
/// Can be generally added but view might already have this element.
pub fn is_compatible(&self) -> bool {
match self {
Self::Compatible { .. } => true,
Self::No { .. } => false,
}
}

/// Can be added and view doesn't have it already.
pub fn is_compatible_and_missing(&self) -> bool {
self == &Self::Compatible {
already_added: false,
}
}

pub fn join(&self, other: &Self) -> Self {
match self {
Self::Compatible { already_added } => {
let already_added = if let Self::Compatible {
already_added: already_added_other,
} = other
{
*already_added && *already_added_other
} else {
*already_added
};
Self::Compatible { already_added }
}
Self::No { .. } => other.clone(),
}
}
}

#[derive(Default)]
#[allow(dead_code)]
struct EntityAddInfo {
can_add: CanAddToView,
can_add_self_or_descendant: CanAddToView,
}

fn create_entity_add_info(
ctx: &ViewerContext<'_>,
tree: &EntityTree,
view: &ViewBlueprint,
query_result: &DataQueryResult,
) -> IntMap<EntityPath, EntityAddInfo> {
let mut meta_data: IntMap<EntityPath, EntityAddInfo> = IntMap::default();

// TODO(andreas): This should be state that is already available because it's part of the view's state.
let class = view.class(ctx.view_class_registry);
let visualizable_entities = class.determine_visualizable_entities(
ctx.applicable_entities_per_visualizer,
ctx.recording(),
&ctx.view_class_registry
.new_visualizer_collection(view.class_identifier()),
&view.space_origin,
);

tree.visit_children_recursively(|entity_path| {
let can_add: CanAddToView =
if visualizable_entities.iter().any(|(_, entities)| entities.contains(entity_path)) {
CanAddToView::Compatible {
already_added: query_result.contains_entity(entity_path),
}
} else {
// TODO(#6321): This shouldn't necessarily prevent us from adding it.
CanAddToView::No {
reason: format!(
"Entity can't be displayed by any of the available visualizers in this class of view ({}).",
view.class_identifier()
),
}
};

if can_add.is_compatible() {
// Mark parents aware that there is some descendant that is compatible
let mut path = entity_path.clone();
while let Some(parent) = path.parent() {
let data = meta_data.entry(parent.clone()).or_default();
data.can_add_self_or_descendant = data.can_add_self_or_descendant.join(&can_add);
path = parent;
}
}

let can_add_self_or_descendant = can_add.clone();
meta_data.insert(
entity_path.clone(),
EntityAddInfo {
can_add,
can_add_self_or_descendant,
},
);
});

meta_data
}
3 changes: 2 additions & 1 deletion crates/viewer/re_time_panel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ impl TimePanel {
} = ui
.list_item()
.selected(is_selected)
.draggable(true)
.force_hovered(is_item_hovered)
.show_hierarchical_with_children(
ui,
Expand Down Expand Up @@ -726,7 +727,7 @@ impl TimePanel {
&response,
SelectionUpdateBehavior::UseSelection,
);
ctx.handle_select_hover_drag_interactions(&response, item.to_item(), false);
ctx.handle_select_hover_drag_interactions(&response, item.to_item(), true);

let is_closed = body_response.is_none();
let response_rect = response.rect;
Expand Down
11 changes: 11 additions & 0 deletions crates/viewer/re_ui/src/design_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,17 @@ impl DesignTokens {
pub fn thumbnail_background_color(&self) -> egui::Color32 {
self.color(ColorToken::gray(S250))
}

/// Stroke used to indicate that a UI element is a container that will receive a drag-and-drop
/// payload.
///
/// Sometimes this is the UI element that is being dragged over (e.g., a view receiving a new
/// entity). Sometimes this is a UI element not under the pointer, but whose content is
/// being hovered (e.g., a container in the blueprint tree)
#[inline]
pub fn drop_target_container_stroke(&self) -> egui::Stroke {
egui::Stroke::new(2.0, self.color(ColorToken::blue(S350)))
}
}

// ----------------------------------------------------------------------------
Expand Down
7 changes: 2 additions & 5 deletions crates/viewer/re_ui/src/list_item/list_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,10 @@ impl ListItem {
let bg_rect_to_paint = ui.painter().round_rect_to_pixels(bg_rect);

if drag_target {
let stroke = crate::design_tokens().drop_target_container_stroke();
ui.painter().set(
background_frame,
Shape::rect_stroke(
bg_rect_to_paint.expand(-1.0),
0.0,
egui::Stroke::new(1.0, ui.visuals().selection.bg_fill),
),
Shape::rect_stroke(bg_rect_to_paint.shrink(stroke.width), 0.0, stroke),
);
}

Expand Down
6 changes: 5 additions & 1 deletion crates/viewer/re_ui/src/ui_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,11 @@ pub trait UiExt {
return *span;
}

if node.has_visible_frame() || node.is_area_ui() || node.is_root_ui() {
if node.has_visible_frame()
|| node.is_area_ui()
|| node.is_panel_ui()
|| node.is_root_ui()
{
return (node.max_rect + node.frame().inner_margin).x_range();
}
}
Expand Down
15 changes: 8 additions & 7 deletions crates/viewer/re_viewer/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use re_smart_channel::ReceiveSet;
use re_types::blueprint::components::PanelState;
use re_ui::{ContextExt as _, DesignTokens};
use re_viewer_context::{
drag_and_drop_payload_cursor_ui, AppOptions, ApplicationSelectionState, BlueprintUndoState,
CommandSender, ComponentUiRegistry, PlayState, RecordingConfig, StoreContext, StoreHub,
AppOptions, ApplicationSelectionState, BlueprintUndoState, CommandSender, ComponentUiRegistry,
DragAndDropManager, PlayState, RecordingConfig, StoreContext, StoreHub,
SystemCommandSender as _, ViewClassExt as _, ViewClassRegistry, ViewStates, ViewerContext,
};
use re_viewport::ViewportUi;
Expand Down Expand Up @@ -215,8 +215,9 @@ impl AppState {
);

// The root container cannot be dragged.
let undraggable_items =
re_viewer_context::Item::Container(viewport_ui.blueprint.root_container).into();
let drag_and_drop_manager = DragAndDropManager::new(re_viewer_context::Item::Container(
viewport_ui.blueprint.root_container,
));

let applicable_entities_per_visualizer =
view_class_registry.applicable_entities_for_visualizer_systems(&recording.store_id());
Expand Down Expand Up @@ -278,7 +279,7 @@ impl AppState {
render_ctx: Some(render_ctx),
command_sender,
focused_item,
undraggable_items: &undraggable_items,
drag_and_drop_manager: &drag_and_drop_manager,
};

// We move the time at the very start of the frame,
Expand Down Expand Up @@ -350,7 +351,7 @@ impl AppState {
render_ctx: Some(render_ctx),
command_sender,
focused_item,
undraggable_items: &undraggable_items,
drag_and_drop_manager: &drag_and_drop_manager,
};

if *show_settings_ui {
Expand Down Expand Up @@ -517,7 +518,7 @@ impl AppState {
//

add_view_or_container_modal_ui(&ctx, &viewport_ui.blueprint, ui);
drag_and_drop_payload_cursor_ui(ctx.egui_ctx);
drag_and_drop_manager.payload_cursor_ui(ctx.egui_ctx);

// Process deferred layout operations and apply updates back to blueprint:
viewport_ui.save_to_blueprint_store(&ctx, view_class_registry);
Expand Down
Loading
Loading