Browse Source

Merge pull request #55355 from ConteZero/drag_and_drop_3.x

Rémi Verschelde 3 years ago
parent
commit
08c3e00b95

+ 6 - 0
doc/classes/Control.xml

@@ -505,6 +505,12 @@
 				See [method add_stylebox_override].
 			</description>
 		</method>
+		<method name="is_drag_successful" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if drag operation is successful.
+			</description>
+		</method>
 		<method name="minimum_size_changed">
 			<return type="void" />
 			<description>

+ 7 - 0
doc/classes/TextEdit.xml

@@ -325,6 +325,13 @@
 				Returns if the given line is wrapped.
 			</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_selection_active" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 6 - 0
doc/classes/Viewport.xml

@@ -114,6 +114,12 @@
 				Returns [code]true[/code] if there are visible modals on-screen.
 			</description>
 		</method>
+		<method name="gui_is_drag_successful" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if the drag operation is successful.
+			</description>
+		</method>
 		<method name="gui_is_dragging" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 5 - 0
scene/gui/control.cpp

@@ -769,6 +769,10 @@ void Control::set_drag_preview(Control *p_control) {
 	get_viewport()->_gui_set_drag_preview(this, p_control);
 }
 
+bool Control::is_drag_successful() const {
+	return is_inside_tree() && get_viewport()->gui_is_drag_successful();
+}
+
 bool Control::is_window_modal_on_top() const {
 	if (!is_inside_tree()) {
 		return false;
@@ -2756,6 +2760,7 @@ void Control::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("set_drag_forwarding", "target"), &Control::set_drag_forwarding);
 	ClassDB::bind_method(D_METHOD("set_drag_preview", "control"), &Control::set_drag_preview);
+	ClassDB::bind_method(D_METHOD("is_drag_successful"), &Control::is_drag_successful);
 
 	ClassDB::bind_method(D_METHOD("warp_mouse", "to_position"), &Control::warp_mouse);
 

+ 1 - 0
scene/gui/control.h

@@ -316,6 +316,7 @@ public:
 	virtual void drop_data(const Point2 &p_point, const Variant &p_data);
 	void set_drag_preview(Control *p_control);
 	void force_drag(const Variant &p_data, Control *p_control);
+	bool is_drag_successful() const;
 
 	void set_custom_minimum_size(const Size2 &p_custom);
 	Size2 get_custom_minimum_size() const;

+ 75 - 16
scene/gui/line_edit.cpp

@@ -31,11 +31,13 @@
 #include "line_edit.h"
 
 #include "core/message_queue.h"
+#include "core/os/input.h"
 #include "core/os/keyboard.h"
 #include "core/os/os.h"
 #include "core/print_string.h"
 #include "core/translation.h"
 #include "label.h"
+#include "scene/main/viewport.h"
 
 #ifdef TOOLS_ENABLED
 #include "editor/editor_scale.h"
@@ -79,7 +81,9 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
 				return;
 			}
 
-			shift_selection_check_pre(b->get_shift());
+			if (b->get_shift()) {
+				shift_selection_check_pre(true);
+			}
 
 			set_cursor_at_pixel_pos(b->get_position().x);
 
@@ -124,7 +128,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
 					deselect();
 					selection.cursor_start = cursor_pos;
 					selection.creating = true;
-				} else if (selection.enabled) {
+				} else if (selection.enabled && !selection.doubleclick) {
 					selection.drag_attempt = true;
 				}
 			}
@@ -146,6 +150,9 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
 			}
 			selection.creating = false;
 			selection.doubleclick = false;
+			if (!drag_action) {
+				selection.drag_attempt = false;
+			}
 
 			show_virtual_keyboard();
 		}
@@ -170,6 +177,11 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
 				selection_fill_at_cursor();
 			}
 		}
+
+		if (drag_action && can_drop_data(m->get_position(), get_viewport()->gui_get_drag_data())) {
+			drag_caret_force_displayed = true;
+			set_cursor_at_pixel_pos(m->get_position().x);
+		}
 	}
 
 	Ref<InputEventKey> k = p_event;
@@ -641,28 +653,50 @@ bool LineEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const
 		return drop_override;
 	}
 
-	return p_data.get_type() == Variant::STRING;
+	return is_editable() && p_data.get_type() == Variant::STRING;
 }
 
 void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
 	Control::drop_data(p_point, p_data);
 
-	if (p_data.get_type() == Variant::STRING) {
+	if (p_data.get_type() == Variant::STRING && is_editable()) {
 		set_cursor_at_pixel_pos(p_point.x);
-		int selected = selection.end - selection.begin;
+		int caret_column_tmp = cursor_pos;
+		bool is_inside_sel = selection.enabled && cursor_pos >= selection.begin && cursor_pos <= selection.end;
+		if (Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+			is_inside_sel = selection.enabled && cursor_pos > selection.begin && cursor_pos < selection.end;
+		}
+		if (selection.drag_attempt) {
+			selection.drag_attempt = false;
+			if (!is_inside_sel) {
+				if (!Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+					if (caret_column_tmp > selection.end) {
+						caret_column_tmp = caret_column_tmp - (selection.end - selection.begin);
+					}
+					selection_delete();
+				}
 
-		Ref<Font> font = get_font("font");
-		if (font != nullptr) {
-			for (int i = selection.begin; i < selection.end; i++) {
-				cached_width -= font->get_char_size(pass ? secret_character[0] : text[i]).width;
+				set_cursor_position(caret_column_tmp);
+				append_at_cursor(p_data);
 			}
+		} else if (selection.enabled && cursor_pos >= selection.begin && cursor_pos <= selection.end) {
+			caret_column_tmp = selection.begin;
+			selection_delete();
+			set_cursor_position(caret_column_tmp);
+			append_at_cursor(p_data);
+			grab_focus();
+		} else {
+			append_at_cursor(p_data);
+			grab_focus();
 		}
-
-		text.erase(selection.begin, selected);
-
-		append_at_cursor(p_data);
-		selection.begin = cursor_pos - selected;
-		selection.end = cursor_pos;
+		select(caret_column_tmp, cursor_pos);
+		if (!text_changed_dirty) {
+			if (is_inside_tree()) {
+				MessageQueue::get_singleton()->push_call(this, "_text_changed");
+			}
+			text_changed_dirty = true;
+		}
+		update();
 	}
 }
 
@@ -918,7 +952,7 @@ void LineEdit::_notification(int p_what) {
 				}
 			}
 
-			if ((char_ofs == cursor_pos || using_placeholder) && draw_caret) { // May be at the end, or placeholder.
+			if ((char_ofs == cursor_pos || using_placeholder || drag_caret_force_displayed) && draw_caret) { // May be at the end, or placeholder.
 				if (ime_text.length() == 0) {
 					int caret_x_ofs = x_ofs;
 					if (using_placeholder) {
@@ -985,6 +1019,25 @@ void LineEdit::_notification(int p_what) {
 				update();
 			}
 		} break;
+		case Control::NOTIFICATION_DRAG_BEGIN: {
+			drag_action = true;
+		} 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_CONTROL)) {
+						selection_delete();
+						// } else if (deselect_on_focus_loss_enabled) {
+						// 	deselect();
+					}
+				}
+			} else {
+				selection.drag_attempt = false;
+			}
+			drag_action = false;
+			drag_caret_force_displayed = false;
+		} break;
 	}
 }
 
@@ -1041,6 +1094,9 @@ void LineEdit::undo() {
 	} else if (undo_stack_pos == undo_stack.front()) {
 		return;
 	}
+
+	deselect();
+
 	undo_stack_pos = undo_stack_pos->prev();
 	TextOperation op = undo_stack_pos->get();
 	text = op.text;
@@ -1062,6 +1118,9 @@ void LineEdit::redo() {
 	if (undo_stack_pos == undo_stack.back()) {
 		return;
 	}
+
+	deselect();
+
 	undo_stack_pos = undo_stack_pos->next();
 	TextOperation op = undo_stack_pos->get();
 	text = op.text;

+ 3 - 0
scene/gui/line_edit.h

@@ -92,6 +92,9 @@ private:
 
 	bool virtual_keyboard_enabled = true;
 
+	bool drag_action = false;
+	bool drag_caret_force_displayed = false;
+
 	Ref<Texture> right_icon;
 
 	struct Selection {

+ 57 - 7
scene/gui/rich_text_label.cpp

@@ -33,6 +33,7 @@
 #include "core/math/math_defs.h"
 #include "core/os/keyboard.h"
 #include "core/os/os.h"
+#include "label.h"
 #include "scene/scene_string_names.h"
 
 #ifdef TOOLS_ENABLED
@@ -1062,6 +1063,9 @@ void RichTextLabel::_notification(int p_what) {
 				update();
 			}
 		}
+		case Control::NOTIFICATION_DRAG_END: {
+			selection.drag_attempt = false;
+		} break;
 	}
 }
 
@@ -1144,6 +1148,9 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
 				Item *item = nullptr;
 
 				bool outside;
+
+				selection.drag_attempt = false;
+
 				_find_click(main, b->get_position(), &item, &line, &outside);
 
 				if (item) {
@@ -1153,13 +1160,18 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
 
 						// Erase previous selection.
 						if (selection.active) {
-							selection.from = nullptr;
-							selection.from_char = '\0';
-							selection.to = nullptr;
-							selection.to_char = '\0';
-							selection.active = false;
-
-							update();
+							if (_is_click_inside_selection()) {
+								selection.drag_attempt = true;
+								selection.click = nullptr;
+							} else {
+								selection.from = nullptr;
+								selection.from_char = '\0';
+								selection.to = nullptr;
+								selection.to_char = '\0';
+								selection.active = false;
+
+								update();
+							}
 						}
 					}
 				}
@@ -1169,6 +1181,8 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
 				Item *item = nullptr;
 				bool outside;
 
+				selection.drag_attempt = false;
+
 				_find_click(main, b->get_position(), &item, &line, &outside);
 
 				while (item && item->type != ITEM_TEXT) {
@@ -1189,6 +1203,25 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
 					}
 				}
 			} else if (!b->is_pressed()) {
+				if (selection.drag_attempt) {
+					selection.drag_attempt = false;
+					int line = 0;
+					Item *item = nullptr;
+					bool outside;
+					_find_click(main, b->get_position(), &item, &line, &outside);
+					selection.click = item;
+					selection.click_char = line;
+					if (_is_click_inside_selection()) {
+						selection.active = false;
+						selection.from = nullptr;
+						selection.from_char = '\0';
+						selection.to = nullptr;
+						selection.to_char = '\0';
+						selection.active = false;
+
+						update();
+					}
+				}
 				selection.click = nullptr;
 
 				if (!b->is_doubleclick() && !scroll_updated) {
@@ -2498,6 +2531,22 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) {
 	}
 }
 
+Variant RichTextLabel::get_drag_data(const Point2 &p_point) {
+	if (selection.drag_attempt && selection.enabled) {
+		String t = get_selected_text();
+		Label *l = memnew(Label);
+		l->set_text(t);
+		set_drag_preview(l);
+		return t;
+	}
+
+	return Variant();
+}
+
+bool RichTextLabel::_is_click_inside_selection() const {
+	return (selection.click->index >= selection.from->index && selection.click->index <= selection.to->index && (selection.click->index > selection.from->index || selection.click_char >= selection.from_char) && (selection.click->index < selection.to->index || selection.click_char <= selection.to_char));
+}
+
 bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p_search_previous) {
 	ERR_FAIL_COND_V(!selection.enabled, false);
 	Item *it = main;
@@ -3007,6 +3056,7 @@ RichTextLabel::RichTextLabel() {
 	selection.click = nullptr;
 	selection.active = false;
 	selection.enabled = false;
+	selection.drag_attempt = false;
 
 	visible_characters = -1;
 	percent_visible = 1;

+ 3 - 0
scene/gui/rich_text_label.h

@@ -358,6 +358,7 @@ private:
 
 		bool active; // anything selected? i.e. from, to, etc. valid?
 		bool enabled; // allow selections?
+		bool drag_attempt;
 	};
 
 	Selection selection;
@@ -365,6 +366,7 @@ private:
 	int visible_characters;
 	float percent_visible;
 
+	bool _is_click_inside_selection() const;
 	int _process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &y, int p_width, int p_line, ProcessMode p_mode, const Ref<Font> &p_base_font, const Color &p_base_color, const Color &p_font_color_shadow, bool p_shadow_as_outline, const Point2 &shadow_ofs, const Point2i &p_click_pos = Point2i(), Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr, int p_char_count = 0);
 	void _find_click(ItemFrame *p_frame, const Point2i &p_click, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr);
 
@@ -465,6 +467,7 @@ public:
 	VScrollBar *get_v_scroll() { return vscroll; }
 
 	virtual CursorShape get_cursor_shape(const Point2 &p_pos) const;
+	virtual Variant get_drag_data(const Point2 &p_point);
 
 	void set_selection_enabled(bool p_enabled);
 	bool is_selection_enabled() const;

+ 162 - 3
scene/gui/text_edit.cpp

@@ -36,6 +36,7 @@
 #include "core/os/os.h"
 #include "core/project_settings.h"
 #include "core/script_language.h"
+#include "label.h"
 #include "scene/main/viewport.h"
 
 #ifdef TOOLS_ENABLED
@@ -432,6 +433,7 @@ void TextEdit::_click_selection_held() {
 }
 
 void TextEdit::_update_selection_mode_pointer() {
+	selection.drag_attempt = false;
 	dragging_selection = true;
 	Point2 mp = get_local_mouse_position();
 
@@ -448,6 +450,7 @@ void TextEdit::_update_selection_mode_pointer() {
 }
 
 void TextEdit::_update_selection_mode_word() {
+	selection.drag_attempt = false;
 	dragging_selection = true;
 	Point2 mp = get_local_mouse_position();
 
@@ -505,6 +508,7 @@ void TextEdit::_update_selection_mode_word() {
 }
 
 void TextEdit::_update_selection_mode_line() {
+	selection.drag_attempt = false;
 	dragging_selection = true;
 	Point2 mp = get_local_mouse_position();
 
@@ -1504,7 +1508,7 @@ void TextEdit::_notification(int p_what) {
 								}
 							}
 							if (ime_text.length() == 0) {
-								if (draw_caret) {
+								if (draw_caret || drag_caret_force_displayed) {
 									if (insert_mode) {
 #ifdef TOOLS_ENABLED
 										int caret_h = (block_caret) ? 4 : 2 * EDSCALE;
@@ -1610,7 +1614,7 @@ void TextEdit::_notification(int p_what) {
 							}
 						}
 						if (ime_text.length() == 0) {
-							if (draw_caret) {
+							if (draw_caret || drag_caret_force_displayed) {
 								if (insert_mode) {
 									int char_w = cache.font->get_char_size(' ').width;
 #ifdef TOOLS_ENABLED
@@ -1893,6 +1897,37 @@ void TextEdit::_notification(int p_what) {
 				update();
 			}
 		} break;
+		case Control::NOTIFICATION_DRAG_BEGIN: {
+			selection.selecting_mode = 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 (!readonly && !Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+						_remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
+						cursor_set_line(selection.from_line, false);
+						cursor_set_column(selection.from_column);
+						selection.active = false;
+						selection.selecting_mode = Selection::MODE_NONE;
+						update();
+					}
+				}
+			} else {
+				selection.drag_attempt = false;
+			}
+			drag_action = false;
+			drag_caret_force_displayed = false;
+			dragging_minimap = false;
+			dragging_selection = false;
+			can_drag_minimap = false;
+			click_select_held->stop();
+		} break;
 	}
 }
 
@@ -2486,6 +2521,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 
 				cursor_set_line(row, false, false);
 				cursor_set_column(col);
+				selection.drag_attempt = false;
 
 				if (mb->get_shift() && (cursor.column != prev_col || cursor.line != prev_line)) {
 					if (!selection.active) {
@@ -2531,7 +2567,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 
 						update();
 					}
-
+				} else if (is_mouse_over_selection()) {
+					selection.selecting_mode = Selection::MODE_NONE;
+					selection.drag_attempt = true;
 				} else {
 					selection.active = false;
 					selection.selecting_mode = Selection::MODE_POINTER;
@@ -2595,6 +2633,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 			}
 		} else {
 			if (mb->get_button_index() == BUTTON_LEFT) {
+				if (selection.drag_attempt && selection.selecting_mode == Selection::MODE_NONE && is_mouse_over_selection()) {
+					selection.active = false;
+				}
 				if (mb->get_command() && highlighted_word != String()) {
 					int row, col;
 					_get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col);
@@ -2607,6 +2648,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				dragging_selection = false;
 				can_drag_minimap = false;
 				click_select_held->stop();
+				if (!drag_action) {
+					selection.drag_attempt = false;
+				}
 			}
 
 			// Notify to show soft keyboard.
@@ -2676,6 +2720,22 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				}
 			}
 		}
+
+		if (drag_action && can_drop_data(mm->get_position(), get_viewport()->gui_get_drag_data())) {
+			drag_caret_force_displayed = true;
+			Point2 mp = get_local_mouse_position();
+			int row, col;
+			_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
+			cursor_set_line(row, true);
+			cursor_set_column(col);
+			if (row <= get_first_visible_line()) {
+				_scroll_lines_up();
+			} else if (row >= get_last_full_visible_line()) {
+				_scroll_lines_down();
+			}
+			dragging_selection = true;
+			update();
+		}
 	}
 
 	if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
@@ -4967,6 +5027,104 @@ void TextEdit::insert_text_at_cursor(const String &p_text) {
 	update();
 }
 
+Variant TextEdit::get_drag_data(const Point2 &p_point) {
+	if (selection.active && selection.drag_attempt) {
+		String t = get_selection_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 !readonly && 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 && !readonly) {
+		Point2 mp = get_local_mouse_position();
+		int caret_row_tmp, caret_column_tmp;
+		_get_mouse_pos(Point2i(mp.x, mp.y), caret_row_tmp, caret_column_tmp);
+		if (selection.drag_attempt) {
+			selection.drag_attempt = false;
+			if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(KEY_CONTROL))) {
+				begin_complex_operation();
+				if (!Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+					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);
+					}
+
+					_remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
+					cursor_set_line(selection.from_line, false);
+					cursor_set_column(selection.from_column);
+					selection.active = false;
+					selection.selecting_mode = Selection::MODE_NONE;
+				} else {
+					deselect();
+				}
+
+				cursor_set_line(caret_row_tmp, true, false);
+				cursor_set_column(caret_column_tmp);
+				insert_text_at_cursor(p_data);
+				end_complex_operation();
+			}
+		} else if (is_mouse_over_selection()) {
+			begin_complex_operation();
+			caret_row_tmp = selection.from_line;
+			caret_column_tmp = selection.from_column;
+
+			_remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
+			cursor_set_line(selection.from_line, false);
+			cursor_set_column(selection.from_column);
+			selection.active = false;
+			selection.selecting_mode = Selection::MODE_NONE;
+
+			cursor_set_line(caret_row_tmp, true, false);
+			cursor_set_column(caret_column_tmp);
+			insert_text_at_cursor(p_data);
+			end_complex_operation();
+			grab_focus();
+		} else {
+			deselect();
+			cursor_set_line(caret_row_tmp, true, false);
+			cursor_set_column(caret_column_tmp);
+			insert_text_at_cursor(p_data);
+			grab_focus();
+		}
+
+		if (caret_row_tmp != cursor.line || caret_column_tmp != cursor.column) {
+			select(caret_row_tmp, caret_column_tmp, cursor.line, cursor.column);
+		}
+	}
+}
+
+bool TextEdit::is_mouse_over_selection(bool p_edges) const {
+	if (!selection.active) {
+		return false;
+	}
+	Point2 mp = get_local_mouse_position();
+	int row, col;
+	_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
+	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));
+}
+
 Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
 	if (highlighted_word != String()) {
 		return CURSOR_POINTING_HAND;
@@ -7278,6 +7436,7 @@ void TextEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_selection_to_line"), &TextEdit::get_selection_to_line);
 	ClassDB::bind_method(D_METHOD("get_selection_to_column"), &TextEdit::get_selection_to_column);
 	ClassDB::bind_method(D_METHOD("get_selection_text"), &TextEdit::get_selection_text);
+	ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges"), &TextEdit::is_mouse_over_selection);
 	ClassDB::bind_method(D_METHOD("get_word_under_cursor"), &TextEdit::get_word_under_cursor);
 	ClassDB::bind_method(D_METHOD("search", "key", "flags", "from_line", "from_column"), &TextEdit::_search_bind);
 

+ 10 - 0
scene/gui/text_edit.h

@@ -192,6 +192,8 @@ private:
 		int to_line, to_column;
 
 		bool shiftclick_left;
+		bool drag_attempt;
+
 		Selection() {
 			selecting_mode = MODE_NONE;
 			selecting_line = 0;
@@ -206,6 +208,7 @@ private:
 			to_line = 0;
 			to_column = 0;
 			shiftclick_left = false;
+			drag_attempt = false;
 		}
 	} selection;
 
@@ -473,6 +476,9 @@ private:
 	int get_char_pos_for(int p_px, String p_str) const;
 	int get_column_x_offset(int p_char, String p_str) const;
 
+	bool drag_action = false;
+	bool drag_caret_force_displayed = false;
+
 	void adjust_viewport_to_cursor();
 	void _scroll_moved(double);
 	void _update_scrollbars();
@@ -587,6 +593,9 @@ public:
 	};
 
 	virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const;
+	virtual Variant get_drag_data(const Point2 &p_point);
+	virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
+	virtual void drop_data(const Point2 &p_point, const Variant &p_data);
 
 	void _get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const;
 	void _get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const;
@@ -723,6 +732,7 @@ public:
 	int get_selection_to_line() const;
 	int get_selection_to_column() const;
 	String get_selection_text() const;
+	bool is_mouse_over_selection(bool p_edges = true) const;
 
 	String get_word_under_cursor() const;
 	String get_word_at_pos(const Vector2 &p_pos) const;

+ 10 - 2
scene/main/viewport.cpp

@@ -183,6 +183,7 @@ public:
 
 Viewport::GUI::GUI() {
 	dragging = false;
+	drag_successful = false;
 	mouse_focus = nullptr;
 	mouse_click_grabber = nullptr;
 	mouse_focus_mask = 0;
@@ -1978,8 +1979,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
 
 			if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == BUTTON_LEFT) {
 				//alternate drop use (when using force_drag(), as proposed by #5342
+				gui.drag_successful = false;
 				if (gui.mouse_focus) {
-					_gui_drop(gui.mouse_focus, pos, false);
+					gui.drag_successful = _gui_drop(gui.mouse_focus, pos, false);
 				}
 
 				gui.drag_data = Variant();
@@ -1997,11 +1999,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
 			_gui_cancel_tooltip();
 		} else {
 			if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == BUTTON_LEFT) {
+				gui.drag_successful = false;
 				if (gui.mouse_over) {
 					Size2 pos = mpos;
 					pos = gui.focus_inv_xform.xform(pos);
 
-					_gui_drop(gui.mouse_over, pos, false);
+					gui.drag_successful = _gui_drop(gui.mouse_over, pos, false);
 				}
 
 				Control *drag_preview = _gui_get_drag_preview();
@@ -3124,6 +3127,10 @@ bool Viewport::gui_is_dragging() const {
 	return gui.dragging;
 }
 
+bool Viewport::gui_is_drag_successful() const {
+	return gui.drag_successful;
+}
+
 void Viewport::set_input_as_handled() {
 	_drop_physics_mouseover();
 	if (handle_input_locally) {
@@ -3257,6 +3264,7 @@ void Viewport::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("gui_has_modal_stack"), &Viewport::gui_has_modal_stack);
 	ClassDB::bind_method(D_METHOD("gui_get_drag_data"), &Viewport::gui_get_drag_data);
 	ClassDB::bind_method(D_METHOD("gui_is_dragging"), &Viewport::gui_is_dragging);
+	ClassDB::bind_method(D_METHOD("gui_is_drag_successful"), &Viewport::gui_is_drag_successful);
 
 	ClassDB::bind_method(D_METHOD("get_modal_stack_top"), &Viewport::get_modal_stack_top);
 

+ 2 - 0
scene/main/viewport.h

@@ -322,6 +322,7 @@ private:
 		List<Control *> roots;
 		int canvas_sort_index; //for sorting items with canvas as root
 		bool dragging;
+		bool drag_successful;
 
 		GUI();
 	} gui;
@@ -581,6 +582,7 @@ public:
 	bool is_handling_input_locally() const;
 
 	bool gui_is_dragging() const;
+	bool gui_is_drag_successful() const;
 
 	Viewport();
 	~Viewport();