better screen and parent handling. scroll improvements.

This commit is contained in:
Christopher Jeffrey 2013-06-09 08:33:23 -05:00
parent 48658c4cf9
commit 2cbbe786fb
2 changed files with 155 additions and 51 deletions

View File

@ -51,27 +51,67 @@ var EventEmitter = require('events').EventEmitter;
function Node(options) {
EventEmitter.call(this);
this.options = options || {};
this.children = options.children || [];
if (this._isScreen && !this.focused) this.focused = this.children[0];
this.screen = this.screen
|| Screen._default
|| (function(){throw new Error('No active screen.')})();
this.parent = options.parent || null; // this.screen;
this.children = [];
(options.children || []).forEach(this.append.bind(this));
if (this._isScreen && !this.focused) {
this.focused = this.children[0];
}
}
Node.prototype.__proto__ = EventEmitter.prototype;
Node.prototype.prepend = function(element) {
if (this._isScreen && !this.focused) this.focused = element;
this.children.unshift(element);
element.parent = this;
if (this._isScreen && !this.focused) {
this.focused = element;
}
if (!~this.children.indexOf(element)) {
this.children.unshift(element);
}
// element.emit('reparent', this);
// this.emit('append', element);
};
Node.prototype.append = function(element) {
if (this._isScreen && !this.focused) this.focused = element;
this.children.push(element);
element.parent = this;
if (this._isScreen && !this.focused) {
this.focused = element;
}
if (!~this.children.indexOf(element)) {
this.children.push(element);
}
// element.emit('reparent', this);
// this.emit('append', element);
};
Node.prototype.remove = function(element) {
element.parent = null; // this.screen;
var i = this.children.indexOf(element);
if (~i) this.children.splice(i, 1);
if (this._isScreen && this.focused === element) this.focused = this.children[0];
if (~i) {
this.children.splice(i, 1);
}
if (this._isScreen && this.focused === element) {
this.focused = this.children[0];
}
// element.emit('reparent', null);
// this.emit('remove', element);
};
Node.prototype.detach = function(element) {
@ -85,10 +125,13 @@ Node.prototype.detach = function(element) {
function Screen(options) {
var self = this;
if (!Screen._default) {
Screen._default = this;
}
Node.call(this, options);
this._isScreen = true;
this.children = options.children || [];
this.program = options.program;
this.tput = this.program.tput;
this.dattr = ((0 << 18) | (0x1ff << 9)) | 0x1ff;
@ -112,6 +155,8 @@ function Screen(options) {
this.input = [];
}
Screen._default = null;
Screen.prototype.__proto__ = Node.prototype;
// TODO: Bubble events.
@ -217,6 +262,7 @@ Screen.prototype.render = function() {
el.render();
});
this.draw(0, this.rows - 1);
// this.emit('draw');
};
Screen.prototype.draw = function(start, end) {
@ -405,8 +451,7 @@ Screen.prototype.clearRegion = function(xi, xl, yi, yl) {
function Element(options) {
Node.call(this, options);
this.screen = options.screen;
this.parent = options.parent || (function(){throw Error('No parent.')})();
this.position = {
left: options.left || 0,
right: options.right || 0,
@ -438,8 +483,6 @@ function Element(options) {
if (this.border.bg === -1) this.border.bg = exports.NORMAL;
}
this.children = options.children || [];
if (options.clickable) {
this.screen._listenMouse(this);
}
@ -1000,7 +1043,9 @@ function List(options) {
}
if (this.children.length) {
this.select(0);
// Will throw if this.parent is not set!
// Probably not good to have in a constructor.
// this.select(0);
}
if (this.mouse) {
@ -1088,24 +1133,6 @@ List.prototype.select = function(index) {
this.items[index].underline = this.selectedUnderline;
}
/*
var diff = index - this.selected;
this.childOffset += diff;
this.selected = index;
var visible = this.height - (this.border ? 2 : 0);
if (this.childOffset > visible - 1) {
var d = this.childOffset - (visible - 1);
this.childOffset -= d;
this.childBase += d;
} else if (this.childOffset < 0) {
var d = this.childOffset;
this.childOffset += -d;
this.childBase += d;
}
*/
var diff = index - this.selected;
this.selected = index;
this.scroll(diff);
@ -1150,8 +1177,17 @@ ScrollableText.prototype.__proto__ = ScrollableBox.prototype;
ScrollableText.prototype._scroll = ScrollableText.prototype.scroll;
ScrollableText.prototype.scroll = function(offset) {
var ret = this._scroll(offset);
var base = this.childBase
, ret = this._scroll(offset)
, diff = this.childBase - base;
if (diff === 0) {
return ret;
}
// When scrolling text, we want to be able to handle SGR codes as well as line
// feeds. This allows us to take preformatted text output from other programs
// and put it in a scrollable text box.
if (this.content != null) {
var cb = this.childBase
, data = this.render(true)
@ -1159,8 +1195,71 @@ ScrollableText.prototype.scroll = function(offset) {
, xl = data.xl
, xxl = xl - (this.border ? 1 : 0)
, xxi
, ci = 0;
, ci = 0
, xxxi
, cci;
if (this.contentIndex != null) {
ci = this.contentIndex;
cb = diff;
// Scroll up.
// This is confusing because we have to parse the
// text backwards if we want to be efficient instead
// of being O(ridiculous).
// TODO: Remove code duplication.
if (cb < 0) {
cb = -cb;
while (cb--) {
if (ci < 0) break;
for (xxi = xi + (this.border ? 1 : 0); xxi < xxl; xxi++) {
if (this.content[ci] === '\n' || this.content[ci] === '\r') {
ci--;
// TODO: Come up with a cleaner way of doing this:
for (xxxi = xi + (this.border ? 1 : 0); xxxi < xxl; xxxi++) {
if (this.content[ci] === '\n' || this.content[ci] === '\r') {
ci++;
break;
} else if (this.content[ci] === 'm') {
for (cci = ci - 1; cci >= 0; cci--) {
if (/[^\x1b\[\d;]/.test(this.content[cci])) {
break;
}
if (this.content[cci] === '\x1b') {
xxxi -= (ci - cci);
ci = cci;
break;
}
}
ci--;
} else {
ci--;
}
}
break;
} else if (this.content[ci] === 'm') {
for (cci = ci - 1; cci >= 0; cci--) {
if (/[^\x1b\[\d;]/.test(this.content[cci])) {
break;
}
if (this.content[cci] === '\x1b') {
xxi -= (ci - cci);
ci = cci;
break;
}
}
ci--;
} else {
ci--;
}
}
}
if (ci < 0) ci = 0;
this.contentIndex = ci;
return ret;
}
}
// Scroll down.
while (cb--) {
for (xxi = xi + (this.border ? 1 : 0); xxi < xxl; xxi++) {
if (this.content[ci] === '\n' || this.content[ci] === '\r') {
@ -1168,12 +1267,25 @@ ScrollableText.prototype.scroll = function(offset) {
break;
} else if (this.content[ci] === '\x1b') {
for (; ci < this.content.length; ci++) {
xxi--;
if (this.content[ci] === 'm') break;
}
ci++;
} else {
ci++;
}
ci++;
}
}
// TODO: Parse the last few lines to see how
// many characters we need to subtract.
if (ci >= this.content.length) {
if (this.contentIndex >= this.content.length) {
this.childBase = base;
}
ci = this.content.length;
}
this.contentIndex = ci;
}
@ -1285,6 +1397,7 @@ ProgressBar.prototype.progress = function(filled) {
* Helpers
*/
// Convert an SGR string to our own attribute format.
function attrCode(code, cur) {
var flags = (cur >> 18) & 0x1ff;
var fg = (cur >> 9) & 0x1ff;

View File

@ -10,16 +10,12 @@ screen = new blessed.Screen({
});
screen.append(new blessed.Text({
screen: screen,
parent: screen,
top: 0,
left: 2,
content: 'Welcome to my program'
}));
screen.append(new blessed.Line({
screen: screen,
parent: screen,
orientation: 'horizontal',
top: 1,
left: 0,
@ -64,8 +60,6 @@ screen.children[0].append(new blessed.Box({
*/
var list = new blessed.List({
screen: screen,
parent: screen,
mouse: true,
fg: 4,
bg: -1,
@ -94,20 +88,19 @@ var list = new blessed.List({
});
screen.append(list);
list.select(0);
list.prepend(new blessed.Text({
screen: screen,
parent: list,
left: 2,
content: ' My list '
}));
list.on('keypress', function(ch, key) {
if (key.name === 'up') {
if (key.name === 'up' || key.name === 'k') {
list.up();
screen.render();
return;
} else if (key.name === 'down') {
} else if (key.name === 'down' || key.name === 'j') {
list.down();
screen.render();
return;
@ -119,8 +112,6 @@ list.on('click', function() {
});
var progress = new blessed.ProgressBar({
screen: screen,
parent: screen,
fg: 4,
bg: -1,
barBg: -1,
@ -144,9 +135,9 @@ var lorem = 'Lorem ipsum \x1b[41mdolor sit amet, \nconsectetur adipisicing elit,
var lorem = require('fs').readFileSync(__dirname + '/../t.log', 'utf8');
//lorem = lorem.replace(/\x1b[^m]*m/g, '');
var stext = new blessed.ScrollableText({
screen: screen,
parent: screen,
mouse: true,
content: lorem,
fg: 4,
@ -171,11 +162,11 @@ stext.on('click', function() {
screen.append(stext);
stext.on('keypress', function(ch, key) {
if (key.name === 'up') {
if (key.name === 'up' || key.name === 'k') {
stext.scroll(-1);
screen.render();
return;
} else if (key.name === 'down') {
} else if (key.name === 'down' || key.name === 'j') {
stext.scroll(1);
screen.render();
return;