Support lineHeight

This commit is contained in:
Daniel Imms
2017-09-02 12:27:09 -07:00
parent 9cabb93b8b
commit c92a2f964a
9 changed files with 56 additions and 36 deletions
+1
View File
@@ -127,6 +127,7 @@ export interface ITerminalOptions {
fontFamily?: string;
geometry?: [number, number];
handler?: (data: string) => void;
lineHeight?: number;
rows?: number;
screenKeys?: boolean;
scrollback?: number;
+2 -2
View File
@@ -273,7 +273,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* @param event The mouse event.
*/
private _getMouseBufferCoords(event: MouseEvent): [number, number] {
const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true);
const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);
if (!coords) {
return null;
}
@@ -293,7 +293,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
*/
private _getMouseEventScrollAmount(event: MouseEvent): number {
let offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1];
const terminalHeight = this._terminal.rows * this._charMeasure.height;
const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight);
if (offset >= 0 && offset <= terminalHeight) {
return 0;
}
+20 -8
View File
@@ -72,6 +72,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
bellStyle: 'none',
fontFamily: 'courier-new, courier, monospace',
fontSize: 15,
lineHeight: 1.0,
scrollback: 1000,
screenKeys: false,
debug: false,
@@ -356,19 +357,24 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
break;
case 'cursorStyle':
if (!value) {
value = 'block';
}
break;
if (!value) {
value = 'block';
}
break;
case 'lineHeight':
if (value < 1) {
console.warn(`${key} cannot be less than 1, value: ${value}`);
return;
}
case 'tabStopWidth':
if (value < 1) {
console.warn(`tabStopWidth cannot be less than 1, value: ${value}`);
console.warn(`${key} cannot be less than 1, value: ${value}`);
return;
}
break;
case 'scrollback':
if (value < 0) {
console.warn(`scrollback cannot be less than 0, value: ${value}`);
console.warn(`${key} cannot be less than 0, value: ${value}`);
return;
}
if (this.options[key] !== value) {
@@ -395,6 +401,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.renderer.clear();
this.charMeasure.measure(this.options);
break;
case 'lineHeight':
// When the font changes the size of the cells may change which requires a renderer clear
this.renderer.clear();
this.renderer.onResize(this.cols, this.rows);
this.refresh(0, this.rows - 1);
// this.charMeasure.measure(this.options);
case 'scrollback':
this.buffers.resize(this.cols, this.rows);
this.viewport.syncScrollArea();
@@ -709,7 +721,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
button = getButton(ev);
// get mouse coordinates
pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
if (!pos) return;
sendEvent(button, pos);
@@ -735,7 +747,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
function sendMove(ev: MouseEvent): void {
let button = pressed;
let pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
let pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.options.lineHeight, self.cols, self.rows);
if (!pos) return;
// buttons marked as motions
+8 -7
View File
@@ -46,19 +46,20 @@ export class Viewport implements IViewport {
*/
private refresh(): void {
if (this.charMeasure.height > 0) {
const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;
const lineHeight = Math.ceil(this.charMeasure.height * this.terminal.options.lineHeight);
const rowHeightChanged = lineHeight !== this.currentRowHeight;
if (rowHeightChanged) {
this.currentRowHeight = this.charMeasure.height;
this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';
this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';
this.currentRowHeight = lineHeight;
this.viewportElement.style.lineHeight = lineHeight + 'px';
this.terminal.rowContainer.style.lineHeight = lineHeight + 'px';
}
const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
if (rowHeightChanged || viewportHeightChanged) {
this.lastRecordedViewportHeight = this.terminal.rows;
this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';
this.viewportElement.style.height = lineHeight * this.terminal.rows + 'px';
this.terminal.selectionContainer.style.height = this.viewportElement.style.height;
}
this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';
this.scrollArea.style.height = (lineHeight * this.lastRecordedBufferLength) + 'px';
}
}
@@ -75,7 +76,7 @@ export class Viewport implements IViewport {
this.refresh();
} else {
// If size has changed, refresh viewport
if (this.charMeasure.height !== this.currentRowHeight) {
if (Math.ceil(this.charMeasure.height * this.terminal.options.lineHeight) !== this.currentRowHeight) {
this.refresh();
}
}
+3 -1
View File
@@ -49,7 +49,7 @@
var geometry = {
cols: parseInt(availableWidth / term.charMeasure.width, 10),
rows: parseInt(availableHeight / term.charMeasure.height, 10)
rows: parseInt(availableHeight / (term.charMeasure.height * term.getOption('lineHeight')), 10)
};
return geometry;
@@ -62,6 +62,8 @@
var geometry = exports.proposeGeometry(term);
if (geometry) {
// Force a full render
term.renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}, 0);
+15 -11
View File
@@ -11,6 +11,8 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _ctx: CanvasRenderingContext2D;
private scaledCharWidth: number;
private scaledCharHeight: number;
private scaledLineHeight: number;
private scaledLineDrawY: number;
// TODO: This should be shared between terminals, but not for static as some
// terminals may have different styles
@@ -55,6 +57,8 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void {
this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio;
this.scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio;
this.scaledLineHeight = Math.ceil(this.scaledCharHeight * terminal.options.lineHeight);
this.scaledLineDrawY = terminal.options.lineHeight === 1 ? 0 : Math.round((this.scaledLineHeight - this.scaledCharHeight) / 2);
this._canvas.width = canvasWidth * window.devicePixelRatio;
this._canvas.height = canvasHeight * window.devicePixelRatio;
this._canvas.style.width = `${canvasWidth}px`;
@@ -68,13 +72,13 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public abstract reset(terminal: ITerminal): void;
protected fillCells(startCol: number, startRow: number, colWidth: number, colHeight: number): void {
this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight);
this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledLineHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledLineHeight);
}
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 */,
(y + 1) * this.scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
this.scaledCharWidth,
window.devicePixelRatio);
}
@@ -82,9 +86,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected drawLeftLineAtCell(x: number, y: number): void {
this._ctx.fillRect(
x * this.scaledCharWidth,
y * this.scaledCharHeight,
y * this.scaledLineHeight,
window.devicePixelRatio,
this.scaledCharHeight);
this.scaledLineHeight);
}
protected drawRectAtCell(x: number, y: number, width: number, height: number, color: string): void {
@@ -92,9 +96,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.lineWidth = window.devicePixelRatio;
this._ctx.strokeRect(
x * this.scaledCharWidth + window.devicePixelRatio / 2,
y * this.scaledCharHeight + (window.devicePixelRatio / 2),
y * this.scaledLineHeight + (window.devicePixelRatio / 2),
(width * this.scaledCharWidth) - window.devicePixelRatio,
(height * this.scaledCharHeight) - window.devicePixelRatio);
(height * this.scaledLineHeight) - window.devicePixelRatio);
}
protected clearAll(): void {
@@ -102,7 +106,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
protected clearCells(startCol: number, startRow: number, colWidth: number, colHeight: number): void {
this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight);
this._ctx.clearRect(startCol * this.scaledCharWidth, startRow * this.scaledLineHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledLineHeight);
}
protected drawCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number, color: string): void {
@@ -116,7 +120,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
// can bleed into other cells. This code will clip the following fillText,
// ensuring that its contents don't go beyond the cell bounds.
this._ctx.beginPath();
this._ctx.rect(x * this.scaledCharWidth, y * this.scaledCharHeight, charData[CHAR_DATA_WIDTH_INDEX] * this.scaledCharWidth, this.scaledCharHeight);
this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this.scaledCharWidth, this.scaledCharHeight);
this._ctx.clip();
this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this.scaledCharWidth, y * this.scaledCharHeight);
@@ -137,7 +141,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
const charAtlasCellHeight = this.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
this._ctx.drawImage(this._charAtlas,
code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this.scaledCharWidth, this.scaledCharHeight,
x * this.scaledCharWidth, y * this.scaledCharHeight, this.scaledCharWidth, this.scaledCharHeight);
x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, this.scaledCharWidth, this.scaledCharHeight);
} else {
this._drawUncachedChar(terminal, char, width, fg, x, y);
}
@@ -164,11 +168,11 @@ export abstract class BaseRenderLayer implements IRenderLayer {
// can bleed into other cells. This code will clip the following fillText,
// ensuring that its contents don't go beyond the cell bounds.
this._ctx.beginPath();
this._ctx.rect(x * this.scaledCharWidth, y * this.scaledCharHeight, width * this.scaledCharWidth, this.scaledCharHeight);
this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, width * this.scaledCharWidth, this.scaledCharHeight);
this._ctx.clip();
// Draw the character
this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledCharHeight);
this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY);
this._ctx.restore();
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ export class Renderer {
public onResize(cols: number, rows: number): void {
const width = this._terminal.charMeasure.width * this._terminal.cols;
const height = this._terminal.charMeasure.height * this._terminal.rows;
const height = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight) * this._terminal.rows;
this._renderLayers.forEach(l => l.resize(this._terminal, width, height, false));
}
+4 -4
View File
@@ -36,7 +36,7 @@ export function getCoordsRelativeToElement(event: MouseEvent, element: HTMLEleme
* apply an offset to the x value such that the left half of the cell will
* select that cell and the right half will select the next cell.
*/
export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
// Coordinates cannot be measured if charMeasure has not been initialized
if (!charMeasure.width || !charMeasure.height) {
return null;
@@ -49,7 +49,7 @@ export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeas
// Convert to cols/rows.
coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width);
coords[1] = Math.ceil(coords[1] / charMeasure.height);
coords[1] = Math.ceil(coords[1] / Math.ceil(charMeasure.height * lineHeight));
// Ensure coordinates are within the terminal viewport.
coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1);
@@ -68,8 +68,8 @@ export function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeas
* @param colCount The number of columns in the terminal.
* @param rowCount The number of rows in the terminal.
*/
export function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } {
const coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount);
export function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } {
const coords = getCoords(event, rowContainer, charMeasure, lineHeight, colCount, rowCount);
let x = coords[0];
let y = coords[1];
+2 -2
View File
@@ -328,7 +328,7 @@ declare module 'xterm' {
* Retrieves an option's value from the terminal.
* @param key The option key.
*/
getOption(key: 'cols' | 'fontSize' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
getOption(key: 'cols' | 'fontSize' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
/**
* Retrieves an option's value from the terminal.
* @param key The option key.
@@ -380,7 +380,7 @@ declare module 'xterm' {
* @param key The option key.
* @param value The option value.
*/
setOption(key: 'cols' | 'fontSize' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void;
setOption(key: 'cols' | 'fontSize' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void;
/**
* Sets an option on the terminal.
* @param key The option key.