Skip to content

Commit 704438f

Browse files
abey79emilk
andauthored
Allow drag-and-dropping multiple containers and views in the blueprint tree (#8334)
### Related * Closes #8276 * Closes #8275 * Part of #8266 * Part of #8267 * Related to #7108 ### What This PR makes it possible to drag multi-selection of views and containers within the blueprint tree. It lays the foundation of a system that will be extended to other drag payload and UI sections. Specifically: - `ctx.handle_select_hover_drag_interactions()` (formerly `ctx.select_hovered_on_click()`) is now able to initiate drag interactions. This is opt-in for now, as dragging from most place isn't supported yet (to implemented in follow-up PRs). - Introduce a new `DragAndDropPayload` type to interoperate between various part of the UI. This type also enforce the grouping of items that can meaningfully be dragged together (e.g. it's ok to drag a view and a container together, because there exist somewhere they can be dropped to, but it's not ok to drag a view and an entity together). - When a drag is successfully initiated, a "pill" is displayed along the cursor which indicates the content of what's being dragged. - Introduces a _very_ hack mechanism for a black list of undraggable items (aka the root container). - Update blueprint tree to support multiple selection and the new drag and drop payload type. - Updates egui to latest `master`. --------- Co-authored-by: Emil Ernerfeldt <[email protected]>
1 parent 388c8aa commit 704438f

File tree

32 files changed

+403
-111
lines changed

32 files changed

+403
-111
lines changed

Cargo.lock

+11-11
Original file line numberDiff line numberDiff line change
@@ -1934,7 +1934,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
19341934
[[package]]
19351935
name = "ecolor"
19361936
version = "0.29.1"
1937-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
1937+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
19381938
dependencies = [
19391939
"bytemuck",
19401940
"color-hex",
@@ -1951,7 +1951,7 @@ checksum = "18aade80d5e09429040243ce1143ddc08a92d7a22820ac512610410a4dd5214f"
19511951
[[package]]
19521952
name = "eframe"
19531953
version = "0.29.1"
1954-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
1954+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
19551955
dependencies = [
19561956
"ahash",
19571957
"bytemuck",
@@ -1990,7 +1990,7 @@ dependencies = [
19901990
[[package]]
19911991
name = "egui"
19921992
version = "0.29.1"
1993-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
1993+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
19941994
dependencies = [
19951995
"accesskit",
19961996
"ahash",
@@ -2007,7 +2007,7 @@ dependencies = [
20072007
[[package]]
20082008
name = "egui-wgpu"
20092009
version = "0.29.1"
2010-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
2010+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
20112011
dependencies = [
20122012
"ahash",
20132013
"bytemuck",
@@ -2026,7 +2026,7 @@ dependencies = [
20262026
[[package]]
20272027
name = "egui-winit"
20282028
version = "0.29.1"
2029-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
2029+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
20302030
dependencies = [
20312031
"accesskit_winit",
20322032
"ahash",
@@ -2068,7 +2068,7 @@ dependencies = [
20682068
[[package]]
20692069
name = "egui_extras"
20702070
version = "0.29.1"
2071-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
2071+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
20722072
dependencies = [
20732073
"ahash",
20742074
"egui",
@@ -2085,7 +2085,7 @@ dependencies = [
20852085
[[package]]
20862086
name = "egui_glow"
20872087
version = "0.29.1"
2088-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
2088+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
20892089
dependencies = [
20902090
"ahash",
20912091
"bytemuck",
@@ -2103,7 +2103,7 @@ dependencies = [
21032103
[[package]]
21042104
name = "egui_kittest"
21052105
version = "0.29.1"
2106-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
2106+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
21072107
dependencies = [
21082108
"dify",
21092109
"egui",
@@ -2172,7 +2172,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
21722172
[[package]]
21732173
name = "emath"
21742174
version = "0.29.1"
2175-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
2175+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
21762176
dependencies = [
21772177
"bytemuck",
21782178
"serde",
@@ -2288,7 +2288,7 @@ dependencies = [
22882288
[[package]]
22892289
name = "epaint"
22902290
version = "0.29.1"
2291-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
2291+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
22922292
dependencies = [
22932293
"ab_glyph",
22942294
"ahash",
@@ -2307,7 +2307,7 @@ dependencies = [
23072307
[[package]]
23082308
name = "epaint_default_fonts"
23092309
version = "0.29.1"
2310-
source = "git+https://github.com/emilk/egui.git?rev=577ee8d22810752540636febac5660a5119c6550#577ee8d22810752540636febac5660a5119c6550"
2310+
source = "git+https://github.com/emilk/egui.git?rev=13352d606496d7b1c5fd6fcfbe3c85baae39c040#13352d606496d7b1c5fd6fcfbe3c85baae39c040"
23112311

23122312
[[package]]
23132313
name = "equivalent"

Cargo.toml

+7-7
Original file line numberDiff line numberDiff line change
@@ -560,13 +560,13 @@ significant_drop_tightening = "allow" # An update of parking_lot made this trigg
560560
# As a last resport, patch with a commit to our own repository.
561561
# ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk.
562562

563-
ecolor = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04
564-
eframe = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04
565-
egui = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04
566-
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04
567-
egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04
568-
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04
569-
emath = { git = "https://github.com/emilk/egui.git", rev = "577ee8d22810752540636febac5660a5119c6550" } # egui master 2024-12-04
563+
ecolor = { git = "https://github.com/emilk/egui.git", rev = "13352d606496d7b1c5fd6fcfbe3c85baae39c040" } # egui master 2024-12-09
564+
eframe = { git = "https://github.com/emilk/egui.git", rev = "13352d606496d7b1c5fd6fcfbe3c85baae39c040" } # egui master 2024-12-09
565+
egui = { git = "https://github.com/emilk/egui.git", rev = "13352d606496d7b1c5fd6fcfbe3c85baae39c040" } # egui master 2024-12-09
566+
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "13352d606496d7b1c5fd6fcfbe3c85baae39c040" } # egui master 2024-12-09
567+
egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "13352d606496d7b1c5fd6fcfbe3c85baae39c040" } # egui master 2024-12-09
568+
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "13352d606496d7b1c5fd6fcfbe3c85baae39c040" } # egui master 2024-12-09
569+
emath = { git = "https://github.com/emilk/egui.git", rev = "13352d606496d7b1c5fd6fcfbe3c85baae39c040" } # egui master 2024-12-09
570570

571571
# Useful while developing:
572572
# ecolor = { path = "../../egui/crates/ecolor" }

crates/viewer/re_blueprint_tree/src/blueprint_tree.rs

+53-43
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use re_types::blueprint::components::Visible;
1010
use re_ui::{drag_and_drop::DropTarget, list_item, ContextExt as _, DesignTokens, UiExt as _};
1111
use re_viewer_context::{
1212
contents_name_style, icon_for_container_kind, CollapseScope, Contents, DataResultNodeOrPath,
13-
SystemCommandSender,
13+
DragAndDropPayload, SystemCommandSender,
1414
};
1515
use re_viewer_context::{
1616
ContainerId, DataQueryResult, DataResultNode, HoverHighlight, Item, ViewId, ViewerContext,
@@ -168,7 +168,7 @@ impl BlueprintTree {
168168
let item_response = ui
169169
.list_item()
170170
.selected(ctx.selection().contains_item(&item))
171-
.draggable(false)
171+
.draggable(true) // allowed for consistency but results in an invalid drag
172172
.drop_target_style(self.is_candidate_drop_parent_container(&container_id))
173173
.show_flat(
174174
ui,
@@ -189,7 +189,7 @@ impl BlueprintTree {
189189
SelectionUpdateBehavior::UseSelection,
190190
);
191191
self.scroll_to_me_if_needed(ui, &item, &item_response);
192-
ctx.select_hovered_on_click(&item_response, item);
192+
ctx.handle_select_hover_drag_interactions(&item_response, item, true);
193193

194194
self.handle_root_container_drag_and_drop_interaction(
195195
viewport,
@@ -270,12 +270,11 @@ impl BlueprintTree {
270270
SelectionUpdateBehavior::UseSelection,
271271
);
272272
self.scroll_to_me_if_needed(ui, &item, &response);
273-
ctx.select_hovered_on_click(&response, item);
273+
ctx.handle_select_hover_drag_interactions(&response, item, true);
274274

275275
viewport.set_content_visibility(ctx, &content, visible);
276276

277277
self.handle_drag_and_drop_interaction(
278-
ctx,
279278
viewport,
280279
ui,
281280
content,
@@ -406,13 +405,12 @@ impl BlueprintTree {
406405
SelectionUpdateBehavior::UseSelection,
407406
);
408407
self.scroll_to_me_if_needed(ui, &item, &response);
409-
ctx.select_hovered_on_click(&response, item);
408+
ctx.handle_select_hover_drag_interactions(&response, item, true);
410409

411410
let content = Contents::View(*view_id);
412411

413412
viewport.set_content_visibility(ctx, &content, visible);
414413
self.handle_drag_and_drop_interaction(
415-
ctx,
416414
viewport,
417415
ui,
418416
content,
@@ -494,6 +492,7 @@ impl BlueprintTree {
494492

495493
let list_item = ui
496494
.list_item()
495+
.draggable(true)
497496
.selected(is_selected)
498497
.force_hovered(is_item_hovered);
499498

@@ -596,7 +595,7 @@ impl BlueprintTree {
596595
SelectionUpdateBehavior::UseSelection,
597596
);
598597
self.scroll_to_me_if_needed(ui, &item, &response);
599-
ctx.select_hovered_on_click(&response, item);
598+
ctx.handle_select_hover_drag_interactions(&response, item, true);
600599
}
601600

602601
/// Add a button to trigger the addition of a new view or container.
@@ -636,16 +635,21 @@ impl BlueprintTree {
636635
response: &egui::Response,
637636
) {
638637
//
639-
// check if a drag is in progress and set the cursor accordingly
638+
// check if a drag with acceptable content is in progress
640639
//
641640

642-
let Some(dragged_item_id) = egui::DragAndDrop::payload(ui.ctx()).map(|payload| *payload)
641+
let Some(dragged_payload) = egui::DragAndDrop::payload::<DragAndDropPayload>(ui.ctx())
643642
else {
644-
// nothing is being dragged, so nothing to do
645643
return;
646644
};
647645

648-
ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
646+
let DragAndDropPayload::Contents {
647+
contents: dragged_contents,
648+
} = dragged_payload.as_ref()
649+
else {
650+
// nothing we care about is being dragged
651+
return;
652+
};
649653

650654
//
651655
// find the drop target
@@ -668,39 +672,34 @@ impl BlueprintTree {
668672
);
669673

670674
if let Some(drop_target) = drop_target {
671-
self.handle_drop_target(viewport, ui, dragged_item_id, &drop_target);
675+
self.handle_contents_drop_target(viewport, ui, dragged_contents, &drop_target);
672676
}
673677
}
674678

675679
fn handle_drag_and_drop_interaction(
676680
&mut self,
677-
ctx: &ViewerContext<'_>,
678681
viewport: &ViewportBlueprint,
679682
ui: &egui::Ui,
680683
contents: Contents,
681684
response: &egui::Response,
682685
body_response: Option<&egui::Response>,
683686
) {
684687
//
685-
// initiate drag and force single-selection
686-
//
687-
688-
if response.drag_started() {
689-
ctx.selection_state().set_selection(contents.as_item());
690-
egui::DragAndDrop::set_payload(ui.ctx(), contents);
691-
}
692-
693-
//
694-
// check if a drag is in progress and set the cursor accordingly
688+
// check if a drag with acceptable content is in progress
695689
//
696690

697-
let Some(dragged_item_id) = egui::DragAndDrop::payload(ui.ctx()).map(|payload| *payload)
691+
let Some(dragged_payload) = egui::DragAndDrop::payload::<DragAndDropPayload>(ui.ctx())
698692
else {
699-
// nothing is being dragged, so nothing to do
700693
return;
701694
};
702695

703-
ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
696+
let DragAndDropPayload::Contents {
697+
contents: dragged_contents,
698+
} = dragged_payload.as_ref()
699+
else {
700+
// nothing we care about is being dragged
701+
return;
702+
};
704703

705704
//
706705
// find our parent, our position within parent, and the previous container (if any)
@@ -752,7 +751,7 @@ impl BlueprintTree {
752751
);
753752

754753
if let Some(drop_target) = drop_target {
755-
self.handle_drop_target(viewport, ui, dragged_item_id, &drop_target);
754+
self.handle_contents_drop_target(viewport, ui, dragged_contents, &drop_target);
756755
}
757756
}
758757

@@ -763,16 +762,21 @@ impl BlueprintTree {
763762
empty_space: egui::Rect,
764763
) {
765764
//
766-
// check if a drag is in progress and set the cursor accordingly
765+
// check if a drag with acceptable content is in progress
767766
//
768767

769-
let Some(dragged_item_id) = egui::DragAndDrop::payload(ui.ctx()).map(|payload| *payload)
768+
let Some(dragged_payload) = egui::DragAndDrop::payload::<DragAndDropPayload>(ui.ctx())
770769
else {
771-
// nothing is being dragged, so nothing to do
772770
return;
773771
};
774772

775-
ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
773+
let DragAndDropPayload::Contents {
774+
contents: dragged_contents,
775+
} = dragged_payload.as_ref()
776+
else {
777+
// nothing we care about is being dragged
778+
return;
779+
};
776780

777781
//
778782
// prepare a drop target corresponding to "insert last in root container"
@@ -788,25 +792,31 @@ impl BlueprintTree {
788792
usize::MAX,
789793
);
790794

791-
self.handle_drop_target(viewport, ui, dragged_item_id, &drop_target);
795+
self.handle_contents_drop_target(viewport, ui, dragged_contents, &drop_target);
792796
}
793797
}
794798

795-
fn handle_drop_target(
799+
fn handle_contents_drop_target(
796800
&mut self,
797801
viewport: &ViewportBlueprint,
798802
ui: &Ui,
799-
dragged_item_id: Contents,
803+
dragged_contents: &[Contents],
800804
drop_target: &DropTarget<Contents>,
801805
) {
802-
// We cannot allow the target location to be "inside" the dragged item, because that would amount moving
803-
// myself inside of me.
804-
if let Contents::Container(dragged_container_id) = &dragged_item_id {
805-
if viewport
806-
.is_contents_in_container(&drop_target.target_parent_id, dragged_container_id)
807-
{
808-
return;
806+
// We cannot allow the target location to be "inside" any of the dragged items, because that
807+
// would amount to moving myself inside of me.
808+
let parent_contains_dragged_content = |content: &Contents| {
809+
if let Contents::Container(dragged_container_id) = content {
810+
if viewport
811+
.is_contents_in_container(&drop_target.target_parent_id, dragged_container_id)
812+
{
813+
return true;
814+
}
809815
}
816+
false
817+
};
818+
if dragged_contents.iter().any(parent_contains_dragged_content) {
819+
return;
810820
}
811821

812822
ui.painter().hline(
@@ -822,7 +832,7 @@ impl BlueprintTree {
822832

823833
if ui.input(|i| i.pointer.any_released()) {
824834
viewport.move_contents(
825-
dragged_item_id,
835+
dragged_contents.to_vec(),
826836
target_container_id,
827837
drop_target.target_position_index,
828838
);

crates/viewer/re_data_ui/src/instance_path.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ fn component_list_ui(
243243
});
244244

245245
if interactive {
246-
ctx.select_hovered_on_click(&response, item);
246+
ctx.handle_select_hover_drag_interactions(&response, item, false);
247247
}
248248
}
249249
});

crates/viewer/re_data_ui/src/item_ui.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ pub fn cursor_interact_with_selectable(
596596
let is_item_hovered =
597597
ctx.selection_state().highlight_for_ui_element(&item) == HoverHighlight::Hovered;
598598

599-
ctx.select_hovered_on_click(&response, item);
599+
ctx.handle_select_hover_drag_interactions(&response, item, false);
600600
// TODO(andreas): How to deal with shift click for selecting ranges?
601601

602602
if is_item_hovered {

crates/viewer/re_selection_panel/src/selection_panel.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ fn list_existing_data_blueprints(
675675

676676
// We don't use item_ui::cursor_interact_with_selectable here because the forced
677677
// hover background is distracting and not useful.
678-
ctx.select_hovered_on_click(&response, item);
678+
ctx.handle_select_hover_drag_interactions(&response, item, false);
679679
}
680680
}
681681
}
@@ -926,7 +926,7 @@ fn show_list_item_for_container_child(
926926
&response,
927927
SelectionUpdateBehavior::Ignore,
928928
);
929-
ctx.select_hovered_on_click(&response, item);
929+
ctx.handle_select_hover_drag_interactions(&response, item, false);
930930

931931
if remove_contents {
932932
viewport.mark_user_interaction(ctx);

0 commit comments

Comments
 (0)