image: keep Kitty placements intact when a floating pane is dragged

Dragging a floating pane over a Kitty image left two kinds of damage.

A redraw marked every placement touching its area as stale and deleted it
whole, but only replaced the part inside the area, so the rest of the
image vanished and left holes behind the moved pane. kitty_redraw_start()
now places the parts outside the area again before the old placement is
deleted.

redraw_draw_damage_rect() only cleared placements in cells owned by a
pane, so a placement where the floating pane used to be, on cells now
belonging to its border, was never deleted and stayed drawn over it.
Clear placements under every span type except the status line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Michael Grant
2026-09-23 18:37:20 +01:00
parent 3cdb7a9216
commit 386799eef8
2 changed files with 87 additions and 8 deletions

View File

@@ -131,6 +131,9 @@ struct kitty_placement_cache {
u_int y;
u_int width;
u_int height;
u_int source_x;
u_int source_y;
int32_t z;
int pending_delete;
struct kitty_placement_cache *next;
};
@@ -178,6 +181,61 @@ kitty_free_placements(struct kitty_image_cache *entry)
entry->placements = NULL;
}
static void kitty_place(struct tty *, struct kitty_image_cache *,
struct image *, u_int, u_int, u_int, u_int, u_int, u_int,
int32_t);
/* Place one piece of an existing placement again as a new placement. */
static void
kitty_redraw_keep_piece(struct tty *tty, struct kitty_image_cache *entry,
struct image *im, struct kitty_placement_cache *placement, u_int x,
u_int y, u_int width, u_int height)
{
if (width == 0 || height == 0)
return;
kitty_place(tty, entry, im, placement->source_x + (x - placement->x),
placement->source_y + (y - placement->y), width, height, x, y,
placement->z);
}
/*
* Place the parts of a placement outside a redraw area again, since the
* redraw will not replace them.
*/
static void
kitty_redraw_keep(struct tty *tty, struct kitty_image_cache *entry,
struct kitty_placement_cache *placement, u_int x, u_int y, u_int width,
u_int height)
{
struct image *im;
u_int px0, px1, py0, py1, ix0, ix1, iy0, iy1;
px0 = placement->x;
px1 = placement->x + placement->width;
py0 = placement->y;
py1 = placement->y + placement->height;
ix0 = (x > px0 ? x : px0);
ix1 = (x + width < px1 ? x + width : px1);
iy0 = (y > py0 ? y : py0);
iy1 = (y + height < py1 ? y + height : py1);
if (ix0 == px0 && ix1 == px1 && iy0 == py0 && iy1 == py1)
return;
im = image_find(entry->server_id);
if (im == NULL)
return;
/* Callers have checked the areas intersect, so ix0 < ix1, iy0 < iy1. */
kitty_redraw_keep_piece(tty, entry, im, placement, px0, py0,
px1 - px0, iy0 - py0);
kitty_redraw_keep_piece(tty, entry, im, placement, px0, iy1,
px1 - px0, py1 - iy1);
kitty_redraw_keep_piece(tty, entry, im, placement, px0, iy0,
ix0 - px0, iy1 - iy0);
kitty_redraw_keep_piece(tty, entry, im, placement, ix1, iy0,
px1 - ix1, iy1 - iy0);
}
/*
* Mark Kitty placements intersecting a redraw area as stale, without
* deleting them yet - see kitty_redraw_finish(). Deleting immediately here
@@ -186,6 +244,10 @@ kitty_free_placements(struct kitty_image_cache *entry)
* implementations free an image's pixel data once it has no placements
* left, which would leave the replacement referencing already-discarded
* data and render as nothing.
*
* A placement only partly inside the area is deleted as a whole, but the
* redraw only replaces the part inside, so the parts outside are placed
* again first - otherwise they would vanish from cells nothing redraws.
*/
void
kitty_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
@@ -200,12 +262,16 @@ kitty_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
for (entry = ko->images; entry != NULL; entry = entry->next) {
for (placement = entry->placements; placement != NULL;
placement = placement->next) {
if (placement->pending_delete)
continue;
if (placement->x >= x + width ||
placement->x + placement->width <= x ||
placement->y >= y + height ||
placement->y + placement->height <= y)
continue;
placement->pending_delete = 1;
kitty_redraw_keep(tty, entry, placement, x, y, width,
height);
}
}
}
@@ -330,6 +396,9 @@ kitty_place(struct tty *tty, struct kitty_image_cache *entry,
placement->y = destination_y;
placement->width = width;
placement->height = height;
placement->source_x = source_x;
placement->source_y = source_y;
placement->z = z;
placement->next = entry->placements;
entry->placements = placement;
tty_cursor(tty, destination_x, destination_y);

View File

@@ -2488,6 +2488,11 @@ redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
* path) leaves every previous placement behind, all still visible
* and now overlapping the newly placed ones. Skipped when trusting a
* scroll to have moved the image itself - nothing is being replaced.
*
* Every span type is included, not just panes: when a floating pane
* moves, cells where its image was placed can now belong to a border
* (or anything else), and a Kitty placement left there would stay
* drawn over it.
*/
if (!skip_images) {
for (yy = y; yy < y + sy; yy++) {
@@ -2496,15 +2501,20 @@ redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y,
cy = dctx->status_lines + yy;
else
cy = yy;
spans = &line->spans[REDRAW_SPAN_PANE];
TAILQ_FOREACH(span, spans, entry) {
clip_x = (span->x > x) ? span->x : x;
clip_end = (span->x + span->width < x + sx) ?
span->x + span->width : x + sx;
if (clip_end <= clip_x)
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
if (type == REDRAW_SPAN_STATUS)
continue;
image_redraw_start(&scene->c->tty, clip_x, cy,
clip_end - clip_x, 1);
spans = &line->spans[type];
TAILQ_FOREACH(span, spans, entry) {
clip_x = (span->x > x) ? span->x : x;
clip_end = (span->x + span->width <
x + sx) ? span->x + span->width :
x + sx;
if (clip_end <= clip_x)
continue;
image_redraw_start(&scene->c->tty,
clip_x, cy, clip_end - clip_x, 1);
}
}
}
}