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:
parent
462336ed49
commit
64f16c141d
2 changed files with 67 additions and 9 deletions
|
@ -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'));
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue