From 3d1bb2c60fddbc1e0b4e8b7e9585b6e859763d28 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 31 Aug 2017 00:09:23 -0700 Subject: [PATCH] Support selection --- src/SelectionManager.ts | 5 ++-- src/Terminal.ts | 6 ++-- src/renderer/BackgroundRenderLayer.ts | 4 +-- src/renderer/ForegroundRenderLayer.ts | 4 +-- src/renderer/Interfaces.ts | 7 +++++ src/renderer/Renderer.ts | 32 +++++++++++++++------ src/renderer/SelectionRenderLayer.ts | 40 ++++++++++++--------------- src/xterm.css | 10 ++----- 8 files changed, 59 insertions(+), 49 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1aaec14b..1e3fe9b8 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -116,8 +116,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this._mouseMoveListener = event => this._onMouseMove(event); this._mouseUpListener = event => this._onMouseUp(event); - this._rowContainer.addEventListener('mousedown', event => this._onMouseDown(event)); - // Only adjust the selection on trim, shiftElements is rarely used (only in // reverseIndex) and delete in a splice is only ever used when the same // number of elements was just added. Given this is could actually be @@ -312,7 +310,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * Handles te mousedown event, setting up for a new selection. * @param event The mousedown event. */ - private _onMouseDown(event: MouseEvent): void { + public onMouseDown(event: MouseEvent): void { + console.log('mousedown selectionmanager'); // If we have selection, we want the context menu on right click even if the // terminal is in mouse mode. if (event.button === 2 && this.hasSelection) { diff --git a/src/Terminal.ts b/src/Terminal.ts index 594a6fb2..f88ebb7f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -751,10 +751,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.renderer = new Renderer(this); this.on('resize', () => this.renderer.onResize(this.cols, this.rows)); this.charMeasure.on('charsizechanged', () => this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height)); + this.selectionManager = new SelectionManager(this, this.buffer, this.rowContainer, this.charMeasure); - // this.selectionManager.on('refresh', data => { - // this.renderer.refreshSelection(data.start, data.end); - // }); + this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e)); + this.selectionManager.on('refresh', data => this.renderer.onSelectionChanged(data.start, data.end)); this.selectionManager.on('newselection', text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index dac476a0..162b96a4 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -1,11 +1,11 @@ -import { IRenderLayer } from './Interfaces'; +import { IDataRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; -export class BackgroundRenderLayer implements IRenderLayer { +export class BackgroundRenderLayer implements IDataRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; private _state: GridCache; diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 2b1ff272..0f74ff28 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -1,4 +1,4 @@ -import { IRenderLayer } from './Interfaces'; +import { IDataRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; @@ -6,7 +6,7 @@ import { FLAGS } from './Types'; import { GridCache } from './GridCache'; import { CharData } from '../Types'; -export class ForegroundRenderLayer implements IRenderLayer { +export class ForegroundRenderLayer implements IDataRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; private _charAtlas: ImageBitmap; diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 4d275375..4d6d2d3b 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -2,5 +2,12 @@ import { ITerminal } from '../Interfaces'; export interface IRenderLayer { resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void; +} + +export interface IDataRenderLayer extends IRenderLayer { render(terminal: ITerminal, startRow: number, endRow: number): void; } + +export interface ISelectionRenderLayer extends IRenderLayer { + render(terminal: ITerminal, start: [number, number], end: [number, number]): void; +} diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 61a4b110..b4d9b153 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -6,37 +6,51 @@ import { ITerminal } from '../Interfaces'; import { DomElementObjectPool } from '../utils/DomElementObjectPool'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { createBackgroundFillData } from './Canvas'; -import { IRenderLayer } from './Interfaces'; +import { IDataRenderLayer, ISelectionRenderLayer } from './Interfaces'; import { BackgroundRenderLayer } from './BackgroundRenderLayer'; import { ForegroundRenderLayer } from './ForegroundRenderLayer'; +import { SelectionRenderLayer } from './SelectionRenderLayer'; export class Renderer { /** A queue of the rows to be refreshed */ private _refreshRowsQueue: {start: number, end: number}[] = []; private _refreshAnimationFrame = null; - private _renderLayers: IRenderLayer[]; + private _dataRenderLayers: IDataRenderLayer[]; + private _selectionRenderLayers: ISelectionRenderLayer[]; constructor(private _terminal: ITerminal) { - this._renderLayers = [ + this._dataRenderLayers = [ new BackgroundRenderLayer(this._terminal.element), new ForegroundRenderLayer(this._terminal.element) ]; + this._selectionRenderLayers = [ + new SelectionRenderLayer(this._terminal.element) + ]; } public onResize(cols: number, rows: number): void { const width = Math.ceil(this._terminal.charMeasure.width) * this._terminal.cols; const height = Math.ceil(this._terminal.charMeasure.height) * this._terminal.rows; - for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].resize(this._terminal, width, height, false); + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].resize(this._terminal, width, height, false); } } public onCharSizeChanged(charWidth: number, charHeight: number): void { const width = Math.ceil(charWidth) * this._terminal.cols; const height = Math.ceil(charHeight) * this._terminal.rows; - for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].resize(this._terminal, width, height, true); + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].resize(this._terminal, width, height, true); + } + for (let i = 0; i < this._selectionRenderLayers.length; i++) { + this._selectionRenderLayers[i].resize(this._terminal, width, height, true); + } + } + + public onSelectionChanged(start: [number, number], end: [number, number]): void { + for (let i = 0; i < this._selectionRenderLayers.length; i++) { + this._selectionRenderLayers[i].render(this._terminal, start, end); } } @@ -81,8 +95,8 @@ export class Renderer { this._refreshAnimationFrame = null; // Render - for (let i = 0; i < this._renderLayers.length; i++) { - this._renderLayers[i].render(this._terminal, start, end); + for (let i = 0; i < this._dataRenderLayers.length; i++) { + this._dataRenderLayers[i].render(this._terminal, start, end); } this._terminal.emit('refresh', {start, end}); } diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index bc31022c..b630359e 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -1,21 +1,20 @@ -import { IRenderLayer } from './Interfaces'; +import { ISelectionRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; import { GridCache } from './GridCache'; import { FLAGS } from './Types'; -export class BackgroundRenderLayer implements IRenderLayer { +export class SelectionRenderLayer implements ISelectionRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; private _state: {start: [number, number], end: [number, number]}; constructor(container: HTMLElement) { this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-bg-layer'); + this._canvas.classList.add('xterm-selection-layer'); this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - this._ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; container.appendChild(this._canvas); this._state = { start: null, @@ -30,15 +29,10 @@ export class BackgroundRenderLayer implements IRenderLayer { this._canvas.style.height = `${canvasHeight}px`; } - public render(terminal: ITerminal, startRow: number, endRow: number): void { + public render(terminal: ITerminal, start: [number, number], end: [number, number]): void { const scaledCharWidth = Math.ceil(terminal.charMeasure.width) * window.devicePixelRatio; const scaledCharHeight = Math.ceil(terminal.charMeasure.height) * window.devicePixelRatio; - const start = terminal.selectionManager.selectionStart; - const end = terminal.selectionManager.selectionEnd; - - // TODO: Need to redraw selection if the viewport has moved - // Selection has not changed if (this._state.start === start || this._state.end === end) { return; @@ -63,21 +57,21 @@ export class BackgroundRenderLayer implements IRenderLayer { return; } - // Create the selections // Draw first row const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; - this._ctx.fillRect(startCol * scaledCharWidth, viewportCappedStartRow * scaledCharHeight, (endCol - startCol) * scaledCharWidth, scaledCharHeight); - // documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); + const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; + this._ctx.fillStyle = 'rgba(255,255,255,0.3)'; + this._ctx.fillRect(startCol * scaledCharWidth, viewportCappedStartRow * scaledCharHeight, (startRowEndCol - startCol) * scaledCharWidth, scaledCharHeight); + // Draw middle rows - // const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; - // documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); - // // Draw final row - // if (viewportCappedStartRow !== viewportCappedEndRow) { - // // Only draw viewportEndRow if it's not the same as viewporttartRow - // const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; - // documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol)); - // } - // this._terminal.selectionContainer.appendChild(documentFragment); + const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); + this._ctx.fillRect(0, (viewportCappedStartRow + 1) * scaledCharHeight, terminal.cols * scaledCharWidth, middleRowsCount * scaledCharHeight); + + // Draw final row + if (viewportCappedStartRow !== viewportCappedEndRow) { + // Only draw viewportEndRow if it's not the same as viewporttartRow + const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; + this._ctx.fillRect(0, viewportCappedEndRow * scaledCharHeight, endCol * scaledCharWidth, scaledCharHeight); + } } } diff --git a/src/xterm.css b/src/xterm.css index 2ee98e54..8a48c6f7 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -164,13 +164,9 @@ top: 0; } -.terminal .xterm-bg-layer { - z-index: 0; -} - -.terminal .xterm-fg-layer { - z-index: 1; -} +.terminal .xterm-bg-layer { z-index: 0; } +.terminal .xterm-fg-layer { z-index: 1; } +.terminal .xterm-selection-layer { z-index: 2; } .terminal .xterm-rows { position: absolute;