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.event.Event import me.eater.threedom.event.EventListener import me.eater.threedom.event.trigger import me.eater.threedom.utils.ObservableSet import org.joml.Matrix4d 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 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)) } } override var model: Matrix4d = Matrix4d() 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() }