diff --git a/Userland/Libraries/LibGUI/TextEditor.cpp b/Userland/Libraries/LibGUI/TextEditor.cpp index d07504a9bb..29df2c4c68 100644 --- a/Userland/Libraries/LibGUI/TextEditor.cpp +++ b/Userland/Libraries/LibGUI/TextEditor.cpp @@ -290,6 +290,28 @@ void TextEditor::mousedown_event(MouseEvent& event) } } + if (gutter_content_rect(text_position.line()).contains(event.position())) { + auto& gutter_indicators = m_line_data[text_position.line()]->gutter_indicators; + auto indicator_position = 0; + for (auto i = 0u; i < m_gutter_indicators.size(); ++i) { + if ((gutter_indicators & (1 << i)) == 0) + continue; + + if (gutter_indicator_rect(text_position.line(), indicator_position).contains(event.position())) { + auto& indicator_data = m_gutter_indicators[i]; + if (indicator_data.on_click) + indicator_data.on_click(text_position.line(), event.modifiers()); + return; + } + indicator_position++; + } + + // We didn't click on an indicator + if (on_gutter_click) + on_gutter_click(text_position.line(), event.modifiers()); + return; + } + if (on_mousedown) on_mousedown(); @@ -404,7 +426,7 @@ int TextEditor::gutter_width() const { if (!m_gutter_visible) return 0; - return line_height(); // square gutter + return line_height() * (m_most_gutter_indicators_displayed_on_one_line + 1); } Gfx::IntRect TextEditor::gutter_content_rect(size_t line_index) const @@ -446,6 +468,18 @@ Gfx::IntRect TextEditor::folding_indicator_rect(size_t line_index) const }; } +Gfx::IntRect TextEditor::gutter_indicator_rect(size_t line_number, int indicator_position) const +{ + auto gutter_rect = gutter_content_rect(line_number); + auto indicator_size = gutter_rect.height(); + return Gfx::IntRect { + gutter_rect.right() - static_cast(lroundf(indicator_size * (indicator_position + 1.5f))), + gutter_rect.top(), + indicator_size, + indicator_size + }; +} + Gfx::IntRect TextEditor::gutter_rect_in_inner_coordinates() const { return { 0, 0, gutter_width(), widget_inner_rect().height() }; @@ -599,6 +633,28 @@ void TextEditor::paint_event(PaintEvent& event) } } + // Draw gutter indicators + if (m_gutter_visible) { + for (size_t line_index = first_visible_line; line_index <= last_visible_line; ++line_index) { + if (!document().line_is_visible(line_index)) + continue; + + auto& gutter_indicators = m_line_data[line_index]->gutter_indicators; + if (gutter_indicators == 0) + continue; + + auto indicator_position = 0; + for (auto i = 0u; i < m_gutter_indicators.size(); ++i) { + if ((gutter_indicators & (1 << i)) == 0) + continue; + + auto rect = gutter_indicator_rect(line_index, indicator_position); + m_gutter_indicators[i].draw_indicator(painter, rect, line_index); + indicator_position++; + } + } + } + auto horizontal_scrollbar_value = horizontal_scrollbar().value(); painter.translate(-horizontal_scrollbar_value, -vertical_scrollbar().value()); if (m_icon && horizontal_scrollbar_value > 0) @@ -2511,4 +2567,46 @@ void TextEditor::highlighter_did_set_folding_regions(Vector TextEditor::register_gutter_indicator(PaintGutterIndicator draw_indicator, OnGutterIndicatorClick on_click) +{ + // We use a u32 to store a line's active gutter indicators, so that's the limit of how many we can have. + VERIFY(m_gutter_indicators.size() < 32); + + GutterIndicatorID id = m_gutter_indicators.size(); + TRY(m_gutter_indicators.try_empend(move(draw_indicator), move(on_click))); + return id; +} + +void TextEditor::add_gutter_indicator(GutterIndicatorID id, size_t line) +{ + auto& line_indicators = m_line_data[line]->gutter_indicators; + if (line_indicators & (1 << id.value())) + return; + line_indicators |= (1 << id.value()); + + // Ensure the gutter is at least wide enough to display all the indicators on this line. + if (m_most_gutter_indicators_displayed_on_one_line < m_gutter_indicators.size()) { + unsigned indicators_on_line = popcount(line_indicators); + if (indicators_on_line > m_most_gutter_indicators_displayed_on_one_line) { + m_most_gutter_indicators_displayed_on_one_line = indicators_on_line; + update(); + } + } + + update(gutter_content_rect(line)); +} + +void TextEditor::remove_gutter_indicator(GutterIndicatorID id, size_t line) +{ + m_line_data[line]->gutter_indicators &= ~(1 << id.value()); + update(gutter_content_rect(line)); +} + +void TextEditor::clear_gutter_indicators(GutterIndicatorID id) +{ + for (auto line = 0u; line < line_count(); ++line) + remove_gutter_indicator(id, line); + update(); +} + } diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h index dfaf80fa2f..dfabda73cb 100644 --- a/Userland/Libraries/LibGUI/TextEditor.h +++ b/Userland/Libraries/LibGUI/TextEditor.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -105,6 +106,15 @@ public: bool is_gutter_visible() const { return m_gutter_visible; } void set_gutter_visible(bool); + AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(size_t, GutterIndicatorID, CastToUnderlying, Comparison); + using PaintGutterIndicator = Function; + using OnGutterIndicatorClick = Function; + ErrorOr register_gutter_indicator(PaintGutterIndicator, OnGutterIndicatorClick = nullptr); + void add_gutter_indicator(GutterIndicatorID, size_t line); + void remove_gutter_indicator(GutterIndicatorID, size_t line); + void clear_gutter_indicators(GutterIndicatorID); + Gfx::IntRect gutter_indicator_rect(size_t line_number, int indicator_position) const; + void set_icon(Gfx::Bitmap const*); Gfx::Bitmap const* icon() const { return m_icon; } @@ -113,6 +123,7 @@ public: Function on_focusin; Function on_focusout; Function on_highlighter_change; + Function on_gutter_click; void set_text(StringView, AllowCallback = AllowCallback::Yes); void scroll_cursor_into_view(); @@ -400,6 +411,13 @@ private: int m_horizontal_content_padding { 3 }; TextRange m_selection {}; + struct GutterIndicator { + PaintGutterIndicator draw_indicator; + OnGutterIndicatorClick on_click; + }; + Vector m_gutter_indicators; + unsigned m_most_gutter_indicators_displayed_on_one_line { 0 }; + Optional m_substitution_code_point; mutable OwnPtr> m_substitution_string_data; // Used to avoid repeated String construction. @@ -430,6 +448,7 @@ private: struct LineData { Vector visual_lines; Gfx::IntRect visual_rect; + u32 gutter_indicators { 0 }; // A bitfield of which gutter indicators are present. (1 << GutterIndicatorID) }; Vector> m_line_data;