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.

178 lines
5.1 KiB
Kotlin

4 years ago
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<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 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))
}
}
override var model: Matrix4d = Matrix4d()
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()
}