rewrite scrolling system a bit.

This commit is contained in:
Christopher Jeffrey 2013-07-21 05:31:24 -05:00
parent 62ebd04231
commit de79656b04
3 changed files with 266 additions and 210 deletions

View File

@ -1633,7 +1633,7 @@ Element.prototype._focus = function() {
, visible = el.height - el.iheight;
if (ryi < el.childBase) {
el.scrollTo(ryi);
el.scrollTo(ryi - 0);
this.screen.render();
} else if (ryi >= el.childBase + visible) {
el.scrollTo(ryi);
@ -1720,7 +1720,7 @@ Element.prototype._parseAttr = function(lines) {
return;
}
if (this.contentIndex == null || this.childBase == null) {
if (this.childBase == null) {
return;
}
@ -1896,13 +1896,19 @@ Element.prototype.removeKey = function() {
};
Element.prototype.clearPos = function() {
if (this.lpos && !this.lpos.cleared) {
// optimize by making sure we only clear once.
this.lpos.cleared = true;
this.screen.clearRegion(
this.lpos.xi, this.lpos.xl,
this.lpos.yi, this.lpos.yl);
}
// if (this.lpos && !this.lpos.cleared) {
// // optimize by making sure we only clear once.
// this.lpos.cleared = true;
// this.screen.clearRegion(
// this.lpos.xi, this.lpos.xl,
// this.lpos.yi, this.lpos.yl);
// }
if (this.detached) return;
var lpos = this._getCoords();
if (!lpos) return;
this.screen.clearRegion(
lpos.xi, lpos.xl,
lpos.yi, lpos.yl);
};
/**
@ -2412,27 +2418,52 @@ Box.prototype._getCoords = function(get) {
// Check to make sure we're visible and
// inside of the visible scroll area.
// NOTE: Lists have a property where only
// the list items are obfuscated.
if (el && (!el.items || ~el.items.indexOf(this))) {
// This is unfortunate because this._getTop(true) is based on its
// this.parent.lpos.yi - which was saved after it's render() **with the
// childBase subtracted!!!!*** This means nested elements within
// a scrollable box are screwy unless we do this.
var cb = el.childBase;
if (get) {
var el_ = this;
// Do we need to get every parent?
while (el_ = el_.parent) {
if (el_ === el) break;
yi += el_.lpos.cb;
yl += el_.lpos.cb;
}
}
ryi = yi - el._getTop(get) - el.itop;
ryl = yl - el._getTop(get) - el.ibottom;
visible = el._getHeight(get) - el.iheight;
// if (ryi < el.childBase) {
// if (ryl > el.childBase) {
// // Is partially covered above.
// v = ryl - el.childBase;
// yi += (ryl - ryi) - v;
// } else {
// // Is above.
// return;
// }
// } else if (ryi >= el.childBase + visible) {
// // Is below.
// return;
// } else if (ryl >= el.childBase + visible) {
// // Is partially covered below.
// v = el.childBase + visible + (yl - yi) - ryl;
// yl = yi + v;
// }
if (ryi < el.childBase) {
if (ryl > el.childBase) {
// Is partially covered above.
v = ryl - el.childBase;
yi += (ryl - ryi) - v;
} else {
// Is above.
return;
}
// Is above.
return;
} else if (ryi >= el.childBase + visible) {
// Is below.
return;
} else if (ryl >= el.childBase + visible) {
// Is partially covered below.
v = el.childBase + visible + (yl - yi) - ryl;
yl = yi + v;
}
yi -= el.childBase;
@ -2451,7 +2482,8 @@ Box.prototype._getCoords = function(get) {
xi: xi,
xl: xl,
yi: yi,
yl: yl
yl: yl,
cb: cb || 0
};
};
@ -2488,13 +2520,8 @@ Box.prototype.render = function() {
// If we're in a scrollable text box, check to
// see which attributes this line starts with.
if (this.contentIndex != null && this.childBase != null) {
// if (this._clines.length > this.childBase) {
// attr = this._clines.attr[this.childBase];
// } else {
// attr = this._clines.attr[this._clines.length - 1];
// }
attr = this._clines.attr[this.childBase];
if (this.childBase != null) {
attr = this._clines.attr[Math.min(this.childBase, this._clines.length - 1)];
}
if (this.border) xi++, xl--, yi++, yl--;
@ -2582,18 +2609,14 @@ Box.prototype.render = function() {
// Draw the scrollbar.
if (this.scrollbar) {
i = this.type === 'scrollable-text'
? this._clines.length + 1
: this._scrollBottom() + (yl - yi) + 1;
//i = Math.max(this._clines.length + 1, this._scrollBottom() + (yl - yi) + 1);
i = Math.max(this._clines.length + 1,
this._scrollBottom() + (yl - yi) + 1);
}
if (this.scrollbar && (yl - yi) < i) {
i -= yl - yi;
x = xl - 1;
if (this.scrollbar.ignoreBorder && this.border) x++;
y = this.selected == null
? this.childBase
: this.selected;
y = this.childBase + this.childOffset;
y = y / i;
y = yi + ((yl - yi) * y | 0);
cell = lines[y] && lines[y][x];
@ -2871,6 +2894,8 @@ Line.prototype.type = 'line';
*/
function ScrollableBox(options) {
var self = this;
if (!(this instanceof ScrollableBox)) {
return new ScrollableBox(options);
}
@ -2882,6 +2907,7 @@ function ScrollableBox(options) {
this.scrollable = true;
this.childOffset = 0;
this.childBase = 0;
this.contentIndex = 0;
this.baseLimit = options.baseLimit || Infinity;
this.alwaysScroll = options.alwaysScroll;
@ -2900,6 +2926,68 @@ function ScrollableBox(options) {
}
this.scrollbar.style = this.style.scrollbar;
}
if (options.mouse) {
this.on('wheeldown', function(data) {
self.scroll(self.height / 2 | 0 || 1);
self.screen.render();
});
this.on('wheelup', function(data) {
self.scroll(-(self.height / 2 | 0) || -1);
self.screen.render();
});
}
if (options.keys && !options.ignoreKeys) {
this.on('keypress', function(ch, key) {
if (key.name === 'up' || (options.vi && key.name === 'k')) {
self.scroll(-1);
self.screen.render();
return;
}
if (key.name === 'down' || (options.vi && key.name === 'j')) {
self.scroll(1);
self.screen.render();
return;
}
if (options.vi && key.name === 'u' && key.ctrl) {
self.scroll(-(self.height / 2 | 0) || -1);
self.screen.render();
return;
}
if (options.vi && key.name === 'd' && key.ctrl) {
self.scroll(self.height / 2 | 0 || 1);
self.screen.render();
return;
}
if (options.vi && key.name === 'b' && key.ctrl) {
self.scroll(-self.height || -1);
self.screen.render();
return;
}
if (options.vi && key.name === 'f' && key.ctrl) {
self.scroll(self.height || 1);
self.screen.render();
return;
}
if (options.vi && key.name === 'g' && !key.shift) {
self.scroll(-self._clines.length);
self.screen.render();
return;
}
if (options.vi && key.name === 'g' && key.shift) {
self.scroll(self._clines.length);
self.screen.render();
return;
}
});
}
this.on('parsed content', function() {
self._recalculateIndex();
});
self._recalculateIndex();
}
ScrollableBox.prototype.__proto__ = Box.prototype;
@ -2907,6 +2995,8 @@ ScrollableBox.prototype.__proto__ = Box.prototype;
ScrollableBox.prototype.type = 'scrollable-box';
ScrollableBox.prototype._scrollBottom = function() {
if (!this.scrollable) return 0;
// We could just calculate the children, but we can
// optimize for lists by just returning the items.length.
if (this.type === 'list') {
@ -2921,6 +3011,7 @@ ScrollableBox.prototype._scrollBottom = function() {
return Math.max(current, el.rtop + el.height);
}, 0);
//bottom -= this.height;
if (this.lpos) this.lpos._scrollBottom = bottom;
return bottom;
@ -2931,6 +3022,9 @@ ScrollableBox.prototype.scrollTo = function(offset) {
};
ScrollableBox.prototype.scroll = function(offset) {
if (!this.scrollable) return;
// Handle scrolling.
var visible = this.height - this.iheight
, base = this.childBase
, d
@ -2938,8 +3032,6 @@ ScrollableBox.prototype.scroll = function(offset) {
, t
, b;
// Maybe do for lists:
// if (this.items) visible = Math.min(this.items.length, visible);
if (this.alwaysScroll) {
// Semi-workaround
this.childOffset = offset > 0
@ -2965,12 +3057,47 @@ ScrollableBox.prototype.scroll = function(offset) {
this.childBase = this.baseLimit;
}
// This code works for scrollable box and list, but it
// makes scrollable text choke because it can't diff properly.
if (this.type !== 'scrollable-text') {
var bottom = this._scrollBottom();
if (this.childBase > bottom) {
this.childBase = bottom;
// Find max "bottom" value for
// content and descendant elements.
// Scroll the content if necessary.
var diff = this.childBase - base
, w
, i
, max
, emax
, t;
if (diff === 0) {
return this.emit('scroll');
}
// When scrolling text, we want to be able to handle SGR codes as well as line
// feeds. This allows us to take preformatted text output from other programs
// and put it in a scrollable text box.
this.parseContent();
max = this._clines.length - (this.height - this.iheight);
if (max < 0) max = 0;
emax = this._scrollBottom() - (this.height - this.iheight);
this.childBase = Math.min(this.childBase, Math.max(emax, max));
diff = this.childBase - base;
if (this.childBase < 0) {
this.childBase = 0;
} else if (this.childBase > this.baseLimit) {
this.childBase = this.baseLimit;
}
if (diff > 0) {
for (i = base; i < this.childBase; i++) {
if (!this._clines[i]) continue;
this.contentIndex += this._clines[i].length + 1;
}
} else {
for (i = base - 1; i >= this.childBase; i--) {
if (!this._clines[i]) continue;
this.contentIndex -= this._clines[i].length + 1;
}
}
@ -2981,10 +3108,6 @@ ScrollableBox.prototype.scroll = function(offset) {
b = p.yl - this.ibottom - 1;
d = this.childBase - base;
// var i = p.xi + this.ileft;
// var attr = this.screen.olines[t][i][0];
// this.screen.program.write(this.screen.codeAttr(attr, this.screen));
if (d > 0 && d < visible) {
// scrolled down
this.screen.deleteLine(d, t, t, b);
@ -2993,16 +3116,46 @@ ScrollableBox.prototype.scroll = function(offset) {
d = -d;
this.screen.insertLine(d, t, t, b);
}
// this.screen.program.sgr0();
}
this.emit('scroll');
return this.emit('scroll');
};
ScrollableBox.prototype._recalculateIndex = function() {
var max, emax, i, t;
if (this.detached || !this.scrollabe) {
this.contentIndex = 0;
return 0;
}
max = this._clines.length - (this.height - this.iheight);
if (max < 0) max = 0;
emax = this._scrollBottom() - (this.height - this.iheight);
this.childBase = Math.min(this.childBase, Math.max(emax, max));
if (this.childBase < 0) {
this.childBase = 0;
} else if (this.childBase > this.baseLimit) {
this.childBase = this.baseLimit;
}
for (i = 0, t = 0; i < this.childBase; i++) {
if (!this._clines[i]) continue;
t += this._clines[i].length + 1;
}
this.contentIndex = t;
return t;
};
ScrollableBox.prototype.resetScroll = function() {
this.contentIndex = 0;
this.childOffset = 0;
this.childBase = 0;
return this.emit('scroll');
};
/**
@ -3018,6 +3171,7 @@ function List(options) {
options = options || {};
options.ignoreKeys = true;
ScrollableBox.call(this, options);
this.value = '';
@ -3080,7 +3234,7 @@ function List(options) {
// this.select(0);
}
if (this.mouse) {
if (options.mouse) {
this.on('wheeldown', function(data) {
self.select(self.selected + 2);
self.screen.render();
@ -3289,10 +3443,8 @@ List.prototype.select = function(index) {
if (this.selected === index && this._listInitialized) return;
this._listInitialized = true;
//var diff = index - this.selected;
this.selected = index;
this.value = this.ritems[this.selected];
//this.scroll(diff);
this.scrollTo(this.selected);
};
@ -3313,153 +3465,18 @@ List.prototype.down = function(offset) {
*/
function ScrollableText(options) {
var self = this;
if (!(this instanceof ScrollableText)) {
return new ScrollableText(options);
}
options = options || {};
options.alwaysScroll = true;
ScrollableBox.call(this, options);
if (options.mouse) {
var self = this;
this.on('wheeldown', function(data) {
self.scroll(self.height / 2 | 0 || 1);
self.screen.render();
});
this.on('wheelup', function(data) {
self.scroll(-(self.height / 2 | 0) || -1);
self.screen.render();
});
}
if (options.keys) {
this.on('keypress', function(ch, key) {
if (key.name === 'up' || (options.vi && key.name === 'k')) {
self.scroll(-1);
self.screen.render();
return;
}
if (key.name === 'down' || (options.vi && key.name === 'j')) {
self.scroll(1);
self.screen.render();
return;
}
if (options.vi && key.name === 'u' && key.ctrl) {
self.scroll(-(self.height / 2 | 0) || -1);
self.screen.render();
return;
}
if (options.vi && key.name === 'd' && key.ctrl) {
self.scroll(self.height / 2 | 0 || 1);
self.screen.render();
return;
}
if (options.vi && key.name === 'b' && key.ctrl) {
self.scroll(-self.height || -1);
self.screen.render();
return;
}
if (options.vi && key.name === 'f' && key.ctrl) {
self.scroll(self.height || 1);
self.screen.render();
return;
}
if (options.vi && key.name === 'g' && !key.shift) {
self.scroll(-self._clines.length);
self.screen.render();
return;
}
if (options.vi && key.name === 'g' && key.shift) {
self.scroll(self._clines.length);
self.screen.render();
return;
}
});
}
this.on('parsed content', function() {
self._recalculateIndex();
});
self._recalculateIndex();
}
ScrollableText.prototype.__proto__ = ScrollableBox.prototype;
ScrollableText.prototype.type = 'scrollable-text';
ScrollableText.prototype._scroll = ScrollableText.prototype.scroll;
ScrollableText.prototype.scroll = function(offset) {
var base = this.childBase
, ret = this._scroll(offset)
, cb = this.childBase
, diff = cb - base
, w
, i
, max
, t;
if (diff === 0) return ret;
// When scrolling text, we want to be able to handle SGR codes as well as line
// feeds. This allows us to take preformatted text output from other programs
// and put it in a scrollable text box.
if (this.content != null) {
this.parseContent();
max = this._clines.length - (this.height - this.iheight);
if (max < 0) max = 0;
if (cb > max) {
this.childBase = cb = max;
diff = cb - base;
}
// this.childBase = Math.max(this.childBase, max);
// cb = this.childBase;
// diff = cb - base;
if (diff > 0) {
for (i = base; i < cb; i++) {
// if (!this._clines[i]) continue;
this.contentIndex += this._clines[i].length + 1;
}
} else {
for (i = base - 1; i >= cb; i--) {
// if (!this._clines[i]) continue;
this.contentIndex -= this._clines[i].length + 1;
}
}
}
return ret;
};
ScrollableText.prototype._recalculateIndex = function() {
if (this.detached) return;
var max = this._clines.length - (this.height - this.iheight);
if (max < 0) max = 0;
if (this.childBase > max) {
this.childBase = max;
}
//this.childBase = Math.max(this.childBase, max);
for (var i = 0, t = 0; i < this.childBase; i++) {
// if (!this._clines[i]) continue;
t += this._clines[i].length + 1;
}
this.contentIndex = t;
};
/**
* Form
*/
@ -3473,6 +3490,7 @@ function Form(options) {
options = options || {};
options.ignoreKeys = true;
ScrollableBox.call(this, options);
if (options.keys) {
@ -3723,8 +3741,12 @@ Textbox.prototype.type = 'textbox';
Textbox.prototype.updateCursor = function() {
if (this.screen.focused !== this) return;
this.screen.program.cup(this.top + this.itop,
this.left + this.ileft + this.value.length);
//this.screen.program.cup(this.top + this.itop,
// this.left + this.ileft + this.value.length);
var lpos = this._getCoords();
if (!lpos) return;
this.screen.program.cup(lpos.yi + this.itop,
lpos.xi + this.ileft + this.value.length);
};
Textbox.prototype.input =
@ -3887,6 +3909,9 @@ Textarea.prototype.updateCursor = function() {
return;
}
var lpos = this.getCoords();
if (!lpos) return;
var last = this._clines[this._clines.length-1]
, program = this.screen.program
, line
@ -3903,10 +3928,10 @@ Textarea.prototype.updateCursor = function() {
line = Math.min(
this._clines.length - 1 - this.childBase,
this.height - this.iheight - 1);
(lpos.yl - lpos.yi) - this.iheight - 1);
cy = this.top + this.itop + line;
cx = this.left + this.ileft + last.length;
cy = lpos.yi + this.itop + line;
cx = lpos.xi + this.ileft + last.length;
if (cy === program.y && cx === program.x) {
return;
@ -4421,9 +4446,10 @@ function Checkbox(options) {
}
this.on('focus', function(old) {
if (!self.lpos) return;
var lpos = self._getCoords();
if (!lpos) return;
self.screen.program.saveCursor();
self.screen.program.cup(self.lpos.yi, self.lpos.xi + 1);
self.screen.program.cup(lpos.yi, lpos.xi + 1);
self.screen.program.showCursor();
});
@ -4439,7 +4465,7 @@ Checkbox.prototype.type = 'checkbox';
Checkbox.prototype._render = Checkbox.prototype.render;
Checkbox.prototype.render = function() {
if (!this._getCoords(true)) return;
//if (!this._getCoords(true)) return;
if (this.type === 'radio-button') {
this.setContent('(' + (this.checked ? '*' : ' ') + ') ' + this.text);
} else {
@ -4501,8 +4527,15 @@ function RadioButton(options) {
Checkbox.call(this, options);
this.on('check', function() {
if (self.parent.type !== 'radio-set') return;
self.parent.children.forEach(function(el) {
var el = self;
while (el = el.parent) {
if (self.parent.type === 'radio-set'
|| self.parent.type === 'form') {
break;
}
}
if (!el) el = self.parent;
el.children.forEach(function(el) {
if (el === self) return;
el.uncheck();
});

View File

@ -9,9 +9,19 @@ var form = blessed.form({
left: 0,
top: 0,
width: '100%',
height: 5,
height: 12,
bg: 'green',
content: 'foobar'
content: 'foobar',
border: {
type: 'ch',
ch: ' ',
style: { inverse: true }
},
scrollbar: {
ch: ' ',
inverse: true
}
//alwaysScroll: true
});
form.on('submit', function(data) {
@ -19,10 +29,20 @@ form.on('submit', function(data) {
screen.render();
});
form.key('d', function() {
form.scroll(1);
screen.render();
});
form.key('u', function() {
form.scroll(-1);
screen.render();
});
var set = blessed.radioset({
parent: form,
left: 0,
top: 0,
left: 1,
top: 1,
shrink: true,
//padding: 1,
//content: 'f',
@ -63,7 +83,7 @@ var text = blessed.textbox({
height: 1,
width: 20,
left: 1,
top: 2,
top: 3,
name: 'text'
});
@ -79,7 +99,7 @@ var check = blessed.checkbox({
bg: 'magenta',
height: 1,
left: 28,
top: 0,
top: 1,
name: 'check',
content: 'check'
});
@ -92,7 +112,7 @@ var check2 = blessed.checkbox({
bg: 'magenta',
height: 1,
left: 28,
top: 10,
top: 14,
name: 'foooooooo2',
content: 'foooooooo2'
});
@ -106,8 +126,8 @@ var submit = blessed.button({
left: 1,
right: 1
},
left: 30,
top: 2,
left: 29,
top: 3,
shrink: true,
name: 'submit',
content: 'submit',
@ -128,7 +148,7 @@ var output = blessed.scrollabletext({
mouse: true,
keys: true,
left: 0,
top: 5,
top: 12,
width: '100%',
bg: 'red',
content: 'foobar'

View File

@ -150,7 +150,10 @@ var stext = blessed.scrollabletext({
//height: 4,
height: 6,
left: 0,
bottom: 0
bottom: 0,
scrollbar: {
inverse: true
}
});
screen.append(stext);