Stylesheets moved to properties

This commit is contained in:
Alexander Nozik 2020-12-10 12:38:35 +03:00
parent 6a742658af
commit a38d70bade
14 changed files with 62 additions and 114 deletions

View File

@ -10,6 +10,7 @@
- Point3D and Point2D are made separate classes instead of expect/actual (to split up different engines. - Point3D and Point2D are made separate classes instead of expect/actual (to split up different engines.
- JavaFX support moved to a separate module - JavaFX support moved to a separate module
- Threejs support moved to a separate module - Threejs support moved to a separate module
- \[Breaking change!\] Stylesheets are moved into properties under `@stylesheet` key
### Deprecated ### Deprecated

View File

@ -3,7 +3,7 @@ plugins {
} }
val dataforgeVersion by extra("0.2.1-dev-4") val dataforgeVersion by extra("0.2.1-dev-4")
val ktorVersion by extra("1.4.2") val ktorVersion by extra("1.4.3")
val htmlVersion by extra("0.7.2") val htmlVersion by extra("0.7.2")
val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.20") val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.20")
val fxVersion by extra("14") val fxVersion by extra("14")

View File

@ -3,9 +3,9 @@ package hep.dataforge.vision.bootstrap
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.getStyle
import hep.dataforge.vision.react.configEditor import hep.dataforge.vision.react.configEditor
import hep.dataforge.vision.react.metaViewer import hep.dataforge.vision.react.metaViewer
import hep.dataforge.vision.resolveStyle
import org.w3c.dom.Element import org.w3c.dom.Element
import react.RBuilder import react.RBuilder
import react.dom.render import react.dom.render
@ -24,7 +24,7 @@ public fun RBuilder.visionPropertyEditor(
card("Styles") { card("Styles") {
accordion("styles") { accordion("styles") {
styles.forEach { styleName -> styles.forEach { styleName ->
val style = item.resolveStyle(styleName) val style = item.getStyle(styleName)
if (style != null) { if (style != null) {
entry(styleName) { entry(styleName) {
metaViewer(style) metaViewer(style)

View File

@ -1,62 +1,30 @@
@file:UseSerializers(MetaSerializer::class)
package hep.dataforge.vision package hep.dataforge.vision
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName import hep.dataforge.names.asName
import kotlinx.serialization.KSerializer import hep.dataforge.names.plus
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/** /**
* A container for styles * A container for styles
*/ */
@Serializable(StyleSheet.Companion::class) public inline class StyleSheet(private val owner: VisionGroup) {
public class StyleSheet private constructor(private val styleMap: MutableMap<String, Meta>) {
@Transient
internal var owner: Vision? = null
public constructor(owner: Vision) : this(LinkedHashMap()) { private val styleNode get() = owner.properties?.get(STYLESHEET_KEY).node
this.owner = owner
}
public val items: Map<String, Meta> get() = styleMap public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY }
public operator fun get(key: String): Meta? = owner.getStyle(key)
private fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) {
if (styles.contains(key)) {
//TODO optimize set concatenation
val tokens: Collection<Name> =
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
.map { it.asName() }
tokens.forEach { parent?.propertyChanged(it) }
}
if (this is VisionGroup) {
for (obj in this) {
obj.styleChanged(key, oldStyle, newStyle)
}
}
}
public operator fun get(key: String): Meta? {
return styleMap[key] ?: owner?.parent?.styleSheet?.get(key)
}
/** /**
* Define a style without notifying owner * Define a style without notifying owner
*/ */
public fun define(key: String, style: Meta?) { public fun define(key: String, style: Meta?) {
if (style == null) { if (style == null) {
styleMap.remove(key) styleNode?.remove(key)
} else { } else {
styleMap[key] = style owner.config[STYLESHEET_KEY + key] = style
} }
} }
@ -64,11 +32,13 @@ public class StyleSheet private constructor(private val styleMap: MutableMap<Str
* Set or clear the style * Set or clear the style
*/ */
public operator fun set(key: String, style: Meta?) { public operator fun set(key: String, style: Meta?) {
val oldStyle = styleMap[key] val oldStyle = get(key)
define(key, style) define(key, style)
owner?.styleChanged(key, oldStyle, style) owner.styleChanged(key, oldStyle, style)
} }
public inline operator fun invoke(block: StyleSheet.() -> Unit): Unit = this.block()
/** /**
* Create and set a style * Create and set a style
*/ */
@ -77,33 +47,33 @@ public class StyleSheet private constructor(private val styleMap: MutableMap<Str
set(key, newStyle.seal()) set(key, newStyle.seal())
} }
public fun update(key: String, meta: Meta) { public companion object {
val existing = get(key) public val STYLESHEET_KEY: Name = "@stylesheet".asName()
set(key, existing?.edit { this.update(meta) } ?: meta)
} }
}
public fun update(other: StyleSheet) { internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) {
other.items.forEach { (key, value) -> if (styles.contains(key)) {
update(key, value) //TODO optimize set concatenation
} val tokens: Collection<Name> =
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
.map { it.asName() }
tokens.forEach { parent?.propertyChanged(it) }
} }
if (this is VisionGroup) {
public companion object : KSerializer<StyleSheet> { for (obj in this) {
private val mapSerializer = MapSerializer(String.serializer(), MetaSerializer) obj.styleChanged(key, oldStyle, newStyle)
override val descriptor: SerialDescriptor get() = mapSerializer.descriptor
override fun deserialize(decoder: Decoder): StyleSheet {
val map = mapSerializer.deserialize(decoder)
return StyleSheet(map as? MutableMap<String, Meta> ?: LinkedHashMap(map))
}
override fun serialize(encoder: Encoder, value: StyleSheet) {
mapSerializer.serialize(encoder, value.items)
} }
} }
} }
/**
* A stylesheet for this group and its descendants. Stylesheet is not applied directly,
* but instead is just a repository for named configurations.
*/
public val VisionGroup.styleSheet: StyleSheet get() = StyleSheet(this)
/** /**
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment. * Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
*/ */
@ -111,12 +81,19 @@ public fun Vision.useStyle(name: String) {
styles = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name styles = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name
} }
/**
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision].
*/
public tailrec fun Vision.getStyle(name: String): Meta? =
properties?.get(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name)
/** /**
* Resolve an item in all style layers * Resolve an item in all style layers
*/ */
public fun Vision.getStyleItems(name: Name): Sequence<MetaItem<*>> { public fun Vision.getStyleItems(name: Name): Sequence<MetaItem<*>> {
return styles.asSequence().map { return styles.asSequence().map {
resolveStyle(it) getStyle(it)
}.map { }.map {
it[name] it[name]
}.filterNotNull() }.filterNotNull()
@ -125,4 +102,4 @@ public fun Vision.getStyleItems(name: Name): Sequence<MetaItem<*>> {
/** /**
* Collect all styles for this object in a single laminate * Collect all styles for this object in a single laminate
*/ */
public val Vision.allStyles: Laminate get() = Laminate(styles.mapNotNull(::resolveStyle)) public val Vision.allStyles: Laminate get() = Laminate(styles.mapNotNull(::getStyle))

View File

@ -98,12 +98,6 @@ public fun Vision.setProperty(key: String, value: Any?) {
config[key] = value config[key] = value
} }
/**
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision].
*/
public tailrec fun Vision.resolveStyle(name: String): Meta? =
(this as? VisionGroup)?.styleSheet?.get(name) ?: parent?.resolveStyle(name)
/** /**
* Control visibility of the element * Control visibility of the element
*/ */

View File

@ -34,7 +34,7 @@ public open class VisionBase : Vision {
override val descriptor: NodeDescriptor? get() = null override val descriptor: NodeDescriptor? get() = null
protected fun updateStyles(names: List<String>) { protected fun updateStyles(names: List<String>) {
names.mapNotNull { resolveStyle(it) }.asSequence() names.mapNotNull { getStyle(it) }.asSequence()
.flatMap { it.items.asSequence() } .flatMap { it.items.asSequence() }
.distinctBy { it.key } .distinctBy { it.key }
.forEach { .forEach {

View File

@ -3,7 +3,7 @@ package hep.dataforge.vision
import hep.dataforge.names.* import hep.dataforge.names.*
import hep.dataforge.provider.Provider import hep.dataforge.provider.Provider
public interface VisionContainer<out V: Vision>{ public interface VisionContainer<out V : Vision> {
public operator fun get(name: Name): V? public operator fun get(name: Name): V?
} }
@ -18,12 +18,6 @@ public interface VisionGroup : Provider, Vision, VisionContainer<Vision> {
override val defaultTarget: String get() = Vision.TYPE override val defaultTarget: String get() = Vision.TYPE
/**
* A stylesheet for this group and its descendants. Stylesheet is not applied directly,
* but instead is just a repository for named configurations.
*/
public val styleSheet: StyleSheet?
/** /**
* A map of direct children for specific target * A map of direct children for specific target
* (currently "visual" or "style") * (currently "visual" or "style")
@ -38,7 +32,7 @@ public interface VisionGroup : Provider, Vision, VisionContainer<Vision> {
} }
res.entries res.entries
}.associate { it.toPair() } }.associate { it.toPair() }
STYLE_TARGET -> styleSheet?.items?.mapKeys { it.key.toName() } ?: emptyMap() STYLE_TARGET -> styleSheet.items?.mapKeys { it.key.asName() } ?: emptyMap()
else -> emptyMap() else -> emptyMap()
} }
@ -54,7 +48,6 @@ public interface VisionGroup : Provider, Vision, VisionContainer<Vision> {
* A fix for serialization bug that writes all proper parents inside the tree after deserialization * A fix for serialization bug that writes all proper parents inside the tree after deserialization
*/ */
public fun attachChildren() { public fun attachChildren() {
styleSheet?.owner = this
children.values.forEach { children.values.forEach {
it.parent = this it.parent = this
(it as? VisionGroup)?.attachChildren() (it as? VisionGroup)?.attachChildren()
@ -73,7 +66,7 @@ public operator fun VisionGroup.iterator(): Iterator<Vision> = children.values.i
public val VisionGroup.isEmpty: Boolean get() = this.children.isEmpty() public val VisionGroup.isEmpty: Boolean get() = this.children.isEmpty()
public interface VisionContainerBuilder<in V: Vision>{ public interface VisionContainerBuilder<in V : Vision> {
public operator fun set(name: Name, child: V?) public operator fun set(name: Name, child: V?)
} }
@ -97,9 +90,11 @@ public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision
// public operator fun set(name: Name, child: Vision?) // public operator fun set(name: Name, child: Vision?)
} }
public operator fun <V: Vision> VisionContainer<V>.get(str: String): V? = get(str.toName()) public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName())
public operator fun <V: Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit = set(token.asName(), child) public operator fun <V : Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit =
public operator fun <V: Vision> VisionContainerBuilder<V>.set(key: String, child: V?): Unit = set(key.toName(), child) set(token.asName(), child)
public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String, child: V?): Unit = set(key.toName(), child)
public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null } public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null }

View File

@ -26,19 +26,6 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
*/ */
override val children: Map<NameToken, Vision> get() = childrenInternal override val children: Map<NameToken, Vision> get() = childrenInternal
final override var styleSheet: StyleSheet? = null
private set
/**
* Update or create stylesheet
*/
public open fun styleSheet(block: StyleSheet.() -> Unit) {
if (styleSheet == null) {
styleSheet = StyleSheet(this@VisionGroupBase)
}
styleSheet!!.block()
}
override fun propertyChanged(name: Name) { override fun propertyChanged(name: Name) {
super.propertyChanged(name) super.propertyChanged(name)
for (obj in this) { for (obj in this) {

View File

@ -5,7 +5,7 @@ import hep.dataforge.meta.Meta
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.update import hep.dataforge.meta.update
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.resolveStyle import hep.dataforge.vision.getStyle
import hep.dataforge.vision.setProperty import hep.dataforge.vision.setProperty
import javafx.beans.binding.Binding import javafx.beans.binding.Binding
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
@ -55,7 +55,7 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() {
private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding() { private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding() {
VBox().apply { VBox().apply {
item?.styles?.forEach { styleName -> item?.styles?.forEach { styleName ->
val styleMeta = item?.resolveStyle(styleName) val styleMeta = item?.getStyle(styleName)
if (styleMeta != null) { if (styleMeta != null) {
titledpane(styleName, node = MetaViewer(styleMeta).root) titledpane(styleName, node = MetaViewer(styleMeta).root)
} }

View File

@ -10,6 +10,7 @@ import hep.dataforge.names.toName
import hep.dataforge.vision.set import hep.dataforge.vision.set
import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.*
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vision.styleSheet
import hep.dataforge.vision.useStyle import hep.dataforge.vision.useStyle
import kscience.gdml.* import kscience.gdml.*
import kotlin.math.cos import kotlin.math.cos

View File

@ -3,7 +3,10 @@ package hep.dataforge.vision.solid
import hep.dataforge.meta.update import hep.dataforge.meta.update
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.vision.* import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionContainerBuilder
import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.set
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -28,9 +31,6 @@ public class Composite(
override val children: Map<NameToken, Vision> override val children: Map<NameToken, Vision>
get() = mapOf(NameToken("first") to first, NameToken("second") to second) get() = mapOf(NameToken("first") to first, NameToken("second") to second)
override val styleSheet: StyleSheet?
get() = null
} }
public inline fun VisionContainerBuilder<Solid>.composite( public inline fun VisionContainerBuilder<Solid>.composite(

View File

@ -103,10 +103,6 @@ internal class Prototypes(
children: Map<NameToken, Vision> = emptyMap(), children: Map<NameToken, Vision> = emptyMap(),
) : VisionGroupBase(), PrototypeHolder { ) : VisionGroupBase(), PrototypeHolder {
override fun styleSheet(block: StyleSheet.() -> Unit) {
error("Can't define stylesheet for prototypes block")
}
init { init {
this.childrenInternal.putAll(children) this.childrenInternal.putAll(children)
} }

View File

@ -58,8 +58,6 @@ public class SolidReference(
get() = (parent as? SolidGroup)?.getPrototype(templateName) get() = (parent as? SolidGroup)?.getPrototype(templateName)
?: error("Prototype with name $templateName not found in $parent") ?: error("Prototype with name $templateName not found in $parent")
override val styleSheet: StyleSheet get() = parent?.styleSheet ?: StyleSheet(this)
@Transient @Transient
private val propertyCache: HashMap<Name, Config> = HashMap() private val propertyCache: HashMap<Name, Config> = HashMap()
@ -90,8 +88,6 @@ public class SolidReference(
override val prototype: Solid get() = prototypeFor(name) override val prototype: Solid get() = prototypeFor(name)
override val styleSheet: StyleSheet get() = this@SolidReference.styleSheet
override val children: Map<NameToken, Vision> override val children: Map<NameToken, Vision>
get() = (prototype as? VisionGroup)?.children get() = (prototype as? VisionGroup)?.children
?.filter { !it.key.toString().startsWith("@") } ?.filter { !it.key.toString().startsWith("@") }

View File

@ -3,6 +3,7 @@ package hep.dataforge.vision.solid
import hep.dataforge.meta.int import hep.dataforge.meta.int
import hep.dataforge.meta.set import hep.dataforge.meta.set
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.vision.styleSheet
import hep.dataforge.vision.useStyle import hep.dataforge.vision.useStyle
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals