1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 17:28:11 +00:00

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.
This commit is contained in:
Andreas Kling 2019-08-19 19:12:34 +02:00
parent 462336ed49
commit 64f16c141d
2 changed files with 67 additions and 9 deletions

View file

@ -8,6 +8,7 @@
#include <LibGUI/GApplication.h>
#include <LibGUI/GClipboard.h>
#include <LibGUI/GPainter.h>
#include <LibGUI/GScrollBar.h>
#include <LibGUI/GWindow.h>
#include <errno.h>
#include <stdio.h>
@ -28,6 +29,12 @@ TerminalWidget::TerminalWidget(int ptm_fd, RefPtr<CConfigFile> 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'));

View file

@ -9,6 +9,8 @@
#include <LibGUI/GFrame.h>
#include <LibVT/Terminal.h>
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<CConfigFile> m_config;
GScrollBar* m_scrollbar { nullptr };
};