From 40b646a54a9e63d1019673c909a1182c7d5d54ff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 9 Aug 2026 21:29:40 +0530 Subject: [PATCH] A tab change reveal animation --- kitty/shaders.c | 12 ++++ kitty/shaders/custom/tab-focus.pipeline | 7 ++ kitty/shaders/custom/tab-focus.slang | 96 +++++++++++++++++++++++++ kitty/shaders/custom/types.slang | 5 ++ 4 files changed, 120 insertions(+) create mode 100644 kitty/shaders/custom/tab-focus.pipeline create mode 100644 kitty/shaders/custom/tab-focus.slang diff --git a/kitty/shaders.c b/kitty/shaders.c index 10f099532..ad57939ec 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -2359,6 +2359,7 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now) float mouse_pos[4]; float mouse_button_pressed[4]; float active_window_geometry[4]; + float central_area[4]; float cursor_trail_corners_x[4]; float cursor_trail_corners_y[4]; float cursor_trail_edge[4]; @@ -2474,6 +2475,17 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now) d->active_window_geometry[2] = (float)(active_win_geom.right - active_win_geom.left) / vpw; d->active_window_geometry[3] = (float)(active_win_geom.bottom - active_win_geom.top) / vph; } + if (vpw > 0 && vph > 0) { + Region central_rgn = {0}, tab_bar_rgn = {0}; + os_window_regions(os_window, ¢ral_rgn, &tab_bar_rgn); + d->central_area[0] = (float)central_rgn.left / vpw; + d->central_area[1] = 1.f - (float)central_rgn.bottom / vph; + d->central_area[2] = (float)(central_rgn.right - central_rgn.left) / vpw; + d->central_area[3] = (float)(central_rgn.bottom - central_rgn.top) / vph; + } else { + d->central_area[2] = 1.f; + d->central_area[3] = 1.f; + } unmap_vao_buffer(custom_end_vao_idx, 0); bind_vao_uniform_buffer(custom_end_vao_idx, 0, CUSTOM_END_DATA_BINDING_POINT); diff --git a/kitty/shaders/custom/tab-focus.pipeline b/kitty/shaders/custom/tab-focus.pipeline new file mode 100644 index 000000000..d4ee17049 --- /dev/null +++ b/kitty/shaders/custom/tab-focus.pipeline @@ -0,0 +1,7 @@ +startgroup + animation_start tab-change + animation_stop 600 + animation_curve ease-out + animation_step 16 + shaders tab-focus +endgroup diff --git a/kitty/shaders/custom/tab-focus.slang b/kitty/shaders/custom/tab-focus.slang new file mode 100644 index 000000000..b8b92fe16 --- /dev/null +++ b/kitty/shaders/custom/tab-focus.slang @@ -0,0 +1,96 @@ +#language slang 2026 +// Copyright (C) 2026 Kovid Goyal +// Distributed under terms of the GPLv3 license. +// +// Tab-change reveal shader +// Plays a brief reveal animation whenever the active tab changes. +// The reveal direction for wipe-based modes is derived from d.central_area: +// the wipe starts at whichever edge of the content area is furthest from the +// viewport boundary — that edge is where the tab bar sits. +// +// ANIMATION_TYPE selects the effect: +// 0 = FADE — uniform brightness fade-in over the whole screen +// 1 = WIPE — a single swept edge travelling away from the tab bar +// 2 = BLINDS — sequential strip reveals sweeping away from the tab bar +// 3 = IRIS — circular iris expanding from the centre of the content area + +import kitty_custom_shader_types; + +// 0=FADE, 1=WIPE, 2=BLINDS, 3=IRIS +extern static const int ANIMATION_TYPE = 1; + +// Number of strips for ANIMATION_TYPE == 2 (BLINDS). +extern static const int NUM_BLINDS = 8; + +// Soft-edge width in UV units used by WIPE and IRIS. +extern static const float EDGE_SOFTNESS = 0.04; + +// Returns a value in [0, 1] for this pixel: 0 at the tab-bar edge of the +// content area, 1 at the opposite edge. The wipe sweeps from 0 toward 1. +float wipe_pixel_pos(float2 uv, float4 ca) { + float gap_left = ca.x; + float gap_right = 1.0 - (ca.x + ca.z); + float gap_bottom = ca.y; + float gap_top = 1.0 - (ca.y + ca.w); + float max_gap = max(max(gap_left, gap_right), max(gap_bottom, gap_top)); + + float pos; + if (max_gap == gap_top) { + // Tab bar at top — sweep from top-of-content downward + pos = (ca.y + ca.w - uv.y) / ca.w; + } else if (max_gap == gap_bottom) { + // Tab bar at bottom — sweep from bottom-of-content upward + pos = (uv.y - ca.y) / ca.w; + } else if (max_gap == gap_left) { + // Tab bar at left — sweep from left-of-content rightward + pos = (uv.x - ca.x) / ca.z; + } else { + // Tab bar at right — sweep from right-of-content leftward + pos = (ca.x + ca.z - uv.x) / ca.z; + } + return clamp(pos, 0.0, 1.0); +} + +public float4 fragment_main( + float4 color, + KittyTextures t, + KittyCustomShaderData d, + float4 viewport, + float animation_progress +) { + float4 ca = d.central_area; + if (ca.z <= 0.0 || ca.w <= 0.0) return color; + + float p = animation_progress; + float brightness; + + if (ANIMATION_TYPE == 0) { + // FADE: uniform ramp over the whole frame + brightness = p; + } else if (ANIMATION_TYPE == 1) { + // WIPE: single swept edge with soft falloff + float pixel_pos = wipe_pixel_pos(t.pos, ca); + // 1.0 where revealed (pixel_pos < p), 0.0 where not yet reached + brightness = 1.0 - smoothstep(p - EDGE_SOFTNESS, p + EDGE_SOFTNESS, pixel_pos); + } else if (ANIMATION_TYPE == 2) { + // BLINDS: strips opening in sequence from the tab-bar side + float pixel_pos = wipe_pixel_pos(t.pos, ca); + float inv_n = 1.0 / float(NUM_BLINDS); + float strip_idx = floor(pixel_pos * float(NUM_BLINDS)); + float strip_start = strip_idx * inv_n; + // Progress local to this strip: 0 when it starts opening, 1 when fully open + float strip_p = clamp((p - strip_start) / inv_n, 0.0, 1.0); + // Position within the strip (0 at tab-bar side, 1 at far side) + float local = fract(pixel_pos * float(NUM_BLINDS)); + float soft = 0.1; + brightness = 1.0 - smoothstep(strip_p - soft, strip_p + soft, local); + } else { + // IRIS: circular reveal expanding from the centre of the content area + float2 ca_center = ca.xy + ca.zw * 0.5; + float half_diag = length(ca.zw * 0.5); + float dist = length(t.pos - ca_center) / max(half_diag, 0.001); + brightness = 1.0 - smoothstep(p - EDGE_SOFTNESS, p + EDGE_SOFTNESS, dist); + } + + return float4(color.rgb * brightness, color.a); +} diff --git a/kitty/shaders/custom/types.slang b/kitty/shaders/custom/types.slang index fbd0d4358..c86a709f5 100644 --- a/kitty/shaders/custom/types.slang +++ b/kitty/shaders/custom/types.slang @@ -22,6 +22,11 @@ public struct KittyCustomShaderData { // .xy = lower-left corner of the window, .zw = width and height. // All components are zero when no active window was rendered this frame. public float4 active_window_geometry; + // Geometry of the central content area of the OS window (the tab area, excluding any tab bar) + // in UV co-ordinates (origin bottom-left). + // .xy = lower-left corner, .zw = width and height. + // Equals the full viewport when no tab bar is visible. + public float4 central_area; // Cursor trail state (UV coordinates, origin bottom-left, (1,1) top-right). // All fields are zero when the cursor_trail kitty option is disabled or the trail is inactive.