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:
parent
d47cb4ad03
commit
28d5df8d52
3 changed files with 152 additions and 14 deletions
|
|
@ -262,7 +262,9 @@ Pass `--overlay-previews` to list window thumbnails instead of titles:
|
|||
each row is the window's icon followed by a small view of the window as
|
||||
it last appeared (no captions). Rows divide the content-sized menu, which
|
||||
stays centered with a 50 pixel top and bottom margin; no thumbnail grows
|
||||
past a quarter of the screen height. The
|
||||
past a quarter of the screen height. A header row at the top of the menu
|
||||
names the selected window (icon plus ellipsized title) without affecting
|
||||
the menu size. The
|
||||
shown window keeps its thumbnail live -- it commits continuously, so the
|
||||
preview tracks it. Hidden windows freeze with no frame callbacks while
|
||||
another tab is shown, so their thumbnails sit at the last-shown frame
|
||||
|
|
|
|||
87
kick.c
87
kick.c
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -6683,8 +6683,9 @@ def _backdrop_band(s, path):
|
|||
|
||||
def test_standalone_headless_overlay_previews_menu_height():
|
||||
"""Preview menu is content-sized and centered: on the 800px headless
|
||||
screen two rows cap at 200px thumbs (208px rows), so the menu is
|
||||
2*208+8+2*6 = 436px tall starting at (800-436)/2 = 182."""
|
||||
screen two rows cap at 200px thumbs (208px rows) below a 44px title
|
||||
header + 8px gap, so the menu is 44+8+2*208+8+2*6 = 488px tall
|
||||
starting at (800-488)/2 = 156."""
|
||||
with _run_standalone(
|
||||
{}, "--overlay-previews",
|
||||
"%s '#ff0000'" % WL, "%s '#0000ff'" % WL,
|
||||
|
|
@ -6698,9 +6699,73 @@ def test_standalone_headless_overlay_previews_menu_height():
|
|||
finally:
|
||||
s.mods(0)
|
||||
assert first is not None, "no menu backdrop visible while Alt held"
|
||||
assert abs(first - 182) <= 3, f"menu top {first}, want 182"
|
||||
assert abs((last - first + 1) - 436) <= 3, \
|
||||
f"menu height {last - first + 1}, want 436"
|
||||
assert abs(first - 156) <= 3, f"menu top {first}, want 156"
|
||||
assert abs((last - first + 1) - 488) <= 3, \
|
||||
f"menu height {last - first + 1}, want 488"
|
||||
_assert_alive(s)
|
||||
|
||||
|
||||
def test_standalone_headless_overlay_previews_selected_title():
|
||||
"""Preview menu opens with a full-width header naming the selected
|
||||
window (icon + title), followed by the thumbnail rows: one contiguous
|
||||
488px band at y=156 on the 800px screen. Dumping with the selection
|
||||
on each of two very differently titled tabs gives identical menu
|
||||
extents (no width jumping)."""
|
||||
with _run_standalone(
|
||||
{}, "--overlay-previews",
|
||||
"%s --title R '#ff0000'" % WL,
|
||||
"%s --title A_much_longer_blue_window_title '#0000ff'" % WL,
|
||||
wait="any_frame") as s:
|
||||
s.wait_surface_mapped(0)
|
||||
s.wait_surface_mapped(1)
|
||||
|
||||
def is_backdrop(r, g, b):
|
||||
return abs(r - 18) <= 6 and abs(g - 18) <= 6 and abs(b - 23) <= 6
|
||||
|
||||
def is_bright(r, g, b):
|
||||
return r > 150 and g > 150 and b > 150
|
||||
|
||||
extents = []
|
||||
for _ in range(2):
|
||||
s.mods(ALT)
|
||||
try:
|
||||
time.sleep(1.0)
|
||||
w, h, px = s.ppm_rgb(s.dump_host())
|
||||
finally:
|
||||
s.mods(0)
|
||||
rows = [y for y in range(h)
|
||||
if any(is_backdrop(px[(y * w + i) * 3],
|
||||
px[(y * w + i) * 3 + 1],
|
||||
px[(y * w + i) * 3 + 2])
|
||||
for i in range(w))]
|
||||
assert rows, "no menu backdrop visible while Alt held"
|
||||
# One contiguous band: header lives inside the menu now.
|
||||
assert rows == list(range(rows[0], rows[-1] + 1)), \
|
||||
"menu backdrop not contiguous"
|
||||
xs = [i for y in rows for i in range(w)
|
||||
if is_backdrop(px[(y * w + i) * 3],
|
||||
px[(y * w + i) * 3 + 1],
|
||||
px[(y * w + i) * 3 + 2])]
|
||||
extents.append((rows[0], rows[-1], min(xs), max(xs)))
|
||||
# Bright title-text pixels sit in the header at the top of
|
||||
# the menu (dumps are vertically flipped, so flip back).
|
||||
hdr = [h - 1 - y for y in rows
|
||||
if any(is_bright(px[(y * w + i) * 3],
|
||||
px[(y * w + i) * 3 + 1],
|
||||
px[(y * w + i) * 3 + 2])
|
||||
for i in range(w))]
|
||||
assert hdr, "no title text pixels in the menu"
|
||||
top = h - 1 - rows[-1]
|
||||
assert all(top <= y <= top + 60 for y in hdr), \
|
||||
f"title text outside header: {hdr[:5]}"
|
||||
s.cycle_ack(-1)
|
||||
s.wait_active_frame()
|
||||
(f0, l0, x0, x1), (f1, l1, x2, x3) = extents
|
||||
assert abs((h - 1 - l0) - 156) <= 3, f"menu top {h - 1 - l0}, want 156"
|
||||
assert abs((l0 - f0 + 1) - 488) <= 3, \
|
||||
f"menu height {l0 - f0 + 1}, want 488"
|
||||
assert (f0, l0, x0, x1) == (f1, l1, x2, x3), \
|
||||
"menu extents moved with the selection"
|
||||
_assert_alive(s)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue