add terminal widget and hover box.
This commit is contained in:
parent
4050376761
commit
879a072353
29
README.md
29
README.md
|
@ -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.
|
||||
|
|
|
@ -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();
|
|
@ -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
|
||||
|
|
248
lib/widget.js
248
lib/widget.js
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue