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

Add drag and drop to TextEdit #55294

Merged
merged 1 commit into from
Dec 2, 2021
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
7 changes: 7 additions & 0 deletions doc/classes/TextEdit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,13 @@
Returns whether the menu is visible. Use this instead of [code]get_menu().visible[/code] to improve performance (so the creation of the menu is avoided).
</description>
</method>
<method name="is_mouse_over_selection" qualifiers="const">
<return type="bool" />
<argument index="0" name="edges" type="bool" />
<description>
Returns whether the mouse is over selection. If [code]edges[/code] is [code]true[/code], the edges are considered part of the selection.
</description>
</method>
<method name="is_overtype_mode_enabled" qualifiers="const">
<return type="bool" />
<description>
Expand Down
18 changes: 12 additions & 6 deletions scene/gui/line_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
deselect();
selection.start_column = caret_column;
selection.creating = true;
} else if (selection.enabled) {
} else if (selection.enabled && !selection.double_click) {
selection.drag_attempt = true;
}
}
Expand Down Expand Up @@ -588,13 +588,19 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
if (p_data.get_type() == Variant::STRING && is_editable()) {
set_caret_at_pixel_pos(p_point.x);
int caret_column_tmp = caret_column;
bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end;
if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
is_inside_sel = selection.enabled && caret_column > selection.begin && caret_column < selection.end;
}
if (selection.drag_attempt) {
selection.drag_attempt = false;
if (caret_column < selection.begin || caret_column > selection.end) {
if (caret_column_tmp > selection.end) {
caret_column_tmp = caret_column_tmp - (selection.end - selection.begin);
if (!is_inside_sel) {
if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) {
if (caret_column_tmp > selection.end) {
caret_column_tmp = caret_column_tmp - (selection.end - selection.begin);
}
selection_delete();
}
selection_delete();

set_caret_column(caret_column_tmp);
insert_text_at_caret(p_data);
Expand Down Expand Up @@ -975,7 +981,7 @@ void LineEdit::_notification(int p_what) {
if (is_drag_successful()) {
if (selection.drag_attempt) {
selection.drag_attempt = false;
if (is_editable()) {
if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
selection_delete();
} else if (deselect_on_focus_loss_enabled) {
deselect();
Expand Down
133 changes: 131 additions & 2 deletions scene/gui/text_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "core/os/os.h"
#include "core/string/string_builder.h"
#include "core/string/translation.h"
#include "label.h"

#include "scene/main/window.h"

Expand Down Expand Up @@ -1224,7 +1225,7 @@ void TextEdit::_notification(int p_what) {

if (caret.draw_pos.x >= xmargin_beg && caret.draw_pos.x < xmargin_end) {
caret.visible = true;
if (draw_caret) {
if (draw_caret || drag_caret_force_displayed) {
if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) {
//Block or underline caret, draw trailing carets at full height.
int h = font->get_height(font_size);
Expand Down Expand Up @@ -1391,7 +1392,7 @@ void TextEdit::_notification(int p_what) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}

if (deselect_on_focus_loss_enabled) {
if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
deselect();
}
} break;
Expand All @@ -1411,6 +1412,30 @@ void TextEdit::_notification(int p_what) {
update();
}
} break;
case Control::NOTIFICATION_DRAG_BEGIN: {
selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE;
drag_action = true;
dragging_minimap = false;
dragging_selection = false;
can_drag_minimap = false;
click_select_held->stop();
} break;
case Control::NOTIFICATION_DRAG_END: {
if (is_drag_successful()) {
if (selection.drag_attempt) {
selection.drag_attempt = false;
if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
delete_selection();
} else if (deselect_on_focus_loss_enabled) {
deselect();
}
}
} else {
selection.drag_attempt = false;
}
drag_action = false;
drag_caret_force_displayed = false;
} break;
}
}

Expand Down Expand Up @@ -1495,6 +1520,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {

set_caret_line(row, false, false);
set_caret_column(col);
selection.drag_attempt = false;

if (mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) {
if (!selection.active) {
Expand Down Expand Up @@ -1538,6 +1564,9 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {

update();
}
} else if (is_mouse_over_selection()) {
selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE;
selection.drag_attempt = true;
} else {
selection.active = false;
selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
Expand All @@ -1551,6 +1580,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance) {
// Triple-click select line.
selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE;
selection.drag_attempt = false;
_update_selection_mode_line();
last_dblclk = 0;
} else if (mb->is_double_click() && text[caret.line].length()) {
Expand Down Expand Up @@ -1601,10 +1631,16 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
} else {
if (mb->get_button_index() == MouseButton::LEFT) {
if (selection.drag_attempt && is_mouse_over_selection()) {
selection.active = false;
}
dragging_minimap = false;
dragging_selection = false;
can_drag_minimap = false;
click_select_held->stop();
if (!drag_action) {
selection.drag_attempt = false;
}
Comment on lines +1641 to +1643
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't NOTIFICATION_DRAG_END do this already?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drag will be triggered in Viewport only if there is a mouse movement, so it is possible that selection.drag_attempt is true but NOTIFICATION_DRAG_END is not called.

if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
Expand Down Expand Up @@ -1689,6 +1725,14 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
hovered_gutter = current_hovered_gutter;
update();
}

if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) {
drag_caret_force_displayed = true;
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
set_caret_line(pos.y, false);
set_caret_column(pos.x);
dragging_selection = true;
}
}

if (draw_minimap && !dragging_selection) {
Expand Down Expand Up @@ -2406,6 +2450,75 @@ bool TextEdit::is_text_field() const {
return true;
}

Variant TextEdit::get_drag_data(const Point2 &p_point) {
if (selection.active && selection.drag_attempt) {
String t = get_selected_text();
Label *l = memnew(Label);
l->set_text(t);
set_drag_preview(l);
return t;
}

return Variant();
}

bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
bool drop_override = Control::can_drop_data(p_point, p_data); // In case user wants to drop custom data.
if (drop_override) {
return drop_override;
}

return is_editable() && p_data.get_type() == Variant::STRING;
}

void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
Control::drop_data(p_point, p_data);

if (p_data.get_type() == Variant::STRING && is_editable()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can invert this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
int caret_row_tmp = pos.y;
int caret_column_tmp = pos.x;
if (selection.drag_attempt) {
selection.drag_attempt = false;
if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CTRL))) {
begin_complex_operation();
if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) {
if (caret_row_tmp > selection.to_line) {
caret_row_tmp = caret_row_tmp - (selection.to_line - selection.from_line);
} else if (caret_row_tmp == selection.to_line && caret_column_tmp >= selection.to_column) {
caret_column_tmp = caret_column_tmp - (selection.to_column - selection.from_column);
}
delete_selection();
} else {
deselect();
}

set_caret_line(caret_row_tmp, true, false);
set_caret_column(caret_column_tmp);
insert_text_at_caret(p_data);
end_complex_operation();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to call delete_selection here as insert_text_at_caret will handle that. This should also remove the need to make this a complex operation. Similar for the else if below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the second delete_selection after the else if
The first one instead is needed, because is true that insert_text_at_caret call delete_selection, but delete_selection reset the caret position at the beginning of the selection and this break the code for the drag and drop.

}
} else if (is_mouse_over_selection()) {
caret_row_tmp = selection.from_line;
caret_column_tmp = selection.from_column;
set_caret_line(caret_row_tmp, true, false);
set_caret_column(caret_column_tmp);
insert_text_at_caret(p_data);
grab_focus();
} else {
deselect();
set_caret_line(caret_row_tmp, true, false);
set_caret_column(caret_column_tmp);
insert_text_at_caret(p_data);
grab_focus();
}

if (caret_row_tmp != caret.line || caret_column_tmp != caret.column) {
select(caret_row_tmp, caret_column_tmp, caret.line, caret.column);
}
}
}

Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
Point2i pos = get_line_column_at_pos(p_pos);
int row = pos.y;
Expand Down Expand Up @@ -3580,6 +3693,21 @@ bool TextEdit::is_dragging_cursor() const {
return dragging_selection || dragging_minimap;
}

bool TextEdit::is_mouse_over_selection(bool p_edges) const {
if (!has_selection()) {
return false;
}
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
int row = pos.y;
int col = pos.x;
if (p_edges) {
if ((row == selection.from_line && col == selection.from_column) || (row == selection.to_line && col == selection.to_column)) {
return true;
}
}
return (row >= selection.from_line && row <= selection.to_line && (row > selection.from_line || col > selection.from_column) && (row < selection.to_line || col < selection.to_column));
}

/* Caret */
void TextEdit::set_caret_type(CaretType p_type) {
caret_type = p_type;
Expand Down Expand Up @@ -4776,6 +4904,7 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos);

ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor);
ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges"), &TextEdit::is_mouse_over_selection);

/* Caret. */
BIND_ENUM_CONSTANT(CARET_TYPE_LINE);
Expand Down
8 changes: 8 additions & 0 deletions scene/gui/text_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@ class TextEdit : public Control {

bool caret_mid_grapheme_enabled = false;

bool drag_action = false;
bool drag_caret_force_displayed = false;

void _emit_caret_changed();

void _reset_caret_blink_timer();
Expand All @@ -400,6 +403,7 @@ class TextEdit : public Control {
int to_column = 0;

bool shiftclick_left = false;
bool drag_attempt = false;
} selection;

bool selecting_enabled = true;
Expand Down Expand Up @@ -611,6 +615,9 @@ class TextEdit : public Control {
virtual Size2 get_minimum_size() const override;
virtual bool is_text_field() const override;
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
virtual Variant get_drag_data(const Point2 &p_point) override;
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override;
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override;
virtual String get_tooltip(const Point2 &p_pos) const override;
void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata);

Expand Down Expand Up @@ -731,6 +738,7 @@ class TextEdit : public Control {
int get_minimap_line_at_pos(const Point2i &p_pos) const;

bool is_dragging_cursor() const;
bool is_mouse_over_selection(bool p_edges = true) const;

/* Caret */
void set_caret_type(CaretType p_type);
Expand Down