feat: add settings panel to Terminal
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user