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.

126 lines
4.2 KiB
Kotlin

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.generated.EventNames
import kotlin.reflect.KClass
class Document : IDocument {
val root: PlainNode = PlainNode(this)
private val eventTree = EventTree {
on<DOMTreeUpdate.Remove> { (event) ->
removeNodeFromSearch(event.child)
}
on<DOMTreeUpdate.Insert> { (event) ->
addNodeToSearch(event.child)
}
on<NodeIDUpdate> { (event) ->
event.old?.let {
byId.remove(it, event.node.nodeId)
}
event.new?.let {
byId.putIfAbsent(it, event.node.nodeId)
}
}
on<NodeClassListUpdate.Added> { (event) ->
for (className in event.classNames) {
byClass.getOrPut(className, ::mutableSetOf).add(event.node.nodeId)
}
}
on<NodeClassListUpdate.Removed> { (event) ->
for (className in event.classNames) {
val set = byClass[className] ?: continue
set.remove(event.node.nodeId)
if (set.size == 0) {
byClass.remove(className)
}
}
}
}
private val allNodes: MutableMap<Long, INode<*>> = mutableMapOf()
private val byId: MutableMap<String, Long> = mutableMapOf()
private val byClass: MutableMap<String, MutableSet<Long>> = mutableMapOf()
override fun <T> addEventListener(eventName: String, refNode: INode<*>, block: (Event<T>) -> Unit) {
@Suppress("UNCHECKED_CAST")
eventTree.addEventListener(eventName, refNode, block as (Event<*>) -> Unit)
}
fun <T> addEventListener(eventName: String, block: (Event<T>) -> Unit) = addEventListener(eventName, root, block)
fun <T> addTopLevelEventListener(eventName: String, block: (Event<T>) -> Unit) =
@Suppress("UNCHECKED_CAST")
eventTree.addTopLevelEventListener(eventName, block as (Event<*>) -> Unit)
override fun trigger(eventName: String, targetNode: INode<*>, event: Event<*>) =
eventTree.trigger(eventName, targetNode, event)
override fun addNode(newNode: INode<*>) = root.addNode(newNode)
override fun removeNode(refNode: INode<*>) = root.removeNode(refNode)
override fun deleteNode(refNode: INode<*>) {
refNode.detachFromDocument()
eventTree.removeNode(refNode)
}
override fun removeAll() = root.removeAll()
override fun replaceNode(newNode: INode<*>, refNode: INode<*>): Boolean = root.replaceNode(newNode, refNode)
override fun hasChild(refNode: INode<*>): Boolean = root.hasChild(refNode)
override fun sequence(): Sequence<INode<*>> = root.sequence()
override fun iterator(): Iterator<INode<*>> = root.iterator()
override fun getNodeById(id: String): INode<*>? = byId[id]?.let(allNodes::get)
override fun getNodesByClassName(className: String): Sequence<INode<*>> =
byClass[className]?.asSequence()?.mapNotNull(allNodes::get) ?: emptySequence()
private fun addNodeToSearch(node: INode<*>) {
allNodes[node.nodeId] = node
node.id?.let { byId.putIfAbsent(it, node.nodeId) }
for (className in node.classList) {
byClass.getOrPut(className, ::mutableSetOf).add(node.nodeId)
}
}
private fun removeNodeFromSearch(node: INode<*>) {
allNodes.remove(node.nodeId)
node.id?.let { byId.remove(it, node.nodeId) }
for (className in node.classList) {
val set = byClass.get(className) ?: continue
set.remove(node.nodeId)
if (set.size == 0) {
byClass.remove(className)
}
}
}
inline fun <reified T> on(topLevel: Boolean = false, noinline block: (Event<T>) -> Unit) = if (topLevel)
addTopLevelEventListener(EventNames.getEventName<T>(), block)
else
addEventListener(EventNames.getEventName<T>(), block)
override fun <T : INode<T>> createNode(nodeType: KClass<T>): T =
nodeType.java.getConstructor(IDocument::class.java).newInstance(this)
}