mirror of
https://github.com/embarklabs/neo-blessed.git
synced 2025-02-22 07:38:06 +00:00
better screen and parent handling. scroll improvements.
This commit is contained in:
parent
48658c4cf9
commit
2cbbe786fb
183
lib/widget.js
183
lib/widget.js
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user