Fixed all tests
This commit is contained in:
parent
30ad680688
commit
cbf3f4941a
@ -3,14 +3,14 @@ import space.kscience.gradle.useSPCTeam
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("space.kscience.gradle.project")
|
id("space.kscience.gradle.project")
|
||||||
// id("org.jetbrains.kotlinx.kover") version "0.5.0"
|
id("org.jetbrains.kotlinx.kover") version "0.5.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
val dataforgeVersion by extra("0.8.0")
|
val dataforgeVersion by extra("0.8.0")
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "space.kscience"
|
group = "space.kscience"
|
||||||
version = "0.4.0-dev-2"
|
version = "0.4.0-dev-3"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.dataforge.meta.double
|
|
||||||
import space.kscience.dataforge.meta.int
|
|
||||||
import space.kscience.dataforge.meta.set
|
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.parseAsName
|
import space.kscience.dataforge.names.parseAsName
|
||||||
import space.kscience.dataforge.names.plus
|
import space.kscience.dataforge.names.plus
|
||||||
@ -365,7 +362,7 @@ private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid?
|
|||||||
}
|
}
|
||||||
return if (group.children.isEmpty()) {
|
return if (group.children.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else if (group.items.size == 1 && group.properties.own == null) {
|
} else if (group.items.size == 1 && group.properties.own.isEmpty()) {
|
||||||
group.items.values.first().apply { parent = null }
|
group.items.values.first().apply { parent = null }
|
||||||
} else {
|
} else {
|
||||||
group
|
group
|
||||||
|
@ -7,7 +7,7 @@ import kotlinx.html.h2
|
|||||||
import kotlinx.html.p
|
import kotlinx.html.p
|
||||||
import space.kscience.visionforge.VisionControlEvent
|
import space.kscience.visionforge.VisionControlEvent
|
||||||
import space.kscience.visionforge.html.*
|
import space.kscience.visionforge.html.*
|
||||||
import space.kscience.visionforge.onClick
|
import space.kscience.visionforge.onSubmit
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ fun main() = serve {
|
|||||||
|
|
||||||
vision {
|
vision {
|
||||||
button("Click me") {
|
button("Click me") {
|
||||||
onClick(context) {
|
onSubmit(context) {
|
||||||
pushEvent(this)
|
pushEvent(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package space.kscience.visionforge
|
package space.kscience.visionforge
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import space.kscience.dataforge.meta.MutableMeta
|
import space.kscience.dataforge.meta.MutableMeta
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||||
|
|
||||||
@ -14,16 +12,12 @@ public abstract class AbstractVision : Vision {
|
|||||||
override var parent: Vision? = null
|
override var parent: Vision? = null
|
||||||
|
|
||||||
@SerialName("properties")
|
@SerialName("properties")
|
||||||
protected var propertiesInternal: MutableMeta? = null
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@EncodeDefault(EncodeDefault.Mode.NEVER)
|
||||||
|
protected var propertiesInternal: MutableMeta = MutableMeta()
|
||||||
|
|
||||||
final override val properties: MutableVisionProperties by lazy {
|
final override val properties: MutableVisionProperties by lazy {
|
||||||
object : AbstractVisionProperties(this) {
|
AbstractVisionProperties(this, propertiesInternal)
|
||||||
override var properties: MutableMeta?
|
|
||||||
get() = propertiesInternal
|
|
||||||
set(value) {
|
|
||||||
propertiesInternal = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val descriptor: MetaDescriptor? get() = null
|
override val descriptor: MetaDescriptor? get() = null
|
||||||
|
@ -34,11 +34,11 @@ public interface ControlVision : Vision {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param payload The optional payload associated with the click event.
|
* An event for submitting changes
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("control.click")
|
@SerialName("control.submit")
|
||||||
public class VisionClickEvent(override val meta: Meta) : VisionControlEvent() {
|
public class VisionSubmitEvent(override val meta: Meta) : VisionControlEvent() {
|
||||||
public val payload: Meta get() = meta[::payload.name] ?: Meta.EMPTY
|
public val payload: Meta get() = meta[::payload.name] ?: Meta.EMPTY
|
||||||
|
|
||||||
public val name: Name? get() = meta["name"].string?.parseAsName()
|
public val name: Name? get() = meta["name"].string?.parseAsName()
|
||||||
@ -46,28 +46,28 @@ public class VisionClickEvent(override val meta: Meta) : VisionControlEvent() {
|
|||||||
override fun toString(): String = meta.toString()
|
override fun toString(): String = meta.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun VisionClickEvent(payload: Meta = Meta.EMPTY, name: Name? = null): VisionClickEvent = VisionClickEvent(
|
public fun VisionSubmitEvent(payload: Meta = Meta.EMPTY, name: Name? = null): VisionSubmitEvent = VisionSubmitEvent(
|
||||||
Meta {
|
Meta {
|
||||||
VisionClickEvent::payload.name put payload
|
VisionSubmitEvent::payload.name put payload
|
||||||
VisionClickEvent::name.name put name.toString()
|
VisionSubmitEvent::name.name put name.toString()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
public interface ClickControl : ControlVision {
|
public interface DataControl : ControlVision {
|
||||||
/**
|
/**
|
||||||
* Create and dispatch a click event
|
* Create and dispatch submit event
|
||||||
*/
|
*/
|
||||||
public suspend fun click(builder: MutableMeta.() -> Unit = {}) {
|
public suspend fun submit(builder: MutableMeta.() -> Unit = {}) {
|
||||||
dispatchControlEvent(VisionClickEvent(Meta(builder)))
|
dispatchControlEvent(VisionSubmitEvent(Meta(builder)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register listener
|
* Register listener
|
||||||
*/
|
*/
|
||||||
public fun ClickControl.onClick(scope: CoroutineScope, block: suspend VisionClickEvent.() -> Unit): Job =
|
public fun DataControl.onSubmit(scope: CoroutineScope, block: suspend VisionSubmitEvent.() -> Unit): Job =
|
||||||
controlEventFlow.filterIsInstance<VisionClickEvent>().onEach(block).launchIn(scope)
|
controlEventFlow.filterIsInstance<VisionSubmitEvent>().onEach(block).launchIn(scope)
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -67,7 +67,7 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
|
|||||||
* List of names of styles applied to this object. Order matters. Not inherited.
|
* List of names of styles applied to this object. Order matters. Not inherited.
|
||||||
*/
|
*/
|
||||||
public var Vision.styles: List<String>
|
public var Vision.styles: List<String>
|
||||||
get() = properties.own?.getValue(Vision.STYLE_KEY)?.stringList ?: emptyList()
|
get() = properties.own[Vision.STYLE_KEY]?.stringList ?: emptyList()
|
||||||
set(value) {
|
set(value) {
|
||||||
properties.setValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
|
properties.setValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ public val Vision.styleSheet: StyleSheet get() = StyleSheet(this)
|
|||||||
* The style with given name does not necessary exist at the moment.
|
* The style with given name does not necessary exist at the moment.
|
||||||
*/
|
*/
|
||||||
public fun Vision.useStyle(name: String, notify: Boolean = true) {
|
public fun Vision.useStyle(name: String, notify: Boolean = true) {
|
||||||
val newStyle = properties.own?.get(Vision.STYLE_KEY)?.value?.list?.plus(name.asValue()) ?: listOf(name.asValue())
|
val newStyle = properties.own[Vision.STYLE_KEY]?.value?.list?.plus(name.asValue()) ?: listOf(name.asValue())
|
||||||
properties.setValue(Vision.STYLE_KEY, newStyle.asValue(), notify)
|
properties.setValue(Vision.STYLE_KEY, newStyle.asValue(), notify)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ public fun Vision.useStyle(name: String, notify: Boolean = true) {
|
|||||||
* Resolve a style with given name for given [Vision]. The style is not necessarily applied to this [Vision].
|
* Resolve a style with given name for given [Vision]. The style is not necessarily applied to this [Vision].
|
||||||
*/
|
*/
|
||||||
public fun Vision.getStyle(name: String): Meta? =
|
public fun Vision.getStyle(name: String): Meta? =
|
||||||
properties.own?.get(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name)
|
properties.own[StyleSheet.STYLESHEET_KEY + name] ?: parent?.getStyle(name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a property from all styles
|
* Resolve a property from all styles
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
package space.kscience.visionforge
|
package space.kscience.visionforge
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import space.kscience.dataforge.context.logger
|
import space.kscience.dataforge.context.logger
|
||||||
import space.kscience.dataforge.context.warn
|
import space.kscience.dataforge.context.warn
|
||||||
import space.kscience.dataforge.meta.asValue
|
import space.kscience.dataforge.meta.asValue
|
||||||
@ -73,14 +69,4 @@ public var Vision.visible: Boolean?
|
|||||||
get() = properties.getValue(Vision.VISIBLE_KEY)?.boolean
|
get() = properties.getValue(Vision.VISIBLE_KEY)?.boolean
|
||||||
set(value) {
|
set(value) {
|
||||||
properties.setValue(Vision.VISIBLE_KEY, value?.asValue())
|
properties.setValue(Vision.VISIBLE_KEY, value?.asValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
|
|
||||||
*/
|
|
||||||
public fun Vision.onPropertyChange(
|
|
||||||
scope: CoroutineScope,
|
|
||||||
callback: suspend (Name) -> Unit,
|
|
||||||
): Job = properties.changes.onEach {
|
|
||||||
callback(it)
|
|
||||||
}.launchIn(scope)
|
|
@ -147,8 +147,8 @@ private fun CoroutineScope.collectChange(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
//Collect properties change
|
//Collect properties change
|
||||||
source.properties.changes.onEach { propertyName ->
|
source.properties.flowChanges().onEach { propertyName ->
|
||||||
val newItem = source.properties.own?.get(propertyName)
|
val newItem = source.properties.own[propertyName]
|
||||||
collector.propertyChanged(name, propertyName, newItem)
|
collector.propertyChanged(name, propertyName, newItem)
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont
|
|||||||
polymorphic(VisionEvent::class) {
|
polymorphic(VisionEvent::class) {
|
||||||
subclass(VisionChange.serializer())
|
subclass(VisionChange.serializer())
|
||||||
subclass(VisionMetaEvent.serializer())
|
subclass(VisionMetaEvent.serializer())
|
||||||
subclass(VisionClickEvent.serializer())
|
subclass(VisionSubmitEvent.serializer())
|
||||||
subclass(VisionValueChangeEvent.serializer())
|
subclass(VisionValueChangeEvent.serializer())
|
||||||
subclass(VisionInputEvent.serializer())
|
subclass(VisionInputEvent.serializer())
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package space.kscience.visionforge
|
package space.kscience.visionforge
|
||||||
|
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||||
@ -15,7 +14,7 @@ public interface VisionProperties : MetaProvider {
|
|||||||
/**
|
/**
|
||||||
* Raw Visions own properties without styles, defaults, etc.
|
* Raw Visions own properties without styles, defaults, etc.
|
||||||
*/
|
*/
|
||||||
public val own: Meta?
|
public val own: Meta
|
||||||
|
|
||||||
public val descriptor: MetaDescriptor?
|
public val descriptor: MetaDescriptor?
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ public interface VisionProperties : MetaProvider {
|
|||||||
override fun get(name: Name): Meta? = get(name, null, null)
|
override fun get(name: Name): Meta? = get(name, null, null)
|
||||||
|
|
||||||
|
|
||||||
public val changes: Flow<Name>
|
public fun flowChanges(): Flow<Name>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify all listeners that a property has been changed and should be invalidated.
|
* Notify all listeners that a property has been changed and should be invalidated.
|
||||||
@ -112,7 +111,7 @@ private class VisionPropertiesItem(
|
|||||||
|
|
||||||
override val items: Map<NameToken, MutableMeta>
|
override val items: Map<NameToken, MutableMeta>
|
||||||
get() {
|
get() {
|
||||||
val metaKeys = properties.own?.get(nodeName)?.items?.keys ?: emptySet()
|
val metaKeys = properties.own[nodeName]?.items?.keys ?: emptySet()
|
||||||
val descriptorKeys = descriptor?.nodes?.map { NameToken(it.key) } ?: emptySet()
|
val descriptorKeys = descriptor?.nodes?.map { NameToken(it.key) } ?: emptySet()
|
||||||
val defaultKeys = default?.get(nodeName)?.items?.keys ?: emptySet()
|
val defaultKeys = default?.get(nodeName)?.items?.keys ?: emptySet()
|
||||||
val inheritFlag = descriptor?.inherited ?: inherit
|
val inheritFlag = descriptor?.inherited ?: inherit
|
||||||
@ -158,25 +157,12 @@ private class VisionPropertiesItem(
|
|||||||
/**
|
/**
|
||||||
* A base implementation of [MutableVisionProperties]
|
* A base implementation of [MutableVisionProperties]
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractVisionProperties(
|
public open class AbstractVisionProperties(
|
||||||
public val vision: Vision,
|
public val vision: Vision,
|
||||||
|
final override val own: MutableMeta,
|
||||||
) : MutableVisionProperties {
|
) : MutableVisionProperties {
|
||||||
override val descriptor: MetaDescriptor? get() = vision.descriptor
|
override val descriptor: MetaDescriptor? get() = vision.descriptor
|
||||||
|
|
||||||
protected abstract var properties: MutableMeta?
|
|
||||||
|
|
||||||
override val own: Meta? get() = properties
|
|
||||||
|
|
||||||
@JvmSynchronized
|
|
||||||
protected fun getOrCreateProperties(): MutableMeta {
|
|
||||||
if (properties == null) {
|
|
||||||
//TODO check performance issues
|
|
||||||
val newProperties = MutableMeta()
|
|
||||||
properties = newProperties
|
|
||||||
}
|
|
||||||
return properties!!
|
|
||||||
}
|
|
||||||
|
|
||||||
private val descriptorCache = HashMap<Name, MetaDescriptor?>()
|
private val descriptorCache = HashMap<Name, MetaDescriptor?>()
|
||||||
|
|
||||||
override fun getValue(
|
override fun getValue(
|
||||||
@ -184,7 +170,7 @@ public abstract class AbstractVisionProperties(
|
|||||||
inherit: Boolean?,
|
inherit: Boolean?,
|
||||||
includeStyles: Boolean?,
|
includeStyles: Boolean?,
|
||||||
): Value? {
|
): Value? {
|
||||||
own?.get(name)?.value?.let { return it }
|
own[name]?.value?.let { return it }
|
||||||
|
|
||||||
val descriptor = descriptor?.let { descriptor -> descriptorCache.getOrPut(name) { descriptor[name] } }
|
val descriptor = descriptor?.let { descriptor -> descriptorCache.getOrPut(name) { descriptor[name] } }
|
||||||
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
|
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
|
||||||
@ -202,14 +188,26 @@ public abstract class AbstractVisionProperties(
|
|||||||
|
|
||||||
override fun set(name: Name, node: Meta?, notify: Boolean) {
|
override fun set(name: Name, node: Meta?, notify: Boolean) {
|
||||||
//ignore if the value is the same as existing
|
//ignore if the value is the same as existing
|
||||||
if (own?.get(name) == node) return
|
if (own[name] == node) return
|
||||||
|
|
||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
properties = node?.asMutableMeta()
|
if (node == null) {
|
||||||
|
own.items.keys.forEach {
|
||||||
|
remove(it.asName())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(own.items.keys - node.items.keys).forEach {
|
||||||
|
remove(it.asName())
|
||||||
|
}
|
||||||
|
node.items.forEach { (token, item) ->
|
||||||
|
set(token, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (node == null) {
|
} else if (node == null) {
|
||||||
properties?.set(name, node)
|
own[name] = node
|
||||||
} else {
|
} else {
|
||||||
getOrCreateProperties()[name] = node
|
own[name] = node
|
||||||
}
|
}
|
||||||
if (notify) {
|
if (notify) {
|
||||||
invalidate(name)
|
invalidate(name)
|
||||||
@ -218,12 +216,12 @@ public abstract class AbstractVisionProperties(
|
|||||||
|
|
||||||
override fun setValue(name: Name, value: Value?, notify: Boolean) {
|
override fun setValue(name: Name, value: Value?, notify: Boolean) {
|
||||||
//ignore if the value is the same as existing
|
//ignore if the value is the same as existing
|
||||||
if (own?.getValue(name) == value) return
|
if (own.getValue(name) == value) return
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
properties?.get(name)?.value = null
|
own[name]?.value = null
|
||||||
} else {
|
} else {
|
||||||
getOrCreateProperties().setValue(name, value)
|
own.setValue(name, value)
|
||||||
}
|
}
|
||||||
if (notify) {
|
if (notify) {
|
||||||
invalidate(name)
|
invalidate(name)
|
||||||
@ -231,12 +229,20 @@ public abstract class AbstractVisionProperties(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
protected val changesInternal: MutableSharedFlow<Name> = MutableSharedFlow(500, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
protected val changesInternal: MutableSharedFlow<Name> = MutableSharedFlow()
|
||||||
override val changes: SharedFlow<Name> get() = changesInternal
|
|
||||||
|
override fun flowChanges(): Flow<Name> = changesInternal
|
||||||
|
|
||||||
override fun invalidate(propertyName: Name) {
|
override fun invalidate(propertyName: Name) {
|
||||||
//send update signal
|
//send update signal
|
||||||
changesInternal.tryEmit(propertyName)
|
val manager = vision.manager
|
||||||
|
if (manager != null) {
|
||||||
|
manager.context.launch {
|
||||||
|
changesInternal.emit(propertyName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changesInternal.tryEmit(propertyName)
|
||||||
|
}
|
||||||
|
|
||||||
//notify children if there are any
|
//notify children if there are any
|
||||||
if (vision is VisionGroup) {
|
if (vision is VisionGroup) {
|
||||||
|
@ -18,7 +18,7 @@ public fun Vision.flowProperty(
|
|||||||
): Flow<Meta> = flow {
|
): Flow<Meta> = flow {
|
||||||
//Pass initial value.
|
//Pass initial value.
|
||||||
emit(properties.get(propertyName, inherit, includeStyles))
|
emit(properties.get(propertyName, inherit, includeStyles))
|
||||||
properties.changes.collect { name ->
|
properties.flowChanges().collect { name ->
|
||||||
if (name.startsWith(propertyName)) {
|
if (name.startsWith(propertyName)) {
|
||||||
emit(properties.get(propertyName, inherit, includeStyles))
|
emit(properties.get(propertyName, inherit, includeStyles))
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ public fun Vision.flowPropertyValue(
|
|||||||
): Flow<Value?> = flow {
|
): Flow<Value?> = flow {
|
||||||
//Pass initial value.
|
//Pass initial value.
|
||||||
emit(properties.getValue(propertyName, inherit, includeStyles))
|
emit(properties.getValue(propertyName, inherit, includeStyles))
|
||||||
properties.changes.collect { name ->
|
properties.flowChanges().collect { name ->
|
||||||
if (name.startsWith(propertyName)) {
|
if (name.startsWith(propertyName)) {
|
||||||
emit(properties.getValue(propertyName, inherit, includeStyles))
|
emit(properties.getValue(propertyName, inherit, includeStyles))
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import kotlinx.serialization.Serializable
|
|||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.node
|
import space.kscience.dataforge.meta.node
|
||||||
import space.kscience.dataforge.meta.string
|
import space.kscience.dataforge.meta.string
|
||||||
import space.kscience.visionforge.ClickControl
|
import space.kscience.visionforge.DataControl
|
||||||
import space.kscience.visionforge.onClick
|
import space.kscience.visionforge.onSubmit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param formId an id of the element in rendered DOM, this form is bound to
|
* @param formId an id of the element in rendered DOM, this form is bound to
|
||||||
@ -18,7 +18,7 @@ import space.kscience.visionforge.onClick
|
|||||||
@SerialName("html.form")
|
@SerialName("html.form")
|
||||||
public class VisionOfHtmlForm(
|
public class VisionOfHtmlForm(
|
||||||
public val formId: String,
|
public val formId: String,
|
||||||
) : VisionOfHtmlControl(), ClickControl {
|
) : VisionOfHtmlControl(), DataControl {
|
||||||
public var values: Meta? by properties.node()
|
public var values: Meta? by properties.node()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,12 +40,12 @@ public inline fun <T, C : TagConsumer<T>> C.visionOfForm(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun VisionOfHtmlForm.onSubmit(scope: CoroutineScope, block: (Meta?) -> Unit): Job = onClick(scope) { block(payload) }
|
public fun VisionOfHtmlForm.onFormSubmit(scope: CoroutineScope, block: (Meta?) -> Unit): Job = onSubmit(scope) { block(payload) }
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("html.button")
|
@SerialName("html.button")
|
||||||
public class VisionOfHtmlButton : VisionOfHtmlControl(), ClickControl {
|
public class VisionOfHtmlButton : VisionOfHtmlControl(), DataControl {
|
||||||
public var label: String? by properties.string()
|
public var label: String? by properties.string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package space.kscience.visionforge
|
|||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
@ -19,12 +20,12 @@ public fun Vision.useProperty(
|
|||||||
propertyName: Name,
|
propertyName: Name,
|
||||||
inherit: Boolean? = null,
|
inherit: Boolean? = null,
|
||||||
includeStyles: Boolean? = null,
|
includeStyles: Boolean? = null,
|
||||||
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties"),
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
|
||||||
callback: (Meta) -> Unit,
|
callback: (Meta) -> Unit,
|
||||||
): Job {
|
): Job {
|
||||||
//Pass initial value.
|
//Pass initial value.
|
||||||
callback(properties.get(propertyName, inherit, includeStyles))
|
callback(properties.get(propertyName, inherit, includeStyles))
|
||||||
return properties.changes.onEach { name ->
|
return properties.flowChanges().onEach { name ->
|
||||||
if (name.startsWith(propertyName)) {
|
if (name.startsWith(propertyName)) {
|
||||||
callback(properties.get(propertyName, inherit, includeStyles))
|
callback(properties.get(propertyName, inherit, includeStyles))
|
||||||
}
|
}
|
||||||
@ -35,33 +36,41 @@ public fun Vision.useProperty(
|
|||||||
propertyName: String,
|
propertyName: String,
|
||||||
inherit: Boolean? = null,
|
inherit: Boolean? = null,
|
||||||
includeStyles: Boolean? = null,
|
includeStyles: Boolean? = null,
|
||||||
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties"),
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
|
||||||
callback: (Meta) -> Unit,
|
callback: (Meta) -> Unit,
|
||||||
): Job = useProperty(propertyName.parseAsName(), inherit, includeStyles, scope, callback)
|
): Job = useProperty(propertyName.parseAsName(), inherit, includeStyles, scope, callback)
|
||||||
|
|
||||||
|
public fun <V : Vision, T> V.useProperty(
|
||||||
|
property: KProperty1<V, T>,
|
||||||
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
|
||||||
|
callback: V.(T) -> Unit,
|
||||||
|
): Job {
|
||||||
|
//Pass initial value.
|
||||||
|
callback(property.get(this))
|
||||||
|
return properties.flowChanges().onEach { name ->
|
||||||
|
if (name.startsWith(property.name.asName())) {
|
||||||
|
callback(property.get(this@useProperty))
|
||||||
|
}
|
||||||
|
}.launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
|
||||||
|
*/
|
||||||
|
public fun Vision.onPropertyChange(
|
||||||
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
|
||||||
|
callback: suspend (Name) -> Unit,
|
||||||
|
): Job = properties.flowChanges().onEach {
|
||||||
|
callback(it)
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe changes to the specific property without passing the initial value.
|
* Observe changes to the specific property without passing the initial value.
|
||||||
*/
|
*/
|
||||||
public fun <V : Vision, T> V.onPropertyChange(
|
public fun <V : Vision, T> V.onPropertyChange(
|
||||||
property: KProperty1<V, T>,
|
property: KProperty1<V, T>,
|
||||||
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties"),
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
|
||||||
callback: suspend V.(T) -> Unit,
|
callback: suspend V.(T) -> Unit,
|
||||||
): Job = properties.changes.onEach { name ->
|
): Job = properties.flowChanges().filter { it.startsWith(property.name.asName()) }.onEach {
|
||||||
if (name.startsWith(property.name.asName())) {
|
|
||||||
callback(property.get(this))
|
|
||||||
}
|
|
||||||
}.launchIn(scope)
|
|
||||||
|
|
||||||
public fun <V : Vision, T> V.useProperty(
|
|
||||||
property: KProperty1<V, T>,
|
|
||||||
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties"),
|
|
||||||
callback: V.(T) -> Unit,
|
|
||||||
): Job {
|
|
||||||
//Pass initial value.
|
|
||||||
callback(property.get(this))
|
callback(property.get(this))
|
||||||
return properties.changes.onEach { name ->
|
}.launchIn(scope)
|
||||||
if (name.startsWith(property.name.asName())) {
|
|
||||||
callback(property.get(this@useProperty))
|
|
||||||
}
|
|
||||||
}.launchIn(scope)
|
|
||||||
}
|
|
@ -55,7 +55,7 @@ class HtmlTagTest {
|
|||||||
div {
|
div {
|
||||||
h2 { +"Properties" }
|
h2 { +"Properties" }
|
||||||
ul {
|
ul {
|
||||||
vision.properties.own?.items?.forEach {
|
vision.properties.own.items.forEach {
|
||||||
li {
|
li {
|
||||||
a { +it.key.toString() }
|
a { +it.key.toString() }
|
||||||
p { +it.value.toString() }
|
p { +it.value.toString() }
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package space.kscience.visionforge.meta
|
package space.kscience.visionforge.meta
|
||||||
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.take
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
import space.kscience.dataforge.context.request
|
import space.kscience.dataforge.context.request
|
||||||
@ -92,7 +91,7 @@ internal class VisionPropertyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testChildrenPropertyFlow() = runTest(timeout = 200.milliseconds) {
|
fun testChildrenPropertyFlow() = runTest(timeout = 500.milliseconds) {
|
||||||
val group = Global.request(VisionManager).group {
|
val group = Global.request(VisionManager).group {
|
||||||
|
|
||||||
properties {
|
properties {
|
||||||
@ -109,17 +108,32 @@ internal class VisionPropertyTest {
|
|||||||
|
|
||||||
val child = group.children["child"]!!
|
val child = group.children["child"]!!
|
||||||
|
|
||||||
launch {
|
val semaphore = Semaphore(1, 1)
|
||||||
val list = child.flowPropertyValue("test", inherit = true).take(3).map { it?.int }.toList()
|
|
||||||
assertEquals(22, list.first())
|
val changesFlow = child.flowPropertyValue("test", inherit = true).map {
|
||||||
//assertEquals(11, list[1]) //a race
|
semaphore.release()
|
||||||
assertEquals(33, list.last())
|
it!!.int
|
||||||
}
|
}
|
||||||
|
|
||||||
//wait for subscription to be created
|
val collectedValues = ArrayList<Int>(5)
|
||||||
delay(5)
|
|
||||||
|
|
||||||
|
val collectorJob = changesFlow.onEach {
|
||||||
|
collectedValues.add(it)
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
|
assertEquals(22, child.properties["test", true].int)
|
||||||
|
|
||||||
|
semaphore.acquire()
|
||||||
child.properties.remove("test")
|
child.properties.remove("test")
|
||||||
|
|
||||||
|
assertEquals(11, child.properties["test", true].int)
|
||||||
|
|
||||||
|
semaphore.acquire()
|
||||||
group.properties["test"] = 33
|
group.properties["test"] = 33
|
||||||
|
assertEquals(33, child.properties["test", true].int)
|
||||||
|
|
||||||
|
semaphore.acquire()
|
||||||
|
collectorJob.cancel()
|
||||||
|
assertEquals(listOf(22, 11, 33), collectedValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,17 +3,11 @@ package space.kscience.visionforge
|
|||||||
import kotlinx.dom.clear
|
import kotlinx.dom.clear
|
||||||
import kotlinx.html.TagConsumer
|
import kotlinx.html.TagConsumer
|
||||||
import kotlinx.html.dom.append
|
import kotlinx.html.dom.append
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.InternalSerializationApi
|
|
||||||
import kotlinx.serialization.serializerOrNull
|
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.misc.DfType
|
import space.kscience.dataforge.misc.DfType
|
||||||
import space.kscience.dataforge.misc.Named
|
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
import space.kscience.dataforge.names.parseAsName
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.cast
|
import kotlin.reflect.cast
|
||||||
|
|
||||||
@ -21,7 +15,7 @@ import kotlin.reflect.cast
|
|||||||
* A browser renderer for a [Vision].
|
* A browser renderer for a [Vision].
|
||||||
*/
|
*/
|
||||||
@DfType(ElementVisionRenderer.TYPE)
|
@DfType(ElementVisionRenderer.TYPE)
|
||||||
public interface ElementVisionRenderer : Named {
|
public interface ElementVisionRenderer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer
|
* Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer
|
||||||
@ -58,11 +52,6 @@ public class SingleTypeVisionRenderer<T : Vision>(
|
|||||||
private val renderFunction: TagConsumer<HTMLElement>.(name: Name, client: VisionClient, vision: T, meta: Meta) -> Unit,
|
private val renderFunction: TagConsumer<HTMLElement>.(name: Name, client: VisionClient, vision: T, meta: Meta) -> Unit,
|
||||||
) : ElementVisionRenderer {
|
) : ElementVisionRenderer {
|
||||||
|
|
||||||
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
|
|
||||||
override val name: Name
|
|
||||||
get() = kClass.serializerOrNull()?.descriptor?.serialName?.parseAsName()
|
|
||||||
?: kClass.toString().asName()
|
|
||||||
|
|
||||||
override fun rateVision(vision: Vision): Int =
|
override fun rateVision(vision: Vision): Int =
|
||||||
if (vision::class == kClass) acceptRating else ElementVisionRenderer.ZERO_RATING
|
if (vision::class == kClass) acceptRating else ElementVisionRenderer.ZERO_RATING
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import space.kscience.dataforge.meta.MetaSerializer
|
|||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
import space.kscience.dataforge.meta.int
|
import space.kscience.dataforge.meta.int
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.asName
|
||||||
import space.kscience.dataforge.names.parseAsName
|
import space.kscience.dataforge.names.parseAsName
|
||||||
import space.kscience.visionforge.html.VisionTagConsumer
|
import space.kscience.visionforge.html.VisionTagConsumer
|
||||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
||||||
@ -262,7 +263,7 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
rangeVisionRenderer,
|
rangeVisionRenderer,
|
||||||
formVisionRenderer,
|
formVisionRenderer,
|
||||||
buttonVisionRenderer
|
buttonVisionRenderer
|
||||||
).associateByName()
|
).associateBy { it.toString().asName() }
|
||||||
} else super<AbstractPlugin>.content(target)
|
} else super<AbstractPlugin>.content(target)
|
||||||
|
|
||||||
public companion object : PluginFactory<JsVisionClient> {
|
public companion object : PluginFactory<JsVisionClient> {
|
||||||
@ -321,7 +322,7 @@ public class VisionClientApplication(public val context: Context) : Application
|
|||||||
client.renderers.joinToString(
|
client.renderers.joinToString(
|
||||||
prefix = "\n\t",
|
prefix = "\n\t",
|
||||||
separator = "\n\t"
|
separator = "\n\t"
|
||||||
) { it.name.toString() }
|
) { it.toString() }
|
||||||
}"
|
}"
|
||||||
}
|
}
|
||||||
val element = document.body ?: error("Document does not have a body")
|
val element = document.body ?: error("Document does not have a body")
|
||||||
|
@ -70,7 +70,7 @@ internal val formVisionRenderer: ElementVisionRenderer =
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
val formData = FormData(form).toMeta()
|
val formData = FormData(form).toMeta()
|
||||||
client.context.launch {
|
client.context.launch {
|
||||||
client.sendEvent(name, VisionClickEvent(name = name, payload = formData))
|
client.sendEvent(name, VisionSubmitEvent(name = name, payload = formData))
|
||||||
}
|
}
|
||||||
console.info("Sent form data: ${formData.toMap()}")
|
console.info("Sent form data: ${formData.toMap()}")
|
||||||
false
|
false
|
||||||
@ -83,7 +83,7 @@ internal val buttonVisionRenderer: ElementVisionRenderer =
|
|||||||
button.subscribeToVision(vision)
|
button.subscribeToVision(vision)
|
||||||
button.onclick = {
|
button.onclick = {
|
||||||
client.context.launch {
|
client.context.launch {
|
||||||
client.sendEvent(name, VisionClickEvent(name = name))
|
client.sendEvent(name, VisionSubmitEvent(name = name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vision.useProperty(VisionOfHtmlButton::label) {
|
vision.useProperty(VisionOfHtmlButton::label) {
|
||||||
|
@ -8,12 +8,16 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.MutableMeta
|
||||||
|
import space.kscience.dataforge.meta.MutableMetaSerializer
|
||||||
|
import space.kscience.dataforge.meta.ObservableMeta
|
||||||
|
import space.kscience.dataforge.meta.asObservable
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.plotly.Plot
|
import space.kscience.plotly.Plot
|
||||||
import space.kscience.plotly.Plotly
|
import space.kscience.plotly.Plotly
|
||||||
import space.kscience.plotly.PlotlyConfig
|
import space.kscience.plotly.PlotlyConfig
|
||||||
|
import space.kscience.visionforge.AbstractVisionProperties
|
||||||
import space.kscience.visionforge.MutableVisionProperties
|
import space.kscience.visionforge.MutableVisionProperties
|
||||||
import space.kscience.visionforge.Vision
|
import space.kscience.visionforge.Vision
|
||||||
import space.kscience.visionforge.VisionBuilder
|
import space.kscience.visionforge.VisionBuilder
|
||||||
@ -32,32 +36,9 @@ public class VisionOfPlotly private constructor(
|
|||||||
override var parent: Vision? = null
|
override var parent: Vision? = null
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
override val properties: MutableVisionProperties = object : MutableVisionProperties {
|
override val properties: MutableVisionProperties = object : AbstractVisionProperties(this, meta) {
|
||||||
override fun set(name: Name, node: Meta?, notify: Boolean) {
|
|
||||||
meta[name] = node
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(name: Name, value: Value?, notify: Boolean) {
|
override fun flowChanges(): Flow<Name> = if (meta is ObservableMeta) {
|
||||||
meta.setValue(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val own: Meta get() = meta
|
|
||||||
|
|
||||||
override val descriptor: MetaDescriptor? get() = this@VisionOfPlotly.descriptor
|
|
||||||
|
|
||||||
override fun get(
|
|
||||||
name: Name,
|
|
||||||
inherit: Boolean?,
|
|
||||||
includeStyles: Boolean?,
|
|
||||||
): MutableMeta = meta[name] ?: MutableMeta()
|
|
||||||
|
|
||||||
override fun getValue(
|
|
||||||
name: Name,
|
|
||||||
inherit: Boolean?,
|
|
||||||
includeStyles: Boolean?,
|
|
||||||
): Value? = meta.getValue(name)
|
|
||||||
|
|
||||||
override val changes: Flow<Name> = if (meta is ObservableMeta) {
|
|
||||||
callbackFlow {
|
callbackFlow {
|
||||||
meta.onChange(this) {
|
meta.onChange(this) {
|
||||||
launch {
|
launch {
|
||||||
@ -78,7 +59,7 @@ public class VisionOfPlotly private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override val descriptor: MetaDescriptor? = null // TODO add descriptor for Plot
|
override val descriptor: MetaDescriptor = Plot.descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this)
|
public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this)
|
||||||
|
@ -2,6 +2,7 @@ package space.kscience.visionforge.solid
|
|||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import space.kscience.dataforge.meta.isEmpty
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.visionforge.MutableVisionContainer
|
import space.kscience.visionforge.MutableVisionContainer
|
||||||
import space.kscience.visionforge.VisionBuilder
|
import space.kscience.visionforge.VisionBuilder
|
||||||
@ -55,7 +56,7 @@ public fun SolidGroup.smartComposite(
|
|||||||
@VisionBuilder builder: SolidGroup.() -> Unit,
|
@VisionBuilder builder: SolidGroup.() -> Unit,
|
||||||
): Solid = if (type == CompositeType.GROUP) {
|
): Solid = if (type == CompositeType.GROUP) {
|
||||||
val group = SolidGroup().apply(builder)
|
val group = SolidGroup().apply(builder)
|
||||||
if (name == null && group.properties.own == null) {
|
if (name == null && group.properties.own.isEmpty()) {
|
||||||
//append directly to group if no properties are defined
|
//append directly to group if no properties are defined
|
||||||
group.items.forEach { (_, value) ->
|
group.items.forEach { (_, value) ->
|
||||||
value.parent = null
|
value.parent = null
|
||||||
|
@ -177,7 +177,7 @@ internal fun float32Vector(
|
|||||||
): ReadWriteProperty<Solid, Float32Vector3D?> =
|
): ReadWriteProperty<Solid, Float32Vector3D?> =
|
||||||
object : ReadWriteProperty<Solid, Float32Vector3D?> {
|
object : ReadWriteProperty<Solid, Float32Vector3D?> {
|
||||||
override fun getValue(thisRef: Solid, property: KProperty<*>): Float32Vector3D? {
|
override fun getValue(thisRef: Solid, property: KProperty<*>): Float32Vector3D? {
|
||||||
val item = thisRef.properties.own?.get(name) ?: return null
|
val item = thisRef.properties.own[name] ?: return null
|
||||||
//using dynamic property accessor because values could change
|
//using dynamic property accessor because values could change
|
||||||
return object : Float32Vector3D {
|
return object : Float32Vector3D {
|
||||||
override val x: Float get() = item[X_KEY]?.float ?: defaultX
|
override val x: Float get() = item[X_KEY]?.float ?: defaultX
|
||||||
|
@ -6,9 +6,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||||
import space.kscience.dataforge.meta.descriptors.get
|
import space.kscience.dataforge.meta.descriptors.get
|
||||||
@ -51,21 +49,19 @@ public class SolidReference(
|
|||||||
}
|
}
|
||||||
override val descriptor: MetaDescriptor get() = prototype.descriptor
|
override val descriptor: MetaDescriptor get() = prototype.descriptor
|
||||||
|
|
||||||
|
|
||||||
@SerialName("properties")
|
@SerialName("properties")
|
||||||
private var propertiesInternal: MutableMeta? = null
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@EncodeDefault(EncodeDefault.Mode.NEVER)
|
||||||
|
private val propertiesInternal: MutableMeta = MutableMeta()
|
||||||
|
|
||||||
override val properties: MutableVisionProperties by lazy {
|
override val properties: MutableVisionProperties by lazy {
|
||||||
object : AbstractVisionProperties(this) {
|
object : AbstractVisionProperties(this, propertiesInternal) {
|
||||||
override var properties: MutableMeta?
|
|
||||||
get() = propertiesInternal
|
|
||||||
set(value) {
|
|
||||||
propertiesInternal = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getValue(name: Name, inherit: Boolean?, includeStyles: Boolean?): Value? {
|
override fun getValue(name: Name, inherit: Boolean?, includeStyles: Boolean?): Value? {
|
||||||
if (name == Vision.STYLE_KEY) {
|
if (name == Vision.STYLE_KEY) {
|
||||||
return buildList {
|
return buildList {
|
||||||
properties?.getValue(Vision.STYLE_KEY)?.list?.forEach {
|
own.getValue(Vision.STYLE_KEY)?.list?.forEach {
|
||||||
add(it)
|
add(it)
|
||||||
}
|
}
|
||||||
prototype.styles.forEach {
|
prototype.styles.forEach {
|
||||||
@ -74,18 +70,18 @@ public class SolidReference(
|
|||||||
}.distinct().asValue()
|
}.distinct().asValue()
|
||||||
}
|
}
|
||||||
//1. resolve own properties
|
//1. resolve own properties
|
||||||
properties?.getValue(name)?.let { return it }
|
own.getValue(name)?.let { return it }
|
||||||
|
|
||||||
val descriptor = descriptor?.get(name)
|
val descriptor = descriptor?.get(name)
|
||||||
val inheritFlag = inherit ?: descriptor?.inherited ?: false
|
val inheritFlag = inherit ?: descriptor?.inherited ?: false
|
||||||
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
|
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
|
||||||
|
|
||||||
//2. Resolve prototype onw properties
|
//2. Resolve prototype onw properties
|
||||||
prototype.properties.own?.getValue(name)?.let { return it }
|
prototype.properties.own.getValue(name)?.let { return it }
|
||||||
|
|
||||||
if (stylesFlag) {
|
if (stylesFlag) {
|
||||||
//3. own styles
|
//3. own styles
|
||||||
own?.getValue(Vision.STYLE_KEY)?.list?.forEach { styleName ->
|
own.getValue(Vision.STYLE_KEY)?.list?.forEach { styleName ->
|
||||||
getStyle(styleName.string)?.getValue(name)?.let { return it }
|
getStyle(styleName.string)?.getValue(name)?.let { return it }
|
||||||
}
|
}
|
||||||
//4. prototype styles
|
//4. prototype styles
|
||||||
@ -178,7 +174,8 @@ internal class SolidReferenceChild(
|
|||||||
own.setValue(name, value)
|
own.setValue(name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val changes: Flow<Name> get() = owner.properties.changes.filter { it.startsWith(childToken(childName)) }
|
override fun flowChanges(): Flow<Name> =
|
||||||
|
owner.properties.flowChanges().filter { it.startsWith(childToken(childName)) }
|
||||||
|
|
||||||
override fun invalidate(propertyName: Name) {
|
override fun invalidate(propertyName: Name) {
|
||||||
owner.properties.invalidate(childPropertyName(childName, propertyName))
|
owner.properties.invalidate(childPropertyName(childName, propertyName))
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
package space.kscience.visionforge.solid
|
package space.kscience.visionforge.solid
|
||||||
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import space.kscience.dataforge.meta.getValue
|
import space.kscience.dataforge.meta.getValue
|
||||||
import space.kscience.dataforge.meta.int
|
import space.kscience.dataforge.meta.int
|
||||||
import space.kscience.dataforge.meta.set
|
import space.kscience.dataforge.meta.set
|
||||||
import space.kscience.dataforge.meta.string
|
import space.kscience.dataforge.meta.string
|
||||||
import space.kscience.dataforge.names.asName
|
import space.kscience.dataforge.names.asName
|
||||||
import space.kscience.visionforge.*
|
import space.kscience.visionforge.getValue
|
||||||
|
import space.kscience.visionforge.styleSheet
|
||||||
|
import space.kscience.visionforge.styles
|
||||||
|
import space.kscience.visionforge.useStyle
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
@ -29,25 +28,14 @@ class SolidPropertyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testColorUpdate() = runTest(timeout = 200.milliseconds) {
|
fun testColorUpdate() {
|
||||||
val box = Box(10.0f, 10.0f, 10.0f)
|
val box = Box(10.0f, 10.0f, 10.0f)
|
||||||
|
|
||||||
val c = CompletableDeferred<String?>()
|
|
||||||
|
|
||||||
|
|
||||||
val subscription = box.onPropertyChange(this) { key ->
|
|
||||||
if (key == SolidMaterial.MATERIAL_COLOR_KEY) {
|
|
||||||
c.complete(box.color.string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delay(5)
|
|
||||||
|
|
||||||
box.material {
|
box.material {
|
||||||
color("pink")
|
color("pink")
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals("pink", c.await())
|
assertEquals("pink", box.properties[SolidMaterial.MATERIAL_COLOR_KEY].string)
|
||||||
subscription.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -133,7 +133,7 @@ internal var Material.cached: Boolean
|
|||||||
|
|
||||||
public fun Mesh.setMaterial(vision: Vision) {
|
public fun Mesh.setMaterial(vision: Vision) {
|
||||||
if (
|
if (
|
||||||
vision.properties.own?.get(SolidMaterial.MATERIAL_KEY) == null
|
vision.properties.own[SolidMaterial.MATERIAL_KEY] == null
|
||||||
&& vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()
|
&& vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()
|
||||||
) {
|
) {
|
||||||
//if this is a reference, use material of the prototype
|
//if this is a reference, use material of the prototype
|
||||||
|
@ -14,7 +14,6 @@ import space.kscience.visionforge.compose.ComposeVisionRenderer
|
|||||||
import space.kscience.visionforge.solid.*
|
import space.kscience.visionforge.solid.*
|
||||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||||
import space.kscience.visionforge.solid.three.compose.ThreeView
|
import space.kscience.visionforge.solid.three.compose.ThreeView
|
||||||
import space.kscience.visionforge.solid.three.set
|
|
||||||
import three.core.Object3D
|
import three.core.Object3D
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -85,7 +84,7 @@ public class ThreePlugin : AbstractPlugin(), ComposeVisionRenderer {
|
|||||||
updatePosition(vision)
|
updatePosition(vision)
|
||||||
//obj.onChildrenChange()
|
//obj.onChildrenChange()
|
||||||
if (observe) {
|
if (observe) {
|
||||||
vision.properties.changes.onEach { name ->
|
vision.properties.flowChanges().onEach { name ->
|
||||||
if (
|
if (
|
||||||
name.startsWith(Solid.POSITION_KEY) ||
|
name.startsWith(Solid.POSITION_KEY) ||
|
||||||
name.startsWith(Solid.ROTATION_KEY) ||
|
name.startsWith(Solid.ROTATION_KEY) ||
|
||||||
|
@ -138,7 +138,7 @@ public fun ThreeView(
|
|||||||
scope = context,
|
scope = context,
|
||||||
rootMeta = vision.properties.root(),
|
rootMeta = vision.properties.root(),
|
||||||
getPropertyState = { name ->
|
getPropertyState = { name ->
|
||||||
if (vision.properties.own?.get(name) != null) {
|
if (vision.properties.own[name] != null) {
|
||||||
EditorPropertyState.Defined
|
EditorPropertyState.Defined
|
||||||
} else if (vision.properties.root()[name] != null) {
|
} else if (vision.properties.root()[name] != null) {
|
||||||
// TODO differentiate
|
// TODO differentiate
|
||||||
@ -148,7 +148,7 @@ public fun ThreeView(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
name = Name.EMPTY,
|
name = Name.EMPTY,
|
||||||
updates = vision.properties.changes,
|
updates = vision.properties.flowChanges(),
|
||||||
rootDescriptor = vision.descriptor
|
rootDescriptor = vision.descriptor
|
||||||
)
|
)
|
||||||
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
|
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
|
||||||
|
Loading…
Reference in New Issue
Block a user