You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
6.6 KiB
Kotlin

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<T : INode<T>>(document: IDocument?) : INode<T>, 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<INode<*>> = 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<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))
}
}
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<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 getNodesByTag(tagName: String): Sequence<INode<*>> = if (nodes.isEmpty())
emptySequence()
else
document?.getNodesByTag(tagName)?.filter { it.hasParent(this) } ?: emptySequence()
override fun <T : INode<T>> getNodesByClass(className: String): Sequence<T> =
document?.getNodesByClass<T>(className)?.filter { it.hasParent(this) } ?: emptySequence()
override fun <T : IRenderTarget<C>, C> getNodesByRenderTarget(targetType: String): Sequence<IRenderNode<*, T, C>> =
document?.getNodesByRenderTarget<T, C>(targetType)?.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()
}