Skip to content

Commit 677e527

Browse files
committed
Store z-index for drawing order
1 parent d4c3b69 commit 677e527

4 files changed

Lines changed: 33 additions & 7 deletions

File tree

addons/addon-image/src/ImageStorage.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export class ImageStorage implements IDisposable {
250250
* Method to add an image to the storage.
251251
* Returns the internal image ID assigned to the stored image.
252252
*/
253-
public addImage(img: HTMLCanvasElement | ImageBitmap, layer: ImageLayer = 'top'): number {
253+
public addImage(img: HTMLCanvasElement | ImageBitmap, layer: ImageLayer = 'top', zIndex: number = 0): number {
254254
// never allow storage to exceed memory limit
255255
this._evictOldest(img.width * img.height);
256256

@@ -340,7 +340,8 @@ export class ImageStorage implements IDisposable {
340340
marker: endMarker || undefined,
341341
tileCount,
342342
bufferType: this._terminal.buffer.active.type,
343-
layer
343+
layer,
344+
zIndex
344345
};
345346

346347
// finally add the image
@@ -419,7 +420,11 @@ export class ImageStorage implements IDisposable {
419420
// clear drawing area
420421
this._renderer.clearLines(start, end);
421422

422-
// walk all cells in viewport and draw tiles found
423+
// Collect draw calls so we can sort by z-index (lower z drawn first).
424+
const drawCalls: { imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number }[] = [];
425+
const placeholderCalls: { col: number, row: number, count: number }[] = [];
426+
427+
// walk all cells in viewport and collect tiles found
423428
for (let row = start; row <= end; ++row) {
424429
const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt;
425430
if (!line) return;
@@ -453,16 +458,29 @@ export class ImageStorage implements IDisposable {
453458
col--;
454459
if (imgSpec) {
455460
if (imgSpec.actual) {
456-
this._renderer.draw(imgSpec, startTile, startCol, row, count);
461+
drawCalls.push({ imgSpec, tileId: startTile, col: startCol, row, count });
457462
}
458463
} else if (this._opts.showPlaceholder) {
459-
this._renderer.drawPlaceholder(startCol, row, count);
464+
placeholderCalls.push({ col: startCol, row, count });
460465
}
461466
this._fullyCleared = false;
462467
}
463468
}
464469
}
465470
}
471+
472+
// Sort by z-index so lower z draws first (higher z renders on top)
473+
drawCalls.sort((a, b) => a.imgSpec.zIndex - b.imgSpec.zIndex);
474+
475+
// Draw placeholders first (lowest priority)
476+
for (const call of placeholderCalls) {
477+
this._renderer.drawPlaceholder(call.col, call.row, call.count);
478+
}
479+
480+
// Draw images in z-index order
481+
for (const call of drawCalls) {
482+
this._renderer.draw(call.imgSpec, call.tileId, call.col, call.row, call.count);
483+
}
466484
}
467485

468486
public viewportResize(metrics: { cols: number, rows: number }): void {

addons/addon-image/src/Types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,5 @@ export interface IImageSpec {
110110
tileCount: number;
111111
bufferType: 'alternate' | 'normal';
112112
layer: ImageLayer;
113+
zIndex: number;
113114
}

addons/addon-image/src/kitty/KittyGraphicsHandler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,12 +510,13 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler {
510510
const wantsBottom = cmd.zIndex !== undefined && cmd.zIndex < 0;
511511
const layer: ImageLayer = (wantsBottom && this._coreTerminal.options.allowTransparency) ? 'bottom' : 'top';
512512

513+
const zIndex = cmd.zIndex ?? 0;
513514
let storageId: number;
514515
if (w !== bitmap.width || h !== bitmap.height) {
515516
const resized = await createImageBitmap(bitmap, { resizeWidth: w, resizeHeight: h });
516-
storageId = this._storage.addImage(resized, layer);
517+
storageId = this._storage.addImage(resized, layer, zIndex);
517518
} else {
518-
storageId = this._storage.addImage(bitmap, layer);
519+
storageId = this._storage.addImage(bitmap, layer, zIndex);
519520
}
520521
this._kittyIdToStorageId.set(image.id, storageId);
521522

addons/addon-image/test/KittyGraphics.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,20 +552,23 @@ test.describe('Kitty Graphics Protocol', () => {
552552
await timeout(100);
553553
strictEqual(await getImageStorageLength(), 1);
554554
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');
555+
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), 0);
555556
});
556557

557558
test('z=0 stores image on top layer', async () => {
558559
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=0;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
559560
await timeout(100);
560561
strictEqual(await getImageStorageLength(), 1);
561562
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');
563+
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), 0);
562564
});
563565

564566
test('z=1 (positive) stores image on top layer', async () => {
565567
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=1;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
566568
await timeout(100);
567569
strictEqual(await getImageStorageLength(), 1);
568570
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');
571+
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), 1);
569572
});
570573

571574
test('z=-1 falls back to top layer when allowTransparency is disabled', async () => {
@@ -574,6 +577,7 @@ test.describe('Kitty Graphics Protocol', () => {
574577
await timeout(100);
575578
strictEqual(await getImageStorageLength(), 1);
576579
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');
580+
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), -1);
577581
});
578582

579583
test('z=-1 (negative) stores image on bottom layer when allowTransparency is enabled', async () => {
@@ -582,6 +586,7 @@ test.describe('Kitty Graphics Protocol', () => {
582586
await timeout(100);
583587
strictEqual(await getImageStorageLength(), 1);
584588
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'bottom');
589+
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), -1);
585590
});
586591

587592
test('z=-100 (large negative) stores image on bottom layer when allowTransparency is enabled', async () => {
@@ -590,6 +595,7 @@ test.describe('Kitty Graphics Protocol', () => {
590595
await timeout(100);
591596
strictEqual(await getImageStorageLength(), 1);
592597
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'bottom');
598+
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).zIndex`), -100);
593599
});
594600

595601
test('top layer canvas has correct CSS class', async () => {

0 commit comments

Comments
 (0)