From 6ea74c268a12325296a1d2e7fc31b02030ddb8bc Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 12 May 2023 17:42:09 -0300 Subject: 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 years since the previous time. First commits are from January 2017, and the code originates a month earlier in KAaRMNoD! --- ui/form/Button.js | 51 ------ ui/form/CancelDialog.js | 63 -------- ui/form/ConfirmDialog.js | 79 --------- ui/form/FocusBox.js | 32 ---- ui/form/FocusElement.js | 45 ------ ui/form/Form.js | 143 ---------------- ui/form/ListScrollForm.js | 404 ---------------------------------------------- ui/form/TextInput.js | 145 ----------------- 8 files changed, 962 deletions(-) delete mode 100644 ui/form/Button.js delete mode 100644 ui/form/CancelDialog.js delete mode 100644 ui/form/ConfirmDialog.js delete mode 100644 ui/form/FocusBox.js delete mode 100644 ui/form/FocusElement.js delete mode 100644 ui/form/Form.js delete mode 100644 ui/form/ListScrollForm.js delete mode 100644 ui/form/TextInput.js (limited to 'ui/form') diff --git a/ui/form/Button.js b/ui/form/Button.js deleted file mode 100644 index 46329a6..0000000 --- a/ui/form/Button.js +++ /dev/null @@ -1,51 +0,0 @@ -const ansi = require('../../util/ansi') -const telc = require('../../util/telchars') - -const FocusElement = require('./FocusElement') - -module.exports = class Button extends FocusElement { - // A button. - - constructor(text) { - super() - - this.text = text - - this.cursorX = null - this.cursorY = null - } - - fixLayout() { - this.w = ansi.measureColumns(this.text) - this.h = 1 - } - - drawTo(writable) { - if (this.isSelected) { - writable.write(ansi.invert()) - } - - writable.write(ansi.moveCursor(this.absTop, this.absLeft)) - writable.write(this.text) - - writable.write(ansi.resetAttributes()) - - super.drawTo(writable) - } - - keyPressed(keyBuf) { - if (telc.isSelect(keyBuf)) { - this.emit('pressed') - } - } - - clicked(button) { - if (button === 'left') { - if (this.isSelected) { - this.emit('pressed') - } else { - this.root.select(this) - } - } - } -} diff --git a/ui/form/CancelDialog.js b/ui/form/CancelDialog.js deleted file mode 100644 index 21ff6df..0000000 --- a/ui/form/CancelDialog.js +++ /dev/null @@ -1,63 +0,0 @@ -const telc = require('../../util/telchars') - -const FocusElement = require('./FocusElement') - -const Button = require('./Button') -const Form = require('./Form') -const Label = require('../Label') -const Pane = require('../Pane') - -module.exports = class ConfirmDialog extends FocusElement { - // A basic cancel dialog. Has one buttons, cancel, and a label. - // The escape (esc) key can be used to exit the dialog (which sends a - // 'cancelled' event, as the cancel button also does). - - constructor(text) { - super() - - this.pane = new Pane() - this.addChild(this.pane) - - this.cancelBtn = new Button('Cancel') - this.pane.addChild(this.cancelBtn) - - this.label = new Label(text) - this.pane.addChild(this.label) - - this.initEventListeners() - } - - initEventListeners() { - this.cancelBtn.on('pressed', () => this.cancelPressed()) - } - - fixLayout() { - this.w = this.parent.contentW - this.h = this.parent.contentH - - this.pane.w = Math.max(40, 4 + this.label.w) - this.pane.h = 7 - this.pane.centerInParent() - - this.label.x = Math.floor((this.pane.contentW - this.label.w) / 2) - this.label.y = 1 - - this.cancelBtn.x = Math.floor( - (this.pane.contentW - this.cancelBtn.w) / 2) - this.cancelBtn.y = this.pane.contentH - 2 - } - - selected() { - this.root.select(this.cancelBtn) - } - - keyPressed(keyBuf) { - if (telc.isCancel(keyBuf)) { - this.emit('cancelled') - } - } - - cancelPressed() { - this.emit('cancelled') - } -} diff --git a/ui/form/ConfirmDialog.js b/ui/form/ConfirmDialog.js deleted file mode 100644 index 230230d..0000000 --- a/ui/form/ConfirmDialog.js +++ /dev/null @@ -1,79 +0,0 @@ -const telc = require('../../util/telchars') - -const FocusElement = require('./FocusElement') - -const Button = require('./Button') -const Form = require('./Form') -const Label = require('../Label') -const Pane = require('../Pane') - -module.exports = class ConfirmDialog extends FocusElement { - // A basic yes/no dialog. Has two buttons, confirm/cancel, and a label. - // The escape (esc) key can be used to exit the dialog (which sends a - // 'cancelled' event, as the cancel button also does). - - constructor(text) { - super() - - this.pane = new Pane() - this.addChild(this.pane) - - this.form = new Form() - this.pane.addChild(this.form) - - this.confirmBtn = new Button('Confirm') - this.form.addInput(this.confirmBtn) - - this.cancelBtn = new Button('Cancel') - this.form.addInput(this.cancelBtn) - - this.label = new Label(text) - this.form.addChild(this.label) - - this.initEventListeners() - } - - initEventListeners() { - this.confirmBtn.on('pressed', () => this.confirmPressed()) - this.cancelBtn.on('pressed', () => this.cancelPressed()) - } - - fixLayout() { - this.w = this.parent.contentW - this.h = this.parent.contentH - - this.pane.w = Math.max(40, 2 + this.label.w) - this.pane.h = 7 - this.pane.centerInParent() - - this.form.w = this.pane.contentW - this.form.h = this.pane.contentH - - this.label.x = Math.floor((this.form.contentW - this.label.w) / 2) - this.label.y = 1 - - this.confirmBtn.x = 1 - this.confirmBtn.y = this.form.contentH - 2 - - this.cancelBtn.x = this.form.right - this.cancelBtn.w - 1 - this.cancelBtn.y = this.form.contentH - 2 - } - - selected() { - this.root.select(this.form) - } - - keyPressed(keyBuf) { - if (telc.isCancel(keyBuf)) { - this.emit('cancelled') - } - } - - confirmPressed() { - this.emit('confirmed') - } - - cancelPressed() { - this.emit('cancelled') - } -} diff --git a/ui/form/FocusBox.js b/ui/form/FocusBox.js deleted file mode 100644 index 69b5bf5..0000000 --- a/ui/form/FocusBox.js +++ /dev/null @@ -1,32 +0,0 @@ -const ansi = require('../../util/ansi') - -const FocusElement = require('./FocusElement') - -module.exports = class FocusBox extends FocusElement { - // A box (not to be confused with Pane!) that can be selected. When it's - // selected, it applies an invert effect to its children. (This won't work - // well if you have elements inside of it that have their own attributes, - // since they're likely to reset all effects after drawing - including the - // invert from the FocusBox! Bad ANSI limitations; it's relatively likely - // I'll implement maaaaaagic to help deal with this - maybe something - // similar to 'pushMatrix' from Processing - at some point... [TODO]) - - constructor() { - super() - - this.cursorX = null - this.cursorY = null - } - - drawTo(writable) { - if (this.isSelected) { - writable.write(ansi.invert()) - } - } - - didRenderTo(writable) { - if (this.isSelected) { - writable.write(ansi.resetAttributes()) - } - } -} diff --git a/ui/form/FocusElement.js b/ui/form/FocusElement.js deleted file mode 100644 index 23c2e02..0000000 --- a/ui/form/FocusElement.js +++ /dev/null @@ -1,45 +0,0 @@ -const DisplayElement = require('../DisplayElement') - -module.exports = class FocusElement extends DisplayElement { - // A basic element that can receive cursor focus. - - constructor() { - super() - - this.cursorVisible = false - this.cursorX = 0 - this.cursorY = 0 - } - - selected() { - // Should be overridden in subclasses. - } - - unselected() { - // Should be overridden in subclasses. - } - - get selectable() { - // Should be overridden if you want to make the element unselectable - // (according to particular conditions). - - return true - } - - keyPressed(keyBuf) { - // Do something with a buffer containing the key pressed (that is, - // telnet data sent). Should be overridden in subclasses. - // - // Arrow keys are sent as a buffer in the form of - // ESC[# where # is A, B, C or D. See more here: - // http://stackoverflow.com/a/11432632/4633828 - } - - get isSelected() { - const selected = this.root.selectedElement - return !!(selected && [selected, ...selected.directAncestors].includes(this)) - } - - get absCursorX() { return this.absX + this.cursorX } - get absCursorY() { return this.absY + this.cursorY } -} diff --git a/ui/form/Form.js b/ui/form/Form.js deleted file mode 100644 index 451baa4..0000000 --- a/ui/form/Form.js +++ /dev/null @@ -1,143 +0,0 @@ -const telc = require('../../util/telchars') - -const FocusElement = require('./FocusElement') - -module.exports = class Form extends FocusElement { - constructor() { - super() - - this.inputs = [] - this.curIndex = 0 - this.captureTab = true - } - - addInput(input, asChild = true, opts = {}) { - // Adds the given input as a child element and pushes it to the input - // list. If the optional argument asChild is false, it won't add the - // input element as a child of the form. - - this.inputs.push(input) - - if (asChild) { - this.addChild(input, this.children.length, opts) - } - } - - removeInput(input, asChild = true, opts = {}) { - // Removes the given input from the form's input list. If the optional - // argument asChild is false, it won't try to removeChild the input. - - if (this.inputs.includes(input)) { - this.inputs.splice(this.inputs.indexOf(input), 1) - - if (asChild) { - this.removeChild(input, opts) - } - } - } - - selectInput(input) { - if (this.inputs.includes(input)) { - this.curIndex = this.inputs.indexOf(input) - this.updateSelectedElement() - } - } - - keyPressed(keyBuf) { - // Don't do anything if captureTab is set to false. This is handy for - // nested forms. - if (!this.captureTab) { - return - } - - if (telc.isTab(keyBuf) || telc.isBackTab(keyBuf)) { - // No inputs to tab through, so do nothing. - if (this.inputs.length < 2) { - return - } - - if (telc.isTab(keyBuf)) { - this.nextInput() - } else { - this.previousInput() - } - - return false - } - } - - get selectable() { - return this.inputs.some(inp => inp.selectable) - } - - updateSelectedElement() { - if (this.root.select && this.inputs.length) { - if (this.curIndex > this.inputs.length - 1) { - this.curIndex = this.inputs.length - 1 - } - - this.root.select(this.inputs[this.curIndex], {fromForm: true}) - } - } - - previousInput() { - // TODO: Forms currently assume there is at least one selectable input, - // but this isn't necessarily always the case. - do { - this.curIndex = (this.curIndex - 1) - if (this.curIndex < 0) { - this.curIndex = (this.inputs.length - 1) - } - } while (!this.inputs[this.curIndex].selectable) - - this.updateSelectedElement() - } - - nextInput() { - // TODO: See previousInput - do { - this.curIndex = (this.curIndex + 1) % this.inputs.length - } while (!this.inputs[this.curIndex].selectable) - - this.updateSelectedElement() - } - - firstInput(selectForm = true) { - this.curIndex = 0 - - // TODO: See previousInput - if (!this.inputs[this.curIndex].selectable) { - this.nextInput() - } - - if (selectForm || ( - this.root.isChildOrSelfSelected && this.root.isChildOrSelfSelected(this) - )) { - this.updateSelectedElement() - } - } - - lastInput(selectForm = true) { - this.curIndex = this.inputs.length - 1 - - // TODO: See previousInput - if (!this.inputs[this.curIndex].selectable) { - this.previousInput() - } - - if (selectForm || ( - this.root.isChildOrSelfSelected && this.root.isChildOrSelfSelected(this) - )) { - this.updateSelectedElement() - } - } - - selected() { - if (this.root.selectedElement === this) { - this.updateSelectedElement() - } - } - - get curIndex() { return this.getDep('curIndex') } - set curIndex(v) { return this.setDep('curIndex', v) } -} 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 - } -} diff --git a/ui/form/TextInput.js b/ui/form/TextInput.js deleted file mode 100644 index 78d3b6d..0000000 --- a/ui/form/TextInput.js +++ /dev/null @@ -1,145 +0,0 @@ -const ansi = require('../../util/ansi') -const unic = require('../../util/unichars') -const telc = require('../../util/telchars') - -const FocusElement = require('./FocusElement') - -module.exports = class TextInput extends FocusElement { - // An element that the user can type in. - - constructor() { - super() - - this.value = '' - this.cursorVisible = true - this.cursorIndex = 0 - this.scrollChars = 0 - } - - drawTo(writable) { - // There should be room for the cursor so move the "right edge" left a - // single character. - - const startRange = this.scrollChars - const endRange = this.scrollChars + this.w - 3 - - let str = this.value.slice(startRange, endRange) - - writable.write(ansi.moveCursor(this.absTop, this.absLeft + 1)) - writable.write(str) - - // Ellipsis on left side, if there's more characters behind the visible - // area. - if (startRange > 0) { - writable.write(ansi.moveCursor(this.absTop, this.absLeft)) - writable.write(unic.ELLIPSIS) - } - - // Ellipsis on the right side, if there's more characters ahead of the - // visible area. - if (endRange < this.value.length) { - writable.write(ansi.moveCursor(this.absTop, this.absRight - 1)) - writable.write(unic.ELLIPSIS.repeat(2)) - } - - this.cursorX = this.cursorIndex - this.scrollChars + 1 - - super.drawTo(writable) - } - - keyPressed(keyBuf) { - try { - if (keyBuf[0] === 127) { - this.value = ( - this.value.slice(0, this.cursorIndex - 1) + - this.value.slice(this.cursorIndex) - ) - this.cursorIndex-- - this.root.cursorMoved() - return false - } else if (keyBuf[0] === 13) { - // These are aliases for each other. - this.emit('value', this.value) - this.emit('confirm', this.value) - } else if (keyBuf[0] === 0x1b && keyBuf[1] === 0x5b) { - // Keyboard navigation - if (keyBuf[2] === 0x44) { - this.cursorIndex-- - this.root.cursorMoved() - } else if (keyBuf[2] === 0x43) { - this.cursorIndex++ - this.root.cursorMoved() - } - return false - } else if (telc.isEscape(keyBuf)) { - // ESC is bad and we don't want that in the text input! - // Also emit a "cancel" event, which doesn't necessarily do anything, - // but can be listened to. - this.emit('cancel') - } else { - const isTextInput = keyBuf.toString().split('').every(chr => { - const n = chr.charCodeAt(0) - return n > 31 && n < 127 - }) - - if (isTextInput) { - this.value = ( - this.value.slice(0, this.cursorIndex) + keyBuf.toString() + - this.value.slice(this.cursorIndex) - ) - this.cursorIndex += keyBuf.toString().length - this.root.cursorMoved() - this.emit('change', this.value) - - return false - } - } - } finally { - this.keepCursorInRange() - } - } - - setValue(value) { - this.value = value - this.moveToEnd() - } - - moveToEnd() { - this.cursorIndex = this.value.length - this.keepCursorInRange() - } - - keepCursorInRange() { - // Keep the cursor inside or at the end of the input value. - - if (this.cursorIndex < 0) { - this.cursorIndex = 0 - } - - if (this.cursorIndex > this.value.length) { - this.cursorIndex = this.value.length - } - - // Scroll right, if the cursor is past the right edge of where text is - // displayed. - while (this.cursorIndex - this.scrollChars > this.w - 3) { - this.scrollChars++ - } - - // Scroll left, if the cursor is behind the left edge of where text is - // displayed. - while (this.cursorIndex - this.scrollChars < 0) { - this.scrollChars-- - } - - // Scroll left, if we can see past the end of the text. - while (this.scrollChars > 0 && ( - this.scrollChars + this.w - 3 > this.value.length) - ) { - this.scrollChars-- - } - } - - get value() { return this.getDep('value') } - set value(v) { return this.setDep('value', v) } -} -- cgit 1.3.0-6-gf8a5