diff --git a/docs/changelog.rst b/docs/changelog.rst index 16c1b1354..3b1a643fc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -209,7 +209,7 @@ Detailed list of changes color profile - Custom shaders: The builtin :code:`dim-inactive-windows` shader no longer dims - the tab bar and the padding around the active window (:iss:`10524`) + the tab bar and the padding, border and margin around the active window (:iss:`10524`) 0.49.1 [2026-09-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/custom-shaders.rst b/docs/custom-shaders.rst index 50a482d51..692b854f5 100644 --- a/docs/custom-shaders.rst +++ b/docs/custom-shaders.rst @@ -99,7 +99,7 @@ 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 title bar and padding in its unchanged area. Explicit cell backgrounds +window's title bar, margin, border and 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. ``animation_step 0`` makes this static effect redraw only when content or shader events change. diff --git a/kitty/shaders.c b/kitty/shaders.c index 37a2887d0..d1011bf24 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -2546,10 +2546,14 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now) 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; + // spaces is all the space the layout reserved around the window: + // margin, border (only if the layout reserves it), padding and + // cell-size compensation. + const WindowGeometry *sg = &w->render_data.geometry; + d->active_window_padding[0] = (float)sg->spaces.left / width; + d->active_window_padding[1] = (float)sg->spaces.bottom / height; + d->active_window_padding[2] = (float)sg->spaces.right / width; + d->active_window_padding[3] = (float)sg->spaces.top / height; // The content geometry excludes the title bar. Include it before // extending to the outer padding, so the effect boundary cannot // cut through a title bar at either edge of the window. diff --git a/kitty/shaders/custom/focus-highlight.slang b/kitty/shaders/custom/focus-highlight.slang index 11ee99190..16128aeaa 100644 --- a/kitty/shaders/custom/focus-highlight.slang +++ b/kitty/shaders/custom/focus-highlight.slang @@ -22,7 +22,7 @@ static const float INACTIVE_DIM = 0.3; 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 title bar and padding in its unchanged/highlighted area. +// Include the active window's title bar, margin, border and padding in its unchanged/highlighted area. static const bool INCLUDE_WINDOW_PADDING = false; public float4 diff --git a/kitty/shaders/custom/types.slang b/kitty/shaders/custom/types.slang index 0f361ae8e..fc4f55163 100644 --- a/kitty/shaders/custom/types.slang +++ b/kitty/shaders/custom/types.slang @@ -22,8 +22,8 @@ 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; - // Extents beyond the active content, including a visible title bar, padding - // and cell-size compensation. + // Extents beyond the active content, including a visible title bar, margin, + // border, padding and 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). diff --git a/kitty_tests/layout.py b/kitty_tests/layout.py index 6f32e1f52..54d8df4af 100644 --- a/kitty_tests/layout.py +++ b/kitty_tests/layout.py @@ -1071,6 +1071,38 @@ class TestSplitBorderResize(BaseSplitGeometryTest): self.assertIsNone(data.vertical_id) +class TestReservedSpaces(BaseSplitGeometryTest): + def test_reserved_spaces_do_not_overlap(self): + # Custom shaders treat the space a layout reserves around the active + # window (WindowGeometry.spaces) as part of it, so the reserved boxes + # of visible windows must fit in the central area without overlapping. + central = Region((19, 47, 1518, 1146, 1500, 1100)) + for cls in (Tall, Grid, Horizontal, Stack, Splits): + for minimal in (True, False): + for num in (1, 2, 3, 5): + layout = create_layout(cls) + windows = create_windows(layout, num=num) + self.stub_dimensions(layout, central, minimal) + layout(windows) + boxes = [] + for w in windows: + if not w.is_visible_in_layout: + continue + g = w.geometry + boxes.append((g.left - g.spaces.left, g.top - g.spaces.top, g.right + g.spaces.right, g.bottom + g.spaces.bottom)) + with self.subTest(layout=cls.name, minimal=minimal, num=num): + self.assertTrue(boxes) + for left, top, right, bottom in boxes: + self.assertGreaterEqual(left, central.left) + self.assertGreaterEqual(top, central.top) + self.assertLessEqual(right, central.left + central.width) + self.assertLessEqual(bottom, central.top + central.height) + for i, a in enumerate(boxes): + for b in boxes[i + 1 :]: + overlaps = a[0] < b[2] and b[0] < a[2] and a[1] < b[3] and b[1] < a[3] + self.assertFalse(overlaps, f'{a} overlaps {b}') + + class TestSplitDragGeometry(BaseSplitGeometryTest): def make_layout(self, shape, minimal=True, num=4): from types import SimpleNamespace