Skip to content

Commit b92fe32

Browse files
committed
Merge pull request #67531 from Sauermann/fix-drag-n-drop
Enable Drag and Drop between SubViewports and Windows
2 parents 6681f25 + 60aaa01 commit b92fe32

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
@@ -151,7 +151,7 @@
151151
<method name="gui_is_dragging" qualifiers="const">
152152
<return type="bool" />
153153
<description>
154-
Returns [code]true[/code] if the viewport is currently performing a drag operation.
154+
Returns [code]true[/code] if a drag operation is currently ongoing and where the drop action could happen in this viewport.
155155
Alternative to [constant Node.NOTIFICATION_DRAG_BEGIN] and [constant Node.NOTIFICATION_DRAG_END] when you prefer polling the value.
156156
</description>
157157
</method>

platform/linuxbsd/x11/display_server_x11.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -1787,12 +1787,6 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
17871787
_send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT);
17881788
}
17891789

1790-
window_set_rect_changed_callback(Callable(), p_id);
1791-
window_set_window_event_callback(Callable(), p_id);
1792-
window_set_input_event_callback(Callable(), p_id);
1793-
window_set_input_text_callback(Callable(), p_id);
1794-
window_set_drop_files_callback(Callable(), p_id);
1795-
17961790
while (wd.transient_children.size()) {
17971791
window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);
17981792
}
@@ -1836,6 +1830,12 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
18361830
XUnmapWindow(x11_display, wd.x11_window);
18371831
XDestroyWindow(x11_display, wd.x11_window);
18381832

1833+
window_set_rect_changed_callback(Callable(), p_id);
1834+
window_set_window_event_callback(Callable(), p_id);
1835+
window_set_input_event_callback(Callable(), p_id);
1836+
window_set_input_text_callback(Callable(), p_id);
1837+
window_set_drop_files_callback(Callable(), p_id);
1838+
18391839
windows.erase(p_id);
18401840
}
18411841

scene/main/viewport.cpp

+55-113
Original file line numberDiff line numberDiff line change
@@ -1235,14 +1235,16 @@ Ref<World2D> Viewport::find_world_2d() const {
12351235
}
12361236
}
12371237

1238-
void Viewport::_propagate_viewport_notification(Node *p_node, int p_what) {
1238+
void Viewport::_propagate_drag_notification(Node *p_node, int p_what) {
1239+
// Send notification to p_node and all children and descendant nodes of p_node, except to SubViewports which are not children of a SubViewportContainer.
12391240
p_node->notification(p_what);
1241+
bool is_svc = Object::cast_to<SubViewportContainer>(p_node);
12401242
for (int i = 0; i < p_node->get_child_count(); i++) {
12411243
Node *c = p_node->get_child(i);
1242-
if (Object::cast_to<Viewport>(c)) {
1244+
if (!is_svc && Object::cast_to<SubViewport>(c)) {
12431245
continue;
12441246
}
1245-
_propagate_viewport_notification(c, p_what);
1247+
Viewport::_propagate_drag_notification(c, p_what);
12461248
}
12471249
}
12481250

@@ -1345,7 +1347,7 @@ Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) {
13451347

13461348
Vector2 Viewport::get_mouse_position() const {
13471349
ERR_READ_THREAD_GUARD_V(Vector2());
1348-
if (!is_directly_attached_to_screen()) {
1350+
if (get_section_root_viewport() != SceneTree::get_singleton()->get_root()) {
13491351
// Rely on the most recent mouse coordinate from an InputEventMouse in push_input.
13501352
// In this case get_screen_transform is not applicable, because it is ambiguous.
13511353
return gui.last_mouse_pos;
@@ -1701,14 +1703,15 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_
17011703
}
17021704

17031705
bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) {
1704-
// Attempt grab, try parent controls too.
1706+
// Attempt drop, try parent controls too.
17051707
CanvasItem *ci = p_at_control;
1708+
Viewport *section_root = get_section_root_viewport();
17061709
while (ci) {
17071710
Control *control = Object::cast_to<Control>(ci);
17081711
if (control) {
1709-
if (control->can_drop_data(p_at_pos, gui.drag_data)) {
1712+
if (control->can_drop_data(p_at_pos, section_root->gui.drag_data)) {
17101713
if (!p_just_check) {
1711-
control->drop_data(p_at_pos, gui.drag_data);
1714+
control->drop_data(p_at_pos, section_root->gui.drag_data);
17121715
}
17131716

17141717
return true;
@@ -1806,13 +1809,13 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18061809

18071810
if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) {
18081811
// Alternate drop use (when using force_drag(), as proposed by #5342).
1809-
_perform_drop(gui.mouse_focus, pos);
1812+
_perform_drop(gui.mouse_focus);
18101813
}
18111814

18121815
_gui_cancel_tooltip();
18131816
} else {
18141817
if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) {
1815-
_perform_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos);
1818+
_perform_drop(gui.drag_mouse_over);
18161819
}
18171820

18181821
gui.mouse_focus_mask.clear_flag(mouse_button_to_mask(mb->get_button_index())); // Remove from mask.
@@ -1848,7 +1851,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18481851
Point2 mpos = mm->get_position();
18491852

18501853
// Drag & drop.
1851-
if (!gui.drag_attempted && gui.mouse_focus && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
1854+
Viewport *section_root = get_section_root_viewport();
1855+
if (!gui.drag_attempted && gui.mouse_focus && section_root && !section_root->gui.global_dragging && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
18521856
gui.drag_accum += mm->get_relative();
18531857
float len = gui.drag_accum.length();
18541858
if (len > 10) {
@@ -1857,11 +1861,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18571861
while (ci) {
18581862
Control *control = Object::cast_to<Control>(ci);
18591863
if (control) {
1860-
gui.dragging = true;
1861-
gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum));
1862-
if (gui.drag_data.get_type() != Variant::NIL) {
1864+
section_root->gui.global_dragging = true;
1865+
section_root->gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum));
1866+
if (section_root->gui.drag_data.get_type() != Variant::NIL) {
18631867
gui.mouse_focus = nullptr;
18641868
gui.mouse_focus_mask.clear();
1869+
gui.dragging = true;
18651870
break;
18661871
} else {
18671872
Control *drag_preview = _gui_get_drag_preview();
@@ -1870,7 +1875,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18701875
memdelete(drag_preview);
18711876
gui.drag_preview_id = ObjectID();
18721877
}
1873-
gui.dragging = false;
1878+
section_root->gui.global_dragging = false;
18741879
}
18751880

18761881
if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
@@ -1888,7 +1893,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
18881893

18891894
gui.drag_attempted = true;
18901895
if (gui.dragging) {
1891-
_propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN);
1896+
Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN);
18921897
}
18931898
}
18941899
}
@@ -1986,105 +1991,29 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
19861991
}
19871992

19881993
if (gui.dragging) {
1989-
// Handle drag & drop.
1994+
// Handle drag & drop. This happens in the viewport where dragging started.
19901995

19911996
Control *drag_preview = _gui_get_drag_preview();
19921997
if (drag_preview) {
19931998
drag_preview->set_position(mpos);
19941999
}
19952000

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

2087-
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && !Object::cast_to<SubViewportContainer>(over)) {
2014+
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && (gui.dragging || (!section_root->gui.global_dragging && !Object::cast_to<SubViewportContainer>(over)))) {
2015+
// If dragging is active, then set the cursor shape only from the Viewport where dragging started.
2016+
// If dragging is inactive, then set the cursor shape only when not over a SubViewportContainer.
20882017
DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape);
20892018
}
20902019
}
@@ -2284,10 +2213,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
22842213
}
22852214
}
22862215

2287-
void Viewport::_perform_drop(Control *p_control, Point2 p_pos) {
2216+
void Viewport::_perform_drop(Control *p_control) {
22882217
// Without any arguments, simply cancel Drag and Drop.
22892218
if (p_control) {
2290-
gui.drag_successful = _gui_drop(p_control, p_pos, false);
2219+
gui.drag_successful = _gui_drop(p_control, p_control->get_local_mouse_position(), false);
22912220
} else {
22922221
gui.drag_successful = false;
22932222
}
@@ -2298,10 +2227,12 @@ void Viewport::_perform_drop(Control *p_control, Point2 p_pos) {
22982227
gui.drag_preview_id = ObjectID();
22992228
}
23002229

2301-
gui.drag_data = Variant();
2230+
Viewport *section_root = get_section_root_viewport();
2231+
section_root->gui.drag_data = Variant();
23022232
gui.dragging = false;
2233+
section_root->gui.global_dragging = false;
23032234
gui.drag_mouse_over = nullptr;
2304-
_propagate_viewport_notification(this, NOTIFICATION_DRAG_END);
2235+
Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_END);
23052236
// Display the new cursor shape instantly.
23062237
update_mouse_cursor_state();
23072238
}
@@ -2331,14 +2262,16 @@ void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control *
23312262
ERR_FAIL_COND_MSG(p_data.get_type() == Variant::NIL, "Drag data must be a value.");
23322263

23332264
gui.dragging = true;
2334-
gui.drag_data = p_data;
2265+
Viewport *section_root = get_section_root_viewport();
2266+
section_root->gui.global_dragging = true;
2267+
section_root->gui.drag_data = p_data;
23352268
gui.mouse_focus = nullptr;
23362269
gui.mouse_focus_mask.clear();
23372270

23382271
if (p_control) {
23392272
_gui_set_drag_preview(p_base, p_control);
23402273
}
2341-
_propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN);
2274+
Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN);
23422275
}
23432276

23442277
void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) {
@@ -3022,6 +2955,7 @@ void Viewport::_update_mouse_over() {
30222955
}
30232956

30242957
void Viewport::_update_mouse_over(Vector2 p_pos) {
2958+
gui.last_mouse_pos = p_pos; // Necessary, because mouse cursor can be over Viewports that are not reached by the InputEvent.
30252959
// Look for embedded windows at mouse position.
30262960
if (is_embedding_subwindows()) {
30272961
for (int i = gui.sub_windows.size() - 1; i >= 0; i--) {
@@ -3073,6 +3007,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
30733007

30743008
// Look for Controls at mouse position.
30753009
Control *over = gui_find_control(p_pos);
3010+
get_section_root_viewport()->gui.target_control = over;
30763011
bool notify_embedded_viewports = false;
30773012
if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) {
30783013
// Find the common ancestor of `gui.mouse_over` and `over`.
@@ -3195,6 +3130,10 @@ void Viewport::_drop_mouse_over(Control *p_until_control) {
31953130
if (gui.mouse_over && gui.mouse_over->is_inside_tree()) {
31963131
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
31973132
}
3133+
Viewport *section_root = get_section_root_viewport();
3134+
if (section_root && section_root->gui.target_control == gui.mouse_over) {
3135+
section_root->gui.target_control = nullptr;
3136+
}
31983137
gui.mouse_over = nullptr;
31993138

32003139
// Send Mouse Exit notifications to children first. Don't send to p_until_control or above.
@@ -3403,7 +3342,7 @@ bool Viewport::is_input_disabled() const {
34033342

34043343
Variant Viewport::gui_get_drag_data() const {
34053344
ERR_READ_THREAD_GUARD_V(Variant());
3406-
return gui.drag_data;
3345+
return get_section_root_viewport()->gui.drag_data;
34073346
}
34083347

34093348
PackedStringArray Viewport::get_configuration_warnings() const {
@@ -3597,7 +3536,7 @@ bool Viewport::is_snap_2d_vertices_to_pixel_enabled() const {
35973536

35983537
bool Viewport::gui_is_dragging() const {
35993538
ERR_READ_THREAD_GUARD_V(false);
3600-
return gui.dragging;
3539+
return get_section_root_viewport()->gui.global_dragging;
36013540
}
36023541

36033542
bool Viewport::gui_is_drag_successful() const {
@@ -5156,9 +5095,12 @@ Transform2D SubViewport::get_popup_base_transform() const {
51565095
return c->get_screen_transform() * container_transform * get_final_transform();
51575096
}
51585097

5159-
bool SubViewport::is_directly_attached_to_screen() const {
5160-
// SubViewports, that are used as Textures are not considered to be directly attached to screen.
5161-
return Object::cast_to<SubViewportContainer>(get_parent()) && get_parent()->get_viewport() && get_parent()->get_viewport()->is_directly_attached_to_screen();
5098+
Viewport *SubViewport::get_section_root_viewport() const {
5099+
if (Object::cast_to<SubViewportContainer>(get_parent()) && get_parent()->get_viewport()) {
5100+
return get_parent()->get_viewport()->get_section_root_viewport();
5101+
}
5102+
SubViewport *vp = const_cast<SubViewport *>(this);
5103+
return vp;
51625104
}
51635105

51645106
bool SubViewport::is_attached_in_viewport() const {

0 commit comments

Comments
 (0)