mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-03 13:58:28 +08:00
Improved observable API and slightly simplified implementation of some observables.
This commit is contained in:
parent
b84ef99e22
commit
89ea739c65
@ -0,0 +1,33 @@
|
||||
package world.phantasmal.observable
|
||||
|
||||
import world.phantasmal.core.disposable.TrackedDisposable
|
||||
import world.phantasmal.core.unsafe.unsafeCast
|
||||
|
||||
/**
|
||||
* Calls [callback] when [dependency] changes.
|
||||
*/
|
||||
class CallbackChangeObserver<T, E : ChangeEvent<T>>(
|
||||
private val dependency: Dependency,
|
||||
// We don't use Observer<T> because of type system limitations. It would break e.g.
|
||||
// AbstractListCell.observeListChange.
|
||||
private val callback: (E) -> Unit,
|
||||
) : TrackedDisposable(), Dependent {
|
||||
init {
|
||||
dependency.addDependent(this)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
dependency.removeDependent(this)
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
override fun dependencyMightChange() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
override fun dependencyChanged(dependency: Dependency, event: ChangeEvent<*>?) {
|
||||
if (event != null) {
|
||||
callback(unsafeCast(event))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +1,46 @@
|
||||
package world.phantasmal.observable
|
||||
|
||||
import world.phantasmal.core.disposable.TrackedDisposable
|
||||
import world.phantasmal.core.unsafe.unsafeCast
|
||||
|
||||
class CallbackObserver<T, E : ChangeEvent<T>>(
|
||||
private val dependency: Dependency,
|
||||
private val callback: (E) -> Unit,
|
||||
/**
|
||||
* Calls [callback] when one or more dependency in [dependencies] changes.
|
||||
*/
|
||||
class CallbackObserver(
|
||||
private vararg val dependencies: Dependency,
|
||||
private val callback: () -> Unit,
|
||||
) : TrackedDisposable(), Dependent {
|
||||
private var changingDependencies = 0
|
||||
private var dependenciesActuallyChanged = false
|
||||
|
||||
init {
|
||||
dependency.addDependent(this)
|
||||
for (dependency in dependencies) {
|
||||
dependency.addDependent(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
dependency.removeDependent(this)
|
||||
for (dependency in dependencies) {
|
||||
dependency.removeDependent(this)
|
||||
}
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
override fun dependencyMightChange() {
|
||||
// Do nothing.
|
||||
changingDependencies++
|
||||
}
|
||||
|
||||
override fun dependencyChanged(dependency: Dependency, event: ChangeEvent<*>?) {
|
||||
if (event != null) {
|
||||
callback(unsafeCast(event))
|
||||
dependenciesActuallyChanged = true
|
||||
}
|
||||
|
||||
changingDependencies--
|
||||
|
||||
if (changingDependencies == 0 && dependenciesActuallyChanged) {
|
||||
dependenciesActuallyChanged = false
|
||||
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package world.phantasmal.observable
|
||||
|
||||
typealias ChangeObserver<T> = (ChangeEvent<T>) -> Unit
|
||||
|
||||
open class ChangeEvent<out T>(
|
||||
/**
|
||||
* The observable's new value.
|
||||
@ -8,5 +10,3 @@ open class ChangeEvent<out T>(
|
||||
) {
|
||||
operator fun component1() = value
|
||||
}
|
||||
|
||||
typealias Observer<T> = (ChangeEvent<T>) -> Unit
|
@ -3,7 +3,7 @@ package world.phantasmal.observable
|
||||
interface Dependency {
|
||||
/**
|
||||
* This method is not meant to be called from typical application code. Usually you'll want to
|
||||
* use [Observable.observe].
|
||||
* use [Observable.observeChange].
|
||||
*/
|
||||
fun addDependent(dependent: Dependent)
|
||||
|
||||
|
@ -3,5 +3,8 @@ package world.phantasmal.observable
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
|
||||
interface Observable<out T> : Dependency {
|
||||
fun observe(observer: Observer<T>): Disposable
|
||||
/**
|
||||
* [observer] will be called whenever this observable changes.
|
||||
*/
|
||||
fun observeChange(observer: ChangeObserver<T>): Disposable
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
package world.phantasmal.observable
|
||||
|
||||
fun <T> emitter(): Emitter<T> = SimpleEmitter()
|
@ -0,0 +1,8 @@
|
||||
package world.phantasmal.observable
|
||||
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
|
||||
fun <T> emitter(): Emitter<T> = SimpleEmitter()
|
||||
|
||||
fun <T> Observable<T>.observe(observer: (T) -> Unit): Disposable =
|
||||
observeChange { observer(it.value) }
|
@ -15,8 +15,8 @@ class SimpleEmitter<T> : AbstractDependency(), Emitter<T> {
|
||||
ChangeManager.changed(this)
|
||||
}
|
||||
|
||||
override fun observe(observer: Observer<T>): Disposable =
|
||||
CallbackObserver(this, observer)
|
||||
override fun observeChange(observer: ChangeObserver<T>): Disposable =
|
||||
CallbackChangeObserver(this, observer)
|
||||
|
||||
override fun emitDependencyChanged() {
|
||||
if (event != null) {
|
||||
|
@ -2,25 +2,15 @@ package world.phantasmal.observable.cell
|
||||
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.observable.AbstractDependency
|
||||
import world.phantasmal.observable.CallbackObserver
|
||||
import world.phantasmal.observable.CallbackChangeObserver
|
||||
import world.phantasmal.observable.ChangeEvent
|
||||
import world.phantasmal.observable.Observer
|
||||
import world.phantasmal.observable.ChangeObserver
|
||||
|
||||
abstract class AbstractCell<T> : AbstractDependency(), Cell<T> {
|
||||
private var mightChangeEmitted = false
|
||||
|
||||
final override fun observe(observer: Observer<T>): Disposable =
|
||||
observe(callNow = false, observer)
|
||||
|
||||
override fun observe(callNow: Boolean, observer: Observer<T>): Disposable {
|
||||
val observingCell = CallbackObserver(this, observer)
|
||||
|
||||
if (callNow) {
|
||||
observer(ChangeEvent(value))
|
||||
}
|
||||
|
||||
return observingCell
|
||||
}
|
||||
override fun observeChange(observer: ChangeObserver<T>): Disposable =
|
||||
CallbackChangeObserver(this, observer)
|
||||
|
||||
protected fun emitMightChange() {
|
||||
if (!mightChangeEmitted) {
|
||||
|
@ -1,10 +1,6 @@
|
||||
package world.phantasmal.observable.cell
|
||||
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.observable.Observable
|
||||
import world.phantasmal.observable.Observer
|
||||
import world.phantasmal.observable.cell.list.DependentListCell
|
||||
import world.phantasmal.observable.cell.list.ListCell
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
@ -14,9 +10,4 @@ interface Cell<out T> : Observable<T> {
|
||||
val value: T
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
|
||||
|
||||
/**
|
||||
* @param callNow Call [observer] immediately with the current [value].
|
||||
*/
|
||||
fun observe(callNow: Boolean = false, observer: Observer<T>): Disposable
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package world.phantasmal.observable.cell
|
||||
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.observable.CallbackObserver
|
||||
|
||||
private val TRUE_CELL: Cell<Boolean> = ImmutableCell(true)
|
||||
private val FALSE_CELL: Cell<Boolean> = ImmutableCell(false)
|
||||
private val NULL_CELL: Cell<Nothing?> = ImmutableCell(null)
|
||||
@ -36,6 +39,62 @@ fun <T> mutableCell(value: T): MutableCell<T> = SimpleCell(value)
|
||||
fun <T> mutableCell(getter: () -> T, setter: (T) -> Unit): MutableCell<T> =
|
||||
DelegatingCell(getter, setter)
|
||||
|
||||
fun <T> Cell<T>.observeNow(
|
||||
observer: (T) -> Unit,
|
||||
): Disposable {
|
||||
observer(value)
|
||||
|
||||
return observeChange { observer(it.value) }
|
||||
}
|
||||
|
||||
fun <T1, T2> observeNow(
|
||||
c1: Cell<T1>,
|
||||
c2: Cell<T2>,
|
||||
observer: (T1, T2) -> Unit,
|
||||
): Disposable {
|
||||
observer(c1.value, c2.value)
|
||||
|
||||
return CallbackObserver(c1, c2) { observer(c1.value, c2.value) }
|
||||
}
|
||||
|
||||
fun <T1, T2, T3> observeNow(
|
||||
c1: Cell<T1>,
|
||||
c2: Cell<T2>,
|
||||
c3: Cell<T3>,
|
||||
observer: (T1, T2, T3) -> Unit,
|
||||
): Disposable {
|
||||
observer(c1.value, c2.value, c3.value)
|
||||
|
||||
return CallbackObserver(c1, c2, c3) { observer(c1.value, c2.value, c3.value) }
|
||||
}
|
||||
|
||||
fun <T1, T2, T3, T4> observeNow(
|
||||
c1: Cell<T1>,
|
||||
c2: Cell<T2>,
|
||||
c3: Cell<T3>,
|
||||
c4: Cell<T4>,
|
||||
observer: (T1, T2, T3, T4) -> Unit,
|
||||
): Disposable {
|
||||
observer(c1.value, c2.value, c3.value, c4.value)
|
||||
|
||||
return CallbackObserver(c1, c2, c3, c4) { observer(c1.value, c2.value, c3.value, c4.value) }
|
||||
}
|
||||
|
||||
fun <T1, T2, T3, T4, T5> observeNow(
|
||||
c1: Cell<T1>,
|
||||
c2: Cell<T2>,
|
||||
c3: Cell<T3>,
|
||||
c4: Cell<T4>,
|
||||
c5: Cell<T5>,
|
||||
observer: (T1, T2, T3, T4, T5) -> Unit,
|
||||
): Disposable {
|
||||
observer(c1.value, c2.value, c3.value, c4.value, c5.value)
|
||||
|
||||
return CallbackObserver(c1, c2, c3, c4, c5) {
|
||||
observer(c1.value, c2.value, c3.value, c4.value, c5.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a transformation function over this cell.
|
||||
*
|
@ -3,21 +3,12 @@ package world.phantasmal.observable.cell
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.disposable.nopDisposable
|
||||
import world.phantasmal.observable.AbstractDependency
|
||||
import world.phantasmal.observable.ChangeEvent
|
||||
import world.phantasmal.observable.Observer
|
||||
import world.phantasmal.observable.ChangeObserver
|
||||
|
||||
class ImmutableCell<T>(override val value: T) : AbstractDependency(), Cell<T> {
|
||||
override fun observe(callNow: Boolean, observer: Observer<T>): Disposable {
|
||||
if (callNow) {
|
||||
observer(ChangeEvent(value))
|
||||
}
|
||||
|
||||
return nopDisposable()
|
||||
}
|
||||
|
||||
override fun observe(observer: Observer<T>): Disposable = nopDisposable()
|
||||
override fun observeChange(observer: ChangeObserver<T>): Disposable = nopDisposable()
|
||||
|
||||
override fun emitDependencyChanged() {
|
||||
error("StaticCell can't change.")
|
||||
error("ImmutableCell can't change.")
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ package world.phantasmal.observable.cell.list
|
||||
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.unsafe.unsafeAssertNotNull
|
||||
import world.phantasmal.observable.CallbackObserver
|
||||
import world.phantasmal.observable.CallbackChangeObserver
|
||||
import world.phantasmal.observable.ChangeObserver
|
||||
import world.phantasmal.observable.Dependent
|
||||
import world.phantasmal.observable.Observer
|
||||
import world.phantasmal.observable.cell.AbstractDependentCell
|
||||
import world.phantasmal.observable.cell.Cell
|
||||
import world.phantasmal.observable.cell.DependentCell
|
||||
@ -55,28 +55,11 @@ abstract class AbstractDependentListCell<E> :
|
||||
return unsafeAssertNotNull(_notEmpty)
|
||||
}
|
||||
|
||||
final override fun observe(callNow: Boolean, observer: Observer<List<E>>): Disposable =
|
||||
observeList(callNow, observer as ListObserver<E>)
|
||||
final override fun observeChange(observer: ChangeObserver<List<E>>): Disposable =
|
||||
observeListChange(observer)
|
||||
|
||||
override fun observeList(callNow: Boolean, observer: ListObserver<E>): Disposable {
|
||||
val observingCell = CallbackObserver(this, observer)
|
||||
|
||||
if (callNow) {
|
||||
observer(
|
||||
ListChangeEvent(
|
||||
value,
|
||||
listOf(ListChange.Structural(
|
||||
index = 0,
|
||||
prevSize = 0,
|
||||
removed = emptyList(),
|
||||
inserted = value,
|
||||
)),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return observingCell
|
||||
}
|
||||
override fun observeListChange(observer: ListChangeObserver<E>): Disposable =
|
||||
CallbackChangeObserver(this, observer)
|
||||
|
||||
final override fun dependenciesChanged() {
|
||||
val oldElements = value
|
||||
|
@ -2,8 +2,8 @@ package world.phantasmal.observable.cell.list
|
||||
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.unsafe.unsafeAssertNotNull
|
||||
import world.phantasmal.observable.CallbackObserver
|
||||
import world.phantasmal.observable.Observer
|
||||
import world.phantasmal.observable.CallbackChangeObserver
|
||||
import world.phantasmal.observable.ChangeObserver
|
||||
import world.phantasmal.observable.cell.AbstractCell
|
||||
import world.phantasmal.observable.cell.Cell
|
||||
import world.phantasmal.observable.cell.DependentCell
|
||||
@ -19,7 +19,7 @@ abstract class AbstractListCell<E> : AbstractCell<List<E>>(), ListCell<E> {
|
||||
* its old value whenever a change event was emitted.
|
||||
*/
|
||||
// TODO: Optimize this by using a weak reference to avoid copying when nothing references the
|
||||
// wrapper.
|
||||
// wrapper.
|
||||
private var _elementsWrapper: DelegatingList<E>? = null
|
||||
protected val elementsWrapper: DelegatingList<E>
|
||||
get() {
|
||||
@ -39,28 +39,11 @@ abstract class AbstractListCell<E> : AbstractCell<List<E>>(), ListCell<E> {
|
||||
|
||||
final override val notEmpty: Cell<Boolean> = !empty
|
||||
|
||||
final override fun observe(callNow: Boolean, observer: Observer<List<E>>): Disposable =
|
||||
observeList(callNow, observer as ListObserver<E>)
|
||||
final override fun observeChange(observer: ChangeObserver<List<E>>): Disposable =
|
||||
observeListChange(observer)
|
||||
|
||||
override fun observeList(callNow: Boolean, observer: ListObserver<E>): Disposable {
|
||||
val observingCell = CallbackObserver(this, observer)
|
||||
|
||||
if (callNow) {
|
||||
observer(
|
||||
ListChangeEvent(
|
||||
value,
|
||||
listOf(ListChange.Structural(
|
||||
index = 0,
|
||||
prevSize = 0,
|
||||
removed = emptyList(),
|
||||
inserted = value
|
||||
)),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return observingCell
|
||||
}
|
||||
override fun observeListChange(observer: ListChangeObserver<E>): Disposable =
|
||||
CallbackChangeObserver(this, observer)
|
||||
|
||||
protected fun copyAndResetWrapper() {
|
||||
_elementsWrapper?.backingList = elements.toList()
|
||||
|
@ -3,8 +3,7 @@ package world.phantasmal.observable.cell.list
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.disposable.nopDisposable
|
||||
import world.phantasmal.observable.AbstractDependency
|
||||
import world.phantasmal.observable.ChangeEvent
|
||||
import world.phantasmal.observable.Observer
|
||||
import world.phantasmal.observable.ChangeObserver
|
||||
import world.phantasmal.observable.cell.Cell
|
||||
import world.phantasmal.observable.cell.cell
|
||||
import world.phantasmal.observable.cell.falseCell
|
||||
@ -20,31 +19,9 @@ class ImmutableListCell<E>(private val elements: List<E>) : AbstractDependency()
|
||||
override fun get(index: Int): E =
|
||||
elements[index]
|
||||
|
||||
override fun observe(callNow: Boolean, observer: Observer<List<E>>): Disposable {
|
||||
if (callNow) {
|
||||
observer(ChangeEvent(value))
|
||||
}
|
||||
override fun observeChange(observer: ChangeObserver<List<E>>): Disposable = nopDisposable()
|
||||
|
||||
return nopDisposable()
|
||||
}
|
||||
|
||||
override fun observe(observer: Observer<List<E>>): Disposable = nopDisposable()
|
||||
|
||||
override fun observeList(callNow: Boolean, observer: ListObserver<E>): Disposable {
|
||||
if (callNow) {
|
||||
observer(ListChangeEvent(
|
||||
value,
|
||||
listOf(ListChange.Structural(
|
||||
index = 0,
|
||||
prevSize = 0,
|
||||
removed = emptyList(),
|
||||
inserted = value,
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
return nopDisposable()
|
||||
}
|
||||
override fun observeListChange(observer: ListChangeObserver<E>): Disposable = nopDisposable()
|
||||
|
||||
override fun emitDependencyChanged() {
|
||||
error("ImmutableListCell can't change.")
|
||||
|
@ -14,7 +14,11 @@ interface ListCell<out E> : Cell<List<E>> {
|
||||
|
||||
operator fun get(index: Int): E = value[index]
|
||||
|
||||
fun observeList(callNow: Boolean = false, observer: ListObserver<E>): Disposable
|
||||
/**
|
||||
* List variant of [Cell.observeChange].
|
||||
*/
|
||||
// Exists solely because function parameters are invariant.
|
||||
fun observeListChange(observer: ListChangeObserver<E>): Disposable
|
||||
|
||||
operator fun contains(element: @UnsafeVariance E): Boolean = element in value
|
||||
}
|
||||
|
@ -42,4 +42,4 @@ sealed class ListChange<out E> {
|
||||
) : ListChange<E>()
|
||||
}
|
||||
|
||||
typealias ListObserver<E> = (ListChangeEvent<E>) -> Unit
|
||||
typealias ListChangeObserver<E> = (ListChangeEvent<E>) -> Unit
|
@ -16,7 +16,7 @@ interface ObservableTests : DependencyTests {
|
||||
var changes = 0
|
||||
|
||||
disposer.add(
|
||||
p.observable.observe {
|
||||
p.observable.observeChange {
|
||||
changes++
|
||||
}
|
||||
)
|
||||
@ -37,7 +37,7 @@ interface ObservableTests : DependencyTests {
|
||||
val p = createProvider()
|
||||
var changes = 0
|
||||
|
||||
val observer = p.observable.observe {
|
||||
val observer = p.observable.observeChange {
|
||||
changes++
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package world.phantasmal.observable.cell
|
||||
|
||||
import world.phantasmal.core.disposable.use
|
||||
import world.phantasmal.observable.ChangeEvent
|
||||
import world.phantasmal.observable.ObservableTests
|
||||
import kotlin.test.*
|
||||
|
||||
@ -15,6 +16,7 @@ interface CellTests : ObservableTests {
|
||||
fun value_is_accessible_without_observers() = test {
|
||||
val p = createProvider()
|
||||
|
||||
// We literally just test that accessing the value property doesn't throw or return null.
|
||||
assertNotNull(p.observable.value)
|
||||
}
|
||||
|
||||
@ -22,106 +24,55 @@ interface CellTests : ObservableTests {
|
||||
fun value_is_accessible_with_observers() = test {
|
||||
val p = createProvider()
|
||||
|
||||
var observedValue: Any? = null
|
||||
disposer.add(p.observable.observeChange {})
|
||||
|
||||
disposer.add(p.observable.observe(callNow = true) {
|
||||
observedValue = it.value
|
||||
})
|
||||
|
||||
assertNotNull(observedValue)
|
||||
// We literally just test that accessing the value property doesn't throw or return null.
|
||||
assertNotNull(p.observable.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun propagates_changes_to_mapped_cell() = test {
|
||||
fun emits_no_change_event_until_changed() = test {
|
||||
val p = createProvider()
|
||||
val mapped = p.observable.map { it.hashCode() }
|
||||
val initialValue = mapped.value
|
||||
|
||||
var observedValue: Int? = null
|
||||
var observedEvent: ChangeEvent<Any>? = null
|
||||
|
||||
disposer.add(mapped.observe {
|
||||
assertNull(observedValue)
|
||||
observedValue = it.value
|
||||
disposer.add(p.observable.observeChange { changeEvent ->
|
||||
observedEvent = changeEvent
|
||||
})
|
||||
|
||||
assertNull(observedEvent)
|
||||
|
||||
p.emit()
|
||||
|
||||
assertNotEquals(initialValue, mapped.value)
|
||||
assertEquals(mapped.value, observedValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun propagates_changes_to_flat_mapped_cell() = test {
|
||||
val p = createProvider()
|
||||
|
||||
val mapped = p.observable.flatMap { ImmutableCell(it.hashCode()) }
|
||||
val initialValue = mapped.value
|
||||
|
||||
var observedValue: Int? = null
|
||||
|
||||
disposer.add(mapped.observe {
|
||||
assertNull(observedValue)
|
||||
observedValue = it.value
|
||||
})
|
||||
|
||||
p.emit()
|
||||
|
||||
assertNotEquals(initialValue, mapped.value)
|
||||
assertEquals(mapped.value, observedValue)
|
||||
assertNotNull(observedEvent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emits_correct_value_in_change_events() = test {
|
||||
val p = createProvider()
|
||||
|
||||
var prevValue: Any?
|
||||
var observedValue: Any? = null
|
||||
|
||||
disposer.add(p.observable.observe {
|
||||
disposer.add(p.observable.observeChange { changeEvent ->
|
||||
assertNull(observedValue)
|
||||
observedValue = it.value
|
||||
observedValue = changeEvent.value
|
||||
})
|
||||
|
||||
repeat(3) {
|
||||
prevValue = observedValue
|
||||
observedValue = null
|
||||
|
||||
p.emit()
|
||||
|
||||
// We should have observed a value, it should be different from the previous value, and
|
||||
// it should be equal to the cell's current value.
|
||||
assertNotNull(observedValue)
|
||||
assertNotEquals(prevValue, observedValue)
|
||||
assertEquals(p.observable.value, observedValue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When [Cell.observe] is called with callNow = true, it should call the observer immediately.
|
||||
* Otherwise it should only call the observer when it changes.
|
||||
*/
|
||||
@Test
|
||||
fun respects_call_now_argument() = test {
|
||||
val p = createProvider()
|
||||
var changes = 0
|
||||
|
||||
// Test callNow = false
|
||||
p.observable.observe(callNow = false) {
|
||||
changes++
|
||||
}.use {
|
||||
p.emit()
|
||||
|
||||
assertEquals(1, changes)
|
||||
}
|
||||
|
||||
// Test callNow = true
|
||||
changes = 0
|
||||
|
||||
p.observable.observe(callNow = true) {
|
||||
changes++
|
||||
}.use {
|
||||
p.emit()
|
||||
|
||||
assertEquals(2, changes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Cell.value] should correctly reflect changes even when the [Cell] has no observers.
|
||||
* Typically this means that the cell's value is not updated in real time, only when it is
|
||||
@ -148,6 +99,63 @@ interface CellTests : ObservableTests {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// CellUtils Tests
|
||||
//
|
||||
|
||||
@Test
|
||||
fun propagates_changes_to_observeNow_observers() = test {
|
||||
val p = createProvider()
|
||||
var changes = 0
|
||||
|
||||
p.observable.observeNow {
|
||||
changes++
|
||||
}.use {
|
||||
p.emit()
|
||||
|
||||
assertEquals(2, changes)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun propagates_changes_to_mapped_cell() = test {
|
||||
val p = createProvider()
|
||||
val mapped = p.observable.map { it.hashCode() }
|
||||
val initialValue = mapped.value
|
||||
|
||||
var observedValue: Int? = null
|
||||
|
||||
disposer.add(mapped.observeChange { changeEvent ->
|
||||
assertNull(observedValue)
|
||||
observedValue = changeEvent.value
|
||||
})
|
||||
|
||||
p.emit()
|
||||
|
||||
assertNotEquals(initialValue, mapped.value)
|
||||
assertEquals(mapped.value, observedValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun propagates_changes_to_flat_mapped_cell() = test {
|
||||
val p = createProvider()
|
||||
|
||||
val mapped = p.observable.flatMap { ImmutableCell(it.hashCode()) }
|
||||
val initialValue = mapped.value
|
||||
|
||||
var observedValue: Int? = null
|
||||
|
||||
disposer.add(mapped.observeChange {
|
||||
assertNull(observedValue)
|
||||
observedValue = it.value
|
||||
})
|
||||
|
||||
p.emit()
|
||||
|
||||
assertNotEquals(initialValue, mapped.value)
|
||||
assertEquals(mapped.value, observedValue)
|
||||
}
|
||||
|
||||
interface Provider : ObservableTests.Provider {
|
||||
override val observable: Cell<Any>
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ interface CellWithDependenciesTests : CellTests {
|
||||
|
||||
var observedChanges = 0
|
||||
|
||||
disposer.add(leaf.observe { observedChanges++ })
|
||||
disposer.add(leaf.observeChange { observedChanges++ })
|
||||
|
||||
// Change root, which results in both branches changing and thus two dependencies of leaf
|
||||
// changing.
|
||||
@ -47,7 +47,7 @@ interface CellWithDependenciesTests : CellTests {
|
||||
|
||||
assertTrue(dependency.publicDependents.isEmpty())
|
||||
|
||||
disposer.add(cell.observe { })
|
||||
disposer.add(cell.observeChange { })
|
||||
|
||||
assertEquals(1, dependency.publicDependents.size)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class ChangeTests : ObservableTestSuite {
|
||||
val dependent = dependency.map { 2 * it }
|
||||
|
||||
var dependentObservedValue: Int? = null
|
||||
disposer.add(dependent.observe { dependentObservedValue = it.value })
|
||||
disposer.add(dependent.observeChange { dependentObservedValue = it.value })
|
||||
|
||||
assertFails {
|
||||
change {
|
||||
|
@ -7,24 +7,21 @@ import kotlin.test.assertEquals
|
||||
|
||||
class ImmutableCellTests : ObservableTestSuite {
|
||||
@Test
|
||||
fun observing_it_should_never_create_leaks() = test {
|
||||
fun observing_it_never_creates_leaks() = test {
|
||||
val cell = ImmutableCell("test value")
|
||||
|
||||
TrackedDisposable.checkNoLeaks {
|
||||
// We never call dispose on the returned disposables.
|
||||
cell.observe {}
|
||||
cell.observe(callNow = false) {}
|
||||
cell.observe(callNow = true) {}
|
||||
// We never call dispose on the returned disposable.
|
||||
cell.observeChange {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun observe_respects_callNow() = test {
|
||||
fun observeNow_calls_the_observer_once() = test {
|
||||
val cell = ImmutableCell("test value")
|
||||
var calls = 0
|
||||
|
||||
cell.observe(callNow = false) { calls++ }
|
||||
cell.observe(callNow = true) { calls++ }
|
||||
cell.observeNow { calls++ }
|
||||
|
||||
assertEquals(1, calls)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ interface MutableCellTests<T : Any> : CellTests {
|
||||
|
||||
var observedValue: Any? = null
|
||||
|
||||
disposer.add(p.observable.observe {
|
||||
disposer.add(p.observable.observeChange {
|
||||
assertNull(observedValue)
|
||||
observedValue = it.value
|
||||
})
|
||||
|
@ -46,10 +46,10 @@ class FilteredListCellTests : ListCellTests {
|
||||
var changes = 0
|
||||
var listChanges = 0
|
||||
|
||||
disposer.add(list.observe {
|
||||
disposer.add(list.observeChange {
|
||||
changes++
|
||||
})
|
||||
disposer.add(list.observeList {
|
||||
disposer.add(list.observeListChange {
|
||||
listChanges++
|
||||
})
|
||||
|
||||
@ -74,7 +74,7 @@ class FilteredListCellTests : ListCellTests {
|
||||
val list = FilteredListCell(dep, predicate = { it % 2 == 0 })
|
||||
var event: ListChangeEvent<Int>? = null
|
||||
|
||||
disposer.add(list.observeList {
|
||||
disposer.add(list.observeListChange {
|
||||
assertNull(event)
|
||||
event = it
|
||||
})
|
||||
@ -128,7 +128,7 @@ class FilteredListCellTests : ListCellTests {
|
||||
val list = FilteredListCell(dep, predicate = { it.value % 2 == 0 })
|
||||
var event: ListChangeEvent<SimpleCell<Int>>? = null
|
||||
|
||||
disposer.add(list.observeList {
|
||||
disposer.add(list.observeListChange {
|
||||
assertNull(event)
|
||||
event = it
|
||||
})
|
||||
|
@ -1,43 +1,29 @@
|
||||
package world.phantasmal.observable.cell.list
|
||||
|
||||
import world.phantasmal.core.disposable.TrackedDisposable
|
||||
import world.phantasmal.observable.cell.observeNow
|
||||
import world.phantasmal.observable.test.ObservableTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ImmutableListCellTests : ObservableTestSuite {
|
||||
@Test
|
||||
fun observing_it_should_never_create_leaks() = test {
|
||||
fun observing_it_never_creates_leaks() = test {
|
||||
val listCell = ImmutableListCell(listOf(1, 2, 3))
|
||||
|
||||
TrackedDisposable.checkNoLeaks {
|
||||
// We never call dispose on the returned disposables.
|
||||
listCell.observe {}
|
||||
listCell.observe(callNow = false) {}
|
||||
listCell.observe(callNow = true) {}
|
||||
listCell.observeList(callNow = false) {}
|
||||
listCell.observeList(callNow = true) {}
|
||||
listCell.observeChange {}
|
||||
listCell.observeListChange {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun observe_respects_callNow() = test {
|
||||
fun observeNow_calls_the_observer_once() = test {
|
||||
val listCell = ImmutableListCell(listOf(1, 2, 3))
|
||||
var calls = 0
|
||||
|
||||
listCell.observe(callNow = false) { calls++ }
|
||||
listCell.observe(callNow = true) { calls++ }
|
||||
|
||||
assertEquals(1, calls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun observeList_respects_callNow() = test {
|
||||
val listCell = ImmutableListCell(listOf(1, 2, 3))
|
||||
var calls = 0
|
||||
|
||||
listCell.observeList(callNow = false) { calls++ }
|
||||
listCell.observeList(callNow = true) { calls++ }
|
||||
listCell.observeNow { calls++ }
|
||||
|
||||
assertEquals(1, calls)
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ interface ListCellTests : CellTests {
|
||||
fun list_value_is_accessible_without_observers() = test {
|
||||
val p = createListProvider(empty = false)
|
||||
|
||||
// We literally just test that accessing the value property doesn't throw or return the
|
||||
// wrong list.
|
||||
assertTrue(p.observable.value.isNotEmpty())
|
||||
}
|
||||
|
||||
@ -23,14 +25,28 @@ interface ListCellTests : CellTests {
|
||||
fun list_value_is_accessible_with_observers() = test {
|
||||
val p = createListProvider(empty = false)
|
||||
|
||||
var observedValue: List<*>? = null
|
||||
disposer.add(p.observable.observeListChange {})
|
||||
|
||||
disposer.add(p.observable.observe(callNow = true) {
|
||||
observedValue = it.value
|
||||
// We literally just test that accessing the value property doesn't throw or return the
|
||||
// wrong list.
|
||||
assertTrue(p.observable.value.isNotEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emits_no_list_change_event_until_changed() = test {
|
||||
val p = createListProvider(empty = false)
|
||||
|
||||
var observedEvent: ListChangeEvent<Any>? = null
|
||||
|
||||
disposer.add(p.observable.observeListChange { listChangeEvent ->
|
||||
observedEvent = listChangeEvent
|
||||
})
|
||||
|
||||
assertTrue(observedValue!!.isNotEmpty())
|
||||
assertTrue(p.observable.value.isNotEmpty())
|
||||
assertNull(observedEvent)
|
||||
|
||||
p.emit()
|
||||
|
||||
assertNotNull(observedEvent)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -40,7 +56,7 @@ interface ListCellTests : CellTests {
|
||||
var event: ListChangeEvent<*>? = null
|
||||
|
||||
disposer.add(
|
||||
p.observable.observeList {
|
||||
p.observable.observeListChange {
|
||||
assertNull(event)
|
||||
event = it
|
||||
}
|
||||
@ -64,7 +80,7 @@ interface ListCellTests : CellTests {
|
||||
var observedSize: Int? = null
|
||||
|
||||
disposer.add(
|
||||
p.observable.size.observe {
|
||||
p.observable.size.observeChange {
|
||||
assertNull(observedSize)
|
||||
observedSize = it.value
|
||||
}
|
||||
@ -102,7 +118,7 @@ interface ListCellTests : CellTests {
|
||||
|
||||
var observedValue: Int? = null
|
||||
|
||||
disposer.add(fold.observe {
|
||||
disposer.add(fold.observeChange {
|
||||
assertNull(observedValue)
|
||||
observedValue = it.value
|
||||
})
|
||||
@ -127,7 +143,7 @@ interface ListCellTests : CellTests {
|
||||
|
||||
var observedValue: Int? = null
|
||||
|
||||
disposer.add(sum.observe {
|
||||
disposer.add(sum.observeChange {
|
||||
assertNull(observedValue)
|
||||
observedValue = it.value
|
||||
})
|
||||
@ -152,7 +168,7 @@ interface ListCellTests : CellTests {
|
||||
|
||||
var event: ListChangeEvent<*>? = null
|
||||
|
||||
disposer.add(filtered.observeList {
|
||||
disposer.add(filtered.observeListChange {
|
||||
assertNull(event)
|
||||
event = it
|
||||
})
|
||||
@ -177,7 +193,7 @@ interface ListCellTests : CellTests {
|
||||
|
||||
var observedValue: Any? = null
|
||||
|
||||
disposer.add(firstOrNull.observe {
|
||||
disposer.add(firstOrNull.observeChange {
|
||||
assertNull(observedValue)
|
||||
observedValue = it.value
|
||||
})
|
||||
|
@ -16,7 +16,7 @@ interface MutableListCellTests<T : Any> : ListCellTests, MutableCellTests<List<T
|
||||
|
||||
var changeEvent: ListChangeEvent<T>? = null
|
||||
|
||||
disposer.add(p.observable.observeList {
|
||||
disposer.add(p.observable.observeListChange {
|
||||
assertNull(changeEvent)
|
||||
changeEvent = it
|
||||
})
|
||||
|
@ -245,7 +245,7 @@ class SimpleListCellTests : MutableListCellTests<Int> {
|
||||
|
||||
var event: ListChangeEvent<SimpleCell<String>>? = null
|
||||
|
||||
disposer.add(list.observeList {
|
||||
disposer.add(list.observeListChange {
|
||||
assertNull(event)
|
||||
event = it
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
package world.phantasmal.web.application.widgets
|
||||
|
||||
import org.w3c.dom.Node
|
||||
import world.phantasmal.observable.Observable
|
||||
import world.phantasmal.observable.cell.Cell
|
||||
import world.phantasmal.observable.cell.nullCell
|
||||
import world.phantasmal.observable.cell.trueCell
|
||||
import world.phantasmal.web.core.PwToolType
|
||||
@ -12,7 +12,7 @@ import world.phantasmal.webui.widgets.Control
|
||||
|
||||
class PwToolButton(
|
||||
private val tool: PwToolType,
|
||||
private val toggled: Observable<Boolean>,
|
||||
private val toggled: Cell<Boolean>,
|
||||
private val onMouseDown: () -> Unit,
|
||||
) : Control(visible = trueCell(), enabled = trueCell(), tooltip = nullCell()) {
|
||||
private val inputId = "pw-application-pw-tool-button-${tool.name.lowercase()}"
|
||||
@ -25,7 +25,7 @@ class PwToolButton(
|
||||
type = "radio"
|
||||
id = inputId
|
||||
name = "pw-application-pw-tool-button"
|
||||
observe(toggled) { checked = it }
|
||||
observeNow(toggled) { checked = it }
|
||||
}
|
||||
label {
|
||||
htmlFor = inputId
|
||||
|
@ -15,7 +15,7 @@ open class PathAwareTabContainerController<T : PathAwareTab>(
|
||||
tabs: List<T>,
|
||||
) : TabContainerController<T>(tabs) {
|
||||
init {
|
||||
observe(uiStore.path) { path ->
|
||||
observeNow(uiStore.path) { path ->
|
||||
if (uiStore.currentTool.value == tool) {
|
||||
tabs.find { path.startsWith(it.path) }?.let {
|
||||
setActiveTab(it, replaceUrl = true)
|
||||
|
@ -82,7 +82,7 @@ class UiStore(private val applicationUrl: ApplicationUrl) : Store() {
|
||||
window.disposableListener("keydown", ::dispatchGlobalKeyDown),
|
||||
)
|
||||
|
||||
observe(applicationUrl.url) { setDataFromUrl(it) }
|
||||
observeNow(applicationUrl.url) { setDataFromUrl(it) }
|
||||
}
|
||||
|
||||
fun setCurrentTool(tool: PwToolType) {
|
||||
@ -126,12 +126,12 @@ class UiStore(private val applicationUrl: ApplicationUrl) : Store() {
|
||||
}
|
||||
|
||||
return Disposer(
|
||||
value.observe {
|
||||
value.observeChange {
|
||||
if (it.value != param.value) {
|
||||
setParameter(tool, path, param, it.value, replaceUrl = false)
|
||||
}
|
||||
},
|
||||
param.observe { onChange(it.value) },
|
||||
param.observeChange { onChange(it.value) },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ class DockWidget(
|
||||
style.width = ""
|
||||
style.height = ""
|
||||
|
||||
addDisposable(size.observe { (size) ->
|
||||
addDisposable(size.observeChange { (size) ->
|
||||
goldenLayout.updateSize(size.width, size.height)
|
||||
})
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class RendererWidget(
|
||||
div {
|
||||
className = "pw-core-renderer"
|
||||
|
||||
observe(selfOrAncestorVisible) { visible ->
|
||||
observeNow(selfOrAncestorVisible) { visible ->
|
||||
if (visible) {
|
||||
renderer.startRendering()
|
||||
} else {
|
||||
@ -20,7 +20,7 @@ class RendererWidget(
|
||||
}
|
||||
}
|
||||
|
||||
addDisposable(size.observe { (size) ->
|
||||
addDisposable(size.observeChange { (size) ->
|
||||
renderer.setSize(size.width.toInt(), size.height.toInt())
|
||||
})
|
||||
|
||||
|
@ -31,7 +31,7 @@ class HuntMethodStore(
|
||||
|
||||
/** Hunting methods supported by the current server. */
|
||||
val methods: ListCell<HuntMethodModel> by lazy {
|
||||
observe(uiStore.server) { _methodsStatus.load() }
|
||||
observeNow(uiStore.server) { _methodsStatus.load() }
|
||||
_methods
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ class HuntOptimizerStore(
|
||||
private var wantedItemsPersistenceObserver: Disposable? = null
|
||||
|
||||
val huntableItems: ListCell<ItemType> by lazy {
|
||||
observe(uiStore.server) { server ->
|
||||
observeNow(uiStore.server) { server ->
|
||||
_huntableItems.clear()
|
||||
|
||||
scope.launch {
|
||||
@ -71,12 +71,12 @@ class HuntOptimizerStore(
|
||||
}
|
||||
|
||||
val wantedItems: ListCell<WantedItemModel> by lazy {
|
||||
observe(uiStore.server) { loadWantedItems(it) }
|
||||
observeNow(uiStore.server) { loadWantedItems(it) }
|
||||
_wantedItems
|
||||
}
|
||||
|
||||
val optimizationResult: Cell<OptimizationResultModel> by lazy {
|
||||
observe(wantedItems, huntMethodStore.methods) { wantedItems, huntMethods ->
|
||||
observeNow(wantedItems, huntMethodStore.methods) { wantedItems, huntMethods ->
|
||||
scope.launch(Dispatchers.Default) {
|
||||
_optimizationResult.value = optimize(wantedItems, huntMethods)
|
||||
}
|
||||
@ -114,7 +114,7 @@ class HuntOptimizerStore(
|
||||
_wantedItems.replaceAll(wantedItems)
|
||||
|
||||
// Wanted items are loaded, start observing them and persist whenever they change.
|
||||
wantedItemsPersistenceObserver = _wantedItems.observe {
|
||||
wantedItemsPersistenceObserver = _wantedItems.observeChange {
|
||||
val items = it.value
|
||||
|
||||
scope.launch(Dispatchers.Main) {
|
||||
|
@ -11,8 +11,8 @@ class DestinationInstance(
|
||||
) : Instance<QuestObjectModel>(entity, mesh, instanceIndex) {
|
||||
init {
|
||||
addDisposables(
|
||||
entity.destinationPosition.observe { updateMatrix() },
|
||||
entity.destinationRotationY.observe { updateMatrix() },
|
||||
entity.destinationPosition.observeChange { updateMatrix() },
|
||||
entity.destinationRotationY.observeChange { updateMatrix() },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -13,14 +13,14 @@ class EntityInstance(
|
||||
) : Instance<QuestEntityModel<*, *>>(entity, mesh, instanceIndex) {
|
||||
init {
|
||||
if (entity is QuestObjectModel) {
|
||||
addDisposable(entity.model.observe(callNow = false) {
|
||||
addDisposable(entity.model.observeChange {
|
||||
modelChanged(this.instanceIndex)
|
||||
})
|
||||
}
|
||||
|
||||
addDisposables(
|
||||
entity.worldPosition.observe { updateMatrix() },
|
||||
entity.worldRotation.observe { updateMatrix() },
|
||||
entity.worldPosition.observeChange { updateMatrix() },
|
||||
entity.worldRotation.observeChange { updateMatrix() },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import mu.KotlinLogging
|
||||
import org.khronos.webgl.Float32Array
|
||||
import world.phantasmal.core.disposable.DisposableSupervisedScope
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.observable.cell.observeNow
|
||||
import world.phantasmal.psolib.fileFormats.quest.EntityType
|
||||
import world.phantasmal.web.core.rendering.disposeObject3DResources
|
||||
import world.phantasmal.web.externals.three.*
|
||||
@ -92,13 +93,13 @@ class EntityMeshManager(
|
||||
}
|
||||
|
||||
init {
|
||||
observe(questEditorStore.highlightedEntity) { entity ->
|
||||
observeNow(questEditorStore.highlightedEntity) { entity ->
|
||||
// getEntityInstance can return null at this point because the entity mesh might not be
|
||||
// loaded yet.
|
||||
markHighlighted(entity?.let(::getEntityInstance))
|
||||
}
|
||||
|
||||
observe(questEditorStore.selectedEntity) { entity ->
|
||||
observeNow(questEditorStore.selectedEntity) { entity ->
|
||||
// getEntityInstance can return null at this point because the entity mesh might not be
|
||||
// loaded yet.
|
||||
markSelected(entity?.let(::getEntityInstance))
|
||||
@ -135,7 +136,9 @@ class EntityMeshManager(
|
||||
destinationInstanceContainer.addInstance(entity)
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
// Do nothing.
|
||||
logger.trace(e) {
|
||||
"Mesh loading for entity of type ${entity.type} cancelled."
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logger.error(e) {
|
||||
"Couldn't load mesh for entity of type ${entity.type}."
|
||||
@ -228,12 +231,12 @@ class EntityMeshManager(
|
||||
newInstance.entity is QuestObjectModel &&
|
||||
newInstance.entity.hasDestination
|
||||
) {
|
||||
warpLineDisposer.add(newInstance.entity.worldPosition.observe(callNow = true) {
|
||||
warpLineBufferAttribute.setXYZ(0, it.value.x, it.value.y, it.value.z)
|
||||
warpLineDisposer.add(newInstance.entity.worldPosition.observeNow {
|
||||
warpLineBufferAttribute.setXYZ(0, it.x, it.y, it.z)
|
||||
warpLineBufferAttribute.needsUpdate = true
|
||||
})
|
||||
warpLineDisposer.add(newInstance.entity.destinationPosition.observe(callNow = true) {
|
||||
warpLineBufferAttribute.setXYZ(1, it.value.x, it.value.y, it.value.z)
|
||||
warpLineDisposer.add(newInstance.entity.destinationPosition.observeNow {
|
||||
warpLineBufferAttribute.setXYZ(1, it.x, it.y, it.z)
|
||||
warpLineBufferAttribute.needsUpdate = true
|
||||
})
|
||||
warpLines.visible = true
|
||||
|
@ -14,14 +14,14 @@ class QuestEditorMeshManager(
|
||||
renderContext: QuestRenderContext,
|
||||
) : QuestMeshManager(areaAssetLoader, entityAssetLoader, questEditorStore, renderContext) {
|
||||
init {
|
||||
observe(
|
||||
observeNow(
|
||||
questEditorStore.currentQuest,
|
||||
questEditorStore.currentAreaVariant,
|
||||
) { quest, areaVariant ->
|
||||
loadAreaMeshes(quest?.episode, areaVariant)
|
||||
}
|
||||
|
||||
observe(
|
||||
observeNow(
|
||||
questEditorStore.currentQuest,
|
||||
questEditorStore.currentArea,
|
||||
questEditorStore.selectedEvent.flatMapNull { it?.wave },
|
||||
@ -39,7 +39,7 @@ class QuestEditorMeshManager(
|
||||
)
|
||||
}
|
||||
|
||||
observe(
|
||||
observeNow(
|
||||
questEditorStore.currentQuest,
|
||||
questEditorStore.currentArea,
|
||||
) { quest, area ->
|
||||
@ -54,7 +54,7 @@ class QuestEditorMeshManager(
|
||||
)
|
||||
}
|
||||
|
||||
observe(questEditorStore.showCollisionGeometry) {
|
||||
observeNow(questEditorStore.showCollisionGeometry) {
|
||||
renderContext.collisionGeometryVisible = it
|
||||
renderContext.renderGeometryVisible = !it
|
||||
}
|
||||
|
@ -5,10 +5,10 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.disposable.DisposableSupervisedScope
|
||||
import world.phantasmal.psolib.Episode
|
||||
import world.phantasmal.observable.cell.list.ListCell
|
||||
import world.phantasmal.observable.cell.list.ListChange
|
||||
import world.phantasmal.observable.cell.list.ListChangeEvent
|
||||
import world.phantasmal.psolib.Episode
|
||||
import world.phantasmal.web.questEditor.loading.AreaAssetLoader
|
||||
import world.phantasmal.web.questEditor.loading.EntityAssetLoader
|
||||
import world.phantasmal.web.questEditor.models.AreaVariantModel
|
||||
@ -55,7 +55,9 @@ abstract class QuestMeshManager protected constructor(
|
||||
npcObserver?.dispose()
|
||||
npcMeshManager.removeAll()
|
||||
|
||||
npcObserver = npcs.observeList(callNow = true, ::npcsChanged)
|
||||
npcs.value.forEach(npcMeshManager::add)
|
||||
|
||||
npcObserver = npcs.observeListChange(::npcsChanged)
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +67,9 @@ abstract class QuestMeshManager protected constructor(
|
||||
objectObserver?.dispose()
|
||||
objectMeshManager.removeAll()
|
||||
|
||||
objectObserver = objects.observeList(callNow = true, ::objectsChanged)
|
||||
objects.value.forEach(objectMeshManager::add)
|
||||
|
||||
objectObserver = objects.observeListChange(::objectsChanged)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ class QuestRenderer(
|
||||
),
|
||||
)
|
||||
|
||||
observe(questEditorStore.currentQuest) { inputManager.resetCamera() }
|
||||
observe(questEditorStore.currentAreaVariant) { inputManager.resetCamera() }
|
||||
observeNow(questEditorStore.currentQuest) { inputManager.resetCamera() }
|
||||
observeNow(questEditorStore.currentAreaVariant) { inputManager.resetCamera() }
|
||||
}
|
||||
}
|
||||
|
@ -84,8 +84,8 @@ class QuestInputManager(
|
||||
stateContext = StateContext(questEditorStore, renderContext, cameraInputManager)
|
||||
state = IdleState(stateContext, entityManipulationEnabled)
|
||||
|
||||
observe(questEditorStore.selectedEntity) { returnToIdleState() }
|
||||
observe(questEditorStore.questEditingEnabled) { entityManipulationEnabled = it }
|
||||
observeNow(questEditorStore.selectedEntity) { returnToIdleState() }
|
||||
observeNow(questEditorStore.questEditingEnabled) { entityManipulationEnabled = it }
|
||||
|
||||
pointerTrap.className = "pw-quest-editor-input-manager-pointer-trap"
|
||||
pointerTrap.hidden = true
|
||||
|
@ -55,11 +55,11 @@ class AsmStore(
|
||||
val problems: ListCell<AssemblyProblem> = asmAnalyser.problems
|
||||
|
||||
init {
|
||||
observe(questEditorStore.currentQuest) { quest ->
|
||||
observeNow(questEditorStore.currentQuest) { quest ->
|
||||
setTextModel(quest, inlineStackArgs.value)
|
||||
}
|
||||
|
||||
observe(inlineStackArgs) { inlineStackArgs ->
|
||||
observeNow(inlineStackArgs) { inlineStackArgs ->
|
||||
// Ensure we have the most up-to-date bytecode before we disassemble it again.
|
||||
if (setBytecodeIrTimeout != null) {
|
||||
setBytecodeIr()
|
||||
@ -72,7 +72,7 @@ class AsmStore(
|
||||
scope.launch { questEditorStore.setMapDesignations(it) }
|
||||
}
|
||||
|
||||
observe(problems) { problems ->
|
||||
observeNow(problems) { problems ->
|
||||
textModel.value?.let { model ->
|
||||
val markers = Array<IMarkerData>(problems.size) {
|
||||
val problem = problems[it]
|
||||
|
@ -94,13 +94,13 @@ class QuestEditorStore(
|
||||
},
|
||||
)
|
||||
|
||||
observe(uiStore.currentTool) { tool ->
|
||||
observeNow(uiStore.currentTool) { tool ->
|
||||
if (tool == PwToolType.QuestEditor) {
|
||||
makeMainUndoCurrent()
|
||||
}
|
||||
}
|
||||
|
||||
observe(currentQuest.flatMap { it?.npcs ?: emptyListCell() }) { npcs ->
|
||||
observeNow(currentQuest.flatMap { it?.npcs ?: emptyListCell() }) { npcs ->
|
||||
val selected = selectedEntity.value
|
||||
|
||||
if (selected is QuestNpcModel && selected !in npcs) {
|
||||
@ -108,7 +108,7 @@ class QuestEditorStore(
|
||||
}
|
||||
}
|
||||
|
||||
observe(currentQuest.flatMap { it?.objects ?: emptyListCell() }) { objects ->
|
||||
observeNow(currentQuest.flatMap { it?.objects ?: emptyListCell() }) { objects ->
|
||||
val selected = selectedEntity.value
|
||||
|
||||
if (selected is QuestObjectModel && selected !in objects) {
|
||||
|
@ -53,7 +53,7 @@ class TextModelUndo(
|
||||
|
||||
init {
|
||||
undoManager.addUndo(this)
|
||||
modelObserver = model.observe(callNow = true) { onModelChange(it.value) }
|
||||
modelObserver = model.observeNow(::onModelChange)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
@ -32,11 +32,11 @@ class AsmEditorWidget(private val ctrl: AsmEditorController) : Widget() {
|
||||
|
||||
addDisposable(disposable { editor.dispose() })
|
||||
|
||||
observe(ctrl.textModel) { editor.setModel(it) }
|
||||
observeNow(ctrl.textModel) { editor.setModel(it) }
|
||||
|
||||
observe(ctrl.readOnly) { editor.updateOptions(obj { readOnly = it }) }
|
||||
observeNow(ctrl.readOnly) { editor.updateOptions(obj { readOnly = it }) }
|
||||
|
||||
addDisposable(size.observe { (size) ->
|
||||
addDisposable(size.observeChange { (size) ->
|
||||
if (size.width > .0 && size.height > .0) {
|
||||
editor.layout(obj {
|
||||
width = size.width
|
||||
@ -65,7 +65,7 @@ class AsmEditorWidget(private val ctrl: AsmEditorController) : Widget() {
|
||||
editor.trigger(
|
||||
source = AsmEditorWidget::class.simpleName,
|
||||
handlerId = "undo",
|
||||
payload = undefined
|
||||
payload = undefined,
|
||||
)
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ class AsmEditorWidget(private val ctrl: AsmEditorController) : Widget() {
|
||||
editor.trigger(
|
||||
source = AsmEditorWidget::class.simpleName,
|
||||
handlerId = "redo",
|
||||
payload = undefined
|
||||
payload = undefined,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ class EntityInfoWidget(private val ctrl: EntityInfoController) : Widget(enabled
|
||||
val inputValue = mutableCell(value.value)
|
||||
var timeout = -1
|
||||
|
||||
observe(value) {
|
||||
observeNow(value) {
|
||||
if (timeout == -1) {
|
||||
timeout = window.setTimeout({
|
||||
inputValue.value = value.value
|
||||
|
@ -36,7 +36,7 @@ class EventWidget(
|
||||
}
|
||||
}
|
||||
|
||||
observe(isSelected) {
|
||||
observeNow(isSelected) {
|
||||
if (it) {
|
||||
scrollIntoView(obj<ScrollIntoViewOptions> {
|
||||
behavior = ScrollBehavior.SMOOTH
|
||||
|
@ -56,14 +56,14 @@ class MeshRenderer(
|
||||
)
|
||||
|
||||
init {
|
||||
observe(viewerStore.currentNinjaGeometry) { rebuildMesh(resetCamera = true) }
|
||||
observe(viewerStore.currentTextures) { rebuildMesh(resetCamera = true) }
|
||||
observe(viewerStore.applyTextures) { rebuildMesh(resetCamera = false) }
|
||||
observe(viewerStore.currentNinjaMotion, ::ninjaMotionChanged)
|
||||
observe(viewerStore.showSkeleton) { skeletonHelper?.visible = it }
|
||||
observe(viewerStore.animationPlaying, ::animationPlayingChanged)
|
||||
observe(viewerStore.frameRate, ::frameRateChanged)
|
||||
observe(viewerStore.frame, ::frameChanged)
|
||||
observeNow(viewerStore.currentNinjaGeometry) { rebuildMesh(resetCamera = true) }
|
||||
observeNow(viewerStore.currentTextures) { rebuildMesh(resetCamera = true) }
|
||||
observeNow(viewerStore.applyTextures) { rebuildMesh(resetCamera = false) }
|
||||
observeNow(viewerStore.currentNinjaMotion, ::ninjaMotionChanged)
|
||||
observeNow(viewerStore.showSkeleton) { skeletonHelper?.visible = it }
|
||||
observeNow(viewerStore.animationPlaying, ::animationPlayingChanged)
|
||||
observeNow(viewerStore.frameRate, ::frameRateChanged)
|
||||
observeNow(viewerStore.frame, ::frameChanged)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
@ -46,7 +46,7 @@ class TextureRenderer(
|
||||
))
|
||||
|
||||
init {
|
||||
observe(store.currentTextures) {
|
||||
observeNow(store.currentTextures) {
|
||||
texturesChanged(it.filterNotNull())
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package world.phantasmal.web.questEditor.controllers
|
||||
|
||||
import world.phantasmal.observable.cell.observeNow
|
||||
import world.phantasmal.web.questEditor.models.QuestEventActionModel
|
||||
import world.phantasmal.web.questEditor.models.QuestEventModel
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
@ -115,9 +116,9 @@ class EventsControllerTests : WebTestSuite {
|
||||
// We test the observed value instead of the cell's value property.
|
||||
var canGoToEventValue: Boolean? = null
|
||||
|
||||
disposer.add(canGoToEvent.observe(callNow = true) {
|
||||
disposer.add(canGoToEvent.observeNow {
|
||||
assertNull(canGoToEventValue)
|
||||
canGoToEventValue = it.value
|
||||
canGoToEventValue = it
|
||||
})
|
||||
|
||||
assertEquals(true, canGoToEventValue)
|
||||
|
@ -4,8 +4,9 @@ import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.core.disposable.TrackedDisposable
|
||||
import world.phantasmal.observable.Observable
|
||||
import world.phantasmal.observable.Observer
|
||||
import world.phantasmal.observable.cell.Cell
|
||||
import world.phantasmal.observable.cell.observeNow
|
||||
import world.phantasmal.observable.observe
|
||||
|
||||
abstract class DisposableContainer : TrackedDisposable() {
|
||||
private val disposer = Disposer()
|
||||
@ -29,85 +30,55 @@ abstract class DisposableContainer : TrackedDisposable() {
|
||||
disposer.remove(disposable, dispose)
|
||||
}
|
||||
|
||||
protected fun <V1> observe(observable: Observable<V1>, operation: (V1) -> Unit) {
|
||||
addDisposable(
|
||||
if (observable is Cell<V1>) {
|
||||
observable.observe(callNow = true) { operation(it.value) }
|
||||
} else {
|
||||
observable.observe { operation(it.value) }
|
||||
}
|
||||
)
|
||||
protected fun <T> observe(
|
||||
o1: Observable<T>,
|
||||
observer: (T) -> Unit,
|
||||
) {
|
||||
addDisposable(o1.observe(observer))
|
||||
}
|
||||
|
||||
protected fun <V1, V2> observe(
|
||||
v1: Cell<V1>,
|
||||
v2: Cell<V2>,
|
||||
operation: (V1, V2) -> Unit,
|
||||
protected fun <T> observeNow(
|
||||
c1: Cell<T>,
|
||||
observer: (T) -> Unit,
|
||||
) {
|
||||
val observer: Observer<*> = {
|
||||
operation(v1.value, v2.value)
|
||||
}
|
||||
addDisposables(
|
||||
v1.observe(observer),
|
||||
v2.observe(observer),
|
||||
)
|
||||
operation(v1.value, v2.value)
|
||||
addDisposable(c1.observeNow(observer))
|
||||
}
|
||||
|
||||
protected fun <V1, V2, V3> observe(
|
||||
v1: Cell<V1>,
|
||||
v2: Cell<V2>,
|
||||
v3: Cell<V3>,
|
||||
operation: (V1, V2, V3) -> Unit,
|
||||
protected fun <T1, T2> observeNow(
|
||||
c1: Cell<T1>,
|
||||
c2: Cell<T2>,
|
||||
observer: (T1, T2) -> Unit,
|
||||
) {
|
||||
val observer: Observer<*> = {
|
||||
operation(v1.value, v2.value, v3.value)
|
||||
}
|
||||
addDisposables(
|
||||
v1.observe(observer),
|
||||
v2.observe(observer),
|
||||
v3.observe(observer),
|
||||
)
|
||||
operation(v1.value, v2.value, v3.value)
|
||||
addDisposable(world.phantasmal.observable.cell.observeNow(c1, c2, observer))
|
||||
}
|
||||
|
||||
protected fun <V1, V2, V3, V4> observe(
|
||||
v1: Cell<V1>,
|
||||
v2: Cell<V2>,
|
||||
v3: Cell<V3>,
|
||||
v4: Cell<V4>,
|
||||
operation: (V1, V2, V3, V4) -> Unit,
|
||||
protected fun <T1, T2, T3> observeNow(
|
||||
c1: Cell<T1>,
|
||||
c2: Cell<T2>,
|
||||
c3: Cell<T3>,
|
||||
observer: (T1, T2, T3) -> Unit,
|
||||
) {
|
||||
val observer: Observer<*> = {
|
||||
operation(v1.value, v2.value, v3.value, v4.value)
|
||||
}
|
||||
addDisposables(
|
||||
v1.observe(observer),
|
||||
v2.observe(observer),
|
||||
v3.observe(observer),
|
||||
v4.observe(observer),
|
||||
)
|
||||
operation(v1.value, v2.value, v3.value, v4.value)
|
||||
addDisposable(world.phantasmal.observable.cell.observeNow(c1, c2, c3, observer))
|
||||
}
|
||||
|
||||
protected fun <V1, V2, V3, V4, V5> observe(
|
||||
v1: Cell<V1>,
|
||||
v2: Cell<V2>,
|
||||
v3: Cell<V3>,
|
||||
v4: Cell<V4>,
|
||||
v5: Cell<V5>,
|
||||
operation: (V1, V2, V3, V4, V5) -> Unit,
|
||||
protected fun <T1, T2, T3, T4> observeNow(
|
||||
c1: Cell<T1>,
|
||||
c2: Cell<T2>,
|
||||
c3: Cell<T3>,
|
||||
c4: Cell<T4>,
|
||||
observer: (T1, T2, T3, T4) -> Unit,
|
||||
) {
|
||||
val observer: Observer<*> = {
|
||||
operation(v1.value, v2.value, v3.value, v4.value, v5.value)
|
||||
}
|
||||
addDisposables(
|
||||
v1.observe(observer),
|
||||
v2.observe(observer),
|
||||
v3.observe(observer),
|
||||
v4.observe(observer),
|
||||
v5.observe(observer),
|
||||
)
|
||||
operation(v1.value, v2.value, v3.value, v4.value, v5.value)
|
||||
addDisposable(world.phantasmal.observable.cell.observeNow(c1, c2, c3, c4, observer))
|
||||
}
|
||||
|
||||
protected fun <T1, T2, T3, T4, T5> observeNow(
|
||||
c1: Cell<T1>,
|
||||
c2: Cell<T2>,
|
||||
c3: Cell<T3>,
|
||||
c4: Cell<T4>,
|
||||
c5: Cell<T5>,
|
||||
observer: (T1, T2, T3, T4, T5) -> Unit,
|
||||
) {
|
||||
addDisposable(world.phantasmal.observable.cell.observeNow(c1, c2, c3, c4, c5, observer))
|
||||
}
|
||||
}
|
||||
|
@ -249,11 +249,8 @@ private fun <T> bindChildrenTo(
|
||||
list: Cell<List<T>>,
|
||||
createChild: Node.(T, index: Int) -> Node,
|
||||
childrenRemoved: () -> Unit,
|
||||
): Disposable =
|
||||
list.observe(callNow = true) { (items) ->
|
||||
parent.innerHTML = ""
|
||||
childrenRemoved()
|
||||
|
||||
): Disposable {
|
||||
inline fun createChildNodes(items: List<T>) {
|
||||
val frag = document.createDocumentFragment()
|
||||
|
||||
items.forEachIndexed { i, item ->
|
||||
@ -263,14 +260,33 @@ private fun <T> bindChildrenTo(
|
||||
parent.appendChild(frag)
|
||||
}
|
||||
|
||||
parent.innerHTML = ""
|
||||
createChildNodes(list.value)
|
||||
|
||||
return list.observeChange { (items) ->
|
||||
parent.innerHTML = ""
|
||||
childrenRemoved()
|
||||
createChildNodes(items)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> bindChildrenTo(
|
||||
parent: Element,
|
||||
list: ListCell<T>,
|
||||
createChild: Node.(T, index: Int) -> Node,
|
||||
childrenRemoved: (index: Int, count: Int) -> Unit,
|
||||
after: (ListChangeEvent<T>) -> Unit,
|
||||
): Disposable =
|
||||
list.observeList(callNow = true) { event: ListChangeEvent<T> ->
|
||||
): Disposable {
|
||||
parent.innerHTML = ""
|
||||
val initialFrag = document.createDocumentFragment()
|
||||
|
||||
list.value.forEachIndexed { i, value ->
|
||||
initialFrag.appendChild(initialFrag.createChild(value, i))
|
||||
}
|
||||
|
||||
parent.appendChild(initialFrag)
|
||||
|
||||
return list.observeListChange { event: ListChangeEvent<T> ->
|
||||
for (change in event.changes) {
|
||||
if (change is ListChange.Structural) {
|
||||
if (change.allRemoved) {
|
||||
@ -299,3 +315,4 @@ private fun <T> bindChildrenTo(
|
||||
|
||||
after(event)
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ open class Button(
|
||||
className = "pw-button-center"
|
||||
|
||||
if (textCell != null) {
|
||||
observe(textCell) {
|
||||
observeNow(textCell) {
|
||||
textContent = it
|
||||
hidden = it.isEmpty()
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class Checkbox(
|
||||
type = "checkbox"
|
||||
|
||||
if (this@Checkbox.checked != null) {
|
||||
observe(this@Checkbox.checked) {
|
||||
observeNow(this@Checkbox.checked) {
|
||||
checked = it
|
||||
}
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ class ComboBox<T : Any>(
|
||||
id = labelId
|
||||
placeholderText?.let { placeholder = it }
|
||||
hidden(!visible)
|
||||
observe(enabled) { disabled = !it }
|
||||
observe(selected) { value = it?.let(itemToString) ?: "" }
|
||||
observeNow(enabled) { disabled = !it }
|
||||
observeNow(selected) { value = it?.let(itemToString) ?: "" }
|
||||
|
||||
onmousedown = ::onInputMouseDown
|
||||
onkeydown = ::onInputKeyDown
|
||||
|
@ -73,7 +73,7 @@ open class Dialog(
|
||||
}
|
||||
|
||||
init {
|
||||
observe(visible) {
|
||||
observeNow(visible) {
|
||||
if (it) {
|
||||
setPosition(
|
||||
(window.innerWidth - WIDTH) / 2,
|
||||
|
@ -34,13 +34,13 @@ abstract class Input<T>(
|
||||
id = labelId
|
||||
classList.add("pw-input-inner")
|
||||
|
||||
observe(this@Input.enabled) { disabled = !it }
|
||||
observeNow(this@Input.enabled) { disabled = !it }
|
||||
|
||||
onchange = { callOnChange(this) }
|
||||
|
||||
interceptInputElement(this)
|
||||
|
||||
observe(this@Input.value) {
|
||||
observeNow(this@Input.value) {
|
||||
setInputValue(this, it)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class LazyLoader(
|
||||
div {
|
||||
className = "pw-lazy-loader"
|
||||
|
||||
observe(this@LazyLoader.visible) { v ->
|
||||
observeNow(this@LazyLoader.visible) { v ->
|
||||
if (v && !initialized) {
|
||||
initialized = true
|
||||
addChild(createWidget())
|
||||
|
@ -53,7 +53,7 @@ class Menu<T : Any>(
|
||||
}
|
||||
}
|
||||
|
||||
observe(this@Menu.visible) {
|
||||
observeNow(this@Menu.visible) {
|
||||
if (it) {
|
||||
onDocumentMouseDownListener =
|
||||
document.disposableListener("mousedown", ::onDocumentMouseDown)
|
||||
@ -66,13 +66,13 @@ class Menu<T : Any>(
|
||||
}
|
||||
}
|
||||
|
||||
observe(enabled) {
|
||||
observeNow(enabled) {
|
||||
if (!it) {
|
||||
clearHighlightItem()
|
||||
}
|
||||
}
|
||||
|
||||
observe(items) {
|
||||
observeNow(items) {
|
||||
clearHighlightItem()
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ class Select<T : Any>(
|
||||
this@Select.className?.let { classList.add(it) }
|
||||
|
||||
// Default to a single space so the inner text part won't be hidden.
|
||||
observe(selected) { buttonText.value = it?.let(itemToString) ?: " " }
|
||||
observeNow(selected) { buttonText.value = it?.let(itemToString) ?: " " }
|
||||
|
||||
addWidget(Button(
|
||||
enabled = enabled,
|
||||
|
@ -28,7 +28,7 @@ class TabContainer<T : Tab>(
|
||||
title = tab.title
|
||||
textContent = tab.title
|
||||
|
||||
observe(ctrl.activeTab) {
|
||||
observeNow(ctrl.activeTab) {
|
||||
if (it == tab) {
|
||||
classList.add(ACTIVE_CLASS)
|
||||
} else {
|
||||
@ -55,7 +55,7 @@ class TabContainer<T : Tab>(
|
||||
}
|
||||
|
||||
init {
|
||||
observe(selfOrAncestorVisible, ctrl::visibleChanged)
|
||||
observeNow(selfOrAncestorVisible, ctrl::visibleChanged)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -4,6 +4,7 @@ import org.w3c.dom.*
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.observable.cell.Cell
|
||||
import world.phantasmal.observable.cell.observeNow
|
||||
import world.phantasmal.observable.cell.trueCell
|
||||
import world.phantasmal.webui.LoadingStatus
|
||||
import world.phantasmal.webui.controllers.Column
|
||||
@ -30,7 +31,7 @@ class Table<T>(
|
||||
div {
|
||||
className = "pw-table-notification"
|
||||
|
||||
observe(loadingStatus) { status ->
|
||||
observeNow(loadingStatus) { status ->
|
||||
when (status) {
|
||||
LoadingStatus.Uninitialized,
|
||||
LoadingStatus.InitialLoad,
|
||||
@ -167,8 +168,8 @@ class Table<T>(
|
||||
style.width = "${column.width}px"
|
||||
column.textAlign?.let { style.textAlign = it }
|
||||
|
||||
disposer.add(column.footer.observe(callNow = true) { textContent = it.value ?: "" })
|
||||
disposer.add(column.footerTooltip.observe(callNow = true) { title = it.value ?: "" })
|
||||
disposer.add(column.footer.observeNow { textContent = it ?: "" })
|
||||
disposer.add(column.footerTooltip.observeNow { title = it ?: "" })
|
||||
}
|
||||
|
||||
return Pair(cell, disposer)
|
||||
|
@ -37,13 +37,13 @@ class TextArea(
|
||||
id = labelId
|
||||
className = "pw-text-area-inner"
|
||||
|
||||
observe(this@TextArea.enabled) { disabled = !it }
|
||||
observeNow(this@TextArea.enabled) { disabled = !it }
|
||||
|
||||
if (onChange != null) {
|
||||
onchange = { onChange.invoke(value) }
|
||||
}
|
||||
|
||||
observe(this@TextArea.value) { value = it }
|
||||
observeNow(this@TextArea.value) { value = it }
|
||||
|
||||
this@TextArea.maxLength?.let { maxLength = it }
|
||||
fontFamily?.let { style.fontFamily = it }
|
||||
|
@ -12,7 +12,6 @@ import org.w3c.dom.pointerevents.PointerEvent
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.disposable.DisposableSupervisedScope
|
||||
import world.phantasmal.core.disposable.disposable
|
||||
import world.phantasmal.observable.Observable
|
||||
import world.phantasmal.observable.cell.*
|
||||
import world.phantasmal.webui.DisposableContainer
|
||||
import world.phantasmal.webui.dom.*
|
||||
@ -36,12 +35,12 @@ abstract class Widget(
|
||||
private val elementDelegate = lazy {
|
||||
val el = documentFragment().createElement()
|
||||
|
||||
observe(visible) { visible ->
|
||||
observeNow(visible) { visible ->
|
||||
el.hidden = !visible
|
||||
children.forEach { setAncestorVisible(it, visible && ancestorVisible.value) }
|
||||
}
|
||||
|
||||
observe(enabled) { enabled ->
|
||||
observeNow(enabled) { enabled ->
|
||||
if (enabled) {
|
||||
el.removeAttribute("disabled")
|
||||
el.classList.remove("pw-disabled")
|
||||
@ -51,7 +50,7 @@ abstract class Widget(
|
||||
}
|
||||
}
|
||||
|
||||
observe(tooltip) { tooltip ->
|
||||
observeNow(tooltip) { tooltip ->
|
||||
if (tooltip == null) {
|
||||
el.removeAttribute("title")
|
||||
} else {
|
||||
@ -111,16 +110,16 @@ abstract class Widget(
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
protected fun Node.text(observable: Observable<String>) {
|
||||
observe(observable) { textContent = it }
|
||||
protected fun Node.text(cell: Cell<String>) {
|
||||
observeNow(cell) { textContent = it }
|
||||
}
|
||||
|
||||
protected fun HTMLElement.hidden(observable: Observable<Boolean>) {
|
||||
observe(observable) { hidden = it }
|
||||
protected fun HTMLElement.hidden(cell: Cell<Boolean>) {
|
||||
observeNow(cell) { hidden = it }
|
||||
}
|
||||
|
||||
protected fun HTMLElement.toggleClass(className: String, observable: Observable<Boolean>) {
|
||||
observe(observable) {
|
||||
protected fun HTMLElement.toggleClass(className: String, cell: Cell<Boolean>) {
|
||||
observeNow(cell) {
|
||||
if (it) classList.add(className)
|
||||
else classList.remove(className)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user