From e6f8622723181ef0da854c940b7157433e0855c3 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Sat, 21 Mar 2026 09:53:02 -0700 Subject: [PATCH] initial edge bleed impl --- config.c | 7 ++ config.h | 1 + doc/foot.ini.5.scd | 11 ++ render.c | 230 +++++++++++++++++++++++++++++++++++++++++- subprojects/.wraplock | 0 tests/test-config.c | 3 + 6 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 subprojects/.wraplock diff --git a/config.c b/config.c index 12c594bc..edd6a241 100644 --- a/config.c +++ b/config.c @@ -2826,6 +2826,12 @@ parse_section_tweak(struct context *ctx) else if (streq(key, "overflowing-glyphs")) return value_to_bool(ctx, &conf->tweak.overflowing_glyphs); + else if (streq(key, "edge-bg-bleed")) { + bool ret = value_to_bool(ctx, &conf->tweak.edge_bg_bleed); + LOG_WARN("edge-bg-bleed: %s", conf->tweak.edge_bg_bleed ? "yes" : "no"); + return ret; + } + else if (streq(key, "damage-whole-window")) return value_to_bool(ctx, &conf->tweak.damage_whole_window); @@ -3599,6 +3605,7 @@ config_load(struct config *conf, const char *conf_path, .tweak = { .fcft_filter = FCFT_SCALING_FILTER_LANCZOS3, .overflowing_glyphs = true, + .edge_bg_bleed = false, #if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING .grapheme_shaping = fcft_caps & FCFT_CAPABILITY_GRAPHEME_SHAPING, #endif diff --git a/config.h b/config.h index a3522f44..6654dabb 100644 --- a/config.h +++ b/config.h @@ -427,6 +427,7 @@ struct config { struct { enum fcft_scaling_filter fcft_filter; bool overflowing_glyphs; + bool edge_bg_bleed; bool grapheme_shaping; enum { GRAPHEME_WIDTH_WCSWIDTH, diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index a1ee326f..006270c4 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1859,6 +1859,17 @@ any of these options. Default: _yes_. +*edge-bg-bleed* + Boolean. When enabled, the background color of edge cells (cells at + the borders of the grid) is extended into the padding/margin area, + instead of using the default background color. + + This makes colored backgrounds (e.g. status bars, syntax + highlighting) extend to the window edge rather than stopping at the + grid boundary with a visible gap. + + Default: _no_. + *render-timer* Enables a frame rendering timer, that prints the time it takes to render each frame, in microseconds, either on-screen, to stderr, diff --git a/render.c b/render.c index c47133b3..fe6022b1 100644 --- a/render.c +++ b/render.c @@ -682,6 +682,76 @@ draw_cursor(const struct terminal *term, const struct cell *cell, } } +static void +cell_effective_bg(const struct terminal *term, const struct cell *cell, + uint32_t *out_bg, uint16_t *out_alpha) +{ + uint32_t _fg; + uint32_t _bg; + uint16_t alpha = 0xffff; + + switch (cell->attrs.fg_src) { + case COLOR_RGB: _fg = cell->attrs.fg; break; + case COLOR_BASE16: + case COLOR_BASE256: _fg = term->colors.table[cell->attrs.fg]; break; + case COLOR_DEFAULT: _fg = term->reverse ? term->colors.bg : term->colors.fg; break; + } + + switch (cell->attrs.bg_src) { + case COLOR_RGB: _bg = cell->attrs.bg; break; + case COLOR_BASE16: + case COLOR_BASE256: _bg = term->colors.table[cell->attrs.bg]; break; + case COLOR_DEFAULT: _bg = term->reverse ? term->colors.fg : term->colors.bg; break; + } + + if (unlikely(cell->attrs.selected)) { + const uint32_t cell_fg = _fg; + const uint32_t cell_bg = _bg; + + const bool custom_fg = term->colors.selection_fg >> 24 == 0; + const bool custom_bg = term->colors.selection_bg >> 24 == 0; + const bool custom_both = custom_fg && custom_bg; + + if (custom_both) { + _bg = term->colors.selection_bg; + } else if (custom_bg) { + _bg = term->colors.selection_bg; + } else if (custom_fg) { + _bg = cell->attrs.reverse ? cell_fg : cell_bg; + } else { + _bg = cell_fg; + } + } else { + if (unlikely(cell->attrs.reverse)) { + _bg = _fg; + } else if (!term->window->is_fullscreen && term->colors.alpha != 0xffff) { + switch (term->conf->colors_dark.alpha_mode) { + case ALPHA_MODE_DEFAULT: + if (cell->attrs.bg_src == COLOR_DEFAULT) + alpha = term->colors.alpha; + break; + case ALPHA_MODE_MATCHING: + if (cell->attrs.bg_src == COLOR_DEFAULT || + ((cell->attrs.bg_src == COLOR_BASE16 || + cell->attrs.bg_src == COLOR_BASE256) && + term->colors.table[cell->attrs.bg] == term->colors.bg) || + (cell->attrs.bg_src == COLOR_RGB && + cell->attrs.bg == term->colors.bg)) + { + alpha = term->colors.alpha; + } + break; + case ALPHA_MODE_ALL: + alpha = term->colors.alpha; + break; + } + } + } + + *out_bg = _bg; + *out_alpha = alpha; +} + static int render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damage, struct row *row, int row_no, int col, @@ -1302,6 +1372,149 @@ render_margin(struct terminal *term, struct buffer *buf, } } +static void +render_margin_bleed(struct terminal *term, struct buffer *buf, + int start_line, int end_line, bool apply_damage) +{ + LOG_WARN("render_margin_bleed: start_line=%d, end_line=%d, apply_damage=%d", + start_line, end_line, apply_damage); + + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); + struct grid *grid = term->grid; + const int width = term->cell_width; + const int height = term->cell_height; + const int rmargin = term->width - term->margins.right; + const int bmargin = term->height - term->margins.bottom; + const int line_count = end_line - start_line; + + /* Left and right margin strips (per row) */ + for (int r = start_line; r < end_line; r++) { + struct row *row = grid_row_in_view(grid, r); + int y = term->margins.top + r * height; + + if (term->margins.left > 0) { + const struct cell *cell = &row->cells[0]; + uint32_t bg; uint16_t alpha; + cell_effective_bg(term, cell, &bg, &alpha); + pixman_color_t c = color_hex_to_pixman_with_alpha(bg, alpha, gamma_correct); + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &c, 1, + &(pixman_rectangle16_t){0, y, term->margins.left, height}); + } + + if (term->margins.right > 0) { + const struct cell *cell = &row->cells[term->cols - 1]; + uint32_t bg; uint16_t alpha; + cell_effective_bg(term, cell, &bg, &alpha); + pixman_color_t c = color_hex_to_pixman_with_alpha(bg, alpha, gamma_correct); + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &c, 1, + &(pixman_rectangle16_t){rmargin, y, term->margins.right, height}); + } + } + + /* Top margin (per column + corners) */ + if (term->margins.top > 0) { + struct row *row0 = grid_row_in_view(grid, 0); + + /* Top-left corner */ + if (term->margins.left > 0) { + uint32_t bg; uint16_t alpha; + cell_effective_bg(term, &row0->cells[0], &bg, &alpha); + pixman_color_t c = color_hex_to_pixman_with_alpha(bg, alpha, gamma_correct); + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &c, 1, + &(pixman_rectangle16_t){0, 0, term->margins.left, term->margins.top}); + } + + for (int col = 0; col < term->cols; col++) { + const struct cell *cell = &row0->cells[col]; + uint32_t bg; uint16_t alpha; + cell_effective_bg(term, cell, &bg, &alpha); + pixman_color_t c = color_hex_to_pixman_with_alpha(bg, alpha, gamma_correct); + int x = term->margins.left + col * width; + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &c, 1, + &(pixman_rectangle16_t){x, 0, width, term->margins.top}); + } + + /* Top-right corner */ + if (term->margins.right > 0) { + uint32_t bg; uint16_t alpha; + cell_effective_bg(term, &row0->cells[term->cols - 1], &bg, &alpha); + pixman_color_t c = color_hex_to_pixman_with_alpha(bg, alpha, gamma_correct); + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &c, 1, + &(pixman_rectangle16_t){rmargin, 0, term->margins.right, term->margins.top}); + } + } + + /* Bottom margin (per column + corners) */ + if (term->margins.bottom > 0) { + struct row *rowN = grid_row_in_view(grid, term->rows - 1); + + /* Bottom-left corner */ + if (term->margins.left > 0) { + uint32_t bg; uint16_t alpha; + cell_effective_bg(term, &rowN->cells[0], &bg, &alpha); + pixman_color_t c = color_hex_to_pixman_with_alpha(bg, alpha, gamma_correct); + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &c, 1, + &(pixman_rectangle16_t){0, bmargin, term->margins.left, term->margins.bottom}); + } + + for (int col = 0; col < term->cols; col++) { + const struct cell *cell = &rowN->cells[col]; + uint32_t bg; uint16_t alpha; + cell_effective_bg(term, cell, &bg, &alpha); + pixman_color_t c = color_hex_to_pixman_with_alpha(bg, alpha, gamma_correct); + int x = term->margins.left + col * width; + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &c, 1, + &(pixman_rectangle16_t){x, bmargin, width, term->margins.bottom}); + } + + /* Bottom-right corner */ + if (term->margins.right > 0) { + uint32_t bg; uint16_t alpha; + cell_effective_bg(term, &rowN->cells[term->cols - 1], &bg, &alpha); + pixman_color_t c = color_hex_to_pixman_with_alpha(bg, alpha, gamma_correct); + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix[0], &c, 1, + &(pixman_rectangle16_t){rmargin, bmargin, term->margins.right, term->margins.bottom}); + } + } + + if (term->render.urgency) + render_urgency(term, buf); + + /* Dirty tracking */ + pixman_region32_union_rect( + &buf->dirty[0], &buf->dirty[0], 0, 0, term->width, term->margins.top); + pixman_region32_union_rect( + &buf->dirty[0], &buf->dirty[0], 0, bmargin, term->width, term->margins.bottom); + pixman_region32_union_rect( + &buf->dirty[0], &buf->dirty[0], 0, 0, term->margins.left, term->height); + pixman_region32_union_rect( + &buf->dirty[0], &buf->dirty[0], + rmargin, 0, term->margins.right, term->height); + + if (apply_damage) { + wl_surface_damage_buffer( + term->window->surface.surf, 0, 0, term->width, term->margins.top); + wl_surface_damage_buffer( + term->window->surface.surf, 0, bmargin, term->width, term->margins.bottom); + wl_surface_damage_buffer( + term->window->surface.surf, + 0, term->margins.top + start_line * term->cell_height, + term->margins.left, line_count * term->cell_height); + wl_surface_damage_buffer( + term->window->surface.surf, + rmargin, term->margins.top + start_line * term->cell_height, + term->margins.right, line_count * term->cell_height); + } +} + static void grid_render_scroll(struct terminal *term, struct buffer *buf, const struct damage *dmg) @@ -1381,8 +1594,10 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, if (did_shm_scroll) { /* Restore margins */ - render_margin( - term, buf, dmg->region.end - dmg->lines, term->rows, false); + if (term->conf->tweak.edge_bg_bleed) + render_margin_bleed(term, buf, dmg->region.end - dmg->lines, term->rows, false); + else + render_margin(term, buf, dmg->region.end - dmg->lines, term->rows, false); } else { /* Fallback for when we either cannot do SHM scrolling, or it failed */ uint8_t *raw = buf->data; @@ -1458,8 +1673,10 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, if (did_shm_scroll) { /* Restore margins */ - render_margin( - term, buf, dmg->region.start, dmg->region.start + dmg->lines, false); + if (term->conf->tweak.edge_bg_bleed) + render_margin_bleed(term, buf, dmg->region.start, dmg->region.start + dmg->lines, false); + else + render_margin(term, buf, dmg->region.start, dmg->region.start + dmg->lines, false); } else { /* Fallback for when we either cannot do SHM scrolling, or it failed */ uint8_t *raw = buf->data; @@ -3177,7 +3394,10 @@ static void force_full_repaint(struct terminal *term, struct buffer *buf) { tll_free(term->grid->scroll_damage); - render_margin(term, buf, 0, term->rows, true); + if (term->conf->tweak.edge_bg_bleed) + render_margin_bleed(term, buf, 0, term->rows, true); + else + render_margin(term, buf, 0, term->rows, true); term_damage_view(term); } diff --git a/subprojects/.wraplock b/subprojects/.wraplock new file mode 100644 index 00000000..e69de29b diff --git a/tests/test-config.c b/tests/test-config.c index 9774cba9..951283e0 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -1464,6 +1464,9 @@ test_section_tweak(void) test_boolean(&ctx, &parse_section_tweak, "overflowing-glyphs", &conf.tweak.overflowing_glyphs); + test_boolean(&ctx, &parse_section_tweak, "edge-bg-bleed", + &conf.tweak.edge_bg_bleed); + test_enum( &ctx, &parse_section_tweak, "render-timer", 4,