ソースを参照

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.
Daniel Bertalan 4 年 前
コミット
99033876ec

+ 30 - 47
Kernel/TTY/VirtualConsole.cpp

@@ -17,6 +17,7 @@
 #include <Kernel/IO.h>
 #include <Kernel/StdLib.h>
 #include <Kernel/TTY/ConsoleManagement.h>
+#include <LibVT/Color.h>
 
 namespace Kernel {
 
@@ -205,73 +206,55 @@ 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:
+    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 ANSIColor::DarkGray:
-        return Graphics::Console::Color::DarkGray;
-    case ANSIColor::BrightRed:
+    case VT::Color::ANSIColor::BrightRed:
         return Graphics::Console::Color::BrightRed;
-    case ANSIColor::BrightGreen:
+    case VT::Color::ANSIColor::BrightGreen:
         return Graphics::Console::Color::BrightGreen;
-    case ANSIColor::Yellow:
+    case VT::Color::ANSIColor::BrightYellow:
         return Graphics::Console::Color::Yellow;
-    case ANSIColor::BrightBlue:
+    case VT::Color::ANSIColor::BrightBlue:
         return Graphics::Console::Color::BrightBlue;
-    case ANSIColor::BrightMagenta:
+    case VT::Color::ANSIColor::BrightMagenta:
         return Graphics::Console::Color::BrightMagenta;
-    case ANSIColor::BrightCyan:
+    case VT::Color::ANSIColor::BrightCyan:
         return Graphics::Console::Color::BrightCyan;
-    case ANSIColor::White:
-        return Graphics::Console::Color::White;
     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;
     }
-    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;

+ 1 - 0
Kernel/TTY/VirtualConsole.h

@@ -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>
 

+ 7 - 6
Userland/Libraries/LibVT/Attribute.h

@@ -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;

+ 141 - 0
Userland/Libraries/LibVT/Color.h

@@ -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;
+    }
+};
+}

+ 13 - 8
Userland/Libraries/LibVT/Terminal.cpp

@@ -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

+ 31 - 13
Userland/Libraries/LibVT/TerminalWidget.cpp

@@ -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);

+ 3 - 0
Userland/Libraries/LibVT/TerminalWidget.h

@@ -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: