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

Enable Drag and Drop between SubViewports and Windows #67531

Merged
merged 1 commit into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion doc/classes/Viewport.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<method name="gui_is_dragging" qualifiers="const">
<return type="bool" />
<description>
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.
</description>
</method>
Expand Down
12 changes: 6 additions & 6 deletions platform/linuxbsd/x11/display_server_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}

Expand Down
168 changes: 55 additions & 113 deletions scene/main/viewport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1235,14 +1235,16 @@ Ref<World2D> 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<SubViewportContainer>(p_node);
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *c = p_node->get_child(i);
if (Object::cast_to<Viewport>(c)) {
if (!is_svc && Object::cast_to<SubViewport>(c)) {
continue;
}
_propagate_viewport_notification(c, p_what);
Viewport::_propagate_drag_notification(c, p_what);
}
}

Expand Down Expand Up @@ -1345,7 +1347,7 @@ Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &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;
Expand Down Expand Up @@ -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<Control>(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;
Expand Down Expand Up @@ -1806,13 +1809,13 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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.
Expand Down Expand Up @@ -1848,7 +1851,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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) {
Expand All @@ -1857,11 +1861,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
while (ci) {
Control *control = Object::cast_to<Control>(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();
Expand All @@ -1870,7 +1875,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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) {
Expand All @@ -1888,7 +1893,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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);
}
}
}
Expand Down Expand Up @@ -1986,105 +1991,29 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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<Window>(this);
if (w) {
if (w->is_embedded()) {
embedder = w->get_embedder();

viewport_pos = get_final_transform().xform(mpos) + w->get_position(); // To parent coords.
}
}
}

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<Window>(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());
}
}
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;
}
}

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;
}
Sauermann marked this conversation as resolved.
Show resolved Hide resolved

} else {
gui.drag_mouse_over = nullptr;
}
}

if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && !Object::cast_to<SubViewportContainer>(over)) {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && (gui.dragging || (!section_root->gui.global_dragging && !Object::cast_to<SubViewportContainer>(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);
}
}
Expand Down Expand Up @@ -2284,10 +2213,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> 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;
}
Expand All @@ -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();
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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--) {
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<SubViewportContainer>(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<SubViewportContainer>(get_parent()) && get_parent()->get_viewport()) {
return get_parent()->get_viewport()->get_section_root_viewport();
}
SubViewport *vp = const_cast<SubViewport *>(this);
return vp;
}

bool SubViewport::is_attached_in_viewport() const {
Expand Down
Loading
Loading