1
0
Fork 0
forked from entailz/toes

Merge pull request 'add gradient tab rendering mode' (#1) from atagen/toes:gradient-tabs into master

Reviewed-on: entailz/toes#1
This commit is contained in:
atagen 2026-05-17 23:35:31 -04:00
commit 3a4814e1fa
4 changed files with 124 additions and 13 deletions

View file

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

View file

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

View file

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

132
render.c
View file

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