UNPKG

ascii-ui

Version:

Graphic terminal emulator for HTML canvas elements

234 lines 8.01 kB
import { isEmpty } from 'vanilla-type-check/isEmpty'; import { assignCharStyle } from '../util/assignCharStyle'; import { coalesce } from '../util/coalesce'; import { deepAssign } from '../util/deepAssign'; import { noWrap } from '../util/tokenizer'; import { Widget } from '../Widget'; export class Box extends Widget { constructor(terminal, options, parent) { super(terminal, deepAssign({}, Box.defaultOptions, options), parent); } render() { if (!this.allocated) { return; } const baseOptions = this.getAspectOptions(); const boxTitle = baseOptions.boxTitle; const title = noWrap(this.options.title, this.options.width - boxTitle.marginLeft - boxTitle.marginRight - 2, baseOptions.boxTitle.ellipsis); const tiles = this.getBoxTiles(title); for (let j = 0; j < tiles.length; j++) { this.terminal.setTiles(tiles[j], this.options.col, this.options.line + j); } if (this.attachedWidget) { this.attachedWidget.render(); } } attachWidget(WidgetClass, options) { const positionOptions = this.getAvailableSpace(); const newWidgetOptions = Object.assign({}, options, positionOptions); this.attachedWidget = Reflect.construct(WidgetClass, [ this.terminal, newWidgetOptions, this, ]); return this.attachedWidget; } dettachWidget(widget) { if (widget !== this.attachedWidget) { return false; } const oldWidget = this.attachedWidget; this.attachedWidget = undefined; if (oldWidget) { this.terminal.clear(this.options.col + 1, this.options.line + 1, this.options.width - 2, this.options.height - 2); } return true; } getWidgetAt(column, line) { return (this.attachedWidget && this.attachedWidget.isAt(column, line)) ? this.attachedWidget : undefined; } [Symbol.iterator](startWidget) { const widget = this.attachedWidget; let index; const it = { next: () => { index++; if (index > 1) { index = 1; } return { value: index === 0 ? widget : undefined, done: index !== 0, }; }, prev: () => { index--; if (index < -1) { index = -1; } return { value: index === 0 ? widget : undefined, done: index !== 0, }; }, seek: (value) => { index = typeof value === 'number' ? (value < 0 ? 0 - value : value - 1) : this.attachedWidget === value ? 0 : -1; }, }; if (startWidget) { it.seek(startWidget); } else { index = -1; } return it; } updateOptions(changes) { if (!isEmpty(changes)) { this.optionsFocus = deepAssign(this.optionsFocus, this.options.base, this.options.focus); this.optionsDisabled = deepAssign(this.optionsDisabled, this.options.base, this.options.disabled); if (this.attachedWidget && coalesce(changes.width, changes.height, changes.col, changes.line, changes.padding) !== undefined) { this.attachedWidget.setOptions(this.getAvailableSpace()); } this.render(); } } setBorderPool() { const pool = Box.boxTilesPool; const aspectOptions = this.getAspectOptions().boxBorders; pool.topLeft.char = aspectOptions.topLeft; pool.top.char = aspectOptions.top; pool.topRight.char = aspectOptions.topRight; pool.left.char = aspectOptions.left; pool.center.char = aspectOptions.center; pool.right.char = aspectOptions.right; pool.bottomLeft.char = aspectOptions.bottomLeft; pool.bottom.char = aspectOptions.bottom; pool.bottomRight.char = aspectOptions.bottomRight; assignCharStyle(pool.topLeft, aspectOptions); assignCharStyle(pool.top, aspectOptions); assignCharStyle(pool.topRight, aspectOptions); assignCharStyle(pool.left, aspectOptions); assignCharStyle(pool.center, aspectOptions); assignCharStyle(pool.right, aspectOptions); assignCharStyle(pool.bottomLeft, aspectOptions); assignCharStyle(pool.bottom, aspectOptions); assignCharStyle(pool.bottomRight, aspectOptions); } getBoxTiles(title) { const tiles = []; const pool = Box.boxTilesPool; const width = this.options.width; const height = this.options.height; const aspectOptions = this.getAspectOptions(); const titleStyle = Object.assign({}, assignCharStyle({}, aspectOptions.boxTitle)); this.setBorderPool(); const top = Array(width); top[0] = pool.topLeft; top.fill(pool.top, 1, width); top[width - 1] = pool.topRight; if (title) { const titleStart = aspectOptions.boxTitle.marginLeft + 1; for (let i = 0; i < title.length; i++) { let tile = pool.title[i]; if (tile) { tile.char = title[i]; } else { pool.title[i] = { char: title[i] }; tile = pool.title[i]; } top[titleStart + i] = tile; assignCharStyle(tile, titleStyle); } } tiles[0] = top; const center = Array(width); center[0] = pool.left; center.fill(pool.center, 1, width); center[width - 1] = pool.right; for (let line = 1; line < height - 1; line++) { tiles[line] = center; } const bottom = Array(width); bottom[0] = pool.bottomLeft; bottom.fill(pool.bottom, 1, width); bottom[width - 1] = pool.bottomRight; tiles[height - 1] = bottom; return tiles; } getAspectOptions() { if (!this.options.focusable) { return this.optionsDisabled; } if (this.isFocused()) { return this.optionsFocus; } return this.options.base; } getAvailableSpace() { const boxOptions = this.options; const padding = boxOptions.padding; const positionOptions = { col: boxOptions.col + padding.left + 1, line: boxOptions.line + padding.top + 1, width: boxOptions.width - padding.left - padding.right - 2, height: boxOptions.height - padding.top - padding.bottom - 2, }; return positionOptions; } } Box.boxTilesPool = { title: [], topLeft: { char: '' }, top: { char: '' }, topRight: { char: '' }, left: { char: '' }, center: { char: '' }, right: { char: '' }, bottomLeft: { char: '' }, bottom: { char: '' }, bottomRight: { char: '' }, }; Box.defaultOptions = { padding: { top: 1, right: 1, bottom: 1, left: 1, }, base: { boxBorders: { fg: '#00ff00', topLeft: '┌', top: '─', topRight: '┐', left: '│', center: ' ', right: '│', bottomLeft: '└', bottom: '─', bottomRight: '┘', }, boxTitle: { fg: '#00ff00', marginLeft: 1, marginRight: 1, ellipsis: '...', }, }, focus: { boxBorders: { fg: '#ffff00' }, boxTitle: { fg: '#ffff00' }, }, disabled: { boxBorders: { fg: '#009900' }, boxTitle: { fg: '#009900' }, }, }; //# sourceMappingURL=Box.js.map