From 83c09c34dbdd4a9419031ae9daf82ff2eef2fec4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:23:09 -0700 Subject: [PATCH] Share selection render model code --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 78 +---------------- addons/xterm-addon-webgl/src/RenderModel.ts | 22 +---- addons/xterm-addon-webgl/src/WebglRenderer.ts | 56 +----------- .../renderer/shared/SelectionRenderModel.ts | 85 +++++++++++++++++++ src/browser/renderer/shared/Types.d.ts | 21 +++-- 5 files changed, 106 insertions(+), 156 deletions(-) create mode 100644 src/browser/renderer/shared/SelectionRenderModel.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 586be167..c0e20abd 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -9,6 +9,7 @@ import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; @@ -16,7 +17,6 @@ import { Attributes, BgFlags, FgFlags, WHITESPACE_CELL_CODE } from 'common/buffe import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; -import { IGlyphIdentifier } from './atlas/Types'; import { IRenderLayer } from './Types'; // Work variables to avoid garbage collection @@ -39,16 +39,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionStart: [number, number] | undefined; protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _selectionModel: ISelectionRenderModel = { - hasSelection: false, - columnSelectMode: false, - viewportStartRow: 0, - viewportEndRow: 0, - viewportCappedStartRow: 0, - viewportCappedEndRow: 0, - startCol: 0, - endCol: 0 - }; + protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); protected _charAtlas!: ITextureAtlas; @@ -99,7 +90,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._selectionStart = start; this._selectionEnd = end; this._columnSelectMode = columnSelectMode; - this._updateSelectionModel(start, end, columnSelectMode); + this._selectionModel.update(this._terminal, start, end, columnSelectMode); } public setColors(colorSet: IColorSet): void { @@ -430,9 +421,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { } }); - // TODO: Selection? // Apply the selection color if needed - $isSelected = this._isCellSelected(x, y); + $isSelected = this._selectionModel.isCellSelected(this._terminal, x, y); if ($isSelected) { $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; $hasBg = true; @@ -498,66 +488,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { workColors.fg = $hasFg ? $fg : workColors.fg; } - private _isCellSelected(x: number, y: number): boolean { - if (!this._selectionStart) { - return false; - } - y -= this._terminal.buffer.active.viewportY; - if (this._selectionModel.columnSelectMode) { - if (this._selectionModel.startCol <= this._selectionModel.endCol) { - return x >= this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && - x < this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; - } - return x < this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && - x >= this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; - } - return (y > this._selectionModel.viewportStartRow && y < this._selectionModel.viewportEndRow) || - (this._selectionModel.viewportStartRow === this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol && x < this._selectionModel.endCol) || - (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportEndRow && x < this._selectionModel.endCol) || - (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol); - } - - private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - const terminal = this._terminal; - - // Selection does not exist - if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { - this._clearSelectionMoidel(); - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.active.viewportY; - const viewportEndRow = end[1] - terminal.buffer.active.viewportY; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { - this._clearSelectionMoidel(); - return; - } - - this._selectionModel.hasSelection = true; - this._selectionModel.columnSelectMode = columnSelectMode; - this._selectionModel.viewportStartRow = viewportStartRow; - this._selectionModel.viewportEndRow = viewportEndRow; - this._selectionModel.viewportCappedStartRow = viewportCappedStartRow; - this._selectionModel.viewportCappedEndRow = viewportCappedEndRow; - this._selectionModel.startCol = start[0]; - this._selectionModel.endCol = end[0]; - } - - private _clearSelectionMoidel(): void { - this._selectionModel.hasSelection = false; - this._selectionModel.viewportStartRow = 0; - this._selectionModel.viewportEndRow = 0; - this._selectionModel.viewportCappedStartRow = 0; - this._selectionModel.viewportCappedEndRow = 0; - this._selectionModel.startCol = 0; - this._selectionModel.endCol = 0; - } - /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. * @param y The row to clip. diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index ecb25aa9..b1542d98 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -6,6 +6,7 @@ import { IRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; export const RENDER_MODEL_BG_OFFSET = 1; @@ -22,16 +23,7 @@ export class RenderModel implements IRenderModel { constructor() { this.cells = new Uint32Array(0); this.lineLengths = new Uint32Array(0); - this.selection = { - hasSelection: false, - columnSelectMode: false, - viewportStartRow: 0, - viewportEndRow: 0, - viewportCappedStartRow: 0, - viewportCappedEndRow: 0, - startCol: 0, - endCol: 0 - }; + this.selection = createSelectionRenderModel(); } public resize(cols: number, rows: number): void { @@ -46,14 +38,4 @@ export class RenderModel implements IRenderModel { fill(this.cells, 0, 0); fill(this.lineLengths, 0, 0); } - - public clearSelection(): void { - this.selection.hasSelection = false; - this.selection.viewportStartRow = 0; - this.selection.viewportEndRow = 0; - this.selection.viewportCappedStartRow = 0; - this.selection.viewportCappedEndRow = 0; - this.selection.startCol = 0; - this.selection.endCol = 0; - } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index f0ee105c..d7512908 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -229,7 +229,7 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.onSelectionChanged(this._terminal, start, end, columnSelectMode); } - this._updateSelectionModel(start, end, columnSelectMode); + this._model.selection.update(this._terminal, start, end, columnSelectMode); this._requestRedrawViewport(); } @@ -336,7 +336,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Tell renderer the frame is beginning if (this._glyphRenderer.beginFrame()) { this._clearModel(true); - this._updateSelectionModel(undefined, undefined); + this._model.selection.clear(); } // Update model to reflect what's drawn @@ -484,7 +484,7 @@ export class WebglRenderer extends Disposable implements IRenderer { }); // Apply the selection color if needed - $isSelected = this._isCellSelected(x, y); + $isSelected = this._model.selection.isCellSelected(this._terminal, x, y); if ($isSelected) { $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; $hasBg = true; @@ -550,56 +550,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.fg = $hasFg ? $fg : this._workColors.fg; } - private _isCellSelected(x: number, y: number): boolean { - if (!this._model.selection.hasSelection) { - return false; - } - y -= this._terminal.buffer.active.viewportY; - if (this._model.selection.columnSelectMode) { - if (this._model.selection.startCol <= this._model.selection.endCol) { - return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow && - x < this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow; - } - return x < this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow && - x >= this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow; - } - return (y > this._model.selection.viewportStartRow && y < this._model.selection.viewportEndRow) || - (this._model.selection.viewportStartRow === this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol && x < this._model.selection.endCol) || - (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportEndRow && x < this._model.selection.endCol) || - (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol); - } - - private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - const terminal = this._terminal; - - // Selection does not exist - if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { - this._model.clearSelection(); - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.active.viewportY; - const viewportEndRow = end[1] - terminal.buffer.active.viewportY; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { - this._model.clearSelection(); - return; - } - - this._model.selection.hasSelection = true; - this._model.selection.columnSelectMode = columnSelectMode; - this._model.selection.viewportStartRow = viewportStartRow; - this._model.selection.viewportEndRow = viewportEndRow; - this._model.selection.viewportCappedStartRow = viewportCappedStartRow; - this._model.selection.viewportCappedEndRow = viewportCappedEndRow; - this._model.selection.startCol = start[0]; - this._model.selection.endCol = end[0]; - } - /** * Recalculates the character and canvas dimensions. */ diff --git a/src/browser/renderer/shared/SelectionRenderModel.ts b/src/browser/renderer/shared/SelectionRenderModel.ts new file mode 100644 index 00000000..8a6e8b85 --- /dev/null +++ b/src/browser/renderer/shared/SelectionRenderModel.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { Terminal } from 'xterm'; + +class SelectionRenderModel implements ISelectionRenderModel { + public hasSelection!: boolean; + public columnSelectMode!: boolean; + public viewportStartRow!: number; + public viewportEndRow!: number; + public viewportCappedStartRow!: number; + public viewportCappedEndRow!: number; + public startCol!: number; + public endCol!: number; + + constructor() { + this.clear(); + } + + public clear(): void { + this.hasSelection = false; + this.columnSelectMode = false; + this.viewportStartRow = 0; + this.viewportEndRow = 0; + this.viewportCappedStartRow = 0; + this.viewportCappedEndRow = 0; + this.startCol = 0; + this.endCol = 0; + } + + public update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + // Selection does not exist + if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { + this.clear(); + return; + } + + // Translate from buffer position to viewport position + const viewportStartRow = start[1] - terminal.buffer.active.viewportY; + const viewportEndRow = end[1] - terminal.buffer.active.viewportY; + const viewportCappedStartRow = Math.max(viewportStartRow, 0); + const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + + // No need to draw the selection + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + this.clear(); + return; + } + + this.hasSelection = true; + this.columnSelectMode = columnSelectMode; + this.viewportStartRow = viewportStartRow; + this.viewportEndRow = viewportEndRow; + this.viewportCappedStartRow = viewportCappedStartRow; + this.viewportCappedEndRow = viewportCappedEndRow; + this.startCol = start[0]; + this.endCol = end[0]; + } + + public isCellSelected(terminal: Terminal, x: number, y: number): boolean { + if (!this.hasSelection) { + return false; + } + y -= terminal.buffer.active.viewportY; + if (this.columnSelectMode) { + if (this.startCol <= this.endCol) { + return x >= this.startCol && y >= this.viewportCappedStartRow && + x < this.endCol && y <= this.viewportCappedEndRow; + } + return x < this.startCol && y >= this.viewportCappedStartRow && + x >= this.endCol && y <= this.viewportCappedEndRow; + } + return (y > this.viewportStartRow && y < this.viewportEndRow) || + (this.viewportStartRow === this.viewportEndRow && y === this.viewportStartRow && x >= this.startCol && x < this.endCol) || + (this.viewportStartRow < this.viewportEndRow && y === this.viewportEndRow && x < this.endCol) || + (this.viewportStartRow < this.viewportEndRow && y === this.viewportStartRow && x >= this.startCol); + } +} + +export function createSelectionRenderModel(): ISelectionRenderModel { + return new SelectionRenderModel(); +} diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 81a025f2..5e29411a 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { FontWeight } from 'xterm'; +import { FontWeight, Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { IDisposable } from 'common/Types'; import { IEvent } from 'common/EventEmitter'; @@ -141,12 +141,15 @@ export interface IBoundingBox { } export interface ISelectionRenderModel { - hasSelection: boolean; - columnSelectMode: boolean; - viewportStartRow: number; - viewportEndRow: number; - viewportCappedStartRow: number; - viewportCappedEndRow: number; - startCol: number; - endCol: number; + readonly hasSelection: boolean; + readonly columnSelectMode: boolean; + readonly viewportStartRow: number; + readonly viewportEndRow: number; + readonly viewportCappedStartRow: number; + readonly viewportCappedEndRow: number; + readonly startCol: number; + readonly endCol: number; + clear(): void; + update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void; + isCellSelected(terminal: Terminal, x: number, y: number): boolean; }