diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualGroup.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualGroup.kt index 23b889f6..aed504e9 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualGroup.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualGroup.kt @@ -28,31 +28,31 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), VisualGroup { } } - private data class Listener(val owner: Any?, val callback: (Name, VisualObject?) -> Unit) + private data class StructureChangeListeners(val owner: Any?, val callback: (Name, VisualObject?) -> Unit) @Transient - private val listeners = HashSet() + private val structureChangeListeners = HashSet() /** * Add listener for children change */ override fun onChildrenChange(owner: Any?, action: (Name, VisualObject?) -> Unit) { - listeners.add(Listener(owner, action)) + structureChangeListeners.add(StructureChangeListeners(owner, action)) } /** * Remove children change listener */ override fun removeChildrenChangeListener(owner: Any?) { - listeners.removeAll { it.owner === owner } + structureChangeListeners.removeAll { it.owner === owner } } -// /** -// * Propagate children change event upwards -// */ -// protected fun childrenChanged(name: Name, child: VisualObject?) { -// -// } + /** + * Propagate children change event upwards + */ + protected fun childrenChanged(name: Name, child: VisualObject?) { + structureChangeListeners.forEach { it.callback(name, child) } + } /** * Remove a child with given name token @@ -62,7 +62,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), VisualGroup { /** * Add, remove or replace child with given name */ - protected abstract fun setChild(token: NameToken, child: VisualObject?) + protected abstract fun setChild(token: NameToken, child: VisualObject) /** * Add a static child. Statics could not be found by name, removed or replaced @@ -99,7 +99,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), VisualGroup { parent[name.last()!!.asName()] = child } } - listeners.forEach { it.callback(name, child) } + structureChangeListeners.forEach { it.callback(name, child) } } operator fun set(key: String, child: VisualObject?) = if (key.isBlank()) { diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt index 7b3b7a89..5d4622e2 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/AbstractVisualObject.kt @@ -2,6 +2,7 @@ package hep.dataforge.vis.common import hep.dataforge.meta.* import hep.dataforge.names.Name +import hep.dataforge.names.asName import kotlinx.serialization.Transient internal data class PropertyListener( @@ -14,6 +15,19 @@ abstract class AbstractVisualObject : VisualObject { @Transient override var parent: VisualObject? = null + override var style: Meta? = null + set(value) { + //notify about style removed + style?.items?.forEach {(name, value) -> + propertyChanged(name.asName(), value, null) + } + field = value + //notify about style adition + value?.items?.forEach { (name, value) -> + propertyChanged(name.asName(), null, value) + } + } + @Transient private val listeners = HashSet() @@ -31,7 +45,8 @@ abstract class AbstractVisualObject : VisualObject { listeners.removeAll { it.owner == owner } } - abstract var properties: Config? + protected abstract var properties: Config? + override val config: Config get() = properties ?: Config().also { config -> properties = config.apply { onChange(this, ::propertyChanged) } @@ -43,9 +58,9 @@ abstract class AbstractVisualObject : VisualObject { override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { return if (inherit) { - properties?.get(name) ?: parent?.getProperty(name, inherit) + style?.get(name) ?: properties?.get(name) ?: parent?.getProperty(name, inherit) } else { - properties?.get(name) + style?.get(name) ?: properties?.get(name) } } diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt index 1c15a0a5..9aebedbd 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt @@ -21,6 +21,11 @@ interface VisualObject : MetaRepr, Configurable { @Transient var parent: VisualObject? + /** + * A style which is set externally and could not be modified from inside + */ + var style: Meta? + /** * Set property for this object */ diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy.kt index f91ec56a..e1e0d559 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy.kt @@ -1,4 +1,4 @@ -@file:UseSerializers(Point3DSerializer::class, NameSerializer::class) +@file:UseSerializers(Point3DSerializer::class, NameSerializer::class, ConfigSerializer::class) package hep.dataforge.vis.spatial @@ -8,15 +8,20 @@ import hep.dataforge.meta.Config import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.MetaItem import hep.dataforge.names.Name +import hep.dataforge.names.NameToken +import hep.dataforge.names.asName import hep.dataforge.vis.common.AbstractVisualObject +import hep.dataforge.vis.common.VisualGroup +import hep.dataforge.vis.common.VisualObject import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import kotlinx.serialization.UseSerializers /** * A proxy [VisualObject3D] to reuse a template object */ @Serializable -class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D { +class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D { override var position: Point3D? = null override var rotation: Point3D? = null @@ -28,10 +33,10 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D { /** * Recursively search for defined template in the parent */ - val template: VisualObject3D - get() = (parent as? VisualGroup3D)?.getTemplate(templateName) + val template: VisualObject3D by lazy { + (parent as? VisualGroup3D)?.getTemplate(templateName) ?: error("Template with name $templateName not found in $parent") - + } override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { return if (inherit) { @@ -45,16 +50,58 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D { //TODO add reference to child updatePosition() } -} -//fun VisualGroup3D.proxy( -// templateName: Name, -// //name: String? = null, -// builder: VisualGroup3D.() -> Unit -//): Proxy { -// val template = getTemplate(templateName) ?: templates.builder() -// return Proxy(this, templateName).also { set(name, it) } -//} + override val children: Map + get() = (template as? VisualGroup)?.children?.mapValues { + ProxyChild(it.key.asName()) + } ?: emptyMap() + + private data class ProxyChangeListeners(val owner: Any?, val callback: (Name, VisualObject?) -> Unit) + + @Transient + private val listeners = HashSet() + + override fun onChildrenChange(owner: Any?, action: (Name, VisualObject?) -> Unit) { + listeners.add(ProxyChangeListeners(owner, action)) + } + + override fun removeChildrenChangeListener(owner: Any?) { + listeners.removeAll { it.owner == owner } + } + + override fun set(name: Name, child: VisualObject?) { + error("Content change not supported for proxy") + } + + private val propertyCache: HashMap = HashMap() + + private fun Config.attachListener(obj: VisualObject) { + onChange(this@Proxy) { name, before, after -> + listeners.forEach { listener -> + listener.callback(name, obj) + } + } + } + + inner class ProxyChild(val name: Name) : AbstractVisualObject() { + override var properties: Config? + get() = propertyCache.getOrPut(name) { + Config().apply { + attachListener(this@ProxyChild) + } + } + set(value) { + if (value == null) { + propertyCache.remove(name)?.removeListener(this@Proxy) + } else { + propertyCache[name] = value.apply { + attachListener(this@ProxyChild) + } + } + } + + } +} inline fun VisualGroup3D.ref( templateName: Name, diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualGroup3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualGroup3D.kt index ea0837b3..20024ad9 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualGroup3D.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualGroup3D.kt @@ -1,9 +1,9 @@ @file:UseSerializers(Point3DSerializer::class, ConfigSerializer::class, NameTokenSerializer::class) + package hep.dataforge.vis.spatial import hep.dataforge.io.ConfigSerializer import hep.dataforge.meta.Config -import hep.dataforge.meta.Configurable import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.set import hep.dataforge.names.Name @@ -16,7 +16,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers @Serializable -class VisualGroup3D : AbstractVisualGroup(), VisualObject3D, Configurable { +class VisualGroup3D() : AbstractVisualGroup(), VisualObject3D { /** * A container for templates visible inside this group */ @@ -36,21 +36,26 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D, Configurable { private val _children = HashMap() override val children: Map get() = _children - override fun removeChild(token: NameToken) { - _children.remove(token) + init { + //Do after deserialization + _children.values.forEach { + it.parent = this + } } - override fun setChild(token: NameToken, child: VisualObject?) { - if (child == null) { - _children.remove(token) + override fun removeChild(token: NameToken) { + _children.remove(token) + childrenChanged(token.asName(), null) + } + + override fun setChild(token: NameToken, child: VisualObject) { + if (child.parent == null) { + child.parent = this } else { - if (child.parent == null) { - child.parent = this - } else { - error("Can't reassign existing parent for $child") - } - _children[token] = child + error("Can't reassign existing parent for $child") } + _children[token] = child + childrenChanged(token.asName(), child) } /** diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt index b873eed2..f36a33d0 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt @@ -90,15 +90,15 @@ var VisualObject3D.detail: Int? get() = getProperty(DETAIL_KEY, false).int set(value) = setProperty(DETAIL_KEY, value) -var VisualObject3D.material: Meta? +var VisualObject.material: Meta? get() = getProperty(MATERIAL_KEY).node set(value) = setProperty(MATERIAL_KEY, value) -var VisualObject3D.visible: Boolean? +var VisualObject.visible: Boolean? get() = getProperty(VISIBLE_KEY).boolean set(value) = setProperty(VISIBLE_KEY, value) -fun VisualObject3D.color(rgb: Int) { +fun VisualObject.color(rgb: Int) { material = buildMeta { "color" to rgb } } diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt index 9bd40342..8621091c 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt @@ -40,6 +40,7 @@ class ThreePlugin : AbstractPlugin() { fun buildObject3D(obj: VisualObject3D): Object3D { return when (obj) { + is Proxy -> proxyFactory(obj) is VisualGroup3D -> { val group = info.laht.threekt.objects.Group() obj.children.forEach { (name, child) -> @@ -49,7 +50,6 @@ class ThreePlugin : AbstractPlugin() { object3D.name = name.toString() group.add(object3D) } catch (ex: Throwable) { -// console.error(ex) logger.error(ex) { "Failed to render $name" } } } @@ -61,7 +61,6 @@ class ThreePlugin : AbstractPlugin() { } } is Composite -> compositeFactory(obj) - is Proxy -> proxyFactory(obj) else -> { //find specialized factory for this type if it is present val factory = findObjectFactory(obj::class) diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt index bb037270..f198e470 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt @@ -1,14 +1,23 @@ package hep.dataforge.vis.spatial.three +import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.spatial.Proxy import hep.dataforge.vis.spatial.VisualObject3D +import hep.dataforge.vis.spatial.material +import hep.dataforge.vis.spatial.visible import info.laht.threekt.core.Object3D +import info.laht.threekt.objects.Mesh class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory { private val cache = HashMap() override val type = Proxy::class + private fun Mesh.updateProperties(obj: VisualObject?) { + material = obj?.material.jsMaterial() + visible = obj?.visible ?: true + } + override fun invoke(obj: Proxy): Object3D { val template = obj.template val cachedObject = cache.getOrPut(template) { @@ -16,9 +25,13 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory { } //val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material) - val mesh = cachedObject.clone() + val object3D = cachedObject.clone() + object3D.updatePosition(obj) - mesh.updatePosition(obj) - return mesh + obj.onChildrenChange(object3D) { name, propertyHolder -> + (object3D.findChild(name) as? Mesh)?.updateProperties(propertyHolder) + } + + return object3D } } \ No newline at end of file