package me.eater.threedom.dom import me.eater.threedom.dom.event.DOMTreeUpdate import me.eater.threedom.dom.event.NodeClassListUpdate import me.eater.threedom.dom.event.NodeIDUpdate import me.eater.threedom.dom.event.NodeModelUpdate import me.eater.threedom.event.Event import me.eater.threedom.event.EventListener import me.eater.threedom.event.trigger import me.eater.threedom.utils.ObservableSet import me.eater.threedom.utils.joml.mutable import me.eater.threedom.utils.joml.times import org.joml.Matrix4d import org.joml.Matrix4dc import org.joml.Vector3dc abstract class Node>(document: IDocument?) : INode, EventListener { override var document: IDocument? = document protected set override val nodeId = INode.getNextNodeId() private val nodes: MutableSet> = mutableSetOf() override var parentNode: INode<*>? = null protected set private var _id: String? = null override var id: String? get() = _id set(value) { val old = _id _id = value trigger(NodeIDUpdate(this, old, value)) } override var absolute: Matrix4dc = Matrix4d() protected set override val classList: MutableSet = ObservableSet { when (it.action) { ObservableSet.Action.Removed -> trigger(NodeClassListUpdate.Removed(it.elements, this)) ObservableSet.Action.Added -> trigger(NodeClassListUpdate.Added(it.elements, this)) } } private inline fun trigger(event: T) { if (parentNode !== null) { document?.trigger(Event(event, this)) } } private var _model: Matrix4d = Matrix4d() override var model: Matrix4dc get() = _model set(value) { val old = Matrix4d(_model) _model = value.mutable() trigger(NodeModelUpdate(this, old)) } fun model(block: Matrix4d.() -> Matrix4dc) { this.model = block(this._model) } var children: List> = nodes.toList() override fun addNode(newNode: INode<*>) { nodes.add(newNode) if (newNode.parentNode != this) { newNode.updateParent(this) } } override fun removeNode(refNode: INode<*>) { nodes.remove(refNode) if (refNode.parentNode == this) { refNode.updateParent(null) } } override fun removeAll() { nodes.forEach(::removeNode) } override fun replaceNode(newNode: INode<*>, refNode: INode<*>): Boolean { if (nodes.remove(refNode)) { nodes.add(newNode) return true } return false } override fun sequence(): Sequence> = nodes.asSequence() override fun iterator(): Iterator> = nodes.iterator() fun recursiveSequence(): Sequence> = sequence { val iterators = mutableListOf>>() var current = iterator() while (true) { for (node in current) { yield(node) iterators.add(node.iterator()) } current = iterators.firstOrNull() ?: return@sequence } } protected abstract fun cloneSelf(): T override fun clone(deep: Boolean): T { val cloned = cloneSelf() if (!deep) { return cloned } for (node in cloned) { cloned.addNode(node.clone(true)) } return cloned } override fun updateParent(refNode: INode<*>?): Boolean { val parent = parentNode if (parent == refNode) { return true } if (refNode != null && !refNode.hasChild(this)) { return false } parentNode = refNode parent?.removeNode(this) val event = when { parent == null && refNode != null -> DOMTreeUpdate.Insert(refNode, this).also(::trigger) refNode == null && parent != null -> { val data = DOMTreeUpdate.Remove(parent, this) // Trigger on removed as well as on the parent // Since you can't bubble from a detached node var ev = Event(data, this) document?.trigger(this, ev) if (ev.bubble) { document?.trigger(parent, Event(data, this)) } // Trigger on removed as well as on the parent // Since you can't bubble from a detached node ev = Event(data, this) document?.trigger(this, ev) if (ev.bubble) { document?.trigger(parent, Event(data, this)) } return true } refNode != null && parent != null -> DOMTreeUpdate.Move(parent, refNode, this).also(::trigger) else -> return true } trigger(event) return true } override fun hasChild(refNode: INode<*>) = nodes.contains(refNode) override fun on(eventName: String, block: (Event) -> Unit) { document?.addEventListener(eventName, this, block) } override fun detachFromDocument() { val doc = this.document ?: return this.document = null doc.removeNode(this) } fun finalize() { document?.removeNode(this) } override fun getNodeById(id: String): INode<*>? = document?.getNodeById(id)?.takeIf { it.hasParent(this) } override fun getNodesByClassName(className: String): Sequence> = if (nodes.isEmpty()) emptySequence() else document?.getNodesByClassName(className)?.filter { it.hasParent(this) } ?: emptySequence() override fun updateAbsolute() { this.absolute = (this.parentNode?.absolute ?: Matrix4d()) * model } override fun findInRegion(pointA: Vector3dc, pointB: Vector3dc): Sequence> = document?.findInRegion(pointA, pointB)?.filter { it.hasParent(this) } ?: emptySequence() override fun findInRange(origin: Vector3dc, range: Number): Sequence> = document?.findInRange(origin, range)?.filter { it.hasParent(this) } ?: emptySequence() }