diff --git a/lib/high.js b/lib/high.js new file mode 100644 index 0000000..8e608e9 --- /dev/null +++ b/lib/high.js @@ -0,0 +1,601 @@ +/** + * Blessed high-level interface + * Still in development + */ + +/** + * Node + */ + +function Node(options) {} + +/** + * Screen + */ + +function Screen(options) { + Node.call(this, options); + this.cols = options.cols; + this.rows = options.rows; + this.children = options.children || []; + this.program = options.program; + this.tput = this.program.tput; + this.lines = []; + this.dattr = ((0 << 18) | (0x1ff << 9)) | 0x1ff; + this.offset = {}; + var x, y; + for (y = 0; y < this.rows; y++) { + this.lines[y] = []; + for (x = 0; x < this.cols; x++) { + this.lines[y][x] = [this.dattr, ' ']; + } + this.lines[y].dirty = true; + } +} + +Screen.prototype.__proto__ = Node.prototype; + +Screen.prototype.render = function() { + this.children.forEach(function(el) { + el.render(); + }); + this.draw(0, this.rows - 1); +}; + +Screen.prototype.draw = function(start, end) { + var x + , y + , i + , line + , out + , ch + , data + , attr + , fgColor + , bgColor + , flags; + + this.program.saveCursor(); + + for (y = start; y <= end; y++) { + line = this.lines[y]; + + if (!line.dirty) continue; + line.dirty = false; + + out = ''; + attr = this.dattr; + + for (i = 0; i < this.cols; i++) { + data = line[i][0]; + ch = line[i][1]; + + if (data && data !== attr) { + if (attr !== this.dattr) { + out += '\x1b[m'; + } + if (data !== this.dattr) { + out += '\x1b['; + + bgColor = data & 0x1ff; + fgColor = (data >> 9) & 0x1ff; + flags = data >> 18; + + // bold + if (flags & 1) { + out += '1;'; + } + + // underline + if (flags & 2) { + out += '4;'; + } + + // blink + if (flags & 4) { + out += '5;'; + } + + // inverse + if (flags & 8) { + out += '7;'; + } + + // invisible + if (flags & 16) { + out += '8;'; + } + + if (bgColor !== 0x1ff) { + if (bgColor < 16) { //|| this.tput.colors <= 16) { + if (bgColor < 8) { + bgColor += 40; + } else if (bgColor < 16) { + bgColor += 100; + } + out += bgColor + ';'; + } else { + out += '48;5;' + bgColor + ';'; + } + } + + if (fgColor !== 0x1ff) { + if (fgColor < 16) { //|| this.tput.colors <= 16) { + if (fgColor < 8) { + fgColor += 30; + } else if (fgColor < 16) { + fgColor += 90; + } + out += fgColor + ';'; + } else { + out += '38;5;' + fgColor + ';'; + } + } + + if (out[out.length-1] === ';') out = out.slice(0, -1); + + out += 'm'; + } + } + + out += ch; + attr = data; + } + + if (attr && attr !== this.dattr) { + out += '\x1b[m'; + } + + this.program.write('\x1b[' + y + ';0H' + out); + } + + this.program.restoreCursor(); +}; + +/** + * Element + */ + +// Example Borders: +// options.border = { +// type: 'ascii', +// color: 'red', +// left: false +// }; +// options.border = { +// type: 'bg', +// color: 'lightblack', +// right: false, +// width: 1 +// }; + +function Element(options) { + Node.call(this, options); + this.screen = options.screen; + this.parent = options.parent || (function(){throw Error('No parent.')})(); + this.offset = { + left: options.left || 0, + right: options.right || 0, + top: options.top || 0, + bottom: options.bottom || 0 + }; + this.width = options.width || null; + this.height = options.height || null; + +/* + if (!this.right) { + this.width = options.width; + } + + if (!this.bottom) { + this.height = options.height; + } +*/ + + this.fg = options.fg || 0x1ff; + this.bg = options.bg || 0x1ff; + this.bold = options.bold ? 1 : 0; + this.underline = options.underline ? 2 : 0; + + this.fixed = options.fixed || false; + this.border = options.border; + + this.children = options.children || []; + + this.clickable = this.clickable || false; + this.hover = this.hover || false; + + this.content = options.content || ''; +} + +Element.prototype.__proto__ = Node.prototype; + +Element.prototype.prepend = function(element) { + this.children.unshift(element); +}; + +Element.prototype.append = function(element) { + this.children.push(element); +}; + +Element.prototype.remove = function(element) { + var i = this.children.indexOf(element); + if (~i) this.children.splice(i, 1); +}; + +Element.prototype.detach = function(element) { + this.parent.remove(element); +}; + +Element.prototype.__defineGetter__('left', function() { + return (this.parent.left || 0) + this.offset.left; +}); + +Element.prototype.__defineGetter__('right', function() { + return (this.parent.right || 0) + this.offset.right; +}); + +Element.prototype.__defineGetter__('top', function() { + return (this.parent.top || 0) + this.offset.top; +}); + +Element.prototype.__defineGetter__('bottom', function() { + return (this.parent.bottom || 0) + this.offset.bottom; +}); + +/** + * Box + */ + +function Box(options) { + Element.call(this, options); +} + +Box.prototype.__proto__ = Element.prototype; + +Box.prototype.render = function() { + var lines = this.screen.lines + , xi = this.left + , xl = this.screen.cols - this.right + , yi = this.top + , yl = this.screen.rows - this.bottom + , cell + , attr + , ch + , ci = 0 + , cl = this.content.length; + + if (this.width) { + xl = this.width; + } + + if (this.height) { + yl = this.height; + } + +/* + if (this.border) { + attr = ((this.border.bold << 18) + (this.border.underline << 18)) | (this.border.fg << 9) | this.border.bg; + if (this.border.type === 'ascii') { + var i = xi, l = xl - 1; + for (; i < xl; i++) { + cell = lines[yi][i]; + ch = '─'; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[yi].dirty = true; + } + + cell = lines[yl - 1][i]; + ch = '─'; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[yl - 1].dirty = true; + } + } + + var i = yi, l = yl - 1; + for (; i < yl; i++) { + cell = lines[i][xi]; + ch = '─'; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[i].dirty = true; + } + + cell = lines[i][xl - 1]; + ch = '─'; + } + + cell = lines[yi][xi]; + ch = '┌'; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[yi].dirty = true; + } + + cell = lines[yi][xl - 1]; + ch = '┐'; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[yi].dirty = true; + } + + cell = lines[yl - 1][xi]; + ch = '└'; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[yl - 1].dirty = true; + } + + cell = lines[yl - 1][xl - 1]; + ch = '┘'; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[yl - 1].dirty = true; + } + } else if (this.border.type === 'bg') { + var i = xi, l = xl - 1; + for (; i < xl; i++) { + cell = lines[yi][i]; + ch = ' '; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[yi].dirty = true; + } + + cell = lines[yl - 1][i]; + ch = ' '; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[yl - 1].dirty = true; + } + } + var i = yi, l = yl - 1; + for (; i < yl; i++) { + cell = lines[i][xi]; + ch = ' '; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[i].dirty = true; + } + + cell = lines[i][xl - 1]; + ch = ' '; + if (attr !== cell[0] || ch !== cell[1]) { + cell[0] = attr; + cell[1] = ch; + lines[i].dirty = true; + } + } + } + xi++; + yi++; + xl--; + yl--; + } +*/ + + for (yi = this.top; yi < yl; yi++) { + for (xi = this.left; xi < xl; xi++) { + if (this.border && (yi === this.top || xi === this.left || yi === yl - 1 || xi === xl - 1)) { + attr = ((this.border.bold << 18) + (this.border.underline << 18)) | (this.border.fg << 9) | this.border.bg; + if (this.border.type === 'ascii') { + if (yi === this.top) { + if (xi === this.left) ch = '┌'; + else if (xi === xl - 1) ch = '┐'; + else ch = '─'; + } else (yi === yl - 1) { + if (xi === this.left) ch = '└'; + else if (xi === xl - 1) ch = '┘'; + else ch = '─'; + } else if (xi === this.left || xi === xl - 1) { + ch = '│'; + } + } else if (this.border.type === 'bg') { + ch = ' '; + } + } else { + attr = ((this.bold << 18) + (this.underline << 18)) | (this.fg << 9) | this.bg; + ch = this.content[ci++] || ' '; + } + cell = lines[yi][xi]; + if (attr !== cell[0] || ch !== cell[1]) { + lines[yi][xi][0] = attr; + lines[yi][xi][1] = ch; + lines[yi].dirty = true; + } + } + } + + this.children.forEach(function(el) { + el.render(); + }); +}; + +/** + * Text + */ + +function Text(options) { + Element.call(this, options); +} + +Text.prototype.__proto__ = Element.prototype; + +Text.prototype.render = function() { + var lines = this.screen.lines + , xi = this.left + , xl = this.screen.cols - this.right + , yi = this.top + , yl = this.screen.rows - this.bottom + , cell + , attr + , ch + , ci = 0 + , cl = this.content.length; + + if (this.width) { + xl = this.width; + } + + if (this.height) { + yl = this.height; + } + + for (yi = this.top; yi < yl; yi++) { + for (xi = this.left; xi < xl; xi++) { + cell = lines[yi][xi]; + attr = ((this.bold << 18) + (this.underline << 18)) | (this.fg << 9) | this.bg; + ch = this.content[ci++]; + if (!ch) break; + if (attr !== cell[0] || ch !== cell[1]) { + lines[yi][xi][0] = attr; + lines[yi][xi][1] = ch; + lines[yi].dirty = true; + } + } + } +}; + +/** + * ScrollableBox + */ + +function ScrollableBox(options) { + Box.call(this, options); + this.childOffset = 0; +} + +ScrollableBox.prototype.__proto__ = Box.prototype; + +ScrollableBox.prototype.scroll = function(offset) { + if (typeof offset === 'object') { + this.childOffset = this.children.indexOf(offset); + return; + } + this.childOffset += offset; + if (this.childOffset < 0) { + this.childOffset = 0; + } else if (this.childOffset >= this.children.length) { + this.childOffset = this.children.length - 1; + } +}; + +ScrollableBox.prototype._render = ScrollableBox.prototype.render; +ScrollableBox.prototype.render = function() { + var children = this.children; + this.children = this.children.slice(this.childOffset); + this._render(); + this.children = children; +}; + +/** + * List + */ + +function List(options) { + ScrollableBox.call(this, options); + this.items = []; + this.selected = 0; + + this.selectedBg = options.selectedBg; + this.selectedFg = options.selectedFg; + this.selectedBold = options.selectedBold; + this.selectedUnderline = options.selectedUnderline; + + if (options.items) { + options.items.forEach(this.add.bind(this)); + } +} + +List.prototype.__proto__ = ScrollableBox.prototype; + +List.prototype.add = function(item) { + this.items.push(new Text({ + screen: this.screen, + parent: this, + top: this.items.length, + content: item.content, + height: 1 + })); +}; + +List.prototype.select = function(index) { + if (typeof index === 'object') { + index = this.items.indexOf(index); + } + if (this.selectedBg) { + this.items[this.selected].bg = this.bg; + this.items[index].bg = this.selectedBg; + } + if (this.selectedFg) { + this.items[this.selected].fg = this.fg; + this.items[index].fg = this.selectedFg; + } + if (this.selectedBold != null) { + this.items[this.selected].bold = this.bold; + this.items[index].bold = this.selectedBold; + } + if (this.selectedUnderline != null) { + this.items[this.selected].underline = this.underline; + this.items[index].underline = this.selectedUnderline; + } + this.selected = index; +}; + +List.prototype.remove = function(index) { + if (typeof index === 'object') { + index = this.items.indexOf(index); + } + this.items.splice(index, 1); +}; + +/** + * Input + */ + +function Input(options) { + Box.call(this, options); +} + +Input.prototype.__proto__ = Box.prototype; + +/** + * Textbox + */ + +function Textbox(options) { + Input.call(this, options); +} + +Textbox.prototype.__proto__ = Input.prototype; + +/** + * Button + */ + +function Button(options) { + Input.call(this, options); +} + +Button.prototype.__proto__ = Input.prototype; + +/** + * Expose + */ + +exports.Screen = Screen; +exports.Box = Box; +exports.Text = Text; +exports.List = List;