Style viewer and styles resolution fix for proxies.

This commit is contained in:
Alexander Nozik 2020-08-10 22:28:05 +03:00
parent de2ef1dcc5
commit 48705e6670
21 changed files with 265 additions and 205 deletions

View File

@ -7,7 +7,10 @@ import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.bootstrap.* import hep.dataforge.vision.bootstrap.*
import hep.dataforge.vision.gdml.toVision import hep.dataforge.vision.gdml.toVision
import hep.dataforge.vision.react.* import hep.dataforge.vision.react.component
import hep.dataforge.vision.react.flexColumn
import hep.dataforge.vision.react.objectTree
import hep.dataforge.vision.react.state
import hep.dataforge.vision.solid.Solid import hep.dataforge.vision.solid.Solid
import hep.dataforge.vision.solid.SolidGroup import hep.dataforge.vision.solid.SolidGroup
import hep.dataforge.vision.solid.specifications.Camera import hep.dataforge.vision.solid.specifications.Camera
@ -78,7 +81,7 @@ val GDMLApp = component<GDMLAppProps> { props ->
classes.add("p-1") classes.add("p-1")
overflow = Overflow.auto overflow = Overflow.auto
} }
gridColumn(3, maxSize= GridMaxSize.XL, classes = "order-2 order-xl-1") { gridColumn(3, maxSize = GridMaxSize.XL, classes = "order-2 order-xl-1") {
card("Load data") { card("Load data") {
fileDrop("(drag file here)") { files -> fileDrop("(drag file here)") { files ->
val file = files?.get(0) val file = files?.get(0)
@ -102,7 +105,7 @@ val GDMLApp = component<GDMLAppProps> { props ->
} }
} }
gridColumn(6, maxSize= GridMaxSize.XL, classes = "order-1 order-xl-2") { gridColumn(6, maxSize = GridMaxSize.XL, classes = "order-1 order-xl-2") {
//canvas //canvas
(visual as? Solid)?.let { visual3D -> (visual as? Solid)?.let { visual3D ->
child(ThreeCanvasComponent::class) { child(ThreeCanvasComponent::class) {
@ -118,7 +121,7 @@ val GDMLApp = component<GDMLAppProps> { props ->
} }
} }
} }
gridColumn(3, maxSize= GridMaxSize.XL, classes = "order-3") { gridColumn(3, maxSize = GridMaxSize.XL, classes = "order-3") {
container { container {
//settings //settings
canvas?.let { canvas?.let {
@ -127,21 +130,21 @@ val GDMLApp = component<GDMLAppProps> { props ->
} }
} }
} }
container {
namecrumbs(selected, "World") { selected = it }
}
container { container {
//properties //properties
card("Properties") { namecrumbs(selected, "World") { selected = it }
selected.let { selected -> selected.let { selected ->
val selectedObject: Vision? = when { val selectedObject: Vision? = when {
selected == null -> null selected == null -> null
selected.isEmpty() -> visual selected.isEmpty() -> visual
else -> (visual as? VisionGroup)?.get(selected) else -> (visual as? VisionGroup)?.get(selected)
} }
if (selectedObject != null) { if (selectedObject != null) {
configEditor(selectedObject, default = selectedObject.getAllProperties(), key = selected) visionPropertyEditor(
} selectedObject,
default = selectedObject.getAllProperties(),
key = selected
)
} }
} }
} }

View File

@ -1,6 +1,7 @@
package ru.mipt.npm.muon.monitor.server package ru.mipt.npm.muon.monitor.server
import hep.dataforge.meta.DFExperimental
import hep.dataforge.vision.solid.SolidManager import hep.dataforge.vision.solid.SolidManager
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.call import io.ktor.application.call
@ -28,6 +29,7 @@ import java.net.URI
private val generator = Cos2TrackGenerator(JDKRandomGenerator(223)) private val generator = Cos2TrackGenerator(JDKRandomGenerator(223))
@OptIn(DFExperimental::class)
fun Application.module() { fun Application.module() {
val currentDir = File(".").absoluteFile val currentDir = File(".").absoluteFile
environment.log.info("Current directory: $currentDir") environment.log.info("Current directory: $currentDir")

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

2
gradlew vendored
View File

@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath

1
gradlew.bat vendored
View File

@ -84,6 +84,7 @@ set CMD_LINE_ARGS=%*
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

View File

@ -1,8 +1,7 @@
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.js.Application import hep.dataforge.js.Application
import hep.dataforge.js.startApplication import hep.dataforge.js.startApplication
import hep.dataforge.names.Name import hep.dataforge.vision.bootstrap.visionPropertyEditor
import hep.dataforge.vision.bootstrap.visualPropertyEditor
import hep.dataforge.vision.react.objectTree import hep.dataforge.vision.react.objectTree
import hep.dataforge.vision.solid.Point3D import hep.dataforge.vision.solid.Point3D
import hep.dataforge.vision.solid.SolidGroup import hep.dataforge.vision.solid.SolidGroup
@ -41,7 +40,7 @@ private class PlayGroundApp : Application {
threeCanvas(obj) threeCanvas(obj)
} }
div("col-3") { div("col-3") {
visualPropertyEditor(Name.EMPTY, obj) visionPropertyEditor(obj)
} }
} }
} }

View File

@ -1,87 +0,0 @@
package hep.dataforge.vision.bootstrap
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.NameToken
import kotlinx.html.classes
import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event
import react.*
import react.dom.*
interface MetaViewerProps : RProps {
var name: NameToken
var meta: Meta
var descriptor: NodeDescriptor?
}
interface TreeState : RState {
var expanded: Boolean
}
@Deprecated("To be replaced by react functional component")
class MetaViewerComponent : RComponent<MetaViewerProps, TreeState>() {
override fun TreeState.init() {
expanded = false
}
private val onClick: (Event) -> Unit = {
setState {
expanded = !expanded
}
}
override fun RBuilder.render() {
div("d-inline-block text-truncate") {
if (props.meta.items.isNotEmpty()) {
span("tree-caret") {
attrs {
if (state.expanded) {
classes += "tree-caret-down"
}
onClickFunction = onClick
}
}
}
label("tree-label") {
+props.name.toString()
}
ul("tree") {
props.meta.items.forEach { (token, item) ->
//val descriptor = props.
li {
when (item) {
is MetaItem.NodeItem -> {
child(MetaViewerComponent::class) {
attrs {
name = token
meta = item.node
descriptor = props.descriptor?.nodes?.get(token.body)
}
}
}
is MetaItem.ValueItem -> {
div("row") {
div("col") {
label("tree-label") {
+token.toString()
}
}
div("col") {
label {
+item.value.toString()
}
}
}
}
}
}
}
}
}
}
}

View File

@ -1,49 +0,0 @@
package hep.dataforge.vision.bootstrap
import hep.dataforge.meta.Meta
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty
import hep.dataforge.vision.Vision
import hep.dataforge.vision.react.configEditor
import org.w3c.dom.Element
import react.RBuilder
import react.dom.li
import react.dom.nav
import react.dom.ol
import react.dom.render
import kotlin.collections.set
fun RBuilder.visualPropertyEditor(
path: Name,
item: Vision,
descriptor: NodeDescriptor? = item.descriptor,
default: Meta? = null
) {
card("Properties") {
if (!path.isEmpty()) {
nav {
attrs {
attributes["aria-label"] = "breadcrumb"
}
ol("breadcrumb") {
path.tokens.forEach { token ->
li("breadcrumb-item") {
+token.toString()
}
}
}
}
}
configEditor(item, descriptor, default)
}
}
fun Element.visualPropertyEditor(
path: Name,
item: Vision,
descriptor: NodeDescriptor? = item.descriptor,
default: Meta? = null
) = render(this) {
this.visualPropertyEditor(path, item, descriptor, default)
}

View File

@ -0,0 +1,45 @@
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.react.configEditor
import hep.dataforge.vision.react.metaViewer
import hep.dataforge.vision.resolveStyle
import org.w3c.dom.Element
import react.RBuilder
import react.dom.render
fun RBuilder.visionPropertyEditor(
item: Vision,
descriptor: NodeDescriptor? = item.descriptor,
default: Meta? = null,
key: Any? = null
) {
card("Properties") {
configEditor(item, descriptor, default, key)
}
val styles = item.styles
if(styles.isNotEmpty()) {
card("Styles") {
accordion("styles") {
styles.forEach { styleName ->
val style = item.resolveStyle(styleName)
if (style != null) {
entry(styleName) {
metaViewer(style)
}
}
}
}
}
}
}
fun Element.visionPropertyEditor(
item: Vision,
descriptor: NodeDescriptor? = item.descriptor,
default: Meta? = null
) = render(this) {
visionPropertyEditor(item, descriptor, default)
}

View File

@ -113,7 +113,7 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
css { css {
+TreeStyles.tree +TreeStyles.tree
} }
val keys = buildSet<NameToken> { val keys = buildSet {
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach { (descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
add(NameToken(it)) add(NameToken(it))
} }
@ -121,7 +121,7 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
defaultItem?.node?.items?.keys?.let { addAll(it) } defaultItem?.node?.items?.keys?.let { addAll(it) }
} }
keys.forEach { token -> keys.filter { !it.body.startsWith("@") }.forEach { token ->
styledLi { styledLi {
css { css {
+TreeStyles.treeItem +TreeStyles.treeItem
@ -145,7 +145,6 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
styledDiv { styledDiv {
css { css {
+TreeStyles.treeLeaf +TreeStyles.treeLeaf
// justifyContent = JustifyContent.flexEnd
} }
styledDiv { styledDiv {
css { css {
@ -238,13 +237,4 @@ fun RBuilder.configEditor(
descriptor: NodeDescriptor? = obj.descriptor, descriptor: NodeDescriptor? = obj.descriptor,
default: Meta? = null, default: Meta? = null,
key: Any? = null key: Any? = null
) { ) = configEditor(obj.config,descriptor, default, key)
child(ConfigEditor) {
attrs {
this.key = key?.toString() ?: ""
this.root = obj.config
this.descriptor = descriptor
this.default = default
}
}
}

View File

@ -0,0 +1,157 @@
package hep.dataforge.vision.react
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.descriptors.ItemDescriptor
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.defaultItem
import hep.dataforge.meta.descriptors.get
import hep.dataforge.meta.get
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.plus
import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event
import react.*
import react.dom.a
import styled.*
interface MetaViewerProps : RProps {
/**
* Root meta
*/
var root: Meta
/**
* Full path to the displayed node in [root]. Could be empty
*/
var name: Name
/**
* Root descriptor
*/
var descriptor: NodeDescriptor?
}
private val MetaViewerItem: FunctionalComponent<MetaViewerProps> = component { props ->
metaViewerItem(props)
}
private fun RFBuilder.metaViewerItem(props: MetaViewerProps) {
var expanded: Boolean by state { true }
val item = props.root[props.name]
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
val actualItem = item ?: descriptorItem?.defaultItem()
val token = props.name.last()?.toString() ?: "Meta"
val expanderClick: (Event) -> Unit = {
expanded = !expanded
}
when (actualItem) {
is MetaItem.NodeItem -> {
styledDiv {
css {
+TreeStyles.treeLeaf
}
styledSpan {
css {
+TreeStyles.treeCaret
if (expanded) {
+TreeStyles.treeCaredDown
}
}
attrs {
onClickFunction = expanderClick
}
}
styledSpan {
css {
+TreeStyles.treeLabel
if (item == null) {
+TreeStyles.treeLabelInactive
}
}
+token
}
}
if (expanded) {
styledUl {
css {
+TreeStyles.tree
}
val keys = buildSet {
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
add(NameToken(it))
}
actualItem.node.items.keys.let { addAll(it) }
}
keys.filter { !it.body.startsWith("@") }.forEach { token ->
styledLi {
css {
+TreeStyles.treeItem
}
child(MetaViewerItem) {
attrs {
this.key = props.name.toString()
this.root = props.root
this.name = props.name + token
this.descriptor = props.descriptor
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
}
}
is MetaItem.ValueItem -> {
styledDiv {
css {
+TreeStyles.treeLeaf
}
styledDiv {
css {
+TreeStyles.treeLabel
}
styledSpan {
css {
if (item == null) {
+TreeStyles.treeLabelInactive
}
}
+token
}
}
styledDiv {
a {
+actualItem.value.toString()
}
}
}
}
}
}
val MetaViewer = component<MetaViewerProps> { props ->
child(MetaViewerItem) {
attrs {
this.key = ""
this.root = props.root
this.name = Name.EMPTY
this.descriptor = props.descriptor
}
}
}
fun RBuilder.metaViewer(meta: Meta, descriptor: NodeDescriptor? = null, key: Any? = null) {
child(MetaViewer) {
attrs {
this.key = key?.toString() ?: ""
this.root = meta
this.descriptor = descriptor
}
}
}

View File

@ -1,9 +0,0 @@
package hep.dataforge.vision.react
import styled.StyleSheet
class MainStyle: StyleSheet("main", true){
}

View File

@ -5,7 +5,6 @@ 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.asName import hep.dataforge.names.asName
import hep.dataforge.values.asValue
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
@ -90,15 +89,6 @@ class StyleSheet private constructor(private val styleMap: MutableMap<String, Me
} }
/**
* List of names of styles applied to this object. Order matters. Not inherited
*/
var Vision.styles: List<String>
get() = properties?.get(Vision.STYLE_KEY).stringList
set(value) {
setItem(Vision.STYLE_KEY,value.map { it.asValue() }.asValue())
}
/** /**
* 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.
*/ */

View File

@ -5,6 +5,7 @@ import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.values.asValue
import hep.dataforge.vision.Vision.Companion.TYPE import hep.dataforge.vision.Vision.Companion.TYPE
import kotlinx.serialization.PolymorphicSerializer import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
@ -56,13 +57,22 @@ interface Vision : Configurable {
*/ */
fun removeChangeListener(owner: Any?) fun removeChangeListener(owner: Any?)
/**
* List of names of styles applied to this object. Order matters. Not inherited.
*/
var styles: List<String>
get() = properties[STYLE_KEY].stringList
set(value) {
setItem(STYLE_KEY,value.map { it.asValue() }.asValue())
}
companion object { companion object {
const val TYPE = "visual" const val TYPE = "vision"
val STYLE_KEY = "@style".asName() val STYLE_KEY = "@style".asName()
private val VISUAL_OBJECT_SERIALIZER = PolymorphicSerializer(Vision::class) private val VISION_SERIALIZER = PolymorphicSerializer(Vision::class)
fun serializer() = VISUAL_OBJECT_SERIALIZER fun serializer() = VISION_SERIALIZER
} }
} }

View File

@ -84,7 +84,7 @@ class VisionManager(meta: Meta) : AbstractPlugin(meta) {
} }
fun buildVision(meta: Meta): Vision { fun buildVision(meta: Meta): Vision {
val type = meta["type"].string ?: SimpleVisionGroup.serializer().descriptor.serialName val type = meta["type"].string ?: Vision.serializer().descriptor.serialName
val form = forms.values.find { it.name.toString() == type } ?: error("Could not resolve a form for type $type") val form = forms.values.find { it.name.toString() == type } ?: error("Could not resolve a form for type $type")
return form.buildVision(meta, visionSerialModule) return form.buildVision(meta, visionSerialModule)
} }

View File

@ -6,7 +6,6 @@ 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.resolveStyle
import hep.dataforge.vision.styles
import javafx.beans.binding.Binding import javafx.beans.binding.Binding
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.scene.Node import javafx.scene.Node

View File

@ -2,12 +2,10 @@
package hep.dataforge.vision.solid package hep.dataforge.vision.solid
import hep.dataforge.meta.Config import hep.dataforge.meta.*
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.get
import hep.dataforge.names.* import hep.dataforge.names.*
import hep.dataforge.values.asValue
import hep.dataforge.vision.* import hep.dataforge.vision.*
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -89,6 +87,12 @@ class Proxy private constructor(
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) }) //override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
override var styles: List<String>
get() = properties[Vision.STYLE_KEY].stringList + prototype.styles
set(value) {
setItem(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
}
override val descriptor: NodeDescriptor? override val descriptor: NodeDescriptor?
get() = prototype.descriptor get() = prototype.descriptor
@ -147,6 +151,12 @@ class Proxy private constructor(
Laminate(properties, allStyles, prototype.getAllProperties(), parent?.getAllProperties()) Laminate(properties, allStyles, prototype.getAllProperties(), parent?.getAllProperties())
override var styles: List<String>
get() = properties[Vision.STYLE_KEY].stringList + prototype.styles
set(value) {
setItem(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
}
override val descriptor: NodeDescriptor? override val descriptor: NodeDescriptor?
get() = prototype.descriptor get() = prototype.descriptor
} }

View File

@ -123,7 +123,7 @@ internal object PrototypesSerializer : KSerializer<MutableVisionGroup> {
fun Vision.stringify(): String = SolidManager.jsonForSolids.stringify(Vision.serializer(), this) fun Vision.stringify(): String = SolidManager.jsonForSolids.stringify(Vision.serializer(), this)
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
fun Vision.Companion.parseJson(str: String) = SolidManager.jsonForSolids.parse(Vision.serializer(), str).also { fun Vision.Companion.parseJson(str: String) = SolidManager.jsonForSolids.parse(serializer(), str).also {
if(it is VisionGroup){ if(it is VisionGroup){
it.attachChildren() it.attachChildren()
} }

View File

@ -1,13 +1,11 @@
package hep.dataforge.vision.solid package hep.dataforge.vision.solid
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.*
import hep.dataforge.meta.getIndexed
import hep.dataforge.meta.node
import hep.dataforge.meta.toMetaItem
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class ConvexTest { class ConvexTest {
@OptIn(DFExperimental::class)
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
@Test @Test
fun testConvexBuilder() { fun testConvexBuilder() {

View File

@ -3,7 +3,6 @@ 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.styles
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