package me.eater.threedom.dom import me.eater.threedom.event.Event import me.eater.threedom.generated.EventNames class EventTree(block: EventTree.() -> Unit = {}) { private val topLevel: MutableMap) -> Unit>> = mutableMapOf() private val listeners: MutableMap) -> Unit>>> = mutableMapOf() private val nodes: MutableMap> = mutableMapOf() init { block(this) } fun addTopLevelEventListener(eventName: String, handler: (Event<*>) -> Unit) { this.topLevel.getOrPut(eventName, ::mutableSetOf).add(handler) } fun addEventListener(eventName: String, target: INode<*>, handler: (Event<*>) -> Unit) { this.listeners.getOrPut(eventName, ::mutableMapOf).getOrPut(target.nodeId, ::mutableSetOf).add(handler) this.nodes.getOrPut(target.nodeId, ::mutableSetOf).add(eventName) } fun removeNode(node: INode<*>) { val events = this.nodes.remove(node.nodeId) ?: return for (event in events) { this.listeners[event]?.remove(node.nodeId) } } fun trigger(eventName: String, target: INode<*>, event: Event<*>) { this.topLevel[eventName]?.let { for (handler in it) { handler(event) if (!event.propagate) { return } } if (!event.bubble) { return } } val map = this.listeners[eventName] ?: return var current: INode<*>? = target while (current != null) { val set = map[current.nodeId] if (set != null) { for (eventHandler in set) { eventHandler(event) if (!event.propagate) { break } } } if (!event.bubble) { break } current = current.parentNode } } @Suppress("UNCHECKED_CAST") inline fun on(target: INode<*>, noinline block: (Event) -> Unit) = addEventListener(EventNames.getEventName(), target, block as (Event<*>) -> Unit) @Suppress("UNCHECKED_CAST") inline fun on(noinline block: (Event) -> Unit) = addTopLevelEventListener(EventNames.getEventName(), block as (Event<*>) -> Unit) }