|
|
|
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<T : INode<T>>(document: IDocument?) : INode<T>, EventListener {
|
|
|
|
override var document: IDocument? = document
|
|
|
|
protected set
|
|
|
|
override val nodeId = INode.getNextNodeId()
|
|
|
|
|
|
|
|
private val nodes: MutableSet<INode<*>> = 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<String> = 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 <reified T> 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<INode<*>> = 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<INode<*>> = nodes.asSequence()
|
|
|
|
override fun iterator(): Iterator<INode<*>> = nodes.iterator()
|
|
|
|
|
|
|
|
fun recursiveSequence(): Sequence<INode<*>> = sequence {
|
|
|
|
val iterators = mutableListOf<Iterator<INode<*>>>()
|
|
|
|
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<DOMTreeUpdate>(this, ev)
|
|
|
|
if (ev.bubble) {
|
|
|
|
document?.trigger<DOMTreeUpdate>(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 <T> on(eventName: String, block: (Event<T>) -> 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<INode<*>> = 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<INode<*>> =
|
|
|
|
document?.findInRegion(pointA, pointB)?.filter { it.hasParent(this) } ?: emptySequence()
|
|
|
|
|
|
|
|
|
|
|
|
override fun findInRange(origin: Vector3dc, range: Number): Sequence<INode<*>> =
|
|
|
|
document?.findInRange(origin, range)?.filter { it.hasParent(this) } ?: emptySequence()
|
|
|
|
}
|