mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-08-04 14:46:03 +00:00
A new option to color compensatory padding with neighboring cell colors
This commit is contained in:
parent
37474b478e
commit
90df23db0c
19 changed files with 368 additions and 18 deletions
|
|
@ -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]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
26
kitty/gl.c
26
kitty/gl.c
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
8
kitty/options/parse.py
generated
8
kitty/options/parse.py
generated
|
|
@ -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:
|
||||
|
|
|
|||
15
kitty/options/to-c-generated.h
generated
15
kitty/options/to-c-generated.h
generated
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
3
kitty/options/types.py
generated
3
kitty/options/types.py
generated
|
|
@ -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
|
||||
|
|
|
|||
111
kitty/shaders.c
111
kitty/shaders.c
|
|
@ -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);
|
||||
|
|
|
|||
99
kitty/shaders/padding.slang
Normal file
99
kitty/shaders/padding.slang
Normal 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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue