From 7bd5cd7dbd322434c71d5522bbdb51fd0724f1ce Mon Sep 17 00:00:00 2001 From: Andrew Hamilton Date: Mon, 24 Aug 2026 09:11:15 +1000 Subject: [PATCH] unify overlay rendering: one shared layout behind host-GL and wlr-pass backends --- kick.c | 296 +++++++++++++++++++++++---------------------------------- 1 file changed, 119 insertions(+), 177 deletions(-) diff --git a/kick.c b/kick.c index 9ce2aa2..d81c8ea 100644 --- a/kick.c +++ b/kick.c @@ -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(); }