add terminal widget and hover box.

This commit is contained in:
Christopher Jeffrey 2015-01-24 18:34:57 -08:00
parent 4050376761
commit 879a072353
5 changed files with 358 additions and 47 deletions

View File

@ -287,6 +287,7 @@ The base element.
- **focused** - element is focused.
- **hidden** - whether the element is hidden.
- **label** - a simple text label for the element.
- **hoverText** - a floating text label for the element which appears on mouseover.
- **align** - text alignment: `left`, `center`, or `right`.
- **valign** - vertical text alignment: `top`, `middle`, or `bottom`.
- **shrink** - shrink/flex/grow to content and child elements. width/height
@ -367,8 +368,12 @@ The base element.
- **setIndex(z)** - set the z-index of the element (changes rendering order).
- **setFront()** - put the element in front of its siblings.
- **setBack()** - put the element in back of its siblings.
- **setLabel(text)** - set the label text for the top-left corner.
- **setLabel(text/options)** - set the label text for the top-left corner.
example options: `{text:'foo',side:'left'}`
- **removeLabel()** - remove the label completely.
- **setHover(text/options)** - set the hover text for the bottom-right corner.
example options: `{text:'foo'}`
- **removeHover()** - remove the hover label completely.
###### Content Methods
@ -846,6 +851,28 @@ A radio button which can be used in a form element.
- inherits all from Checkbox.
#### Terminal (from Box)
A box which spins up a pseudo terminal and renders the output. (Requires
term.js to be installed).
##### Options:
- inherits all from Box.
##### Properties:
- inherits all from Box.
##### Events:
- inherits all from Box.
##### Methods:
- inherits all from Box.
### Positioning
Offsets may be a number, a percentage (e.g. `50%`), or a keyword (e.g.

85
example/multiplex.js Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env node
/**
* multiplex.js
* https://github.com/chjj/blessed
* Copyright (c) 2013-2015, Christopher Jeffrey (MIT License)
* A terminal multiplexer created by blessed.
*/
process.title = 'multiplex.js';
var blessed = require('blessed')
, pty = require('pty.js');
var screen = blessed.screen();
var terminals = [];
var pty0 = pty.fork('bash', [], {
name: process.env.TERM,
cols: process.stdout.columns,
rows: process.stdout.rows,
cwd: process.env.HOME,
env: process.env
});
terminals[0] = blessed.terminal({
parent: screen,
//mouse: true,
left: 0,
top: 2,
bottom: 2,
width: '40%',
border: 'line',
handler: function(data) {
pty0.write(data);
screen.render();
}
});
pty0.on('data', function(data) {
terminals[0].write(data);
//terminals[0].scrollTo(terminals[0]._scrollBottom());
screen.render();
});
var pty1 = pty.fork('bash', [], {
name: process.env.TERM,
cols: process.stdout.columns,
rows: process.stdout.rows,
cwd: process.env.HOME,
env: process.env
});
terminals[1] = blessed.terminal({
parent: screen,
//mouse: true,
right: 2,
top: 2,
bottom: 2,
width: '40%',
border: 'line',
handler: function(data) {
pty1.write(data);
screen.render();
}
});
pty1.on('data', function(data) {
terminals[1].write(data);
//terminals[1].scrollTo(terminals[1]._scrollBottom());
screen.render();
});
terminals[0].focus();
screen.on('keypress', function() {
screen.render();
});
screen.key('C-c', function() {
process.exit(0);
});
screen.render();

View File

@ -37,7 +37,7 @@ exports.match = function(r1, g1, b1) {
g2 = c[1];
b2 = c[2];
diff = colorDistance3dWeight(r1, g1, b1, r2, g2, b2);
diff = colorDistance(r1, g1, b1, r2, g2, b2);
if (diff === 0) {
li = i;
@ -85,49 +85,16 @@ exports.hexToRGB = function(hex) {
// As it happens, comparing how similar two colors are is really hard. Here is
// one of the simplest solutions, which doesn't require conversion to another
// color space, posted on stackoverflow. Maybe someone better at math can
// color space, posted on stackoverflow[1]. Maybe someone better at math can
// propose a superior solution.
// [1] http://stackoverflow.com/questions/1633828
// http://stackoverflow.com/questions/9018016
function colorDistance3d(r1, g1, b1, r2, g2, b2) {
var d;
d = Math.sqrt(
Math.pow(r2 - r1, 2)
+ Math.pow(g2 - g1, 2)
+ Math.pow(b2 - b1, 2));
d /= Math.sqrt(
Math.pow(255, 2)
+ Math.pow(255, 2)
+ Math.pow(255, 2));
return d;
}
// http://stackoverflow.com/questions/1633828
function colorDistance3dWeight(r1, g1, b1, r2, g2, b2) {
function colorDistance(r1, g1, b1, r2, g2, b2) {
return Math.pow(30 * (r1 - r2), 2)
+ Math.pow(59 * (g1 - g2), 2)
+ Math.pow(11 * (b1 - b2), 2);
}
function colorDistance3dWeightSqrt(r1, g1, b1, r2, g2, b2) {
var d;
d = Math.sqrt(
Math.pow(30 * (r2 - r1), 2)
+ Math.pow(59 * (g2 - g1), 2)
+ Math.pow(11 * (b2 - b1), 2));
d /= Math.sqrt(
Math.pow(255, 2)
+ Math.pow(255, 2)
+ Math.pow(255, 2));
return d;
}
exports._cache = {};
// XTerm Colors

View File

@ -1617,6 +1617,7 @@ Screen.prototype.setEffects = function(el, fel, over, out, effects, temp) {
var val = effects[key];
if (val !== null && typeof val === 'object') {
tmp[key] = tmp[key] || {};
// element.style[key] = element.style[key] || {};
Object.keys(val).forEach(function(k) {
var v = val[k];
tmp[key][k] = element.style[key][k];
@ -1636,6 +1637,7 @@ Screen.prototype.setEffects = function(el, fel, over, out, effects, temp) {
var val = effects[key];
if (val !== null && typeof val === 'object') {
tmp[key] = tmp[key] || {};
// element.style[key] = element.style[key] || {};
Object.keys(val).forEach(function(k) {
if (tmp[key].hasOwnProperty(k)) {
element.style[key][k] = tmp[key][k];
@ -1792,6 +1794,10 @@ function Element(options) {
this.setLabel(options.label);
}
if (options.hoverText) {
this.setHover(options.hoverText);
}
// TODO: Possibly move this to Node for screen.on('mouse', ...).
this.on('newListener', function fn(type) {
//type = type.split(' ').slice(1).join(' ');
@ -2340,29 +2346,55 @@ Element.prototype.clearPos = function(get) {
lpos.yi, lpos.yl);
};
Element.prototype.setLabel = function(text) {
Element.prototype.setLabel = function(options) {
var self = this;
if (typeof options === 'string') {
options = { text: options };
}
if (this._label) {
this._label.setContent(text);
this._label.setContent(options.text);
if (options.side !== 'right') {
this._label.rleft = 2 + (this.border ? -1 : 0);
this._label.position.right = undefined;
if (!this.screen.autoPadding) {
this._label.rleft = 2;
}
} else {
this._label.rright = 2 + (this.border ? -1 : 0);
this._label.position.left = undefined;
if (!this.screen.autoPadding) {
this._label.rright = 2;
}
}
return;
}
this._label = new Box({
screen: this.screen,
parent: this,
content: text,
left: 2 + (this.border ? -1 : 0),
content: options.text,
top: this.border ? -1 : 0,
tags: this.parseTags,
shrink: true,
style: this.style.label
});
if (options.side !== 'right') {
this._label.rleft = 2 + (this.border ? -1 : 0);
} else {
this._label.rright = 2 + (this.border ? -1 : 0);
}
this._label._isLabel = true;
if (!this.screen.autoPadding) {
this._label.rleft = 2;
if (options.side !== 'right') {
this._label.rleft = 2;
} else {
this._label.rright = 2;
}
this._label.rtop = 0;
}
@ -2381,6 +2413,75 @@ Element.prototype.removeLabel = function() {
delete this._label;
};
Element.prototype.setHover = function(options) {
var self = this;
if (typeof options === 'string') {
options = { text: options };
}
if (this._hover) {
this._hover.setContent(options.text);
return;
}
this._hover = new Box({
screen: this.screen,
content: options.text,
left: 0,
top: 0,
tags: this.parseTags,
height: 'shrink',
width: 'shrink',
border: 'line',
style: {
border: {
fg: 'default'
},
bg: 'default',
fg: 'default'
}
});
this._hover._isHover = true;
this._hover._.over = false;
this.on('mouseover', function(data) {
self._hover._.over = true;
});
this.on('mouse', function(data) {
if (!self._hover._.over) return;
var el = self._hover
, x = data.x
, y = data.y;
self.screen.append(el);
while (el = el.parent) {
x -= el.rleft;
y -= el.rtop;
}
self._hover.rleft = x;
self._hover.rtop = y;
self.screen.render();
});
this.on('mouseout', function() {
self._hover._.over = false;
self._hover.detach();
self.screen.render();
});
};
Element.prototype.removeHover = function() {
this._hover.detach();
delete this._hover;
};
/**
* Positioning
*/
@ -5644,7 +5745,9 @@ Prompt.prototype.__proto__ = Box.prototype;
Prompt.prototype.type = 'prompt';
Prompt.prototype.type = function(text, value, callback) {
Prompt.prototype.input =
Prompt.prototype.setInput =
Prompt.prototype.readInput = function(text, value, callback) {
var self = this;
var okay, cancel;
@ -5683,8 +5786,6 @@ Prompt.prototype.type = function(text, value, callback) {
this.screen.render();
};
Prompt.prototype.type.toString = function() { return 'prompt'; };
/**
* Question
*/
@ -6403,6 +6504,135 @@ Passbox.prototype.__proto__ = Textbox.prototype;
Passbox.prototype.type = 'passbox';
/**
* Terminal
*/
function Terminal(options) {
var self = this;
if (!(this instanceof Node)) {
return new Terminal(options);
}
options = options || {};
options.scrollable = true;
Box.call(this, options);
var element = {
// window
get document() { return element; },
navigator: { userAgent: 'node.js' },
// document
get defaultView() { return element; },
get documentElement() { return element; },
createElement: function() { return element; },
// element
get ownerDocument() { return element; },
addEventListener: function() {},
removeEventListener: function() {},
getElementsByTagName: function(name) { return [element]; },
getElementById: function() { return element; },
// parentNode: null,
// offsetParent: null,
parentNode: element,
offsetParent: element,
appendChild: function() {},
removeChild: function() {},
setAttribute: function() {},
getAttribute: function() {},
style: {},
focus: function() {},
blur: function() {},
console: console
};
this.term = require('term.js')({
cols: this.width - this.iwidth,
rows: this.height - this.iheight,
context: element,
document: element,
body: element,
parent: element
});
this.term.refresh = function() {
self.screen.render();
};
this.term.keyDown = function() {};
this.term.keyPress = function() {};
this.term.open(element);
// Emits key sequences in html-land.
// Technically not necessary here.
// In reality if we wanted to be neat, we would overwrite the keyDown and
// keyPress methods with our own node.js-keys->terminal-keys methods, but
// since all the keys are already coming in as escape sequences, we can just
// send the input directly to the handler/socket (see below).
// this.term.on('data', function(data) {
// options.handler(data);
// });
// Incoming keys and mouse inputs.
// NOTE: Cannot pass mouse events - coordinates will be off!
this.screen.program.input.on('data', function(data) {
if (self.screen.focused === self) {
options.handler(data);
}
});
this.term.on('title', function(title) {
self.title = title;
self.emit('title', title);
});
this.on('resize', function() {
setImmediate(function() {
self.term.resize(self.width - self.iwidth, self.height - self.iheight);
});
});
this.once('render', function() {
self.term.resize(self.width - self.iwidth, self.height - self.iheight);
});
}
Terminal.prototype.__proto__ = Box.prototype;
Terminal.prototype.type = 'terminal';
Terminal.prototype.write = function(data) {
return this.term.write(data);
};
Terminal.prototype.render = function() {
var ret = this._render();
if (!ret) return;
var xi = ret.xi + this.ileft
, xl = ret.xl - this.iright
, yi = ret.yi + this.itop
, yl = ret.yl - this.ibottom;
var scrollback = this.term.lines.length - (yl - yi);
for (var y = yi; y < yl; y++) {
var line = this.screen.lines[y];
for (var x = xi; x < xl; x++) {
line[x][0] = this.term.lines[scrollback + y - yi][x - xi][0];
line[x][1] = this.term.lines[scrollback + y - yi][x - xi][1];
}
line.dirty = true;
}
return ret;
};
/**
* Helpers
*/
@ -6521,4 +6751,6 @@ exports.Listbar = exports.listbar = Listbar;
exports.DirManager = exports.dirmanager = DirManager;
exports.Passbox = exports.passbox = Passbox;
exports.Terminal = exports.terminal = Terminal;
exports.helpers = helpers;

View File

@ -69,7 +69,7 @@ var loader = blessed.loading({
vi: true
});
prompt.type('Question?', '', function(err, value) {
prompt.input('Question?', '', function(err, value) {
question.ask('Question?', function(err, value) {
msg.display('Hello world!', 3, function(err) {
msg.display('Hello world again!', -1, function(err) {