From da49a89b2878d1386573bb8b1342a7b863807054 Mon Sep 17 00:00:00 2001
From: Christopher Jeffrey <chjjeffrey@gmail.com>
Date: Tue, 23 Jul 2013 15:26:57 -0500
Subject: [PATCH] refactor wrapContent. fix/use/improve getCoords(), scroll(),
 hide().

---
 lib/widget.js | 105 +++++++++++++++++++++++++++++---------------------
 1 file changed, 62 insertions(+), 43 deletions(-)

diff --git a/lib/widget.js b/lib/widget.js
index cbc5683..e39447b 100644
--- a/lib/widget.js
+++ b/lib/widget.js
@@ -439,7 +439,9 @@ Screen.prototype._listenMouse = function(el) {
       // if (self.grabKeys && self.focused !== el
       //     && !el.hasAncestor(self.focused)) continue;
 
-      ret = el.lpos;
+      // Need to use _getCoords() over lpos for when the
+      // element is obfuscated by a scrollable parent.
+      ret = el._getCoords();
       if (!ret) continue;
       left = ret.xi;
       top = ret.yi;
@@ -1777,8 +1779,8 @@ Element.prototype.onScreenEvent = function(type, listener) {
 
 Element.prototype.hide = function() {
   if (this.hidden) return;
-  this.hidden = true;
   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();
@@ -1833,9 +1835,9 @@ Element.prototype._focus = function() {
 };
 
 Element.prototype.setContent = function(content, noClear) {
+  if (!noClear) this.clearPos();
   this.content = content || '';
   this.parseContent();
-  if (!noClear) this.clearPos();
 };
 
 Element.prototype.parseContent = function() {
@@ -1950,10 +1952,20 @@ Element.prototype._wrapContent = function(content, width) {
     , margin = 0
     , rtof = []
     , ftor = []
-    , fake = [];
+    , fake = []
+    , out = []
+    , no = 0
+    , line
+    , align
+    , cap
+    , total
+    , i
+    , part
+    , esc
+    , j
+    , lines;
 
-  var lines = content.split('\n')
-    , out = [];
+  lines = content.split('\n');
 
   if (!content) {
     out.push(content);
@@ -1968,9 +1980,15 @@ Element.prototype._wrapContent = function(content, width) {
   if (this.type === 'textarea') margin++;
   if (width > margin) width -= margin;
 
-  lines.forEach(function(line, no) {
-    var align = state
-      , cap;
+  for (; no < lines.length; no++) {
+    line = lines[no];
+    align = state;
+    //cap = null;
+    //total = null;
+    //i = null;
+    //part = null;
+    //esc = null;
+    //j = null;
 
     ftor.push([]);
 
@@ -2000,11 +2018,6 @@ Element.prototype._wrapContent = function(content, width) {
       }
     }
 
-    var total
-      , i
-      , part
-      , esc;
-
     if (!wrap && line.length > width) {
       line = line.slice(0, width);
     }
@@ -2018,7 +2031,7 @@ Element.prototype._wrapContent = function(content, width) {
         if (++total === width) {
           // Try to find a space to break on:
           if (line[i] !== ' ') {
-            var j = i;
+            j = i;
             while (j > i - 10 && j > 0 && line[j] !== ' ') j--;
             if (line[j] === ' ') i = j + 1;
             else i++;
@@ -2050,19 +2063,19 @@ Element.prototype._wrapContent = function(content, width) {
 
       // Make sure we didn't wrap the line to the very end, otherwise
       // we get a pointless empty line after a newline.
-      if (line === '') return;
+      if (line === '') continue;
     }
 
     // If only an escape code got cut off, at it to `part`.
     if (/^(?:\x1b[\[\d;]*m)+$/.test(line)) {
       out[out.length-1] += line;
-      return;
+      continue;
     }
 
     out.push(self._align(line, width, align));
     ftor[no].push(out.length - 1);
     rtof.push(no);
-  });
+  }
 
   out.rtof = rtof;
   out.ftor = ftor;
@@ -2103,14 +2116,16 @@ 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.detached) return;
+  // Need to use _getCoords() over lpos here to avoid clearing
+  // elements which are obfuscated by a scrollable parent.
+
+  // NOTE: COULD USE THIS MULTIPLE PLACES - explanation:
+  // We could use this.lpos because we don't want
+  // to clear coordinates that may have changed and may
+  // not be what/where is actually rendered.
+  // var lpos = this._getCoords() && this.lpos;
+
   var lpos = this._getCoords();
   if (!lpos) return;
   this.screen.clearRegion(
@@ -3298,7 +3313,11 @@ ScrollableBox.prototype.scroll = function(offset, always) {
     , d
     , p
     , t
-    , b;
+    , b
+    , i
+    , max
+    , emax
+    , l;
 
   if (this.alwaysScroll || always) {
     // Semi-workaround
@@ -3328,15 +3347,7 @@ ScrollableBox.prototype.scroll = function(offset, always) {
   // Find max "bottom" value for
   // content and descendant elements.
   // Scroll the content if necessary.
-  var diff = this.childBase - base
-    , w
-    , i
-    , max
-    , emax
-    , t
-    , l;
-
-  if (diff === 0) {
+  if (this.childBase === base) {
     return this.emit('scroll');
   }
 
@@ -3351,7 +3362,6 @@ ScrollableBox.prototype.scroll = function(offset, always) {
   if (emax < 0) emax = 0;
 
   this.childBase = Math.min(this.childBase, Math.max(emax, max));
-  diff = this.childBase - base;
 
   if (this.childBase < 0) {
     this.childBase = 0;
@@ -3359,20 +3369,26 @@ ScrollableBox.prototype.scroll = function(offset, always) {
     this.childBase = this.baseLimit;
   }
 
-  if (diff > 0) {
+  if (this.childBase > base) {
     l = Math.min(this.childBase, this._clines.length);
     for (i = base; i < l; i++) {
       this.contentIndex += this._clines[i].length + 1;
     }
-  } else {
+  } else if (this.childBase < base) {
+    b = Math.min(base, this._clines.length);
     l = Math.min(this.childBase, this._clines.length);
-    for (i = base - 1; i >= l; i--) {
+    for (i = b - 1; i >= l; i--) {
       this.contentIndex -= this._clines[i].length + 1;
     }
   }
 
   // Optimize scrolling with CSR + IL/DL.
   p = this.lpos;
+  // Only really need _getCoords() if we want
+  // to allow nestable scrolling elements...
+  // or if we **really** want shrinkable
+  // scrolling elements.
+  // p = this._getCoords();
   if (this.childBase !== base && this.screen.cleanSides(this)) {
     t = p.yi + this.itop;
     b = p.yl - this.ibottom - 1;
@@ -4013,8 +4029,6 @@ 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);
   var lpos = this._getCoords();
   if (!lpos) return;
   this.screen.program.cup(lpos.yi + this.itop,
@@ -5329,8 +5343,11 @@ Listbar.prototype.render = function() {
 };
 
 Listbar.prototype.select = function(offset) {
+  var lpos = this._getCoords();
+  if (!lpos) return;
+
   var self = this
-    , width = this.lpos ? this.lpos.xl - this.lpos.xi : this.width
+    , width = lpos.xl - lpos.xi
     , drawn = 0
     , visible = 0
     , el;
@@ -5345,7 +5362,9 @@ Listbar.prototype.select = function(offset) {
 
   this.items.forEach(function(el, i) {
     if (i < self.leftBase) return;
-    drawn += (el.lpos ? el.lpos.xl - el.lpos.xi : el.width) + 3;
+    var lpos = el._getCoords();
+    if (!lpos) return;
+    drawn += (lpos.xl - lpos.xi) + 3;
     if (drawn <= width) visible++;
   });