summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2021-01-03 16:58:46 +0100
committerAndreas Kling <kling@serenityos.org>2021-01-03 17:25:06 +0100
commita0f2135f47dd683e662bf787149657d1469d6fd8 (patch)
treec7e145e1d2470870189d3d27cf9facd3601e9dfb
parent05345fc07dee45bea4fb0578ecf83326b1b6aae2 (diff)
downloadserenity-a0f2135f47dd683e662bf787149657d1469d6fd8.zip
LibVT: Make terminal hyperlinks double-click-to-activate
This patch makes hyperlinked terminal cells require a double click to activate. The appearance of a hovered link is changed to not look like a classic underlined link, but instead like some kind of typical double-clickable GUI widget. Also the hover cursor for links is changed from Hand to Arrow.
-rw-r--r--Libraries/LibVT/TerminalWidget.cpp74
1 files changed, 66 insertions, 8 deletions
diff --git a/Libraries/LibVT/TerminalWidget.cpp b/Libraries/LibVT/TerminalWidget.cpp
index 275886c3c8..4e55c05656 100644
--- a/Libraries/LibVT/TerminalWidget.cpp
+++ b/Libraries/LibVT/TerminalWidget.cpp
@@ -49,6 +49,7 @@
#include <LibGfx/Font.h>
#include <LibGfx/FontDatabase.h>
#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
@@ -309,6 +310,30 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
row_with_cursor = m_terminal.cursor_row() + rows_from_history;
}
+ // Pass: Compute the rect(s) of the currently hovered link, if any.
+ Vector<Gfx::IntRect> hovered_href_rects;
+ if (!m_hovered_href_id.is_null()) {
+ for (u16 visual_row = 0; visual_row < m_terminal.rows(); ++visual_row) {
+ auto& line = m_terminal.line(first_row_from_history + visual_row);
+ for (size_t column = 0; column < line.length(); ++column) {
+ if (m_hovered_href_id == line.attributes()[column].href_id) {
+ bool merged_with_existing_rect = false;
+ auto glyph_rect = this->glyph_rect(visual_row, column);
+ for (auto& rect : hovered_href_rects) {
+ if (rect.inflated(1, 1).intersects(glyph_rect)) {
+ rect = rect.united(glyph_rect);
+ merged_with_existing_rect = true;
+ break;
+ }
+ }
+ if (!merged_with_existing_rect)
+ hovered_href_rects.append(glyph_rect);
+ }
+ }
+ }
+ }
+
+ // Pass: Paint background & text decorations.
for (u16 visual_row = 0; visual_row < m_terminal.rows(); ++visual_row) {
auto row_rect = this->row_rect(visual_row);
if (!event.rect().contains(row_rect))
@@ -321,16 +346,15 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
painter.clear_rect(row_rect, color_from_rgb(line.attributes()[0].background_color).with_alpha(m_opacity));
for (size_t column = 0; column < line.length(); ++column) {
- u32 code_point = line.code_point(column);
bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
&& m_has_logical_focus
&& visual_row == row_with_cursor
&& column == m_terminal.cursor_column();
should_reverse_fill_for_cursor_or_selection |= selection_contains({ first_row_from_history + visual_row, (int)column });
auto attribute = line.attributes()[column];
- auto text_color = color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.background_color : attribute.foreground_color);
auto character_rect = glyph_rect(visual_row, column);
auto cell_rect = character_rect.inflated(0, m_line_spacing);
+ auto text_color = color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.background_color : attribute.foreground_color);
if ((!visual_beep_active && !has_only_one_background_color) || should_reverse_fill_for_cursor_or_selection) {
painter.clear_rect(cell_rect, color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.foreground_color : attribute.background_color).with_alpha(m_opacity));
}
@@ -368,10 +392,41 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
painter.set_pixel({ x, y }, dotted_line_color);
}
}
+ }
+ }
+
+ // Paint the hovered link rects, if any.
+ for (auto rect : hovered_href_rects) {
+ rect.inflate(6, 6);
+ painter.fill_rect(rect, palette().base());
+ painter.draw_rect(rect.inflated(2, 2).intersected(frame_inner_rect()), palette().base_text());
+ }
+
+ // Pass: Paint foreground (text).
+ for (u16 visual_row = 0; visual_row < m_terminal.rows(); ++visual_row) {
+ auto row_rect = this->row_rect(visual_row);
+ if (!event.rect().contains(row_rect))
+ continue;
+ auto& line = m_terminal.line(first_row_from_history + visual_row);
+ for (size_t column = 0; column < line.length(); ++column) {
+ auto attribute = line.attributes()[column];
+ bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
+ && m_has_logical_focus
+ && visual_row == row_with_cursor
+ && column == m_terminal.cursor_column();
+ should_reverse_fill_for_cursor_or_selection |= selection_contains({ first_row_from_history + visual_row, (int)column });
+ auto text_color = color_from_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.background_color : attribute.foreground_color);
+ u32 code_point = line.code_point(column);
if (code_point == ' ')
continue;
+ auto character_rect = glyph_rect(visual_row, column);
+
+ if (!m_hovered_href_id.is_null() && attribute.href_id == m_hovered_href_id) {
+ text_color = palette().base_text();
+ }
+
painter.draw_glyph_or_emoji(
character_rect.location(),
code_point,
@@ -380,6 +435,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
}
}
+ // Draw cursor.
if (!m_has_logical_focus && row_with_cursor < m_terminal.rows()) {
auto& cursor_line = m_terminal.line(first_row_from_history + row_with_cursor);
if (m_terminal.cursor_row() < (m_terminal.rows() - rows_from_history)) {
@@ -656,6 +712,13 @@ VT::Range TerminalWidget::find_previous(const StringView& needle, const VT::Posi
void TerminalWidget::doubleclick_event(GUI::MouseEvent& event)
{
if (event.button() == GUI::MouseButton::Left) {
+ auto attribute = m_terminal.attribute_at(buffer_position_at(event.position()));
+ if (!attribute.href_id.is_null()) {
+ dbgln("Open hyperlinked URL: '{}'", attribute.href);
+ Desktop::Launcher::open(attribute.href);
+ return;
+ }
+
m_triple_click_timer.start();
auto position = buffer_position_at(event.position());
@@ -705,11 +768,6 @@ void TerminalWidget::copy()
void TerminalWidget::mouseup_event(GUI::MouseEvent& event)
{
if (event.button() == GUI::MouseButton::Left) {
- auto attribute = m_terminal.attribute_at(buffer_position_at(event.position()));
- if (!m_active_href_id.is_null() && attribute.href_id == m_active_href_id) {
- dbgln("Open hyperlinked URL: '{}'", attribute.href);
- Desktop::Launcher::open(attribute.href);
- }
if (!m_active_href_id.is_null()) {
m_active_href = {};
m_active_href_id = {};
@@ -768,7 +826,7 @@ void TerminalWidget::mousemove_event(GUI::MouseEvent& event)
m_hovered_href = {};
}
if (!m_hovered_href.is_empty())
- set_override_cursor(Gfx::StandardCursor::Hand);
+ set_override_cursor(Gfx::StandardCursor::Arrow);
else
set_override_cursor(Gfx::StandardCursor::IBeam);
update();