1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 02:07:34 +00:00

LibVT+Kernel: Create Color class

Previously, we converted colors to their RGB values immediately when
they were set. This meant that their semantic meaning was lost, we could
not tell a precise RGB value apart from a named/indexed color.

The new way of storing colors will allow us to retain this information,
so we can change a color scheme on the fly, and previously emitted text
will also be affected.
This commit is contained in:
Daniel Bertalan 2021-05-27 19:19:30 +02:00 committed by Linus Groh
parent 054c742d17
commit 99033876ec
7 changed files with 232 additions and 80 deletions

View file

@ -17,6 +17,7 @@
#include <Kernel/IO.h>
#include <Kernel/StdLib.h>
#include <Kernel/TTY/ConsoleManagement.h>
#include <LibVT/Color.h>
namespace Kernel {
@ -205,74 +206,56 @@ UNMAP_AFTER_INIT VirtualConsole::~VirtualConsole()
VERIFY_NOT_REACHED();
}
enum class ANSIColor : u8 {
Black = 0,
Red,
Green,
Brown,
Blue,
Magenta,
Cyan,
LightGray,
DarkGray,
BrightRed,
BrightGreen,
Yellow,
BrightBlue,
BrightMagenta,
BrightCyan,
White,
__Count,
};
static inline Graphics::Console::Color ansi_color_to_standard_vga_color(ANSIColor color)
static inline Graphics::Console::Color ansi_color_to_standard_vga_color(VT::Color::ANSIColor color)
{
switch (color) {
case ANSIColor::Black:
case VT::Color::ANSIColor::DefaultBackground:
case VT::Color::ANSIColor::Black:
return Graphics::Console::Color::Black;
case ANSIColor::Red:
case VT::Color::ANSIColor::Red:
return Graphics::Console::Color::Red;
case ANSIColor::Brown:
case VT::Color::ANSIColor::Green:
return Graphics::Console::Green;
case VT::Color::ANSIColor::Yellow:
// VGA only has bright yellow, and treats normal yellow as a brownish orange color.
return Graphics::Console::Color::Brown;
case ANSIColor::Blue:
case VT::Color::ANSIColor::Blue:
return Graphics::Console::Color::Blue;
case ANSIColor::Magenta:
case VT::Color::ANSIColor::Magenta:
return Graphics::Console::Color::Magenta;
case ANSIColor::Green:
return Graphics::Console::Color::Green;
case ANSIColor::Cyan:
case VT::Color::ANSIColor::Cyan:
return Graphics::Console::Color::Cyan;
case ANSIColor::LightGray:
return Graphics::Console::Color::LightGray;
case ANSIColor::DarkGray:
return Graphics::Console::Color::DarkGray;
case ANSIColor::BrightRed:
return Graphics::Console::Color::BrightRed;
case ANSIColor::BrightGreen:
return Graphics::Console::Color::BrightGreen;
case ANSIColor::Yellow:
return Graphics::Console::Color::Yellow;
case ANSIColor::BrightBlue:
return Graphics::Console::Color::BrightBlue;
case ANSIColor::BrightMagenta:
return Graphics::Console::Color::BrightMagenta;
case ANSIColor::BrightCyan:
return Graphics::Console::Color::BrightCyan;
case ANSIColor::White:
case VT::Color::ANSIColor::DefaultForeground:
case VT::Color::ANSIColor::White:
return Graphics::Console::Color::White;
case VT::Color::ANSIColor::BrightBlack:
return Graphics::Console::Color::LightGray;
case VT::Color::ANSIColor::BrightRed:
return Graphics::Console::Color::BrightRed;
case VT::Color::ANSIColor::BrightGreen:
return Graphics::Console::Color::BrightGreen;
case VT::Color::ANSIColor::BrightYellow:
return Graphics::Console::Color::Yellow;
case VT::Color::ANSIColor::BrightBlue:
return Graphics::Console::Color::BrightBlue;
case VT::Color::ANSIColor::BrightMagenta:
return Graphics::Console::Color::BrightMagenta;
case VT::Color::ANSIColor::BrightCyan:
return Graphics::Console::Color::BrightCyan;
default:
VERIFY_NOT_REACHED();
}
}
static inline Graphics::Console::Color xterm_to_standard_color(u32 color)
static inline Graphics::Console::Color terminal_to_standard_color(VT::Color color)
{
for (u8 i = 0; i < (u8)ANSIColor::__Count; i++) {
if (xterm_colors[i] == color)
return (Graphics::Console::Color)ansi_color_to_standard_vga_color((ANSIColor)i);
}
switch (color.kind()) {
case VT::Color::Kind::Named:
return ansi_color_to_standard_vga_color(color.as_named());
default:
return Graphics::Console::Color::LightGray;
}
}
void VirtualConsole::on_key_pressed(KeyEvent event)
{
@ -338,13 +321,13 @@ void VirtualConsole::flush_dirty_lines()
for (size_t column = 0; column < columns(); ++column) {
auto& cell = cell_at(column, visual_row);
auto foreground_color = xterm_to_standard_color(cell.attribute.effective_foreground_color());
auto foreground_color = terminal_to_standard_color(cell.attribute.effective_foreground_color());
if (cell.attribute.flags & VT::Attribute::Flags::Bold)
foreground_color = (Graphics::Console::Color)((u8)foreground_color | 0x08);
GraphicsManagement::the().console()->write(column,
visual_row,
((u8)cell.ch < 128 ? cell.ch : '?'),
xterm_to_standard_color(cell.attribute.effective_background_color()),
terminal_to_standard_color(cell.attribute.effective_background_color()),
foreground_color);
}
line.dirty = false;

View file

@ -17,6 +17,7 @@
#include <Kernel/Graphics/Console/Console.h>
#include <Kernel/TTY/TTY.h>
#include <LibVT/Attribute.h>
#include <LibVT/Color.h>
#include <LibVT/Position.h>
#include <LibVT/Terminal.h>

View file

@ -9,6 +9,7 @@
#include <AK/Noncopyable.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibVT/Color.h>
#include <LibVT/XtermColors.h>
namespace VT {
@ -16,8 +17,8 @@ namespace VT {
struct Attribute {
Attribute() { reset(); }
static const u32 default_foreground_color = xterm_colors[7];
static const u32 default_background_color = xterm_colors[0];
static constexpr Color default_foreground_color = Color::named(Color::ANSIColor::DefaultForeground);
static constexpr Color default_background_color = Color::named(Color::ANSIColor::DefaultBackground);
void reset()
{
@ -25,11 +26,11 @@ struct Attribute {
background_color = default_background_color;
flags = Flags::NoAttributes;
}
u32 foreground_color {};
u32 background_color {};
Color foreground_color { default_foreground_color };
Color background_color { default_background_color };
u32 effective_background_color() const { return flags & Negative ? foreground_color : background_color; }
u32 effective_foreground_color() const { return flags & Negative ? background_color : foreground_color; }
Color effective_background_color() const { return flags & Negative ? foreground_color : background_color; }
Color effective_foreground_color() const { return flags & Negative ? background_color : foreground_color; }
#ifndef KERNEL
String href;

View file

@ -0,0 +1,141 @@
/*
* Copyright (c) 2021, Daniel Bertalan <dani@danielbertalan.dev>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace VT {
class Color {
public:
enum class ANSIColor : u16 {
Black = 0,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
// We use the values above to directly index into the color lookup table,
// but the ones below are handled separately.
DefaultForeground = 256,
DefaultBackground
};
static constexpr Color rgb(u32 rgb)
{
return Color(rgb);
}
static constexpr Color indexed(u8 index)
{
return Color(index);
}
static constexpr Color named(ANSIColor name)
{
return Color(name);
}
constexpr bool is_rgb() const
{
return m_kind == Kind::RGB;
}
constexpr bool is_indexed() const
{
return m_kind == Kind::Indexed;
}
constexpr bool is_named() const
{
return m_kind == Kind::Named;
}
constexpr u32 as_rgb() const
{
VERIFY(is_rgb());
return m_value.as_rgb;
}
constexpr u8 as_indexed() const
{
VERIFY(is_indexed());
return m_value.as_indexed;
}
constexpr ANSIColor as_named() const
{
VERIFY(is_named());
return m_value.as_named;
}
constexpr bool operator==(const Color& other) const
{
if (m_kind != other.kind())
return false;
switch (m_kind) {
case RGB:
return m_value.as_rgb == other.as_rgb();
case Indexed:
return m_value.as_indexed == other.as_indexed();
case Named:
return m_value.as_named == other.as_named();
default:
VERIFY_NOT_REACHED();
};
}
enum Kind {
RGB,
Indexed,
Named
};
constexpr Kind kind() const
{
return m_kind;
}
private:
Kind m_kind;
union {
u32 as_rgb;
u8 as_indexed;
ANSIColor as_named;
} m_value;
constexpr Color(u32 rgb)
: m_kind(Kind::RGB)
{
m_value.as_rgb = rgb;
}
constexpr Color(u8 index)
: m_kind(Kind::Indexed)
{
m_value.as_indexed = index;
}
constexpr Color(ANSIColor name)
: m_kind(Kind::Named)
{
m_value.as_named = name;
}
};
}

View file

@ -9,6 +9,7 @@
#include <AK/Debug.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <LibVT/Color.h>
#include <LibVT/Terminal.h>
#ifdef KERNEL
# include <Kernel/TTY/VirtualConsole.h>
@ -182,29 +183,33 @@ void Terminal::SGR(Parameters params)
m_current_state.attribute.reset();
return;
}
auto parse_color = [&]() -> Optional<u32> {
auto parse_color = [&]() -> Optional<Color> {
if (params.size() < 2) {
dbgln("Color code has no type");
return {};
}
u32 color = 0;
u32 rgb = 0;
switch (params[1]) {
case 5: // 8-bit
if (params.size() < 3) {
dbgln("8-bit color code has too few parameters");
return {};
}
return xterm_colors[params[2]];
if (params[2] > 255) {
dbgln("8-bit color code has out-of-bounds value");
return {};
}
return Color::indexed(params[2]);
case 2: // 24-bit
if (params.size() < 5) {
dbgln("24-bit color code has too few parameters");
return {};
}
for (size_t i = 0; i < 3; ++i) {
color <<= 8;
color |= params[i + 2];
rgb <<= 8;
rgb |= params[i + 2];
}
return color;
return Color::rgb(rgb);
default:
dbgln("Unknown color type {}", params[1]);
return {};
@ -264,7 +269,7 @@ void Terminal::SGR(Parameters params)
// Foreground color
if (m_current_state.attribute.flags & Attribute::Bold)
param += 8;
m_current_state.attribute.foreground_color = xterm_colors[param - 30];
m_current_state.attribute.foreground_color = Color::named((Color::ANSIColor)(param - 30));
break;
case 39:
// reset foreground
@ -281,7 +286,7 @@ void Terminal::SGR(Parameters params)
// Background color
if (m_current_state.attribute.flags & Attribute::Bold)
param += 8;
m_current_state.attribute.background_color = xterm_colors[param - 40];
m_current_state.attribute.background_color = Color::named((Color::ANSIColor)(param - 40));
break;
case 49:
// reset background

View file

@ -153,11 +153,6 @@ TerminalWidget::~TerminalWidget()
{
}
static inline Color color_from_rgb(unsigned color)
{
return Color::from_rgb(color);
}
Gfx::IntRect TerminalWidget::glyph_rect(u16 row, u16 column)
{
int y = row * m_line_height;
@ -275,9 +270,9 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
painter.add_clip_rect(terminal_buffer_rect);
if (visual_beep_active)
painter.clear_rect(frame_inner_rect(), Color::Red);
painter.clear_rect(frame_inner_rect(), terminal_color_to_rgb(VT::Color::named(VT::Color::ANSIColor::Red)));
else
painter.clear_rect(frame_inner_rect(), Color(Color::Black).with_alpha(m_opacity));
painter.clear_rect(frame_inner_rect(), terminal_color_to_rgb(VT::Color::named(VT::Color::ANSIColor::Black)).with_alpha(m_opacity));
invalidate_cursor();
int rows_from_history = 0;
@ -320,9 +315,9 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
auto& line = m_terminal.line(first_row_from_history + visual_row);
bool has_only_one_background_color = line.has_only_one_background_color();
if (visual_beep_active)
painter.clear_rect(row_rect, Color::Red);
painter.clear_rect(row_rect, terminal_color_to_rgb(VT::Color::named(VT::Color::ANSIColor::Red)));
else if (has_only_one_background_color)
painter.clear_rect(row_rect, color_from_rgb(line.attribute_at(0).effective_background_color()).with_alpha(m_opacity));
painter.clear_rect(row_rect, terminal_color_to_rgb(line.attribute_at(0).effective_background_color()).with_alpha(m_opacity));
for (size_t column = 0; column < line.length(); ++column) {
bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
@ -334,9 +329,9 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
auto attribute = line.attribute_at(column);
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.effective_background_color() : attribute.effective_foreground_color());
auto text_color = terminal_color_to_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.effective_background_color() : attribute.effective_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.effective_foreground_color() : attribute.effective_background_color()));
painter.clear_rect(cell_rect, terminal_color_to_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.effective_foreground_color() : attribute.effective_background_color()));
enum class UnderlineStyle {
None,
@ -398,7 +393,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
&& 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.effective_background_color() : attribute.effective_foreground_color());
auto text_color = terminal_color_to_rgb(should_reverse_fill_for_cursor_or_selection ? attribute.effective_background_color() : attribute.effective_foreground_color());
u32 code_point = line.code_point(column);
if (code_point == ' ')
@ -427,7 +422,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
if (m_has_logical_focus && (m_cursor_style == VT::CursorStyle::BlinkingBlock || m_cursor_style == VT::CursorStyle::SteadyBlock))
return; // This has already been handled by inverting the cell colors
auto cursor_color = color_from_rgb(cursor_line.attribute_at(m_terminal.cursor_column()).effective_foreground_color());
auto cursor_color = terminal_color_to_rgb(cursor_line.attribute_at(m_terminal.cursor_column()).effective_foreground_color());
auto cell_rect = glyph_rect(row_with_cursor, m_terminal.cursor_column()).inflated(0, m_line_spacing);
if (m_cursor_style == VT::CursorStyle::BlinkingUnderline || m_cursor_style == VT::CursorStyle::SteadyUnderline) {
auto x1 = cell_rect.bottom_left().x();
@ -1148,6 +1143,29 @@ Gfx::IntSize TerminalWidget::widget_size_for_font(const Gfx::Font& font) const
};
}
Gfx::Color TerminalWidget::terminal_color_to_rgb(VT::Color color)
{
switch (color.kind()) {
case VT::Color::Kind::RGB:
return Gfx::Color::from_rgb(color.as_rgb());
case VT::Color::Kind::Indexed:
return Gfx::Color::from_rgb(xterm_colors[color.as_indexed()]);
case VT::Color::Kind::Named: {
auto ansi = color.as_named();
if ((u16)ansi < 256)
return Gfx::Color::from_rgb(xterm_colors[(u16)ansi]);
else if (ansi == VT::Color::ANSIColor::DefaultForeground)
return Gfx::Color::from_rgb(xterm_colors[7]);
else if (ansi == VT::Color::ANSIColor::DefaultBackground)
return Gfx::Color::from_rgb(xterm_colors[0]);
else
VERIFY_NOT_REACHED();
}
default:
VERIFY_NOT_REACHED();
}
};
void TerminalWidget::set_font_and_resize_to_fit(const Gfx::Font& font)
{
set_font(font);

View file

@ -14,6 +14,7 @@
#include <LibGUI/Frame.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Rect.h>
#include <LibVT/Color.h>
#include <LibVT/Range.h>
#include <LibVT/Terminal.h>
@ -86,6 +87,8 @@ public:
GUI::Menu& context_menu() { return *m_context_menu; }
Gfx::Color terminal_color_to_rgb(VT::Color);
void set_font_and_resize_to_fit(const Gfx::Font&);
private: