From 60aaa017ff3dc026d89cc8a0eb7817f2b7eac727 Mon Sep 17 00:00:00 2001
From: Markus Sauermann <6299227+Sauermann@users.noreply.github.com>
Date: Fri, 20 Jan 2023 00:21:11 +0100
Subject: [PATCH] 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.
---
doc/classes/Viewport.xml | 2 +-
platform/linuxbsd/x11/display_server_x11.cpp | 12 +-
scene/main/viewport.cpp | 168 ++++++-------------
scene/main/viewport.h | 21 +--
scene/main/window.cpp | 12 +-
scene/main/window.h | 2 +-
tests/scene/test_viewport.h | 144 ++++++++++++++--
7 files changed, 213 insertions(+), 148 deletions(-)
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index 350fd651972..85663e10b28 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -151,7 +151,7 @@
- Returns [code]true[/code] if the viewport is currently performing a drag operation.
+ Returns [code]true[/code] if a drag operation is currently ongoing and where the drop action could happen in this viewport.
Alternative to [constant Node.NOTIFICATION_DRAG_BEGIN] and [constant Node.NOTIFICATION_DRAG_END] when you prefer polling the value.
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 840cadace3e..499df55befe 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -1787,12 +1787,6 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
_send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT);
}
- window_set_rect_changed_callback(Callable(), p_id);
- window_set_window_event_callback(Callable(), p_id);
- window_set_input_event_callback(Callable(), p_id);
- window_set_input_text_callback(Callable(), p_id);
- window_set_drop_files_callback(Callable(), p_id);
-
while (wd.transient_children.size()) {
window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID);
}
@@ -1836,6 +1830,12 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
XUnmapWindow(x11_display, wd.x11_window);
XDestroyWindow(x11_display, wd.x11_window);
+ window_set_rect_changed_callback(Callable(), p_id);
+ window_set_window_event_callback(Callable(), p_id);
+ window_set_input_event_callback(Callable(), p_id);
+ window_set_input_text_callback(Callable(), p_id);
+ window_set_drop_files_callback(Callable(), p_id);
+
windows.erase(p_id);
}
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index de2332125f5..44463ef0630 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1235,14 +1235,16 @@ Ref Viewport::find_world_2d() const {
}
}
-void Viewport::_propagate_viewport_notification(Node *p_node, int p_what) {
+void Viewport::_propagate_drag_notification(Node *p_node, int p_what) {
+ // Send notification to p_node and all children and descendant nodes of p_node, except to SubViewports which are not children of a SubViewportContainer.
p_node->notification(p_what);
+ bool is_svc = Object::cast_to(p_node);
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *c = p_node->get_child(i);
- if (Object::cast_to(c)) {
+ if (!is_svc && Object::cast_to(c)) {
continue;
}
- _propagate_viewport_notification(c, p_what);
+ Viewport::_propagate_drag_notification(c, p_what);
}
}
@@ -1345,7 +1347,7 @@ Ref Viewport::_make_input_local(const Ref &ev) {
Vector2 Viewport::get_mouse_position() const {
ERR_READ_THREAD_GUARD_V(Vector2());
- if (!is_directly_attached_to_screen()) {
+ if (get_section_root_viewport() != SceneTree::get_singleton()->get_root()) {
// Rely on the most recent mouse coordinate from an InputEventMouse in push_input.
// In this case get_screen_transform is not applicable, because it is ambiguous.
return gui.last_mouse_pos;
@@ -1701,14 +1703,15 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_
}
bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) {
- // Attempt grab, try parent controls too.
+ // Attempt drop, try parent controls too.
CanvasItem *ci = p_at_control;
+ Viewport *section_root = get_section_root_viewport();
while (ci) {
Control *control = Object::cast_to(ci);
if (control) {
- if (control->can_drop_data(p_at_pos, gui.drag_data)) {
+ if (control->can_drop_data(p_at_pos, section_root->gui.drag_data)) {
if (!p_just_check) {
- control->drop_data(p_at_pos, gui.drag_data);
+ control->drop_data(p_at_pos, section_root->gui.drag_data);
}
return true;
@@ -1806,13 +1809,13 @@ void Viewport::_gui_input_event(Ref p_event) {
if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) {
// Alternate drop use (when using force_drag(), as proposed by #5342).
- _perform_drop(gui.mouse_focus, pos);
+ _perform_drop(gui.mouse_focus);
}
_gui_cancel_tooltip();
} else {
if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) {
- _perform_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos);
+ _perform_drop(gui.drag_mouse_over);
}
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 p_event) {
Point2 mpos = mm->get_position();
// Drag & drop.
- if (!gui.drag_attempted && gui.mouse_focus && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
+ Viewport *section_root = get_section_root_viewport();
+ if (!gui.drag_attempted && gui.mouse_focus && section_root && !section_root->gui.global_dragging && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
gui.drag_accum += mm->get_relative();
float len = gui.drag_accum.length();
if (len > 10) {
@@ -1857,11 +1861,12 @@ void Viewport::_gui_input_event(Ref p_event) {
while (ci) {
Control *control = Object::cast_to(ci);
if (control) {
- gui.dragging = true;
- gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum));
- if (gui.drag_data.get_type() != Variant::NIL) {
+ section_root->gui.global_dragging = true;
+ section_root->gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum));
+ if (section_root->gui.drag_data.get_type() != Variant::NIL) {
gui.mouse_focus = nullptr;
gui.mouse_focus_mask.clear();
+ gui.dragging = true;
break;
} else {
Control *drag_preview = _gui_get_drag_preview();
@@ -1870,7 +1875,7 @@ void Viewport::_gui_input_event(Ref p_event) {
memdelete(drag_preview);
gui.drag_preview_id = ObjectID();
}
- gui.dragging = false;
+ section_root->gui.global_dragging = false;
}
if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
@@ -1888,7 +1893,7 @@ void Viewport::_gui_input_event(Ref p_event) {
gui.drag_attempted = true;
if (gui.dragging) {
- _propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN);
+ Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN);
}
}
}
@@ -1986,105 +1991,29 @@ void Viewport::_gui_input_event(Ref p_event) {
}
if (gui.dragging) {
- // Handle drag & drop.
+ // Handle drag & drop. This happens in the viewport where dragging started.
Control *drag_preview = _gui_get_drag_preview();
if (drag_preview) {
drag_preview->set_position(mpos);
}
- gui.drag_mouse_over = over;
- gui.drag_mouse_over_pos = Vector2();
-
- // Find the window this is above of.
- // See if there is an embedder.
- Viewport *embedder = nullptr;
- Vector2 viewport_pos;
-
- if (is_embedding_subwindows()) {
- embedder = this;
- viewport_pos = mpos;
- } else {
- // Not an embedder, but may be a subwindow of an embedder.
- Window *w = Object::cast_to(this);
- if (w) {
- if (w->is_embedded()) {
- embedder = w->get_embedder();
-
- viewport_pos = get_final_transform().xform(mpos) + w->get_position(); // To parent coords.
- }
+ gui.drag_mouse_over = section_root->gui.target_control;
+ if (gui.drag_mouse_over) {
+ if (!_gui_drop(gui.drag_mouse_over, gui.drag_mouse_over->get_local_mouse_position(), true)) {
+ gui.drag_mouse_over = nullptr;
}
- }
-
- Viewport *viewport_under = nullptr;
-
- if (embedder) {
- // Use embedder logic.
-
- for (int i = embedder->gui.sub_windows.size() - 1; i >= 0; i--) {
- Window *sw = embedder->gui.sub_windows[i].window;
- Rect2 swrect = Rect2i(sw->get_position(), sw->get_size());
- if (!sw->get_flag(Window::FLAG_BORDERLESS)) {
- int title_height = sw->theme_cache.title_height;
- swrect.position.y -= title_height;
- swrect.size.y += title_height;
- }
-
- if (swrect.has_point(viewport_pos)) {
- viewport_under = sw;
- viewport_pos -= sw->get_position();
- }
- }
-
- if (!viewport_under) {
- // Not in a subwindow, likely in embedder.
- viewport_under = embedder;
- }
- } else {
- // Use DisplayServer logic.
- Vector2i screen_mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
-
- DisplayServer::WindowID window_id = DisplayServer::get_singleton()->get_window_at_screen_position(screen_mouse_pos);
-
- if (window_id != DisplayServer::INVALID_WINDOW_ID) {
- ObjectID object_under = DisplayServer::get_singleton()->window_get_attached_instance_id(window_id);
-
- if (object_under != ObjectID()) { // Fetch window.
- Window *w = Object::cast_to(ObjectDB::get_instance(object_under));
- if (w) {
- viewport_under = w;
- viewport_pos = w->get_final_transform().affine_inverse().xform(screen_mouse_pos - w->get_position());
- }
- }
- }
- }
-
- if (viewport_under) {
- if (viewport_under != this) {
- Transform2D ai = viewport_under->get_final_transform().affine_inverse();
- viewport_pos = ai.xform(viewport_pos);
- }
- // Find control under at position.
- gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos);
if (gui.drag_mouse_over) {
- Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse();
- gui.drag_mouse_over_pos = localizer.xform(viewport_pos);
-
- bool can_drop = _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, true);
-
- if (!can_drop) {
- ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN;
- } else {
- ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP;
- }
+ ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP;
+ } else {
+ ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN;
}
-
- } else {
- gui.drag_mouse_over = nullptr;
}
}
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && !Object::cast_to(over)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && (gui.dragging || (!section_root->gui.global_dragging && !Object::cast_to(over)))) {
+ // If dragging is active, then set the cursor shape only from the Viewport where dragging started.
+ // If dragging is inactive, then set the cursor shape only when not over a SubViewportContainer.
DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape);
}
}
@@ -2284,10 +2213,10 @@ void Viewport::_gui_input_event(Ref p_event) {
}
}
-void Viewport::_perform_drop(Control *p_control, Point2 p_pos) {
+void Viewport::_perform_drop(Control *p_control) {
// Without any arguments, simply cancel Drag and Drop.
if (p_control) {
- gui.drag_successful = _gui_drop(p_control, p_pos, false);
+ gui.drag_successful = _gui_drop(p_control, p_control->get_local_mouse_position(), false);
} else {
gui.drag_successful = false;
}
@@ -2298,10 +2227,12 @@ void Viewport::_perform_drop(Control *p_control, Point2 p_pos) {
gui.drag_preview_id = ObjectID();
}
- gui.drag_data = Variant();
+ Viewport *section_root = get_section_root_viewport();
+ section_root->gui.drag_data = Variant();
gui.dragging = false;
+ section_root->gui.global_dragging = false;
gui.drag_mouse_over = nullptr;
- _propagate_viewport_notification(this, NOTIFICATION_DRAG_END);
+ Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_END);
// Display the new cursor shape instantly.
update_mouse_cursor_state();
}
@@ -2331,14 +2262,16 @@ void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control *
ERR_FAIL_COND_MSG(p_data.get_type() == Variant::NIL, "Drag data must be a value.");
gui.dragging = true;
- gui.drag_data = p_data;
+ Viewport *section_root = get_section_root_viewport();
+ section_root->gui.global_dragging = true;
+ section_root->gui.drag_data = p_data;
gui.mouse_focus = nullptr;
gui.mouse_focus_mask.clear();
if (p_control) {
_gui_set_drag_preview(p_base, p_control);
}
- _propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN);
+ Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN);
}
void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) {
@@ -3022,6 +2955,7 @@ void Viewport::_update_mouse_over() {
}
void Viewport::_update_mouse_over(Vector2 p_pos) {
+ gui.last_mouse_pos = p_pos; // Necessary, because mouse cursor can be over Viewports that are not reached by the InputEvent.
// Look for embedded windows at mouse position.
if (is_embedding_subwindows()) {
for (int i = gui.sub_windows.size() - 1; i >= 0; i--) {
@@ -3073,6 +3007,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) {
// Look for Controls at mouse position.
Control *over = gui_find_control(p_pos);
+ get_section_root_viewport()->gui.target_control = over;
bool notify_embedded_viewports = false;
if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) {
// Find the common ancestor of `gui.mouse_over` and `over`.
@@ -3195,6 +3130,10 @@ void Viewport::_drop_mouse_over(Control *p_until_control) {
if (gui.mouse_over && gui.mouse_over->is_inside_tree()) {
gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF);
}
+ Viewport *section_root = get_section_root_viewport();
+ if (section_root && section_root->gui.target_control == gui.mouse_over) {
+ section_root->gui.target_control = nullptr;
+ }
gui.mouse_over = nullptr;
// 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 {
Variant Viewport::gui_get_drag_data() const {
ERR_READ_THREAD_GUARD_V(Variant());
- return gui.drag_data;
+ return get_section_root_viewport()->gui.drag_data;
}
PackedStringArray Viewport::get_configuration_warnings() const {
@@ -3597,7 +3536,7 @@ bool Viewport::is_snap_2d_vertices_to_pixel_enabled() const {
bool Viewport::gui_is_dragging() const {
ERR_READ_THREAD_GUARD_V(false);
- return gui.dragging;
+ return get_section_root_viewport()->gui.global_dragging;
}
bool Viewport::gui_is_drag_successful() const {
@@ -5156,9 +5095,12 @@ Transform2D SubViewport::get_popup_base_transform() const {
return c->get_screen_transform() * container_transform * get_final_transform();
}
-bool SubViewport::is_directly_attached_to_screen() const {
- // SubViewports, that are used as Textures are not considered to be directly attached to screen.
- return Object::cast_to(get_parent()) && get_parent()->get_viewport() && get_parent()->get_viewport()->is_directly_attached_to_screen();
+Viewport *SubViewport::get_section_root_viewport() const {
+ if (Object::cast_to(get_parent()) && get_parent()->get_viewport()) {
+ return get_parent()->get_viewport()->get_section_root_viewport();
+ }
+ SubViewport *vp = const_cast(this);
+ return vp;
}
bool SubViewport::is_attached_in_viewport() const {
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index faa36851e98..f6a461d54e4 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -281,7 +281,7 @@ private:
bool disable_3d = false;
- void _propagate_viewport_notification(Node *p_node, int p_what);
+ static void _propagate_drag_notification(Node *p_node, int p_what);
void _update_global_transform();
@@ -362,7 +362,6 @@ private:
Window *subwindow_over = nullptr; // mouse_over and subwindow_over are mutually exclusive. At all times at least one of them is nullptr.
Window *windowmanager_window_over = nullptr; // Only used in root Viewport.
Control *drag_mouse_over = nullptr;
- Vector2 drag_mouse_over_pos;
Control *tooltip_control = nullptr;
Window *tooltip_popup = nullptr;
Label *tooltip_label = nullptr;
@@ -371,7 +370,7 @@ private:
Point2 last_mouse_pos;
Point2 drag_accum;
bool drag_attempted = false;
- Variant drag_data;
+ Variant drag_data; // Only used in root-Viewport and SubViewports, that are not children of a SubViewportContainer.
ObjectID drag_preview_id;
Ref tooltip_timer;
double tooltip_delay = 0.0;
@@ -379,8 +378,10 @@ private:
List roots;
HashSet canvas_parents_with_dirty_order;
int canvas_sort_index = 0; //for sorting items with canvas as root
- bool dragging = false;
+ bool dragging = false; // Is true in the viewport in which dragging started while dragging is active.
+ bool global_dragging = false; // Is true while dragging is active. Only used in root-Viewport and SubViewports that are not children of a SubViewportContainer.
bool drag_successful = false;
+ Control *target_control = nullptr; // Control that the mouse is over in the innermost nested Viewport. Only used in root-Viewport and SubViewports, that are not children of a SubViewportContainer.
bool embed_subwindows_hint = false;
Window *subwindow_focused = nullptr;
@@ -408,7 +409,7 @@ private:
Control *_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform);
void _gui_input_event(Ref p_event);
- void _perform_drop(Control *p_control = nullptr, Point2 p_pos = Point2());
+ void _perform_drop(Control *p_control = nullptr);
void _gui_cleanup_internal_state(Ref p_event);
void _push_unhandled_input_internal(const Ref &p_event);
@@ -672,9 +673,9 @@ public:
Transform2D get_screen_transform() const;
virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const;
virtual Transform2D get_popup_base_transform() const { return Transform2D(); }
- virtual bool is_directly_attached_to_screen() const { return false; };
- virtual bool is_attached_in_viewport() const { return false; };
- virtual bool is_sub_viewport() const { return false; };
+ virtual Viewport *get_section_root_viewport() const { return nullptr; }
+ virtual bool is_attached_in_viewport() const { return false; }
+ virtual bool is_sub_viewport() const { return false; }
private:
// 2D audio, camera, and physics. (don't put World2D here because World2D is needed for Control nodes).
@@ -836,9 +837,9 @@ public:
virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override;
virtual Transform2D get_popup_base_transform() const override;
- virtual bool is_directly_attached_to_screen() const override;
+ virtual Viewport *get_section_root_viewport() const override;
virtual bool is_attached_in_viewport() const override;
- virtual bool is_sub_viewport() const override { return true; };
+ virtual bool is_sub_viewport() const override { return true; }
void _validate_property(PropertyInfo &p_property) const;
SubViewport();
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 6b299eab6ea..803ce89bc9e 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -2755,12 +2755,16 @@ Transform2D Window::get_popup_base_transform() const {
return popup_base_transform;
}
-bool Window::is_directly_attached_to_screen() const {
+Viewport *Window::get_section_root_viewport() const {
if (get_embedder()) {
- return get_embedder()->is_directly_attached_to_screen();
+ return get_embedder()->get_section_root_viewport();
}
- // Distinguish between the case that this is a native Window and not inside the tree.
- return is_inside_tree();
+ if (is_inside_tree()) {
+ // Native window.
+ return SceneTree::get_singleton()->get_root();
+ }
+ Window *vp = const_cast(this);
+ return vp;
}
bool Window::is_attached_in_viewport() const {
diff --git a/scene/main/window.h b/scene/main/window.h
index 84d2febe518..47aaf737287 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -469,7 +469,7 @@ public:
virtual Transform2D get_final_transform() const override;
virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override;
virtual Transform2D get_popup_base_transform() const override;
- virtual bool is_directly_attached_to_screen() const override;
+ virtual Viewport *get_section_root_viewport() const override;
virtual bool is_attached_in_viewport() const override;
Rect2i get_parent_rect() const;
diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h
index 1341cc03321..9d02c41719f 100644
--- a/tests/scene/test_viewport.h
+++ b/tests/scene/test_viewport.h
@@ -119,8 +119,23 @@ public:
class DragTarget : public NotificationControlViewport {
GDCLASS(DragTarget, NotificationControlViewport);
+protected:
+ void _notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_DRAG_BEGIN: {
+ during_drag = true;
+ } break;
+
+ case NOTIFICATION_DRAG_END: {
+ during_drag = false;
+ } break;
+ }
+ }
+
public:
Variant drag_data;
+ bool valid_drop = false;
+ bool during_drag = false;
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override {
StringName string_data = p_data;
// Verify drag data is compatible.
@@ -136,6 +151,7 @@ public:
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override {
drag_data = p_data;
+ valid_drop = true;
}
};
@@ -1107,12 +1123,10 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SUBCASE("[Viewport][GuiInputEvent] Drag and Drop") {
// FIXME: Drag-Preview will likely change. Tests for this part would have to be rewritten anyway.
// See https://github.com/godotengine/godot/pull/67531#issuecomment-1385353430 for details.
- // FIXME: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions
- // FIXME: Drag and Drop currently doesn't work with embedded Windows and SubViewports - not testing.
- // See https://github.com/godotengine/godot/issues/28522 for example.
+ // Note: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions.
int min_grab_movement = 11;
- SUBCASE("[Viewport][GuiInputEvent] Drag from one Control to another in the same viewport.") {
- SUBCASE("[Viewport][GuiInputEvent] Perform successful Drag and Drop on a different Control.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from one Control to another in the same viewport.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Perform successful Drag and Drop on a different Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1131,7 +1145,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK((StringName)node_d->drag_data == SNAME("Drag Data"));
}
- SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on Control.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1157,7 +1171,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
- SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on No-Control.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on No-Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1171,7 +1185,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
// Move away from Controls.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::LEFT, Key::NONE);
- CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); // This could also be CURSOR_FORBIDDEN.
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
CHECK(root->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
@@ -1179,7 +1193,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
- SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop outside of window.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop outside of window.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1192,7 +1206,6 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
// Move outside of window.
SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::LEFT, Key::NONE);
- CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
CHECK(root->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_outside, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
@@ -1200,7 +1213,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
- SUBCASE("[Viewport][GuiInputEvent] Drag and Drop doesn't work with other Mouse Buttons than LMB.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag and Drop doesn't work with other Mouse Buttons than LMB.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::MIDDLE, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@@ -1209,7 +1222,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::NONE, Key::NONE);
}
- SUBCASE("[Viewport][GuiInputEvent] Drag and Drop parent propagation.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag and Drop parent propagation.") {
Node2D *node_aa = memnew(Node2D);
Control *node_aaa = memnew(Control);
Node2D *node_dd = memnew(Node2D);
@@ -1318,7 +1331,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
memdelete(node_aa);
}
- SUBCASE("[Viewport][GuiInputEvent] Force Drag and Drop.") {
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Force Drag and Drop.") {
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
node_a->force_drag(SNAME("Drag Data"), nullptr);
@@ -1339,6 +1352,111 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
}
}
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to a different Viewport.") {
+ SubViewportContainer *svc = memnew(SubViewportContainer);
+ svc->set_size(Size2(100, 100));
+ svc->set_position(Point2(200, 50));
+ root->add_child(svc);
+
+ SubViewport *sv = memnew(SubViewport);
+ sv->set_embedding_subwindows(true);
+ sv->set_size(Size2i(100, 100));
+ svc->add_child(sv);
+
+ DragStart *sv_a = memnew(DragStart);
+ sv_a->set_position(Point2(10, 10));
+ sv_a->set_size(Size2(10, 10));
+ sv->add_child(sv_a);
+ Point2i on_sva = Point2i(215, 65);
+
+ DragTarget *sv_b = memnew(DragTarget);
+ sv_b->set_position(Point2(30, 30));
+ sv_b->set_size(Size2(20, 20));
+ sv->add_child(sv_b);
+ Point2i on_svb = Point2i(235, 85);
+
+ Window *ew = memnew(Window);
+ ew->set_position(Point2(50, 200));
+ ew->set_size(Size2(100, 100));
+ root->add_child(ew);
+
+ DragStart *ew_a = memnew(DragStart);
+ ew_a->set_position(Point2(10, 10));
+ ew_a->set_size(Size2(10, 10));
+ ew->add_child(ew_a);
+ Point2i on_ewa = Point2i(65, 215);
+
+ DragTarget *ew_b = memnew(DragTarget);
+ ew_b->set_position(Point2(30, 30));
+ ew_b->set_size(Size2(20, 20));
+ ew->add_child(ew_b);
+ Point2i on_ewb = Point2i(85, 235);
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to SubViewport") {
+ sv_b->valid_drop = false;
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+ CHECK(sv_b->during_drag);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_svb, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_svb, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(sv_b->valid_drop);
+ CHECK(!sv_b->during_drag);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from SubViewport") {
+ node_d->valid_drop = false;
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_sva, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_sva + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(sv->gui_is_dragging());
+ CHECK(node_d->during_drag);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_d->valid_drop);
+ CHECK(!node_d->during_drag);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to embedded Window") {
+ ew_b->valid_drop = false;
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+ CHECK(ew_b->during_drag);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_ewb, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_ewb, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(ew_b->valid_drop);
+ CHECK(!ew_b->during_drag);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from embedded Window") {
+ node_d->valid_drop = false;
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_ewa, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_ewa + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(ew->gui_is_dragging());
+ CHECK(node_d->during_drag);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_d->valid_drop);
+ CHECK(!node_d->during_drag);
+ }
+
+ memdelete(ew_a);
+ memdelete(ew_b);
+ memdelete(ew);
+ memdelete(sv_a);
+ memdelete(sv_b);
+ memdelete(sv);
+ memdelete(svc);
+ }
}
memdelete(node_j);