diff --git a/docs/changelog.rst b/docs/changelog.rst index f19e5af15..20b28288f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -204,6 +204,9 @@ Detailed list of changes - Support for :doc:`/custom-shaders` for adding various graphical effects (:iss:`10344`) +- Custom shaders: Allow tinting inactive windows toward a configurable color, + preserving the tab bar and active window padding. + - Various throughput performance improvements for a 15-35% real world improvement depending on workload - Add :opt:`window_border_radius` for rounded window borders (:pull:`10421`) diff --git a/docs/custom-shaders.rst b/docs/custom-shaders.rst index 7670a66f6..8b82c1dae 100644 --- a/docs/custom-shaders.rst +++ b/docs/custom-shaders.rst @@ -79,6 +79,29 @@ mouse shader. In the first group we have the crt shader and its color has been customised. The second group defines the spotlight shader and specifies exactly when the spotlight animation should start and stop. +For example, to blend inactive windows toward gray instead of black, save the +following as :file:`shaders/dim-gray.pipeline` in your kitty config directory and +set ``custom_shaders dim-gray`` and ``inactive_text_alpha 1`` in :file:`kitty.conf`:: + + startgroup + var float HIGHLIGHT_INTENSITY = 0 + var float BORDER_WIDTH = 0 + var float INACTIVE_DIM = 0.74 + var float3 INACTIVE_DIM_COLOR = float3(0.2158605) + var bool DIM_CENTRAL_AREA_ONLY = true + var bool INCLUDE_WINDOW_PADDING = true + shaders focus-highlight + endgroup + +This uses the same shader as ``dim-inactive-windows``. ``INACTIVE_DIM`` is the +fraction of the original color retained: ``0.74`` blends in 26% of the target +color. ``INACTIVE_DIM_COLOR`` is in linear RGB; ``0.2158605`` corresponds to +sRGB ``#808080``. The default target is black, preserving the existing dimming +effect. The two optional flags preserve the tab bar and include the active +window's padding in its unchanged area. Explicit cell backgrounds and images +are blended together with text, while transparency and colors reported to +terminal applications are preserved. + Anatomy of a custom shader ---------------------------- diff --git a/kitty/shaders.c b/kitty/shaders.c index 78bf80b95..faafde04e 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -2433,6 +2433,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 active_window_padding[4]; float bell_window_geometry[4]; float central_area[4]; float cursor_trail_corners_x[4]; @@ -2475,6 +2476,13 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now) if (t->num_windows) { Window *w = t->windows + t->active_window; active_win_geom = w->render_data.geometry; + if (os_window->viewport_width && os_window->viewport_height) { + const float width = (float)os_window->viewport_width, height = (float)os_window->viewport_height; + d->active_window_padding[0] = (float)(w->padding.left + w->size_mismatch_padding.left) / width; + d->active_window_padding[1] = (float)(w->padding.bottom + w->size_mismatch_padding.bottom) / height; + d->active_window_padding[2] = (float)(w->padding.right + w->size_mismatch_padding.right) / width; + d->active_window_padding[3] = (float)(w->padding.top + w->size_mismatch_padding.top) / height; + } Screen *s = w->render_data.screen; if (s) { active_bg = colorprofile_to_color(s->color_profile, s->color_profile->overridden.default_bg, s->color_profile->configured.default_bg).rgb; diff --git a/kitty/shaders/custom/focus-highlight.slang b/kitty/shaders/custom/focus-highlight.slang index 651b9ec57..10a7f16f4 100644 --- a/kitty/shaders/custom/focus-highlight.slang +++ b/kitty/shaders/custom/focus-highlight.slang @@ -16,14 +16,28 @@ static const float HIGHLIGHT_INTENSITY = 0.30; static const float BORDER_WIDTH = 0.04; // Peak additive brightness multiplier at the window border edge. static const float BORDER_GLOW = 0.55; -// Dim factor applied to pixels outside the active window (1.0 = unchanged, 0.0 = black). +// Fraction of the original color retained outside the active window (1.0 = unchanged, 0.0 = blend target). static const float INACTIVE_DIM = 0.3; +// Blend target in linear RGB; black preserves the original dimming behavior. +static const float3 INACTIVE_DIM_COLOR = float3(0.0); +// Keep the tab bar outside the effect when requested by a pipeline. +static const bool DIM_CENTRAL_AREA_ONLY = false; +// Include the active window's padding in its unchanged/highlighted area. +static const bool INCLUDE_WINDOW_PADDING = false; public float4 fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) { + if (DIM_CENTRAL_AREA_ONLY) { + float4 area = d.central_area; + if (any(t.pos < area.xy) || any(t.pos > area.xy + area.zw)) return color; + } float4 g = d.active_window_geometry; // Pass through unchanged if no active window geometry is known. if (g.z <= 0.0 || g.w <= 0.0) return color; + if (INCLUDE_WINDOW_PADDING) { + g.xy -= d.active_window_padding.xy; + g.zw += d.active_window_padding.xy + d.active_window_padding.zw; + } float2 uv = t.pos; float2 winMin = g.xy; @@ -48,5 +62,5 @@ fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) { } // Slightly dim inactive areas to draw the eye to the focused window. float dim = lerp(1.0, INACTIVE_DIM, strength); - return float4(color.rgb * dim, color.a); + return float4(lerp(INACTIVE_DIM_COLOR, color.rgb, dim), color.a); } diff --git a/kitty/shaders/custom/types.slang b/kitty/shaders/custom/types.slang index 4b2a96ab1..d73d32859 100644 --- a/kitty/shaders/custom/types.slang +++ b/kitty/shaders/custom/types.slang @@ -22,6 +22,9 @@ 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; + // Effective padding of the active window, including cell-size compensation. + // .xy = left/bottom, .zw = right/top, in viewport UV units. + public float4 active_window_padding; // Geometry of the most recently belled window in UV co-ordinates (origin bottom-left). // .xy = lower-left corner of the window, .zw = width and height. // If the belled window is not currently visible in the active tab/layout, equals central_area. diff --git a/kitty_tests/slang.py b/kitty_tests/slang.py index 5d08875f9..1a9fcb26f 100644 --- a/kitty_tests/slang.py +++ b/kitty_tests/slang.py @@ -33,6 +33,33 @@ _SUPPORT_SHADER_NAMES = frozenset(('types', 'pipeline')) class TestSlang(BaseTest): + def test_inactive_window_tint_pipeline(self): + if not shutil.which(slangc()[0]): + self.skipTest(f'slangc ({slangc()[0]}) not found in PATH') + + pipeline = parse_pipeline_definition( + """ + startgroup + var float HIGHLIGHT_INTENSITY = 0 + var float BORDER_WIDTH = 0 + var float INACTIVE_DIM = 0.74 + var float3 INACTIVE_DIM_COLOR = float3(0.2158605) + var bool DIM_CENTRAL_AREA_ONLY = true + var bool INCLUDE_WINDOW_PADDING = true + shaders focus-highlight + endgroup + """.splitlines(), + 'dim-gray', + ) + with tempfile.TemporaryDirectory() as cache_dir: + clear_caches() + try: + vertex, fragment, _ = build_custom_shader_pipeline_glsl(pipeline, cache_dir=cache_dir) + finally: + clear_caches() + self.assertTrue(vertex) + self.assertTrue(fragment) + def test_slang_parser(self): def check(src: str, expected: SlangFile) -> None: actual = parse_slang_text(src)