Skip to content

Commit 4de03aa

Browse files
committed
Try to reduce duplication from 1d0cf32
1 parent 677e527 commit 4de03aa

1 file changed

Lines changed: 55 additions & 64 deletions

File tree

addons/addon-image/src/ImageRenderer.ts

Lines changed: 55 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,9 @@ const PLACEHOLDER_HEIGHT = 24;
1818
* - draw image tiles onRender
1919
*/
2020
export class ImageRenderer extends Disposable implements IDisposable {
21-
/** @deprecated Use canvasTop instead. Kept for backward compat — points to canvasTop. */
22-
public get canvas(): HTMLCanvasElement | undefined { return this._canvasTop; }
23-
private _canvasTop: HTMLCanvasElement | undefined;
24-
private _canvasBottom: HTMLCanvasElement | undefined;
25-
private _ctxTop: CanvasRenderingContext2D | null | undefined;
26-
private _ctxBottom: CanvasRenderingContext2D | null | undefined;
21+
/** @deprecated Kept for backward compat — points to top layer canvas. */
22+
public get canvas(): HTMLCanvasElement | undefined { return this._layers.get('top')?.canvas; }
23+
private _layers = new Map<ImageLayer, CanvasRenderingContext2D>();
2724
private _placeholder: HTMLCanvasElement | undefined;
2825
private _placeholderBitmap: ImageBitmap | undefined;
2926
private _optionsRefresh = this._register(new MutableDisposable());
@@ -100,10 +97,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
10097
this._oldSetRenderer = undefined;
10198
}
10299
this._renderService = undefined;
103-
this._canvasTop = undefined;
104-
this._canvasBottom = undefined;
105-
this._ctxTop = undefined;
106-
this._ctxBottom = undefined;
100+
this._layers.clear();
107101
this._placeholderBitmap?.close();
108102
this._placeholderBitmap = undefined;
109103
this._placeholder = undefined;
@@ -152,10 +146,10 @@ export class ImageRenderer extends Disposable implements IDisposable {
152146
const w = this.dimensions?.css.canvas.width || 0;
153147
const h = (++end - start) * (this.dimensions?.css.cell.height || 0);
154148
if (!layer || layer === 'top') {
155-
this._ctxTop?.clearRect(0, y, w, h);
149+
this._layers.get('top')?.clearRect(0, y, w, h);
156150
}
157151
if (!layer || layer === 'bottom') {
158-
this._ctxBottom?.clearRect(0, y, w, h);
152+
this._layers.get('bottom')?.clearRect(0, y, w, h);
159153
}
160154
}
161155

@@ -164,18 +158,20 @@ export class ImageRenderer extends Disposable implements IDisposable {
164158
*/
165159
public clearAll(layer?: ImageLayer): void {
166160
if (!layer || layer === 'top') {
167-
this._ctxTop?.clearRect(0, 0, this._canvasTop?.width || 0, this._canvasTop?.height || 0);
161+
const ctx = this._layers.get('top');
162+
ctx?.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
168163
}
169164
if (!layer || layer === 'bottom') {
170-
this._ctxBottom?.clearRect(0, 0, this._canvasBottom?.width || 0, this._canvasBottom?.height || 0);
165+
const ctx = this._layers.get('bottom');
166+
ctx?.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
171167
}
172168
}
173169

174170
/**
175171
* Draw neighboring tiles on the image layer canvas.
176172
*/
177173
public draw(imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number = 1): void {
178-
const ctx = imgSpec.layer === 'bottom' ? this._ctxBottom : this._ctxTop;
174+
const ctx = this._layers.get(imgSpec.layer);
179175
if (!ctx) {
180176
return;
181177
}
@@ -243,7 +239,8 @@ export class ImageRenderer extends Disposable implements IDisposable {
243239
* Draw a line with placeholder on the image layer canvas.
244240
*/
245241
public drawPlaceholder(col: number, row: number, count: number = 1): void {
246-
if (this._ctxTop) {
242+
const ctx = this._layers.get('top');
243+
if (ctx) {
247244
const { width, height } = this.cellSize;
248245

249246
// Don't try to draw anything, if we cannot get valid renderer metrics.
@@ -257,7 +254,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
257254
this._createPlaceHolder(height + 1);
258255
}
259256
if (!this._placeholder) return;
260-
this._ctxTop.drawImage(
257+
ctx.drawImage(
261258
this._placeholderBitmap ?? this._placeholder!,
262259
col * width,
263260
(row * height) % 2 ? 0 : 1, // needs %2 offset correction
@@ -278,13 +275,11 @@ export class ImageRenderer extends Disposable implements IDisposable {
278275
public rescaleCanvas(): void {
279276
const w = this.dimensions?.css.canvas.width || 0;
280277
const h = this.dimensions?.css.canvas.height || 0;
281-
if (this._canvasTop && (this._canvasTop.width !== w || this._canvasTop.height !== h)) {
282-
this._canvasTop.width = w;
283-
this._canvasTop.height = h;
284-
}
285-
if (this._canvasBottom && (this._canvasBottom.width !== w || this._canvasBottom.height !== h)) {
286-
this._canvasBottom.width = w;
287-
this._canvasBottom.height = h;
278+
for (const ctx of this._layers.values()) {
279+
if (ctx.canvas.width !== w || ctx.canvas.height !== h) {
280+
ctx.canvas.width = w;
281+
ctx.canvas.height = h;
282+
}
288283
}
289284
}
290285

@@ -323,61 +318,57 @@ export class ImageRenderer extends Disposable implements IDisposable {
323318
this._renderService = this._terminal._core._renderService;
324319
this._oldSetRenderer = this._renderService.setRenderer.bind(this._renderService);
325320
this._renderService.setRenderer = (renderer: any) => {
326-
this.removeLayerFromDom();
327-
this.removeLayerFromDom('bottom');
321+
for (const key of [...this._layers.keys()]) {
322+
this.removeLayerFromDom(key);
323+
}
328324
this._oldSetRenderer?.call(this._renderService, renderer);
329325
};
330326
}
331327

332328
public insertLayerToDom(layer: ImageLayer = 'top'): void {
333329
// make sure that the terminal is attached to a document and to DOM
334-
if (this.document && this._terminal._core.screenElement) {
335-
if (layer === 'top' && !this._canvasTop) {
336-
this._canvasTop = ImageRenderer.createCanvas(
337-
this.document, this.dimensions?.css.canvas.width || 0,
338-
this.dimensions?.css.canvas.height || 0
339-
);
340-
this._canvasTop.classList.add('xterm-image-layer-top');
341-
this._terminal._core.screenElement.appendChild(this._canvasTop);
342-
this._ctxTop = this._canvasTop.getContext('2d', { alpha: true, desynchronized: true });
343-
this.clearAll('top');
344-
}
345-
if (layer === 'bottom' && !this._canvasBottom) {
346-
this._canvasBottom = ImageRenderer.createCanvas(
347-
this.document, this.dimensions?.css.canvas.width || 0,
348-
this.dimensions?.css.canvas.height || 0
349-
);
350-
this._canvasBottom.classList.add('xterm-image-layer-bottom');
351-
// Use z-index:-1 so it paints behind non-positioned text elements.
352-
// The screen element needs to be a stacking context to contain the
353-
// negative z-index, otherwise it would go behind the entire terminal.
354-
this._canvasBottom.style.zIndex = '-1';
355-
const screenElement = this._terminal._core.screenElement;
356-
screenElement.style.zIndex = '0';
357-
screenElement.insertBefore(this._canvasBottom, screenElement.firstChild);
358-
this._ctxBottom = this._canvasBottom.getContext('2d', { alpha: true, desynchronized: true });
359-
this.clearAll('bottom');
360-
}
361-
} else {
330+
if (!this.document || !this._terminal._core.screenElement) {
362331
console.warn('image addon: cannot insert output canvas to DOM, missing document or screenElement');
332+
return;
363333
}
334+
if (this._layers.has(layer)) {
335+
return;
336+
}
337+
const canvas = ImageRenderer.createCanvas(
338+
this.document, this.dimensions?.css.canvas.width || 0,
339+
this.dimensions?.css.canvas.height || 0
340+
);
341+
canvas.classList.add(`xterm-image-layer-${layer}`);
342+
const screenElement = this._terminal._core.screenElement;
343+
if (layer === 'bottom') {
344+
// Use z-index:-1 so it paints behind non-positioned text elements.
345+
// The screen element needs to be a stacking context to contain the
346+
// negative z-index, otherwise it would go behind the entire terminal.
347+
canvas.style.zIndex = '-1';
348+
screenElement.style.zIndex = '0';
349+
screenElement.insertBefore(canvas, screenElement.firstChild);
350+
} else {
351+
screenElement.appendChild(canvas);
352+
}
353+
const ctx = canvas.getContext('2d', { alpha: true, desynchronized: true });
354+
if (!ctx) {
355+
canvas.remove();
356+
return;
357+
}
358+
this._layers.set(layer, ctx);
359+
this.clearAll(layer);
364360
}
365361

366362
public removeLayerFromDom(layer: ImageLayer = 'top'): void {
367-
if (layer === 'top' && this._canvasTop) {
368-
this._ctxTop = undefined;
369-
this._canvasTop.remove();
370-
this._canvasTop = undefined;
371-
}
372-
if (layer === 'bottom' && this._canvasBottom) {
373-
this._ctxBottom = undefined;
374-
this._canvasBottom.remove();
375-
this._canvasBottom = undefined;
363+
const ctx = this._layers.get(layer);
364+
if (ctx) {
365+
ctx.canvas.remove();
366+
this._layers.delete(layer);
376367
}
377368
}
378369

379370
public hasLayer(layer: ImageLayer): boolean {
380-
return layer === 'top' ? !!this._canvasTop : !!this._canvasBottom;
371+
return this._layers.has(layer);
381372
}
382373

383374
private _createPlaceHolder(height: number = PLACEHOLDER_HEIGHT): void {

0 commit comments

Comments
 (0)