diff --git a/config.c b/config.c index c02f5b7..aaae7f2 100644 --- a/config.c +++ b/config.c @@ -1861,7 +1861,7 @@ parse_section_tabs(struct context *ctx) "enum is not 32-bit"); return value_to_enum( ctx, - (const char *[]){"rounded", "square", NULL}, + (const char *[]){"rounded", "square", "gradient", NULL}, (int *)&conf->tabs.style); } diff --git a/config.h b/config.h index 33ea725..2e9c34c 100644 --- a/config.h +++ b/config.h @@ -475,6 +475,7 @@ struct config { enum { CONF_TABS_STYLE_ROUNDED, CONF_TABS_STYLE_SQUARE, + CONF_TABS_STYLE_GRADIENT, } style; enum { CONF_TABS_LAYOUT_SPAN, diff --git a/foot.ini b/foot.ini index 1722de0..3d0e5d0 100644 --- a/foot.ini +++ b/foot.ini @@ -178,7 +178,7 @@ [tabs] enabled=yes position=bottom -style=rounded +style=rounded # rounded | square | gradient layout=floating height=26 # tab-width=200 (max width per tab in floating mode) diff --git a/render.c b/render.c index c8f1a4e..9e8548a 100644 --- a/render.c +++ b/render.c @@ -5684,6 +5684,98 @@ render_tab_label(pixman_image_t *pix, struct fcft_font *font, } } +/* Greyscale ramp indices for the gradient tab style. + * The 256-color palette greyscale ramp lives at indices 232..255. */ +#define GRADIENT_BAR_BG_IDX 234 +#define GRADIENT_PILL_ACTIVE_IDX 250 +#define GRADIENT_PILL_INACTIVE_IDX 240 +#define GRADIENT_FG_ACTIVE_IDX 232 +#define GRADIENT_FG_INACTIVE_IDX 250 + +/* Hardcoded fade level masks (braille bitmasks); index = fade level (1..6). + * Mirrors the visual progression: ⠐ ⠡ ⡐ ⢔ ⣑ ⣪ */ +static const uint8_t gradient_fade_masks[7] = { + 0x00, 0x10, 0x21, 0x50, 0x94, 0xD1, 0xEA, +}; + +/* Braille bit (0..7) → (col, row) within the 2-col × 4-row dot grid */ +static const struct { uint8_t col, row; } gradient_dot_pos[8] = { + {0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {0, 3}, {1, 3}, +}; + +static void +draw_gradient_pill(pixman_image_t *pix, const struct terminal *term, + uint8_t pill_idx, uint8_t bar_bg_idx, bool gamma_correct, + int x, int y, int w, int h, float scale) +{ + const int n_levels = (int)ALEN(gradient_fade_masks) - 1; /* 6 */ + const int cell_w = max(2, (int)roundf(scale * 4)); + const int fade_w = n_levels * cell_w; + + const pixman_color_t pill = color_hex_to_pixman( + term->colors.table[pill_idx], gamma_correct); + + if (w <= 2 * fade_w) { + /* Tab too narrow for full fades — draw it solid */ + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, &pill, + 1, &(pixman_rectangle16_t){x, y, w, h}); + return; + } + + /* Solid pill interior */ + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, &pill, + 1, &(pixman_rectangle16_t){x + fade_w, y, w - 2 * fade_w, h}); + + const int dot_size = max(1, (int)roundf(scale)); + const float sub_w = (float)cell_w / 2.0f; + const float sub_h = (float)h / 4.0f; + + const int inner = (int)pill_idx - 1; + const int outer = (int)bar_bg_idx; + const int range = inner - outer; + const int t_den = max(1, n_levels - 1); + + for (int side = 0; side < 2; side++) { + const bool left = (side == 0); + for (int i = 1; i <= n_levels; i++) { + /* i == 1 is the outermost cell, i == n_levels the innermost */ + const int cell_x = left + ? x + (i - 1) * cell_w + : x + w - i * cell_w; + + const uint8_t band_idx = (uint8_t)( + outer + (range * (i - 1) + t_den / 2) / t_den); + const uint8_t dot_idx = band_idx > 233 ? band_idx - 2 : 232; + + const pixman_color_t band = color_hex_to_pixman( + term->colors.table[band_idx], gamma_correct); + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, &band, + 1, &(pixman_rectangle16_t){cell_x, y, cell_w, h}); + + const uint8_t mask = gradient_fade_masks[i]; + const pixman_color_t dot = color_hex_to_pixman( + term->colors.table[dot_idx], gamma_correct); + + pixman_rectangle16_t dot_rects[8]; + int n_dots = 0; + for (int b = 0; b < 8; b++) { + if (!(mask & (1u << b))) + continue; + const int dx = (int)roundf((gradient_dot_pos[b].col + 0.5f) * sub_w) + - dot_size / 2; + const int dy = (int)roundf((gradient_dot_pos[b].row + 0.5f) * sub_h) + - dot_size / 2; + dot_rects[n_dots++] = (pixman_rectangle16_t){ + cell_x + dx, y + dy, dot_size, dot_size, + }; + } + if (n_dots > 0) + pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, &dot, + n_dots, dot_rects); + } + } +} + static void render_tab_bar(struct terminal *term) { @@ -5712,11 +5804,15 @@ render_tab_bar(struct terminal *term) const bool gamma_correct = wayl_do_linear_blending(win->term->wl, conf); const bool rounded = (conf->tabs.style == CONF_TABS_STYLE_ROUNDED); + const bool gradient = (conf->tabs.style == CONF_TABS_STYLE_GRADIENT); const int r = rounded ? (int)roundf(scale * conf->tabs.corner_radius) : 0; - /* Clear buffer: transparent for floating (shows terminal behind gaps), bg for span */ + /* Clear buffer: transparent for floating (shows terminal behind gaps), bg for span. + * Gradient style overrides the configured bar bg with a fixed greyscale ramp index. */ const pixman_color_t transparent = {0, 0, 0, 0}; - const pixman_color_t bg_color = color_hex_to_pixman(conf->tabs.colors.bg, gamma_correct); + const pixman_color_t bg_color = gradient + ? color_hex_to_pixman(term->colors.table[GRADIENT_BAR_BG_IDX], gamma_correct) + : color_hex_to_pixman(conf->tabs.colors.bg, gamma_correct); pixman_image_fill_rectangles(PIXMAN_OP_SRC, buf->pix[0], floating ? &transparent : &bg_color, 1, &(pixman_rectangle16_t){0, 0, width, total_h}); @@ -5809,15 +5905,25 @@ render_tab_bar(struct terminal *term) const int x = tab_xs[i]; const int w = tab_ws[i]; - const pixman_color_t tab_bg = color_hex_to_pixman( - is_active ? conf->tabs.colors.active_bg : conf->tabs.colors.bg, - gamma_correct); - const pixman_color_t fg_color = color_hex_to_pixman( - is_active ? conf->tabs.colors.active_fg : conf->tabs.colors.fg, - gamma_correct); + const pixman_color_t fg_color = gradient + ? color_hex_to_pixman( + term->colors.table[is_active + ? GRADIENT_FG_ACTIVE_IDX : GRADIENT_FG_INACTIVE_IDX], + gamma_correct) + : color_hex_to_pixman( + is_active ? conf->tabs.colors.active_fg : conf->tabs.colors.fg, + gamma_correct); - if (rounded) { + if (gradient) { + const uint8_t pill_idx = is_active + ? GRADIENT_PILL_ACTIVE_IDX : GRADIENT_PILL_INACTIVE_IDX; + draw_gradient_pill(buf->pix[0], term, pill_idx, GRADIENT_BAR_BG_IDX, + gamma_correct, x, tab_y, w, tab_h, scale); + } else if (rounded) { /* Floating: all 4 corners rounded. Span: only the open edge rounded. */ + const pixman_color_t tab_bg = color_hex_to_pixman( + is_active ? conf->tabs.colors.active_bg : conf->tabs.colors.bg, + gamma_correct); unsigned corners; if (floating) { corners = 0xf; /* all corners */ @@ -5828,12 +5934,16 @@ render_tab_bar(struct terminal *term) } draw_rounded_rect(buf->pix[0], &tab_bg, x, tab_y, w, tab_h, r, corners); } else { + const pixman_color_t tab_bg = color_hex_to_pixman( + is_active ? conf->tabs.colors.active_bg : conf->tabs.colors.bg, + gamma_correct); pixman_image_fill_rectangles(PIXMAN_OP_SRC, buf->pix[0], &tab_bg, 1, &(pixman_rectangle16_t){x, tab_y, w, tab_h}); } - /* Span: separator between inactive tabs */ - if (!floating && !is_active && i + 1 < n) { + /* Span: separator between inactive tabs (skipped for gradient — fades + * already provide visual separation) */ + if (!floating && !is_active && i + 1 < n && !gradient) { const pixman_color_t sep = color_hex_to_pixman( conf->tabs.colors.bg, gamma_correct); pixman_image_fill_rectangles(PIXMAN_OP_SRC, buf->pix[0], &sep,