feat: show selected window title as header row in preview overlay

Preview menus open with a full-width header naming the active tab's current window (icon plus ellipsized title). Full width keeps menu sizing independent of the selection, so moving the highlight never resizes anything; thumbnail fill-share accounts for the header block within the existing margins. Taps on the header are swallowed.
This commit is contained in:
Andrew Hamilton 2026-09-29 07:12:04 +10:00
parent d47cb4ad03
commit 28d5df8d52
3 changed files with 152 additions and 14 deletions

87
kick.c
View file

@ -2673,8 +2673,9 @@ static void overlay_pass_icon_tex(struct wlr_render_pass *pass,
/* --overlay-previews geometry: thumbnail rows divide the menu, which is
content-sized and centered with a 50px top/bottom margin. Each row would
fill an equal share of the margined height, but no thumbnail grows past
a quarter of the screen height; the menu then wraps the capped rows.
fill an equal share of the margined height less the selected-window
header block, but no thumbnail grows past a quarter of the screen
height; the menu then wraps the header plus the capped rows.
Thumbnails keep a fixed 16:9 size; the preview row pads them like a text
row pads its font (+8). Pending (windowless) rows share the uniform row
height. */
@ -2689,10 +2690,12 @@ static int overlay_title_row_h(void) {
A slightly stale snapshot only changes the effective downscale filtering
at draw time, which stretches any snapshot to the live thumb box. */
static int preview_thumb_w_cached, preview_thumb_h_cached;
static int overlay_preview_head_h(void);
static void overlay_preview_metrics(int n_rows, int *row_h, int *thumb_w,
int *thumb_h) {
int boxpad = 6, row_gap = 8;
int avail_h = host.height - 2 * OVERLAY_MENU_MARGIN_V;
int avail_h = host.height - 2 * OVERLAY_MENU_MARGIN_V -
overlay_preview_head_h();
int fill = 0;
if (n_rows > 0)
fill = (avail_h - 2 * boxpad - (n_rows - 1) * row_gap) / n_rows - 8;
@ -3317,7 +3320,8 @@ static int overlay_tab_box(struct overlay_box *box) {
if (max_row_w > avail_w) max_row_w = avail_w;
int bw = max_row_w + 2 * boxpad;
int bh = n_rows * row_h + (n_rows - 1) * row_gap + 2 * boxpad;
int bh = overlay_preview_head_h() + n_rows * row_h +
(n_rows - 1) * row_gap + 2 * boxpad;
if (bw > host.width - 2 * margin) bw = host.width - 2 * margin;
/* Preview menus are content-sized by construction; the margin clamp is
only a safety net for degenerate (tiny) screens. */
@ -3402,8 +3406,62 @@ static void draw_confirm_quit_pass(struct wlr_render_pass *pass,
}
}
/* Selected-window header (preview mode): the active tab's current window
-- what the highlight sits on -- as a full-width header row at the top
of the menu: icon left-anchored, title centered in the remaining width
and ellipsized to it. Full width keeps the menu size independent of
the selection, so moving the highlight never resizes anything. Reuses
the row machinery: tab_row_title for the string (with its pending-tab
command fallback), the icon loader, and a budgeted text tile. */
static struct kick_window *overlay_selected_window(void) {
if (!server.active_tab) return NULL;
return tab_current_window(server.active_tab);
}
/* Vertical block the header reserves above the thumbnail rows: the title
row plus one standard row gap. Zero outside preview mode. */
static int overlay_preview_head_h(void) {
if (!opt_overlay_previews) return 0;
return overlay_title_row_h() + 8; /* row_gap */
}
static void overlay_pass_selected_header(struct wlr_render_pass *pass,
int box_x, int box_y, int boxpad,
int row_inner) {
struct kick_window *w = overlay_selected_window();
if (!w) return;
const char *t = tab_row_title(server.active_tab);
if (!t) t = "";
int icon_sz = OVERLAY_ICON_SZ, icon_gap = 8;
int row_h = overlay_title_row_h();
int hy = box_y + boxpad;
overlay_pass_rect(pass, box_x + boxpad - 2, hy - 1, row_inner + 4,
row_h, 0.22f, 0.22f, 0.25f, 1.0f);
if (overlay_window_icon_needs_load(w))
overlay_icon_load_window(w);
int ix = box_x + boxpad, iy = hy + (row_h - icon_sz) / 2;
if (w->icon_wlr) {
overlay_pass_icon_tex(pass, w->icon_wlr, ix, iy, icon_sz, 1.0f);
} else {
overlay_pass_rect(pass, ix, iy, icon_sz, icon_sz,
0.45f, 0.45f, 0.48f, 0.85f);
overlay_pass_rect(pass, ix + 3, iy + 3, icon_sz - 6,
icon_sz - 6, 0.07f, 0.07f, 0.09f, 1.0f);
}
int rx = ix + icon_sz + icon_gap;
int rw = row_inner - (icon_sz + icon_gap);
if (rw < 0) rw = 0;
struct overlay_text_entry *te = overlay_text_tile(t, rw);
int tw = te ? te->w : 0;
int tx = rx + (rw - tw) / 2;
if (tx < rx) tx = rx;
overlay_pass_text(pass, t, rw, tx,
hy + (row_h - host.overlay_font_h) / 2, 1.0f);
}
static void draw_tab_overlay_pass(struct wlr_render_pass *pass,
int origin_x, int origin_y) {
int origin_x, int origin_y) {
if (!host.overlay_visible && !confirm_quit_pending) return;
if (confirm_quit_pending) {
draw_confirm_quit_pass(pass, origin_x, origin_y);
@ -3441,13 +3499,19 @@ static void draw_tab_overlay_pass(struct wlr_render_pass *pass,
overlay_pass_rect(pass, box_x + box_w - bw, box_y, bw, box_h,
0.45f, 0.45f, 0.48f, 1.0f);
/* Selected-window header first (preview mode); thumbnail rows follow
below it with the standard gap. */
int head_h = overlay_preview_head_h();
int row_inner = box_w - 2 * boxpad;
if (opt_overlay_previews && head_h > 0)
overlay_pass_selected_header(pass, box_x, box_y, boxpad, row_inner);
int row = 0;
wl_list_for_each(tab, &server.tabs, link) {
if (!tab_overlay_row(tab)) continue;
(void)tab_row_title(tab);
int ry = box_y + boxpad + row * (row_h + row_gap);
int ry = box_y + boxpad + head_h + row * (row_h + row_gap);
bool active = tab == server.active_tab;
int row_inner = box_w - 2 * boxpad;
struct kick_window *wins[32];
char tbuf[32][256];
@ -3577,10 +3641,17 @@ static bool overlay_hit_test(double ox, double oy,
}
struct kick_tab *tab;
/* The selected-window header is display-only: taps on it change
nothing. */
int head_h = overlay_preview_head_h();
if (head_h > 0 && oy >= box_y + boxpad &&
oy < box_y + boxpad + head_h)
return false;
int row = 0;
wl_list_for_each(tab, &server.tabs, link) {
if (!tab_overlay_row(tab)) continue;
int ry = box_y + boxpad + row * (row_h + row_gap);
int ry = box_y + boxpad + head_h + row * (row_h + row_gap);
int row_inner = box_w - 2 * boxpad;
struct kick_window *wins[32];