Custom shaders: Include border and margin in active_window_padding

active_window_padding only counted padding and cell-size compensation,
so the space a layout reserves for the window border (and margin) was
treated as outside the active window. With dim-inactive-windows this
left a thin dimmed strip around the active window and dimmed the active
border color.

Use render_data.geometry.spaces, which is exactly what the layout
reserved around the window. This respects layouts that do not reserve
border space in minimal border mode, so the active area never extends
into a neighboring window. Add a test that the reserved boxes of
visible windows never overlap and stay within the central area.
This commit is contained in:
Kovid Goyal 2026-09-24 19:38:01 +05:30
parent b2f3c15aad
commit 8e80e96c64
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
6 changed files with 45 additions and 9 deletions

View file

@ -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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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).

View file

@ -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