diff options
author | (quasar) nebula <qznebula@protonmail.com> | 2023-05-12 17:42:09 -0300 |
---|---|---|
committer | (quasar) nebula <qznebula@protonmail.com> | 2023-05-13 12:48:36 -0300 |
commit | 6ea74c268a12325296a1d2e7fc31b02030ddb8bc (patch) | |
tree | 5da94d93acb64e7ab650d240d6cb23c659ad02ca /ui/form/ListScrollForm.js | |
parent | e783bcf8522fa68e6b221afd18469c3c265b1bb7 (diff) |
use ESM module syntax & minor cleanups
The biggest change here is moving various element classes under more scope-specific directories, which helps to avoid circular dependencies and is just cleaner to navigate and expand in the future. Otherwise this is a largely uncritical port to ESM module syntax! There are probably a number of changes and other cleanups that remain much needed. Whenever I make changes to tui-lib it's hard to believe it's already been <INSERT COUNTING NUMBER HERE> years since the previous time. First commits are from January 2017, and the code originates a month earlier in KAaRMNoD!
Diffstat (limited to 'ui/form/ListScrollForm.js')
-rw-r--r-- | ui/form/ListScrollForm.js | 404 |
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 - } -} |