diff --git a/addons/xterm-addon-canvas/README.md b/addons/xterm-addon-canvas/README.md index ed65967d..bffac96e 100644 --- a/addons/xterm-addon-canvas/README.md +++ b/addons/xterm-addon-canvas/README.md @@ -2,6 +2,7 @@ An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a canvas-based renderer using a 2d context to draw. This addon requires xterm.js v5+. +The purpose of this addon is to be used as a fallback for the [webgl addon](https://www.npmjs.com/package/xterm-addon-webgl) when better performance is desired over the default DOM renderer, but WebGL2 isn't supported or performant for some reason. ### Install @@ -21,3 +22,7 @@ terminal.loadAddon(new CanvasAddon()); ``` See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts) for more advanced usage. + +### See also + +- [xterm-addon-webgl](https://www.npmjs.com/package/xterm-addon-webgl) A renderer for xterm.js that uses WebGL diff --git a/addons/xterm-addon-webgl/README.md b/addons/xterm-addon-webgl/README.md index 2519fb7b..a431170f 100644 --- a/addons/xterm-addon-webgl/README.md +++ b/addons/xterm-addon-webgl/README.md @@ -36,3 +36,7 @@ terminal.loadAddon(addon); ``` Read more about handling WebGL context losses on the [Khronos wiki](https://www.khronos.org/webgl/wiki/HandlingContextLost). + +### See also + +- [xterm-addon-canvas](https://www.npmjs.com/package/xterm-addon-canvas) A renderer for xterm.js that uses a 2d canvas that can be used as a fallback when WebGL is not available diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 6eff4ced..6817cb21 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -203,10 +203,11 @@ export class GlyphRenderer extends Disposable { return; } - if (bg !== lastBg && rasterizedGlyph.offset.x > 0) { - const clippedPixels = rasterizedGlyph.offset.x; + const leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2); + if (bg !== lastBg && rasterizedGlyph.offset.x > leftCellPadding) { + const clippedPixels = rasterizedGlyph.offset.x - leftCellPadding; // a_origin - array[i ] = this._dimensions.scaledCharLeft; + array[i ] = -(rasterizedGlyph.offset.x - clippedPixels) + this._dimensions.scaledCharLeft; array[i + 1] = -rasterizedGlyph.offset.y + this._dimensions.scaledCharTop; // a_size array[i + 2] = (rasterizedGlyph.size.x - clippedPixels) / this._dimensions.scaledCanvasWidth; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index bc08ad4a..f16fdec0 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -12,6 +12,7 @@ import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable, toDisposable } from 'common/Lifecycle'; +import { DIM_OPACITY } from 'browser/renderer/Constants'; const enum VertexAttribLocations { POSITION = 0, @@ -208,6 +209,7 @@ export class RectangleRenderer extends Disposable { private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { let rgba: number | undefined; + let isDefault = false; if (fg & FgFlags.INVERSE) { switch (fg & Attributes.CM_MASK) { case Attributes.CM_P16: @@ -233,6 +235,7 @@ export class RectangleRenderer extends Disposable { case Attributes.CM_DEFAULT: default: rgba = this._colors.background.rgba; + isDefault = true; } } @@ -244,7 +247,7 @@ export class RectangleRenderer extends Disposable { const r = ((rgba >> 24) & 0xFF) / 255; const g = ((rgba >> 16) & 0xFF) / 255; const b = ((rgba >> 8 ) & 0xFF) / 255; - const a = bg & BgFlags.DIM ? 0.5 : 1; + const a = (!isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1; this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, a); } diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 02ab942e..5b98a048 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -7,13 +7,16 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { isSafari } from 'common/Platform'; import { ICoreService, IDecorationService } from 'common/services/Services'; export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; + + private _onChangeTextureAtlas = new EventEmitter(); + public get onChangeTextureAtlas(): IEvent { return this._onChangeTextureAtlas.event; } private _onContextLoss = new EventEmitter(); public get onContextLoss(): IEvent { return this._onContextLoss.event; } @@ -36,7 +39,8 @@ export class WebglAddon implements ITerminalAddon { const decorationService: IDecorationService = (terminal as any)._core._decorationService; const colors: IColorSet = (terminal as any)._core._colorManager.colors; this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer); - this._renderer.onContextLoss(() => this._onContextLoss.fire()); + forwardEvent(this._renderer.onContextLoss, this._onContextLoss); + forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); renderService.setRenderer(this._renderer); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d2d4f478..9e90df02 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -25,7 +25,6 @@ import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/S import { CharData, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { ICoreService, IDecorationService } from 'common/services/Services'; -import { color, rgba as rgbaNs } from 'common/Color'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -46,6 +45,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _core: ITerminal; private _isAttached: boolean; + private _onChangeTextureAtlas = new EventEmitter(); + public get onChangeTextureAtlas(): IEvent { return this._onChangeTextureAtlas.event; } private _onRequestRedraw = new EventEmitter(); public get onRequestRedraw(): IEvent { return this._onRequestRedraw.event; } @@ -240,7 +241,10 @@ export class WebglRenderer extends Disposable implements IRenderer { if (!('getRasterizedGlyph' in atlas)) { throw new Error('The webgl renderer only works with the webgl char atlas'); } - this._charAtlas = atlas as WebglCharAtlas; + if (this._charAtlas !== atlas) { + this._onChangeTextureAtlas.fire(atlas.cacheCanvas); + } + this._charAtlas = atlas; this._charAtlas.warmUp(); this._glyphRenderer.setAtlas(this._charAtlas); } @@ -402,6 +406,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // override logic throughout the different sub-renderers let bgOverride: number | undefined; let fgOverride: number | undefined; + let isSelected: boolean = false; // Apply decorations on the bottom layer for (const d of this._decorationService.getDecorationsAtCell(x, y, 'bottom')) { @@ -414,7 +419,8 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Apply the selection color if needed - if (this._isCellSelected(x, y)) { + isSelected = this._isCellSelected(x, y); + if (isSelected) { bgOverride = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; if (this._colors.selectionForeground) { fgOverride = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; @@ -434,8 +440,13 @@ export class WebglRenderer extends Disposable implements IRenderer { // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag // ahead of time in order to use the correct cache key if (bgOverride !== undefined) { - // Non-RGB attributes from model + override + force RGB color mode - bgOverride = (this._workCell.bg & ~Attributes.RGB_MASK) | bgOverride | Attributes.CM_RGB; + if (isSelected) { + // Non-RGB attributes from model + force non-dim + override + force RGB color mode + bgOverride = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | bgOverride | Attributes.CM_RGB; + } else { + // Non-RGB attributes from model + override + force RGB color mode + bgOverride = (this._workCell.bg & ~Attributes.RGB_MASK) | bgOverride | Attributes.CM_RGB; + } } if (fgOverride !== undefined) { // Non-RGB attributes from model + force disable inverse + override + force RGB color mode diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index e5f0fe6c..13dceae6 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -45,6 +45,12 @@ const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = { const TMP_CANVAS_GLYPH_PADDING = 2; +interface ICharAtlasActiveRow { + x: number; + y: number; + height: number; +} + export class WebglCharAtlas implements IDisposable { private _didWarmUp: boolean = false; @@ -59,13 +65,22 @@ export class WebglCharAtlas implements IDisposable { // A temporary context that glyphs are drawn to before being transfered to the atlas. private _tmpCtx: CanvasRenderingContext2D; - // Since glyphs are expected to be around the same height, the packing - // strategy used it to fill a row with glyphs while keeping track of the - // tallest glyph in the row. Once the row is full a new row is started at - // (0,lastRow+lastRowTallestGlyph). - private _currentRowY: number = 0; - private _currentRowX: number = 0; - private _currentRowHeight: number = 0; + // Texture atlas current positioning data. The texture packing strategy used is to fill from + // left-to-right and top-to-bottom. When the glyph being written is less than half of the current + // row's height, the following happens: + // + // - The current row becomes the fixed height row A + // - A new fixed height row B the exact size of the glyph is created below the current row + // - A new dynamic height current row is created below B + // + // This strategy does a good job preventing space being wasted for very short glyphs such as + // underscores, hyphens etc. or those with underlines rendered. + private _currentRow: ICharAtlasActiveRow = { + x: 0, + y: 0, + height: 0 + }; + private readonly _fixedRows: ICharAtlasActiveRow[] = []; public hasCanvasChanged = false; @@ -118,7 +133,7 @@ export class WebglCharAtlas implements IDisposable { } public beginFrame(): boolean { - if (this._currentRowY > TEXTURE_CAPACITY) { + if (this._currentRow.y > TEXTURE_CAPACITY) { this.clearTexture(); this.warmUp(); return true; @@ -127,15 +142,16 @@ export class WebglCharAtlas implements IDisposable { } public clearTexture(): void { - if (this._currentRowX === 0 && this._currentRowY === 0) { + if (this._currentRow.x === 0 && this._currentRow.y === 0) { return; } this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); this._cacheMap = {}; this._cacheMapCombined = {}; - this._currentRowHeight = 0; - this._currentRowX = 0; - this._currentRowY = 0; + this._currentRow.x = 0; + this._currentRow.y = 0; + this._currentRow.height = 0; + this._fixedRows.length = 0; this._didWarmUp = false; } @@ -225,7 +241,7 @@ export class WebglCharAtlas implements IDisposable { if (dim) { // Blend here instead of using opacity because transparent colors mess with clipping the // glyph's bounding box - result = color.blend(this._config.colors.background, color.multiplyOpacity(result, 0.5)); + result = color.blend(this._config.colors.background, color.multiplyOpacity(result, DIM_OPACITY)); } return result; @@ -267,7 +283,7 @@ export class WebglCharAtlas implements IDisposable { // Apply dim to the color, opacity is fine to use for the foreground color if (dim) { - result = color.multiplyOpacity(result, 0.5); + result = color.multiplyOpacity(result, DIM_OPACITY); } return result; @@ -401,29 +417,31 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = TEXT_BASELINE; - const powerLineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0)); + const powerlineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0)); const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, excludeFromContrastRatioDemands(chars.charCodeAt(0))); this._tmpCtx.fillStyle = foregroundColor.css; // For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129) - const padding = powerLineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING * 2; + const padding = powerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING * 2; // Draw custom characters if applicable - let drawSuccess = false; + let customGlyph = false; if (this._config.customGlyphs !== false) { - drawSuccess = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight); + customGlyph = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight); } // Whether to clear pixels based on a threshold difference between the glyph color and the // background color. This should be disabled when the glyph contains multiple colors such as // underline colors to prevent important colors could get cleared. - let enableClearThresholdCheck = true; + let enableClearThresholdCheck = !powerlineGlyph; // Draw underline if (underline) { this._tmpCtx.save(); - const lineWidth = Math.max(1, Math.floor(this._config.fontSize * window.devicePixelRatio / 10)); - const yOffset = this._tmpCtx.lineWidth % 2 === 1 ? 0.5 : 0; // When the width is odd, draw at 0.5 position + const lineWidth = Math.max(1, Math.floor(this._config.fontSize * window.devicePixelRatio / 15)); + // When the width is odd, draw at 0.5 position. Offset by an additional 1 dpr to bring the + // underline closer to the character + const yOffset = (lineWidth % 2 === 1 ? 0.5 : 0) + window.devicePixelRatio; this._tmpCtx.lineWidth = lineWidth; // Underline color @@ -492,18 +510,18 @@ export class WebglCharAtlas implements IDisposable { break; case UnderlineStyle.DOTTED: this._tmpCtx.setLineDash([window.devicePixelRatio * 2, window.devicePixelRatio]); - this._tmpCtx.moveTo(xLeft, yMid); - this._tmpCtx.lineTo(xRight, yMid); + this._tmpCtx.moveTo(xLeft, yTop); + this._tmpCtx.lineTo(xRight, yTop); break; case UnderlineStyle.DASHED: this._tmpCtx.setLineDash([window.devicePixelRatio * 4, window.devicePixelRatio * 3]); - this._tmpCtx.moveTo(xLeft, yMid); - this._tmpCtx.lineTo(xRight, yMid); + this._tmpCtx.moveTo(xLeft, yTop); + this._tmpCtx.lineTo(xRight, yTop); break; case UnderlineStyle.SINGLE: default: - this._tmpCtx.moveTo(xLeft, yMid); - this._tmpCtx.lineTo(xRight, yMid); + this._tmpCtx.moveTo(xLeft, yTop); + this._tmpCtx.lineTo(xRight, yTop); break; } this._tmpCtx.stroke(); @@ -511,20 +529,28 @@ export class WebglCharAtlas implements IDisposable { // Draw stroke in the background color for non custom characters in order to give an outline // between the text and the underline - if (!drawSuccess) { + if (!customGlyph) { // This only works when transparency is disabled because it's not clear how to clear stroked // text if (!this._config.allowTransparency && chars !== ' ') { // This translates to 1/2 the line width in either direction + this._tmpCtx.save(); + // Clip the region to only draw in valid pixels near the underline to avoid a slight + // outline around the whole glyph, as well as additional pixels in the glyph at the top + // which would increase GPU memory demands + const clipRegion = new Path2D(); + clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.scaledCellWidth, yBot - yTop + Math.ceil(lineWidth / 2)); + this._tmpCtx.clip(clipRegion); this._tmpCtx.lineWidth = window.devicePixelRatio * 3; this._tmpCtx.strokeStyle = backgroundColor.css; this._tmpCtx.strokeText(chars, padding, padding + this._config.scaledCharHeight); + this._tmpCtx.restore(); } } } // Draw the character - if (!drawSuccess) { + if (!customGlyph) { this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight); } @@ -577,25 +603,69 @@ export class WebglCharAtlas implements IDisposable { return NULL_RASTERIZED_GLYPH; } - const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, powerLineGlyph, drawSuccess); + const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, powerlineGlyph, customGlyph, padding); const clippedImageData = this._clipImageData(imageData, this._workBoundingBox); - // Check if there is enough room in the current row and go to next if needed - if (this._currentRowX + rasterizedGlyph.size.x > TEXTURE_WIDTH) { - this._currentRowX = 0; - this._currentRowY += this._currentRowHeight; - this._currentRowHeight = 0; + // Find the best atlas row to use + let activeRow: ICharAtlasActiveRow; + while (true) { + // Select the ideal existing row, preferring fixed rows over the current row + activeRow = this._currentRow; + for (const row of this._fixedRows) { + if ((activeRow === this._currentRow || row.height < activeRow.height) && rasterizedGlyph.size.y <= row.height) { + activeRow = row; + } + } + + // Create a new one if vertical space would be wasted, fixing the previously active row in the + // process as it now has a fixed height + if (activeRow.height > rasterizedGlyph.size.y * 2) { + // Fix the current row as the new row is being added below + if (this._currentRow.height > 0) { + this._fixedRows.push(this._currentRow); + } + + // Create the new fixed height row + activeRow = { + x: 0, + y: this._currentRow.y + this._currentRow.height, + height: rasterizedGlyph.size.y + }; + this._fixedRows.push(activeRow); + + // Create the new current row below the new fixed height row + this._currentRow = { + x: 0, + y: activeRow.y + activeRow.height, + height: 0 + }; + } + + // Exit the loop if there is enough room in the row + if (activeRow.x + rasterizedGlyph.size.x <= TEXTURE_WIDTH) { + break; + } + + // If there is enough room in the current row, finish it and try again + if (activeRow === this._currentRow) { + activeRow.x = 0; + activeRow.y += activeRow.height; + activeRow.height = 0; + } else { + this._fixedRows.splice(this._fixedRows.indexOf(activeRow), 1); + } } // Record texture position - rasterizedGlyph.texturePosition.x = this._currentRowX; - rasterizedGlyph.texturePosition.y = this._currentRowY; - rasterizedGlyph.texturePositionClipSpace.x = this._currentRowX / TEXTURE_WIDTH; - rasterizedGlyph.texturePositionClipSpace.y = this._currentRowY / TEXTURE_HEIGHT; + rasterizedGlyph.texturePosition.x = activeRow.x; + rasterizedGlyph.texturePosition.y = activeRow.y; + rasterizedGlyph.texturePositionClipSpace.x = activeRow.x / TEXTURE_WIDTH; + rasterizedGlyph.texturePositionClipSpace.y = activeRow.y / TEXTURE_HEIGHT; - // Update atlas current row - this._currentRowHeight = Math.max(this._currentRowHeight, rasterizedGlyph.size.y); - this._currentRowX += rasterizedGlyph.size.x; + // Update atlas current row, for fixed rows the glyph height will never be larger than the row + // height + activeRow.height = Math.max(activeRow.height, rasterizedGlyph.size.y); + activeRow.x += rasterizedGlyph.size.x; // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us this._cacheCtx.putImageData(clippedImageData, rasterizedGlyph.texturePosition.x, rasterizedGlyph.texturePosition.y); @@ -610,10 +680,10 @@ export class WebglCharAtlas implements IDisposable { * @param imageData The image data to read. * @param boundingBox An IBoundingBox to put the clipped bounding box values. */ - private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean): IRasterizedGlyph { + private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number): IRasterizedGlyph { boundingBox.top = 0; const height = restrictedGlyph ? this._config.scaledCellHeight : this._tmpCanvas.height; - const width = restrictedGlyph ? this._config.scaledCharWidth : allowedWidth; + const width = restrictedGlyph ? this._config.scaledCellWidth : allowedWidth; let found = false; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { @@ -685,8 +755,8 @@ export class WebglCharAtlas implements IDisposable { y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT }, offset: { - x: -boundingBox.left + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING) + (customGlyph ? Math.floor(this._config.letterSpacing / 2) : 0), - y: -boundingBox.top + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING) + (customGlyph ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.scaledCellHeight - this._config.scaledCharHeight) / 2) : 0) + x: -boundingBox.left + padding + ((restrictedGlyph || customGlyph) ? Math.floor((this._config.scaledCellWidth - this._config.scaledCharWidth) / 2) : 0), + y: -boundingBox.top + padding + ((restrictedGlyph || customGlyph) ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.scaledCellHeight - this._config.scaledCharHeight) / 2) : 0) } }; } @@ -763,8 +833,3 @@ function checkCompletelyTransparent(imageData: ImageData): boolean { } return true; } - -function toPaddedHex(c: number): string { - const s = c.toString(16); - return s.length < 2 ? '0' + s : s; -} diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts index ee390d8a..74aed0cc 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -12,6 +12,16 @@ declare module 'xterm-addon-webgl' { export class WebglAddon implements ITerminalAddon { public textureAtlas?: HTMLCanvasElement; + /** + * An event that is fired when the renderer loses its canvas context. + */ + public get onContextLoss(): IEvent; + + /** + * An event that is fired when the texture atlas of the renderer changes. + */ + public get onChangeTextureAtlas(): IEvent; + constructor(preserveDrawingBuffer?: boolean); /** @@ -29,10 +39,5 @@ declare module 'xterm-addon-webgl' { * Clears the terminal's texture atlas and triggers a redraw. */ public clearTextureAtlas(): void; - - /** - * Fired when the WebglRenderer loses context - */ - public get onContextLoss(): IEvent; } } diff --git a/demo/client.ts b/demo/client.ts index b7b0058d..8b9dd55a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -240,7 +240,8 @@ function createTerminal(): void { try { typedTerm.loadAddon(addons.webgl.instance); setTimeout(() => { - document.body.appendChild(addons.webgl.instance.textureAtlas); + addTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); }, 0); } catch { @@ -404,20 +405,18 @@ function initOptions(term: TerminalType): void { const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); - if (o === 'cols' || o === 'rows') { - updateTerminalSize(); - } else if (o === 'lineHeight') { + if (o === 'lineHeight') { term.options.lineHeight = parseFloat(input.value); - updateTerminalSize(); } else if (o === 'scrollSensitivity') { term.options.scrollSensitivity = parseFloat(input.value); - updateTerminalSize(); } else if (o === 'scrollback') { term.options.scrollback = parseInt(input.value); setTimeout(() => updateTerminalSize(), 5); } else { term.options[o] = parseInt(input.value); } + // Always update terminal size in case the option changes the dimensions + updateTerminalSize(); }); }); Object.keys(stringOptions).forEach(o => { @@ -526,7 +525,7 @@ function initAddons(term: TerminalType): void { } } else { if (name === 'webgl') { - document.body.removeChild((addon.instance as WebglAddon).textureAtlas); + (addon.instance as WebglAddon).textureAtlas.remove(); } else if (name === 'unicode11') { term.unicode.activeVersion = '6'; } @@ -602,6 +601,9 @@ function htmlSerializeButtonHandler(): void { document.getElementById("htmlserialize-output-result").innerText = "Copied to clipboard"; } +function addTextureAtlas(e: HTMLCanvasElement) { + document.querySelector('#texture-atlas').appendChild(e); +} function writeCustomGlyphHandler() { term.write('\n\r'); @@ -700,7 +702,7 @@ function powerlineSymbolTest() { ` 3 \ue0b1 \x1b[33;44m\ue0b0\x1b[39m` + ` 4 \ue0b1 \x1b[34;45m\ue0b0\x1b[39m` + ` 5 \ue0b1 \x1b[35;46m\ue0b0\x1b[39m` + - ` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[39m` + + ` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[30m` + ` 7 \ue0b1 \x1b[37;49m\ue0b0\x1b[0m` ); term.writeln(''); @@ -713,7 +715,7 @@ function powerlineSymbolTest() { ` 3 \ue0b3 \x1b[7;33;44m\ue0b2\x1b[27;39m` + ` 4 \ue0b3 \x1b[7;34;45m\ue0b2\x1b[27;39m` + ` 5 \ue0b3 \x1b[7;35;46m\ue0b2\x1b[27;39m` + - ` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;39m` + + ` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;30m` + ` 7 \ue0b3 \x1b[7;37;49m\ue0b2\x1b[0m` ); term.writeln(''); @@ -726,7 +728,7 @@ function powerlineSymbolTest() { ` 3 \ue0b5 \x1b[33;44m\ue0b4\x1b[39m` + ` 4 \ue0b5 \x1b[34;45m\ue0b4\x1b[39m` + ` 5 \ue0b5 \x1b[35;46m\ue0b4\x1b[39m` + - ` 6 \ue0b5 \x1b[36;47m\ue0b4\x1b[39m` + + ` 6 \ue0b5 \x1b[36;47m\ue0b4\x1b[30m` + ` 7 \ue0b5 \x1b[37;49m\ue0b4\x1b[0m` ); term.writeln(''); @@ -739,7 +741,7 @@ function powerlineSymbolTest() { ` 3 \ue0b7 \x1b[7;33;44m\ue0b6\x1b[27;39m` + ` 4 \ue0b7 \x1b[7;34;45m\ue0b6\x1b[27;39m` + ` 5 \ue0b7 \x1b[7;35;46m\ue0b6\x1b[27;39m` + - ` 6 \ue0b7 \x1b[7;36;47m\ue0b6\x1b[27;39m` + + ` 6 \ue0b7 \x1b[7;36;47m\ue0b6\x1b[27;30m` + ` 7 \ue0b7 \x1b[7;37;49m\ue0b6\x1b[0m` ); term.writeln(''); diff --git a/demo/index.html b/demo/index.html index bff5b4b2..214dd25f 100644 --- a/demo/index.html +++ b/demo/index.html @@ -87,6 +87,7 @@ +