« get me outta code hell

Scroll bar for ListScrollForm - tui-lib - Pure Node.js library for making visual command-line programs (ala vim, ncdu)
about summary refs log tree commit diff
path: root/ui/form/ListScrollForm.js
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2018-06-07 22:35:34 -0300
committerFlorrie <towerofnix@gmail.com>2018-06-07 22:35:34 -0300
commit774e1e094f0873be539242c5c80a4130200cb700 (patch)
tree45ee77560715ade8f5ccad33d6eadb72a51b6176 /ui/form/ListScrollForm.js
parentd7c5631264c48e07a8383fb46b453b2b4d8a3637 (diff)
Scroll bar for ListScrollForm
Diffstat (limited to 'ui/form/ListScrollForm.js')
-rw-r--r--ui/form/ListScrollForm.js106
1 files changed, 100 insertions, 6 deletions
diff --git a/ui/form/ListScrollForm.js b/ui/form/ListScrollForm.js
index a6de8db..e888fd1 100644
--- a/ui/form/ListScrollForm.js
+++ b/ui/form/ListScrollForm.js
@@ -1,17 +1,26 @@
+const ansi = require('../../util/ansi')
 const telc = require('../../util/telchars')
+const unic = require('../../util/unichars')
 
+const DisplayElement = require('../DisplayElement')
 const Form = require('./Form')
 
 module.exports = class ListScrollForm extends Form {
   // A form that lets the user scroll through a list of items. It
   // automatically adjusts to always allow the selected item to be visible.
 
-  constructor(layoutType = 'vertical') {
+  constructor(layoutType = 'vertical', scrollBarShown = true) {
     super()
 
     this.layoutType = layoutType
 
     this.scrollItems = 0
+
+    this.scrollBar = new ScrollBar(this)
+    this.scrollBarShown = scrollBarShown
+    if (scrollBarShown) {
+      this.addChild(this.scrollBar)
+    }
   }
 
   fixLayout() {
@@ -108,18 +117,21 @@ module.exports = class ListScrollForm extends Form {
     super.firstInput(...args)
   }
 
-  scrollElementIntoEndOfView(element) {
+  getScrollPositionOfElementAtEndOfView(element) {
     // We can decide how many items to scroll past by moving forward until
     // the item's far edge is visible.
-    let i
     let edge = this.formEdge
-    for (i = 0; i < this.inputs.length; i++) {
+    let i = 0
+    for (; i < this.inputs.length; i++) {
       if (this.getItemPos(element) <= edge) break
+
       edge += this.inputs[i][this.sizeProp]
     }
+    return i
+  }
 
-    // Now that we have the right index to scroll to, apply it!
-    this.scrollItems = i
+  scrollElementIntoEndOfView(element) {
+    this.scrollItems = this.getScrollPositionOfElementAtEndOfView(element)
   }
 
   scrollToBeginning() {
@@ -132,6 +144,11 @@ module.exports = class ListScrollForm extends Form {
     this.fixLayout()
   }
 
+  getScrollItemsLength() {
+    const lastInput = this.inputs[this.inputs.length - 1]
+    return this.getScrollPositionOfElementAtEndOfView(lastInput)
+  }
+
   getItemPos(item) {
     // Gets the position of the item in an unscrolled view.
 
@@ -188,4 +205,81 @@ module.exports = class ListScrollForm extends Form {
     return this.inputs.slice(0, this.scrollItems)
       .reduce((a, b) => a + b[this.sizeProp], 0)
   }
+
+  get contentW() {
+    if (this.scrollBarShown && this.layoutType === 'vertical') {
+      return this.w - 1
+    } else {
+      return this.w
+    }
+  }
+
+  get contentH() {
+    if (this.scrollBarShown && this.layoutType === 'horizontal') {
+      return this.h - 1
+    } else {
+      return this.h
+    }
+  }
+}
+
+class ScrollBar extends DisplayElement {
+  constructor(listScrollForm) {
+    super()
+
+    this.listScrollForm = listScrollForm
+  }
+
+  fixLayout() {
+    // Normally we'd subtract one from contentW/contentH when setting the x/y
+    // position, but the scrollbar is actually displayed OUTSIDE of (adjacent
+    // to) the parent's content area.
+    if (this.listScrollForm.layoutType === 'vertical') {
+      this.h = this.listScrollForm.contentH
+      this.w = 1
+      this.x = this.listScrollForm.contentW
+      this.y = 0
+    } else {
+      this.h = 1
+      this.w = this.listScrollForm.contentW
+      this.x = 0
+      this.y = this.listScrollForm.contentH
+    }
+  }
+
+  drawTo(writable) {
+    // Uuuurgh
+    this.fixLayout()
+
+    // TODO: Horizontal layout! Not functionally a lot different, but I'm too
+    // lazy to write a test UI for it right now.
+
+    // - 2 for extra UI elements (arrows)
+    const totalLength = this.h - 2
+    const totalScroll = this.listScrollForm.getScrollItemsLength()
+    const currentScroll = this.listScrollForm.scrollItems
+    const handlePosition = Math.floor(totalLength / (totalScroll + 1) * currentScroll)
+    const handleLength = totalLength / (totalScroll + 1)
+
+    const canScrollBackwards = (currentScroll > 0)
+    const canScrollForwards = (currentScroll < totalScroll)
+
+    if (this.listScrollForm.layoutType === 'vertical') {
+      const start = this.absTop + handlePosition + 1
+      for (let i = 0; i < handleLength; i++) {
+        writable.write(ansi.moveCursor(start + i, this.absLeft))
+        writable.write(unic.BOX_V_DOUBLE)
+      }
+
+      if (canScrollBackwards) {
+        writable.write(ansi.moveCursor(this.absTop, this.absLeft))
+        writable.write(unic.ARROW_UP_DOUBLE)
+      }
+
+      if (canScrollForwards) {
+        writable.write(ansi.moveCursor(this.absBottom, this.absLeft))
+        writable.write(unic.ARROW_DOWN_DOUBLE)
+      }
+    }
+  }
 }