initial edge bleed impl

This commit is contained in:
Michael Peters 2026-03-21 09:53:02 -07:00
parent 2fb7bb0ea4
commit e6f8622723
6 changed files with 247 additions and 5 deletions

View file

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

View file

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

View file

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

230
render.c
View file

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

0
subprojects/.wraplock Normal file
View file

View file

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