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

Fix TextEdit breakpoint hover not hiding on mouse exit
This commit is contained in:
Thaddeus Crews 2025-01-22 16:32:24 -06:00
commit c032ce4050
No known key found for this signature in database
GPG key ID: 62181B86FE9E5D84
4 changed files with 140 additions and 50 deletions

View file

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

View file

@ -1675,6 +1675,10 @@ void TextEdit::_notification(int p_what) {
drag_caret_force_displayed = false;
queue_redraw();
}
if (hovered_gutter != Vector2i(-1, -1)) {
hovered_gutter = Vector2i(-1, -1);
queue_redraw();
}
} break;
}
}
@ -1843,18 +1847,18 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
int col = pos.x;
// Gutters.
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;
Vector2i current_hovered_gutter = _get_hovered_gutter(mpos);
if (current_hovered_gutter != hovered_gutter) {
hovered_gutter = current_hovered_gutter;
queue_redraw();
}
if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) {
emit_signal(SNAME("gutter_clicked"), line, i);
if (hovered_gutter != Vector2i(-1, -1)) {
emit_signal(SNAME("gutter_clicked"), hovered_gutter.y, hovered_gutter.x);
return;
}
left_margin += gutters[i].width;
int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
if (mpos.x < left_margin + gutters_width + gutter_padding) {
return;
}
// 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) {
hovered_gutter = current_hovered_gutter;
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 {
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)) {
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;
}
}
left_margin += gutters[i].width;
}
int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
if (p_pos.x < left_margin + gutters_width + gutter_padding) {
return CURSOR_ARROW;
}
@ -8321,9 +8298,35 @@ void TextEdit::_update_gutter_width() {
if (gutters_width > 0) {
gutter_padding = 2;
}
if (get_viewport()) {
hovered_gutter = _get_hovered_gutter(get_local_mouse_position());
}
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. */
Vector<Pair<int64_t, Color>> TextEdit::_get_line_syntax_highlighting(int p_line) {
if (syntax_highlighter.is_null() || setting_text) {

View file

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

View file

@ -8151,6 +8151,93 @@ TEST_CASE("[SceneTree][TextEdit] 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_added");
SIGNAL_UNWATCH(text_edit, "gutter_removed");