unify overlay rendering: one shared layout behind host-GL and wlr-pass backends

This commit is contained in:
Andrew Hamilton 2026-08-24 09:11:15 +10:00
parent ff8a5bc23b
commit 7bd5cd7dbd

296
kick.c
View file

@ -1935,7 +1935,35 @@ static bool app_overlay_row(struct kick_app *app) {
return app_live(app) && !app->closing;
}
static void draw_tab_overlay(void) {
/* ---------- Alt overlay rendering (shared layout, two backends) ----------
Nested mode composites the overlay onto the imported scene blit with the
host-GL program; standalone paints it as a second wlr render pass on the
output buffer. The layout is identical, so one body emits through three
primitives and only the leaves differ per path. All overlay text is
greyscale: the pass backend folds the value into its single tint axis
(alpha), the only coloring its premultiplied blend exposes for text. */
struct overlay_backend;
typedef void (*overlay_rect_fn)(const struct overlay_backend *,
int x, int y, int w, int h,
float r, float g, float b, float a);
typedef void (*overlay_text_fn)(const struct overlay_backend *,
const char *s, int x, int y,
float v, float a);
typedef bool (*overlay_icon_fn)(const struct overlay_backend *,
struct kick_tab_group_member *m,
int x, int y, int size, bool active);
struct overlay_backend {
struct wlr_render_pass *pass; /* wlr-pass backend only, else NULL */
overlay_rect_fn rect;
overlay_text_fn text;
/* Draws m's loaded icon dimmed when inactive; returns false when none
is loaded so the shared body can paint the placeholder tile. */
overlay_icon_fn icon;
};
static void draw_tab_overlay_with(const struct overlay_backend *bk) {
if (!host.overlay_visible || server.num_apps < 1) return;
int margin = 8, boxpad = 6;
@ -1986,14 +2014,14 @@ static void draw_tab_overlay(void) {
see-through panel; render_standalone() forces whole-scene repaints
while the overlay shows, so a translucent value stays true instead of
stacking toward opaque on partial repaints.) */
draw_rect(box_x + bw, box_y + bw, box_w - 2 * bw, box_h - 2 * bw,
0.07f, 0.07f, 0.09f, 1.0f);
draw_rect(box_x, box_y, box_w, bw, 0.45f, 0.45f, 0.48f, 1.0f);
draw_rect(box_x, box_y + box_h - bw, box_w, bw,
0.45f, 0.45f, 0.48f, 1.0f);
draw_rect(box_x, box_y, bw, box_h, 0.45f, 0.45f, 0.48f, 1.0f);
draw_rect(box_x + box_w - bw, box_y, bw, box_h,
0.45f, 0.45f, 0.48f, 1.0f);
bk->rect(bk, box_x + bw, box_y + bw, box_w - 2 * bw, box_h - 2 * bw,
0.07f, 0.07f, 0.09f, 1.0f);
bk->rect(bk, box_x, box_y, box_w, bw, 0.45f, 0.45f, 0.48f, 1.0f);
bk->rect(bk, box_x, box_y + box_h - bw, box_w, bw,
0.45f, 0.45f, 0.48f, 1.0f);
bk->rect(bk, box_x, box_y, bw, box_h, 0.45f, 0.45f, 0.48f, 1.0f);
bk->rect(bk, box_x + box_w - bw, box_y, bw, box_h,
0.45f, 0.45f, 0.48f, 1.0f);
int row = 0;
for (int i = 0; i < server.num_apps; i++) {
@ -2044,43 +2072,31 @@ static void draw_tab_overlay(void) {
int ctw = overlay_text_width(ctb);
int cmw = icon_sz + icon_gap + ctw;
int cpen = box_x + boxpad + (row_inner - cmw) / 2;
draw_rect(cpen - 2, ry - 1, cmw + 4, row_h,
0.22f, 0.22f, 0.25f, 1.0f);
draw_text(ctb, cpen + icon_sz + icon_gap,
ry + (row_h - host.overlay_font_h) / 2,
1.0f, 1.0f, 1.0f, 0.9f);
bk->rect(bk, cpen - 2, ry - 1, cmw + 4, row_h,
0.22f, 0.22f, 0.25f, 1.0f);
bk->text(bk, ctb, cpen + icon_sz + icon_gap,
ry + (row_h - host.overlay_font_h) / 2, 1.0f, 0.9f);
}
for (int k = 0; k < n; k++) {
struct kick_tab_group_member *m = members[k];
int tw = widths[k];
int mw = icon_sz + icon_gap + tw;
bool is_cursor = active && app->focus_member == m;
draw_rect(pen - 2, ry - 1, mw + 4, row_h,
0.22f, 0.22f, 0.25f, 1.0f);
bk->rect(bk, pen - 2, ry - 1, mw + 4, row_h,
0.22f, 0.22f, 0.25f, 1.0f);
if (is_cursor)
draw_rect(pen - 2, ry - 1, mw + 4, row_h,
0.24f, 0.36f, 0.60f, 0.9f);
bk->rect(bk, pen - 2, ry - 1, mw + 4, row_h,
0.24f, 0.36f, 0.60f, 0.9f);
int mx = pen, my = iy;
if (m->icon_tex) {
GLfloat tint[4] = { active ? 1.0f : 0.72f,
active ? 1.0f : 0.72f,
active ? 1.0f : 0.72f, 1.0f };
draw_texture_rect(m->icon_tex, m->icon_w, m->icon_h,
0, 0, m->icon_w, m->icon_h,
mx, my, icon_sz, icon_sz,
host.width, host.height, tint);
} else {
draw_rect(mx, my, icon_sz, icon_sz,
0.45f, 0.45f, 0.48f, 0.85f);
draw_rect(mx + 3, my + 3, icon_sz - 6, icon_sz - 6,
0.07f, 0.07f, 0.09f, 1.0f);
if (!bk->icon(bk, m, mx, my, icon_sz, active)) {
bk->rect(bk, mx, my, icon_sz, icon_sz,
0.45f, 0.45f, 0.48f, 0.85f);
bk->rect(bk, mx + 3, my + 3, icon_sz - 6, icon_sz - 6,
0.07f, 0.07f, 0.09f, 1.0f);
}
draw_text(fitt[k], mx + icon_sz + icon_gap,
ry + (row_h - host.overlay_font_h) / 2,
active ? 1.0f : 0.72f,
active ? 1.0f : 0.72f,
active ? 1.0f : 0.72f,
active ? 1.0f : 0.9f);
bk->text(bk, fitt[k], mx + icon_sz + icon_gap,
ry + (row_h - host.overlay_font_h) / 2,
active ? 1.0f : 0.72f, active ? 1.0f : 0.9f);
pen += mw + member_gap;
}
row++;
@ -2152,149 +2168,73 @@ static void overlay_pass_icon_tex(struct wlr_render_pass *pass,
wlr_render_pass_add_texture(pass, &o);
}
static void draw_tab_overlay_pass(struct wlr_render_pass *pass) {
if (!host.overlay_visible || server.num_apps < 1) return;
/* --- host-GL backend (nested): leaves onto the imported scene blit --- */
int margin = 8, boxpad = 6;
int icon_sz = OVERLAY_ICON_SZ, icon_gap = 8, member_gap = 12;
int row_h = host.overlay_font_h > 0 ? host.overlay_font_h + 8 : 18;
int row_gap = 8;
if (row_h < icon_sz + 4) row_h = icon_sz + 4;
int max_row_w = 0, n_rows = 0;
for (int i = 0; i < server.num_apps; i++) {
struct kick_app *app = server.apps[i];
if (!app_overlay_row(app)) continue;
(void)app_row_title(app);
int n = 0, row_w = 0;
struct kick_tab_group_member *m;
wl_list_for_each(m, &app->group_members, link) {
if (!member_mapped(m)) continue;
char tb[64];
const char *t = overlay_member_title(m, tb, sizeof(tb));
row_w += icon_sz + icon_gap + overlay_text_width(t);
n++;
}
if (n > 0) {
if (n > 1) row_w += (n - 1) * member_gap;
} else {
const char *ct = (app->cmd && app->cmd[0]) ? app->cmd : "pending";
row_w = icon_sz + icon_gap + overlay_text_width(ct);
}
if (row_w > max_row_w) max_row_w = row_w;
n_rows++;
}
if (n_rows == 0) return;
int avail_w = host.width - 2 * margin - 2 * boxpad;
if (max_row_w > avail_w) max_row_w = avail_w;
int box_w = max_row_w + 2 * boxpad;
int box_h = n_rows * row_h + (n_rows - 1) * row_gap + 2 * boxpad;
if (box_w > host.width - 2 * margin) box_w = host.width - 2 * margin;
if (box_h > host.height - 2 * margin) box_h = host.height - 2 * margin;
int box_x = (host.width - box_w) / 2;
int box_y = (host.height - box_h) / 2;
int bw = 1;
/* Opaque backdrop filling the box, framed by four rim strokes -- keeps
the titles readable over any content. (Alpha below 1.0 here gives a
see-through panel; render_standalone() forces whole-scene repaints
while the overlay shows, so a translucent value stays true instead of
stacking toward opaque on partial repaints.) */
overlay_pass_rect(pass, box_x + bw, box_y + bw, box_w - 2 * bw,
box_h - 2 * bw, 0.07f, 0.07f, 0.09f, 1.0f);
overlay_pass_rect(pass, box_x, box_y, box_w, bw,
0.45f, 0.45f, 0.48f, 1.0f);
overlay_pass_rect(pass, box_x, box_y + box_h - bw, box_w, bw,
0.45f, 0.45f, 0.48f, 1.0f);
overlay_pass_rect(pass, box_x, box_y, bw, box_h,
0.45f, 0.45f, 0.48f, 1.0f);
overlay_pass_rect(pass, box_x + box_w - bw, box_y, bw, box_h,
0.45f, 0.45f, 0.48f, 1.0f);
int row = 0;
for (int i = 0; i < server.num_apps; i++) {
struct kick_app *app = server.apps[i];
if (!app_overlay_row(app)) continue;
(void)app_row_title(app);
int ry = box_y + boxpad + row * (row_h + row_gap);
bool active = app == server.active_app;
int row_inner = box_w - 2 * boxpad;
struct kick_tab_group_member *members[32];
char fitt[32][256];
int widths[32];
int n = 0;
struct kick_tab_group_member *m;
wl_list_for_each(m, &app->group_members, link) {
if (!member_mapped(m)) continue;
if (n >= 32) break;
if (overlay_member_icon_needs_load(m))
overlay_icon_load_member(m);
members[n++] = m;
}
int share = 0;
if (n > 0) {
int avail = row_inner - n * (icon_sz + icon_gap);
if (n > 1) avail -= (n - 1) * member_gap;
share = avail / n;
if (share < 0) share = 0;
}
int total_w = 0;
for (int k = 0; k < n; k++) {
char tb[64];
const char *dt = overlay_member_title(members[k], tb, sizeof(tb));
fit_text(dt, share, fitt[k], sizeof(fitt[k]));
widths[k] = overlay_text_width(fitt[k]);
total_w += icon_sz + icon_gap + widths[k];
}
if (n > 1) total_w += (n - 1) * member_gap;
int pen = box_x + boxpad + (row_inner - total_w) / 2;
int iy = ry + (row_h - icon_sz) / 2;
if (n == 0) {
const char *ct = (app->cmd && app->cmd[0]) ? app->cmd : "pending";
char ctb[64];
int cavail = row_inner - (icon_sz + icon_gap) - 4;
if (cavail < 0) cavail = 0;
fit_text(ct, cavail, ctb, sizeof(ctb));
int ctw = overlay_text_width(ctb);
int cmw = icon_sz + icon_gap + ctw;
int cpen = box_x + boxpad + (row_inner - cmw) / 2;
overlay_pass_rect(pass, cpen - 2, ry - 1, cmw + 4, row_h,
0.22f, 0.22f, 0.25f, 1.0f);
overlay_pass_text(pass, ctb, cpen + icon_sz + icon_gap,
ry + (row_h - host.overlay_font_h) / 2, 1.0f);
}
for (int k = 0; k < n; k++) {
struct kick_tab_group_member *m = members[k];
int tw = widths[k];
int mw = icon_sz + icon_gap + tw;
bool is_cursor = active && app->focus_member == m;
overlay_pass_rect(pass, pen - 2, ry - 1, mw + 4, row_h,
0.22f, 0.22f, 0.25f, 1.0f);
if (is_cursor)
overlay_pass_rect(pass, pen - 2, ry - 1, mw + 4, row_h,
0.24f, 0.36f, 0.60f, 0.9f);
int mx = pen, my = iy;
if (m->icon_wlr) {
overlay_pass_icon_tex(pass, m->icon_wlr, mx, my, icon_sz,
active ? 1.0f : 0.72f);
} else {
overlay_pass_rect(pass, mx, my, icon_sz, icon_sz,
0.45f, 0.45f, 0.48f, 0.85f);
overlay_pass_rect(pass, mx + 3, my + 3, icon_sz - 6, icon_sz - 6,
0.07f, 0.07f, 0.09f, 1.0f);
}
overlay_pass_text(pass, fitt[k], mx + icon_sz + icon_gap,
ry + (row_h - host.overlay_font_h) / 2,
active ? 1.0f : 0.648f);
pen += mw + member_gap;
}
row++;
}
static void ovl_gl_rect(const struct overlay_backend *bk, int x, int y,
int w, int h, float r, float g, float b, float a) {
(void)bk;
draw_rect(x, y, w, h, r, g, b, a);
}
static void ovl_gl_text(const struct overlay_backend *bk, const char *s,
int x, int y, float v, float a) {
(void)bk;
draw_text(s, x, y, v, v, v, a);
}
static bool ovl_gl_icon(const struct overlay_backend *bk,
struct kick_tab_group_member *m,
int x, int y, int size, bool active) {
(void)bk;
if (!m->icon_tex)
return false;
GLfloat tint[4] = { active ? 1.0f : 0.72f,
active ? 1.0f : 0.72f,
active ? 1.0f : 0.72f, 1.0f };
draw_texture_rect(m->icon_tex, m->icon_w, m->icon_h,
0, 0, m->icon_w, m->icon_h,
x, y, size, size, host.width, host.height, tint);
return true;
}
static const struct overlay_backend overlay_backend_gl = {
NULL, ovl_gl_rect, ovl_gl_text, ovl_gl_icon,
};
/* --- wlr-pass backend (standalone): leaves onto the output buffer --- */
static void ovl_pass_rect(const struct overlay_backend *bk, int x, int y,
int w, int h, float r, float g, float b, float a) {
overlay_pass_rect(bk->pass, x, y, w, h, r, g, b, a);
}
static void ovl_pass_text(const struct overlay_backend *bk, const char *s,
int x, int y, float v, float a) {
/* Greyscale text: the dim value folds into the one tint axis. */
overlay_pass_text(bk->pass, s, x, y, v * a);
}
static bool ovl_pass_icon(const struct overlay_backend *bk,
struct kick_tab_group_member *m,
int x, int y, int size, bool active) {
if (!m->icon_wlr)
return false;
overlay_pass_icon_tex(bk->pass, m->icon_wlr, x, y, size,
active ? 1.0f : 0.72f);
return true;
}
/* Nested entry: composite onto the host window's blitted frame. */
static void draw_tab_overlay(void) {
draw_tab_overlay_with(&overlay_backend_gl);
}
/* Standalone entry: second render pass on the output buffer. */
static void draw_tab_overlay_pass(struct wlr_render_pass *pass) {
struct overlay_backend bk = { pass, ovl_pass_rect, ovl_pass_text,
ovl_pass_icon };
draw_tab_overlay_with(&bk);
}
/* ===================== Server-side wlroots setup ===================== */
@ -6202,11 +6142,13 @@ static void overlay_refresh_if_visible(void) {
}
static void handle_toplevel_set_title(struct wl_listener *l, void *data) {
(void)l;
(void)data;
overlay_refresh_if_visible();
}
static void handle_toplevel_set_app_id(struct wl_listener *l, void *data) {
(void)l;
(void)data;
overlay_refresh_if_visible();
}