ascii-ui
Version:
Graphic terminal emulator for HTML canvas elements
234 lines • 8.01 kB
JavaScript
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