A new option to color compensatory padding with neighboring cell colors

This commit is contained in:
Kovid Goyal 2026-07-09 12:52:39 +05:30
parent 37474b478e
commit 90df23db0c
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
19 changed files with 368 additions and 18 deletions

View file

@ -184,6 +184,8 @@ Detailed list of changes
- Add a new :code:`kitten @ screenshot` remote control command to take a pixel perfect PNG screenshot of an OS Window, tab or window
- A new option, :opt:`padding_fill_strategy` to control how the thin padding strips that appear when the window size is not an exact multiple of the cell size are colored. You can choose to have the padding colored to match the background of each neighboring cell, effectively extending the size of the cell or you can continue to use the existing behavior of using the background.
0.48.1 [2026-07-24]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -119,6 +119,7 @@ typedef enum { SCROLLBAR_NEVER, SCROLLBAR_ON_SCROLLED, SCROLLBAR_ON_HOVERED, SCR
typedef enum { PROGRESS_BAR_HIDDEN, PROGRESS_BAR_LEFT, PROGRESS_BAR_RIGHT, PROGRESS_BAR_TOP, PROGRESS_BAR_BOTTOM } ProgressBarPosition;
typedef enum { PROGRESS_STATE_UNSET, PROGRESS_STATE_SET, PROGRESS_STATE_ERROR, PROGRESS_STATE_INDETERMINATE, PROGRESS_STATE_PAUSED } ProgressBarState;
typedef enum { TILING, SCALED, MIRRORED, CLAMPED, CENTER_CLAMPED, CENTER_SCALED } BackgroundImageLayout;
typedef enum { PADDING_FILL_BACKGROUND, PADDING_FILL_NEIGHBORING_CELL } PaddingFillStrategy;
typedef struct ImageAnchorPosition {
float canvas_x, canvas_y, image_x, image_y;
} ImageAnchorPosition;

View file

@ -281,6 +281,7 @@ CURSOR_UNDERLINE: int
DECAWM: int
BGIMAGE_PROGRAM: int
CELL_PROGRAM: int
PADDING_PROGRAM: int
CELL_FG_PROGRAM: int
CELL_BG_PROGRAM: int
BLIT_PROGRAM: int
@ -1426,7 +1427,8 @@ def set_window_title_bar_render_data(
def set_window_render_data(
os_window_id: int, tab_id: int, window_id: int, screen: Screen,
left: int, top: int, right: int, bottom: int,
spaces_left: int, spaces_top: int, spaces_right: int, spaces_bottom: int
spaces_left: int, spaces_top: int, spaces_right: int, spaces_bottom: int,
cp_left: int, cp_top: int, cp_right: int, cp_bottom: int,
) -> None:
pass

View file

@ -595,6 +595,32 @@ add_attribute_to_vao(ssize_t vao_idx, int location, GLint size, GLenum data_type
add_located_attribute_to_vao(vao_idx, location, size, data_type, stride, offset, divisor);
}
void
set_vao_attribute(ssize_t vao_idx, size_t buffer_idx, int location, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) {
// (Re)configure an attribute pointer that reads from a specific buffer of
// the VAO (unlike add_attribute_to_vao which always uses the last added
// buffer). The VAO must be bound before calling this.
VAO *vao = vaos + vao_idx;
ssize_t buf = vao->buffers[buffer_idx];
bind_buffer(buf);
glEnableVertexAttribArray(location);
switch(data_type) {
case GL_BYTE:
case GL_UNSIGNED_BYTE:
case GL_SHORT:
case GL_UNSIGNED_SHORT:
case GL_INT:
case GL_UNSIGNED_INT:
glVertexAttribIPointer(location, size, data_type, stride, offset);
break;
default:
glVertexAttribPointer(location, size, data_type, GL_FALSE, stride, offset);
break;
}
glVertexAttribDivisorARB(location, divisor);
unbind_buffer(buf);
}
void
remove_vao(ssize_t vao_idx) {
VAO *vao = vaos + vao_idx;

View file

@ -66,6 +66,7 @@ ArrayInformation program_uniform_array(int program, const char *name);
ssize_t create_vao(void);
size_t add_buffer_to_vao(ssize_t vao_idx, GLenum usage);
void add_attribute_to_vao(ssize_t vao_idx, int location, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor);
void set_vao_attribute(ssize_t vao_idx, size_t buffer_idx, int location, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor);
ssize_t alloc_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, GLenum usage);
void* alloc_and_map_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, bool frequently_updated);
void unmap_vao_buffer(ssize_t vao_idx, size_t bufnum);

View file

@ -37,6 +37,11 @@ class LayoutData(NamedTuple):
space_before: int = 0
space_after: int = 0
content_size: int = 0
# The portion of space_before/space_after that is compensatory padding
# arising from the window size not being an exact multiple of the cell size
# (as opposed to the intentional margin/border/padding decoration).
compensatory_before: int = 0
compensatory_after: int = 0
DecorationPairs = Sequence[tuple[int, int]]
@ -47,6 +52,7 @@ ListOfWindows = list[WindowType]
class LayoutGlobalData:
draw_minimal_borders: bool = True
draw_active_borders: bool = True
fill_padding_with_neighboring_cell: bool = False
alignment_x: int = 0
alignment_y: int = 0
@ -75,6 +81,7 @@ def effective_draw_minimal_borders(opts: Options, has_more_than_one_visible_grou
def set_layout_options(opts: Options) -> None:
lgd.draw_minimal_borders = effective_draw_minimal_borders(opts)
lgd.draw_active_borders = opts.active_border_color is not None
lgd.fill_padding_with_neighboring_cell = opts.padding_fill_strategy == 'neighboring_cell'
lgd.alignment_x = -1 if opts.placement_strategy.endswith('left') else 1 if opts.placement_strategy.endswith('right') else 0
lgd.alignment_y = -1 if opts.placement_strategy.startswith('top') else 1 if opts.placement_strategy.startswith('bottom') else 0
@ -148,7 +155,12 @@ def layout_dimension(
after_space = (start_at + length) - (pos + content_size)
else:
after_space = after_dec
yield LayoutData(pos, cells_per_window, before_space, after_space, content_size)
# Whatever space is present beyond the intentional decoration is the
# compensatory padding from the size mismatch (only ever non-zero for
# the first/last windows, which absorb the leading/trailing remainder).
yield LayoutData(
pos, cells_per_window, before_space, after_space, content_size,
before_space - before_dec, after_space - after_dec)
pos += content_size + after_space
@ -160,28 +172,50 @@ class Rect(NamedTuple):
def blank_rects_for_window(wg: WindowGeometry) -> Generator[Rect, None, None]:
left_width, right_width = wg.spaces.left, wg.spaces.right
top_height, bottom_height = wg.spaces.top, wg.spaces.bottom
left, top, right, bottom = wg.left, wg.top, wg.right, wg.bottom
left_width, top_height, right_width, bottom_height = wg.spaces
if lgd.fill_padding_with_neighboring_cell:
# The compensatory padding is the innermost slice of the spaces, adjacent
# to the cells. It is drawn separately by the padding shader so that it
# matches its neighboring cell. Exclude it here by expanding the content
# box over it and only painting the remaining outer decoration frame in
# the background color.
c = wg.compensatory
left -= c.left
top -= c.top
right += c.right
bottom += c.bottom
left_width -= c.left
top_height -= c.top
right_width -= c.right
bottom_height -= c.bottom
if left_width > 0:
yield Rect(wg.left - left_width, wg.top - top_height, wg.left, wg.bottom + bottom_height)
yield Rect(left - left_width, top - top_height, left, bottom + bottom_height)
if top_height > 0:
yield Rect(wg.left, wg.top - top_height, wg.right + right_width, wg.top)
yield Rect(left, top - top_height, right + right_width, top)
if right_width > 0:
yield Rect(wg.right, wg.top, wg.right + right_width, wg.bottom + bottom_height)
yield Rect(right, top, right + right_width, bottom + bottom_height)
if bottom_height > 0:
yield Rect(wg.left, wg.bottom, wg.right, wg.bottom + bottom_height)
yield Rect(left, bottom, right, bottom + bottom_height)
def window_geometry(xstart: int, xnum: int, ystart: int, ynum: int, left: int, top: int, right: int, bottom: int) -> WindowGeometry:
def window_geometry(
xstart: int, xnum: int, ystart: int, ynum: int, left: int, top: int, right: int, bottom: int,
compensatory: Edges = Edges(),
) -> WindowGeometry:
return WindowGeometry(
left=xstart, top=ystart, xnum=max(0, xnum), ynum=max(0, ynum),
right=xstart + lgd.cell_width * xnum, bottom=ystart + lgd.cell_height * ynum,
spaces=Edges(left, top, right, bottom)
spaces=Edges(left, top, right, bottom), compensatory=compensatory,
)
def window_geometry_from_layouts(x: LayoutData, y: LayoutData) -> WindowGeometry:
return window_geometry(x.content_pos, x.cells_per_window, y.content_pos, y.cells_per_window, x.space_before, y.space_before, x.space_after, y.space_after)
return window_geometry(
x.content_pos, x.cells_per_window, y.content_pos, y.cells_per_window,
x.space_before, y.space_before, x.space_after, y.space_after,
Edges(x.compensatory_before, y.compensatory_before, x.compensatory_after, y.compensatory_after),
)
def layout_single_window(

View file

@ -1741,6 +1741,25 @@ The value can be one of: :code:`top-left`, :code:`top`, :code:`top-right`,
""",
)
opt(
'padding_fill_strategy',
'background',
choices=('background', 'neighboring_cell'),
ctype='padding_fill_strategy',
long_text="""
When the window size is not an exact multiple of the cell size, thin strips of
compensatory padding are added at the window edges (see
:opt:`placement_strategy`). This option controls how those strips are colored.
:code:`neighboring_cell` colors each strip to match the
background color of the cell adjacent to it, which looks best with full screen
applications such as editors that have differently colored border cells. A value
of :code:`background` colors the strips using the window background
color. Note that this only affects the compensatory padding, the intentional
padding from :opt:`window_padding_width` is always drawn using the background
color.
""",
)
opt(
'active_border_color',
'#00ff00',

View file

@ -1189,6 +1189,14 @@ class Parser:
def open_url_with(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['open_url_with'] = to_cmdline(val)
def padding_fill_strategy(self, val: str, ans: dict[str, typing.Any]) -> None:
val = val.lower()
if val not in self.choices_for_padding_fill_strategy:
raise ValueError(f"The value {val} is not a valid choice for padding_fill_strategy")
ans["padding_fill_strategy"] = val
choices_for_padding_fill_strategy = frozenset(('background', 'neighboring_cell'))
def palette_generate(self, val: str, ans: dict[str, typing.Any]) -> None:
val = val.lower()
if val not in self.choices_for_palette_generate:

View file

@ -876,6 +876,19 @@ convert_from_opts_linux_bell_theme(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_padding_fill_strategy(PyObject *val, Options *opts) {
opts->padding_fill_strategy = padding_fill_strategy(val);
}
static void
convert_from_opts_padding_fill_strategy(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "padding_fill_strategy");
if (ret == NULL) return;
convert_from_python_padding_fill_strategy(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_active_border_color(PyObject *val, Options *opts) {
opts->active_border_color = active_border_color(val);
@ -1675,6 +1688,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_linux_bell_theme(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_padding_fill_strategy(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_active_border_color(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_inactive_border_color(py_opts, opts);

View file

@ -150,6 +150,12 @@ bglayout(PyObject *layout_name) {
return TILING;
}
static inline PaddingFillStrategy
padding_fill_strategy(PyObject *val) {
const char *name = PyUnicode_AsUTF8(val);
return name[0] == 'n' ? PADDING_FILL_NEIGHBORING_CELL : PADDING_FILL_BACKGROUND;
}
static inline ImageAnchorPosition
bganchor(PyObject *anchor_name) {
const char *name = PyUnicode_AsUTF8(anchor_name);

View file

@ -26,6 +26,7 @@ choices_for_focus_follows_mouse = typing.Literal['no', 'n', 'false', 'y', 'yes',
choices_for_linux_display_server = typing.Literal['auto', 'wayland', 'x11']
choices_for_macos_colorspace = typing.Literal['srgb', 'default', 'displayp3']
choices_for_macos_show_window_title_in = typing.Literal['all', 'menubar', 'none', 'window']
choices_for_padding_fill_strategy = typing.Literal['background', 'neighboring_cell']
choices_for_palette_generate = typing.Literal['fixed', 'semantic', 'legacy']
choices_for_placement_strategy = typing.Literal['top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right']
choices_for_pointer_shape_when_grabbed = choices_for_default_pointer_shape
@ -416,6 +417,7 @@ option_names = (
'narrow_symbols',
'notify_on_cmd_finish',
'open_url_with',
'padding_fill_strategy',
'palette_generate',
'paste_actions',
'pixel_scroll',
@ -627,6 +629,7 @@ class Options:
mouse_hide_wait: MouseHideWait = MouseHideWait(hide_wait=0.0, show_wait=0.0, show_threshold=40, scroll_show=True) if is_macos else MouseHideWait(hide_wait=3.0, show_wait=0.0, show_threshold=40, scroll_show=True)
notify_on_cmd_finish: NotifyOnCmdFinish = NotifyOnCmdFinish(when='never', duration=5.0, action='notify', cmdline=(), clear_on=('focus', 'next'))
open_url_with: list[str] = ['default']
padding_fill_strategy: choices_for_padding_fill_strategy = 'background'
palette_generate: choices_for_palette_generate = 'fixed'
paste_actions: frozenset[str] = frozenset({'confirm', 'quote-urls-at-prompt'})
pixel_scroll: bool = True

View file

@ -26,6 +26,7 @@ enum {
BLIT_PROGRAM,
SCREENSHOT_PROGRAM,
ROUNDED_RECT_PROGRAM,
PADDING_PROGRAM,
NUM_PROGRAMS
};
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, SPRITE_DECORATIONS_MAP_UNIT };
@ -370,6 +371,20 @@ init_cell_program(void) {
UniformBlock border_glut = program_uniform_block(BORDERS_PROGRAM, "GammaLUT");
glUniformBlockBinding(program_id(BORDERS_PROGRAM), border_glut.index, GAMMA_LUT_BINDING_POINT);
// The padding program shares the cell background computation and so uses the
// same uniform blocks bound to the same binding points as the cell programs.
{
UniformBlock crd = program_uniform_block(PADDING_PROGRAM, "CellRenderData");
glUniformBlockBinding(program_id(PADDING_PROGRAM), crd.index, CELL_RENDER_DATA_BINDING_POINT);
UniformBlock ct = program_uniform_block(PADDING_PROGRAM, "ColorTable");
glUniformBlockBinding(program_id(PADDING_PROGRAM), ct.index, COLOR_TABLE_BINDING_POINT);
UniformBlock glut = program_uniform_block(PADDING_PROGRAM, "GammaLUT");
glUniformBlockBinding(program_id(PADDING_PROGRAM), glut.index, GAMMA_LUT_BINDING_POINT);
}
#define C(name, expected) { int aloc = attrib_location(PADDING_PROGRAM, #name); if (aloc != expected && aloc != -1) fatal("The attribute location for %s is %d != %d in the padding program", #name, aloc, expected); }
C(colors, 0); C(sprite_idx, 1); C(is_selected, 2);
#undef C
// The gamma LUT is a constant, shared amongst all the programs that use it via a single UBO.
if (shader_globals_vao_idx == -1) {
shader_globals_vao_idx = create_vao();
@ -1331,6 +1346,98 @@ draw_cells_without_layers(const UIRenderData *ui, ssize_t vao_idx) {
call_cell_program(CELL_PROGRAM, ui, vao_idx, true, DRAW_BOTH_BG);
}
static void
configure_cell_vao_attributes(ssize_t vao_idx, unsigned int base_cell, unsigned int cell_step) {
// (Re)point the instanced cell attributes so that instance i corresponds to
// the cell at (base_cell + i*cell_step). Used to draw a single padding strip
// from the shared cell VAO, and to restore the canonical layout afterwards
// (base_cell=0, cell_step=1). The VAO must be bound before calling this.
CELL_BUFFERS;
const GLsizei cell_stride = (GLsizei)(cell_step * sizeof(GPUCell));
const uintptr_t cell_base = (uintptr_t)base_cell * sizeof(GPUCell);
set_vao_attribute(vao_idx, cell_data_buffer, program_attribute_location(CELL_PROGRAM, "sprite_idx"),
2, GL_UNSIGNED_INT, cell_stride, (void*)(cell_base + offsetof(GPUCell, sprite_idx)), 1);
set_vao_attribute(vao_idx, cell_data_buffer, program_attribute_location(CELL_PROGRAM, "colors"),
3, GL_UNSIGNED_INT, cell_stride, (void*)(cell_base + offsetof(GPUCell, fg)), 1);
set_vao_attribute(vao_idx, selection_buffer, program_attribute_location(CELL_PROGRAM, "is_selected"),
1, GL_UNSIGNED_BYTE, (GLsizei)(cell_step * sizeof(GLubyte)), (void*)((uintptr_t)base_cell * sizeof(GLubyte)), 1);
}
static void
draw_padding_strip(
ssize_t vao_idx, bool for_final_output, unsigned int is_horizontal, unsigned int count,
unsigned int base_cell, unsigned int cell_step, float across0, float across1,
float along_start, float along_step, float clamp_lo, float clamp_hi
) {
if (!count) return;
configure_cell_vao_attributes(vao_idx, base_cell, cell_step);
#define L(x) program_uniform_location(PADDING_PROGRAM, #x)
glUniform1ui(L(is_horizontal), is_horizontal);
glUniform1ui(L(along_count), count);
glUniform1ui(L(base_instance), base_cell);
glUniform1ui(L(instance_step), cell_step);
glUniform2f(L(across), across0, across1);
glUniform1f(L(along_start), along_start);
glUniform1f(L(along_step), along_step);
glUniform2f(L(along_clamp), clamp_lo, clamp_hi);
#undef L
draw_quad(!for_final_output, count);
}
static void
draw_window_padding(const UIRenderData *ui, Window *window, ssize_t vao_idx, bool for_final_output) {
// Color the compensatory padding strips (the innermost slice of the window
// padding, arising from the window size not being an exact multiple of the
// cell size) to match their neighboring cell. The strips lie outside the
// per-window cell viewport, so this runs with the full framebuffer viewport.
if (!window || OPT(padding_fill_strategy) != PADDING_FILL_NEIGHBORING_CELL) return;
const unsigned int cl = window->size_mismatch_padding.left, ct = window->size_mismatch_padding.top,
cr = window->size_mismatch_padding.right, cb = window->size_mismatch_padding.bottom;
if (!(cl | ct | cr | cb)) return;
Screen *screen = ui->screen;
const unsigned int columns = screen->columns, lines = screen->lines;
if (!columns || !lines) return;
const unsigned int render_offset = pixel_scroll_enabled(screen) ? 1u : 0u;
const unsigned int top_row = render_offset, bottom_row = render_offset + lines - 1u;
const float fbw = (float)ui->full_framebuffer_width, fbh = (float)ui->full_framebuffer_height;
const float cw = (float)ui->cell_width, ch = (float)ui->cell_height;
const float L = (float)ui->screen_left, T = (float)ui->screen_top;
const float R = L + (float)ui->screen_width, B = T + (float)ui->screen_height;
#define NX(px) (2.f * (px) / fbw - 1.f)
#define NY(px) (1.f - 2.f * (px) / fbh)
const float dx = 2.f * cw / fbw, dy = 2.f * ch / fbh;
bind_program(PADDING_PROGRAM);
bind_vertex_array(vao_idx);
CELL_BUFFERS;
bind_vao_uniform_buffer(vao_idx, uniform_buffer, CELL_RENDER_DATA_BINDING_POINT);
bind_vao_uniform_buffer(vao_idx, color_table_buffer, COLOR_TABLE_BINDING_POINT);
if (for_final_output) glEnable(GL_FRAMEBUFFER_SRGB);
// Top strip: spans content width, per top-row cell. across selected by cell
// corner: top corner -> outer edge (T-ct), bottom corner -> content edge (T).
if (ct) draw_padding_strip(vao_idx, for_final_output, 1u, columns, top_row * columns, 1u,
NY(T - ct), NY(T), NX(L), dx, NX(L), NX(R));
// Bottom strip: top corner -> content edge (B), bottom corner -> outer (B+cb).
if (cb) draw_padding_strip(vao_idx, for_final_output, 1u, columns, bottom_row * columns, 1u,
NY(B), NY(B + cb), NX(L), dx, NX(L), NX(R));
// Left strip: full comp-frame height (corners via along_clamp), per left-column
// cell. left corner -> outer (L-cl), right corner -> content edge (L).
if (cl) draw_padding_strip(vao_idx, for_final_output, 0u, lines, top_row * columns, columns,
NX(L - cl), NX(L), NY(T), -dy, NY(T - ct), NY(B + cb));
// Right strip: left corner -> content edge (R), right corner -> outer (R+cr).
if (cr) draw_padding_strip(vao_idx, for_final_output, 0u, lines, top_row * columns + (columns - 1u), columns,
NX(R), NX(R + cr), NY(T), -dy, NY(T - ct), NY(B + cb));
if (for_final_output) glDisable(GL_FRAMEBUFFER_SRGB);
// Restore the canonical cell attribute layout so cell rendering is unaffected.
configure_cell_vao_attributes(vao_idx, 0u, 1u);
unbind_program();
#undef NX
#undef NY
}
static void
draw_tint(const UIRenderData *ui) {
bind_program(TINT_PROGRAM);
@ -1433,6 +1540,9 @@ draw_cells(const WindowRenderData *srd, OSWindow *os_window, bool is_active_wind
if (ui.os_window->needs_layers) draw_cells_with_layers(&ui, srd->vao_idx);
else draw_cells_without_layers(&ui, srd->vao_idx);
restore_viewport();
// The compensatory padding lies outside the per-window cell viewport, so it
// is drawn after restoring the full framebuffer viewport.
draw_window_padding(&ui, window, srd->vao_idx, !ui.os_window->needs_layers);
}
// }}}
@ -1905,6 +2015,7 @@ init_shaders(PyObject *module) {
C(CELL_PROGRAM); C(CELL_FG_PROGRAM); C(CELL_BG_PROGRAM); C(BORDERS_PROGRAM);
C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(GRAPHICS_ALPHA_MASK_PROGRAM);
C(BGIMAGE_PROGRAM); C(TINT_PROGRAM); C(TRAIL_PROGRAM); C(BLIT_PROGRAM); C(SCREENSHOT_PROGRAM); C(ROUNDED_RECT_PROGRAM);
C(PADDING_PROGRAM);
C(GLSL_VERSION);
C(GL_VERSION);
C(GL_VENDOR);

View file

@ -0,0 +1,99 @@
#language slang 2026
// Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
// Distributed under terms of the GPLv3 license.
// https://github.com/shader-slang/slang/issues/11874
// warnings-disable: 41012
import utils;
import background;
// This shader colors the compensatory window padding (the thin strips that
// appear when the window size is not an exact multiple of the cell size) so
// that each strip matches the background of its neighboring cell. It re-uses
// the cell VAO (colors/sprite_idx/is_selected instanced attributes) and the
// background module's padding_background_premul() to compute the color.
//
// It is invoked once per edge (a "strip") with the cell VAO attributes
// re-pointed so that instance i corresponds to the i'th cell along that strip.
// The geometry of the quad is computed entirely from uniforms in
// framebuffer-NDC, since the padding lies outside the per-window cell viewport
// and is therefore drawn with the full framebuffer viewport.
// Corner indicator per quad vertex, matching cell_pos_map in background.slang.
// .x: 0 -> left, 1 -> right ; .y: 0 -> top, 1 -> bottom
static const uint2 pad_pos_map[4] = {
uint2(1u, 0u), // right, top
uint2(1u, 1u), // right, bottom
uint2(0u, 1u), // left, bottom
uint2(0u, 0u) // left, top
};
struct VertexOutput {
float4 color_premul : COLOR_PREMUL;
float4 position : SV_Position;
};
[shader("vertex")]
VertexOutput vertex_main(
[[vk::location(0)]] uint3 colors,
[[vk::location(1)]] uint2 sprite_idx,
[[vk::location(2)]] uint is_selected,
uint vertex_id : SV_VertexID,
uint instance_id : SV_InstanceID,
// The true cell index (into the cell grid) of this strip cell, used only to
// reconstruct the row/column for the shared background computation. It is
// base_instance + instance_id * instance_step and matches the offset/stride
// used to re-point the VAO attributes on the C side.
uniform uint base_instance,
uniform uint instance_step,
// 1 for the top/bottom strips (the strip runs along x), 0 for left/right.
uniform uint is_horizontal,
// Number of cells along the strip (== instance count).
uniform uint along_count,
// The two NDC values of the thin (across) dimension of the strip, selected
// by the quad corner: .x for the near-content corner mapping, .y for the
// other. The C side assigns these per edge.
uniform float2 across,
// NDC of the along dimension for the first cell's near corner, and the step
// per cell (signed; negative for the vertical strips as NDC y decreases
// downwards).
uniform float along_start,
uniform float along_step,
// NDC bounds used to extend the first/last cell so the strip fills the
// corners (a no-op when set to the natural strip extent).
uniform float2 along_clamp,
) {
VertexOutput vo;
uint real_id = base_instance + instance_id * instance_step;
vo.color_premul = padding_background_premul(colors, sprite_idx, is_selected, real_id);
uint2 p = pad_pos_map[vertex_id];
float along_lo = along_start + float(instance_id) * along_step;
float along_hi = along_lo + along_step;
// Branch-free selection via lerp(). h selects the strip orientation, s is the
// corner component along the strip axis and a the component across it.
float h = float(is_horizontal);
float s = lerp(float(p.y), float(p.x), h);
float a = lerp(float(p.x), float(p.y), h);
// 1 when this is the first/last cell along the strip, else 0.
float is_first = 1.0 - min(float(instance_id), 1.0);
float is_last = 1.0 - min(float(along_count - 1u - instance_id), 1.0);
float along = lerp(along_lo, along_hi, s);
// Extend the first cell's near corner and the last cell's far corner out to
// along_clamp so the strip fills the corners.
along = lerp(along, along_clamp.x, is_first * (1.0 - s));
along = lerp(along, along_clamp.y, is_last * s);
float across_v = lerp(across.x, across.y, a);
// Horizontal strip -> position is (along, across); vertical -> (across, along).
vo.position = float4(lerp(across_v, along, h), lerp(along, across_v, h), 0.0, 1.0);
return vo;
}
[shader("fragment")]
float4 fragment_main(float4 color_premul : COLOR_PREMUL) : SV_Target {
return color_premul;
}

View file

@ -41,6 +41,7 @@ from kitty.fast_data_types import (
GRAPHICS_PROGRAM,
MARK,
MARK_MASK,
PADDING_PROGRAM,
REVERSE,
ROUNDED_RECT_PROGRAM,
SCREENSHOT_PROGRAM,
@ -211,6 +212,7 @@ class LoadShaderPrograms:
'screenshot': SCREENSHOT_PROGRAM,
'rounded_rect': ROUNDED_RECT_PROGRAM,
'border': BORDERS_PROGRAM,
'padding': PADDING_PROGRAM,
}.items():
vert, frag = glsl_shaders(name)
compile_program(prog, (vert,), (frag,), metadata[name], allow_recompile)

View file

@ -1183,12 +1183,16 @@ PYWRAP1(set_window_render_data) {
id_type os_window_id, tab_id, window_id;
WindowGeometry g = {0};
Screen *screen;
PA("KKKOIIIIIIII", &os_window_id, &tab_id, &window_id, &screen,
unsigned int cl, ct, cr, cb;
PA("KKKOIIIIIIIIIIII", &os_window_id, &tab_id, &window_id, &screen,
B(left), B(top), B(right), B(bottom),
S(left), S(top), S(right), S(bottom));
S(left), S(top), S(right), S(bottom),
&cl, &ct, &cr, &cb);
WITH_WINDOW(os_window_id, tab_id, window_id);
init_window_render_data(&window->render_data, g, screen);
window->size_mismatch_padding.left = cl; window->size_mismatch_padding.top = ct;
window->size_mismatch_padding.right = cr; window->size_mismatch_padding.bottom = cb;
END_WITH_WINDOW;
Py_RETURN_NONE;
#undef B

View file

@ -103,6 +103,7 @@ typedef struct Options {
unsigned generation;
} background_images;
BackgroundImageLayout background_image_layout;
PaddingFillStrategy padding_fill_strategy;
ImageAnchorPosition window_logo_position;
bool background_image_linear;
float background_tint, background_tint_gaps, window_logo_alpha;
@ -271,6 +272,14 @@ typedef struct Window {
struct {
unsigned int left, top, right, bottom;
} padding;
// Compensatory padding arising from the window size not being an exact
// multiple of the cell size. This is the innermost slice of the padding,
// adjacent to the cells, and (when padding_fill_strategy is
// neighboring_cell) is colored to match the neighboring cell by the padding
// shader rather than being drawn in the background color.
struct {
unsigned int left, top, right, bottom;
} size_mismatch_padding;
ClickQueue click_queues[8];
monotonic_t last_drag_scroll_at;
uint32_t last_special_key_pressed;

View file

@ -54,6 +54,11 @@ class WindowGeometry(NamedTuple):
xnum: int
ynum: int
spaces: Edges = Edges()
# The part of spaces that comes from the window size not being an exact
# multiple of the cell size (as opposed to intentional margin/border/padding
# decoration). This is the innermost slice of the padding, adjacent to the
# cells, see layout_dimension().
compensatory: Edges = Edges()
class SignalInfo(NamedTuple):

View file

@ -1060,7 +1060,8 @@ class Window:
# Set C-side render data with adjusted top/bottom for content area
set_window_render_data(self.os_window_id, self.tab_id, self.id, self.screen,
g.left, render_top, g.right, render_bottom,
g.spaces.left, g.spaces.top, g.spaces.right, g.spaces.bottom)
g.spaces.left, g.spaces.top, g.spaces.right, g.spaces.bottom,
g.compensatory.left, g.compensatory.top, g.compensatory.right, g.compensatory.bottom)
self.update_effective_padding()
# Handle title bar screen
@ -1930,9 +1931,6 @@ class Window:
def destroy(self) -> None:
self.call_watchers(self.watchers.on_close, {})
self.destroyed = True
if self.clear_progress_timer:
remove_timer(self.clear_progress_timer)
self.clear_progress_timer = 0
self.clipboard_request_manager.close()
del self.kitten_result_processors
if hasattr(self, 'screen'):

View file

@ -29,7 +29,12 @@ make debug
Execute the following two commands to fix any formatting issues in your code:
```
ruff check --fix
gofmt -s -l -w tools kittens
git ls-files '*.go' | xargs gofmt -w -s -l
```
Run the following command to type check python files:
```
./test.py type-check
```
### 🧪 Test Commands