Skip to content

Commit 6bc76a9

Browse files
committed
Enable Drag and Drop for SubViewports and Windows
Make Drag and Drop an application-wide operation. This allows do drop on Controls in other Viewports/Windows. In order to achieve this, `Viewport::_update_mouse_over` is adjusted to remember the Control, that the mouse is over (possibly within nested viewports). This Control is used as a basis for the Drop-operation, which replaces the previous algorithm, which was only aware of the topmost Viewport. Also now all nodes in the SceneTree are notified about the Drag and Drop operation, with the exception of SubViewports that are not children of SubViewportContainers.
1 parent 03e6fbb commit 6bc76a9

File tree

7 files changed

+213
-148
lines changed

7 files changed

+213
-148
lines changed

doc/classes/Viewport.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
<method name="gui_is_dragging" qualifiers="const">
145145
<return type="bool" />
146146
<description>
147-
Returns [code]true[/code] if the viewport is currently performing a drag operation.
147+
Returns [code]true[/code] if a drag operation is currently ongoing and where the drop action could happen in this viewport.
148148
Alternative to [constant Node.NOTIFICATION_DRAG_BEGIN] and [constant Node.NOTIFICATION_DRAG_END] when you prefer polling the value.
149149
</description>
150150
</method>

platform/linuxbsd/x11/display_server_x11.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -1718,12 +1718,6 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
17181718
_send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT);
17191719
}
17201720

1721-
window_set_rect_changed_callback(Callable(), p_id);
1722-
window_set_window_event_callback(Callable(), p_id);
1723-
window_set_input_event_callback(Callable(), p_id);
1724-
window_set_input_text_callback(Callable(), p_id);
1725-
window_set_drop_files_callback(Callable(), p_id);
1726-
17271721
while (wd.transient_children.size()) {
17281722
window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);
17291723
}
@@ -1767,6 +1761,12 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
17671761
XUnmapWindow(x11_display, wd.x11_window);
17681762
XDestroyWindow(x11_display, wd.x11_window);
17691763

1764+
window_set_rect_changed_callback(Callable(), p_id);
1765+
window_set_window_event_callback(Callable(), p_id);
1766+
window_set_input_event_callback(Callable(), p_id);
1767+
window_set_input_text_callback(Callable(), p_id);
1768+
window_set_drop_files_callback(Callable(), p_id);
1769+
17701770
windows.erase(p_id);
17711771
}
17721772

scene/main/viewport.cpp

+55-113
Original file line numberDiff line numberDiff line change
@@ -1224,14 +1224,16 @@ Ref<World2D> Viewport::find_world_2d() const {
12241224
}
12251225
}
12261226

1227-
void Viewport::_propagate_viewport_notification(Node *p_node, int p_what) {
1227+
void Viewport::_propagate_drag_notification(Node *p_node, int p_what) {
1228+
// Send notification to p_node and all children and descendant nodes of p_node, except to SubViewports which are not children of a SubViewportContainer.
12281229
p_node->notification(p_what);
1230+
bool is_svc = Object::cast_to<SubViewportContainer>(p_node);
12291231
for (int i = 0; i < p_node->get_child_count(); i++) {
12301232
Node *c = p_node->get_child(i);
1231-
if (Object::cast_to<Viewport>(c)) {
1233+
if (!is_svc && Object::cast_to<SubViewport>(c)) {
12321234
continue;
12331235
}
1234-
_propagate_viewport_notification(c, p_what);
1236+
Viewport::_propagate_drag_notification(c, p_what);
12351237
}
12361238
}
12371239

@@ -1334,7 +1336,7 @@ Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) {
13341336

13351337
Vector2 Viewport::get_mouse_position() const {
13361338
ERR_READ_THREAD_GUARD_V(Vector2());
1337-
if (!is_directly_attached_to_screen()) {
1339+
if (get_section_root_viewport() != SceneTree::get_singleton()->get_root()) {
13381340
// Rely on the most recent mouse coordinate from an InputEventMouse in push_input.
13391341
// In this case get_screen_transform is not applicable, because it is ambiguous.
13401342
return gui.last_mouse_pos;
@@ -1689,14 +1691,15 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_
16891691
}
16901692

16911693
bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) {
1692-
// Attempt grab, try parent controls too.
1694+
// Attempt drop, try parent controls too.
16931695
CanvasItem *ci = p_at_control;
1696+
Viewport *section_root = get_section_root_viewport();
16941697
while (ci) {
16951698
Control *control = Object::cast_to<Control>(ci);
16961699
if (control) {
1697-
if (control->can_drop_data(p_at_pos, gui.drag_data)) {
1700+
if (control->can_drop_data(p_at_pos, section_root->gui.drag_data)) {
16981701
if (!p_just_check) {
1699-
control->drop_data(p_at_pos, gui.drag_data);
1702+
control->drop_data(p_at_pos, section_root->gui.drag_data);
17001703
}
17011704

17021705
return true;
@@ -1794,13 +1797,13 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
17941797

17951798
if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) {
17961799
// Alternate drop use (when using force_drag(), as proposed by #5342).
1797-
_perform_drop(gui.mouse_focus, pos);
1800+
_perform_drop(gui.mouse_focus);
17981801
}
17991802

18001803
_gui_cancel_tooltip();
18011804
} else {
18021805
if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) {
1803-
_perform_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos);
1806+
_perform_drop(gui.drag_mouse_over);
18041807
}
18051808

18061809
gui.mouse_focus_mask.clear_flag(mouse_button_to_mask(mb->get_button_index())); // Remove from mask.
@@ -1837,7 +1840,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18371840
Point2 mpos = mm->get_position();
18381841

18391842
// Drag & drop.
1840-
if (!gui.drag_attempted && gui.mouse_focus && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
1843+
Viewport *section_root = get_section_root_viewport();
1844+
if (!gui.drag_attempted && gui.mouse_focus && section_root && !section_root->gui.global_dragging && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
18411845
gui.drag_accum += mm->get_relative();
18421846
float len = gui.drag_accum.length();
18431847
if (len > 10) {
@@ -1846,12 +1850,13 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18461850
while (ci) {
18471851
Control *control = Object::cast_to<Control>(ci);
18481852
if (control) {
1849-
gui.dragging = true;
1850-
gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum));
1851-
if (gui.drag_data.get_type() != Variant::NIL) {
1853+
section_root->gui.global_dragging = true;
1854+
section_root->gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum));
1855+
if (section_root->gui.drag_data.get_type() != Variant::NIL) {
18521856
gui.mouse_focus = nullptr;
18531857
gui.forced_mouse_focus = false;
18541858
gui.mouse_focus_mask.clear();
1859+
gui.dragging = true;
18551860
break;
18561861
} else {
18571862
Control *drag_preview = _gui_get_drag_preview();
@@ -1860,7 +1865,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18601865
memdelete(drag_preview);
18611866
gui.drag_preview_id = ObjectID();
18621867
}
1863-
gui.dragging = false;
1868+
section_root->gui.global_dragging = false;
18641869
}
18651870

18661871
if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
@@ -1878,7 +1883,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18781883

18791884
gui.drag_attempted = true;
18801885
if (gui.dragging) {
1881-
_propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN);
1886+
Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN);
18821887
}
18831888
}
18841889
}
@@ -1971,105 +1976,29 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
19711976
}
19721977

19731978
if (gui.dragging) {
1974-
// Handle drag & drop.
1979+
// Handle drag & drop. This happens in the viewport where dragging started.
19751980

19761981
Control *drag_preview = _gui_get_drag_preview();
19771982
if (drag_preview) {
19781983
drag_preview->set_position(mpos);
19791984
}
19801985

1981-
gui.drag_mouse_over = over;
1982-
gui.drag_mouse_over_pos = Vector2();
1983-
1984-
// Find the window this is above of.
1985-
// See if there is an embedder.
1986-
Viewport *embedder = nullptr;
1987-
Vector2 viewport_pos;
1988-
1989-
if (is_embedding_subwindows()) {
1990-
embedder = this;
1991-
viewport_pos = mpos;
1992-
} else {
1993-
// Not an embedder, but may be a subwindow of an embedder.
1994-
Window *w = Object::cast_to<Window>(this);
1995-
if (w) {
1996-
if (w->is_embedded()) {
1997-
embedder = w->get_embedder();
1998-
1999-
viewport_pos = get_final_transform().xform(mpos) + w->get_position(); // To parent coords.
2000-
}
2001-
}
2002-
}
2003-
2004-
Viewport *viewport_under = nullptr;
2005-
2006-
if (embedder) {
2007-
// Use embedder logic.
2008-
2009-
for (int i = embedder->gui.sub_windows.size() - 1; i >= 0; i--) {
2010-
Window *sw = embedder->gui.sub_windows[i].window;
2011-
Rect2 swrect = Rect2i(sw->get_position(), sw->get_size());
2012-
if (!sw->get_flag(Window::FLAG_BORDERLESS)) {
2013-
int title_height = sw->theme_cache.title_height;
2014-
swrect.position.y -= title_height;
2015-
swrect.size.y += title_height;
2016-
}
2017-
2018-
if (swrect.has_point(viewport_pos)) {
2019-
viewport_under = sw;
2020-
viewport_pos -= sw->get_position();
2021-
}
2022-
}
2023-
2024-
if (!viewport_under) {
2025-
// Not in a subwindow, likely in embedder.
2026-
viewport_under = embedder;
2027-
}
2028-
} else {
2029-
// Use DisplayServer logic.
2030-
Vector2i screen_mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
2031-
2032-
DisplayServer::WindowID window_id = DisplayServer::get_singleton()->get_window_at_screen_position(screen_mouse_pos);
2033-
2034-
if (window_id != DisplayServer::INVALID_WINDOW_ID) {
2035-
ObjectID object_under = DisplayServer::get_singleton()->window_get_attached_instance_id(window_id);
2036-
2037-
if (object_under != ObjectID()) { // Fetch window.
2038-
Window *w = Object::cast_to<Window>(ObjectDB::get_instance(object_under));
2039-
if (w) {
2040-
viewport_under = w;
2041-
viewport_pos = w->get_final_transform().affine_inverse().xform(screen_mouse_pos - w->get_position());
2042-
}
2043-
}
1986+
gui.drag_mouse_over = section_root->gui.target_control;
1987+
if (gui.drag_mouse_over) {
1988+
if (!_gui_drop(gui.drag_mouse_over, gui.drag_mouse_over->get_local_mouse_position(), true)) {
1989+
gui.drag_mouse_over = nullptr;
20441990
}
2045-
}
2046-
2047-
if (viewport_under) {
2048-
if (viewport_under != this) {
2049-
Transform2D ai = viewport_under->get_final_transform().affine_inverse();
2050-
viewport_pos = ai.xform(viewport_pos);
2051-
}
2052-
// Find control under at position.
2053-
gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos);
20541991
if (gui.drag_mouse_over) {
2055-
Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse();
2056-
gui.drag_mouse_over_pos = localizer.xform(viewport_pos);
2057-
2058-
bool can_drop = _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, true);
2059-
2060-
if (!can_drop) {
2061-
ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN;
2062-
} else {
2063-
ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP;
2064-
}
1992+
ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP;
1993+
} else {
1994+
ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN;
20651995
}
2066-
2067-
} else {
2068-
gui.drag_mouse_over = nullptr;
20691996
}
20701997
}
20711998

2072-
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && !Object::cast_to<SubViewportContainer>(over)) {
1999+
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && (gui.dragging || (!section_root->gui.global_dragging && !Object::cast_to<SubViewportContainer>(over)))) {
2000+
// If dragging is active, then set the cursor shape only from the Viewport where dragging started.
2001+
// If dragging is inactive, then set the cursor shape only when not over a SubViewportContainer.
20732002
DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape);
20742003
}
20752004
}
@@ -2269,10 +2198,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
22692198
}
22702199
}
22712200

2272-
void Viewport::_perform_drop(Control *p_control, Point2 p_pos) {
2201+
void Viewport::_perform_drop(Control *p_control) {
22732202
// Without any arguments, simply cancel Drag and Drop.
22742203
if (p_control) {
2275-
gui.drag_successful = _gui_drop(p_control, p_pos, false);
2204+
gui.drag_successful = _gui_drop(p_control, p_control->get_local_mouse_position(), false);
22762205
} else {
22772206
gui.drag_successful = false;
22782207
}
@@ -2283,10 +2212,12 @@ void Viewport::_perform_drop(Control *p_control, Point2 p_pos) {
22832212
gui.drag_preview_id = ObjectID();
22842213
}
22852214

2286-
gui.drag_data = Variant();
2215+
Viewport *section_root = get_section_root_viewport();
2216+
section_root->gui.drag_data = Variant();
22872217
gui.dragging = false;
2218+
section_root->gui.global_dragging = false;
22882219
gui.drag_mouse_over = nullptr;
2289-
_propagate_viewport_notification(this, NOTIFICATION_DRAG_END);
2220+
Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_END);
22902221
// Display the new cursor shape instantly.
22912222
update_mouse_cursor_state();
22922223
}
@@ -2316,14 +2247,16 @@ void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control *
23162247
ERR_FAIL_COND_MSG(p_data.get_type() == Variant::NIL, "Drag data must be a value.");
23172248

23182249
gui.dragging = true;
2319-
gui.drag_data = p_data;
2250+
Viewport *section_root = get_section_root_viewport();
2251+
section_root->gui.global_dragging = true;
2252+
section_root->gui.drag_data = p_data;
23202253
gui.mouse_focus = nullptr;
23212254
gui.mouse_focus_mask.clear();
23222255

23232256
if (p_control) {
23242257
_gui_set_drag_preview(p_base, p_control);
23252258
}
2326-
_propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN);
2259+
Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN);
23272260
}
23282261

23292262
void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) {
@@ -3005,6 +2938,7 @@ void Viewport::_update_mouse_over() {
30052938
}
30062939

30072940
void Viewport::_update_mouse_over(Vector2 p_pos) {
2941+
gui.last_mouse_pos = p_pos; // Necessary, because mouse cursor can be over Viewports that are not reached by the InputEvent.
30082942
// Look for embedded windows at mouse position.
30092943
if (is_embedding_subwindows()) {
30102944
for (int i = gui.sub_windows.size() - 1; i >= 0; i--) {
@@ -3056,6 +2990,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
30562990

30572991
// Look for Controls at mouse position.
30582992
Control *over = gui_find_control(p_pos);
2993+
get_section_root_viewport()->gui.target_control = over;
30592994
bool notify_embedded_viewports = false;
30602995
if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) {
30612996
// Find the common ancestor of `gui.mouse_over` and `over`.
@@ -3178,6 +3113,10 @@ void Viewport::_drop_mouse_over(Control *p_until_control) {
31783113
if (gui.mouse_over && gui.mouse_over->is_inside_tree()) {
31793114
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
31803115
}
3116+
Viewport *section_root = get_section_root_viewport();
3117+
if (section_root && section_root->gui.target_control == gui.mouse_over) {
3118+
section_root->gui.target_control = nullptr;
3119+
}
31813120
gui.mouse_over = nullptr;
31823121

31833122
// Send Mouse Exit notifications to children first. Don't send to p_until_control or above.
@@ -3386,7 +3325,7 @@ bool Viewport::is_input_disabled() const {
33863325

33873326
Variant Viewport::gui_get_drag_data() const {
33883327
ERR_READ_THREAD_GUARD_V(Variant());
3389-
return gui.drag_data;
3328+
return get_section_root_viewport()->gui.drag_data;
33903329
}
33913330

33923331
PackedStringArray Viewport::get_configuration_warnings() const {
@@ -3580,7 +3519,7 @@ bool Viewport::is_snap_2d_vertices_to_pixel_enabled() const {
35803519

35813520
bool Viewport::gui_is_dragging() const {
35823521
ERR_READ_THREAD_GUARD_V(false);
3583-
return gui.dragging;
3522+
return get_section_root_viewport()->gui.global_dragging;
35843523
}
35853524

35863525
bool Viewport::gui_is_drag_successful() const {
@@ -5110,9 +5049,12 @@ Transform2D SubViewport::get_popup_base_transform() const {
51105049
return c->get_screen_transform() * container_transform * get_final_transform();
51115050
}
51125051

5113-
bool SubViewport::is_directly_attached_to_screen() const {
5114-
// SubViewports, that are used as Textures are not considered to be directly attached to screen.
5115-
return Object::cast_to<SubViewportContainer>(get_parent()) && get_parent()->get_viewport() && get_parent()->get_viewport()->is_directly_attached_to_screen();
5052+
Viewport *SubViewport::get_section_root_viewport() const {
5053+
if (Object::cast_to<SubViewportContainer>(get_parent()) && get_parent()->get_viewport()) {
5054+
return get_parent()->get_viewport()->get_section_root_viewport();
5055+
}
5056+
SubViewport *vp = const_cast<SubViewport *>(this);
5057+
return vp;
51165058
}
51175059

51185060
bool SubViewport::is_attached_in_viewport() const {

0 commit comments

Comments
 (0)