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.
- **fillRegion(attr, ch, x1, x2, y1, y2)** - fill any region with a character
of a certain attribute.
- **focus(offset)** - focus element by offset of focusable elements.
- **focusPrev()** - focus previous element in the index.
- **focusOffset(offset)** - focus element by offset of focusable elements.
- **focusPrevious()** - focus previous element in the index.
- **focusNext()** - focus next element in the index.
- **focusPush(element)** - push element on the focus stack (equivalent to
`screen.focused = el`).
- **focusPop()/focusLast()** - pop element off the focus stack.
- **focusPop()** - pop element off the focus stack.
- **saveFocus()** - save the 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.
- **onceKey(name, listener)** - bind a keypress listener for a specific key
once.

View File

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

View File

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