improve focus. fix getCoords. readme.
This commit is contained in:
parent
a3e2983b4b
commit
d2226c3295
|
@ -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.
|
||||||
|
|
189
lib/widget.js
189
lib/widget.js
|
@ -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,16 +57,11 @@ 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) {
|
|
||||||
this.focused = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!~this.children.indexOf(element)) {
|
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
this.children.unshift(element);
|
this.children.unshift(element);
|
||||||
} else if (i === this.children.length) {
|
} else if (i === this.children.length) {
|
||||||
|
@ -77,20 +69,19 @@ Node.prototype.insert = function(element, i) {
|
||||||
} else {
|
} else {
|
||||||
this.children.splice(i, 0, element);
|
this.children.splice(i, 0, element);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
element.emit('reparent', this);
|
element.emit('reparent', this);
|
||||||
this.emit('adopt', element);
|
this.emit('adopt', element);
|
||||||
|
|
||||||
if (!old) {
|
|
||||||
// element.emitDescendants('attach', function(el) {
|
|
||||||
// el._detached = false;
|
|
||||||
// });
|
|
||||||
(function emit(el) {
|
(function emit(el) {
|
||||||
el._detached = false;
|
var n = el.detached !== self.detached;
|
||||||
el.emit('attach');
|
el.detached = self.detached;
|
||||||
if (el.children) el.children.forEach(emit);
|
if (n) el.emit('attach');
|
||||||
|
el.children.forEach(emit);
|
||||||
})(element);
|
})(element);
|
||||||
|
|
||||||
|
if (!this.screen.focused) {
|
||||||
|
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);
|
|
||||||
if (~i) {
|
|
||||||
this.children.splice(i, 1);
|
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) {
|
||||||
this.emit('focus', old);
|
old.emit('blur', this);
|
||||||
this.screen.emit('element blur', old, this);
|
this.screen.emit('element blur', old, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('focus', old);
|
||||||
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue