diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 3a0c9929..745dd87a 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -35,6 +35,7 @@ export interface ITerminal extends IEventEmitter { options: ITerminalOptions; buffers: IBufferSet; buffer: IBuffer; + isFocused: boolean; /** * Emit the 'data' event and populate the given data. diff --git a/src/Terminal.ts b/src/Terminal.ts index 616c2945..fc5af887 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -412,6 +412,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea.focus(); } + public get isFocused(): boolean { + return document.activeElement === this.textarea; + } + public setTheme(theme: ITheme): void { // TODO: Allow setting of theme before renderer is ready if (this.renderer) { diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 523edbda..3da7aadc 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -30,6 +30,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Should this do anything? public onOptionsChanged(terminal: ITerminal): void {} + public onBlur(terminal: ITerminal): void {} + public onFocus(terminal: ITerminal): void {} public onCursorMove(terminal: ITerminal): void {} public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {} public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void {} @@ -58,7 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight); } - protected fillBottomLineAtCell(x: number, y: number): void { + protected drawBottomLineAtCell(x: number, y: number): void { this._ctx.fillRect( x * this.scaledCharWidth, (y + 1) * this.scaledCharHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, @@ -66,7 +68,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { window.devicePixelRatio); } - protected fillLeftLineAtCell(x: number, y: number): void { + protected drawLeftLineAtCell(x: number, y: number): void { this._ctx.fillRect( x * this.scaledCharWidth, y * this.scaledCharHeight, @@ -74,6 +76,16 @@ export abstract class BaseRenderLayer implements IRenderLayer { this.scaledCharHeight); } + protected drawSquareAtCell(x: number, y: number, color: string): void { + this._ctx.strokeStyle = color; + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.strokeRect( + x * this.scaledCharWidth + window.devicePixelRatio / 2, + y * this.scaledCharHeight + (window.devicePixelRatio / 2), + this.scaledCharWidth - window.devicePixelRatio, + this.scaledCharHeight - window.devicePixelRatio); + } + protected clearAll(): void { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index cd11f1dc..941d6d3b 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -16,6 +16,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _state: [number, number]; private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; + private _isFocused: boolean; constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { super(container, 'cursor', zIndex, colors); @@ -37,6 +38,20 @@ export class CursorRenderLayer extends BaseRenderLayer { } } + public onBlur(terminal: ITerminal): void { + if (this._cursorBlinkStateManager) { + // this._cursorBlinkStateManager.pause(); + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + + public onFocus(terminal: ITerminal): void { + if (this._cursorBlinkStateManager) { + // this._cursorBlinkStateManager.resume(); + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + public onOptionsChanged(terminal: ITerminal): void { if (terminal.options.cursorBlink) { if (!this._cursorBlinkStateManager) { @@ -69,11 +84,8 @@ export class CursorRenderLayer extends BaseRenderLayer { } private _render(terminal: ITerminal, triggeredByAnimationFrame: boolean): void { - // TODO: Track blur/focus somehow, support unfocused cursor // Don't draw the cursor if it's hidden - if (!terminal.cursorState || - terminal.cursorHidden || - (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible)) { + if (!terminal.cursorState || terminal.cursorHidden) { this._clearCursor(); return; } @@ -87,6 +99,23 @@ export class CursorRenderLayer extends BaseRenderLayer { return; } + const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; + + if (!terminal.isFocused) { + this._clearCursor(); + this._ctx.save(); + this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; + this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); + this._ctx.restore(); + return; + } + + // Don't draw the cursor if it's blinking + if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + this._clearCursor(); + return; + } + if (this._state) { // The cursor is already in the correct spot, don't redraw if (this._state[0] === terminal.buffer.x && this._state[1] === viewportRelativeCursorY) { @@ -95,7 +124,6 @@ export class CursorRenderLayer extends BaseRenderLayer { this._clearCursor(); } - const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; this._ctx.save(); this._ctx.fillStyle = this.colors.ansi[COLOR_CODES.WHITE]; this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); @@ -111,7 +139,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { - this.fillLeftLineAtCell(x, y); + this.drawLeftLineAtCell(x, y); } private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { @@ -120,7 +148,12 @@ export class CursorRenderLayer extends BaseRenderLayer { } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { - this.fillBottomLineAtCell(x, y); + this.drawBottomLineAtCell(x, y); + } + + private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { + // TODO: Support cursor colors + this.drawSquareAtCell(x, y, this.colors.foreground); } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 30f68f4c..f4bc1e77 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -97,7 +97,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer { } else { this._ctx.fillStyle = this.colors.foreground; } - this.fillBottomLineAtCell(x, y); + this.drawBottomLineAtCell(x, y); } this.drawChar(terminal, char, code, x, y, fg); diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 45822e65..b47a75b9 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -1,6 +1,16 @@ import { ITerminal, ITerminalOptions } from '../Interfaces'; export interface IRenderLayer { + /** + * Called when the terminal loses focus. + */ + onBlur(terminal: ITerminal): void; + + /** + * * Called when the terminal gets focus. + */ + onFocus(terminal: ITerminal): void; + /** * Called when the cursor is moved. */ diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 119399b0..474699c5 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -57,6 +57,14 @@ export class Renderer { this._renderLayers.forEach(l => l.resize(this._terminal, width, height, true)); } + public onBlur(): void { + this._renderLayers.forEach(l => l.onBlur(this._terminal)); + } + + public onFocus(): void { + this._renderLayers.forEach(l => l.onFocus(this._terminal)); + } + public onSelectionChanged(start: [number, number], end: [number, number]): void { this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end)); }