From f34cf1bdfe37b828f89839fe0ed9eee3af67df3c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 2 Apr 2015 17:59:04 -0700 Subject: [PATCH] table drawing and options. no tags for table style. readme. see #117. --- README.md | 29 ++++--- lib/widget.js | 158 ++++++++++++++++++--------------------- test/widget-listtable.js | 8 ++ test/widget-log.js | 2 +- test/widget-table.js | 8 ++ 5 files changed, 109 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index a92a51b..50f45e3 100644 --- a/README.md +++ b/README.md @@ -1128,7 +1128,9 @@ A stylized table of text elements. - inherits all from Box. - __rows/data__ - array of array of strings representing rows. - __pad__ - spaces to attempt to pad on the sides of each cell. `2` by default: - one space on each side. + one space on each side (only useful if the width is shrunken). +- __noCellBorders__ - do not draw inner cells. +- __fillCellBorders__ - fill cell borders with the adjacent background color. - __style.header__ - header style. - __style.cell__ - cell style. @@ -1162,7 +1164,8 @@ A stylized table of text elements with a list. - inherits all from List. - __rows/data__ - array of array of strings representing rows. - __pad__ - spaces to attempt to pad on the sides of each cell. `2` by default: - one space on each side. + one space on each side (only useful if the width is shrunken). +- __noCellBorders__ - do not draw inner cells. - __style.header__ - header style. - __style.cell__ - cell style. @@ -1318,15 +1321,15 @@ parent__, 50% as wide and 50% as tall as its parent. To access the calculated offsets, relative to the parent: ``` js -console.log(box.rleft); -console.log(box.rtop); +console.log(box.left); +console.log(box.top); ``` To access the calculated offsets, absolute (relative to the screen): ``` js -console.log(box.left); -console.log(box.top); +console.log(box.aleft); +console.log(box.atop); ``` #### Overlapping offsets and dimensions greater than parents' @@ -1408,7 +1411,7 @@ event occurred on. Returning `false` will cancel propagation up the tree. To actually render the screen buffer, you must call `render`. ``` js -box.setContent('Hello world.'); +box.setContent('Hello {#0fe1ab-fg}world{/}.'); screen.render(); ``` @@ -1443,10 +1446,14 @@ This will actually parse the xterm terminfo and compile every string capability to a javascript function: ``` js -var blessed = require('blessed') - , tput = blessed.tput('xterm-256color'); +var blessed = require('blessed'); -console.log(tput.setaf(4) + 'hello' + tput.sgr0()); +var tput = blessed.tput({ + terminal: 'xterm-256color', + extended: true +}); + +process.stdout.write(tput.setaf(4) + 'Hello' + tput.sgr0() + '\n'); ``` To play around with it on the command line, it works just like tput: @@ -1454,7 +1461,7 @@ To play around with it on the command line, it works just like tput: ``` bash $ tput.js setaf 2 $ tput.js sgr0 -$ echo "$(tput.js setaf 2)hello world$(tput.js sgr0)" +$ echo "$(tput.js setaf 2)Hello World$(tput.js sgr0)" ``` The main functionality is exposed in the main `blessed` module: diff --git a/lib/widget.js b/lib/widget.js index 3d93c45..1a0b71d 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -2306,9 +2306,7 @@ Element.prototype.parseContent = function(noTags) { }; Element.prototype.textLength = function(text) { - if (!this.options.tags && !this.options.parseTags) { - return text.length; - } + if (!this.parseTags) return text.length; return text .replace(/{(\/?)([\w\-,;!#]*)}/g, '') .replace(/\x1b\[[\d;]*m/g, '') @@ -6967,8 +6965,6 @@ function Table(options) { Box.call(this, options); - this.parseTags = true; - this.pad = options.pad != null ? options.pad : 2; @@ -6989,18 +6985,21 @@ Table.prototype.type = 'table'; Table.prototype._calculateMaxes = function() { var self = this; var maxes = []; - var total = 1; this.rows.forEach(function(row) { row.forEach(function(cell, i) { var clen = self.textLength(cell); - if (!maxes[i] || maxes[i] < clen + self.pad) { - maxes[i] = clen + self.pad; - total += maxes[i] + 1; + if (!maxes[i] || maxes[i] < clen) { + maxes[i] = clen; } }); }); + var total = maxes.reduce(function(total, max) { + return total + max; + }, 0); + total += maxes.length + 1; + // XXX There might be an issue with resizing where on the first resize event // width appears to be less than total if it's a percentage or left/right // combination. @@ -7008,15 +7007,19 @@ Table.prototype._calculateMaxes = function() { delete this.position.width; } - if (this.position.width != null && this.width > total) { - var w = this.width / maxes.length | 0; - var wr = this.width % maxes.length; - var drawn = (maxes.length + 1) + (maxes.length * this.pad); + if (this.position.width != null) { + var missing = this.width - total; + var w = missing / maxes.length | 0; + var wr = missing % maxes.length; maxes = maxes.map(function(max, i) { if (i === maxes.length - 1) { - return max + w + wr - drawn; + return max + w + wr; } - return max + w - drawn; + return max + w; + }); + } else { + maxes = maxes.map(function(max) { + return max + self.pad; }); } @@ -7030,9 +7033,6 @@ Table.prototype.setData = function(rows) { , line = '' , align = this.align; - var sheader = generateTags(this.style.header) - , scell = generateTags(this.style.cell); - this.rows = rows || []; this._calculateMaxes(); @@ -7074,16 +7074,6 @@ Table.prototype.setData = function(rows) { } } - if (!self.options.tags && !self.options.parseTags) { - cell = helpers.escape(cell); - } - - if (isHeader) { - cell = sheader.open + cell + sheader.close; - } else { - cell = scell.open + cell + scell.close; - } - text += cell; }); text += '\n'; @@ -7120,12 +7110,17 @@ Table.prototype.render = function() { , ry , i; - var header = this.sattr( + var dattr = this.sattr( + this.style, + this.style.fg, + this.style.bg); + + var hattr = this.sattr( this.style.header, this.style.header.fg, this.style.header.bg); - var cell = this.sattr( + var cattr = this.sattr( this.style.cell, this.style.cell.fg, this.style.cell.bg); @@ -7139,19 +7134,21 @@ Table.prototype.render = function() { , height = coords.yl - coords.yi - this.iheight / 2; // Apply attributes to header cells and cells. - // Problem: Does not allow for tags in cells. - // for (var y = this.iheight / 2; y < height; y++) { - // if (!lines[yi + y]) break; - // for (var x = this.iwidth / 2; x < width; x++) { - // if (y === this.iheight / 2) { - // lines[yi + y][xi + x][0] = header; - // } else { - // lines[yi + y][xi + x][0] = cell; - // } - // } - // } + for (var y = this.iheight / 2; y < height; y++) { + if (!lines[yi + y]) break; + for (var x = this.iwidth / 2; x < width; x++) { + if (!lines[yi + y][xi + x]) break; + // Check to see if it's not the default attr. Allows for tags: + if (lines[yi + y][xi + x][0] !== dattr) continue; + if (y === this.iheight / 2) { + lines[yi + y][xi + x][0] = hattr; + } else { + lines[yi + y][xi + x][0] = cattr; + } + } + } - if (!this.border) return coords; + if (!this.border || this.options.noCellBorders) return coords; // Draw border with correct angles. ry = 0; @@ -7161,6 +7158,7 @@ Table.prototype.render = function() { self._maxes.forEach(function(max, i) { rx += max; if (i === 0) { + if (!lines[yi + ry][xi + 0]) return; // left side if (ry === 0) { // top @@ -7176,6 +7174,7 @@ Table.prototype.render = function() { lines[yi + ry][xi + 0][1] = '\u251c'; // '├' } } else if (i === self._maxes.length - 1) { + if (!lines[yi + ry][xi + rx + 1]) return; // right side if (ry === 0) { // top @@ -7192,6 +7191,7 @@ Table.prototype.render = function() { } return; } + if (!lines[yi + ry][xi + rx + 1]) return; // center if (ry === 0) { // top @@ -7203,7 +7203,12 @@ Table.prototype.render = function() { lines[yi + ry][xi + rx][1] = '\u2534'; // '┴' } else { // middle - lines[yi + ry][xi + ++rx][0] = battr; + if (self.options.fillCellBorders) { + var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff; + lines[yi + ry][xi + ++rx][0] = (battr & ~0x1ff) | lbg; + } else { + lines[yi + ry][xi + ++rx][0] = battr; + } lines[yi + ry][xi + rx][1] = '\u253c'; // '┼' // ++rx; } @@ -7217,8 +7222,14 @@ Table.prototype.render = function() { rx = 0; self._maxes.slice(0, -1).forEach(function(max, i) { rx += max; + if (!lines[yi + ry][xi + rx + 1]) return; if (ry % 2 !== 0) { - lines[yi + ry][xi + ++rx][0] = battr; + if (self.options.fillCellBorders) { + var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff; + lines[yi + ry][xi + ++rx][0] = (battr & ~0x1ff) | lbg; + } else { + lines[yi + ry][xi + ++rx][0] = battr; + } lines[yi + ry][xi + rx][1] = '\u2502'; // '│' } else { rx++; @@ -7228,7 +7239,14 @@ Table.prototype.render = function() { self._maxes.forEach(function(max, i) { while (max--) { if (ry % 2 === 0) { - lines[yi + ry][xi + rx][0] = battr; + if (!lines[yi + ry]) break; + if (!lines[yi + ry][xi + rx + 1]) break; + if (self.options.fillCellBorders) { + var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff; + lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg; + } else { + lines[yi + ry][xi + rx][0] = battr; + } lines[yi + ry][xi + rx][1] = '\u2500'; // '─' } rx++; @@ -7303,42 +7321,7 @@ ListTable.prototype.__proto__ = List.prototype; ListTable.prototype.type = 'list-table'; -ListTable.prototype._calculateMaxes = function() { - var self = this; - var maxes = []; - var total = 1; - - this.rows.forEach(function(row) { - row.forEach(function(cell, i) { - var clen = self.textLength(cell); - if (!maxes[i] || maxes[i] < clen + self.pad) { - maxes[i] = clen + self.pad; - total += maxes[i] + 1; - } - }); - }); - - // XXX There might be an issue with resizing where on the first resize event - // width appears to be less than total if it's a percentage or left/right - // combination. - if (this.width < total) { - delete this.position.width; - } - - if (this.position.width != null && this.width > total) { - var w = this.width / maxes.length | 0; - var wr = this.width % maxes.length; - var drawn = (maxes.length + 1) + (maxes.length * this.pad); - maxes = maxes.map(function(max, i) { - if (i === maxes.length - 1) { - return max + w + wr - drawn; - } - return max + w - drawn; - }); - } - - return this._maxes = maxes; -}; +ListTable.prototype._calculateMaxes = Table.prototype._calculateMaxes; ListTable.prototype.setRows = ListTable.prototype.setData = function(rows) { @@ -7443,7 +7426,7 @@ ListTable.prototype.render = function() { var width = coords.xl - coords.xi - this.iwidth / 2 , height = coords.yl - coords.yi - this.iheight / 2; - if (!this.border) return coords; + if (!this.border || this.options.noCellBorders) return coords; // Draw border with correct angles. ry = 0; @@ -7452,6 +7435,7 @@ ListTable.prototype.render = function() { rx = 0; self._maxes.slice(0, -1).forEach(function(max, i) { rx += max; + if (!lines[yi + ry][xi + rx + 1]) return; // center if (ry === 0) { // top @@ -7475,7 +7459,13 @@ ListTable.prototype.render = function() { rx = 0; self._maxes.slice(0, -1).forEach(function(max, i) { rx += max; - lines[yi + ry][xi + ++rx][0] = battr; + if (!lines[yi + ry][xi + rx + 1]) return; + if (self.options.fillCellBorders !== false) { + var lbg = lines[yi + ry][xi + rx][0] & 0x1ff; + lines[yi + ry][xi + ++rx][0] = (battr & ~0x1ff) | lbg; + } else { + lines[yi + ry][xi + ++rx][0] = battr; + } lines[yi + ry][xi + rx][1] = '\u2502'; // '│' }); } @@ -8340,7 +8330,7 @@ helpers.parseTags = function(text) { helpers.generateTags = generateTags; helpers.textLength = function(text) { - return Element.prototype.textLength.call({ options: { tags: true } }, text); + return Element.prototype.textLength.call({ parseTags: true }, text); }; helpers.attrToBinary = function(obj, fg, bg, target) { diff --git a/test/widget-listtable.js b/test/widget-listtable.js index 627a67a..47fc8a8 100644 --- a/test/widget-listtable.js +++ b/test/widget-listtable.js @@ -36,6 +36,14 @@ var table = blessed.listtable({ } }); +var data = [ + [ 'Animals', 'Foods', 'Times', 'Numbers' ], + [ 'Elephant', 'Apple', '1:00am', 'One' ], + [ 'Bird', 'Orange', '2:15pm', 'Two' ], + [ 'T-Rex', 'Taco', '8:45am', 'Three' ], + [ 'Mouse', 'Cheese', '9:05am', 'Four' ] +]; + var data = [ [ 'Animals', 'Foods', 'Times' ], [ 'Elephant', 'Apple', '1:00am' ], diff --git a/test/widget-log.js b/test/widget-log.js index 9592543..862ecea 100644 --- a/test/widget-log.js +++ b/test/widget-log.js @@ -33,7 +33,7 @@ var logger = blessed.log({ logger.focus(); setInterval(function() { - logger.log('Hello {green-fg}world{/}: {bold}%s{/bold}.', Date.now().toString(36)); + logger.log('Hello {#0fe1ab-fg}world{/}: {bold}%s{/bold}.', Date.now().toString(36)); if (Math.random() < 0.30) { logger.log({foo:{bar:{baz:true}}}); } diff --git a/test/widget-table.js b/test/widget-table.js index 391010c..626af4c 100644 --- a/test/widget-table.js +++ b/test/widget-table.js @@ -29,6 +29,14 @@ var table = blessed.table({ } }); +var data = [ + [ 'Animals', 'Foods', 'Times', 'Numbers' ], + [ 'Elephant', 'Apple', '1:00am', 'One' ], + [ 'Bird', 'Orange', '2:15pm', 'Two' ], + [ 'T-Rex', 'Taco', '8:45am', 'Three' ], + [ 'Mouse', 'Cheese', '9:05am', 'Four' ] +]; + var data = [ [ 'Animals', 'Foods', 'Times' ], [ 'Elephant', 'Apple', '1:00am' ],