diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 7f8b4dca786..66080e51035 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -275,7 +275,6 @@ void CodeEdit::_notification(int p_what) { } break; case NOTIFICATION_MOUSE_EXIT: { - queue_redraw(); symbol_tooltip_timer->stop(); } break; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index addbff1e979..cb1bfeed608 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -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 &p_gui_input) { int col = pos.x; // 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); - 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. @@ -2066,27 +2070,8 @@ void TextEdit::gui_input(const Ref &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)) { - 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; } @@ -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> TextEdit::_get_line_syntax_highlighting(int p_line) { if (syntax_highlighter.is_null() || setting_text) { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index ef511096302..335fe9bf03e 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -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 syntax_highlighter; diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index c41eebdf3a4..d7192def621 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -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");