diff options
-rw-r--r-- | ui/form/ListScrollForm.js | 106 |
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) + } + } + } } |