From e33838804bf8a27f91360f8b1a4bd46adb6b9ac8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 19 Jul 2013 01:57:17 -0500 Subject: [PATCH] scrollable elements. --- lib/widget.js | 124 ++++++++++++++++++++++++-------- test/widget-scrollable-boxes.js | 68 ++++++++++++++++++ test/widget.js | 6 +- 3 files changed, 169 insertions(+), 29 deletions(-) create mode 100644 test/widget-scrollable-boxes.js diff --git a/lib/widget.js b/lib/widget.js index 1ce01d6..85fcdfd 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -2348,29 +2348,43 @@ Box.prototype._getCoords = function(get) { , xl = xi + this._getWidth(get) , yi = this._getTop(get) , yl = yi + this._getHeight(get) - , rtop + , ryi + , ryl , visible - , coords; + , coords + , v; // Check to make sure we're visible and // inside of the visible scroll area. if (this.parent.childBase != null && (!this.parent.items || ~this.parent.items.indexOf(this))) { - rtop = yi - this.parent._getTop(get) + ryi = yi - this.parent._getTop(get) + - (this.parent.border ? 1 : 0) + - this.parent.padding; + ryl = yl - this.parent._getTop(get) - (this.parent.border ? 1 : 0) - this.parent.padding; - visible = this.parent._getHeight(get) - (this.parent.border ? 2 : 0) - this.parent.padding * 2; - if (rtop - this.parent.childBase < 0) { - return; - } - - if (rtop - this.parent.childBase >= visible) { + if (ryi < this.parent.childBase) { + if (ryl > this.parent.childBase) { + // Is partially covered above. TODO: Improve. + v = ryl - this.parent.childBase; + yi += (ryl - ryi) - v; + } else { + // Is above. + return; + } + } else if (ryi >= this.parent.childBase + visible) { + // Is below. return; + } else if (ryl >= this.parent.childBase + visible) { + // Is partially covered below. TODO: Improve. + v = this.parent.childBase + visible + (yl - yi) - ryl; + yl = yi + v; } yi -= this.parent.childBase; @@ -2427,7 +2441,11 @@ 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) { - attr = this._clines.attr[this.childBase]; + if (this._clines.length > this.childBase) { + attr = this._clines.attr[this.childBase]; + } else { + attr = this._clines.attr[this._clines.length - 1]; + } } if (this.border) xi++, xl--, yi++, yl--; @@ -2514,19 +2532,21 @@ Box.prototype.render = function() { } // Draw the scrollbar. - i = this.items ? this.items.length : this._clines.length; + 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); + } if (this.scrollbar && (yl - yi) < i) { + i -= yl - yi; x = xl - 1; if (this.scrollbar.ignoreBorder && this.border) x++; - if (this.selected == null) { - // TODO: The scrollbar seems to disappear at - // the bottom of a scrollable text box. Fix. - i = i - (yl - yi); - y = yi + (yl - yi) * (this.childBase / i) | 0; - } else { - y = this.selected / i; - y = yi + ((yl - yi) * y | 0); - } + y = this.selected == null + ? this.childBase + : this.selected; + y = y / i; + y = yi + ((yl - yi) * y | 0); cell = lines[y] && lines[y][x]; if (cell) { ch = this.scrollbar.ch || ' '; @@ -2819,12 +2839,17 @@ function ScrollableBox(options) { this.scrollbar = options.scrollbar; if (this.scrollbar) { this.scrollbar.ch = this.scrollbar.ch || ' '; - if (!this.scrollbar.style) { - this.scrollbar.style = this.style.scrollbar || {}; - this.scrollbar.style.fg = this.scrollbar.fg; - this.scrollbar.style.bg = this.scrollbar.bg; + this.style.scrollbar = this.style.scrollbar || this.scrollbar.style; + if (!this.style.scrollbar) { + this.style.scrollbar = {}; + this.style.scrollbar.fg = this.scrollbar.fg; + this.style.scrollbar.bg = this.scrollbar.bg; + this.style.scrollbar.bold = this.scrollbar.bold; + this.style.scrollbar.underline = this.scrollbar.underline; + this.style.scrollbar.inverse = this.scrollbar.inverse; + this.style.scrollbar.invisible = this.scrollbar.invisible; } - this.style.scrollbar = this.scrollbar.style; + this.scrollbar.style = this.style.scrollbar; } } @@ -2832,6 +2857,30 @@ ScrollableBox.prototype.__proto__ = Box.prototype; ScrollableBox.prototype.type = 'scrollable-box'; +ScrollableBox.prototype._scrollBottom = function() { + // We could just calculate the children, but we can + // optimize for lists by just returning the items.length. + if (this.type === 'list') { + return this.items.length; + } + + if (this.lpos && this.lpos._scrollBottom) { + return this.lpos._scrollBottom; + } + + var bottom = this.children.reduce(function(current, el) { + return Math.max(current, el.rtop + el.height); + }, 0); + + if (this.lpos) this.lpos._scrollBottom = bottom; + + return bottom; +}; + +ScrollableBox.prototype.scrollTo = function(offset) { + return this.scroll(offset - (this.childBase + this.childOffset)); +}; + ScrollableBox.prototype.scroll = function(offset) { var visible = this.height - (this.border ? 2 : 0) - this.padding * 2 , base = this.childBase @@ -2867,6 +2916,15 @@ 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; + } + } + // Optimize scrolling with CSR + IL/DL. p = this.lpos; if (this.childBase !== base && this.screen.cleanSides(this)) { @@ -3065,7 +3123,7 @@ function List(options) { } this.on('resize', function() { - var visible = self.height - (self.border ? 2 : 0); + var visible = self.height - (self.border ? 2 : 0) - self.padding * 2; if (visible >= self.selected + 1) { //if (self.selected < visible - 1) { self.childBase = 0; @@ -3182,10 +3240,11 @@ List.prototype.select = function(index) { if (this.selected === index && this._listInitialized) return; this._listInitialized = true; - var diff = index - this.selected; + //var diff = index - this.selected; this.selected = index; this.value = this.ritems[this.selected]; - this.scroll(diff); + //this.scroll(diff); + this.scrollTo(this.selected); }; List.prototype.move = function(offset) { @@ -3313,12 +3372,18 @@ ScrollableText.prototype.scroll = function(offset) { 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; } } @@ -3338,7 +3403,10 @@ ScrollableText.prototype._recalculateIndex = function() { 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; } diff --git a/test/widget-scrollable-boxes.js b/test/widget-scrollable-boxes.js new file mode 100644 index 0000000..c801951 --- /dev/null +++ b/test/widget-scrollable-boxes.js @@ -0,0 +1,68 @@ +var blessed = require('../') + , screen = blessed.screen(); + +var box = blessed.scrollablebox({ +//var box = blessed.scrollabletext({ + parent: screen, + left: 'center', + top: 'center', + width: '80%', + height: '80%', + bg: 'green', + border: { + type: 'ascii' + }, + content: 'foobar', + keys: true, + vi: true, + alwaysScroll: true, + scrollbar: { + ch: ' ', + inverse: true + } +}); + +var text = blessed.box({ + parent: box, + content: 'hello', + style: { + bg: 'red' + }, + left: 2, + top: 30, + width: '50%', + height: 4 +}); + +var text2 = blessed.box({ + parent: box, + content: 'world', + style: { + bg: 'red' + }, + left: 2, + top: 50, + width: '50%', + height: 3 +}); + +screen.key('q', function() { + return process.exit(0); +}); + +box.on('keypress', function(ch, key) { + if (key.name === 'up' || key.name === 'k') { + box.scroll(-1); + screen.render(); + return; + } + if (key.name === 'down' || key.name === 'j') { + box.scroll(1); + screen.render(); + return; + } +}); + +box.focus(); + +screen.render(); diff --git a/test/widget.js b/test/widget.js index b75530c..5833663 100644 --- a/test/widget.js +++ b/test/widget.js @@ -79,7 +79,11 @@ var list = blessed.list({ 'eight', 'nine', 'ten' - ] + ], + scrollbar: { + ch: ' ', + inverse: true + } }); screen.append(list);