diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt index 37220400..66a871cf 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt @@ -10,9 +10,9 @@ import hep.dataforge.vision.bootstrap.canvasControls import hep.dataforge.vision.bootstrap.card import hep.dataforge.vision.bootstrap.gridRow import hep.dataforge.vision.react.ThreeCanvasComponent -import hep.dataforge.vision.react.configEditor import hep.dataforge.vision.react.flexColumn import hep.dataforge.vision.react.objectTree +import hep.dataforge.vision.react.propertyEditor import hep.dataforge.vision.solid.specifications.Camera import hep.dataforge.vision.solid.specifications.Canvas3DOptions import hep.dataforge.vision.solid.three.ThreeCanvas @@ -193,7 +193,7 @@ val MMApp = functionalComponent("Muon monitor") { props -> else -> root[selected] } if (selectedObject != null) { - configEditor( + propertyEditor( selectedObject.config, selectedObject.descriptor, default = selectedObject.allProperties, diff --git a/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt b/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt index d4e8d833..366acfc4 100644 --- a/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt +++ b/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vision/solid/demo/VariableBox.kt @@ -5,7 +5,6 @@ import hep.dataforge.names.plus import hep.dataforge.names.startsWith import hep.dataforge.values.asValue import hep.dataforge.vision.getProperty -import hep.dataforge.vision.properties import hep.dataforge.vision.set import hep.dataforge.vision.setProperty import hep.dataforge.vision.solid.* @@ -34,8 +33,8 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV scaleX = xSize scaleY = ySize scaleZ = zSize - properties[MeshThreeFactory.EDGES_ENABLED_KEY] = false - properties[MeshThreeFactory.WIREFRAME_ENABLED_KEY] = false + getProperty(MeshThreeFactory.EDGES_ENABLED_KEY, false) + getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY, false) } override fun render(three: ThreePlugin): Object3D { @@ -63,7 +62,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV mesh.scale.set(xSize, ySize, zSize) //add listener to object properties - propertyInvalidated.onEach { name -> + propertyNameFlow.onEach { name -> when { name.startsWith(GEOMETRY_KEY) -> { val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0 diff --git a/settings.gradle.kts b/settings.gradle.kts index 978abd5e..3877a947 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { - val kotlinVersion = "1.4.20" - val toolsVersion = "0.7.0" + val kotlinVersion = "1.4.21" + val toolsVersion = "0.7.1" repositories { mavenLocal() diff --git a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/visionPropertyEditor.kt b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/visionPropertyEditor.kt index 9734bc74..f172f113 100644 --- a/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/visionPropertyEditor.kt +++ b/ui/bootstrap/src/main/kotlin/hep/dataforge/vision/bootstrap/visionPropertyEditor.kt @@ -3,29 +3,30 @@ package hep.dataforge.vision.bootstrap import hep.dataforge.meta.Meta import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.vision.Vision +import hep.dataforge.vision.allProperties import hep.dataforge.vision.getStyle -import hep.dataforge.vision.properties import hep.dataforge.vision.react.configEditor import hep.dataforge.vision.react.metaViewer +import hep.dataforge.vision.styles import org.w3c.dom.Element import react.RBuilder import react.dom.render public fun RBuilder.visionPropertyEditor( - item: Vision, - descriptor: NodeDescriptor? = item.descriptor, + vision: Vision, + descriptor: NodeDescriptor? = vision.descriptor, default: Meta? = null, - key: Any? = null + key: Any? = null, ) { card("Properties") { - configEditor(item.properties, descriptor, default, key) + configEditor(vision.allProperties(), descriptor, default, key) } - val styles = item.styles - if(styles.isNotEmpty()) { + val styles = vision.styles + if (styles.isNotEmpty()) { card("Styles") { accordion("styles") { styles.forEach { styleName -> - val style = item.getStyle(styleName) + val style = vision.getStyle(styleName) if (style != null) { entry(styleName) { metaViewer(style) @@ -40,7 +41,7 @@ public fun RBuilder.visionPropertyEditor( public fun Element.visionPropertyEditor( item: Vision, descriptor: NodeDescriptor? = item.descriptor, - default: Meta? = null + default: Meta? = null, ): Unit = render(this) { visionPropertyEditor(item, descriptor, default) } \ No newline at end of file diff --git a/ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt b/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt similarity index 60% rename from ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt rename to ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt index b659452f..e88e42a1 100644 --- a/ui/react/src/main/kotlin/hep/dataforge/vision/react/ConfigEditor.kt +++ b/ui/react/src/main/kotlin/hep/dataforge/vision/react/PropertyEditor.kt @@ -7,6 +7,14 @@ import hep.dataforge.names.NameToken import hep.dataforge.names.lastOrNull import hep.dataforge.names.plus import hep.dataforge.values.Value +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.html.js.onClickFunction import org.w3c.dom.Element import org.w3c.dom.events.Event @@ -14,55 +22,59 @@ import react.* import react.dom.render import styled.* -public external interface ConfigEditorItemProps : RProps { +public external interface PropertyEditorItemProps : RProps { /** * Root config object - always non null */ - public var root: MutableItemProvider + public var provider: MutableItemProvider /** - * Full path to the displayed node in [root]. Could be empty + * Full path to the displayed node in [provider]. Could be empty */ public var name: Name - /** - * Root default - */ - public var default: Meta? - /** * Root descriptor */ public var descriptor: NodeDescriptor? + + + public var scope: CoroutineScope? + + /** + * + */ + public var updateFlow: Flow? } -private val ConfigEditorItem: FunctionalComponent = +private val PropertyEditorItem: FunctionalComponent = functionalComponent("ConfigEditorItem") { props -> - configEditorItem(props) + propertyEditorItem(props) } -private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { +private fun RBuilder.propertyEditorItem(props: PropertyEditorItemProps) { var expanded: Boolean by useState { true } - var item: MetaItem<*>? by useState { props.root.getItem(props.name) } + var item: MetaItem<*>? by useState { props.provider.getItem(props.name) } val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name) - val defaultItem = props.default?.get(props.name) - var actualItem: MetaItem? by useState { item ?: defaultItem ?: descriptorItem?.defaultItem() } + var actualItem: MetaItem? by useState { item ?: descriptorItem?.defaultItem() } val token = props.name.lastOrNull()?.toString() ?: "Properties" fun update() { - item = props.root.getItem(props.name) - actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem() + item = props.provider.getItem(props.name) + actualItem = item ?: descriptorItem?.defaultItem() } - useEffectWithCleanup(listOf(props.root)) { - props.root.onChange(this) { name, _, _ -> - if (name == props.name) { - update() - } + if (props.updateFlow != null) { + useEffectWithCleanup(listOf(props.provider, props.updateFlow)) { + val updateJob = props.updateFlow!!.onEach { updatedName -> + if (updatedName == props.name) { + update() + } + }.launchIn(props.scope ?: GlobalScope) + return@useEffectWithCleanup { updateJob.cancel() } } - return@useEffectWithCleanup { props.root.removeListener(this) } } val expanderClick: (Event) -> Unit = { @@ -71,15 +83,15 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { val valueChanged: (Value?) -> Unit = { if (it == null) { - props.root.remove(props.name) + props.provider.remove(props.name) } else { - props.root[props.name] = it + props.provider[props.name] = it } update() } val removeClick: (Event) -> Unit = { - props.root.remove(props.name) + props.provider.remove(props.name) update() } @@ -121,7 +133,6 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { add(NameToken(it)) } item?.node?.items?.keys?.let { addAll(it) } - defaultItem?.node?.items?.keys?.let { addAll(it) } } keys.filter { !it.body.startsWith("@") }.forEach { token -> @@ -129,12 +140,11 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { css { +TreeStyles.treeItem } - child(ConfigEditorItem) { + child(PropertyEditorItem) { attrs { this.key = props.name.toString() - this.root = props.root + this.provider = props.provider this.name = props.name + token - this.default = props.default this.descriptor = props.descriptor } } @@ -190,63 +200,79 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) { } } -public external interface ConfigEditorProps : RProps { - public var id: Name - public var root: MutableItemProvider - public var default: Meta? +public external interface PropertyEditorProps : RProps { + public var provider: MutableItemProvider + public var updateFlow: Flow? public var descriptor: NodeDescriptor? + public var scope: CoroutineScope? } @JsExport -public val ConfigEditor: FunctionalComponent = functionalComponent("ConfigEditor") { props -> - child(ConfigEditorItem) { +public val PropertyEditor: FunctionalComponent = functionalComponent("ConfigEditor") { props -> + child(PropertyEditorItem) { attrs { this.key = "" - this.root = props.root + this.provider = props.provider this.name = Name.EMPTY - this.default = props.default this.descriptor = props.descriptor + this.scope = props.scope } } } +public fun RBuilder.propertyEditor( + provider: MutableItemProvider, + updateFlow: Flow? = null, + descriptor: NodeDescriptor? = null, + key: Any? = null, + scope: CoroutineScope? = null, +) { + child(PropertyEditor) { + attrs { + this.key = key?.toString() ?: "" + this.provider = provider + this.updateFlow = updateFlow + this.descriptor = descriptor + this.scope = scope + } + } +} + +private fun Config.flowUpdates(): Flow = callbackFlow { + onChange(this) { name, _, _ -> + launch { + send(name) + } + } + awaitClose { + removeListener(this) + } +} + +public fun MutableItemProvider.withDefault(default: ItemProvider): MutableItemProvider = object : MutableItemProvider { + override fun getItem(name: Name): MetaItem<*>? = getItem(name) ?: default.getItem(name) + + override fun setItem(name: Name, item: MetaItem<*>?) = this@withDefault.setItem(name, item) +} + + + +public fun RBuilder.configEditor( + config: Config, + descriptor: NodeDescriptor? = null, + default: Meta? = null, + key: Any? = null, + scope: CoroutineScope? = null, +) = propertyEditor(config.withDefault(default ?: ItemProvider.EMPTY), config.flowUpdates(), descriptor, key, scope) + public fun Element.configEditor( config: Config, descriptor: NodeDescriptor? = null, default: Meta? = null, key: Any? = null, + scope: CoroutineScope? = null, ) { render(this) { - child(ConfigEditor) { - attrs { - this.key = key?.toString() ?: "" - this.root = config - this.descriptor = descriptor - this.default = default - } - } + configEditor(config,descriptor,default, key, scope) } -} - -public fun RBuilder.configEditor( - config: MutableItemProvider, - descriptor: NodeDescriptor? = null, - default: Meta? = null, - key: Any? = null, -) { - child(ConfigEditor) { - attrs { - this.key = key?.toString() ?: "" - this.root = config - this.descriptor = descriptor - this.default = default - } - } -} -// -//public fun RBuilder.configEditor( -// obj: Configurable, -// descriptor: NodeDescriptor?, -// default: Meta? = null, -// key: Any? = null -//): Unit = configEditor(obj.config, descriptor, default, key) +} \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt index e7a6d971..783b376c 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/StyleSheet.kt @@ -64,6 +64,15 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) } +/** + * List of names of styles applied to this object. Order matters. Not inherited. + */ +public var Vision.styles: List + get() = getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList() + set(value) { + setProperty(Vision.STYLE_KEY, value) + } + /** * A stylesheet for this group and its descendants. Stylesheet is not applied directly, * but instead is just a repository for named configurations. diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt index 4baa5b8a..e1e73800 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/Vision.kt @@ -1,15 +1,15 @@ package hep.dataforge.vision -import hep.dataforge.meta.* +import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.MutableItemProvider import hep.dataforge.meta.descriptors.Described import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.get import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.names.toName import hep.dataforge.provider.Type -import hep.dataforge.values.asValue import hep.dataforge.vision.Vision.Companion.TYPE -import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY import kotlinx.coroutines.flow.Flow import kotlinx.serialization.Transient @@ -26,7 +26,8 @@ public interface Vision : Described { public var parent: VisionGroup? /** - * A fast accessor method to get own property (no inheritance or styles + * A fast accessor method to get own property (no inheritance or styles). + * Should be equivalent to `getProperty(name,false,false,false)`. */ public fun getOwnProperty(name: Name): MetaItem<*>? @@ -46,13 +47,13 @@ public interface Vision : Described { /** * Set the property value */ - public fun setProperty(name: Name, item: MetaItem<*>?) + public fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean = true) /** * Flow of property invalidation events. It does not contain property values after invalidation since it is not clear * if it should include inherited properties etc. */ - public val propertyInvalidated: Flow + public val propertyNameFlow: Flow /** @@ -60,20 +61,6 @@ public interface Vision : Described { */ public fun notifyPropertyChanged(propertyName: Name): Unit - /** - * List of names of styles applied to this object. Order matters. Not inherited. - */ - public var styles: List - get() = getProperty( - STYLE_KEY, - inherit = false, - includeStyles = false, - includeDefaults = true - )?.stringList ?: emptyList() - set(value) { - setProperty(STYLE_KEY, value) - } - /** * Update this vision using external meta. Children are not updated. */ @@ -90,19 +77,27 @@ public interface Vision : Described { } /** - * Convenient accessor for all properties of a vision. Provided properties include styles and defaults, but do not inherit. + * Convenient accessor for all properties of a vision. + * @param inherit - inherit property value from the parent by default. If null, inheritance is inferred from descriptor */ -public val Vision.properties: MutableItemProvider - get() = object : MutableItemProvider { - override fun getItem(name: Name): MetaItem<*>? = getProperty(name, - inherit = false, - includeStyles = true, - includeDefaults = true +public fun Vision.allProperties( + inherit: Boolean? = null, + includeStyles: Boolean = true, + includeDefaults: Boolean = true, +): MutableItemProvider = object : MutableItemProvider { + override fun getItem(name: Name): MetaItem<*>? { + val actualInherit = inherit ?: descriptor?.get(name)?.inherited ?: false + return getProperty( + name, + inherit = actualInherit, + includeStyles = includeStyles, + includeDefaults = includeDefaults ) - - override fun setItem(name: Name, item: MetaItem<*>?): Unit = setProperty(name, item) } + override fun setItem(name: Name, item: MetaItem<*>?): Unit = setProperty(name, item) +} + /** * Get [Vision] property using key as a String */ @@ -116,31 +111,13 @@ public fun Vision.getProperty( /** * A convenience method to pair [getProperty] */ -public fun Vision.setProperty(key: Name, value: Any?) { - properties[key] = value +public fun Vision.setProperty(key: Name, item: Any?) { + setProperty(key, MetaItem.of(item)) } /** * A convenience method to pair [getProperty] */ -public fun Vision.setProperty(key: String, value: Any?) { - properties[key] = value +public fun Vision.setProperty(key: String, item: Any?) { + setProperty(key.toName(), MetaItem.of(item)) } - -/** - * Control visibility of the element - */ -public var Vision.visible: Boolean? - get() = getProperty(VISIBLE_KEY).boolean - set(value) = setProperty(VISIBLE_KEY, value?.asValue()) - -public fun Vision.props(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider { - override fun getItem(name: Name): MetaItem<*>? { - return getProperty(name, inherit) - } - - override fun setItem(name: Name, item: MetaItem<*>?) { - setProperty(name, item) - } - -} \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt index aa1fcba2..c608e4c4 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionBase.kt @@ -1,9 +1,12 @@ package hep.dataforge.vision -import hep.dataforge.meta.* +import hep.dataforge.meta.Config +import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.MutableMeta import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.defaultItem import hep.dataforge.meta.descriptors.get +import hep.dataforge.meta.update import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.values.ValueType @@ -30,34 +33,28 @@ public open class VisionBase : Vision { /** * Object own properties excluding styles and inheritance */ - @SerialName("properties") - protected var innerProperties: Config? = null + public var properties: Config? = null private set - /** - * All own properties as a read-only Meta - */ - public val ownProperties: Meta get() = innerProperties ?: Meta.EMPTY - @Synchronized private fun getOrCreateConfig(): Config { - if (innerProperties == null) { + if (properties == null) { val newProperties = Config() - innerProperties = newProperties + properties = newProperties newProperties.onChange(this) { name, oldItem, newItem -> if (oldItem != newItem) { notifyPropertyChanged(name) } } } - return innerProperties!! + return properties!! } /** * A fast accessor method to get own property (no inheritance or styles */ override fun getOwnProperty(name: Name): MetaItem<*>? { - return innerProperties?.getItem(name) + return properties?.getItem(name) } override fun getProperty( @@ -77,9 +74,11 @@ public open class VisionBase : Vision { }.merge() @Synchronized - override fun setProperty(name: Name, item: MetaItem<*>?) { + override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) { getOrCreateConfig().setItem(name, item) - notifyPropertyChanged(name) + if(notify) { + notifyPropertyChanged(name) + } } override val descriptor: NodeDescriptor? get() = null @@ -96,11 +95,11 @@ public open class VisionBase : Vision { @Transient private val _propertyInvalidationFlow: MutableSharedFlow = MutableSharedFlow() - override val propertyInvalidated: SharedFlow get() = _propertyInvalidationFlow + override val propertyNameFlow: SharedFlow get() = _propertyInvalidationFlow override fun notifyPropertyChanged(propertyName: Name) { if (propertyName == STYLE_KEY) { - updateStyles(properties.getItem(STYLE_KEY)?.stringList ?: emptyList()) + updateStyles(styles) } _propertyInvalidationFlow.tryEmit(propertyName) diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt index f2ac5d6b..c2717108 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/VisionChange.kt @@ -64,7 +64,7 @@ private fun Vision.isolate(manager: VisionManager): Vision { public class VisionChange( public val reset: Boolean = false, public val vision: Vision? = null, - public val properties: Meta? = null, + @Serializable(MetaSerializer::class) public val properties: Meta? = null, public val children: Map? = null, ) { @@ -81,7 +81,7 @@ private fun CoroutineScope.collectChange( ) { //Collect properties change - source.propertyInvalidated.onEach { propertyName -> + source.propertyNameFlow.onEach { propertyName -> val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false) collector().propertyChanged(name, propertyName, newItem) }.launchIn(this) diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt index 7c9a4521..75cb8f40 100644 --- a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/misc.kt @@ -31,16 +31,15 @@ public inline fun > NodeDescriptor.enum(key: Name, default: } @DFExperimental -public val Vision.ownProperties: Meta? - get() = (this as? VisionBase)?.ownProperties +public val Vision.properties: Config? + get() = (this as? VisionBase)?.properties -@DFExperimental -public val Vision.describedProperties: Meta - get() = Meta { - descriptor?.items?.forEach { (key, _) -> - key put getProperty(key) - } - } +/** + * Control visibility of the element + */ +public var Vision.visible: Boolean? + get() = getProperty(Vision.VISIBLE_KEY).boolean + set(value) = setProperty(Vision.VISIBLE_KEY, value?.asValue()) public fun Vision.configure(meta: Meta?): Unit = update(VisionChange(properties = meta)) diff --git a/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visionDescriptor.kt b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visionDescriptor.kt new file mode 100644 index 00000000..3393c838 --- /dev/null +++ b/visionforge-core/src/commonMain/kotlin/hep/dataforge/vision/visionDescriptor.kt @@ -0,0 +1,25 @@ +package hep.dataforge.vision + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.boolean +import hep.dataforge.meta.descriptors.ItemDescriptor +import hep.dataforge.meta.descriptors.attributes +import hep.dataforge.meta.get +import hep.dataforge.meta.set + +private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited" + +public var ItemDescriptor.inherited: Boolean + get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false + set(value) = attributes { + set(INHERITED_DESCRIPTOR_ATTRIBUTE, value) + } + + +public val Vision.describedProperties: Meta + get() = Meta { + descriptor?.items?.forEach { (key, descriptor) -> + key put getProperty(key, inherit = descriptor.inherited) + } + } + diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/VisualObjectEditorFragment.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/VisualObjectEditorFragment.kt index e8b22dbe..762c8090 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/VisualObjectEditorFragment.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/editor/VisualObjectEditorFragment.kt @@ -19,8 +19,8 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() { constructor( item: Vision?, descriptor: NodeDescriptor?, - selector: (Vision) -> MutableItemProvider = { it.properties } - ) : this({it.describedProperties}) { + selector: (Vision) -> MutableItemProvider = { it.allProperties() }, + ) : this({ it.describedProperties }) { this.item = item this.descriptorProperty.set(descriptor) } diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXReferenceFactory.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXReferenceFactory.kt index 2ffb2660..9b15df72 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXReferenceFactory.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/FXReferenceFactory.kt @@ -15,7 +15,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory + obj.propertyNameFlow.onEach { name-> if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) { val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'") val propertyName = name.cutFirst() diff --git a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/VisualObjectFXBinding.kt b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/VisualObjectFXBinding.kt index 8d19db59..30a8d6a8 100644 --- a/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/VisualObjectFXBinding.kt +++ b/visionforge-fx/src/main/kotlin/hep/dataforge/vision/solid/VisualObjectFXBinding.kt @@ -18,7 +18,7 @@ class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) { private val bindings = HashMap?>>() init { - obj.propertyInvalidated.onEach { name -> + obj.propertyNameFlow.onEach { name -> bindings.filter { it.key.startsWith(name) }.forEach { entry -> Platform.runLater { entry.value.invalidate() diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt index 58ec3b51..2ac28d6b 100644 --- a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt @@ -38,7 +38,6 @@ public class GDMLTransformerSettings { public var solidAction: (GDMLSolid) -> Action = { Action.PROTOTYPE } public var volumeAction: (GDMLGroup) -> Action = { Action.PROTOTYPE } - } private class GDMLTransformer(val settings: GDMLTransformerSettings) { @@ -324,7 +323,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) { } } - fun finalize(final: SolidGroup): SolidGroup { + private fun finalize(final: SolidGroup): SolidGroup { //final.prototypes = proto final.useStyle("GDML") { Solid.ROTATION_ORDER_KEY put RotationOrder.ZXY diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt index bec3822c..125614c6 100644 --- a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt @@ -3,7 +3,6 @@ package hep.dataforge.vision.gdml import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.sequence import hep.dataforge.vision.Vision -import hep.dataforge.vision.ownProperties import hep.dataforge.vision.properties import hep.dataforge.vision.solid.* @@ -28,8 +27,8 @@ internal fun Vision.updateFrom(other: Vision): Vision { scaleY = scaleY.toDouble() * other.scaleY.toDouble() scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble() } - other.ownProperties?.sequence()?.forEach { (name, item) -> - if (properties.getItem(name) == null) { + other.properties?.sequence()?.forEach { (name, item) -> + if (getProperty(name) == null) { setProperty(name, item) } } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt index dacc79bd..e1e79ef8 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Composite.kt @@ -1,5 +1,6 @@ package hep.dataforge.vision.solid +import hep.dataforge.meta.Meta import hep.dataforge.meta.update import hep.dataforge.names.NameToken import hep.dataforge.vision.* @@ -39,7 +40,7 @@ public inline fun VisionContainerBuilder.composite( val children = group.children.values.filterIsInstance() if (children.size != 2) error("Composite requires exactly two children") return Composite(type, children[0], children[1]).also { - it.configure { update(group.ownProperties) } + it.configure { update(group.properties ?: Meta.EMPTY) } if (group.position != null) { it.position = group.position } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/PolyLine.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/PolyLine.kt index 998f7f4f..28f4ad68 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/PolyLine.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/PolyLine.kt @@ -6,7 +6,7 @@ import hep.dataforge.names.asName import hep.dataforge.names.plus import hep.dataforge.vision.VisionBuilder import hep.dataforge.vision.VisionContainerBuilder -import hep.dataforge.vision.props +import hep.dataforge.vision.allProperties import hep.dataforge.vision.set import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -16,7 +16,7 @@ import kotlinx.serialization.Serializable public class PolyLine(public var points: List) : SolidBase(), Solid { //var lineType by string() - public var thickness: Number by props().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY) + public var thickness: Number by allProperties(inherit = false).number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY) public companion object { public val THICKNESS_KEY: Name = "thickness".asName() diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt index d690c6b1..65cf37e4 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/Solid.kt @@ -81,7 +81,7 @@ public interface Solid : Vision { if (first.position != second.position) return false if (first.rotation != second.rotation) return false if (first.scale != second.scale) return false - if (first.ownProperties != second.ownProperties) return false + if (first.properties != second.properties) return false return true } @@ -89,7 +89,7 @@ public interface Solid : Vision { var result = +(solid.position?.hashCode() ?: 0) result = 31 * result + (solid.rotation?.hashCode() ?: 0) result = 31 * result + (solid.scale?.hashCode() ?: 0) - result = 31 * result + solid.properties.hashCode() + result = 31 * result + solid.allProperties().hashCode() return result } } @@ -99,7 +99,7 @@ public interface Solid : Vision { * Get the layer number this solid belongs to. Return 0 if layer is not defined. */ public var Solid.layer: Int - get() = properties.getItem(LAYER_KEY).int ?: 0 + get() = allProperties().getItem(LAYER_KEY).int ?: 0 set(value) { setProperty(LAYER_KEY, value) } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidGroup.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidGroup.kt index cc3b92e4..77aa950c 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidGroup.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidGroup.kt @@ -130,7 +130,7 @@ internal class Prototypes( includeDefaults: Boolean, ): MetaItem<*>? = null - override fun setProperty(name: Name, item: MetaItem<*>?) { + override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) { TODO("Not yet implemented") } diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt index 8cf8755c..f060afea 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidMaterial.kt @@ -41,14 +41,14 @@ public operator fun ColorAccessor?.invoke(webColor: String) { * Set color as RGB integer */ public operator fun ColorAccessor?.invoke(rgb: Int) { - this?.value = rgb.asValue() + this?.value = Colors.rgbToString(rgb).asValue() } /** * Set color as RGB */ public operator fun ColorAccessor?.invoke(r: UByte, g: UByte, b: UByte) { - this?.value = Colors.rgbToString(r, g, b).asValue() + this?.value = Colors.rgbToString(r, g, b).asValue() } @VisionBuilder @@ -112,7 +112,15 @@ public class SolidMaterial : Scheme() { } } -public val Solid.color: ColorAccessor get() = ColorAccessor(properties, MATERIAL_COLOR_KEY) +public val Solid.color: ColorAccessor + get() = ColorAccessor( + allProperties( + inherit = true, + includeStyles = true, + includeDefaults = true + ), + MATERIAL_COLOR_KEY + ) public var Solid.material: SolidMaterial? get() = getProperty(MATERIAL_KEY).node?.let { SolidMaterial.read(it) } @@ -120,7 +128,11 @@ public var Solid.material: SolidMaterial? @VisionBuilder public fun Solid.material(builder: SolidMaterial.() -> Unit) { - val node = properties.getItem(MATERIAL_KEY).node + val node = allProperties( + inherit = true, + includeStyles = true, + includeDefaults = true + ).getItem(MATERIAL_KEY).node if (node != null) { SolidMaterial.update(node, builder) } else { diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt index efc7b1cb..5378f71d 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/SolidReference.kt @@ -47,12 +47,12 @@ public class SolidReferenceGroup( return getOwnProperty(childPropertyName(childName, propertyName)) } - private fun setChildProperty(childName: Name, propertyName: Name, item: MetaItem<*>?) { - setProperty(childPropertyName(childName, propertyName), item) + private fun setChildProperty(childName: Name, propertyName: Name, item: MetaItem<*>?, notify: Boolean) { + setProperty(childPropertyName(childName, propertyName), item, notify) } private fun prototypeFor(name: Name): Solid { - return if(name.isEmpty()) prototype else { + return if (name.isEmpty()) prototype else { (prototype as? SolidGroup)?.get(name) as? Solid ?: error("Prototype with name $name not found in $this") } @@ -98,8 +98,8 @@ public class SolidReferenceGroup( override fun getOwnProperty(name: Name): MetaItem<*>? = getChildProperty(childName, name) - override fun setProperty(name: Name, item: MetaItem<*>?) { - setChildProperty(childName, name, item) + override fun setProperty(name: Name, item: MetaItem<*>?, notify: Boolean) { + setChildProperty(childName, name, item, notify) } override fun getProperty( @@ -119,16 +119,16 @@ public class SolidReferenceGroup( }.merge() override var parent: VisionGroup? - get(){ + get() { val parentName = childName.cutLast() - return if( parentName.isEmpty()) this@SolidReferenceGroup else ReferenceChild(parentName) + return if (parentName.isEmpty()) this@SolidReferenceGroup else ReferenceChild(parentName) } set(value) { error("Setting a parent for a reference child is not possible") } - override val propertyInvalidated: Flow - get() = this@SolidReferenceGroup.propertyInvalidated.filter { name -> + override val propertyNameFlow: Flow + get() = this@SolidReferenceGroup.propertyNameFlow.filter { name -> name.startsWith(childToken(childName)) }.map { name -> name.cutFirst() diff --git a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/RemoveSingleChild.kt b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/RemoveSingleChild.kt index 0b64a7b7..a5b5f1ba 100644 --- a/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/RemoveSingleChild.kt +++ b/visionforge-solid/src/commonMain/kotlin/hep/dataforge/vision/solid/transform/RemoveSingleChild.kt @@ -9,7 +9,7 @@ import hep.dataforge.vision.solid.* internal fun mergeChild(parent: VisionGroup, child: Vision): Vision { return child.apply { - configure(parent.ownProperties) + configure(parent.properties) //parent.properties?.let { config.update(it) } diff --git a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/PropertyTest.kt b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/PropertyTest.kt index 55efe64a..30917b90 100644 --- a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/PropertyTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/PropertyTest.kt @@ -2,10 +2,7 @@ package hep.dataforge.vision.solid import hep.dataforge.meta.int import hep.dataforge.names.asName -import hep.dataforge.vision.getProperty -import hep.dataforge.vision.setProperty -import hep.dataforge.vision.styleSheet -import hep.dataforge.vision.useStyle +import hep.dataforge.vision.* import kotlin.test.Test import kotlin.test.assertEquals diff --git a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/SerializationTest.kt b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/SerializationTest.kt index 017d4db3..c7090eee 100644 --- a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/SerializationTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/SerializationTest.kt @@ -4,7 +4,7 @@ import hep.dataforge.names.Name import hep.dataforge.names.toName import hep.dataforge.vision.MutableVisionGroup import hep.dataforge.vision.get -import hep.dataforge.vision.ownProperties +import hep.dataforge.vision.properties import kotlin.test.Test import kotlin.test.assertEquals @@ -33,7 +33,7 @@ class SerializationTest { val string = SolidManager.encodeToString(cube) println(string) val newCube = SolidManager.decodeFromString(string) - assertEquals(cube.ownProperties, newCube.ownProperties) + assertEquals(cube.properties, newCube.properties) } @Test @@ -54,7 +54,7 @@ class SerializationTest { val string = SolidManager.encodeToString(group) println(string) val reconstructed = SolidManager.decodeFromString(string) as SolidGroup - assertEquals(group["cube"]?.ownProperties, reconstructed["cube"]?.ownProperties) + assertEquals(group["cube"]?.properties, reconstructed["cube"]?.properties) } } \ No newline at end of file diff --git a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/VisionUpdateTest.kt b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/VisionUpdateTest.kt index 79bba867..17babd41 100644 --- a/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/VisionUpdateTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/hep/dataforge/vision/solid/VisionUpdateTest.kt @@ -29,7 +29,7 @@ class VisionUpdateTest { targetVision.update(dif) assertTrue { targetVision["top"] is SolidGroup } assertEquals("red", (targetVision["origin"] as Solid).color.string) // Should work - assertEquals("#00007b", (targetVision["top"] as SolidGroup).color.string) // new item always takes precedence + assertEquals("#00007b", (targetVision["top"] as Solid).color.string) // new item always takes precedence } @Test diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt index 8a80527d..3adaf603 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/MeshThreeFactory.kt @@ -45,7 +45,7 @@ public abstract class MeshThreeFactory( }.applyProperties(obj) //add listener to object properties - obj.propertyInvalidated.onEach { name -> + obj.propertyNameFlow.onEach { name -> when { name.startsWith(Solid.GEOMETRY_KEY) -> { val oldGeometry = mesh.geometry as BufferGeometry diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt index 9ab3f401..4d1c7d99 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLabelFactory.kt @@ -27,7 +27,7 @@ public object ThreeLabelFactory : ThreeFactory { }) return Mesh(textGeo, getMaterial(obj, true)).apply { updatePosition(obj) - obj.propertyInvalidated.onEach { _ -> + obj.propertyNameFlow.onEach { _ -> //TODO three.logger.warn{"Label parameter change not implemented"} }.launchIn(three.updateScope) diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt index 199213ba..d914a218 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeLineFactory.kt @@ -30,7 +30,7 @@ public object ThreeLineFactory : ThreeFactory { updatePosition(obj) //layers.enable(obj.layer) //add listener to object properties - obj.propertyInvalidated.onEach { propertyName -> + obj.propertyNameFlow.onEach { propertyName -> updateProperty(obj, propertyName) }.launchIn(three.updateScope) } diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt index 1bd46f37..2732d624 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreePlugin.kt @@ -68,7 +68,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { updatePosition(obj) //obj.onChildrenChange() - obj.propertyInvalidated.onEach { name -> + obj.propertyNameFlow.onEach { name -> if ( name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.ROTATION) || diff --git a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeReferenceFactory.kt b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeReferenceFactory.kt index 2374c05f..08866d46 100644 --- a/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeReferenceFactory.kt +++ b/visionforge-threejs/src/main/kotlin/hep/dataforge/vision/solid/three/ThreeReferenceFactory.kt @@ -47,7 +47,7 @@ public object ThreeReferenceFactory : ThreeFactory { //TODO apply child properties - obj.propertyInvalidated.onEach { name-> + obj.propertyNameFlow.onEach { name-> if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) { val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'") val propertyName = name.cutFirst()