feat: add settings panel to Terminal

This commit is contained in:
2026-08-29 18:24:07 +02:00
parent e7646bbbdb
commit 615ca7308a
6 changed files with 845 additions and 72 deletions
+98 -39
View File
@@ -60,32 +60,47 @@ struct TerminalState {
int csi_current_param;
};
// ==== Palette ====
//
// The palette is a mutable global rather than a set of constants because the
// terminal lets the user switch themes at runtime. Cells store resolved colors,
// so a theme switch also has to rewrite the cells that were painted with the
// outgoing palette -- see terminal_remap_palette below.
struct TermPalette {
Color bg;
Color fg;
Color cursor;
Color ansi[16];
};
inline constexpr TermPalette TERM_PALETTE_DEFAULT = {
colors::TERM_BG,
colors::TERM_FG,
colors::TERM_FG,
{
Color::from_hex(0x000000), Color::from_hex(0xCC0000),
Color::from_hex(0x4E9A06), Color::from_hex(0xC4A000),
Color::from_hex(0x3465A4), Color::from_hex(0x75507B),
Color::from_hex(0x06989A), Color::from_hex(0xD3D7CF),
Color::from_hex(0x555753), Color::from_hex(0xEF2929),
Color::from_hex(0x8AE234), Color::from_hex(0xFCE94F),
Color::from_hex(0x729FCF), Color::from_hex(0xAD7FA8),
Color::from_hex(0x34E2E2), Color::from_hex(0xEEEEEC),
}
};
inline TermPalette g_term_palette = TERM_PALETTE_DEFAULT;
// Standard ANSI color palette as ARGB pixels
static inline Color term_ansi_color(int idx) {
switch (idx) {
case 0: return Color::from_hex(0x000000);
case 1: return Color::from_hex(0xCC0000);
case 2: return Color::from_hex(0x4E9A06);
case 3: return Color::from_hex(0xC4A000);
case 4: return Color::from_hex(0x3465A4);
case 5: return Color::from_hex(0x75507B);
case 6: return Color::from_hex(0x06989A);
case 7: return Color::from_hex(0xD3D7CF);
case 8: return Color::from_hex(0x555753);
case 9: return Color::from_hex(0xEF2929);
case 10: return Color::from_hex(0x8AE234);
case 11: return Color::from_hex(0xFCE94F);
case 12: return Color::from_hex(0x729FCF);
case 13: return Color::from_hex(0xAD7FA8);
case 14: return Color::from_hex(0x34E2E2);
case 15: return Color::from_hex(0xEEEEEC);
default: return colors::TERM_FG;
}
if (idx < 0 || idx > 15) return g_term_palette.fg;
return g_term_palette.ansi[idx];
}
// ANSI 256-color palette (0-15 = standard, 16-231 = RGB cube, 232-255 = grayscale)
static inline Color term_ansi_256_color(int idx) {
if (idx < 0) return colors::TERM_FG;
if (idx < 0) return g_term_palette.fg;
if (idx <= 15) return term_ansi_color(idx);
if (idx <= 231) {
// 6x6x6 RGB cube: idx 16 = (0,0,0), idx 231 = (5,5,5)
@@ -119,6 +134,47 @@ static inline void terminal_invalidate_render_cache(TerminalState* t) {
t->render_cursor_visible = false;
}
// Translate one already-resolved cell color from an outgoing palette to the
// incoming one. Colors that are not palette entries -- 256-color cube and
// grayscale ramp values -- are left exactly as the program asked for them.
static inline Color term_remap_color(Color c, const TermPalette& from,
const TermPalette& to) {
if (term_color_equal(c, from.bg)) return to.bg;
if (term_color_equal(c, from.fg)) return to.fg;
for (int i = 0; i < 16; i++) {
if (term_color_equal(c, from.ansi[i])) return to.ansi[i];
}
return c;
}
// Repaint an existing terminal in a new palette. Cells carry resolved colors,
// so switching themes without this leaves all scrollback -- and the shell's
// colored prompt -- in the old theme's colors.
static inline void terminal_remap_palette(TerminalState* t,
const TermPalette& from,
const TermPalette& to) {
if (!t || !t->cells) return;
int total = (t->rows + t->max_scrollback) * t->cols;
for (int i = 0; i < total; i++) {
t->cells[i].fg = term_remap_color(t->cells[i].fg, from, to);
t->cells[i].bg = term_remap_color(t->cells[i].bg, from, to);
}
if (t->alt_cells) {
int screen = t->rows * t->cols;
for (int i = 0; i < screen; i++) {
t->alt_cells[i].fg = term_remap_color(t->alt_cells[i].fg, from, to);
t->alt_cells[i].bg = term_remap_color(t->alt_cells[i].bg, from, to);
}
}
t->current_fg = term_remap_color(t->current_fg, from, to);
t->current_bg = term_remap_color(t->current_bg, from, to);
terminal_invalidate_render_cache(t);
t->dirty = true;
}
static inline Color terminal_erase_bg(TerminalState* t) {
return t->current_bg;
}
@@ -163,8 +219,8 @@ static inline void terminal_init_cells(TerminalState* t, int cols, int rows, int
t->scrollback_lines = 0;
t->max_scrollback = max_sb;
t->view_offset = 0;
t->current_fg = colors::TERM_FG;
t->current_bg = colors::TERM_BG;
t->current_fg = g_term_palette.fg;
t->current_bg = g_term_palette.bg;
t->cursor_visible = false;
t->alt_screen_active = false;
t->reverse_video = false;
@@ -202,10 +258,10 @@ static inline void terminal_init_cells(TerminalState* t, int cols, int rows, int
return;
}
for (int i = 0; i < total_cells; i++) {
t->cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
t->cells[i] = {' ', g_term_palette.fg, g_term_palette.bg};
}
for (int i = 0; i < screen_cells; i++) {
t->alt_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
t->alt_cells[i] = {' ', g_term_palette.fg, g_term_palette.bg};
}
}
@@ -258,7 +314,7 @@ static inline void terminal_enter_alt_screen(TerminalState* t) {
TermCell* screen = term_screen_row(t, 0);
for (int i = 0; i < total; i++) {
t->alt_cells[i] = screen[i];
screen[i] = {' ', colors::TERM_FG, colors::TERM_BG};
screen[i] = {' ', g_term_palette.fg, g_term_palette.bg};
}
t->view_offset = 0;
t->cursor_x = 0;
@@ -437,8 +493,8 @@ static inline void terminal_process_csi(TerminalState* t, char cmd) {
}
if (code == 0) {
t->current_fg = colors::TERM_FG;
t->current_bg = colors::TERM_BG;
t->current_fg = g_term_palette.fg;
t->current_bg = g_term_palette.bg;
t->reverse_video = false;
} else if (code == 1) {
// Bold: map to bright version of current color
@@ -486,15 +542,15 @@ static inline void terminal_process_csi(TerminalState* t, char cmd) {
} else if (code >= 100 && code <= 107) {
t->current_bg = term_ansi_color(code - 100 + 8);
} else if (code == 39) {
t->current_fg = colors::TERM_FG;
t->current_fg = g_term_palette.fg;
} else if (code == 49) {
t->current_bg = colors::TERM_BG;
t->current_bg = g_term_palette.bg;
}
}
if (t->csi_param_count == 0) {
// ESC[m with no params = reset
t->current_fg = colors::TERM_FG;
t->current_bg = colors::TERM_BG;
t->current_fg = g_term_palette.fg;
t->current_bg = g_term_palette.bg;
t->reverse_video = false;
}
break;
@@ -545,8 +601,8 @@ static inline void terminal_feed(TerminalState* t, const char* data, int len) {
for (int j = 0; j < 8; j++) t->csi_params[j] = 0;
} else if (ch == 'c') {
// Reset terminal
t->current_fg = colors::TERM_FG;
t->current_bg = colors::TERM_BG;
t->current_fg = g_term_palette.fg;
t->current_bg = g_term_palette.bg;
t->cursor_x = 0;
t->cursor_y = 0;
t->parse_state = TerminalState::STATE_NORMAL;
@@ -640,18 +696,21 @@ static inline void terminal_draw_cursor_pixels(const TermCell& cell,
uint32_t* pixels, int pw, int ph,
int px, int py, int cell_w, int cell_h,
bool use_ttf, GlyphCache* gc) {
// The block cursor takes its color from the palette and punches the glyph
// out in the background color, so it stays legible on light themes too --
// a hardcoded white block disappears on a white background.
terminal_fill_pixel_rect(pixels, pw, ph, px, py, cell_w, cell_h,
colors::WHITE.to_pixel());
g_term_palette.cursor.to_pixel());
if (cell.ch <= 32 && cell.ch >= 0) return;
if (use_ttf) {
int baseline = py + gc->ascent;
fonts::mono->draw_char_to_buffer(pixels, pw, ph,
px, baseline, (unsigned char)cell.ch, colors::BLACK, gc);
px, baseline, (unsigned char)cell.ch, g_term_palette.bg, gc);
} else {
terminal_draw_bitmap_char(pixels, pw, ph, px, py, (unsigned char)cell.ch,
colors::BLACK.to_pixel());
g_term_palette.bg.to_pixel());
}
}
@@ -713,7 +772,7 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
t->render_base_row != base_row ||
t->render_term_cols != t->cols || t->render_term_rows != t->rows;
uint32_t bg_px = colors::TERM_BG.to_pixel();
uint32_t bg_px = g_term_palette.bg.to_pixel();
if (full_redraw) {
int row_bytes = pw * sizeof(uint32_t);
for (int i = 0; i < pw; i++) pixels[i] = bg_px;
@@ -792,9 +851,9 @@ static inline void terminal_resize(TerminalState* t, int new_cols, int new_rows)
// Clear new buffers
for (int i = 0; i < new_total; i++)
new_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
new_cells[i] = {' ', g_term_palette.fg, g_term_palette.bg};
for (int i = 0; i < new_rows * new_cols; i++)
new_alt[i] = {' ', colors::TERM_FG, colors::TERM_BG};
new_alt[i] = {' ', g_term_palette.fg, g_term_palette.bg};
// Copy content: scrollback + visible screen
int old_content = t->scrollback_lines + t->rows;