package me.eater.threedom.dom import me.eater.threedom.dom.event.* import me.eater.threedom.dom.render.IRenderNode import me.eater.threedom.dom.render.IRenderTarget 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 val className: String = this::class.java.name override var document: IDocument? = document protected set override val nodeId = INode.getNextNodeId() private val nodes: MutableSet> = mutableSetOf() override var parentNode: INode<*>? = null protected set override var shouldIndexLocation: Boolean = false set(value) { if (field == value) return field = value trigger(NodeLocationIndexStateChange(field, this)) } override var id: String? = null set(value) { val old = field field = value trigger(NodeIDUpdate(this, old, value)) } override var absolute: Matrix4dc = Matrix4d() protected set override val tagList: 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: Matrix4dc = Matrix4d() set(value) { val old = Matrix4d(field) field = value.mutable() trigger(NodeModelUpdate(this, old)) } fun model(block: Matrix4d.() -> Matrix4dc) { this.model = block(this.model.mutable()) } 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 getNodesByTag(tagName: String): Sequence> = if (nodes.isEmpty()) emptySequence() else document?.getNodesByTag(tagName)?.filter { it.hasParent(this) } ?: emptySequence() override fun > getNodesByClass(className: String): Sequence = document?.getNodesByClass(className)?.filter { it.hasParent(this) } ?: emptySequence() override fun , C> getNodesByRenderTarget(targetType: String): Sequence> = document?.getNodesByRenderTarget(targetType)?.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() }