commonMain.io.nacular.doodle.utils.Observables.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core-jvm Show documentation
Show all versions of core-jvm Show documentation
A pure Kotlin, UI framework for the Web and Desktop
package io.nacular.doodle.utils
import kotlin.math.max
import kotlin.math.min
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* Created by Nicholas Eddy on 10/21/17.
*/
public typealias SetObserver = (source: S, removed: Set, added: Set ) -> Unit
public typealias ListObserver = (source: S, removed: Map, added: Map, moved: Map>) -> Unit
public typealias ChangeObserver = (source: S ) -> Unit
public typealias PropertyObserver = (source: S, old: T, new: T ) -> Unit
public fun setObserver(source: B, to: SetObserver): SetObserver = { _,removed,added ->
to(source, removed, added)
}
public interface Pool {
public operator fun plusAssign (item: T)
public operator fun minusAssign(item: T)
}
public typealias ChangeObservers = Pool>
public typealias PropertyObservers = Pool>
public expect open class SetPool(delegate: MutableSet): Pool, Set {
public constructor()
protected val delegate: MutableSet
}
public expect class ChangeObserversImpl(source: S, mutableSet: MutableSet>): SetPool> {
public constructor(source: S)
public operator fun invoke(): Unit
}
public expect class PropertyObserversImpl(source: S, mutableSet: MutableSet>): SetPool> {
public constructor(source: S)
public operator fun invoke(old: T, new: T): Unit
}
public interface ObservableList: MutableList {
public val changed: Pool, E>>
public fun move(element: E, to: Int): Boolean
public operator fun plusAssign(element: E)
public operator fun minusAssign(element: E)
public fun replaceAll(elements: Collection): Boolean
public fun batch(block: MutableList.() -> T): T
public fun notifyChanged(index: Int)
public companion object {
public operator fun invoke( ): ObservableList = ObservableListImpl(mutableListOf ())
public operator fun invoke(list: List): ObservableList = ObservableListImpl(list.toMutableList())
internal fun wrap(list: MutableList): ObservableList = ObservableListImpl(list)
}
}
public fun ObservableList.sortWith(comparator: Comparator) {
batch { sortWith(comparator) }
}
public fun ObservableList.sortWithDescending(comparator: Comparator) {
batch { sortWith(comparator.reversed()) }
}
public open class ObservableListImpl internal constructor(private val list: MutableList): ObservableList, MutableList by list {
private val changed_ = SetPool, E>>()
public override val changed: Pool, E>> = changed_
public override fun move(element: E, to: Int): Boolean {
val oldIndex = indexOf(element)
if (to !in 0 until size || oldIndex < 0 || oldIndex == to) return false
list.removeAt(oldIndex)
list.add (to, element)
changed_.forEach {
it(this, mapOf(), mapOf(), mapOf(to to (oldIndex to element)))
}
return true
}
override fun iterator(): MutableIterator = list.iterator().let {
object: MutableIterator by it {
private var index = -1
override fun next() = it.next().also { index++ }
override fun remove() {
val element = list[index--]
it.remove()
changed_.forEach {
it(this@ObservableListImpl, mapOf(index to element), mapOf(), mapOf())
}
}
}
}
public override operator fun plusAssign(element: E) {
add(element)
}
public override operator fun minusAssign(element: E) {
remove(element)
}
override fun add(element: E): Boolean = list.add(element).ifTrue {
changed_.forEach {
it(this, mapOf(), mapOf(list.size - 1 to element), mapOf())
}
}
override fun remove(element: E): Boolean {
val index = list.indexOf(element)
return when {
index < 0 -> false
else -> list.remove(element).ifTrue { changed_.forEach { it(this, mapOf(index to element), mapOf(), mapOf()) } }
}
}
public override fun addAll(elements: Collection): Boolean = batch { addAll(elements) }
public override fun addAll(index: Int, elements: Collection): Boolean = batch { addAll(index, elements) }
public override fun removeAll(elements: Collection): Boolean = batch { removeAll(elements) }
public override fun retainAll(elements: Collection): Boolean = batch { retainAll(elements) }
public override fun replaceAll(elements: Collection): Boolean = batch { clear(); addAll(elements) }
private class Move(val from: Int, val to:Int, val value: T) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Move<*>) return false
// if (value != other.value) return false
if (from !in listOf(other.from, other.to )) return false
if (to !in listOf(other.to, other.from)) return false
return true
}
override fun hashCode(): Int {
val first = min(from, to)
val last = max(from, to)
var result = first
result = 31 * result + last
return result
}
}
public override fun batch(block: MutableList.() -> T): T = if (changed_.isEmpty()) {
list.run(block)
} else {
// TODO: Can this be optimized?
val old = ArrayList(list)
list.run(block).also {
diffLists(old, this)?.let { diffs ->
changed_.forEach {
it(this, diffs.removed, diffs.added, diffs.moved)
}
}
}
}
override fun clear() {
val size = list.size
val oldList = ArrayList(list)
list.clear()
changed_.forEach {
it(this, (0 until size).associate { it to oldList[it] }, mapOf(), mapOf())
}
}
override operator fun set(index: Int, element: E): E = list.set(index, element).also { old ->
if (old != element) {
changed_.forEach {
it(this, mapOf(index to old), mapOf(index to element), mapOf())
}
}
}
public override fun notifyChanged(index: Int) {
changed_.forEach {
it(this, mapOf(index to this[index]), mapOf(index to this[index]), mapOf())
}
}
override fun add(index: Int, element: E) {
list.add(index, element)
changed_.forEach {
it(this, mapOf(), mapOf(index to element), mapOf())
}
}
override fun removeAt(index: Int): E = list.removeAt(index).also { removed ->
changed_.forEach {
it(this, mapOf(index to removed), mapOf(), mapOf())
}
}
}
internal data class DiffResult(val removed: Map, val added: Map, val moved: Map>)
private class Move(var from: Int, var to:Int, var value: T) {
override fun toString() = (from to to to value).toString()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Move<*>) return false
// if (value != other.value) return false
if (from !in listOf(other.from, other.to )) return false
if (to !in listOf(other.to, other.from)) return false
return true
}
override fun hashCode(): Int {
val first = min(from, to)
val last = max(from, to)
var result = first
result = 31 * result + last
return result
}
}
internal fun diffLists(old: List, new: List): DiffResult? {
if (old != new) {
val removed = mutableMapOf()
val added = mutableMapOf()
val uniqueMoved = mutableListOf>()
val unusedIndexes = new.indices.toMutableSet()
old.forEachIndexed { index, item ->
if (index >= new.size || new[index] != item) {
when (val newIndex = unusedIndexes.firstOrNull { new.getOrNull(it) == item }) {
null -> removed[index] = item
else -> {
uniqueMoved += Move(from = index, to = newIndex, value = item)
unusedIndexes.remove(newIndex)
}
}
}
}
val removedIndexes = removed.keys.sorted()
var j = removedIndexes.size - 1
var i = uniqueMoved.size - 1
while(j >= 0 && i >= 0) {
when {
uniqueMoved[i].from > removedIndexes[j] -> {
uniqueMoved[i].from -= j + 1
if (uniqueMoved[i].from == uniqueMoved[i].to) {
uniqueMoved.removeAt(i)
}
--i
}
else -> {
if (uniqueMoved[i].from == removedIndexes[j]) {
uniqueMoved.removeAt(i)
--i
}
--j
}
}
}
unusedIndexes.forEach {
val item = new[it]
if (it >= old.size || old[it] != item) {
added[it] = item
// Adjust all the moves
uniqueMoved.filter { element -> element.from >= it }.sortedByDescending { it.to }.forEach { element ->
uniqueMoved.remove(element)
if (element.from + 1 != element.to) {
uniqueMoved.add(Move(element.from + 1, element.to, element.value))
}
}
}
}
return DiffResult(removed = removed, added = added, moved = uniqueMoved.toSet().associate { it.from to (it.to to it.value) })
}
return null
}
public open class ObservableSet private constructor(protected val set: MutableSet): MutableSet by set {
private val changed_ = SetPool, E>>()
public val changed: Pool, E>> = changed_
override fun add(element: E): Boolean = set.add(element).ifTrue {
changed_.forEach {
it(this, emptySet(), setOf(element))
}
}
public override fun remove(element: E): Boolean = set.remove(element).ifTrue { changed_.forEach { it(this, setOf(element), emptySet()) } }
public override fun addAll(elements: Collection): Boolean = batch { addAll(elements) }
public override fun removeAll(elements: Collection): Boolean = batch { removeAll(elements) }
public override fun retainAll(elements: Collection): Boolean = batch { retainAll(elements) }
public fun replaceAll(elements: Collection): Boolean = batch { clear(); addAll(elements) }
public fun batch(block: MutableSet.() -> T): T = if (changed_.isEmpty()) {
set.run(block)
} else {
// TODO: Can this be optimized?
val old = HashSet(set)
set.run(block).also {
if (old != this) {
changed_.forEach {
it(this, old.asSequence().filter { it !in set }.toSet(), set.asSequence().filter { it !in old }.toSet())
}
}
}
}
override fun clear() {
val oldSet = HashSet(set)
set.clear()
changed_.forEach {
it(this, oldSet, emptySet())
}
}
public companion object {
public operator fun invoke( ): ObservableSet = ObservableSet(mutableSetOf())
public operator fun invoke(set: Set): ObservableSet = ObservableSet(set.toMutableSet())
}
}
public fun observable(initial: T, onChange: S.(old: T, new: T) ->Unit): ReadWriteProperty = ObservableProperty(initial) { thisRef, old, new ->
onChange(thisRef, old, new)
}
public fun observable(initial: T, observers: Iterable>): ReadWriteProperty = ObservableProperty(initial) { thisRef, old, new ->
observers.forEach { it(thisRef, old, new) }
}
public fun observable(initial: T, observers: Iterable>, onChange: (old: T, new: T) -> Unit): ReadWriteProperty = ObservableProperty(initial) { thisRef, old, new ->
onChange(old, new)
observers.forEach { it(thisRef, old, new) }
}
private class ObservableProperty(initial: T, private val onChange: (thisRef: S, old: T, new: T) -> Unit): ReadWriteProperty {
private var value: T = initial
override operator fun getValue(thisRef: S, property: KProperty<*>): T = value
override operator fun setValue(thisRef: S, property: KProperty<*>, value: T) {
if (value != this.value) {
val old = this.value
this.value = value
onChange(thisRef, old, this.value)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy