FXMeta viewer and editor fully functional

This commit is contained in:
Alexander Nozik 2019-06-12 21:13:41 +03:00
parent eba5096129
commit 74e70819ee
8 changed files with 217 additions and 361 deletions

View File

@ -1,7 +1,8 @@
val dataforgeVersion by extra("0.1.3-dev-2")
val dataforgeVersion by extra("0.1.3-dev-5")
allprojects {
repositories {
mavenLocal()
jcenter()
maven("https://kotlin.bintray.com/kotlinx")
maven("http://npm.mipt.ru:8081/artifactory/gradle-dev-local")

View File

@ -9,17 +9,17 @@ kotlin {
val commonMain by getting {
dependencies {
api("hep.dataforge:dataforge-output:$dataforgeVersion")
api("hep.dataforge:dataforge-output-metadata:$dataforgeVersion")
// api("hep.dataforge:dataforge-output-metadata:$dataforgeVersion")
}
}
val jvmMain by getting{
val jvmMain by getting {
dependencies {
api("hep.dataforge:dataforge-output-jvm:$dataforgeVersion")
// api("hep.dataforge:dataforge-output-jvm:$dataforgeVersion")
}
}
val jsMain by getting{
val jsMain by getting {
dependencies {
api("hep.dataforge:dataforge-output-js:$dataforgeVersion")
// api("hep.dataforge:dataforge-output-js:$dataforgeVersion")
}
}
}

View File

@ -0,0 +1,53 @@
package hep.dataforge.vis.fx.demo
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.toConfig
import hep.dataforge.values.ValueType
import hep.dataforge.vis.fx.meta.ConfigEditor
import hep.dataforge.vis.fx.meta.FXMeta
import hep.dataforge.vis.fx.meta.MetaViewer
import javafx.geometry.Orientation
import tornadofx.*
class MetaEditorDemoApp : App(MetaEditorDemo::class)
class MetaEditorDemo : View("Meta editor demo") {
val meta = buildMeta {
"aNode" to {
"innerNode" to {
"innerValue" to true
}
"b" to 22
"c" to "StringValue"
}
}.toConfig()
val descriptor = NodeDescriptor.build {
node("aNode") {
info = "A root demo node"
value("b") {
info = "b number value"
type(ValueType.NUMBER)
}
node("otherNode") {
value("otherValue") {
type(ValueType.BOOLEAN)
default(false)
info = "default value"
}
}
}
}
private val rootNode = FXMeta.root(meta,descriptor)
override val root =
splitpane(Orientation.HORIZONTAL, MetaViewer(rootNode).root, ConfigEditor(rootNode).root)
}
fun main() {
launch<MetaEditorDemoApp>()
}

View File

@ -24,27 +24,30 @@ import tornadofx.*
* @author Alexander Nozik
*/
class ConfigEditor(
val configuration: Config,
title: String = "Configuration editor",
val descriptor: NodeDescriptor? = null
val rootNode: FXMetaNode<Config>,
title: String = "Configuration editor"
) : Fragment(title = title, icon = dfIconView) {
val filter: (FXMeta) -> Boolean = { cfg ->
when (cfg) {
is FXMetaNode<*> -> !(cfg.descriptor?.tags?.contains(NO_CONFIGURATOR_TAG) ?: false)
is FXMetaValue -> !(cfg.descriptor?.tags?.contains(NO_CONFIGURATOR_TAG) ?: false)
}
}
constructor(config: Config, descriptor: NodeDescriptor, title: String = "Configuration editor") :
this(FXMeta.root(config, descriptor = descriptor))
override val root = borderpane {
center = treetableview<FXMeta> {
root = TreeItem(FXMeta.root(configuration, descriptor))
center = treetableview<FXMeta<Config>> {
root = TreeItem(rootNode)
root.isExpanded = true
sortMode = TreeSortMode.ALL_DESCENDANTS
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
column("Name", FXMeta::name) {
populate {
when (val fxMeta = it.value) {
is FXMetaNode -> {
fxMeta.children
}
is FXMetaValue -> null
}
}
column("Name", FXMeta<Config>::name) {
setCellFactory {
object : TextFieldTreeTableCell<FXMeta, NameToken>() {
object : TextFieldTreeTableCell<FXMeta<Config>, NameToken>() {
override fun updateItem(item: NameToken?, empty: Boolean) {
super.updateItem(item, empty)
contextMenu?.items?.removeIf { it.text == "Remove" }
@ -57,7 +60,7 @@ class ConfigEditor(
Color.GRAY
}
})
if (treeTableRow.treeItem.value.hasValue.get()) {
if (treeTableRow.treeItem.value.parent != null && treeTableRow.treeItem.value.hasValue.get()) {
contextmenu {
item("Remove") {
action {
@ -73,15 +76,15 @@ class ConfigEditor(
}
}
column("Value") { param: TreeTableColumn.CellDataFeatures<FXMeta, FXMeta> ->
column("Value") { param: TreeTableColumn.CellDataFeatures<FXMeta<Config>, FXMeta<Config>> ->
param.value.valueProperty()
}.setCellFactory {
ValueCell()
}
column("Description") { param: TreeTableColumn.CellDataFeatures<FXMeta, String> -> param.value.value.descriptionProperty }
.setCellFactory { param: TreeTableColumn<FXMeta, String> ->
val cell = TreeTableCell<FXMeta, String>()
column("Description") { param: TreeTableColumn.CellDataFeatures<FXMeta<Config>, String> -> param.value.value.descriptionProperty }
.setCellFactory { param: TreeTableColumn<FXMeta<Config>, String> ->
val cell = TreeTableCell<FXMeta<Config>, String>()
val text = Text()
cell.graphic = text
cell.prefHeight = Control.USE_COMPUTED_SIZE
@ -112,22 +115,20 @@ class ConfigEditor(
return result.orElse(null)
}
private inner class ValueCell : TreeTableCell<FXMeta, FXMeta?>() {
private inner class ValueCell : TreeTableCell<FXMeta<Config>, FXMeta<Config>?>() {
public override fun updateItem(item: FXMeta?, empty: Boolean) {
public override fun updateItem(item: FXMeta<Config>?, empty: Boolean) {
if (!empty) {
if (item != null) {
when (item) {
is FXMetaValue -> {
is FXMetaValue<Config> -> {
text = null
val chooser = ValueChooser.build(item.valueProperty, item.descriptor) {
item.set(it)
}
graphic = chooser.node
}
is FXMetaNode<*> -> {
item as FXMetaNode<Config>
is FXMetaNode<Config> -> {
text = null
graphic = hbox {
button("node", Glyph("FontAwesome", "PLUS_CIRCLE")) {

View File

@ -1,286 +0,0 @@
//package hep.dataforge.vis.fx.meta
//
//import hep.dataforge.descriptors.NodeDescriptor
//import hep.dataforge.descriptors.ValueDescriptor
//import hep.dataforge.meta.Config
//import hep.dataforge.meta.Meta
//import hep.dataforge.names.Name
//import hep.dataforge.values.Null
//import hep.dataforge.values.Value
//import javafx.beans.binding.StringBinding
//import javafx.beans.property.SimpleObjectProperty
//import javafx.beans.property.SimpleStringProperty
//import javafx.beans.value.ObservableBooleanValue
//import javafx.beans.value.ObservableStringValue
//import javafx.collections.FXCollections
//import javafx.collections.ObservableList
//import javafx.scene.control.TreeItem
//import tornadofx.*
//
//class ConfigTreeItem(configFX: ConfigFX) : TreeItem<ConfigFX>(configFX) {
// init {
// this.children.bind(value.children) { ConfigTreeItem(it) }
// }
//
// override fun isLeaf(): Boolean = value is ConfigFXValue
//}
//
//
///**
// * A node, containing relative representation of configuration node and description
// * Created by darksnake on 01-May-17.
// */
//sealed class ConfigFX(name: String) {
//
// val nameProperty = SimpleStringProperty(name)
// val name by nameProperty
//
// val parentProperty = SimpleObjectProperty<ConfigFXNode>()
// val parent by parentProperty
//
// abstract val hasValueProperty: ObservableBooleanValue
// //abstract val hasDefaultProperty: ObservableBooleanValue
//
// abstract val descriptionProperty: ObservableStringValue
//
// abstract val children: ObservableList<ConfigFX>
//
// /**
// * remove itself from parent
// */
// abstract fun remove()
//
// abstract fun invalidate()
//}
//
//
///**
// * Tree item for node
// * Created by darksnake on 30-Apr-17.
// */
//open class ConfigFXNode(name: String, parent: ConfigFXNode? = null) : ConfigFX(name) {
//
// final override val hasValueProperty = parentProperty.booleanBinding(nameProperty) {
// it?.config?.hasMeta(this.name) ?: false
// }
//
// /**
// * A descriptor that could be manually set to the node
// */
// val descriptorProperty = SimpleObjectProperty<NodeDescriptor?>()
//
// /**
// * Actual descriptor which holds value inferred from parrent
// */
// private val actualDescriptor = objectBinding(descriptorProperty, parentProperty, nameProperty) {
// value ?: parent?.descriptor?.getNodeDescriptor(name)
// }
//
// val descriptor: NodeDescriptor? by actualDescriptor
//
// val configProperty = SimpleObjectProperty<Config?>()
//
// private val actualConfig = objectBinding(configProperty, parentProperty, nameProperty) {
// value ?: parent?.config?.getMetaList(name)?.firstOrNull()
// }
//
// val config: Config? by actualConfig
//
// final override val descriptionProperty: ObservableStringValue = stringBinding(actualDescriptor) {
// value?.info ?: ""
// }
//
// override val children: ObservableList<ConfigFX> = FXCollections.observableArrayList<ConfigFX>()
//
// init {
// parentProperty.set(parent)
// hasValueProperty.onChange {
// parent?.hasValueProperty?.invalidate()
// }
// invalidate()
// }
//
// /**
// * Get existing configuration node or create and attach new one
// *
// * @return
// */
// private fun getOrBuildNode(): Config {
// return config ?: if (parent == null) {
// throw RuntimeException("The configuration for root node is note defined")
// } else {
// parent.getOrBuildNode().requestNode(name)
// }
// }
//
// fun addValue(name: String) {
// getOrBuildNode().setValue(name, Null)
// }
//
// fun setValue(name: String, value: Value) {
// getOrBuildNode().setValue(name, value)
// }
//
// fun removeValue(valueName: String) {
// config?.removeValue(valueName)
// children.removeIf { it.name == name }
// }
//
// fun addNode(name: String) {
// getOrBuildNode().requestNode(name)
// }
//
// fun removeNode(name: String) {
// config?.removeNode(name)
// }
//
// override fun remove() {
// //FIXME does not work on multinodes
// parent?.removeNode(name)
// invalidate()
// }
//
// final override fun invalidate() {
// actualDescriptor.invalidate()
// actualConfig.invalidate()
// hasValueProperty.invalidate()
//
// val nodeNames = ArrayList<String>()
// val valueNames = ArrayList<String>()
//
// config?.apply {
// nodeNames.addAll(this.nodeNames.toList())
// valueNames.addAll(this.valueNames.toList())
// }
//
// descriptor?.apply {
// nodeNames.addAll(childrenDescriptors().keys)
// valueNames.addAll(valueDescriptors().keys)
// }
//
// //removing old values
// children.removeIf { !(valueNames.contains(it.name) || nodeNames.contains(it.name)) }
//
// valueNames.forEach { name ->
// children.find { it.name == name }?.invalidate().orElse {
// children.add(ConfigFXValue(name, this))
// }
// }
//
// nodeNames.forEach { name ->
// children.find { it.name == name }?.invalidate().orElse {
// children.add(ConfigFXNode(name, this))
// }
// }
// children.sortBy { it.name }
// }
//
// fun updateValue(path: Name, value: Value?) {
// when {
// path.length == 0 -> kotlin.error("Path never could be empty when updating value")
// path.length == 1 -> {
// val hasDescriptor = descriptor?.getValueDescriptor(path) != null
// if (value == null && !hasDescriptor) {
// //removing the value if it is present
// children.removeIf { it.name == path.unescaped }
// } else {
// //invalidating value if it is present
// children.find { it is ConfigFXValue && it.name == path.unescaped }?.invalidate().orElse {
// //adding new node otherwise
// children.add(ConfigFXValue(path.unescaped, this))
// }
// }
// }
// path.length > 1 -> children.filterIsInstance<ConfigFXNode>().find { it.name == path.first.unescaped }?.updateValue(
// path.cutFirst(),
// value
// )
// }
// }
//
// fun updateNode(path: Name, list: List<Meta>) {
// when {
// path.isEmpty() -> invalidate()
// path.length == 1 -> {
// val hasDescriptor = descriptor?.getNodeDescriptor(path.unescaped) != null
// if (list.isEmpty() && !hasDescriptor) {
// children.removeIf { it.name == path.unescaped }
// } else {
// children.find { it is ConfigFXNode && it.name == path.unescaped }?.invalidate().orElse {
// children.add(ConfigFXNode(path.unescaped, this))
// }
// }
// }
// else -> children.filterIsInstance<ConfigFXNode>().find { it.name == path.first.toString() }?.updateNode(
// path.cutFirst(),
// list
// )
// }
// }
//}
//
//class ConfigFXRoot(rootConfig: Config, rootDescriptor: NodeDescriptor? = null) : ConfigFXNode(rootConfig.name),
// ConfigChangeListener {
//
// init {
// configProperty.set(rootConfig)
// descriptorProperty.set(rootDescriptor)
// rootConfig.addListener(this)
// invalidate()
// }
//
// override fun notifyValueChanged(name: Name, oldItem: Value?, newItem: Value?) {
// updateValue(name, newItem)
// }
//
// override fun notifyNodeChanged(nodeName: Name, oldItem: List<Meta>, newItem: List<Meta>) {
// updateNode(nodeName, newItem)
// }
//}
//
//
///**
// * Created by darksnake on 01-May-17.
// */
//class ConfigFXValue(name: String, parent: ConfigFXNode) : ConfigFX(name) {
//
// init {
// parentProperty.set(parent)
// }
//
// override val hasValueProperty = parentProperty.booleanBinding(nameProperty) {
// it?.config?.hasValue(this.name) ?: false
// }
//
//
// override val children: ObservableList<ConfigFX> = FXCollections.emptyObservableList()
//
// val descriptor: ValueDescriptor? = parent.descriptor?.values[name]
//
// override val descriptionProperty: ObservableStringValue = object : StringBinding() {
// override fun computeValue(): String {
// return descriptor?.info ?: ""
// }
// }
//
// val valueProperty = parentProperty.objectBinding(nameProperty) {
// parent.config?.optValue(name).nullable ?: descriptor?.default
// }
//
// var value: Value
// set(value) {
// parent?.setValue(name, value)
// }
// get() = valueProperty.value ?: Value.NULL
//
//
// override fun remove() {
// parent?.removeValue(name)
// invalidate()
// }
//
// override fun invalidate() {
// valueProperty.invalidate()
// hasValueProperty.invalidate()
// }
//}

View File

@ -1,9 +1,13 @@
package hep.dataforge.vis.fx.meta
import hep.dataforge.descriptors.ItemDescriptor
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.descriptors.ValueDescriptor
import hep.dataforge.meta.*
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
import hep.dataforge.values.Null
import hep.dataforge.values.Value
import javafx.beans.binding.ListBinding
import javafx.beans.binding.ObjectBinding
@ -13,67 +17,129 @@ import javafx.beans.value.ObservableStringValue
import javafx.collections.ObservableList
import tornadofx.*
sealed class FXMeta {
/**
* A display for meta and descriptor
*/
sealed class FXMeta<M : MetaNode<M>> : Comparable<FXMeta<*>> {
abstract val name: NameToken
abstract val parent: FXMetaNode<*>?
abstract val parent: FXMetaNode<M>?
abstract val descriptionProperty: ObservableStringValue
abstract val descriptor: ItemDescriptor?
abstract val hasValue: ObservableBooleanValue
companion object {
fun <M : MetaNode<M>> root(node: M, descriptor: NodeDescriptor? = null): FXMetaNode<M> =
FXMetaNode(NameToken("root"), null, node, descriptor)
override fun compareTo(other: FXMeta<*>): Int {
return if (this.hasValue.get() == other.hasValue.get()) {
this.name.toString().compareTo(other.name.toString())
} else {
this.hasValue.get().compareTo(other.hasValue.get())
}
}
fun root(node: Meta, descriptor: NodeDescriptor? = null): FXMetaNode<SealedMeta> =
root(node.seal(), descriptor)
companion object {
fun <M : MetaNode<M>> root(
node: M,
descriptor: NodeDescriptor? = null,
rootName: String = "root"
): FXMetaNode<M> =
FXMetaNode(NameToken(rootName), null, node, descriptor)
fun root(node: Meta, descriptor: NodeDescriptor? = null, rootName: String = "root"): FXMetaNode<SealedMeta> =
root(node.seal(), descriptor, rootName)
}
}
class FXMetaNode<M : MetaNode<M>>(
override val name: NameToken,
override val parent: FXMetaNode<M>?,
node: M? = null,
descriptor: NodeDescriptor? = null
) : FXMeta() {
nodeValue: M? = null,
descriptorValue: NodeDescriptor? = null
) : FXMeta<M>() {
/**
* A descriptor that could be manually set to the node
*/
val descriptorProperty = SimpleObjectProperty(descriptor)
private val innerDescriptorProperty = SimpleObjectProperty(descriptorValue)
/**
* Actual descriptor which holds value inferred from parrent
*/
private val actualDescriptorProperty = objectBinding(descriptorProperty) {
val descriptorProperty = objectBinding(innerDescriptorProperty) {
value ?: parent?.descriptor?.nodes?.get(this@FXMetaNode.name.body)
}
val descriptor: NodeDescriptor? by actualDescriptorProperty
override val descriptor: NodeDescriptor? by descriptorProperty
private val innerNodeProperty = SimpleObjectProperty(node)
private val innerNodeProperty = SimpleObjectProperty(nodeValue)
val nodeProperty: ObjectBinding<M?> = objectBinding(innerNodeProperty) {
value ?: parent?.node?.get(this@FXMetaNode.name.asName()).node
value ?: parent?.node?.get(this@FXMetaNode.name).node
}
val node: M? by nodeProperty
override val descriptionProperty = descriptorProperty.stringBinding { it?.info ?: "" }
override val descriptionProperty = innerDescriptorProperty.stringBinding { it?.info ?: "" }
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
val children = object : ListBinding<FXMeta>() {
override fun computeValue(): ObservableList<FXMeta> {
TODO()
private val filter: (FXMeta<M>) -> Boolean = { cfg ->
!(cfg.descriptor?.tags?.contains(ConfigEditor.NO_CONFIGURATOR_TAG) ?: false)
}
val children = object : ListBinding<FXMeta<M>>() {
init {
bind(nodeProperty, descriptorProperty)
val listener: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit = { name, _, _ ->
if (name.length == 1) invalidate()
}
(node as? MutableMeta<*>)?.onChange(this, listener)
nodeProperty.addListener { _, oldValue, newValue ->
if (newValue == null) {
(oldValue as? MutableMeta<*>)?.removeListener(this)
}
if (newValue is MutableMeta<*>) {
newValue.onChange(this, listener)
}
}
}
override fun computeValue(): ObservableList<FXMeta<M>> {
val nodeKeys = node?.items?.keys?.toSet() ?: emptySet()
val descriptorKeys = descriptor?.items?.keys?.map { NameToken(it) } ?: emptyList()
val keys: Set<NameToken> = nodeKeys + descriptorKeys
val items = keys.map { token ->
val actualItem = node?.items?.get(token)
val actualDescriptor = descriptor?.items?.get(token.body)
if (actualItem is MetaItem.NodeItem || actualDescriptor is NodeDescriptor) {
FXMetaNode(token, this@FXMetaNode)
} else {
FXMetaValue(token, this@FXMetaNode)
}
}
return items.filter(filter).observable()
}
}
init {
if (parent != null) {
parent.descriptorProperty.onChange { descriptorProperty.invalidate() }
parent.nodeProperty.onChange { nodeProperty.invalidate() }
}
}
}
class FXMetaValue(
class FXMetaValue<M : MetaNode<M>>(
override val name: NameToken,
override val parent: FXMetaNode<*>,
value: Value? = null
) : FXMeta() {
override val parent: FXMetaNode<M>
) : FXMeta<M>() {
val descriptorProperty = parent.descriptorProperty.objectBinding {
it?.values?.get(name.body)
@ -82,15 +148,15 @@ class FXMetaValue(
/**
* A descriptor that could be manually set to the node
*/
val descriptor by descriptorProperty
override val descriptor: ValueDescriptor? by descriptorProperty
private val innerValueProperty = SimpleObjectProperty(value)
//private val innerValueProperty = SimpleObjectProperty(value)
val valueProperty = descriptorProperty.objectBinding { descriptor ->
parent.node[name].value ?: descriptor?.default
}
override val hasValue: ObservableBooleanValue = valueProperty.booleanBinding { it != null }
override val hasValue: ObservableBooleanValue = parent.nodeProperty.booleanBinding { it[name] != null }
val value by valueProperty
@ -102,18 +168,36 @@ fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
children.invalidate()
}
fun FXMeta.remove() {
(parent?.node as? MutableMeta<*>)?.remove(name.asName())
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken): M {
this.setNode(token.asName(), EmptyMeta)
//FIXME possible concurrency bug
return get(token).node!!
}
fun <M : MutableMeta<M>> FXMetaNode<M>.addValue(key: String){
TODO()
fun <M : MutableMeta<M>> FXMetaNode<out M>.getOrCreateNode(): M {
val node = node
return when {
node != null -> node
parent != null -> parent.getOrCreateNode().createEmptyNode(this.name).also {
parent.children.invalidate()
}
else -> kotlin.error("Orphan empty node is not allowed")
}
}
fun <M : MutableMeta<M>> FXMetaNode<M>.addNode(key: String){
TODO()
fun <M : MutableMeta<M>> FXMeta<M>.remove() {
parent?.node?.remove(name.asName())
}
fun FXMetaValue.set(value: Value?){
TODO()
fun <M : MutableMeta<M>> FXMetaNode<M>.addValue(key: String) {
getOrCreateNode()[key] = Null
}
fun <M : MutableMeta<M>> FXMetaNode<M>.addNode(key: String) {
getOrCreateNode()[key] = EmptyMeta
}
fun <M : MutableMeta<M>> FXMetaValue<M>.set(value: Value?) {
parent.getOrCreateNode()[this.name] = value
}

View File

@ -17,7 +17,6 @@
package hep.dataforge.vis.fx.meta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.seal
import hep.dataforge.vis.fx.dfIconView
import javafx.beans.property.SimpleStringProperty
import javafx.scene.control.TreeItem
@ -25,15 +24,17 @@ import javafx.scene.control.TreeSortMode
import javafx.scene.control.TreeTableView
import tornadofx.*
open class MetaViewer(val meta: Meta, title: String = "Meta viewer") : Fragment(title, dfIconView) {
class MetaViewer(val rootNode: FXMetaNode<*>, title: String = "Meta viewer") : Fragment(title, dfIconView) {
constructor(meta: Meta, title: String = "Meta viewer"): this(FXMeta.root(meta))
override val root = borderpane {
center {
treetableview<FXMeta> {
treetableview<FXMeta<*>> {
isShowRoot = false
root = TreeItem(FXMeta.root(meta.seal()))
root = TreeItem(rootNode)
populate {
when (val fxMeta = it.value) {
is FXMetaNode<*> -> {
is FXMetaNode -> {
fxMeta.children
}
is FXMetaValue -> null
@ -42,11 +43,11 @@ open class MetaViewer(val meta: Meta, title: String = "Meta viewer") : Fragment(
root.isExpanded = true
sortMode = TreeSortMode.ALL_DESCENDANTS
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
column("Name", FXMeta::name)
column<FXMeta, String>("Value"){
when(val item = it.value.value){
is FXMetaValue -> item.valueProperty.stringBinding{it?.string?: ""}
is FXMetaNode<*> -> SimpleStringProperty("[node]")
column("Name", FXMeta<*>::name)
column<FXMeta<*>, String>("Value") { cellDataFeatures ->
when (val item = cellDataFeatures.value.value) {
is FXMetaValue -> item.valueProperty.stringBinding { it?.string ?: "" }
is FXMetaNode -> SimpleStringProperty("[node]")
}
}
}

View File

@ -31,7 +31,9 @@ include(
//if(file("../dataforge-core").exists()) {
// includeBuild("../dataforge-core"){
// dependencySubstitution {
// substitute(module("hep.dataforge:dataforge-output")).with(project(":dataforge-output"))
// //substitute(module("hep.dataforge:dataforge-output")).with(project(":dataforge-output"))
// substitute(module("hep.dataforge:dataforge-output-jvm")).with(project(":dataforge-output"))
// substitute(module("hep.dataforge:dataforge-output-js")).with(project(":dataforge-output"))
// }
// }
//}