« get me outta code hell

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:
Diffstat (limited to 'ui/form/ListScrollForm.js')
-rw-r--r--ui/form/ListScrollForm.js404
1 files changed, 0 insertions, 404 deletions
diff --git a/ui/form/ListScrollForm.js b/ui/form/ListScrollForm.js
deleted file mode 100644
index 78c376f..0000000
--- a/ui/form/ListScrollForm.js
+++ /dev/null
@@ -1,404 +0,0 @@
-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.
-  // Unless disabled in the constructor, a scrollbar is automatically displayed
-  // if there are more items than can be shown in the height of the form at a
-  // single time.
-
-  constructor(layoutType = 'vertical', enableScrollBar = true) {
-    super()
-
-    this.layoutType = layoutType
-
-    this.scrollItems = 0
-
-    this.scrollBarEnabled = enableScrollBar
-
-    this.scrollBar = new ScrollBar(this)
-    this.scrollBarShown = false
-  }
-
-  fixLayout() {
-    this.keepScrollInBounds()
-
-    const scrollItems = this.scrollItems
-
-    // The scrollItems property represents the item to the very left of where
-    // we've scrolled, so we know right away that none of those will be
-    // visible and we won't bother iterating over them. We do need to hide
-    // them, though.
-    for (let i = 0; i < Math.min(scrollItems, this.inputs.length); i++) {
-      this.inputs[i].visible = false
-    }
-
-    // This variable stores how far along the respective axis (implied by
-    // layoutType) the next element should be.
-    let nextPos = 0
-
-    let formEdge
-    if (this.layoutType === 'horizontal') {
-      formEdge = this.contentW
-    } else {
-      formEdge = this.contentH
-    }
-
-    for (let i = scrollItems; i < this.inputs.length; i++) {
-      const item = this.inputs[i]
-      item.fixLayout()
-
-      const curPos = nextPos
-      let curSize
-      if (this.layoutType === 'horizontal') {
-        item.x = curPos
-        curSize = item.w
-      } else {
-        item.y = curPos
-        curSize = item.h
-      }
-      nextPos += curSize
-
-      // By default, the item should be visible..
-      item.visible = true
-
-      // ..but the item's far edge is past the form's far edge, it isn't
-      // fully visible and should be hidden.
-      if (curPos + curSize > formEdge) {
-        item.visible = false
-      }
-
-      // Same deal goes for the close edge. We can check it against 0 since
-      // the close edge of the form's content is going to be 0, of course!
-      if (curPos < 0) {
-        item.visible = false
-      }
-    }
-
-    delete this._scrollItemsLength
-
-    if (this.scrollBarEnabled) {
-      this.showScrollbarIfNecessary()
-    }
-  }
-
-  keyPressed(keyBuf) {
-    let ret
-
-    handleKeyPress: {
-      if (this.layoutType === 'horizontal') {
-        if (telc.isLeft(keyBuf)) {
-          this.previousInput()
-          ret = false; break handleKeyPress
-        } else if (telc.isRight(keyBuf)) {
-          this.nextInput()
-          ret = false; break handleKeyPress
-        }
-      } else if (this.layoutType === 'vertical') {
-        if (telc.isUp(keyBuf)) {
-          this.previousInput()
-          ret = false; break handleKeyPress
-        } else if (telc.isDown(keyBuf)) {
-          this.nextInput()
-          ret = false; break handleKeyPress
-        }
-      }
-
-      ret = super.keyPressed(keyBuf)
-    }
-
-    this.scrollSelectedElementIntoView()
-
-    return ret
-  }
-
-  clicked(button) {
-    // Old code for changing the actual selected item...maybe an interesting
-    // functionality to explore later?
-    /*
-    if (button === 'scroll-up') {
-      this.previousInput()
-      this.scrollSelectedElementIntoView()
-    } else if (button === 'scroll-down') {
-      this.nextInput()
-      this.scrollSelectedElementIntoView()
-    }
-    */
-
-    // Scrolling is typically pretty slow with a mouse wheel when it's by
-    // a single line, so scroll at 3x that speed.
-    for (let i = 0; i < 3; i++) {
-      if (button === 'scroll-up') {
-        this.scrollItems--
-      } else if (button === 'scroll-down') {
-        this.scrollItems++
-      } else {
-        return
-      }
-    }
-
-    this.fixLayout()
-  }
-
-  scrollSelectedElementIntoView() {
-    const sel = this.inputs[this.curIndex]
-
-    if (!sel) {
-      return
-    }
-
-    let formEdge
-    if (this.layoutType === 'horizontal') {
-      formEdge = this.contentW
-    } else {
-      formEdge = this.contentH
-    }
-
-    // If the item is ahead of our view (either to the right of or below),
-    // we should move the view so that the item is the farthest right (of all
-    // the visible items).
-    if (this.getItemPos(sel) > formEdge + this.scrollSize) {
-      this.scrollElementIntoEndOfView(sel)
-    }
-
-    // Adjusting the number of scroll items is much simpler to deal with if
-    // the item is behind our view. Since the item's behind, we need to move
-    // the scroll to be immediately behind it, which is simple since we
-    // already have its index.
-    if (this.getItemPos(sel) <= this.scrollSize) {
-      this.scrollItems = this.curIndex
-    }
-
-    this.fixLayout()
-  }
-
-  firstInput(...args) {
-    this.scrollItems = 0
-
-    super.firstInput(...args)
-
-    this.fixLayout()
-  }
-
-  getScrollPositionOfElementAtEndOfView(element) {
-    // We can decide how many items to scroll past by moving forward until
-    // the item's far edge is visible.
-    const pos = this.getItemPos(element)
-
-    let edge
-    if (this.layoutType === 'horizontal') {
-      edge = this.contentW
-    } else {
-      edge = this.contentH
-    }
-
-    for (let i = 0; i < this.inputs.length; i++) {
-      if (pos <= edge) {
-        return i
-      }
-
-      if (this.layoutType === 'horizontal') {
-        edge += this.inputs[i].w
-      } else {
-        edge += this.inputs[i].h
-      }
-    }
-    // No result? Well, it's at the end.
-    return this.inputs.length
-  }
-
-  scrollElementIntoEndOfView(element) {
-    this.scrollItems = this.getScrollPositionOfElementAtEndOfView(element)
-  }
-
-  scrollToBeginning() {
-    this.scrollItems = 0
-    this.fixLayout()
-  }
-
-  scrollToEnd() {
-    this.scrollElementIntoEndOfView(this.inputs[this.inputs.length - 1])
-    this.fixLayout()
-  }
-
-  keepScrollInBounds() {
-    this.scrollItems = Math.max(this.scrollItems, 0)
-    this.scrollItems = Math.min(this.scrollItems, this.getScrollItemsLength())
-  }
-
-  getScrollItemsLength() {
-    if (typeof this._scrollItemsLength === 'undefined') {
-      const lastInput = this.inputs[this.inputs.length - 1]
-      this._scrollItemsLength = this.getScrollPositionOfElementAtEndOfView(lastInput)
-    }
-
-    return this._scrollItemsLength
-  }
-
-  getItemPos(item) {
-    // Gets the position of the item in an unscrolled view.
-
-    const index = this.inputs.indexOf(item)
-    let pos = 0
-    for (let i = 0; i <= index; i++) {
-      if (this.layoutType === 'horizontal') {
-        pos += this.inputs[i].w
-      } else {
-        pos += this.inputs[i].h
-      }
-    }
-    return pos
-  }
-
-  showScrollbarIfNecessary() {
-    this.scrollBarShown = this.scrollBar.canScrollAtAll()
-
-    const isChild = this.children.includes(this.scrollBar)
-    if (this.scrollBarShown) {
-      if (!isChild) this.addChild(this.scrollBar)
-    } else {
-      if (isChild) this.removeChild(this.scrollBar)
-    }
-  }
-
-  get scrollSize() {
-    // Gets the actual length made up by all of the items currently scrolled
-    // past.
-
-    let size = 0
-    for (let i = 0; i < Math.min(this.scrollItems, this.inputs.length); i++) {
-      if (this.layoutType === 'horizontal') {
-        size += this.inputs[i].w
-      } else {
-        size += this.inputs[i].h
-      }
-    }
-    return size
-  }
-
-  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.
-
-    const {
-      backwards: canScrollBackwards,
-      forwards: canScrollForwards
-    } = this.getScrollableDirections()
-
-    // - 2 for extra UI elements (arrows)
-    const totalLength = this.h - 2
-
-    // ..[-----]..
-    //   ^start|
-    //         ^end
-    //
-    // Start and end should correspond to how much of the scroll area
-    // is currently visible. So, if you can see 60% of the full scroll length
-    // at a time, and you are scrolled 10% down, the start position of the
-    // handle should be 10% down, and it should extend 60% of the scrollbar
-    // length, to the 70% mark.
-
-    const currentScroll = this.listScrollForm.scrollItems
-    const edgeLength = this.listScrollForm.contentH
-    const totalItems = this.listScrollForm.inputs.length
-    const itemsVisibleAtOnce = Math.min(totalItems, edgeLength)
-    const handleLength = itemsVisibleAtOnce / totalItems * totalLength
-    let handlePosition = Math.floor(totalLength / totalItems * currentScroll)
-
-    // Silly peeve of mine: The handle should only be visibly touching the top
-    // or bottom of the scrollbar area if you're actually scrolled all the way
-    // to the start or end. Otherwise, it shouldn't be touching! There should
-    // visible space indicating that you can scroll in that direction
-    // (in addition to the arrows we show at the ends).
-
-    if (canScrollBackwards && handlePosition === 0) {
-      handlePosition = 1
-    }
-
-    if (canScrollForwards && (handlePosition + handleLength) === edgeLength) {
-      handlePosition--
-    }
-
-    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)
-      }
-    }
-  }
-
-  getScrollableDirections() {
-    const currentScroll = this.listScrollForm.scrollItems
-    const totalScroll = this.listScrollForm.getScrollItemsLength()
-
-    return {
-      backwards: (currentScroll > 0),
-      forwards: (currentScroll < totalScroll)
-    }
-  }
-
-  canScrollAtAll() {
-    const {backwards, forwards} = this.getScrollableDirections()
-    return backwards || forwards
-  }
-}