improve focus. fix getCoords. readme.

This commit is contained in:
Christopher Jeffrey 2013-07-29 10:22:08 -05:00
parent a3e2983b4b
commit d2226c3295
3 changed files with 147 additions and 83 deletions

View File

@ -193,14 +193,15 @@ The screen on which every other node renders.
- **clearRegion(x1, x2, y1, y2)** - clear any region on the screen. - **clearRegion(x1, x2, y1, y2)** - clear any region on the screen.
- **fillRegion(attr, ch, x1, x2, y1, y2)** - fill any region with a character - **fillRegion(attr, ch, x1, x2, y1, y2)** - fill any region with a character
of a certain attribute. of a certain attribute.
- **focus(offset)** - focus element by offset of focusable elements. - **focusOffset(offset)** - focus element by offset of focusable elements.
- **focusPrev()** - focus previous element in the index. - **focusPrevious()** - focus previous element in the index.
- **focusNext()** - focus next element in the index. - **focusNext()** - focus next element in the index.
- **focusPush(element)** - push element on the focus stack (equivalent to - **focusPush(element)** - push element on the focus stack (equivalent to
`screen.focused = el`). `screen.focused = el`).
- **focusPop()/focusLast()** - pop element off the focus stack. - **focusPop()** - pop element off the focus stack.
- **saveFocus()** - save the focused element. - **saveFocus()** - save the focused element.
- **restoreFocus()** - restore the saved focused element. - **restoreFocus()** - restore the saved focused element.
- **rewindFocus()** - "rewind" focus to the last visible and attached element.
- **key(name, listener)** - bind a keypress listener for a specific key. - **key(name, listener)** - bind a keypress listener for a specific key.
- **onceKey(name, listener)** - bind a keypress listener for a specific key - **onceKey(name, listener)** - bind a keypress listener for a specific key
once. once.

View File

@ -30,6 +30,7 @@ function Node(options) {
options = options || {}; options = options || {};
this.options = options; this.options = options;
this.screen = this.screen this.screen = this.screen
|| options.screen
|| Screen.global || Screen.global
|| (function(){throw new Error('No active screen.')})(); || (function(){throw new Error('No active screen.')})();
this.parent = options.parent || null; this.parent = options.parent || null;
@ -38,19 +39,15 @@ function Node(options) {
this.uid = Node.uid++; this.uid = Node.uid++;
this.index = -1; this.index = -1;
if (this.type !== 'screen') {
this.detached = true;
}
if (this.parent) { if (this.parent) {
this.parent.append(this); this.parent.append(this);
} }
if (!this.parent) {
this._detached = true;
}
(options.children || []).forEach(this.append.bind(this)); (options.children || []).forEach(this.append.bind(this));
// if (this.type === 'screen' && !this.focused) {
// this.focused = this.children[0];
// }
} }
Node.uid = 0; Node.uid = 0;
@ -60,37 +57,31 @@ Node.prototype.__proto__ = EventEmitter.prototype;
Node.prototype.type = 'node'; Node.prototype.type = 'node';
Node.prototype.insert = function(element, i) { Node.prototype.insert = function(element, i) {
var old = element.parent; var self = this;
element.detach(); element.detach();
element.parent = this; element.parent = this;
if (this.type === 'screen' && !this.focused) { if (i === 0) {
this.focused = element; this.children.unshift(element);
} } else if (i === this.children.length) {
this.children.push(element);
if (!~this.children.indexOf(element)) { } else {
if (i === 0) { this.children.splice(i, 0, element);
this.children.unshift(element);
} else if (i === this.children.length) {
this.children.push(element);
} else {
this.children.splice(i, 0, element);
}
} }
element.emit('reparent', this); element.emit('reparent', this);
this.emit('adopt', element); this.emit('adopt', element);
if (!old) { (function emit(el) {
// element.emitDescendants('attach', function(el) { var n = el.detached !== self.detached;
// el._detached = false; el.detached = self.detached;
// }); if (n) el.emit('attach');
(function emit(el) { el.children.forEach(emit);
el._detached = false; })(element);
el.emit('attach');
if (el.children) el.children.forEach(emit); if (!this.screen.focused) {
})(element); this.screen.focused = element;
} }
}; };
@ -115,12 +106,16 @@ Node.prototype.insertAfter = function(element, other) {
Node.prototype.remove = function(element) { Node.prototype.remove = function(element) {
if (element.parent !== this) return; if (element.parent !== this) return;
var i = this.children.indexOf(element);
if (!~i) return;
if (this.type !== 'screen') {
this.clearPos();
}
element.parent = null; element.parent = null;
var i = this.children.indexOf(element); this.children.splice(i, 1);
if (~i) {
this.children.splice(i, 1);
}
if (this.type !== 'screen') { if (this.type !== 'screen') {
i = this.screen.clickable.indexOf(element); i = this.screen.clickable.indexOf(element);
@ -129,23 +124,19 @@ Node.prototype.remove = function(element) {
if (~i) this.screen.keyable.splice(i, 1); if (~i) this.screen.keyable.splice(i, 1);
} }
if (this.type === 'screen' && this.focused === element) {
this.focused = this.children[0];
}
element.emit('reparent', null); element.emit('reparent', null);
this.emit('remove', element); this.emit('remove', element);
// element.emitDescendants('detach', function(el) {
// el._detached = true;
// });
(function emit(el) { (function emit(el) {
el._detached = true; var n = el.detached !== true;
el.emit('detach'); el.detached = true;
if (el.children) el.children.forEach(emit); if (n) el.emit('detach');
el.children.forEach(emit);
})(element); })(element);
// this.clearPos(); if (this.screen.focused === element) {
this.screen.rewindFocus();
}
}; };
Node.prototype.detach = function() { Node.prototype.detach = function() {
@ -317,9 +308,7 @@ function Screen(options) {
self.render(); self.render();
(function emit(el) { (function emit(el) {
el.emit('resize'); el.emit('resize');
if (el.children) { el.children.forEach(emit);
el.children.forEach(emit);
}
})(self); })(self);
// self.emitDescendants('resize'); // self.emitDescendants('resize');
} }
@ -1333,13 +1322,18 @@ Screen.prototype.codeAttr = function(code) {
return '\x1b[' + out + 'm'; return '\x1b[' + out + 'm';
}; };
Screen.prototype.focus = function(offset) { Screen.prototype.focusOffset = function(offset) {
var shown = this.keyable.filter(function(el) { var shown = this.keyable.filter(function(el) {
return el.visible; return el.visible;
}); }).length;
if (!shown.length || !offset) return;
if (!shown || !offset) {
return;
}
var i = this.keyable.indexOf(this.focused); var i = this.keyable.indexOf(this.focused);
if (!~i) return; if (!~i) return;
if (offset > 0) { if (offset > 0) {
while (offset--) { while (offset--) {
if (++i > this.keyable.length - 1) i = 0; if (++i > this.keyable.length - 1) i = 0;
@ -1352,28 +1346,33 @@ Screen.prototype.focus = function(offset) {
if (!this.keyable[i].visible) offset++; if (!this.keyable[i].visible) offset++;
} }
} }
return this.keyable[i].focus(); return this.keyable[i].focus();
}; };
Screen.prototype.focusPrev = function() { Screen.prototype.focusPrev =
return this.focus(-1); Screen.prototype.focusPrevious = function() {
return this.focusOffset(-1);
}; };
Screen.prototype.focusNext = function() { Screen.prototype.focusNext = function() {
return this.focus(1); return this.focusOffset(1);
}; };
Screen.prototype.focusPush = function(el) { Screen.prototype.focusPush = function(el) {
if (!el) return;
var old = this.history[this.history.length-1];
if (this.history.length === 10) { if (this.history.length === 10) {
this.history.shift(); this.history.shift();
} }
this.history.push(el); this.history.push(el);
el._focus(); el._focus(old);
}; };
Screen.prototype.focusLast =
Screen.prototype.focusPop = function() { Screen.prototype.focusPop = function() {
return this.history.pop(); var old = this.history.pop();
this.history[this.history.length-1]._focus(old);
return old;
}; };
Screen.prototype.saveFocus = function() { Screen.prototype.saveFocus = function() {
@ -1387,6 +1386,25 @@ Screen.prototype.restoreFocus = function() {
return this.focused; return this.focused;
}; };
Screen.prototype.rewindFocus = function() {
var old = this.history[this.history.length-1]
, el;
while (this.history.length) {
el = this.history.pop();
if (!el.detached && !el.visible) {
this.history.push(el);
el._focus(old);
return el;
}
}
if (old) {
old.emit('blur');
this.screen.emit('element blur', old);
}
};
Screen.prototype.__defineGetter__('focused', function() { Screen.prototype.__defineGetter__('focused', function() {
return this.history[this.history.length-1]; return this.history[this.history.length-1];
}); });
@ -1838,8 +1856,9 @@ Element.prototype.hide = function() {
this.clearPos(); this.clearPos();
this.hidden = true; this.hidden = true;
this.emit('hide'); this.emit('hide');
var below = this.screen.history[this.screen.history.length-2]; if (this.screen.focused === this) {
if (below && this.screen.focused === this) below.focus(); this.screen.rewindFocus();
}
}; };
Element.prototype.show = function() { Element.prototype.show = function() {
@ -1853,12 +1872,10 @@ Element.prototype.toggle = function() {
}; };
Element.prototype.focus = function() { Element.prototype.focus = function() {
this.screen.focused = this; return this.screen.focused = this;
}; };
Element.prototype._focus = function() { Element.prototype._focus = function(old) {
var old = this.screen.history[this.screen.history.length-2];
// Find a scrollable ancestor if we have one. // Find a scrollable ancestor if we have one.
var el = this; var el = this;
while (el = el.parent) { while (el = el.parent) {
@ -1876,17 +1893,20 @@ Element.prototype._focus = function() {
el.scrollTo(ryi); el.scrollTo(ryi);
this.screen.render(); this.screen.render();
} else if (ryi >= el.childBase + visible) { } else if (ryi >= el.childBase + visible) {
el.scrollTo(ryi); el.scrollTo(ryi + this.height);
this.screen.render(); this.screen.render();
} else if (ryl >= el.childBase + visible) { } else if (ryl >= el.childBase + visible) {
el.scrollTo(ryi); el.scrollTo(ryi + this.height);
this.screen.render(); this.screen.render();
} }
} }
if (old) old.emit('blur', this); if (old) {
old.emit('blur', this);
this.screen.emit('element blur', old, this);
}
this.emit('focus', old); this.emit('focus', old);
this.screen.emit('element blur', old, this);
this.screen.emit('element focus', old, this); this.screen.emit('element focus', old, this);
}; };
@ -1896,6 +1916,25 @@ Element.prototype.setContent = function(content, noClear) {
this.parseContent(); this.parseContent();
}; };
Element.prototype.setContent_ = function(content, noClear) {
var old, pos;
if (!noClear) {
old = this._pcontent;
pos = this._getCoords();
}
this.content = content || '';
this.parseContent();
if (!noClear && pos && this._pcontent !== old) {
// this.clearPos(pos);
this.screen.clearRegion(
pos.xi, pos.xl,
pos.yi, pos.yl);
}
};
Element.prototype.getContent = function() { Element.prototype.getContent = function() {
return this._clines.fake.join('\n'); return this._clines.fake.join('\n');
}; };
@ -2190,7 +2229,7 @@ Element.prototype.__defineGetter__('visible', function() {
return true; return true;
}); });
Element.prototype.__defineGetter__('detached', function() { Element.prototype.__defineGetter__('_detached', function() {
var el = this; var el = this;
do { do {
if (el.type === 'screen') return false; if (el.type === 'screen') return false;
@ -2732,12 +2771,16 @@ Element.prototype._getCoords = function(get) {
if (el) { if (el) {
ppos = this.parent.lpos; ppos = this.parent.lpos;
// The shrink option can cause a stack overflow
// by calling _getCoords on the child again.
// if (!get && !this.parent.shrink) {
// ppos = this.parent._getCoords();
// }
if (!ppos) return; if (!ppos) return;
if (this.parent.scrollable) { yi -= ppos.base;
yi -= ppos.base; yl -= ppos.base;
yl -= ppos.base;
}
if (yi < ppos.yi + this.parent.itop) { if (yi < ppos.yi + this.parent.itop) {
if (yl - 1 < ppos.yi + this.parent.itop) { if (yl - 1 < ppos.yi + this.parent.itop) {
@ -4205,9 +4248,12 @@ Textarea.prototype.readInput = function(callback) {
var self = this var self = this
, focused = this.screen.focused === this; , focused = this.screen.focused === this;
// We need to maintain an array of
// callbacks for legacy reasons.
if (this._callback) { if (this._callback) {
return callback ? this._callbacks.push(callback) : null; return callback ? this._callbacks.push(callback) : null;
} }
this._callbacks = callback ? [callback] : []; this._callbacks = callback ? [callback] : [];
if (!focused) { if (!focused) {
@ -4225,11 +4271,14 @@ Textarea.prototype.readInput = function(callback) {
if (fn.done) return; if (fn.done) return;
fn.done = true; fn.done = true;
self.removeListener('keypress', self.__listener);
self.removeListener('blur', self._callback);
delete self.__listener;
delete self._callback; delete self._callback;
self.removeListener('keypress', self.__listener);
delete self.__listener;
self.removeListener('blur', self.__callback);
delete self.__callback;
self.screen.program.hideCursor(); self.screen.program.hideCursor();
self.screen.grabKeys = false; self.screen.grabKeys = false;
@ -4257,7 +4306,9 @@ Textarea.prototype.readInput = function(callback) {
this.__listener = this._listener.bind(this); this.__listener = this._listener.bind(this);
this.on('keypress', this.__listener); this.on('keypress', this.__listener);
this.on('blur', this._callback);
this.__callback = this._callback.bind(this, null, null);
this.on('blur', this.__callback);
}; };
Textarea.prototype._listener = function(ch, key) { Textarea.prototype._listener = function(ch, key) {
@ -4779,16 +4830,18 @@ function Checkbox(options) {
} }
this.on('focus', function(old) { this.on('focus', function(old) {
var lpos = self._getCoords(); var lpos = self.lpos;
if (!lpos) return; if (!lpos) return;
self.screen.program.saveCursor(); //self.screen.program.saveCursor();
self.screen.program.lsaveCursor('checkbox');
self.screen.program.cup(lpos.yi, lpos.xi + 1); self.screen.program.cup(lpos.yi, lpos.xi + 1);
self.screen.program.showCursor(); self.screen.program.showCursor();
}); });
this.on('blur', function() { this.on('blur', function() {
self.screen.program.hideCursor(); //self.screen.program.hideCursor();
self.screen.program.restoreCursor(); //self.screen.program.restoreCursor();
self.screen.program.lrestoreCursor('checkbox', true);
}); });
} }

View File

@ -201,6 +201,16 @@ var output = blessed.scrollabletext({
content: 'foobar' content: 'foobar'
}); });
var bottom = blessed.line({
parent: form,
type: 'line',
orientation: 'horizontal',
left: 0,
right: 0,
top: 50,
fg: 'blue'
});
screen.key('q', function() { screen.key('q', function() {
return process.exit(0); return process.exit(0);
}); });