« get me outta code hell

An assortment of changes to how selected/unselected works - 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
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2018-07-04 22:50:39 -0300
committerFlorrie <towerofnix@gmail.com>2018-07-04 22:50:39 -0300
commiteebff4fbbac489f96954f05d6d0d838c62a8e6c7 (patch)
treed98e02291111578f298460b5c4e250c5959036e6 /ui
parent4ddc3ece7d713633347f2702c30806b4a2e18ca4 (diff)
An assortment of changes to how selected/unselected works
Diffstat (limited to 'ui')
-rw-r--r--ui/Root.js77
-rw-r--r--ui/form/CancelDialog.js2
-rw-r--r--ui/form/ConfirmDialog.js2
-rw-r--r--ui/form/FocusElement.js8
-rw-r--r--ui/form/Form.js6
-rw-r--r--ui/tools/OpenFileDialog.js2
6 files changed, 68 insertions, 29 deletions
diff --git a/ui/Root.js b/ui/Root.js
index 16b2fc2..a6b3acf 100644
--- a/ui/Root.js
+++ b/ui/Root.js
@@ -13,7 +13,7 @@ module.exports = class Root extends DisplayElement {
 
     this.interfacer = interfacer
 
-    this.selected = null
+    this.selectedElement = null
 
     this.cursorBlinkOffset = Date.now()
 
@@ -25,8 +25,9 @@ module.exports = class Root extends DisplayElement {
   }
 
   handleData(buffer) {
-    if (this.selected) {
-      const els = this.selected.directAncestors.concat([this.selected])
+    if (this.selectedElement) {
+      const els = [
+        ...this.selectedElement.directAncestors, this.selectedElement]
       for (const el of els) {
         if (el instanceof FocusElement) {
           const shouldBreak = (el.keyPressed(buffer) === false)
@@ -47,25 +48,21 @@ module.exports = class Root extends DisplayElement {
   didRenderTo(writable) {
     // Render the cursor, based on the cursorX and cursorY of the currently
     // selected element.
-    if (this.selected && this.selected.cursorVisible) {
+    if (this.selectedElement && this.selectedElement.cursorVisible) {
       if ((Date.now() - this.cursorBlinkOffset) % 1000 < 500) {
-        writable.write(
-          ansi.moveCursor(this.selected.absCursorY, this.selected.absCursorX)
-        )
+        writable.write(ansi.moveCursor(
+          this.selectedElement.absCursorY, this.selectedElement.absCursorX))
         writable.write(ansi.invert())
         writable.write('I')
         writable.write(ansi.resetAttributes())
       }
 
       writable.write(ansi.showCursor())
-      writable.write(
-        ansi.moveCursor(this.selected.absCursorY, this.selected.absCursorX)
-      )
+      writable.write(ansi.moveCursor(
+        this.selectedElement.absCursorY, this.selectedElement.absCursorX))
     } else {
       writable.write(ansi.hideCursor())
     }
-
-    if (this.selected && this.selected.cursorVisible) {}
   }
 
   cursorMoved() {
@@ -79,20 +76,60 @@ module.exports = class Root extends DisplayElement {
     // Select an element. Calls the unfocus method on the already-selected
     // element, if there is one.
 
-    if (this.selected) {
-      this.selected.unfocused()
-    }
+    const oldSelected = this.selectedElement
+    const newSelected = el
+
+    // Relevant elements that COULD have their "isSelected" state change.
+    // We ignore elements where isSelected is undefined, because they aren't
+    // build to handle being selected, and they break the compare-old-and-new-
+    // state code below.
+    const relevantElements = [
+      ...(oldSelected ? [...oldSelected.directAncestors, oldSelected] : []),
+      ...(newSelected ? newSelected.directAncestors : [])
+    ].filter(el => typeof el.isSelected !== 'undefined')
+
+    const oldStates = relevantElements.map(el => [el, el.isSelected])
+
+    this.selectedElement = el
+
+    // Same stuff as in the for loop below. We always call selected() on the
+    // passed element, even if it was already selected before.
+    if (el.selected) el.selected()
+    if (typeof el.focused === 'function') el.focused()
+    if (this.selectedElement !== newSelected) return
+
+    // Compare the old "isSelected" state of every relevant element with their
+    // current "isSelected" state, and call the respective selected/unselected
+    // functions. (Also call focused and unfocused for some sense of trying to
+    // not break old programs, but, like, old programs are going to be broken
+    // anyways.)
+    for (const [ el, wasSelected ] of oldStates) {
+      const { isSelected } = el
+      if (isSelected && !wasSelected) {
+        if (el.selected) el.selected()
+        if (typeof el.focused === 'function') el.focused()
+      } else if (wasSelected && !isSelected) {
+        if (el.unselected) el.unselected()
+        if (typeof el.unfocused === 'function') el.unfocused()
+      }
 
-    this.selected = el
-    this.selected.focused()
+      // If the (un)selected() handler actually selected a different element
+      // itself, then further processing of new selected states is irrelevant,
+      // so stop here. (We return instead of breaking the for loop because
+      // anything after this loop would have already been handled by the call
+      // to Root.select() from the (un)selected() handler.)
+      if (this.selectedElement !== newSelected) {
+        return
+      }
+    }
 
     this.cursorMoved()
   }
 
   isChildOrSelfSelected(el) {
-    if (!this.selected) return false
-    if (this.selected === el) return true
-    if (this.selected.directAncestors.includes(el)) return true
+    if (!this.selectedElement) return false
+    if (this.selectedElement === el) return true
+    if (this.selectedElement.directAncestors.includes(el)) return true
     return false
   }
 }
diff --git a/ui/form/CancelDialog.js b/ui/form/CancelDialog.js
index c5eb7d3..21ff6df 100644
--- a/ui/form/CancelDialog.js
+++ b/ui/form/CancelDialog.js
@@ -47,7 +47,7 @@ module.exports = class ConfirmDialog extends FocusElement {
     this.cancelBtn.y = this.pane.contentH - 2
   }
 
-  focused() {
+  selected() {
     this.root.select(this.cancelBtn)
   }
 
diff --git a/ui/form/ConfirmDialog.js b/ui/form/ConfirmDialog.js
index 3614cf9..230230d 100644
--- a/ui/form/ConfirmDialog.js
+++ b/ui/form/ConfirmDialog.js
@@ -59,7 +59,7 @@ module.exports = class ConfirmDialog extends FocusElement {
     this.cancelBtn.y = this.form.contentH - 2
   }
 
-  focused() {
+  selected() {
     this.root.select(this.form)
   }
 
diff --git a/ui/form/FocusElement.js b/ui/form/FocusElement.js
index 9061838..23c2e02 100644
--- a/ui/form/FocusElement.js
+++ b/ui/form/FocusElement.js
@@ -11,11 +11,11 @@ module.exports = class FocusElement extends DisplayElement {
     this.cursorY = 0
   }
 
-  focused() {
+  selected() {
     // Should be overridden in subclasses.
   }
 
-  unfocused() {
+  unselected() {
     // Should be overridden in subclasses.
   }
 
@@ -36,8 +36,8 @@ module.exports = class FocusElement extends DisplayElement {
   }
 
   get isSelected() {
-    const selected = this.root.selected
-    return selected && [selected, ...selected.directAncestors].includes(this)
+    const selected = this.root.selectedElement
+    return !!(selected && [selected, ...selected.directAncestors].includes(this))
   }
 
   get absCursorX() { return this.absX + this.cursorX }
diff --git a/ui/form/Form.js b/ui/form/Form.js
index 87763bb..ac9f1e4 100644
--- a/ui/form/Form.js
+++ b/ui/form/Form.js
@@ -117,7 +117,9 @@ module.exports = class Form extends FocusElement {
     }
   }
 
-  focused() {
-    this.updateSelectedElement()
+  selected() {
+    if (this.root.selectedElement === this) {
+      this.updateSelectedElement()
+    }
   }
 }
diff --git a/ui/tools/OpenFileDialog.js b/ui/tools/OpenFileDialog.js
index 92bd4af..43f2638 100644
--- a/ui/tools/OpenFileDialog.js
+++ b/ui/tools/OpenFileDialog.js
@@ -90,7 +90,7 @@ module.exports = class OpenFileDialog extends Dialog {
     this.cancelButton.y = this.openButton.y
   }
 
-  focused() {
+  selected() {
     this.form.firstInput()
   }