From 386799eef80c064775e68249f2f6a964682b7fdb Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 23 Sep 2026 18:37:20 +0100 Subject: [PATCH] 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 --- image-kitty.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ screen-redraw.c | 26 +++++++++++++------ 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/image-kitty.c b/image-kitty.c index af3133804..5c91cb7a5 100644 --- a/image-kitty.c +++ b/image-kitty.c @@ -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); diff --git a/screen-redraw.c b/screen-redraw.c index 0a6419137..53639c713 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -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); + } } } }