« 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/DisplayElement.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/DisplayElement.js')
-rw-r--r--ui/DisplayElement.js85
1 files changed, 83 insertions, 2 deletions
diff --git a/ui/DisplayElement.js b/ui/DisplayElement.js
index b7e07b9..87a9cb5 100644
--- a/ui/DisplayElement.js
+++ b/ui/DisplayElement.js
@@ -14,10 +14,14 @@ module.exports = class DisplayElement extends EventEmitter {
   constructor() {
     super()
 
-    this.visible = true
+    this[DisplayElement.drawValues] = {}
+    this[DisplayElement.lastDrawValues] = {}
+    this[DisplayElement.scheduledDraw] = false
 
-    this.parent = null
     this.children = []
+    this.parent = null
+
+    this.visible = true
 
     this.x = 0
     this.y = 0
@@ -82,6 +86,68 @@ module.exports = class DisplayElement extends EventEmitter {
     }
   }
 
+  getDep(key) {
+    return this[DisplayElement.drawValues][key]
+  }
+
+  setDep(key, value) {
+    const oldValue = this[DisplayElement.drawValues][key]
+    // TODO: new map for old values. we only compare value !== oldValue LATER, at render time, not when choosing to schedule - otherwise intermediate sets e.g. f.y = 1; f.y++, which always has a net effect of f.y = 2, will count as a redraw even though the final value isn't changing between frames
+    if (value !== this[DisplayElement.drawValues][key]) {
+      this[DisplayElement.drawValues][key] = value
+      this.scheduleDraw()
+      // Grumble: technically it's possible for a root element to not be an
+      // actual Root. While we don't check for this case most of the time (even
+      // though we ought to), we do here because it's not unlikely for draw
+      // dependency values to be changed before the element is actually added
+      // to a Root element.
+      if (this.root.scheduleRender) {
+        this.root.scheduleRender()
+      }
+    }
+    return value
+  }
+
+  scheduleDrawWithoutPropertyChange() {
+    // Utility function for when you need to schedule a draw without updating
+    // any particular draw-dependency property on the element. Works by setting
+    // an otherwise unused dep to a unique object. (We can't use a symbol here,
+    // because then Object.entries doesn't notice it.)
+    this.setDep('drawWithoutProperty', Math.random())
+  }
+
+  scheduleDraw() {
+    this[DisplayElement.scheduledDraw] = true
+  }
+
+  unscheduleDraw() {
+    this[DisplayElement.scheduledDraw] = false
+  }
+
+  hasScheduledDraw() {
+    if (this[DisplayElement.scheduledDraw]) {
+      for (const [ key, value ] of Object.entries(this[DisplayElement.drawValues])) {
+        if (value !== this[DisplayElement.lastDrawValues][key]) {
+          return true
+        }
+      }
+    }
+    return false
+  }
+
+  updateLastDrawValues() {
+    Object.assign(this[DisplayElement.lastDrawValues], this[DisplayElement.drawValues])
+  }
+
+  eachDescendant(fn) {
+    // Run a function on this element, all of its children, all of their
+    // children, etc.
+    fn(this)
+    for (const child of this.children) {
+      child.eachDescendant(fn)
+    }
+  }
+
   addChild(child, afterIndex = this.children.length, {fixLayout = true} = {}) {
     // TODO Don't let a direct ancestor of this be added as a child. Don't
     // let itself be one of its childs either!
@@ -230,6 +296,17 @@ module.exports = class DisplayElement extends EventEmitter {
     return null
   }
 
+  get x() { return this.getDep('x') }
+  set x(v) { return this.setDep('x', v) }
+  get y() { return this.getDep('y') }
+  set y(v) { return this.setDep('y', v) }
+  get hPadding() { return this.getDep('hPadding') }
+  set hPadding(v) { return this.setDep('hPadding', v) }
+  get vPadding() { return this.getDep('vPadding') }
+  set vPadding(v) { return this.setDep('vPadding', v) }
+  get visible() { return this.getDep('visible') }
+  set visible(v) { return this.setDep('visible', v) }
+
   get absX() {
     if (this.parent) {
       return this.parent.contentX + this.x
@@ -262,3 +339,7 @@ module.exports = class DisplayElement extends EventEmitter {
   get absTop()    { return this.absY }
   get absBottom() { return this.absY + this.h - 1 }
 }
+
+module.exports.drawValues = Symbol('drawValues')
+module.exports.lastDrawValues = Symbol('lastDrawValues')
+module.exports.scheduledDraw = Symbol('scheduledDraw')