summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGUI/TextEditor.cpp
diff options
context:
space:
mode:
authorSam Atkins <atkinssj@serenityos.org>2023-03-15 10:54:20 +0000
committerAndreas Kling <kling@serenityos.org>2023-03-31 12:09:40 +0200
commit4b29702fee73951883578be6cd191304d20ef2d8 (patch)
treeb59c06e15caefc4653f479844d06e2850078f8df /Userland/Libraries/LibGUI/TextEditor.cpp
parentce9b9a4f2070f6533ce101900481ed07a37ec3a6 (diff)
downloadserenity-4b29702fee73951883578be6cd191304d20ef2d8.zip
LibGUI: Add gutter indicators to TextEditor :^)
HackStudio's Editor has displayed indicators in its gutter for a long time, but each required manual code to paint them in the right place and respond to click events. All indicators on a line would be painted in the same location. If any other applications wanted to have gutter indicators, they would also need to manually implement the same code. This commit adds an API to GUI::TextEditor so it deals with these indicators. It makes sure that multiple indicators on the same line each have their own area to paint in, and provides a callback for when one is clicked. - `register_gutter_indicator()` should be called early on. It returns a `GutterIndicatorID` that is then used by the other methods. Indicators on a line are painted from right to left, in the order they were registered. - `add_gutter_indicator()` and `remove_gutter_indicator()` add the indicator to the given line. - `clear_gutter_indicators()` removes a given indicator from every line. - The `on_gutter_click` callback is called whenever the user clicks on the gutter, but *not* on an indicator.
Diffstat (limited to 'Userland/Libraries/LibGUI/TextEditor.cpp')
-rw-r--r--Userland/Libraries/LibGUI/TextEditor.cpp100
1 files changed, 99 insertions, 1 deletions
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<int>(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<GUI::TextDocumentFol
recompute_all_visual_lines();
}
+ErrorOr<TextEditor::GutterIndicatorID> 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();
+}
+
}