Browse Source

Merge pull request #101613 from kitbdev/fix-te-hover-mouse-exit

Fix TextEdit breakpoint hover not hiding on mouse exit
Thaddeus Crews 7 months ago
parent
commit
c032ce4050
4 changed files with 140 additions and 50 deletions
  1. 0 1
      scene/gui/code_edit.cpp
  2. 52 49
      scene/gui/text_edit.cpp
  3. 1 0
      scene/gui/text_edit.h
  4. 87 0
      tests/scene/test_text_edit.h

+ 0 - 1
scene/gui/code_edit.cpp

@@ -275,7 +275,6 @@ void CodeEdit::_notification(int p_what) {
 		} break;
 		} break;
 
 
 		case NOTIFICATION_MOUSE_EXIT: {
 		case NOTIFICATION_MOUSE_EXIT: {
-			queue_redraw();
 			symbol_tooltip_timer->stop();
 			symbol_tooltip_timer->stop();
 		} break;
 		} break;
 	}
 	}

+ 52 - 49
scene/gui/text_edit.cpp

@@ -1675,6 +1675,10 @@ void TextEdit::_notification(int p_what) {
 				drag_caret_force_displayed = false;
 				drag_caret_force_displayed = false;
 				queue_redraw();
 				queue_redraw();
 			}
 			}
+			if (hovered_gutter != Vector2i(-1, -1)) {
+				hovered_gutter = Vector2i(-1, -1);
+				queue_redraw();
+			}
 		} break;
 		} break;
 	}
 	}
 }
 }
@@ -1843,18 +1847,18 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 				int col = pos.x;
 				int col = pos.x;
 
 
 				// Gutters.
 				// Gutters.
+				Vector2i current_hovered_gutter = _get_hovered_gutter(mpos);
+				if (current_hovered_gutter != hovered_gutter) {
+					hovered_gutter = current_hovered_gutter;
+					queue_redraw();
+				}
+				if (hovered_gutter != Vector2i(-1, -1)) {
+					emit_signal(SNAME("gutter_clicked"), hovered_gutter.y, hovered_gutter.x);
+					return;
+				}
 				int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
 				int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
-				for (int i = 0; i < gutters.size(); i++) {
-					if (!gutters[i].draw || gutters[i].width <= 0) {
-						continue;
-					}
-
-					if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) {
-						emit_signal(SNAME("gutter_clicked"), line, i);
-						return;
-					}
-
-					left_margin += gutters[i].width;
+				if (mpos.x < left_margin + gutters_width + gutter_padding) {
+					return;
 				}
 				}
 
 
 				// Minimap.
 				// Minimap.
@@ -2066,27 +2070,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 			}
 			}
 		}
 		}
 
 
-		// Check if user is hovering a different gutter, and update if yes.
-		Vector2i current_hovered_gutter = Vector2i(-1, -1);
-
-		int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
-		if (mpos.x <= left_margin + gutters_width + gutter_padding) {
-			int hovered_row = get_line_column_at_pos(mpos).y;
-			for (int i = 0; i < gutters.size(); i++) {
-				if (!gutters[i].draw || gutters[i].width <= 0) {
-					continue;
-				}
-
-				if (mpos.x >= left_margin && mpos.x < left_margin + gutters[i].width) {
-					// We are in this gutter i's horizontal area.
-					current_hovered_gutter = Vector2i(i, hovered_row);
-					break;
-				}
-
-				left_margin += gutters[i].width;
-			}
-		}
-
+		// Update hovered gutter.
+		Vector2i current_hovered_gutter = _get_hovered_gutter(mpos);
 		if (current_hovered_gutter != hovered_gutter) {
 		if (current_hovered_gutter != hovered_gutter) {
 			hovered_gutter = current_hovered_gutter;
 			hovered_gutter = current_hovered_gutter;
 			queue_redraw();
 			queue_redraw();
@@ -3115,24 +3100,16 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
 }
 }
 
 
 Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
 Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
-	Point2i pos = get_line_column_at_pos(p_pos);
-	int row = pos.y;
-
-	int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
-	int gutter = left_margin + gutters_width;
-	if (p_pos.x < gutter) {
-		for (int i = 0; i < gutters.size(); i++) {
-			if (!gutters[i].draw) {
-				continue;
-			}
-
-			if (p_pos.x >= left_margin && p_pos.x < left_margin + gutters[i].width) {
-				if (gutters[i].clickable || is_line_gutter_clickable(row, i)) {
-					return CURSOR_POINTING_HAND;
-				}
-			}
-			left_margin += gutters[i].width;
+	Vector2i current_hovered_gutter = _get_hovered_gutter(p_pos);
+	if (current_hovered_gutter != Vector2i(-1, -1)) {
+		if (gutters[current_hovered_gutter.x].clickable || is_line_gutter_clickable(current_hovered_gutter.y, current_hovered_gutter.x)) {
+			return CURSOR_POINTING_HAND;
+		} else {
+			return CURSOR_ARROW;
 		}
 		}
+	}
+	int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
+	if (p_pos.x < left_margin + gutters_width + gutter_padding) {
 		return CURSOR_ARROW;
 		return CURSOR_ARROW;
 	}
 	}
 
 
@@ -8321,9 +8298,35 @@ void TextEdit::_update_gutter_width() {
 	if (gutters_width > 0) {
 	if (gutters_width > 0) {
 		gutter_padding = 2;
 		gutter_padding = 2;
 	}
 	}
+	if (get_viewport()) {
+		hovered_gutter = _get_hovered_gutter(get_local_mouse_position());
+	}
 	queue_redraw();
 	queue_redraw();
 }
 }
 
 
+Vector2i TextEdit::_get_hovered_gutter(const Point2 &p_mouse_pos) const {
+	int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
+	if (p_mouse_pos.x > left_margin + gutters_width + gutter_padding) {
+		return Vector2i(-1, -1);
+	}
+	int hovered_row = get_line_column_at_pos(p_mouse_pos, false).y;
+	if (hovered_row == -1) {
+		return Vector2i(-1, -1);
+	}
+	for (int i = 0; i < gutters.size(); i++) {
+		if (!gutters[i].draw || gutters[i].width <= 0) {
+			continue;
+		}
+
+		if (p_mouse_pos.x >= left_margin && p_mouse_pos.x < left_margin + gutters[i].width) {
+			return Vector2i(i, hovered_row);
+		}
+
+		left_margin += gutters[i].width;
+	}
+	return Vector2i(-1, -1);
+}
+
 /* Syntax highlighting. */
 /* Syntax highlighting. */
 Vector<Pair<int64_t, Color>> TextEdit::_get_line_syntax_highlighting(int p_line) {
 Vector<Pair<int64_t, Color>> TextEdit::_get_line_syntax_highlighting(int p_line) {
 	if (syntax_highlighter.is_null() || setting_text) {
 	if (syntax_highlighter.is_null() || setting_text) {

+ 1 - 0
scene/gui/text_edit.h

@@ -565,6 +565,7 @@ private:
 	Vector2i hovered_gutter = Vector2i(-1, -1); // X = gutter index, Y = row.
 	Vector2i hovered_gutter = Vector2i(-1, -1); // X = gutter index, Y = row.
 
 
 	void _update_gutter_width();
 	void _update_gutter_width();
+	Vector2i _get_hovered_gutter(const Point2 &p_mouse_pos) const;
 
 
 	/* Syntax highlighting. */
 	/* Syntax highlighting. */
 	Ref<SyntaxHighlighter> syntax_highlighter;
 	Ref<SyntaxHighlighter> syntax_highlighter;

+ 87 - 0
tests/scene/test_text_edit.h

@@ -8151,6 +8151,93 @@ TEST_CASE("[SceneTree][TextEdit] gutters") {
 		// Merging tested via CodeEdit gutters.
 		// Merging tested via CodeEdit gutters.
 	}
 	}
 
 
+	SUBCASE("[TextEdit] gutter mouse") {
+		DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton());
+		// Set size for mouse input.
+		text_edit->set_size(Size2(200, 200));
+
+		text_edit->set_text("test1\ntest2\ntest3\ntest4");
+		text_edit->grab_focus();
+
+		text_edit->add_gutter();
+		text_edit->set_gutter_name(0, "test_gutter");
+		text_edit->set_gutter_width(0, 10);
+		text_edit->set_gutter_clickable(0, true);
+
+		text_edit->add_gutter();
+		text_edit->set_gutter_name(1, "test_gutter_not_clickable");
+		text_edit->set_gutter_width(1, 10);
+		text_edit->set_gutter_clickable(1, false);
+
+		text_edit->add_gutter();
+		CHECK(text_edit->get_gutter_count() == 3);
+		text_edit->set_gutter_name(2, "test_gutter_3");
+		text_edit->set_gutter_width(2, 10);
+		text_edit->set_gutter_clickable(2, true);
+
+		MessageQueue::get_singleton()->flush();
+		const int line_height = text_edit->get_line_height();
+
+		// Defaults to none.
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
+		CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
+
+		// Hover over gutter.
+		SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 1));
+		SIGNAL_CHECK_FALSE("gutter_clicked");
+		CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_POINTING_HAND);
+
+		// Click on gutter.
+		SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 0));
+		SIGNAL_CHECK("gutter_clicked", build_array(build_array(0, 0)));
+
+		// Click on gutter on another line.
+		SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height * 3 + line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 3));
+		SIGNAL_CHECK("gutter_clicked", build_array(build_array(3, 0)));
+
+		// Unclickable gutter can be hovered.
+		SEND_GUI_MOUSE_MOTION_EVENT(Point2(15, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 1));
+		SIGNAL_CHECK_FALSE("gutter_clicked");
+		CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
+
+		// Unclickable gutter can be clicked.
+		SEND_GUI_MOUSE_BUTTON_EVENT(Point2(15, line_height * 2 + line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 2));
+		SIGNAL_CHECK("gutter_clicked", build_array(build_array(2, 1)));
+		CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
+
+		// Hover past last line.
+		SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height * 5), MouseButtonMask::NONE, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
+		SIGNAL_CHECK_FALSE("gutter_clicked");
+		CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
+
+		// Click on gutter past last line.
+		SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height * 5), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
+		SIGNAL_CHECK_FALSE("gutter_clicked");
+
+		// Mouse exit resets hover.
+		SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 1));
+		SEND_GUI_MOUSE_MOTION_EVENT(Point2(-1, -1), MouseButtonMask::NONE, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
+
+		// Removing gutter updates hover.
+		SEND_GUI_MOUSE_MOTION_EVENT(Point2(25, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(2, 1));
+		text_edit->remove_gutter(2);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
+
+		// Updating size updates hover.
+		text_edit->set_gutter_width(1, 20);
+		CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 1));
+	}
+
 	SIGNAL_UNWATCH(text_edit, "gutter_clicked");
 	SIGNAL_UNWATCH(text_edit, "gutter_clicked");
 	SIGNAL_UNWATCH(text_edit, "gutter_added");
 	SIGNAL_UNWATCH(text_edit, "gutter_added");
 	SIGNAL_UNWATCH(text_edit, "gutter_removed");
 	SIGNAL_UNWATCH(text_edit, "gutter_removed");