diff options
Diffstat (limited to 'ui/DisplayElement.js')
-rw-r--r-- | ui/DisplayElement.js | 306 |
1 files changed, 0 insertions, 306 deletions
diff --git a/ui/DisplayElement.js b/ui/DisplayElement.js deleted file mode 100644 index 8720142..0000000 --- a/ui/DisplayElement.js +++ /dev/null @@ -1,306 +0,0 @@ -const Element = require('./Element') -const exception = require('../util/exception') - -module.exports = class DisplayElement extends Element { - // A general class that handles dealing with screen coordinates, the tree - // of elements, and other common stuff. - // - // This element doesn't handle any real rendering; just layouts. Placing - // characters at specific positions should be implemented in subclasses. - // - // It's a subclass of EventEmitter, so you can make your own events within - // the logic of your subclass. - - constructor() { - super() - - this[DisplayElement.drawValues] = {} - this[DisplayElement.lastDrawValues] = {} - this[DisplayElement.scheduledDraw] = false - - this.visible = true - - this.x = 0 - this.y = 0 - this.w = 0 - this.h = 0 - - this.hPadding = 0 - this.vPadding = 0 - - // Note! This only applies to the parent, not the children. Useful for - // when you want an element to cover the whole screen but allow mouse - // events to pass through. - this.clickThrough = false - } - - drawTo(writable) { - // Writes text to a "writable" - an object that has a "write" method. - // Custom rendering should be handled as an override of this method in - // subclasses of DisplayElement. - } - - renderTo(writable) { - // Like drawTo, but only calls drawTo if the element is visible. Use this - // with your root element, not drawTo. - - if (!this.visible) { - return - } - - const causeRenderEl = this.shouldRender() - if (causeRenderEl) { - this.drawTo(writable) - this.renderChildrenTo(writable) - this.didRenderTo(writable) - } else { - this.renderChildrenTo(writable) - } - } - - shouldRender() { - // WIP! Until this implementation is finished, always return true (or else - // lots of rendering breaks). - /* - return ( - this[DisplayElement.scheduledDraw] || - [...this.directAncestors].find(el => el.shouldRender()) - ) - */ - return true - } - - renderChildrenTo(writable) { - // Renders all of the children to a writable. - - for (const child of this.children) { - child.renderTo(writable) - } - } - - didRenderTo(writable) { - // Called immediately after rendering this element AND all of its - // children. If you need to do something when that happens, override this - // method in your subclass. - // - // It's fine to draw more things to the writable here - just keep in mind - // that it'll be drawn over this element and its children, but not any - // elements drawn in the future. - } - - fixLayout() { - // Adjusts the layout of children in this element. If your subclass has - // any children in it, you should override this method. - } - - fixAllLayout() { - // Runs fixLayout on this as well as all children. - - this.fixLayout() - for (const child of this.children) { - child.fixAllLayout() - } - } - - confirmDrawValuesExists() { - if (!this[DisplayElement.drawValues]) { - this[DisplayElement.drawValues] = {} - } - } - - getDep(key) { - this.confirmDrawValuesExists() - return this[DisplayElement.drawValues][key] - } - - setDep(key, value) { - this.confirmDrawValuesExists() - const oldValue = this[DisplayElement.drawValues][key] - 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]) - } - - centerInParent() { - // Utility function to center this element in its parent. Must be called - // only when it has a parent. Set the width and height of the element - // before centering it! - - if (this.parent === null) { - throw new Error('Cannot center in parent when parent is null') - } - - this.x = Math.round((this.parent.contentW - this.w) / 2) - this.y = Math.round((this.parent.contentH - this.h) / 2) - } - - fillParent() { - // Utility function to fill this element in its parent. Must be called - // only when it has a parent. - - if (this.parent === null) { - throw new Error('Cannot fill parent when parent is null') - } - - this.x = 0 - this.y = 0 - this.w = this.parent.contentW - this.h = this.parent.contentH - } - - fitToParent() { - // Utility function to position this element so that it stays within its - // parent's bounds. Must be called only when it has a parent. - // - // This function is useful when (and only when) the right or bottom edge - // of this element may be past the right or bottom edge of its parent. - // In such a case, the element will first be moved left or up by the - // distance that its edge exceeds that of its parent, so that its edge is - // no longer past the parent's. Then, if the left or top edge of the - // element is less than zero, i.e. outside the parent, it is set to zero - // and the element's width or height is adjusted so that it does not go - // past the bounds of the parent. - - if (this.x + this.w > this.parent.right) { - const offendExtent = (this.x + this.w) - this.parent.contentW - this.x -= offendExtent - if (this.x < 0) { - const offstartExtent = 0 - this.x - this.w -= offstartExtent - this.x = 0 - } - } - - if (this.y + this.h > this.parent.bottom) { - const offendExtent = (this.y + this.h) - this.parent.contentH - this.y -= offendExtent - if (this.y < 0) { - const offstartExtent = 0 - this.y - this.h -= offstartExtent - this.y = 0 - } - } - } - - getElementAt(x, y) { - // Gets the topmost element at the provided absolute coordinate. - // Note that elements which are not visible or have the clickThrough - // property set to true are not considered. - - const children = this.children.slice() - - // Start searching the last- (top-) rendered children first. - children.reverse() - - for (const el of children) { - if (!el.visible || el.clickThrough) { - continue - } - - const el2 = el.getElementAt(x, y) - if (el2) { - return el2 - } - - const { absX, absY, w, h } = el - if (absX <= x && absX + w > x) { - if (absY <= y && absY + h > y) { - return el - } - } - } - 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) } - - // Commented out because this doesn't fix any problems (at least ATM). - // get parent() { return this.getDep('parent') } - // set parent(v) { return this.setDep('parent', v) } - - get absX() { - if (this.parent) { - return this.parent.contentX + this.x - } else { - return this.x - } - } - - get absY() { - if (this.parent) { - return this.parent.contentY + this.y - } else { - return this.y - } - } - - // Where contents should be positioned. - get contentX() { return this.absX + this.hPadding } - get contentY() { return this.absY + this.vPadding } - get contentW() { return this.w - this.hPadding * 2 } - get contentH() { return this.h - this.vPadding * 2 } - - get left() { return this.x } - get right() { return this.x + this.w } - get top() { return this.y } - get bottom() { return this.y + this.h } - - get absLeft() { return this.absX } - get absRight() { return this.absX + this.w - 1 } - 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') |