diff options
Diffstat (limited to 'Source/WebCore/Modules/modern-media-controls/controls/layout-node.js')
-rw-r--r-- | Source/WebCore/Modules/modern-media-controls/controls/layout-node.js | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js b/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js new file mode 100644 index 000000000..2a87f27d6 --- /dev/null +++ b/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js @@ -0,0 +1,307 @@ + +const dirtyNodes = new Set; +const nodesRequiringChildrenUpdate = new Set; + +class LayoutNode +{ + + constructor(stringOrElement) + { + + if (!stringOrElement) + this.element = document.createElement("div"); + else if (stringOrElement instanceof Element) + this.element = stringOrElement; + else if (typeof stringOrElement === "string" || stringOrElement instanceof String) + this.element = elementFromString(stringOrElement); + + this._parent = null; + this._children = []; + + this._x = 0; + this._y = 0; + this._width = 0; + this._height = 0; + this._visible = true; + + this._needsLayout = false; + this._dirtyProperties = new Set; + + this._pendingDOMManipulation = LayoutNode.DOMManipulation.None; + } + + get x() + { + return this._x; + } + + set x(x) + { + if (x === this._x) + return; + + this._x = x; + this.markDirtyProperty("x"); + } + + get y() + { + return this._y; + } + + set y(y) + { + if (y === this._y) + return; + + this._y = y; + this.markDirtyProperty("y"); + } + + get width() + { + return this._width; + } + + set width(width) + { + if (width === this._width) + return; + + this._width = width; + this.markDirtyProperty("width"); + } + + get height() + { + return this._height; + } + + set height(height) + { + if (height === this._height) + return; + + this._height = height; + this.markDirtyProperty("height"); + } + + get visible() + { + return this._visible; + } + + set visible(flag) + { + if (flag === this._visible) + return; + + this._visible = flag; + this.markDirtyProperty("visible"); + } + + get needsLayout() + { + return this._needsLayout || this._pendingDOMManipulation !== LayoutNode.DOMManipulation.None || this._dirtyProperties.size > 0; + } + + set needsLayout(flag) + { + if (this.needsLayout === flag) + return; + + this._needsLayout = flag; + this._updateDirtyState(); + } + + get parent() + { + return this._parent; + } + + get children() + { + return this._children; + } + + set children(children) + { + while (this._children.length) + this.removeChild(this._children[0]); + + for (let child of children) + this.addChild(child); + } + + parentOfType(type) + { + let node = this; + while (node = node._parent) { + if (node instanceof type) + return node; + } + return null; + } + + addChild(child, index) + { + child.remove(); + + if (index === undefined || index < 0 || index > this._children.length) + index = this._children.length; + + this._children.splice(index, 0, child); + child._parent = this; + + child._markNodeManipulation(LayoutNode.DOMManipulation.Addition); + + return child; + } + + insertBefore(newSibling, referenceSibling) + { + return this.addChild(newSibling, this._children.indexOf(referenceSibling)); + } + + insertAfter(newSibling, referenceSibling) + { + const index = this._children.indexOf(referenceSibling); + return this.addChild(newSibling, index + 1); + } + + removeChild(child) + { + if (child._parent !== this) + return; + + const index = this._children.indexOf(child); + if (index === -1) + return; + + this._children.splice(index, 1); + child._parent = null; + + child._markNodeManipulation(LayoutNode.DOMManipulation.Removal); + + return child; + } + + remove() + { + if (this._parent instanceof LayoutNode) + return this._parent.removeChild(this); + } + + markDirtyProperty(propertyName) + { + const hadProperty = this._dirtyProperties.has(propertyName); + this._dirtyProperties.add(propertyName); + + if (!hadProperty) + this._updateDirtyState(); + } + + commitProperty(propertyName) + { + const style = this.element.style; + + switch (propertyName) { + case "x": + style.left = `${this._x}px`; + break; + case "y": + style.top = `${this._y}px`; + break; + case "width": + style.width = `${this._width}px`; + break; + case "height": + style.height = `${this._height}px`; + break; + case "visible": + style.display = this._visible ? "inherit" : "none"; + break; + } + } + + layout() + { + if (this._pendingDOMManipulation === LayoutNode.DOMManipulation.Removal) { + const parent = this.element.parentNode; + if (parent) + parent.removeChild(this.element); + } + + for (let propertyName of this._dirtyProperties) + this.commitProperty(propertyName); + + this._dirtyProperties.clear(); + + if (this._pendingDOMManipulation === LayoutNode.DOMManipulation.Addition) + nodesRequiringChildrenUpdate.add(this.parent); + } + + // Private + + _markNodeManipulation(manipulation) + { + this._pendingDOMManipulation = manipulation; + this._updateDirtyState(); + } + + _updateDirtyState() + { + if (this.needsLayout) { + dirtyNodes.add(this); + scheduler.scheduleLayout(performScheduledLayout); + } else { + dirtyNodes.delete(this); + if (dirtyNodes.size === 0) + scheduler.unscheduleLayout(performScheduledLayout); + } + } + + _updateChildren() + { + let nextChildElement = null; + const element = this.element; + for (let i = this.children.length - 1; i >= 0; --i) { + let child = this.children[i]; + let childElement = child.element; + + if (child._pendingDOMManipulation === LayoutNode.DOMManipulation.Addition) { + element.insertBefore(childElement, nextChildElement); + child._pendingDOMManipulation = LayoutNode.DOMManipulation.None; + } + + nextChildElement = childElement; + } + } + +} + +LayoutNode.DOMManipulation = { + None: 0, + Removal: 1, + Addition: 2 +}; + +function performScheduledLayout() +{ + const previousDirtyNodes = Array.from(dirtyNodes); + dirtyNodes.clear(); + previousDirtyNodes.forEach(node => { + node._needsLayout = false; + node.layout(); + }); + + nodesRequiringChildrenUpdate.forEach(node => node._updateChildren()); + nodesRequiringChildrenUpdate.clear(); +} + +function elementFromString(elementString) +{ + const element = document.createElement("div"); + element.innerHTML = elementString; + return element.firstElementChild; +} |