From 64f16c141db38f83d646e885ea13f37c8ce614fd Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 19 Aug 2019 19:12:34 +0200 Subject: [PATCH] Terminal: Implement basic history scrollback This code needs some optimization work to reduce the amount of repaints but the history feature basically works, which is cool :^) Fixes #431. --- Applications/Terminal/TerminalWidget.cpp | 69 ++++++++++++++++++++---- Applications/Terminal/TerminalWidget.h | 7 +++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/Applications/Terminal/TerminalWidget.cpp b/Applications/Terminal/TerminalWidget.cpp index ced819c48c..070548e308 100644 --- a/Applications/Terminal/TerminalWidget.cpp +++ b/Applications/Terminal/TerminalWidget.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,12 @@ TerminalWidget::TerminalWidget(int ptm_fd, RefPtr config) set_frame_shadow(FrameShadow::Sunken); set_frame_thickness(2); + m_scrollbar = new GScrollBar(Orientation::Vertical, this); + m_scrollbar->set_relative_rect(0, 0, 16, 0); + m_scrollbar->on_change = [this](int) { + force_repaint(); + }; + dbgprintf("Terminal: Load config file from %s\n", m_config->file_name().characters()); m_cursor_blink_timer.set_interval(m_config->read_num_entry("Text", "CursorBlinkInterval", @@ -183,11 +190,26 @@ void TerminalWidget::paint_event(GPaintEvent& event) painter.fill_rect(frame_inner_rect(), Color(Color::Black).with_alpha(m_opacity)); invalidate_cursor(); + int rows_from_history = 0; + int first_row_from_history = 0; + int row_with_cursor = m_terminal.cursor_row(); + if (m_scrollbar->value() != m_scrollbar->max()) { + rows_from_history = min((int)m_terminal.rows(), m_scrollbar->max() - m_scrollbar->value()); + first_row_from_history = m_terminal.history().size() - (m_scrollbar->max() - m_scrollbar->value()); + row_with_cursor = m_terminal.cursor_row() + rows_from_history; + } + + auto line_for_visual_row = [&](u16 row) -> const VT::Terminal::Line& { + if (row < rows_from_history) + return m_terminal.history().at(first_row_from_history + row); + return m_terminal.line(row - rows_from_history); + }; + for (u16 row = 0; row < m_terminal.rows(); ++row) { auto row_rect = this->row_rect(row); if (!event.rect().contains(row_rect)) continue; - auto& line = m_terminal.line(row); + auto& line = line_for_visual_row(row); bool has_only_one_background_color = line.has_only_one_background_color(); if (m_visual_beep_timer.is_active()) painter.fill_rect(row_rect, Color::Red); @@ -195,7 +217,7 @@ void TerminalWidget::paint_event(GPaintEvent& event) painter.fill_rect(row_rect, lookup_color(line.attributes[0].background_color).with_alpha(m_opacity)); for (u16 column = 0; column < m_terminal.columns(); ++column) { char ch = line.characters[column]; - bool should_reverse_fill_for_cursor_or_selection = (m_cursor_blink_state && m_in_active_window && row == m_terminal.cursor_row() && column == m_terminal.cursor_column()) + bool should_reverse_fill_for_cursor_or_selection = (m_cursor_blink_state && m_in_active_window && row == row_with_cursor && column == m_terminal.cursor_column()) || selection_contains({ row, column }); auto& attribute = line.attributes[column]; auto character_rect = glyph_rect(row, column); @@ -213,9 +235,12 @@ void TerminalWidget::paint_event(GPaintEvent& event) } } - if (!m_in_active_window) { - auto cell_rect = glyph_rect(m_terminal.cursor_row(), m_terminal.cursor_column()).inflated(0, m_line_spacing); - painter.draw_rect(cell_rect, lookup_color(m_terminal.line(m_terminal.cursor_row()).attributes[m_terminal.cursor_column()].foreground_color)); + if (!m_in_active_window && row_with_cursor < m_terminal.rows()) { + auto& cursor_line = line_for_visual_row(row_with_cursor); + if (m_terminal.cursor_row() < (m_terminal.rows() - rows_from_history)) { + auto cell_rect = glyph_rect(row_with_cursor, m_terminal.cursor_column()).inflated(0, m_line_spacing); + painter.draw_rect(cell_rect, lookup_color(cursor_line.attributes[m_terminal.cursor_column()].foreground_color)); + } } } @@ -234,7 +259,8 @@ void TerminalWidget::invalidate_cursor() void TerminalWidget::flush_dirty_lines() { - if (m_terminal.m_need_full_flush) { + // FIXME: Update smarter when scrolled + if (m_terminal.m_need_full_flush || m_scrollbar->value() != m_scrollbar->max()) { update(); m_terminal.m_need_full_flush = false; return; @@ -257,15 +283,31 @@ void TerminalWidget::force_repaint() void TerminalWidget::resize_event(GResizeEvent& event) { - int new_columns = (event.size().width() - frame_thickness() * 2 - m_inset * 2) / font().glyph_width('x'); - int new_rows = (event.size().height() - frame_thickness() * 2 - m_inset * 2) / m_line_height; + auto base_size = compute_base_size(); + int new_columns = (event.size().width() - base_size.width()) / font().glyph_width('x'); + int new_rows = (event.size().height() - base_size.height()) / m_line_height; m_terminal.set_size(new_columns, new_rows); + + Rect scrollbar_rect = { + event.size().width() - m_scrollbar->width() - frame_thickness(), + frame_thickness(), + m_scrollbar->width(), + event.size().height() - frame_thickness() * 2, + }; + m_scrollbar->set_relative_rect(scrollbar_rect); +} + +Size TerminalWidget::compute_base_size() const +{ + int base_width = frame_thickness() * 2 + m_inset * 2 + m_scrollbar->width(); + int base_height = frame_thickness() * 2 + m_inset * 2; + return { base_width, base_height }; } void TerminalWidget::apply_size_increments_to_window(GWindow& window) { window.set_size_increment({ font().glyph_width('x'), m_line_height }); - window.set_base_size({ frame_thickness() * 2 + m_inset * 2, frame_thickness() * 2 + m_inset * 2 }); + window.set_base_size(compute_base_size()); } void TerminalWidget::update_cursor() @@ -390,6 +432,15 @@ String TerminalWidget::selected_text() const return builder.to_string(); } +void TerminalWidget::terminal_history_changed() +{ + bool was_max = m_scrollbar->value() == m_scrollbar->max(); + m_scrollbar->set_max(m_terminal.history().size()); + if (was_max) + m_scrollbar->set_value(m_scrollbar->max()); + m_scrollbar->update(); +} + void TerminalWidget::terminal_did_resize(u16 columns, u16 rows) { m_pixel_width = (frame_thickness() * 2) + (m_inset * 2) + (columns * font().glyph_width('x')); diff --git a/Applications/Terminal/TerminalWidget.h b/Applications/Terminal/TerminalWidget.h index 2865a2a7d2..d9c048d5bc 100644 --- a/Applications/Terminal/TerminalWidget.h +++ b/Applications/Terminal/TerminalWidget.h @@ -9,6 +9,8 @@ #include #include +class GScrollBar; + class TerminalWidget final : public GFrame , public VT::TerminalClient { C_OBJECT(TerminalWidget) @@ -51,6 +53,7 @@ private: virtual void beep() override; virtual void set_window_title(const StringView&) override; virtual void terminal_did_resize(u16 columns, u16 rows) override; + virtual void terminal_history_changed() override; Rect glyph_rect(u16 row, u16 column); Rect row_rect(u16 row); @@ -58,6 +61,8 @@ private: void update_cursor(); void invalidate_cursor(); + Size compute_base_size() const; + VT::Terminal m_terminal; VT::Position m_selection_start; @@ -88,4 +93,6 @@ private: CTimer m_cursor_blink_timer; CTimer m_visual_beep_timer; RefPtr m_config; + + GScrollBar* m_scrollbar { nullptr }; };