v0.8.2 #80
@ -1,31 +0,0 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
|
||||
@DFExperimental
|
||||
public class MetaProperty<T : Any>(
|
||||
public val meta: ObservableMutableMeta,
|
||||
public val name: Name,
|
||||
public val converter: MetaConverter<T>,
|
||||
) : Property<T?> {
|
||||
|
||||
override var value: T?
|
||||
get() = converter.readNullable(meta[name])
|
||||
set(value) {
|
||||
meta[name] = converter.convertNullable(value) ?: Meta.EMPTY
|
||||
}
|
||||
|
||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
||||
meta.onChange(owner) { name ->
|
||||
if (name.startsWith(this@MetaProperty.name)) callback(converter.readNullable(this[name]))
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeChangeListener(owner: Any?) {
|
||||
meta.removeListener(owner)
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
|
||||
@DFExperimental
|
||||
public interface Property<T> {
|
||||
public var value: T
|
||||
|
||||
public fun onChange(owner: Any? = null, callback: (T) -> Unit)
|
||||
public fun removeChangeListener(owner: Any? = null)
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public fun <T> Property<T>.toFlow(): StateFlow<T> = MutableStateFlow(value).also { stateFlow ->
|
||||
onChange {
|
||||
stateFlow.value = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect all changes in the [source] property onto this property. Does not reflect changes back.
|
||||
*
|
||||
* @return a mirroring job
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun <T> Property<T>.mirror(source: Property<T>) {
|
||||
source.onChange(this) {
|
||||
this.value = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bi-directional connection between properties
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun <T> Property<T>.bind(other: Property<T>) {
|
||||
onChange(other) {
|
||||
other.value = it
|
||||
}
|
||||
other.onChange {
|
||||
this.value = it
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
|
||||
@DFExperimental
|
||||
public fun <T> ObservableMeta.asFlow(converter: MetaSpec<T>): Flow<T> = callbackFlow {
|
||||
onChange(this){
|
||||
trySend(converter.read(this))
|
||||
}
|
||||
|
||||
awaitClose{
|
||||
removeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public fun <T> MutableMeta.listenTo(
|
||||
scope: CoroutineScope,
|
||||
converter: MetaConverter<T>,
|
||||
flow: Flow<T>,
|
||||
): Job = flow.onEach {
|
||||
update(converter.convert(it))
|
||||
}.launchIn(scope)
|
||||
|
||||
@DFExperimental
|
||||
public fun <T> ObservableMutableMeta.bind(
|
||||
scope: CoroutineScope,
|
||||
converter: MetaConverter<T>,
|
||||
flow: MutableSharedFlow<T>,
|
||||
): Job = scope.launch{
|
||||
listenTo(this, converter,flow)
|
||||
onChange(flow){
|
||||
launch {
|
||||
flow.emit(converter.read(this@onChange))
|
||||
}
|
||||
}
|
||||
flow.onCompletion {
|
||||
removeListener(flow)
|
||||
}
|
||||
}.also {
|
||||
it.invokeOnCompletion {
|
||||
removeListener(flow)
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
|
||||
import space.kscience.dataforge.meta.Scheme
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
||||
@DFExperimental
|
||||
public fun <S : Scheme, T : Any> S.property(property: KMutableProperty1<S, T?>): Property<T?> =
|
||||
object : Property<T?> {
|
||||
override var value: T?
|
||||
get() = property.get(this@property)
|
||||
set(value) {
|
||||
property.set(this@property, value)
|
||||
}
|
||||
|
||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
||||
this@property.meta.onChange(this) { name ->
|
||||
if (name.startsWith(property.name.parseAsName(true))) {
|
||||
callback(property.get(this@property))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeChangeListener(owner: Any?) {
|
||||
this@property.meta.removeListener(this@property)
|
||||
}
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
import space.kscience.dataforge.meta.Scheme
|
||||
import space.kscience.dataforge.meta.SchemeSpec
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class TestScheme : Scheme() {
|
||||
var a by int()
|
||||
var b by int()
|
||||
companion object : SchemeSpec<TestScheme>(::TestScheme)
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
class MetaPropertiesTest {
|
||||
@Test
|
||||
fun testBinding() {
|
||||
val scheme = TestScheme.empty()
|
||||
val a = scheme.property(TestScheme::a)
|
||||
val b = scheme.property(TestScheme::b)
|
||||
a.bind(b)
|
||||
scheme.a = 2
|
||||
assertEquals(2, scheme.b)
|
||||
assertEquals(2, b.value)
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
|
||||
@DFExperimental
|
||||
public fun HTMLInputElement.bindValue(property: Property<String>) {
|
||||
if (this.onchange != null) error("Input element already bound")
|
||||
this.onchange = {
|
||||
property.value = this.value
|
||||
Unit
|
||||
}
|
||||
property.onChange(this) {
|
||||
if (value != it) {
|
||||
value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public fun HTMLInputElement.bindChecked(property: Property<Boolean>) {
|
||||
if (this.onchange != null) error("Input element already bound")
|
||||
this.onchange = {
|
||||
property.value = this.checked
|
||||
Unit
|
||||
}
|
||||
property.onChange(this) {
|
||||
if (checked != it) {
|
||||
checked = it
|
||||
}
|
||||
}
|
||||
}
|
@ -17,12 +17,15 @@ internal data class MetaListener(
|
||||
*/
|
||||
public interface ObservableMeta : Meta {
|
||||
/**
|
||||
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
||||
* Add change listener to this meta. The Owner is declared to be able to remove listeners later.
|
||||
* Listeners without an owner could be only removed all together.
|
||||
*
|
||||
* `this` object in the listener represents the current state of this meta. The name points to a changed node
|
||||
*/
|
||||
public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit)
|
||||
|
||||
/**
|
||||
* Remove all listeners belonging to given owner
|
||||
* Remove all listeners belonging to the given [owner]. Passing null removes all listeners.
|
||||
*/
|
||||
public fun removeListener(owner: Any?)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user