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 f4486451..ba2fabb0 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 @@ -21,6 +21,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), VisualGroup { */ abstract override val children: Map //get() = _children + // init { // //Do after deserialization // children.values.forEach { 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 5d4622e2..6624b0b1 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 @@ -1,8 +1,10 @@ package hep.dataforge.vis.common import hep.dataforge.meta.* +import hep.dataforge.names.EmptyName import hep.dataforge.names.Name -import hep.dataforge.names.asName +import hep.dataforge.names.toName +import hep.dataforge.vis.common.VisualObject.Companion.STYLE_KEY import kotlinx.serialization.Transient internal data class PropertyListener( @@ -15,17 +17,21 @@ abstract class AbstractVisualObject : VisualObject { @Transient override var parent: VisualObject? = null - override var style: Meta? = null + protected abstract var properties: Config? + + override var style: List + get() = properties?.let { it[STYLE_KEY].stringList } ?: emptyList() 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) - } + setProperty(VisualObject.STYLE_KEY, value) + } + + /** + * The config is initialized and assigned on-demand. + * To avoid unnecessary allocations, one should access [properties] via [getProperty] instead. + */ + override val config: Config + get() = properties ?: Config().also { config -> + properties = config.apply { onChange(this, ::propertyChanged) } } @Transient @@ -45,22 +51,32 @@ abstract class AbstractVisualObject : VisualObject { listeners.removeAll { it.owner == owner } } - protected abstract var properties: Config? - - override val config: Config - get() = properties ?: Config().also { config -> - properties = config.apply { onChange(this, ::propertyChanged) } - } - override fun setProperty(name: Name, value: Any?) { config[name] = value } + private var styleCache: Laminate? = null + + private fun styles(): Laminate { + return styleCache ?: kotlin.run { + Laminate(style.map { it.toName() }.mapNotNull(::findStyle)) + .also { styleCache = it } + } + } + + /** + * Helper to reset style cache + */ + protected fun styleChanged() { + styleCache = null + propertyChanged(EmptyName) + } + override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { return if (inherit) { - style?.get(name) ?: properties?.get(name) ?: parent?.getProperty(name, inherit) + properties?.get(name) ?: parent?.getProperty(name, inherit) ?: styles()[name] } else { - style?.get(name) ?: properties?.get(name) + properties?.get(name) ?: styles()[name] } } @@ -71,4 +87,12 @@ abstract class AbstractVisualObject : VisualObject { "properties" to properties updateMeta() } +} + +internal fun VisualObject.findStyle(styleName: Name): Meta? { + if (this is VisualGroup) { + val style = getStyle(styleName) + if (style != null) return style + } + return parent?.findStyle(styleName) } \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt index 437c30ab..a908cc53 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt @@ -1,5 +1,7 @@ package hep.dataforge.vis.common +import kotlin.math.max + /** * Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt */ @@ -177,6 +179,6 @@ object Colors { fun rgbToString(rgb: Int): String { val string = rgb.toString(16) - return "#" + string.substring(string.length - 6) + return "#" + string.substring(max(0, string.length - 6)) } } \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt index 2be37102..4288d640 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt @@ -1,5 +1,6 @@ package hep.dataforge.vis.common +import hep.dataforge.meta.Meta import hep.dataforge.names.* import hep.dataforge.provider.Provider @@ -11,18 +12,19 @@ interface VisualGroup : VisualObject, Provider, Iterable { override val defaultTarget: String get() = VisualObject.TYPE - override fun provideTop(target: String): Map = if (target == VisualObject.TYPE) { - children.flatMap { (key, value) -> - val res: Map = if (value is VisualGroup) { - value.provideTop(target).mapKeys { key + it.key } - } else { - mapOf(key.asName() to value) - } - res.entries - }.associate { it.toPair() } - } else { - emptyMap() - } + override fun provideTop(target: String): Map = + when (target) { + VisualObject.TYPE -> children.flatMap { (key, value) -> + val res: Map = if (value is VisualGroup) { + value.provideTop(target).mapKeys { key + it.key } + } else { + mapOf(key.asName() to value) + } + res.entries + }.associate { it.toPair() } + //TODO add styles + else -> emptyMap() + } /** * Iterate over children of this group @@ -30,7 +32,20 @@ interface VisualGroup : VisualObject, Provider, Iterable { override fun iterator(): Iterator = children.values.iterator() /** - * Add listener for children change + * Resolve style by its name + * TODO change to Config? + */ + fun getStyle(name: Name): Meta? + + /** + * Add or replace style with given name + */ + fun setStyle(name: Name, meta: Meta) + + /** + * Add listener for children structure change. + * @param owner the handler to properly remove listeners + * @param action First argument of the action is the name of changed child. Second argument is the new value of the object. */ fun onChildrenChange(owner: Any?, action: (Name, VisualObject?) -> Unit) 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 4798f36d..a9600be6 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 @@ -1,14 +1,17 @@ package hep.dataforge.vis.common -import hep.dataforge.meta.* +import hep.dataforge.meta.Configurable +import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.MetaRepr import hep.dataforge.names.Name +import hep.dataforge.names.asName import hep.dataforge.names.toName import hep.dataforge.provider.Type import hep.dataforge.vis.common.VisualObject.Companion.TYPE import kotlinx.serialization.Transient -private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers) -private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta) +//private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers) +//private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta) /** * A root type for display hierarchy @@ -22,11 +25,6 @@ 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 */ @@ -52,8 +50,11 @@ interface VisualObject : MetaRepr, Configurable { */ fun removeChangeListener(owner: Any?) + var style: List + companion object { const val TYPE = "visual" + val STYLE_KEY = "style".asName() //const val META_KEY = "@meta" //const val TAGS_KEY = "@tags" @@ -61,5 +62,4 @@ interface VisualObject : MetaRepr, Configurable { } fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = getProperty(key.toName(), inherit) -fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value) - +fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value) \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegates.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegate.kt similarity index 76% rename from dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegates.kt rename to dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegate.kt index c9b88bb3..c51cdfe8 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegates.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegate.kt @@ -1,7 +1,6 @@ package hep.dataforge.vis.common import hep.dataforge.meta.* -import hep.dataforge.names.EmptyName import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.asName @@ -11,12 +10,11 @@ import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -fun String.asName() = if (isBlank()) EmptyName else NameToken(this).asName() /** * A delegate for display object properties */ -class DisplayObjectDelegate( +class VisualObjectDelegate( val key: Name?, val default: MetaItem<*>?, val inherited: Boolean @@ -36,7 +34,7 @@ class DisplayObjectDelegate( } } -class DisplayObjectDelegateWrapper( +class VisualObjectDelegateWrapper( val key: Name?, val default: T, val inherited: Boolean, @@ -63,55 +61,55 @@ class DisplayObjectDelegateWrapper( fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.value } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.value } fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.string } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.string } fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean } fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.number } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.number } fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.double } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.double } fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.int } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int } fun VisualObject.node(key: String? = null, inherited: Boolean = true) = - DisplayObjectDelegateWrapper(key?.asName(), null, inherited) { it.node } + VisualObjectDelegateWrapper(key?.asName(), null, inherited) { it.node } fun VisualObject.item(key: String? = null, inherited: Boolean = true) = - DisplayObjectDelegateWrapper(key?.asName(), null, inherited) { it } + VisualObjectDelegateWrapper(key?.asName(), null, inherited) { it } //fun Configurable.spec(spec: Specification, key: String? = null) = ChildConfigDelegate(key) { spec.wrap(this) } @JvmName("safeString") fun VisualObject.string(default: String, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.string } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.string } @JvmName("safeBoolean") fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean } @JvmName("safeNumber") fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.number } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.number } @JvmName("safeDouble") fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.double } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.double } @JvmName("safeInt") fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.int } + VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int } inline fun > VisualObject.enum(default: E, key: String? = null, inherited: Boolean = false) = - DisplayObjectDelegateWrapper( + VisualObjectDelegateWrapper( key?.let { NameToken(it).asName() }, default, inherited diff --git a/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/visualGDML.kt b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/visualGDML.kt index 580f487e..84f96717 100644 --- a/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/visualGDML.kt +++ b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/visualGDML.kt @@ -1,8 +1,8 @@ package hep.dataforge.vis.spatial.gdml import hep.dataforge.names.EmptyName +import hep.dataforge.names.asName import hep.dataforge.names.plus -import hep.dataforge.vis.common.asName import hep.dataforge.vis.common.get import hep.dataforge.vis.spatial.* import scientifik.gdml.* 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 44bc6b82..18faaee2 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 @@ -5,6 +5,7 @@ package hep.dataforge.vis.spatial import hep.dataforge.io.ConfigSerializer import hep.dataforge.io.NameSerializer import hep.dataforge.meta.Config +import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.MetaItem import hep.dataforge.names.Name @@ -37,6 +38,11 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua get() = (parent as? VisualGroup3D)?.getTemplate(templateName) ?: error("Template with name $templateName not found in $parent") + override fun getStyle(name: Name): Meta? = null + + override fun setStyle(name: Name, meta: Meta) { + //do nothing + } override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { return if (inherit) { 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 2ec91290..d0057f0c 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,21 +1,29 @@ -@file:UseSerializers(Point3DSerializer::class, ConfigSerializer::class, NameTokenSerializer::class) +@file:UseSerializers( + Point3DSerializer::class, + ConfigSerializer::class, + NameTokenSerializer::class, + NameSerializer::class +) package hep.dataforge.vis.spatial import hep.dataforge.io.ConfigSerializer +import hep.dataforge.io.NameSerializer import hep.dataforge.meta.Config +import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.set -import hep.dataforge.names.Name -import hep.dataforge.names.NameToken -import hep.dataforge.names.asName -import hep.dataforge.names.isEmpty +import hep.dataforge.names.* import hep.dataforge.vis.common.AbstractVisualGroup +import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set @Serializable class VisualGroup3D : AbstractVisualGroup(), VisualObject3D { @@ -28,8 +36,34 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D { field = value } + //FIXME to be lifted to AbstractVisualGroup after https://github.com/Kotlin/kotlinx.serialization/issues/378 is fixed public override var properties: Config? = null + private val styles = HashMap() + + override fun getStyle(name: Name): Meta? = styles[name] + + override fun setStyle(name: Name, meta: Meta) { + fun VisualObject.applyStyle(name: Name, meta: Meta) { + if (style.contains(name.toString())) { + //full update + //TODO do a fine grained update + if(this is AbstractVisualObject){ + styleChanged() + } else { + propertyChanged(EmptyName) + } + } + if (this is VisualGroup) { + this.children.forEach { (_, child) -> + child.applyStyle(name, meta) + } + } + } + styles[name] = meta + applyStyle(name, meta) + } + override var position: Point3D? = null override var rotation: Point3D? = null override var scale: Point3D? = null @@ -96,7 +130,7 @@ fun VisualGroup.attachChildren() { it.parent = this (it as? VisualGroup)?.attachChildren() } - if(this is VisualGroup3D){ + if (this is VisualGroup3D) { templates?.apply { parent = this@attachChildren attachChildren() 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 cb882f18..ed6acd9c 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 @@ -4,11 +4,11 @@ package hep.dataforge.vis.spatial import hep.dataforge.io.NameSerializer import hep.dataforge.meta.* +import hep.dataforge.names.asName import hep.dataforge.names.plus import hep.dataforge.output.Output import hep.dataforge.vis.common.Colors.rgbToString import hep.dataforge.vis.common.VisualObject -import hep.dataforge.vis.common.asName import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.LAYER_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.MATERIAL_KEY diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt index 96289702..466e1c4f 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt @@ -2,10 +2,10 @@ package hep.dataforge.vis.spatial.three import hep.dataforge.meta.boolean import hep.dataforge.meta.node +import hep.dataforge.names.asName import hep.dataforge.names.plus import hep.dataforge.names.startsWith import hep.dataforge.provider.Type -import hep.dataforge.vis.common.asName import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE import info.laht.threekt.core.BufferGeometry