Adjust property editors

This commit is contained in:
Alexander Nozik 2021-08-07 11:27:57 +03:00
parent 787c841a51
commit 8d21d3cd74
101 changed files with 1410 additions and 1246 deletions

View File

@ -1,18 +1,13 @@
plugins {
id("ru.mipt.npm.gradle.project")
//Override kotlin version
// val kotlinVersion = "1.5.20-RC"
// kotlin("multiplatform") version(kotlinVersion) apply false
// kotlin("jvm") version(kotlinVersion) apply false
// kotlin("js") version(kotlinVersion) apply false
}
val dataforgeVersion by extra("0.5.0-dev-2")
val dataforgeVersion by extra("0.5.0-dev-9")
val fxVersion by extra("11")
allprojects {
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven("https://repo.kotlin.link")

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.gdml
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.asValue
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.setProperty
@ -24,7 +24,7 @@ class GDMLVisionTest {
@Test
fun testPrototypeProperty() {
val vision = GdmlShowCase.cubes().toVision()
val child = vision["composite-000.segment-0".toName()]
val child = vision[Name.of("composite-000","segment-0")]
assertNotNull(child)
child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)

View File

@ -8,8 +8,8 @@ import space.kscience.dataforge.context.fetch
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.describedProperties
import space.kscience.visionforge.editor.VisualObjectEditorFragment
import space.kscience.visionforge.editor.VisualObjectTreeFragment
import space.kscience.visionforge.editor.VisionEditorFragment
import space.kscience.visionforge.editor.VisionTreeFragment
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.solid.FX3DPlugin
import space.kscience.visionforge.solid.FXCanvas3D
@ -29,18 +29,17 @@ class GDMLView : View() {
private val visionManager = context.fetch(VisionManager)
private val canvas = FXCanvas3D(fx3d)
private val treeFragment = VisualObjectTreeFragment().apply {
private val treeFragment = VisionTreeFragment().apply {
this.itemProperty.bind(canvas.rootObjectProperty)
}
private val propertyEditor = VisualObjectEditorFragment {
private val propertyEditor = VisionEditorFragment {
it.describedProperties
}.apply {
descriptorProperty.set(SolidMaterial.descriptor)
itemProperty.bind(treeFragment.selectedProperty)
}
override val root: Parent = borderpane {
top {
buttonbar {

View File

@ -6,6 +6,7 @@ import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.removeAll
import space.kscience.visionforge.root
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.*
import kotlin.math.PI

View File

@ -67,6 +67,7 @@ kotlin {
val jvmMain by getting{
dependencies {
api(project(":visionforge-server"))
api(project(":visionforge-markdown"))
api("ch.qos.logback:logback-classic:1.2.3")
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
}

View File

@ -0,0 +1,91 @@
package space.kscience.visionforge.examples
import kotlinx.html.div
import kotlinx.html.h1
import space.kscience.dataforge.context.Context
import space.kscience.plotly.layout
import space.kscience.plotly.models.ScatterMode
import space.kscience.plotly.models.TextPosition
import space.kscience.plotly.scatter
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.markup.markdown
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.plotly
import space.kscience.visionforge.solid.*
fun main() {
val context = Context {
plugin(Solids)
plugin(PlotlyPlugin)
}
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
markdown {
//language=markdown
"""
# Section
**TBD**
## Subsection
""".trimIndent()
}
div {
h1 { +"Canvas" }
vision("canvas") {
solid {
box(100, 100, 100)
material {
emissiveColor("red")
}
}
}
}
vision("plot") {
plotly {
scatter {
x(1, 2, 3, 4)
y(10, 15, 13, 17)
mode = ScatterMode.markers
name = "Team A"
text("A-1", "A-2", "A-3", "A-4", "A-5")
textposition = TextPosition.`top center`
textfont {
family = "Raleway, sans-serif"
}
marker { size = 12 }
}
scatter {
x(2, 3, 4, 5)
y(10, 15, 13, 17)
mode = ScatterMode.lines
name = "Team B"
text("B-a", "B-b", "B-c", "B-d", "B-e")
textposition = TextPosition.`bottom center`
textfont {
family = "Times New Roman"
}
marker { size = 12 }
}
layout {
title = "Data Labels Hover"
xaxis {
range(0.75..5.25)
}
legend {
y = 0.5
font {
family = "Arial, sans-serif"
size = 20
color("grey")
}
}
}
}
}
}
}

View File

@ -26,7 +26,7 @@ public fun Context.makeVisionFile(
content: VisionTagConsumer<*>.() -> Unit
): Unit {
val actualPath = page(title, content = content).makeFile(path) { actualPath ->
mapOf("threeJs" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath))
mapOf("playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath))
}
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
}

View File

@ -2,9 +2,7 @@ package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.box
import space.kscience.visionforge.solid.solid
import space.kscience.visionforge.solid.*
fun main() {
val context = Context {
@ -15,6 +13,9 @@ fun main() {
vision("canvas") {
solid {
box(100, 100, 100)
material {
emissiveColor("red")
}
}
}
}

View File

@ -8,7 +8,7 @@ import kotlinx.coroutines.launch
import kotlinx.html.div
import kotlinx.html.h1
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.three.server.*
import space.kscience.visionforge.visionManager
@ -42,7 +42,7 @@ fun main() {
val randomLayer = Random.nextInt(1, 11)
val randomI = Random.nextInt(1, 4)
val randomJ = Random.nextInt(1, 4)
val target = "layer[$randomLayer].segment[$randomI,$randomJ]".toName()
val target = Name.parse("layer[$randomLayer].segment[$randomI,$randomJ]")
val targetVision = sat[target] as Solid
targetVision.color("red")
delay(1000)

View File

@ -1,7 +1,8 @@
package space.kscience.visionforge
package space.kscience.visionforge.solid.demo
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
public interface VisionLayout<in V: Vision> {
public fun render(name: Name, vision: V, meta: Meta = Meta.EMPTY)

View File

@ -6,9 +6,8 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Colors
import space.kscience.visionforge.VisionLayout
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.visible
@ -23,7 +22,7 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
"title" put title
}
val vision = SolidGroup(block)
render(name.toName(), vision)
render(Name.parse(name), vision)
}
val canvasOptions = Canvas3DOptions {

View File

@ -15,7 +15,6 @@ import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.VisionLayout
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin

View File

@ -7,7 +7,6 @@ import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.VisionLayout
import space.kscience.visionforge.solid.FX3DPlugin
import space.kscience.visionforge.solid.FXCanvas3D
import space.kscience.visionforge.solid.Solid

View File

@ -2,12 +2,14 @@ package space.kscience.visionforge.demo
import javafx.geometry.Orientation
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.asConfig
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.node
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.editor.ConfigEditor
import space.kscience.visionforge.editor.FXMeta
import space.kscience.visionforge.editor.FXMetaModel
import space.kscience.visionforge.editor.MetaViewer
import space.kscience.visionforge.editor.MutableMetaEditor
import tornadofx.*
@ -15,7 +17,7 @@ class MetaEditorDemoApp : App(MetaEditorDemo::class)
class MetaEditorDemo : View("Meta editor demo") {
val meta = Meta {
val meta = MutableMeta {
"aNode" put {
"innerNode" put {
"innerValue" put true
@ -23,18 +25,16 @@ class MetaEditorDemo : View("Meta editor demo") {
"b" put 223
"c" put "StringValue"
}
}.asConfig()
}
val descriptor = NodeDescriptor {
val descriptor = MetaDescriptor {
node("aNode") {
info = "A root demo node"
value("b") {
value("b", ValueType.NUMBER) {
info = "b number value"
type(ValueType.NUMBER)
}
node("otherNode") {
value("otherValue") {
type(ValueType.BOOLEAN)
value("otherValue", ValueType.BOOLEAN) {
default(false)
info = "default value"
}
@ -46,12 +46,13 @@ class MetaEditorDemo : View("Meta editor demo") {
}
}
private val rootNode = FXMeta.root(meta, descriptor)
private val rootNode = FXMetaModel.root(meta, descriptor)
override val root =
splitpane(Orientation.HORIZONTAL, MetaViewer(rootNode).root, ConfigEditor(
rootNode
).root)
override val root = splitpane(
Orientation.HORIZONTAL,
MetaViewer(rootNode as Meta).root,
MutableMetaEditor(rootNode as FXMetaModel<MutableMeta>).root
)
}
fun main() {

View File

@ -1,5 +1,5 @@
## Library design
The central point of the library design is the `Vision` interface. The `Vision` stores an optional reference to its parent and is able to store a number of mutable or read-only properties. Each property is represented by its `Name`, and a `MetaItem` value-tree, both following DataForge library specification (discussed in the [Appendix](appendix.md)). The `Vision` objects are organized in a tree using `VisionGroup` as nodes. `VisionGroup` additionally to all `Vision` properties holds a `children` container that holds named references to its direct children `Vision`s. Thus, `Vision`s form a doubly linked tree (a parent stores references to all its children and children store a reference to the parent).
The central point of the library design is the `Vision` interface. The `Vision` stores an optional reference to its parent and is able to store a number of mutable or read-only properties. Each property is represented by its `Name`, and a `Meta` value-tree, both following DataForge library specification (discussed in the [Appendix](appendix.md)). The `Vision` objects are organized in a tree using `VisionGroup` as nodes. `VisionGroup` additionally to all `Vision` properties holds a `children` container that holds named references to its direct children `Vision`s. Thus, `Vision`s form a doubly linked tree (a parent stores references to all its children and children store a reference to the parent).
An important concept using in the VisionForge is the property layering mechanism. It means that if the property with a given name is not found in the `Vision` it is requested from, it could be requested from the parent `Vision`, form the style declaration, the prototype for the vision or any other place defined by the component author. For example, let's take a `color` attribute used in 3D visualization. When one draws a group of objects, he usually wants to make the color of all objects in the group to be defined by a single handle in the group common ancestor. So when the parent color changes, all children color must follow suite, but we also want to change children color individually without changing the parent. In this case two property layers are defined:

View File

@ -3,7 +3,7 @@
interface Vision{
val parent: Vision?
fun getProperty(name): MetaItem?
fun getProperty(name): Meta?
fun setProperty(name, value)
}

View File

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

View File

@ -1,6 +1,6 @@
pluginManagement {
val toolsVersion = "0.10.0"
val toolsVersion = "0.10.2"
repositories {
maven("https://repo.kotlin.link")

View File

@ -11,5 +11,5 @@ dependencies {
implementation(npm("file-saver", "2.0.2"))
implementation(npm("bootstrap","4.6.0"))
implementation(npm("jquery","3.5.1"))
implementation(npm("popper.js","1.16.1"))
implementation(npm("@popperjs/core","2.9.3"))
}

View File

@ -12,6 +12,7 @@ import org.w3c.files.BlobPropertyBag
import react.*
import react.dom.attrs
import react.dom.button
import space.kscience.dataforge.meta.descriptors.defaultNode
import space.kscience.dataforge.meta.withDefault
import space.kscience.visionforge.Vision
import space.kscience.visionforge.react.flexColumn
@ -43,7 +44,7 @@ public external interface CanvasControlsProps : RProps {
public var vision: Vision?
}
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
public val CanvasControls: FunctionComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
flexColumn {
flexRow {
css {
@ -66,7 +67,7 @@ public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functional
}
propertyEditor(
ownProperties = props.canvasOptions,
allProperties = props.canvasOptions.withDefault(Canvas3DOptions.descriptor.defaultMeta),
allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
descriptor = Canvas3DOptions.descriptor,
expanded = false
)

View File

@ -18,7 +18,7 @@ public external class TabProps : RProps {
}
@JsExport
public val Tab: FunctionalComponent<TabProps> = functionalComponent { props ->
public val Tab: FunctionComponent<TabProps> = functionalComponent { props ->
props.children()
}
@ -27,7 +27,7 @@ public external class TabPaneProps : RProps {
}
@JsExport
public val TabPane: FunctionalComponent<TabPaneProps> = functionalComponent("TabPane") { props ->
public val TabPane: FunctionComponent<TabPaneProps> = functionalComponent("TabPane") { props ->
var activeTab: String? by useState(props.activeTab)
val children: Array<out ReactElement?> = Children.map(props.children) {

View File

@ -3,22 +3,25 @@ package space.kscience.visionforge.bootstrap
import org.w3c.dom.Element
import react.RBuilder
import react.dom.render
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.visionforge.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.Vision
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.meta
import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
public fun RBuilder.visionPropertyEditor(
vision: Vision,
descriptor: NodeDescriptor? = vision.descriptor,
descriptor: MetaDescriptor? = vision.descriptor,
key: Any? = null,
) {
card("Properties") {
propertyEditor(
ownProperties = vision.ownProperties,
allProperties = vision.allProperties(),
ownProperties = vision.meta(false,false,false),
allProperties = vision.meta(),
updateFlow = vision.propertyChanges,
descriptor = descriptor,
key = key
@ -47,7 +50,7 @@ public fun RBuilder.visionPropertyEditor(
public fun Element.visionPropertyEditor(
item: Vision,
descriptor: NodeDescriptor? = item.descriptor,
descriptor: MetaDescriptor? = item.descriptor,
): Unit = render(this) {
visionPropertyEditor(item, descriptor = descriptor)
}

View File

@ -8,12 +8,11 @@ import react.*
import react.dom.a
import react.dom.attrs
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaItemNode
import space.kscience.dataforge.meta.MetaItemValue
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.defaultNode
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.isLeaf
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.lastOrNull
@ -41,18 +40,19 @@ public external interface MetaViewerProps : RProps {
/**
* Root descriptor
*/
public var descriptor: NodeDescriptor?
public var descriptor: MetaDescriptor?
}
private val MetaViewerItem: FunctionalComponent<MetaViewerProps> = functionalComponent("MetaViewerItem") { props ->
private val MetaViewerItem: FunctionComponent<MetaViewerProps> = functionalComponent("MetaViewerItem") { props ->
metaViewerItem(props)
}
private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
var expanded: Boolean by useState { true }
val item = props.root[props.name]
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
val actualItem = item ?: descriptorItem?.defaultValue
val descriptorItem: MetaDescriptor? = props.descriptor?.get(props.name)
val actualValue = item?.value ?: descriptorItem?.defaultValue
val actualMeta = item ?: descriptorItem?.defaultNode
val token = props.name.lastOrNull()?.toString() ?: props.rootName ?: ""
@ -60,12 +60,11 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
expanded = !expanded
}
when (actualItem) {
is MetaItemNode -> {
flexRow {
css {
alignItems = Align.center
}
if (actualMeta?.isLeaf == false) {
styledSpan {
css {
+TreeStyles.treeCaret
@ -77,6 +76,8 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
onClickFunction = expanderClick
}
}
}
styledSpan {
css {
+TreeStyles.treeLabel
@ -86,6 +87,11 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
}
+token
}
styledDiv {
a {
+actualValue.toString()
}
}
}
if (expanded) {
flexColumn {
@ -93,10 +99,10 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
+TreeStyles.tree
}
val keys = buildSet {
(descriptorItem as? NodeDescriptor)?.items?.keys?.forEach {
descriptorItem?.children?.keys?.forEach {
add(NameToken(it))
}
actualItem.node.items.keys.let { addAll(it) }
actualMeta!!.items.keys.let { addAll(it) }
}
keys.filter { !it.body.startsWith("@") }.forEach { token ->
@ -117,33 +123,12 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
}
}
}
}
is MetaItemValue -> {
flexRow {
css {
alignItems = Align.center
}
styledSpan {
css {
+TreeStyles.treeLabel
if (item == null) {
+TreeStyles.treeLabelInactive
}
}
+token
}
styledDiv {
a {
+actualItem.value.toString()
}
}
}
}
}
}
@JsExport
public val MetaViewer: FunctionalComponent<MetaViewerProps> =
public val MetaViewer: FunctionComponent<MetaViewerProps> =
functionalComponent<MetaViewerProps>("MetaViewer") { props ->
child(MetaViewerItem) {
attrs {
@ -155,7 +140,7 @@ public val MetaViewer: FunctionalComponent<MetaViewerProps> =
}
}
public fun RBuilder.metaViewer(meta: Meta, descriptor: NodeDescriptor? = null, key: Any? = null) {
public fun RBuilder.metaViewer(meta: Meta, descriptor: MetaDescriptor? = null, key: Any? = null) {
child(MetaViewer) {
attrs {
this.key = key?.toString() ?: ""

View File

@ -5,20 +5,20 @@ import org.w3c.dom.HTMLOptionElement
import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.asList
import org.w3c.dom.events.Event
import react.FunctionalComponent
import react.FunctionComponent
import react.dom.attrs
import react.dom.option
import react.dom.select
import react.functionalComponent
import react.useState
import space.kscience.dataforge.meta.value
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.string
@JsExport
public val MultiSelectChooser: FunctionalComponent<ValueChooserProps> =
public val MultiSelectChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("MultiSelectChooser") { props ->
var selectedItems by useState { props.item.value?.list ?: emptyList() }
var selectedItems by useState { props.item?.value?.list ?: emptyList() }
val onChange: (Event) -> Unit = { event: Event ->
val newSelected = (event.target as HTMLSelectElement).selectedOptions.asList()

View File

@ -18,14 +18,10 @@ import react.*
import react.dom.attrs
import react.dom.render
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRequirement
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.lastOrNull
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.visionforge.hidden
import styled.css
@ -38,12 +34,12 @@ public external interface PropertyEditorProps : RProps {
/**
* Root config object - always non null
*/
public var ownProperties: MutableItemProvider
public var ownProperties: MutableMetaProvider
/**
* Provide default item (greyed out if used)
*/
public var allProperties: ItemProvider?
public var allProperties: MetaProvider?
/**
* Full path to the displayed node in [ownProperties]. Could be empty
@ -53,7 +49,7 @@ public external interface PropertyEditorProps : RProps {
/**
* Root descriptor
*/
public var descriptor: NodeDescriptor?
public var descriptor: MetaDescriptor?
/**
* A coroutine scope for updates
@ -71,21 +67,21 @@ public external interface PropertyEditorProps : RProps {
public var expanded: Boolean?
}
private val PropertyEditorItem: FunctionalComponent<PropertyEditorProps> =
private val PropertyEditorItem: FunctionComponent<PropertyEditorProps> =
functionalComponent("ConfigEditorItem") { props ->
propertyEditorItem(props)
}
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
var expanded: Boolean by useState { props.expanded ?: true }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
var ownProperty: MetaItem? by useState { props.ownProperties.getItem(props.name) }
val actualItem: MetaItem? = props.allProperties?.getItem(props.name)
val descriptor: MetaDescriptor? = props.descriptor?.get(props.name)
var ownProperty: Meta? by useState { props.ownProperties.getMeta(props.name) }
val actualMeta = props.allProperties?.getMeta(props.name)
val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() {
ownProperty = props.ownProperties.getItem(props.name)
ownProperty = props.ownProperties.getMeta(props.name)
}
if (props.updateFlow != null) {
@ -109,7 +105,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
if (it == null) {
props.ownProperties.remove(props.name)
} else {
props.ownProperties[props.name] = it
props.ownProperties.setValue(props.name, it)
}
update()
}
@ -119,19 +115,20 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
update()
}
if (actualItem is MetaItemNode) {
val keys = buildSet {
(descriptorItem as? NodeDescriptor)?.items?.filterNot {
descriptor?.children?.filterNot {
it.key.startsWith("@") || it.value.hidden
}?.forEach {
add(NameToken(it.key))
}
ownProperty?.node?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
}
// Do not show nodes without visible children
if (keys.isEmpty()) return
flexRow {
css {
alignItems = Align.center
}
if(keys.isNotEmpty()) {
styledSpan {
css {
+TreeStyles.treeCaret
@ -143,44 +140,6 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
onClickFunction = expanderClick
}
}
styledSpan {
css {
+TreeStyles.treeLabel
if (ownProperty == null) {
+TreeStyles.treeLabelInactive
}
}
+token
}
}
if (expanded) {
flexColumn {
css {
+TreeStyles.tree
}
keys.forEach { token ->
styledDiv {
css {
+TreeStyles.treeItem
}
child(PropertyEditorItem) {
attrs {
this.key = props.name.toString()
this.ownProperties = props.ownProperties
this.allProperties = props.allProperties
this.name = props.name + token
this.descriptor = props.descriptor
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
}
} else {
flexRow {
css {
alignItems = Align.center
}
styledSpan {
css {
@ -191,7 +150,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
}
+token
}
if(!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) {
styledDiv {
css {
//+TreeStyles.resizeableInput
@ -200,8 +159,8 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
}
valueChooser(
props.name,
actualItem,
descriptorItem as? ValueDescriptor,
actualMeta,
descriptor,
valueChanged
)
}
@ -232,14 +191,38 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
}
}
}
}
}
if (expanded) {
flexColumn {
css {
+TreeStyles.tree
}
keys.forEach { token ->
styledDiv {
css {
+TreeStyles.treeItem
}
child(PropertyEditorItem) {
attrs {
this.key = props.name.toString()
this.ownProperties = props.ownProperties
this.allProperties = props.allProperties
this.name = props.name + token
this.descriptor = props.descriptor
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
}
}
}
}
@JsExport
public val PropertyEditor: FunctionalComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
public val PropertyEditor: FunctionComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
child(PropertyEditorItem) {
attrs {
this.key = ""
@ -254,10 +237,10 @@ public val PropertyEditor: FunctionalComponent<PropertyEditorProps> = functional
}
public fun RBuilder.propertyEditor(
ownProperties: MutableItemProvider,
allProperties: ItemProvider? = ownProperties,
ownProperties: MutableMetaProvider,
allProperties: MetaProvider? = ownProperties,
updateFlow: Flow<Name>? = null,
descriptor: NodeDescriptor? = null,
descriptor: MetaDescriptor? = null,
scope: CoroutineScope? = null,
key: Any? = null,
expanded: Boolean? = null
@ -276,8 +259,8 @@ public fun RBuilder.propertyEditor(
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun Config.flowUpdates(): Flow<Name> = callbackFlow {
onChange(this) { name, _, _ ->
private fun ObservableMutableMeta.flowUpdates(): Flow<Name> = callbackFlow {
onChange(this) { name ->
launch {
send(name)
}
@ -289,16 +272,16 @@ private fun Config.flowUpdates(): Flow<Name> = callbackFlow {
public fun RBuilder.configEditor(
config: Config,
default: ItemProvider? = null,
descriptor: NodeDescriptor? = null,
config: ObservableMutableMeta,
default: MetaProvider? = null,
descriptor: MetaDescriptor? = null,
key: Any? = null,
scope: CoroutineScope? = null,
): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key)
public fun Element.configEditor(
config: Config,
descriptor: NodeDescriptor? = null,
config: ObservableMutableMeta,
descriptor: MetaDescriptor? = null,
default: Meta? = null,
key: Any? = null,
scope: CoroutineScope? = null,

View File

@ -6,7 +6,7 @@ import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event
import react.FunctionalComponent
import react.FunctionComponent
import react.dom.attrs
import react.functionalComponent
import react.useState
@ -18,7 +18,7 @@ import styled.css
import styled.styledInput
@JsExport
public val RangeValueChooser: FunctionalComponent<ValueChooserProps> =
public val RangeValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("RangeValueChooser") { props ->
var innerValue by useState(props.item.double)
var rangeDisabled: Boolean by useState(props.item == null)

View File

@ -21,7 +21,7 @@ public external interface ThreeCanvasProps : RProps {
public var selected: Name?
}
public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functionalComponent(
public val ThreeCanvasComponent: FunctionComponent<ThreeCanvasProps> = functionalComponent(
"ThreeCanvasComponent"
) { props ->
val elementRef = useRef<Element>(null)

View File

@ -107,7 +107,7 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
}
@JsExport
public val ObjectTree: FunctionalComponent<ObjectTreeProps> = functionalComponent("ObjectTree") { props ->
public val ObjectTree: FunctionComponent<ObjectTreeProps> = functionalComponent("ObjectTree") { props ->
visionTree(props)
}

View File

@ -13,8 +13,12 @@ import org.w3c.dom.events.Event
import react.*
import react.dom.attrs
import react.dom.option
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.*
import space.kscience.visionforge.Colors
@ -24,14 +28,15 @@ import styled.styledInput
import styled.styledSelect
public external interface ValueChooserProps : RProps {
public var item: MetaItem?
public var descriptor: ValueDescriptor?
public var item: Meta?
public var descriptor: MetaDescriptor?
//public var nullable: Boolean?
public var valueChanged: ((Value?) -> Unit)?
}
@JsExport
public val StringValueChooser: FunctionalComponent<ValueChooserProps> =
public val StringValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("StringValueChooser") { props ->
var value by useState(props.item.string ?: "")
val keyDown: (Event) -> Unit = { event ->
@ -58,7 +63,7 @@ public val StringValueChooser: FunctionalComponent<ValueChooserProps> =
}
@JsExport
public val BooleanValueChooser: FunctionalComponent<ValueChooserProps> =
public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("BooleanValueChooser") { props ->
val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).checked
@ -77,7 +82,7 @@ public val BooleanValueChooser: FunctionalComponent<ValueChooserProps> =
}
@JsExport
public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("NumberValueChooser") { props ->
var innerValue by useState(props.item.string ?: "")
val keyDown: (Event) -> Unit = { event ->
@ -116,7 +121,7 @@ public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
}
@JsExport
public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("ComboValueChooser") { props ->
var selected by useState(props.item.string ?: "")
val handleChange: (Event) -> Unit = {
@ -141,10 +146,10 @@ public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
}
@JsExport
public val ColorValueChooser: FunctionalComponent<ValueChooserProps> =
public val ColorValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("ColorValueChooser") { props ->
var value by useState(
props.item.value?.let { value ->
props.item?.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string
} ?: "#000000"
@ -166,11 +171,11 @@ public val ColorValueChooser: FunctionalComponent<ValueChooserProps> =
}
@JsExport
public val ValueChooser: FunctionalComponent<ValueChooserProps> = functionalComponent("ValueChooser") { props ->
public val ValueChooser: FunctionComponent<ValueChooserProps> = functionalComponent("ValueChooser") { props ->
val rawInput by useState(false)
val descriptor = props.descriptor
val type = descriptor?.type?.firstOrNull()
val type = descriptor?.valueTypes?.firstOrNull()
when {
rawInput -> child(StringValueChooser, props)
@ -187,8 +192,8 @@ public val ValueChooser: FunctionalComponent<ValueChooserProps> = functionalComp
internal fun RBuilder.valueChooser(
name: Name,
item: MetaItem?,
descriptor: ValueDescriptor? = null,
item: Meta?,
descriptor: MetaDescriptor? = null,
callback: (Value?) -> Unit,
) {
child(ValueChooser) {

View File

@ -17,14 +17,13 @@ kotlin{
dependencies{
api(project(":ui:react"))
//TODO replace by kotlin-wrappers
api("ru.mipt.npm:ring-ui:0.1.0")
//api("ru.mipt.npm:ring-ui:0.1.0")
api("org.jetbrains.kotlin-wrappers:kotlin-ring-ui")
implementation(npm("@jetbrains/icons", "3.14.1"))
implementation(npm("@jetbrains/ring-ui", "4.0.7"))
implementation(npm("core-js","3.12.1"))
implementation(npm("file-saver", "2.0.2"))
compileOnly(npm("url-loader","4.1.1"))
compileOnly(npm("postcss-loader","5.2.0"))
compileOnly(npm("source-map-loader","2.0.1"))
// compileOnly(npm("url-loader","4.1.1"))
// compileOnly(npm("postcss-loader","5.2.0"))
}

View File

@ -14,8 +14,7 @@ import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.length
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.allProperties
import space.kscience.visionforge.ownProperties
import space.kscience.visionforge.meta
import space.kscience.visionforge.react.ThreeCanvasComponent
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
@ -70,7 +69,7 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): ReactElement
}
@JsExport
public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsProps> =
public val ThreeCanvasWithControls: FunctionComponent<ThreeCanvasWithControlsProps> =
functionalComponent("ThreeViewWithControls") { props ->
var selected by useState { props.selected }
@ -135,8 +134,8 @@ public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsP
}
IslandContent {
propertyEditor(
ownProperties = vision.ownProperties,
allProperties = vision.allProperties(),
ownProperties = vision.meta(false, false, false),
allProperties = vision.meta(),
updateFlow = vision.propertyChanges,
descriptor = vision.descriptor,
key = selected

View File

@ -7,16 +7,19 @@ import react.dom.render
import ringui.Island
import ringui.SmartTabs
import ringui.Tab
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.visionforge.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.Vision
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.meta
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
public fun RBuilder.ringPropertyEditor(
vision: Vision,
descriptor: NodeDescriptor? = vision.descriptor,
descriptor: MetaDescriptor? = vision.descriptor,
key: Any? = null,
) {
val styles = if (vision is SolidReference) {
@ -28,8 +31,8 @@ public fun RBuilder.ringPropertyEditor(
flexColumn {
Island("Properties") {
propertyEditor(
ownProperties = vision.ownProperties,
allProperties = vision.allProperties(),
ownProperties = vision.meta(false,false,false),
allProperties = vision.meta(),
updateFlow = vision.propertyChanges,
descriptor = descriptor,
key = key
@ -69,7 +72,7 @@ public fun RBuilder.ringPropertyEditor(
public fun Element.ringPropertyEditor(
item: Vision,
descriptor: NodeDescriptor? = item.descriptor,
descriptor: MetaDescriptor? = item.descriptor,
): Unit = render(this) {
ringPropertyEditor(item, descriptor = descriptor)
}

View File

@ -15,6 +15,7 @@ import react.dom.button
import ringui.Island
import ringui.SmartTabs
import ringui.Tab
import space.kscience.dataforge.meta.descriptors.defaultNode
import space.kscience.dataforge.meta.withDefault
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
@ -49,7 +50,7 @@ internal external interface CanvasControlsProps : RProps {
public var vision: Vision?
}
internal val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
internal val CanvasControls: FunctionComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
flexColumn {
flexRow {
css {
@ -73,7 +74,7 @@ internal val CanvasControls: FunctionalComponent<CanvasControlsProps> = function
}
propertyEditor(
ownProperties = props.options,
allProperties = props.options.withDefault(Canvas3DOptions.descriptor.defaultMeta),
allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
descriptor = Canvas3DOptions.descriptor,
expanded = false
)
@ -91,7 +92,7 @@ public external interface ThreeControlsProps : RProps {
}
@JsExport
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props ->
public val ThreeControls: FunctionComponent<ThreeControlsProps> = functionalComponent { props ->
SmartTabs("Tree") {
props.vision?.let {
Tab("Tree") {

View File

@ -1,6 +1,8 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.string
@ -190,25 +192,18 @@ public object Colors {
/**
* Convert color represented as Meta to string of format #rrggbb
*/
fun fromMeta(item: MetaItem): String {
return when (item) {
is MetaItemNode -> {
val node = item.node
rgbToString(
node[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
node[GREEN_KEY].number?.toByte()?.toUByte() ?: 0u,
node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
)
}
is MetaItemValue -> {
if (item.value.type == ValueType.NUMBER) {
rgbToString(item.value.int)
fun fromMeta(meta: Meta): String = meta.value?.let { value ->
//if value is present, use it
if (value.type == ValueType.NUMBER) {
rgbToString(value.int)
} else {
item.value.string
}
}
}
value.string
}
} ?: rgbToString(
meta[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
meta[GREEN_KEY].number?.toByte()?.toUByte() ?: 0u,
meta[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
)
/**
* Convert Int color to string of format #rrggbb

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.Specification
import kotlin.properties.ReadOnlyProperty
@ -27,7 +27,7 @@ public fun Vision.useStyle(reference: StyleReference) {
@VisionBuilder
public fun VisionGroup.style(
styleKey: String? = null,
builder: MetaBuilder.() -> Unit,
builder: MutableMeta.() -> Unit,
): ReadOnlyProperty<Any?, StyleReference> = ReadOnlyProperty { _, property ->
val styleName = styleKey ?: property.name
styleSheet.define(styleName, Meta(builder))

View File

@ -5,6 +5,7 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.asValue
import kotlin.jvm.JvmInline
/**
@ -13,9 +14,9 @@ import kotlin.jvm.JvmInline
@JvmInline
public value class StyleSheet(private val owner: VisionGroup) {
private val styleNode get() = owner.ownProperties[STYLESHEET_KEY].node
private val styleNode: Meta? get() = owner.getOwnProperty(STYLESHEET_KEY)
public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY }
public val items: Map<NameToken, Meta>? get() = styleNode?.items
public operator fun get(key: String): Meta? = owner.getStyle(key)
@ -23,7 +24,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
* Define a style without notifying owner
*/
public fun define(key: String, style: Meta?) {
owner.setProperty(STYLESHEET_KEY + key, style)
owner.setPropertyNode(STYLESHEET_KEY + key, style)
}
/**
@ -40,7 +41,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
/**
* Create and set a style
*/
public operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
public operator fun set(key: String, builder: MutableMeta.() -> Unit) {
val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder)
set(key, newStyle.seal())
}
@ -70,9 +71,9 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
* List of names of styles applied to this object. Order matters. Not inherited.
*/
public var Vision.styles: List<String>
get() = ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList()
get() = getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList()
set(value) {
setProperty(Vision.STYLE_KEY, value)
setPropertyValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
}
/**
@ -85,7 +86,7 @@ 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.
*/
public fun Vision.useStyle(name: String) {
styles = (ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name
styles = (getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
}
@ -93,13 +94,13 @@ public fun Vision.useStyle(name: String) {
* 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? =
ownProperties[StyleSheet.STYLESHEET_KEY + name].node ?: parent?.getStyle(name)
getOwnProperty(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name)
/**
* Resolve an item in all style layers
*/
public fun Vision.getStyleItems(name: Name): List<MetaItem> = styles.mapNotNull {
getStyle(it)[name]
public fun Vision.getStyleItems(name: Name): List<Meta> = styles.mapNotNull {
getStyle(it)?.get(name)
}

View File

@ -4,14 +4,15 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.update
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.values.Value
import space.kscience.visionforge.Vision.Companion.TYPE
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@ -45,13 +46,13 @@ public interface Vision : Described, CoroutineScope {
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): MetaItem?
): Meta?
/**
* Get an intrinsic property of this Vision excluding any inheritance or defaults. In most cases should be the same as
* `getProperty(name, false, false, false`.
*/
public fun getOwnProperty(name: Name): MetaItem? = getProperty(
public fun getOwnProperty(name: Name): Meta? = getProperty(
name,
inherit = false,
includeStyles = false,
@ -59,9 +60,14 @@ public interface Vision : Described, CoroutineScope {
)
/**
* Set the property value
* Replace the property node. If [node] is null remove node and its descendants
*/
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
public fun setPropertyNode(name: Name, node: Meta?, notify: Boolean = true)
/**
* Set a value of specific property node
*/
public fun setPropertyValue(name: Name, value: Value?, notify: Boolean = true)
/**
* Flow of property invalidation events. It does not contain property values after invalidation since it is not clear
@ -77,9 +83,9 @@ public interface Vision : Described, CoroutineScope {
/**
* Update this vision using a dif represented by [VisionChange].
*/
public fun update(change: VisionChange)
public fun change(change: VisionChange)
override val descriptor: NodeDescriptor?
override val descriptor: MetaDescriptor?
public companion object {
public const val TYPE: String = "vision"
@ -89,11 +95,6 @@ public interface Vision : Described, CoroutineScope {
}
}
/**
* Root property node
*/
public val Vision.meta: Meta get() = ownProperties[Name.EMPTY]?.node ?: Meta.EMPTY
/**
* Subscribe on property updates. The subscription is bound to the given [scope] and canceled when the scope is canceled
*/
@ -105,31 +106,26 @@ public fun Vision.onPropertyChange(scope: CoroutineScope, callback: suspend (Nam
/**
* Own properties, excluding inheritance, styles and descriptor
*/
public val Vision.ownProperties: MutableItemProvider
get() = object : MutableItemProvider {
override fun getItem(name: Name): MetaItem? = getOwnProperty(name)
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
}
/**
* Convenient accessor for all properties of a vision.
* @param inherit - inherit property value from the parent by default. If null, inheritance is inferred from descriptor
*/
public fun Vision.allProperties(
inherit: Boolean? = null,
includeStyles: Boolean? = null,
public fun Vision.meta(
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): MutableItemProvider = object : MutableItemProvider {
override fun getItem(name: Name): MetaItem? = getProperty(
name,
inherit = inherit ?: (descriptor?.get(name)?.inherited == true),
includeStyles = includeStyles ?: (descriptor?.get(name)?.usesStyles != false),
includeDefaults = includeDefaults
)
): MutableMeta = VisionProperties(this, Name.EMPTY, inherit, includeStyles, includeDefaults)
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
public fun Vision.configure(target: Name = Name.EMPTY, block: MutableMeta.() -> Unit): Unit {
VisionProperties(this, target).apply(block)
}
public fun Vision.configure(meta: Meta) {
configure(Name.EMPTY) {
update(meta)
}
}
public fun Vision.configure(block: MutableMeta.() -> Unit): Unit = configure(Meta(block))
public fun Vision.getOwnProperty(key: String): Meta? = getOwnProperty(Name.parse(key))
/**
* Get [Vision] property using key as a String
*/
@ -138,18 +134,20 @@ public fun Vision.getProperty(
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): MetaItem? = getProperty(key.toName(), inherit, includeStyles, includeDefaults)
): Meta? = getProperty(Name.parse(key), inherit, includeStyles, includeDefaults)
/**
* A convenience method to pair [getProperty]
* A convenience method to set property node or value. If Item is null, then node is removed, not a value
*/
public fun Vision.setProperty(key: Name, item: Any?) {
setProperty(key, MetaItem.of(item))
public fun Vision.setProperty(name: Name, item: Any?) {
when (item) {
null -> setPropertyNode(name, null)
is Meta -> setPropertyNode(name, item)
is Value -> setPropertyValue(name, item)
}
}
/**
* A convenience method to pair [getProperty]
*/
public fun Vision.setProperty(key: String, item: Any?) {
setProperty(key.toName(), MetaItem.of(item))
public fun Vision.setPropertyNode(key: String, item: Any?) {
setProperty(Name.parse(key), item)
}

View File

@ -7,21 +7,18 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.defaultNode
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
import kotlin.jvm.Synchronized
internal data class PropertyListener(
val owner: Any? = null,
val action: (name: Name) -> Unit,
)
/**
* A full base implementation for a [Vision]
* @param properties Object own properties excluding styles and inheritance
@ -29,14 +26,17 @@ internal data class PropertyListener(
@Serializable
@SerialName("vision")
public open class VisionBase(
override @Transient var parent: VisionGroup? = null,
protected var properties: MutableItemProvider? = null
@Transient override var parent: VisionGroup? = null,
@Serializable(MutableMetaSerializer::class)
protected var properties: MutableMeta? = null
) : Vision {
//protected val observableProperties: ObservableMutableMeta by lazy { properties.asObservable() }
@Synchronized
protected fun getOrCreateProperties(): MutableItemProvider {
protected fun getOrCreateProperties(): MutableMeta {
if (properties == null) {
val newProperties = MetaBuilder()
val newProperties = MutableMeta()
properties = newProperties
}
return properties!!
@ -45,18 +45,14 @@ public open class VisionBase(
/**
* A fast accessor method to get own property (no inheritance or styles
*/
override fun getOwnProperty(name: Name): MetaItem? = if (name == Name.EMPTY) {
properties?.rootItem
} else {
properties?.getItem(name)
}
override fun getOwnProperty(name: Name): Meta? = properties?.getMeta(name)
override fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) {
): Meta? = if (!inherit && !includeStyles && !includeDefaults) {
getOwnProperty(name)
} else {
buildList {
@ -68,22 +64,32 @@ public open class VisionBase(
add(parent?.getProperty(name, inherit, includeStyles, includeDefaults))
}
if (includeDefaults) {
add(descriptor?.defaultMeta?.get(name))
add(descriptor?.defaultNode?.get(name))
}
}.merge()
}
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
val oldItem = properties?.getItem(name)
if(oldItem!= item) {
getOrCreateProperties().setItem(name, item)
override fun setPropertyNode(name: Name, node: Meta?, notify: Boolean) {
val oldItem = properties?.get(name)
if (oldItem != node) {
getOrCreateProperties().setMeta(name, node)
if (notify) {
invalidateProperty(name)
}
}
}
override val descriptor: NodeDescriptor? get() = null
override fun setPropertyValue(name: Name, value: Value?, notify: Boolean) {
val oldItem = properties?.get(name)?.value
if (oldItem != value) {
getOrCreateProperties()[name] = value
if (notify) {
invalidateProperty(name)
}
}
}
override val descriptor: MetaDescriptor? get() = null
private suspend fun updateStyles(names: List<String>) {
names.mapNotNull { getStyle(it) }.asSequence()
@ -111,31 +117,23 @@ public open class VisionBase(
}
}
override fun update(change: VisionChange) {
override fun change(change: VisionChange) {
change.properties?.let {
updateProperties(Name.EMPTY, it.asMetaItem())
updateProperties(Name.EMPTY, it)
}
}
public companion object {
public val descriptor: NodeDescriptor = NodeDescriptor {
value(STYLE_KEY) {
type(ValueType.STRING)
public val descriptor: MetaDescriptor = MetaDescriptor {
value(STYLE_KEY, ValueType.STRING) {
multiple = true
}
}
public fun Vision.updateProperties(at: Name, item: MetaItem) {
when (item) {
is MetaItemValue -> {
if (item.value == Null) {
setProperty(at, null)
} else
setProperty(at, item)
}
is MetaItemNode -> item.node.items.forEach { (token, childItem) ->
updateProperties(at + token, childItem)
}
public fun Vision.updateProperties(at: Name, item: Meta) {
setPropertyValue(at, item.value)
item.items.forEach { (token, item) ->
updateProperties(at + token, item)
}
}

View File

@ -21,7 +21,7 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
private var reset: Boolean = false
private var vision: Vision? = null
private val propertyChange = MetaBuilder()
private val propertyChange = MutableMeta()
private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
@ -30,10 +30,10 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
children.getOrPut(visionName) { VisionChangeBuilder() }
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem?) {
public fun propertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
if (visionName == Name.EMPTY) {
//Write property removal as [Null]
propertyChange[propertyName] = (item ?: Null.asMetaItem())
propertyChange[propertyName] = (item ?: Meta(Null))
} else {
getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item)
}
@ -90,7 +90,7 @@ private fun CoroutineScope.collectChange(
//Collect properties change
source.onPropertyChange(this) { propertyName ->
val newItem = source.ownProperties[propertyName]
val newItem = source.getOwnProperty(propertyName)
collector().propertyChanged(name, propertyName, newItem)
}

View File

@ -75,12 +75,12 @@ public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision
public val structureChanges: Flow<StructureChange>
}
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(Name.parse(str))
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(key: String?, child: V?): Unit =
set(key?.toName(), child)
set(key?.let(Name::parse), child)
public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null }

View File

@ -131,15 +131,15 @@ public open class VisionGroupBase(
}
}
override fun update(change: VisionChange) {
override fun change(change: VisionChange) {
change.children?.forEach { (name, change) ->
when {
change.delete -> set(name, null)
change.vision != null -> set(name, change.vision)
else -> get(name)?.update(change)
else -> get(name)?.change(change)
}
}
super.update(change)
super.change(change)
}
}

View File

@ -8,12 +8,10 @@ import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.node
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.toJson
import space.kscience.dataforge.meta.toMetaItem
import space.kscience.dataforge.meta.toMeta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import kotlin.reflect.KClass
public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
@ -48,12 +46,11 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
jsonFormat.encodeToJsonElement(visionSerializer, vision)
//TODO remove double transformation with dedicated Meta serial format
public fun decodeFromMeta(meta: Meta, descriptor: NodeDescriptor? = null): Vision =
public fun decodeFromMeta(meta: Meta, descriptor: MetaDescriptor? = null): Vision =
decodeFromJson(meta.toJson(descriptor))
public fun encodeToMeta(vision: Vision, descriptor: NodeDescriptor? = null): Meta =
encodeToJsonElement(vision).toMetaItem(descriptor).node
?: error("Expected node, but value found. Check your serializer!")
public fun encodeToMeta(vision: Vision, descriptor: MetaDescriptor? = null): Meta =
encodeToJsonElement(vision).toMeta(descriptor)
public companion object : PluginFactory<VisionManager> {
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
@ -89,7 +86,7 @@ public abstract class VisionPlugin(meta: Meta = Meta.EMPTY) : AbstractPlugin(met
protected abstract val visionSerializersModule: SerializersModule
override fun content(target: String): Map<Name, Any> = when (target) {
VisionManager.VISION_SERIALIZER_MODULE_TARGET -> mapOf(tag.toString().toName() to visionSerializersModule)
VisionManager.VISION_SERIALIZER_MODULE_TARGET -> mapOf(Name.parse(tag.toString()) to visionSerializersModule)
else -> super.content(target)
}
}

View File

@ -0,0 +1,38 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Value
internal class VisionProperties(
val vision: Vision,
val rootName: Name,
val inherit: Boolean = false,
val includeStyles: Boolean = true,
val includeDefaults: Boolean = true,
) : MutableMeta {
override val items: Map<NameToken, MutableMeta>
get() = vision.getProperty(rootName, inherit, includeStyles, includeDefaults)?.items?.mapValues {
VisionProperties(vision, rootName + it.key, inherit, includeStyles, includeDefaults)
} ?: emptyMap()
override var value: Value?
get() = vision.getProperty(rootName, inherit, includeStyles, includeDefaults)?.value
set(value) {
vision.setPropertyValue(rootName, value)
}
override fun getOrCreate(name: Name): MutableMeta = VisionProperties(vision, rootName + name)
override fun setMeta(name: Name, node: Meta?) {
vision.setPropertyNode(rootName + name, node)
}
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
override fun toString(): String = Meta.toString(this)
}

View File

@ -1,7 +1,11 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Configurable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.Value
/**
* Property containers are used to create a symmetric behaviors for vision properties and style builders
@ -12,21 +16,34 @@ public interface VisionPropertyContainer<out V: Vision> {
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): MetaItem?
): Meta?
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
/**
* Replace the property node. If [node] is null remove node and its descendants
*/
public fun setPropertyNode(name: Name, node: Meta?, notify: Boolean = true)
/**
* Set a value of specific property node
*/
public fun setPropertyValue(name: Name, value: Value?, notify: Boolean = true)
}
public open class SimpleVisionPropertyContainer<out V: Vision>(protected val config: ObservableMeta): VisionPropertyContainer<V>{
public open class SimpleVisionPropertyContainer<out V : Vision>(
override val meta: ObservableMutableMeta,
) : VisionPropertyContainer<V>, Configurable {
override fun getProperty(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean
): MetaItem? = config[name]
): Meta? = meta[name]
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
config[name] = item
override fun setPropertyNode(name: Name, node: Meta?, notify: Boolean) {
this.meta.setMeta(name, node)
}
override fun setPropertyValue(name: Name, value: Value?, notify: Boolean) {
this.meta.setValue(name, value)
}
}

View File

@ -2,12 +2,13 @@ package space.kscience.visionforge.html
import kotlinx.html.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.isEmpty
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import kotlin.collections.set
@ -25,7 +26,7 @@ public class VisionOutput @PublishedApi internal constructor(public val manager:
//TODO expose a way to define required plugins.
public inline fun meta(block: MetaBuilder.() -> Unit) {
public inline fun meta(block: MutableMeta.() -> Unit) {
this.meta = Meta(block)
}
}
@ -94,11 +95,11 @@ public abstract class VisionTagConsumer<R>(
public inline fun <T> TagConsumer<T>.vision(
name: String = DEFAULT_VISION_NAME,
visionProvider: VisionOutput.() -> Vision,
): T = vision(name.toName(), visionProvider)
): T = vision(Name.parse(name), visionProvider)
public fun <T> TagConsumer<T>.vision(
vision: Vision,
): T = vision("vision[${vision.hashCode()}]".toName(), vision)
): T = vision(NameToken("vision", vision.hashCode().toString()).asName(), vision)
/**
* Process the resulting object produced by [TagConsumer]

View File

@ -1,18 +1,20 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Laminate
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.isLeaf
import space.kscience.dataforge.values.asValue
@DslMarker
public annotation class VisionBuilder
public fun List<MetaItem?>.merge(): MetaItem? = when (val first = firstOrNull { it != null }) {
null -> null
is MetaItemValue -> first //fast search for first entry if it is value
is MetaItemNode -> {
//merge nodes if first encountered node is meta
val laminate: Laminate = Laminate(mapNotNull { it.node })
MetaItemNode(laminate)
public fun List<Meta?>.merge(): Meta? {
val first = firstOrNull { it != null }
return when {
first == null -> null
first.isLeaf -> first //fast search for first entry if it is value
else -> Laminate(filterNotNull()) //merge nodes if first encountered node is meta
}
}
@ -21,8 +23,4 @@ public fun List<MetaItem?>.merge(): MetaItem? = when (val first = firstOrNull {
*/
public var Vision.visible: Boolean?
get() = getProperty(Vision.VISIBLE_KEY).boolean
set(value) = setProperty(Vision.VISIBLE_KEY, value?.asValue())
public fun Vision.configure(meta: Meta?): Unit = update(VisionChange(properties = meta))
public fun Vision.configure(block: MetaBuilder.() -> Unit): Unit = configure(Meta(block))
set(value) = setPropertyValue(Vision.VISIBLE_KEY, value?.asValue())

View File

@ -2,11 +2,11 @@ package space.kscience.visionforge
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.asObservable
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.NodeDescriptorBuilder
import space.kscience.dataforge.meta.descriptors.ValueDescriptorBuilder
import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder
import space.kscience.dataforge.meta.descriptors.item
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ValueType
import kotlin.reflect.KProperty1
import kotlin.reflect.typeOf
@ -14,55 +14,53 @@ import kotlin.reflect.typeOf
/**
* TODO to be moved into the core
*/
public inline fun <S : Scheme, reified T> NodeDescriptorBuilder.value(
public inline fun <S : Scheme, reified T> MetaDescriptorBuilder.value(
property: KProperty1<S, T>,
noinline block: ValueDescriptorBuilder.() -> Unit = {},
noinline block: MetaDescriptorBuilder.() -> Unit = {},
) {
when (typeOf<T>()) {
typeOf<Number>(), typeOf<Int>(), typeOf<Double>(), typeOf<Short>(), typeOf<Long>(), typeOf<Float>() ->
value(property.name) {
type(ValueType.NUMBER)
value(property.name, ValueType.NUMBER) {
block()
}
typeOf<Number?>(), typeOf<Int?>(), typeOf<Double?>(), typeOf<Short?>(), typeOf<Long?>(), typeOf<Float?>() ->
value(property.name) {
type(ValueType.NUMBER)
value(property.name, ValueType.NUMBER) {
block()
}
typeOf<Boolean>() -> value(property.name) {
type(ValueType.BOOLEAN)
typeOf<Boolean>() -> value(property.name, ValueType.BOOLEAN) {
block()
}
typeOf<List<Number>>(), typeOf<List<Int>>(), typeOf<List<Double>>(), typeOf<List<Short>>(), typeOf<List<Long>>(), typeOf<List<Float>>(),
typeOf<IntArray>(), typeOf<DoubleArray>(), typeOf<ShortArray>(), typeOf<LongArray>(), typeOf<FloatArray>(),
-> value(property.name) {
type(ValueType.NUMBER)
-> value(property.name, ValueType.NUMBER) {
multiple = true
block()
}
typeOf<String>() -> value(property.name) {
type(ValueType.STRING)
typeOf<String>() -> value(property.name, ValueType.STRING) {
block()
}
typeOf<List<String>>(), typeOf<Array<String>>() -> value(property.name) {
type(ValueType.STRING)
typeOf<List<String>>(), typeOf<Array<String>>() -> value(property.name, ValueType.STRING) {
multiple = true
block()
}
else -> value(property.name, block)
else -> item(property.name, block)
}
}
public fun NodeDescriptor.copy(block: NodeDescriptorBuilder.() -> Unit = {}): NodeDescriptor {
return NodeDescriptorBuilder(toMeta().toMutableMeta().asObservable()).apply(block)
public fun MetaDescriptorBuilder.item(
key: String,
described: Described,
block: MetaDescriptorBuilder.() -> Unit = {},
) {
described.descriptor?.let {
item(Name.parse(key), it, block)
}
}
public inline fun <S : Scheme, reified T : Scheme> NodeDescriptorBuilder.scheme(
public inline fun <S : Scheme, reified T : Scheme> MetaDescriptorBuilder.scheme(
property: KProperty1<S, T>,
spec: SchemeSpec<T>,
noinline block: NodeDescriptorBuilder.() -> Unit = {},
noinline block: MetaDescriptorBuilder.() -> Unit = {},
) {
spec.descriptor?.let { descriptor ->
item(property.name, descriptor.copy(block))
}
item(property.name, spec, block)
}

View File

@ -0,0 +1,93 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.number
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
public fun Vision.propertyNode(
name: Name? = null,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): ReadWriteProperty<Any?, Meta?> = object : ReadWriteProperty<Any?, Meta?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? =
getProperty(name ?: Name.parse(property.name), inherit, includeStyles, includeDefaults)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) {
setPropertyNode(name ?: Name.parse(property.name), value)
}
}
public fun <T> Vision.propertyNode(
converter: MetaConverter<T>,
name: Name? = null,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): ReadWriteProperty<Any?, T?> = object : ReadWriteProperty<Any?, T?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? = getProperty(
name ?: Name.parse(property.name),
inherit,
includeStyles,
includeDefaults
)?.let(converter::metaToObject)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
setPropertyNode(name ?: Name.parse(property.name), value?.let(converter::objectToMeta))
}
}
public fun Vision.propertyValue(
name: Name? = null,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): ReadWriteProperty<Any?, Value?> = object : ReadWriteProperty<Any?, Value?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Value? =
getProperty(name ?: Name.parse(property.name), inherit, includeStyles, includeDefaults)?.value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) {
setPropertyValue(name ?: Name.parse(property.name), value)
}
}
public fun <T> Vision.propertyValue(
name: Name? = null,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
setter: (T) -> Value? = { it?.let(Value::of) },
getter: (Value?) -> T,
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T = getProperty(
name ?: Name.parse(property.name),
inherit,
includeStyles,
includeDefaults
)?.value.let(getter)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
setPropertyValue(name ?: Name.parse(property.name), value?.let(setter))
}
}
public fun Vision.numberProperty(
name: Name? = null,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true
): ReadWriteProperty<Any?, Number?> = propertyValue(name, inherit, includeStyles, includeDefaults) { it?.number }
public fun Vision.numberProperty(
name: Name? = null,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
default: () -> Number
): ReadWriteProperty<Any?, Number> = propertyValue(name, inherit, includeStyles, includeDefaults) {
it?.number ?: default()
}

View File

@ -2,87 +2,63 @@ package space.kscience.visionforge
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.asValue
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"
public val ItemDescriptor.inherited: Boolean
public val MetaDescriptor.inherited: Boolean
get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false
public var ItemDescriptorBuilder.inherited: Boolean
public var MetaDescriptorBuilder.inherited: Boolean
get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false
set(value) = attributes {
set(INHERITED_DESCRIPTOR_ATTRIBUTE, value)
}
set(value) = attributes.set(INHERITED_DESCRIPTOR_ATTRIBUTE, value)
public val ItemDescriptor.usesStyles: Boolean
public val MetaDescriptor.usesStyles: Boolean
get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean ?: true
public var ItemDescriptorBuilder.usesStyles: Boolean
public var MetaDescriptorBuilder.usesStyles: Boolean
get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean ?: true
set(value) = attributes {
set(STYLE_DESCRIPTOR_ATTRIBUTE, value)
}
set(value) = attributes.set(STYLE_DESCRIPTOR_ATTRIBUTE, value)
public val Vision.describedProperties: Meta
get() = Meta {
descriptor?.items?.forEach { (key, descriptor) ->
key put getProperty(key, inherit = descriptor.inherited)
descriptor?.children?.forEach { (key, descriptor) ->
this.setMeta(key.asName(), getProperty(key, inherit = descriptor.inherited))
}
}
public val ValueDescriptor.widget: Meta
get() = attributes["widget"].node ?: Meta.EMPTY
public val MetaDescriptor.widget: Meta
get() = attributes["widget"] ?: Meta.EMPTY
/**
* Extension property to access the "widget" key of [ValueDescriptor]
*/
public var ValueDescriptorBuilder.widget: Meta
get() = attributes["widget"].node ?: Meta.EMPTY
public var MetaDescriptorBuilder.widget: Meta
get() = attributes["widget"] ?: Meta.EMPTY
set(value) {
attributes {
set("widget", value)
}
attributes["widget"] = value
}
public val ValueDescriptor.widgetType: String?
public val MetaDescriptor.widgetType: String?
get() = attributes["widget.type"].string
/**
* Extension property to access the "widget.type" key of [ValueDescriptor]
*/
public var ValueDescriptorBuilder.widgetType: String?
public var MetaDescriptorBuilder.widgetType: String?
get() = attributes["widget.type"].string
set(value) {
attributes {
set("widget.type", value)
}
attributes["widget.type"] = value?.asValue()
}
/**
* If true, this item is hidden in property editor. Default is false
*/
public val ItemDescriptor.hidden: Boolean
public val MetaDescriptor.hidden: Boolean
get() = attributes["widget.hide"].boolean ?: false
public fun ItemDescriptorBuilder.hide(): Unit = attributes {
set("widget.hide", true)
}
public inline fun <reified E : Enum<E>> NodeDescriptorBuilder.enum(
key: Name,
default: E?,
crossinline modifier: ValueDescriptorBuilder.() -> Unit = {},
): Unit = value(key) {
type(ValueType.STRING)
default?.let {
default(default)
}
allowedValues = enumValues<E>().map { it.asValue() }
modifier()
}
public fun MetaDescriptorBuilder.hide(): Unit = attributes.set("widget.hide", true)

View File

@ -57,7 +57,7 @@ class HtmlTagTest {
div {
h2 { +"Properties" }
ul {
(vision as? VisionBase)?.meta?.items?.forEach {
(vision as? VisionBase)?.meta()?.items?.forEach {
li {
a { +it.key.toString() }
p { +it.value.toString() }

View File

@ -92,7 +92,7 @@ public class VisionClient : AbstractPlugin() {
}
logger.debug { "Got update $change for output with name $name" }
vision.update(change)
vision.change(change)
} else {
console.error("WebSocket message data is not a string")
}

View File

@ -115,5 +115,5 @@ public class ApplicationSurrogate : App() {
}
public fun Context.display(width: Double = 800.0, height: Double = 600.0, component: () -> UIComponent) {
plugins.fetch(FXPlugin).display(component(), width, height)
fetch(FXPlugin).display(component(), width, height)
}

View File

@ -9,8 +9,8 @@ import javafx.collections.FXCollections
import javafx.scene.control.ComboBox
import javafx.util.StringConverter
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.value
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.Value
@ -56,7 +56,7 @@ public class ComboBoxValueChooser(public val values: Collection<Value>? = null)
override val name: Name = "combo".asName()
override fun invoke(meta: Meta): ValueChooser =
ComboBoxValueChooser(meta["values"].value?.list)
ComboBoxValueChooser(meta["values"]?.value?.list)
}
}

View File

@ -1,189 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package space.kscience.visionforge.editor
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
import javafx.scene.Node
import javafx.scene.control.*
import javafx.scene.control.cell.TextFieldTreeTableCell
import javafx.scene.layout.BorderPane
import javafx.scene.layout.HBox
import javafx.scene.layout.Priority
import javafx.scene.paint.Color
import javafx.scene.text.Text
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.Config
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.names.NameToken
import space.kscience.visionforge.dfIconView
import tornadofx.*
/**
* A configuration editor fragment
*
* @author Alexander Nozik
*/
public class ConfigEditor(
public val rootNode: FXMetaNode<Config>,
public val allowNew: Boolean = true,
title: String = "Configuration editor"
) : Fragment(title = title, icon = dfIconView) {
//TODO replace parameters by properties
public constructor(config: Config, descriptor: NodeDescriptor?, title: String = "Configuration editor") :
this(FXMeta.root(config, descriptor = descriptor), title = title)
override val root: BorderPane = borderpane {
center = treetableview<FXMeta<Config>> {
root = TreeItem(rootNode)
root.isExpanded = true
sortMode = TreeSortMode.ALL_DESCENDANTS
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
populate {
when (val fxMeta = it.value) {
is FXMetaNode -> {
fxMeta.children
}
is FXMetaValue -> null
}
}
column("Name", FXMeta<Config>::name) {
setCellFactory {
object : TextFieldTreeTableCell<FXMeta<Config>, NameToken>() {
override fun updateItem(item: NameToken?, empty: Boolean) {
super.updateItem(item, empty)
contextMenu?.items?.removeIf { it.text == "Remove" }
if (!empty) {
if (treeTableRow.item != null) {
textFillProperty().bind(treeTableRow.item.hasValue.objectBinding {
if (it == true) {
Color.BLACK
} else {
Color.GRAY
}
})
if (treeTableRow.treeItem.value.parent != null && treeTableRow.treeItem.value.hasValue.get()) {
contextmenu {
item("Remove") {
action {
treeTableRow.item.remove()
}
}
}
}
}
}
}
}
}
}
column("Value") { param: TreeTableColumn.CellDataFeatures<FXMeta<Config>, FXMeta<Config>> ->
param.value.valueProperty()
}.setCellFactory {
ValueCell()
}
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
text.wrappingWidthProperty().bind(param.widthProperty())
text.textProperty().bind(cell.itemProperty())
cell
}
}
}
private fun showNodeDialog(): String? {
val dialog = TextInputDialog()
dialog.title = "Node name selection"
dialog.contentText = "Enter a name for new node: "
dialog.headerText = null
val result = dialog.showAndWait()
return result.orElse(null)
}
private fun showValueDialog(): String? {
val dialog = TextInputDialog()
dialog.title = "Value name selection"
dialog.contentText = "Enter a name for new value: "
dialog.headerText = null
val result = dialog.showAndWait()
return result.orElse(null)
}
private inner class ValueCell : TreeTableCell<FXMeta<Config>, FXMeta<Config>?>() {
public override fun updateItem(item: FXMeta<Config>?, empty: Boolean) {
if (!empty) {
if (item != null) {
when (item) {
is FXMetaValue<Config> -> {
text = null
val chooser = ValueChooser.build(
Global,
item.valueProperty,
item.descriptor
) {
item.set(it)
}
graphic = chooser.node
}
is FXMetaNode<Config> -> {
if (allowNew) {
text = null
graphic = HBox().apply {
val glyph: Node = FontAwesomeIconView(FontAwesomeIcon.PLUS_CIRCLE)
button("node", graphic = glyph) {
hgrow = Priority.ALWAYS
maxWidth = Double.POSITIVE_INFINITY
action {
showNodeDialog()?.let {
item.addNode(it)
}
}
}
button("value", graphic = FontAwesomeIconView(FontAwesomeIcon.PLUS_SQUARE)) {
hgrow = Priority.ALWAYS
maxWidth = Double.POSITIVE_INFINITY
action {
showValueDialog()?.let {
item.addValue(it)
}
}
}
}
} else {
text = ""
}
}
}
} else {
text = null
graphic = null
}
} else {
text = null
graphic = null
}
}
}
companion object {
/**
* The tag not to display node or value in configurator
*/
const val NO_CONFIGURATOR_TAG = "nocfg"
}
}

View File

@ -1,223 +0,0 @@
package space.kscience.visionforge.editor
import javafx.beans.binding.ListBinding
import javafx.beans.binding.ObjectBinding
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableBooleanValue
import javafx.beans.value.ObservableStringValue
import javafx.collections.ObservableList
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import tornadofx.*
/**
* A display for meta and descriptor
*/
sealed class FXMeta<M : TypedMeta<M>> : Comparable<FXMeta<*>> {
abstract val name: NameToken
abstract val parent: FXMetaNode<M>?
abstract val descriptionProperty: ObservableStringValue
abstract val descriptor: ItemDescriptor?
abstract val hasValue: ObservableBooleanValue
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())
}
}
companion object {
fun <M : TypedMeta<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 : TypedMeta<M>>(
override val name: NameToken,
override val parent: FXMetaNode<M>?,
nodeValue: M? = null,
descriptorValue: NodeDescriptor? = null
) : FXMeta<M>() {
/**
* A descriptor that could be manually set to the node
*/
private val innerDescriptorProperty = SimpleObjectProperty(descriptorValue)
/**
* Actual descriptor which holds value inferred from parrent
*/
val descriptorProperty = objectBinding(innerDescriptorProperty) {
value ?: parent?.descriptor?.nodes?.get(this@FXMetaNode.name.body)
}
override val descriptor: NodeDescriptor? by descriptorProperty
private val innerNodeProperty = SimpleObjectProperty(nodeValue)
val nodeProperty: ObjectBinding<M?> = objectBinding(innerNodeProperty) {
value ?: parent?.node?.get(this@FXMetaNode.name).node
}
val node: M? by nodeProperty
override val descriptionProperty = innerDescriptorProperty.stringBinding { it?.info ?: "" }
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
private val filter: (FXMeta<M>) -> Boolean = { cfg ->
!(cfg.descriptor?.attributes?.get(ConfigEditor.NO_CONFIGURATOR_TAG)?.boolean ?: 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? Config)?.onChange(this, listener)
nodeProperty.addListener { _, oldValue, newValue ->
if (newValue == null) {
(oldValue as? Config)?.removeListener(this)
}
if (newValue is Config) {
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 MetaItemNode || actualDescriptor is NodeDescriptor) {
FXMetaNode(token, this@FXMetaNode)
} else {
FXMetaValue(token, this@FXMetaNode)
}
}
return items.filter(filter).asObservable()
}
}
init {
if (parent != null) {
parent.descriptorProperty.onChange { descriptorProperty.invalidate() }
parent.nodeProperty.onChange { nodeProperty.invalidate() }
}
}
}
public class FXMetaValue<M : TypedMeta<M>>(
override val name: NameToken,
override val parent: FXMetaNode<M>
) : FXMeta<M>() {
public val descriptorProperty = parent.descriptorProperty.objectBinding {
it?.values?.get(name.body)
}
/**
* A descriptor that could be manually set to the node
*/
override val descriptor: ValueDescriptor? by descriptorProperty
//private val innerValueProperty = SimpleObjectProperty(value)
public val valueProperty = descriptorProperty.objectBinding { descriptor ->
parent.node?.get(name).value ?: descriptor?.default
}
override val hasValue: ObservableBooleanValue = parent.nodeProperty.booleanBinding { it?.get(name) != null }
public val value by valueProperty
override val descriptionProperty = descriptorProperty.stringBinding { it?.info ?: "" }
}
public fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
node?.remove(name.asName())
children.invalidate()
}
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
return if (append && token.hasIndex()) {
val name = token.asName()
val index = (getIndexed(name).keys.mapNotNull { it?.toIntOrNull() }.maxOrNull() ?: -1) + 1
val newName = name.withIndex(index.toString())
set(newName, Meta.EMPTY)
get(newName).node!!
} else {
this.set(token.asName(), Meta.EMPTY)
//FIXME possible concurrency bug
get(token).node!!
}
}
fun <M : MutableMeta<M>> FXMetaNode<out M>.getOrCreateNode(): M {
val node = node
return when {
node != null -> node
parent != null -> parent.getOrCreateNode().createEmptyNode(this.name, descriptor?.multiple == true).also {
parent.children.invalidate()
}
else -> kotlin.error("Orphan empty node is not allowed")
}
}
fun <M : MutableMeta<M>> FXMeta<M>.remove() {
parent?.node?.remove(name.asName())
}
fun <M : MutableMeta<M>> FXMetaNode<M>.addValue(key: String) {
val parent = getOrCreateNode()
if (descriptor?.multiple == true) {
parent.append(key, Null)
} else {
parent[key] = Null
}
}
fun <M : MutableMeta<M>> FXMetaNode<M>.addNode(key: String) {
val parent = getOrCreateNode()
if (descriptor?.multiple == true) {
parent.append(key, Meta.EMPTY)
} else {
parent[key] = Meta.EMPTY
}
}
fun <M : MutableMeta<M>> FXMetaValue<M>.set(value: Value?) {
if (descriptor?.multiple == true) {
parent.getOrCreateNode().append(this.name.body, value)
} else {
parent.getOrCreateNode()[this.name] = value
}
}

View File

@ -0,0 +1,217 @@
package space.kscience.visionforge.editor
import javafx.beans.binding.Binding
import javafx.beans.binding.BooleanBinding
import javafx.beans.binding.ListBinding
import javafx.beans.property.SimpleObjectProperty
import javafx.collections.ObservableList
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import tornadofx.*
/**
* A display for meta and descriptor
*/
public class FXMetaModel<M : Meta>(
public val root: M,
public val rootDescriptor: MetaDescriptor?,
public val nodeName: Name,
public val title: String = nodeName.lastOrNull()?.toString() ?: "Meta"
) : Comparable<FXMetaModel<*>> {
private val existingNode = SimpleObjectProperty<Meta>(root[nodeName])
public val children: ListBinding<FXMetaModel<M>> = object : ListBinding<FXMetaModel<M>>() {
override fun computeValue(): ObservableList<FXMetaModel<M>> {
val nodeKeys = existingNode.get().items.keys
val descriptorKeys = descriptor?.children?.keys?.map { NameToken(it) } ?: emptySet()
return (nodeKeys + descriptorKeys).map {
FXMetaModel(
root,
rootDescriptor,
nodeName + it
)
}.filter(filter).asObservable()
}
}
init {
//add listener to the root node if possible
if (root is ObservableMeta) {
root.onChange(this) { changed ->
if (changed.startsWith(nodeName)) {
if (nodeName.length == changed.length) existingNode.set(root[nodeName])
else if (changed.length == nodeName.length + 1) children.invalidate()
}
}
}
}
public val descriptor: MetaDescriptor? = rootDescriptor?.get(nodeName)
public val existsProperty: BooleanBinding = existingNode.isNotNull
public val exists: Boolean by existsProperty
public val valueProperty: Binding<Value?> = existingNode.objectBinding {
existingNode.get().value ?: descriptor?.defaultValue
}
override fun compareTo(other: FXMetaModel<*>): Int = if (this.exists == other.exists) {
this.nodeName.toString().compareTo(other.nodeName.toString())
} else {
this.exists.compareTo(other.exists)
}
public companion object {
private val filter: (FXMetaModel<*>) -> Boolean = { cfg ->
!(cfg.descriptor?.attributes?.get(MutableMetaEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false)
}
public fun <M : Meta> root(
node: M,
descriptor: MetaDescriptor? = null,
rootName: String = "root"
): FXMetaModel<M> = FXMetaModel(node, descriptor, Name.EMPTY, title = rootName)
}
// /**
// * A descriptor that could be manually set to the node
// */
// private val innerDescriptorProperty = SimpleObjectProperty(descriptorValue)
//
// /**
// * Actual descriptor which holds value inferred from parrent
// */
// val descriptorProperty: ObjectBinding<MetaDescriptor?> = objectBinding(innerDescriptorProperty) {
// value ?: parent?.descriptor?.get(this@FXMeta.name.body)
// }
//
// val descriptor: MetaDescriptor? by descriptorProperty
//
// private val innerNodeProperty = SimpleObjectProperty(nodeValue)
//
// val nodeProperty: ObjectBinding<M> = objectBinding(innerNodeProperty) {
// value ?: parent?.node?.get(this@FXMeta.name)
// }
//
// val node by nodeProperty
//
// val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
//
// private val filter: (FXMeta<M>) -> Boolean = { cfg ->
// !(cfg.descriptor?.attributes?.get(MutableMetaEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false)
// }
//
// val children: ListBinding<FXMeta<M>> = object : ListBinding<FXMeta<M>>() {
//
// init {
// bind(nodeProperty, descriptorProperty)
//
// val listener: Meta.(Name) -> Unit = { name ->
// if (name.length == 1) invalidate()
// }
//
// (node as? ObservableMeta)?.onChange(this, listener)
//
// nodeProperty.addListener { _, oldValue, newValue ->
// if (newValue == null) {
// (oldValue as? ObservableMeta)?.removeListener(this)
// }
//
// if (newValue is ObservableMeta) {
// newValue.onChange(this, listener)
// }
// }
// }
//
// override fun computeValue(): ObservableList<FXMeta<M>> {
// val nodeKeys = node?.items?.keys?.toSet() ?: emptySet()
// val descriptorKeys = descriptor?.children?.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?.children?.get(token.body)
//
// if (actualItem is MetaNode) {
// FXMetaNode(token, this@FXMetaNode)
// } else {
// FXMetaValue(token, this@FXMetaNode)
// }
// }
//
// return items.filter(filter).asObservable()
// }
// }
//
// init {
// if (parent != null) {
// parent.descriptorProperty.onChange { descriptorProperty.invalidate() }
// parent.nodeProperty.onChange { nodeProperty.invalidate() }
// }
// }
//
}
//
//internal fun <M : MutableMeta> FXMeta<M>.remove(name: NameToken) {
// node?.remove(name.asName())
// children.invalidate()
//}
//
//private fun <M : MutableMeta> M.createEmptyNode(token: NameToken, append: Boolean): M {
// return if (append && token.hasIndex()) {
// val name = token.asName()
// val index = (getIndexed(name).keys.mapNotNull { it?.toIntOrNull() }.maxOrNull() ?: -1) + 1
// val newName = name.withIndex(index.toString())
// set(newName, Meta.EMPTY)
// get(newName).node
// } else {
// this.set(token.asName(), Meta.EMPTY)
// //FIXME possible concurrency bug
// get(token).node
// }
//}
//
//internal fun <M : MutableMeta> FXMeta<out M>.getOrCreateNode(): M {
// val node = node
// return when {
// node != null -> node
// parent != null -> parent.getOrCreateNode().createEmptyNode(this.name, descriptor?.multiple == true).also {
// parent.children.invalidate()
// }
// else -> kotlin.error("Orphan empty node is not allowed")
// }
//
//}
internal fun <M : MutableMeta> FXMetaModel<M>.remove() {
root.remove(nodeName)
}
//
//internal fun <M : MutableMeta> FXMeta<M>.addValue(key: String) {
// val parent = getOrCreateNode()
// if (descriptor?.multiple == true) {
// parent.append(key, Null)
// } else {
// parent[key] = Null
// }
//}
//
//internal fun <M : MutableMeta> FXMeta<M>.addNode(key: String) {
// val parent = getOrCreateNode()
// if (descriptor?.multiple == true) {
// parent.append(key, Meta.EMPTY)
// } else {
// parent[key] = Meta.EMPTY
// }
//}
//
internal fun <M : MutableMeta> FXMetaModel<M>.setValue(value: Value?) {
root.setValue(nodeName, value)
}

View File

@ -16,45 +16,42 @@
package space.kscience.visionforge.editor
import javafx.beans.property.SimpleStringProperty
import javafx.scene.control.TreeItem
import javafx.scene.control.TreeSortMode
import javafx.scene.control.TreeTableView
import javafx.scene.layout.BorderPane
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.values.string
import space.kscience.visionforge.dfIconView
import tornadofx.*
class MetaViewer(val rootNode: FXMetaNode<*>, title: String = "Meta viewer") : Fragment(title,
dfIconView
) {
constructor(meta: Meta, title: String = "Meta viewer"): this(
FXMeta.root(
meta
),title = title)
public class MetaViewer(
private val rootNode: FXMetaModel<Meta>,
title: String = "Meta viewer"
) : Fragment(title, dfIconView) {
override val root = borderpane {
public constructor(meta: Meta, title: String = "Meta viewer") : this(
FXMetaModel.root(
meta
), title = title
)
override val root: BorderPane = borderpane {
center {
treetableview<FXMeta<*>> {
treetableview<FXMetaModel<*>> {
isShowRoot = false
root = TreeItem(rootNode)
populate {
when (val fxMeta = it.value) {
is FXMetaNode -> {
val fxMeta = it.value
fxMeta.children
}
is FXMetaValue -> null
}
}
root.isExpanded = true
sortMode = TreeSortMode.ALL_DESCENDANTS
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
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]")
}
column("Name", FXMetaModel<*>::title)
column<FXMetaModel<*>, String>("Value") { cellDataFeatures ->
val item = cellDataFeatures.value.value
item.valueProperty.stringBinding { it?.string ?: "" }
}
}
}

View File

@ -0,0 +1,193 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package space.kscience.visionforge.editor
import javafx.scene.control.*
import javafx.scene.control.cell.TextFieldTreeTableCell
import javafx.scene.layout.BorderPane
import javafx.scene.paint.Color
import javafx.scene.text.Text
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.dfIconView
import tornadofx.*
/**
* A Configuration editor fragment
*
* @author Alexander Nozik
*/
public class MutableMetaEditor(
public val rootNode: FXMetaModel<MutableMeta>,
public val allowNew: Boolean = true,
title: String = "Configuration editor"
) : Fragment(title = title, icon = dfIconView) {
//TODO replace parameters by properties
public constructor(
MutableMeta: MutableMeta,
descriptor: MetaDescriptor?,
title: String = "Configuration editor"
) :
this(FXMetaModel.root(MutableMeta, descriptor = descriptor), title = title)
override val root: BorderPane = borderpane {
center = treetableview<FXMetaModel<MutableMeta>> {
root = TreeItem(rootNode)
root.isExpanded = true
sortMode = TreeSortMode.ALL_DESCENDANTS
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
populate {
it.value.children
}
column("Name", FXMetaModel<MutableMeta>::title) {
setCellFactory {
object : TextFieldTreeTableCell<FXMetaModel<MutableMeta>, String>() {
override fun updateItem(item: String?, empty: Boolean) {
super.updateItem(item, empty)
contextMenu?.items?.removeIf { it.text == "Remove" }
val content = treeTableRow.item
if (!empty) {
if (treeTableRow.item != null) {
textFillProperty().bind(content.existsProperty.objectBinding {
if (it == true) {
Color.BLACK
} else {
Color.GRAY
}
})
if (content.exists) {
contextmenu {
item("Remove") {
action {
content.remove()
}
}
}
}
}
}
}
}
}
}
column("Value") { param: TreeTableColumn.CellDataFeatures<FXMetaModel<MutableMeta>, FXMetaModel<MutableMeta>> ->
param.value.valueProperty()
}.setCellFactory {
ValueCell()
}
column("Description") { param: TreeTableColumn.CellDataFeatures<FXMetaModel<MutableMeta>, String> ->
(param.value.value.descriptor?.info ?: "").observable()
}.setCellFactory { param: TreeTableColumn<FXMetaModel<MutableMeta>, String> ->
val cell = TreeTableCell<FXMetaModel<MutableMeta>, String>()
val text = Text()
cell.graphic = text
cell.prefHeight = Control.USE_COMPUTED_SIZE
text.wrappingWidthProperty().bind(param.widthProperty())
text.textProperty().bind(cell.itemProperty())
cell
}
}
}
private fun showNodeDialog(): String? {
val dialog = TextInputDialog()
dialog.title = "Node name selection"
dialog.contentText = "Enter a name for new node: "
dialog.headerText = null
val result = dialog.showAndWait()
return result.orElse(null)
}
private fun showValueDialog(): String? {
val dialog = TextInputDialog()
dialog.title = "Value name selection"
dialog.contentText = "Enter a name for new value: "
dialog.headerText = null
val result = dialog.showAndWait()
return result.orElse(null)
}
private inner class ValueCell : TreeTableCell<FXMetaModel<MutableMeta>, FXMetaModel<MutableMeta>?>() {
public override fun updateItem(item: FXMetaModel<MutableMeta>?, empty: Boolean) {
if (!empty) {
if (item != null) {
text = null
val chooser = ValueChooser.build(
Global,
item.valueProperty,
item.descriptor
) {
item.setValue(it)
}
graphic = chooser.node
// when (item) {
// is FXMetaValue<MutableMeta> -> {
// text = null
// val chooser = ValueChooser.build(
// Global,
// item.valueProperty,
// item.descriptor
// ) {
// item.set(it)
// }
// graphic = chooser.node
// }
// is FXMetaNode<MutableMeta> -> {
// if (allowNew) {
// text = null
// graphic = HBox().apply {
// val glyph: Node = FontAwesomeIconView(FontAwesomeIcon.PLUS_CIRCLE)
// button("node", graphic = glyph) {
// hgrow = Priority.ALWAYS
// maxWidth = Double.POSITIVE_INFINITY
// action {
// showNodeDialog()?.let {
// item.addNode(it)
// }
// }
// }
// button("value", graphic = FontAwesomeIconView(FontAwesomeIcon.PLUS_SQUARE)) {
// hgrow = Priority.ALWAYS
// maxWidth = Double.POSITIVE_INFINITY
// action {
// showValueDialog()?.let {
// item.addValue(it)
// }
// }
// }
// }
// } else {
// text = ""
// }
// }
// }
} else {
text = null
graphic = null
}
} else {
text = null
graphic = null
}
}
}
public companion object {
/**
* The tag not to display node or value in MutableMetaurator
*/
public const val NO_CONFIGURATOR_TAG: String = "nocfg"
}
}

View File

@ -10,12 +10,13 @@ import javafx.scene.control.TextField
import javafx.scene.input.KeyCode
import javafx.scene.input.KeyEvent
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.validate
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.*
import tornadofx.*
class TextValueChooser : ValueChooserBase<TextField>() {
public class TextValueChooser : ValueChooserBase<TextField>() {
private val displayText: String
get() = currentValue().let {
@ -85,7 +86,7 @@ class TextValueChooser : ValueChooserBase<TextField>() {
}
private fun validate(value: Value): Boolean {
return descriptor?.isAllowedValue(value) ?: true
return descriptor?.validate(value) ?: true
}
// @Override
@ -101,7 +102,7 @@ class TextValueChooser : ValueChooserBase<TextField>() {
}
}
companion object : ValueChooser.Factory {
public companion object : ValueChooser.Factory {
override val name: Name = "text".asName()
override fun invoke(meta: Meta): ValueChooser =
TextValueChooser()

View File

@ -13,11 +13,11 @@ import space.kscience.dataforge.values.Value
* @param value Value after change
* @param message Message on unsuccessful change
*/
class ValueCallbackResponse(val success: Boolean, val value: Value, val message: String)
public class ValueCallbackResponse(public val success: Boolean, public val value: Value, public val message: String)
/**
* A callback for some visual object trying to change some value
* @author [Alexander Nozik](mailto:altavir@gmail.com)
*/
typealias ValueCallback = (Value) -> ValueCallbackResponse
public typealias ValueCallback = (Value) -> ValueCallbackResponse

View File

@ -10,10 +10,12 @@ import javafx.beans.value.ObservableValue
import javafx.scene.Node
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.meta.descriptors.validate
import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.provider.provideByType
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
@ -42,8 +44,8 @@ public interface ValueChooser {
*
* @return
*/
public val descriptorProperty: ObjectProperty<ValueDescriptor?>
public var descriptor: ValueDescriptor?
public val descriptorProperty: ObjectProperty<MetaDescriptor?>
public var descriptor: MetaDescriptor?
public val valueProperty: ObjectProperty<Value?>
public var value: Value?
@ -71,7 +73,7 @@ public interface ValueChooser {
public companion object {
private fun findWidgetByType(context: Context, type: String): Factory? {
return when (type.toName()) {
return when (Name.parse(type)) {
TextValueChooser.name -> TextValueChooser
ColorValueChooser.name -> ColorValueChooser
ComboBoxValueChooser.name -> ComboBoxValueChooser
@ -79,7 +81,7 @@ public interface ValueChooser {
}
}
private fun build(context: Context, descriptor: ValueDescriptor?): ValueChooser {
private fun build(context: Context, descriptor: MetaDescriptor?): ValueChooser {
return if (descriptor == null) {
TextValueChooser();
} else {
@ -93,7 +95,7 @@ public interface ValueChooser {
descriptor.widget
) ?: TextValueChooser()
}
descriptor.allowedValues.isNotEmpty() -> ComboBoxValueChooser()
!descriptor.allowedValues.isNullOrEmpty() -> ComboBoxValueChooser()
else -> TextValueChooser()
}
chooser.descriptor = descriptor
@ -101,10 +103,10 @@ public interface ValueChooser {
}
}
fun build(
public fun build(
context: Context,
value: ObservableValue<Value?>,
descriptor: ValueDescriptor? = null,
descriptor: MetaDescriptor? = null,
setter: (Value) -> Unit,
): ValueChooser {
val chooser = build(context, descriptor)
@ -113,7 +115,7 @@ public interface ValueChooser {
chooser.setDisplayValue(it ?: Null)
}
chooser.setCallback { result ->
if (descriptor?.isAllowedValue(result) != false) {
if (descriptor?.validate(result) != false) {
setter(result)
ValueCallbackResponse(true, result, "OK")
} else {

View File

@ -8,7 +8,7 @@ package space.kscience.visionforge.editor
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.Node
import org.slf4j.LoggerFactory
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import tornadofx.*
@ -18,16 +18,16 @@ import tornadofx.*
*
* @author Alexander Nozik
*/
abstract class ValueChooserBase<out T : Node> : ValueChooser {
public abstract class ValueChooserBase<out T : Node> : ValueChooser {
override val node by lazy { buildNode() }
final override val valueProperty = SimpleObjectProperty<Value>(Null)
final override val descriptorProperty = SimpleObjectProperty<ValueDescriptor>()
override val node: T by lazy { buildNode() }
final override val valueProperty: SimpleObjectProperty<Value> = SimpleObjectProperty<Value>(Null)
final override val descriptorProperty: SimpleObjectProperty<MetaDescriptor> = SimpleObjectProperty<MetaDescriptor>()
override var descriptor: ValueDescriptor? by descriptorProperty
override var descriptor: MetaDescriptor? by descriptorProperty
override var value: Value? by valueProperty
fun resetValue() {
public fun resetValue() {
setDisplayValue(currentValue())
}
@ -36,7 +36,7 @@ abstract class ValueChooserBase<out T : Node> : ValueChooser {
* @return
*/
protected fun currentValue(): Value {
return value ?: descriptor?.default ?: Null
return value ?: descriptor?.defaultValue ?: Null
}
/**

View File

@ -5,39 +5,36 @@ import javafx.beans.property.SimpleObjectProperty
import javafx.scene.Node
import javafx.scene.Parent
import javafx.scene.layout.VBox
import space.kscience.dataforge.meta.Config
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableItemProvider
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.update
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.*
import tornadofx.*
class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() {
public class VisionEditorFragment(public val selector: (Vision) -> Meta) : Fragment() {
val itemProperty = SimpleObjectProperty<Vision>()
var item: Vision? by itemProperty
val descriptorProperty = SimpleObjectProperty<NodeDescriptor>()
public val itemProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
public var item: Vision? by itemProperty
public val descriptorProperty: SimpleObjectProperty<MetaDescriptor> = SimpleObjectProperty<MetaDescriptor>()
constructor(
public constructor(
item: Vision?,
descriptor: NodeDescriptor?,
selector: (Vision) -> MutableItemProvider = { it.allProperties() },
descriptor: MetaDescriptor?,
selector: (Vision) -> MutableMetaProvider = { it.meta() },
) : this({ it.describedProperties }) {
this.item = item
this.descriptorProperty.set(descriptor)
}
private var currentConfig: Config? = null
private var currentConfig: ObservableMutableMeta? = null
private val configProperty: Binding<Config?> = itemProperty.objectBinding { visualObject ->
if (visualObject == null) return@objectBinding null
val meta = selector(visualObject)
val config = Config().apply {
private val configProperty: Binding<ObservableMutableMeta?> = itemProperty.objectBinding { vision ->
if (vision == null) return@objectBinding null
val meta = selector(vision)
val config = MutableMeta {
update(meta)
onChange(this@VisualObjectEditorFragment) { key, _, after ->
visualObject.setProperty(key, after)
}
config.onChange(this@VisionEditorFragment) { key ->
vision.setPropertyNode(key, config[key])
}
//remember old config reference to cleanup listeners
currentConfig?.removeListener(this)
@ -47,7 +44,7 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() {
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
it?.let {
ConfigEditor(it, descriptorProperty.get()).root
MutableMetaEditor(it, descriptorProperty.get()).root
}
}

View File

@ -3,6 +3,7 @@ package space.kscience.visionforge.editor
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.control.SelectionMode
import javafx.scene.control.TreeItem
import javafx.scene.layout.VBox
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import tornadofx.*
@ -29,13 +30,13 @@ private fun toTreeItem(vision: Vision, title: String): TreeItem<Pair<String, Vis
}
class VisualObjectTreeFragment : Fragment() {
val itemProperty = SimpleObjectProperty<Vision>()
var item: Vision? by itemProperty
public class VisionTreeFragment : Fragment() {
public val itemProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
public var item: Vision? by itemProperty
val selectedProperty = SimpleObjectProperty<Vision>()
public val selectedProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
override val root = vbox {
override val root: VBox = vbox {
titledpane("Object tree", collapsible = false) {
treeview<Pair<String, Vision>> {
cellFormat {

View File

@ -3,11 +3,15 @@ package space.kscience.visionforge.solid
import javafx.scene.paint.Color
import javafx.scene.paint.Material
import javafx.scene.paint.PhongMaterial
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.string
import space.kscience.visionforge.Colors
import space.kscience.visionforge.solid.FXMaterials.GREY
public object FXMaterials {
public val RED: PhongMaterial = PhongMaterial().apply {
@ -26,46 +30,43 @@ public object FXMaterials {
}
public val BLUE: PhongMaterial = PhongMaterial(Color.BLUE)
}
/**
* Infer color based on meta item
* @param opacity default opacity
*/
public fun MetaItem.color(opacity: Double = 1.0): Color {
return when (this) {
is MetaItemValue -> if (this.value.type == ValueType.NUMBER) {
val int = value.int
public fun Meta.color(opacity: Double = 1.0): Color {
return value?.let {
if (it.type == ValueType.NUMBER) {
val int = it.int
val red = int and 0x00ff0000 shr 16
val green = int and 0x0000ff00 shr 8
val blue = int and 0x000000ff
Color.rgb(red, green, blue, opacity)
} else {
Color.web(this.value.string)
Color.web(it.string)
}
is MetaItemNode -> {
Color.rgb(
node[Colors.RED_KEY]?.int ?: 0,
node[Colors.GREEN_KEY]?.int ?: 0,
node[Colors.BLUE_KEY]?.int ?: 0,
node[SolidMaterial.OPACITY_KEY]?.double ?: opacity
} ?: Color.rgb(
this[Colors.RED_KEY]?.int ?: 0,
this[Colors.GREEN_KEY]?.int ?: 0,
this[Colors.BLUE_KEY]?.int ?: 0,
this[SolidMaterial.OPACITY_KEY]?.double ?: opacity
)
}
}
}
/**
* Infer FX material based on meta item
*/
public fun MetaItem?.material(): Material {
return when (this) {
null -> FXMaterials.GREY
is MetaItemValue -> PhongMaterial(color())
is MetaItemNode -> PhongMaterial().apply {
val opacity = node[SolidMaterial.OPACITY_KEY].double ?: 1.0
diffuseColor = node[SolidMaterial.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
specularColor = node[SolidMaterial.SPECULAR_COLOR_KEY]?.color(opacity) ?: Color.WHITE
}
public fun Meta?.material(): Material {
if (this == null) return GREY
return value?.let {
PhongMaterial(color())
} ?: PhongMaterial().apply {
val opacity = get(SolidMaterial.OPACITY_KEY).double ?: 1.0
diffuseColor = get(SolidMaterial.COLOR_KEY)?.color(opacity) ?: Color.DARKGREY
specularColor = get(SolidMaterial.SPECULAR_COLOR_KEY)?.color(opacity) ?: Color.WHITE
}
}

View File

@ -2,7 +2,10 @@ package space.kscience.visionforge.solid
import javafx.scene.Group
import javafx.scene.Node
import space.kscience.dataforge.names.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.cutFirst
import space.kscience.dataforge.names.firstOrNull
import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision
import space.kscience.visionforge.onPropertyChange
import kotlin.reflect.KClass
@ -16,7 +19,7 @@ class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGro
obj.onPropertyChange(plugin.context) { name->
if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
val childName = name.firstOrNull()?.index?.let(Name::parse) ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst()
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")

View File

@ -5,7 +5,6 @@ import javafx.beans.binding.*
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.values.Value
import space.kscience.visionforge.Vision
import space.kscience.visionforge.onPropertyChange
@ -15,7 +14,7 @@ import tornadofx.*
* A caching binding collection for [Vision] properties
*/
public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vision) {
private val bindings = HashMap<Name, ObjectBinding<MetaItem?>>()
private val bindings = HashMap<Name, ObjectBinding<Meta?>>()
init {
obj.onPropertyChange(fx.context) { name ->
@ -33,30 +32,29 @@ public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vi
}
}
public operator fun get(key: Name): ObjectBinding<MetaItem?> {
public operator fun get(key: Name): ObjectBinding<Meta?> {
return bindings.getOrPut(key) {
object : ObjectBinding<MetaItem?>() {
override fun computeValue(): MetaItem? = obj.getProperty(key)
object : ObjectBinding<Meta?>() {
override fun computeValue(): Meta? = obj.getProperty(key)
}
}
}
public operator fun get(key: String) = get(key.toName())
public operator fun get(key: String): ObjectBinding<Meta?> = get(Name.parse(key))
}
public fun ObjectBinding<MetaItem?>.value(): Binding<Value?> = objectBinding { it.value }
public fun ObjectBinding<MetaItem?>.string(): StringBinding = stringBinding { it.string }
public fun ObjectBinding<MetaItem?>.number(): Binding<Number?> = objectBinding { it.number }
public fun ObjectBinding<MetaItem?>.double(): Binding<Double?> = objectBinding { it.double }
public fun ObjectBinding<MetaItem?>.float(): Binding<Float?> = objectBinding { it.float }
public fun ObjectBinding<MetaItem?>.int(): Binding<Int?> = objectBinding { it.int }
public fun ObjectBinding<MetaItem?>.long(): Binding<Long?> = objectBinding { it.long }
public fun ObjectBinding<MetaItem?>.node(): Binding<Meta?> = objectBinding { it.node }
public fun ObjectBinding<Meta?>.value(): Binding<Value?> = objectBinding { it?.value }
public fun ObjectBinding<Meta?>.string(): StringBinding = stringBinding { it.string }
public fun ObjectBinding<Meta?>.number(): Binding<Number?> = objectBinding { it.number }
public fun ObjectBinding<Meta?>.double(): Binding<Double?> = objectBinding { it.double }
public fun ObjectBinding<Meta?>.float(): Binding<Float?> = objectBinding { it.float }
public fun ObjectBinding<Meta?>.int(): Binding<Int?> = objectBinding { it.int }
public fun ObjectBinding<Meta?>.long(): Binding<Long?> = objectBinding { it.long }
public fun ObjectBinding<MetaItem?>.string(default: String): StringBinding = stringBinding { it.string ?: default }
public fun ObjectBinding<MetaItem?>.double(default: Double): DoubleBinding = doubleBinding { it.double ?: default }
public fun ObjectBinding<MetaItem?>.float(default: Float): FloatBinding = floatBinding { it.float ?: default }
public fun ObjectBinding<MetaItem?>.int(default: Int): IntegerBinding = integerBinding { it.int ?: default }
public fun ObjectBinding<MetaItem?>.long(default: Long): LongBinding = longBinding { it.long ?: default }
public fun ObjectBinding<Meta?>.string(default: String): StringBinding = stringBinding { it.string ?: default }
public fun ObjectBinding<Meta?>.double(default: Double): DoubleBinding = doubleBinding { it.double ?: default }
public fun ObjectBinding<Meta?>.float(default: Float): FloatBinding = floatBinding { it.float ?: default }
public fun ObjectBinding<Meta?>.int(default: Int): IntegerBinding = integerBinding { it.int ?: default }
public fun ObjectBinding<Meta?>.long(default: Long): LongBinding = longBinding { it.long ?: default }
public fun <T> ObjectBinding<MetaItem?>.transform(transform: (MetaItem) -> T): Binding<T?> = objectBinding { it?.let(transform) }
public fun <T> ObjectBinding<Meta?>.transform(transform: (Meta) -> T): Binding<T?> = objectBinding { it?.let(transform) }

View File

@ -1,12 +1,12 @@
package space.kscience.visionforge.gdml
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.toName
import space.kscience.gdml.*
import space.kscience.visionforge.*
import space.kscience.visionforge.html.VisionOutput
@ -41,8 +41,8 @@ public class GdmlTransformer {
internal val styleCache = HashMap<Name, Meta>()
public fun Solid.registerAndUseStyle(name: String, builder: MetaBuilder.() -> Unit) {
styleCache.getOrPut(name.toName()) {
public fun Solid.registerAndUseStyle(name: String, builder: MutableMeta.() -> Unit) {
styleCache.getOrPut(Name.parse(name)) {
Meta(builder)
}
useStyle(name)
@ -118,7 +118,7 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
private val proto = SolidGroup()
private val solids = proto.group(solidsName) {
setProperty("edges.enabled", false)
setPropertyNode("edges.enabled", false)
}
private val referenceStore = HashMap<Name, MutableList<SolidReferenceGroup>>()

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.gdml
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.Name
import space.kscience.gdml.*
import space.kscience.visionforge.Vision
import space.kscience.visionforge.get
@ -23,7 +23,7 @@ class TestCubes {
fun testCubesDirect() {
val vision = cubes.toVision()
// println(Solids.encodeToString(vision))
val smallBoxPrototype = vision.getPrototype("solids.smallBox".toName()) as? Box
val smallBoxPrototype = vision.getPrototype(Name.parse("solids.smallBox")) as? Box
assertNotNull(smallBoxPrototype)
assertEquals(30.0, smallBoxPrototype.xSize.toDouble())
val smallBoxVision = vision["composite-111.smallBox"]?.unref as? Box
@ -46,7 +46,7 @@ class TestCubes {
val vision = cubes.toVision()
val serialized = Solids.encodeToString(vision)
val deserialized = testContext.visionManager.decodeFromString(serialized) as SolidGroup
val smallBox = deserialized.getPrototype("solids.smallBox".toName()) as? Box
val smallBox = deserialized.getPrototype(Name.parse("solids.smallBox")) as? Box
assertNotNull(smallBox)
assertEquals(30.0, smallBox.xSize.toDouble())
//println(testContext.visionManager.encodeToString(deserialized))

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.gdml
import org.junit.jupiter.api.Test
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.Name
import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromStream
import space.kscience.visionforge.solid.Solids
@ -23,7 +23,7 @@ class TestConvertor {
val stream = javaClass.getResourceAsStream("/gdml/cubes.gdml")!!
val gdml = Gdml.decodeFromStream(stream)
val vision = gdml.toVision()
assertNotNull(vision.getPrototype("solids.box".toName()))
assertNotNull(vision.getPrototype(Name.parse("solids.box")))
println(Solids.encodeToString(vision))
}

View File

@ -7,7 +7,7 @@ import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.setProperty
@ -27,7 +27,7 @@ public class VisionOfMarkup(
}
public companion object {
public val CONTENT_PROPERTY_KEY: Name = "content".toName()
public val CONTENT_PROPERTY_KEY: Name = "content".asName()
public const val COMMONMARK_FORMAT: String = "markdown.commonmark"
public const val GFM_FORMAT: String = "markdown.gfm"
}

View File

@ -2,7 +2,7 @@ plugins {
id("ru.mipt.npm.gradle.mpp")
}
val plotlyVersion = "0.4.3"
val plotlyVersion = "0.5.0-dev-1"
kscience {
useSerialization()

View File

@ -2,6 +2,8 @@ package space.kscience.visionforge.plotly
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.asObservable
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.plotly.Plot
import space.kscience.plotly.Plotly
@ -13,10 +15,10 @@ import space.kscience.visionforge.root
@SerialName("vision.plotly")
public class VisionOfPlotly private constructor() : VisionBase() {
public constructor(plot: Plot) : this() {
properties = plot.config
properties = plot.meta
}
public val plot: Plot get() = Plot(properties ?: Config())
public val plot: Plot get() = Plot(properties?.asObservable() ?: MutableMeta())
}
public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this)

View File

@ -31,7 +31,7 @@ public actual class PlotlyPlugin : VisionPlugin(), ElementVisionRenderer {
override fun render(element: Element, vision: Vision, meta: Meta) {
val plot = (vision as? VisionOfPlotly)?.plot ?: error("VisionOfPlotly expected but ${vision::class} found")
val config = PlotlyConfig.read(meta)
println(plot.config)
println(plot.meta)
println(plot.data[0].toMeta())
element.plot(plot, config)
}

View File

@ -30,7 +30,6 @@ import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionChange
import space.kscience.visionforge.VisionManager
@ -54,12 +53,12 @@ public class VisionServer internal constructor(
private val application: Application,
private val rootRoute: String,
) : Configurable, CoroutineScope by application {
override val config: ObservableMeta = ObservableMeta()
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
public var cacheFragments: Boolean by config.boolean(true)
public var dataEmbed: Boolean by config.boolean(true, "data.embed".toName())
public var dataFetch: Boolean by config.boolean(false, "data.fetch".toName())
public var dataConnect: Boolean by config.boolean(true, "data.connect".toName())
override val meta: ObservableMutableMeta = MutableMeta()
public var updateInterval: Long by meta.long(300, key = UPDATE_INTERVAL_KEY)
public var cacheFragments: Boolean by meta.boolean(true)
public var dataEmbed: Boolean by meta.boolean(true, Name.parse("data.embed"))
public var dataFetch: Boolean by meta.boolean(false, Name.parse("data.fetch"))
public var dataConnect: Boolean by meta.boolean(true, Name.parse("data.connect"))
/**
* a list of headers that should be applied to all pages
@ -131,14 +130,14 @@ public class VisionServer internal constructor(
?: error("Vision name is not defined in parameters")
application.log.debug("Opened server socket for $name")
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
val vision: Vision = visions[Name.parse(name)] ?: error("Plot with id='$name' not registered")
launch {
incoming.consumeEach {
val change = visionManager.jsonFormat.decodeFromString(
VisionChange.serializer(), it.data.decodeToString()
)
vision.update(change)
vision.change(change)
}
}
@ -161,7 +160,7 @@ public class VisionServer internal constructor(
val name: String = call.request.queryParameters["name"]
?: error("Vision name is not defined in parameters")
val vision: Vision? = visions[name.toName()]
val vision: Vision? = visions[Name.parse(name)]
if (vision == null) {
call.respond(HttpStatusCode.NotFound, "Vision with name '$name' not found")
} else {
@ -232,7 +231,7 @@ public class VisionServer internal constructor(
public companion object {
public const val DEFAULT_PAGE: String = "/"
public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName()
public val UPDATE_INTERVAL_KEY: Name = Name.parse("update.interval")
}
}

View File

@ -1,8 +1,6 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.MutableItemProvider
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.meta.value
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
@ -11,11 +9,17 @@ import space.kscience.visionforge.Colors
import space.kscience.visionforge.VisionBuilder
@VisionBuilder
public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) {
public class ColorAccessor(private val parent: MutableMetaProvider, private val colorKey: Name) {
public var value: Value?
get() = parent.getItem(colorKey).value
get() = parent.getMeta(colorKey)?.value
set(value) {
parent[colorKey] = value
parent.setValue(colorKey,value)
}
public var item: Meta?
get() = parent.getMeta(colorKey)
set(value) {
parent.setMeta(colorKey,value)
}
}

View File

@ -31,7 +31,7 @@ public inline fun VisionContainerBuilder<Solid>.composite(
if (children.size != 2) error("Composite requires exactly two children")
return Composite(type, children[0], children[1]).also { composite ->
composite.configure {
update(group.meta)
update(group.meta())
}
if (group.position != null) {
composite.position = group.position

View File

@ -2,7 +2,8 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.ObservableMeta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.visionforge.*
import kotlin.math.PI
import kotlin.math.cos
@ -95,7 +96,7 @@ public class Extruded(
public class ExtrudeBuilder(
public var shape: List<Point2D> = emptyList(),
public var layers: ArrayList<Layer> = ArrayList(),
config: ObservableMeta = ObservableMeta()
config: ObservableMutableMeta = MutableMeta()
) : SimpleVisionPropertyContainer<Extruded>(config) {
public fun shape(block: Shape2DBuilder.() -> Unit) {
this.shape = Shape2DBuilder().apply(block).build()
@ -105,7 +106,7 @@ public class ExtrudeBuilder(
layers.add(Layer(x.toFloat(), y.toFloat(), z.toFloat(), scale.toFloat()))
}
internal fun build(): Extruded = Extruded(shape, layers).apply { configure(config) }
internal fun build(): Extruded = Extruded(shape, layers).apply { configure(meta()) }
}
@VisionBuilder

View File

@ -2,13 +2,12 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.VisionContainerBuilder
import space.kscience.visionforge.allProperties
import space.kscience.visionforge.numberProperty
import space.kscience.visionforge.set
@Serializable
@ -16,8 +15,8 @@ import space.kscience.visionforge.set
public class PolyLine(public val points: List<Point3D>) : SolidBase(), Solid {
//var lineType by string()
public var thickness: Number by allProperties(inherit = false).number(1.0,
key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)
public var thickness: Number by numberProperty(name = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY) { 1.0 }
public companion object {
public val THICKNESS_KEY: Name = "thickness".asName()

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
@ -35,7 +35,7 @@ import kotlin.reflect.KProperty
*/
public interface Solid : Vision {
override val descriptor: NodeDescriptor get() = Companion.descriptor
override val descriptor: MetaDescriptor get() = Companion.descriptor
public companion object {
// val SELECTED_KEY = "selected".asName()
@ -69,17 +69,15 @@ public interface Solid : Vision {
public val Y_SCALE_KEY: Name = SCALE_KEY + Y_KEY
public val Z_SCALE_KEY: Name = SCALE_KEY + Z_KEY
public val descriptor: NodeDescriptor by lazy {
NodeDescriptor {
value(VISIBLE_KEY) {
public val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
value(VISIBLE_KEY, ValueType.BOOLEAN) {
inherited = false
type(ValueType.BOOLEAN)
default(true)
}
//TODO replace by descriptor merge
value(Vision.STYLE_KEY) {
type(ValueType.STRING)
value(Vision.STYLE_KEY, ValueType.STRING) {
multiple = true
hide()
}
@ -96,12 +94,13 @@ public interface Solid : Vision {
hide()
}
value(DETAIL_KEY) {
type(ValueType.NUMBER)
value(DETAIL_KEY, ValueType.NUMBER) {
hide()
}
item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor)
item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial){
valueRequirement = ValueRequirement.ABSENT
}
enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) {
hide()
@ -115,7 +114,7 @@ public interface Solid : Vision {
* Get the layer number this solid belongs to. Return 0 if layer is not defined.
*/
public var Solid.layer: Int
get() = allProperties().getItem(LAYER_KEY).int ?: 0
get() = getProperty(LAYER_KEY, inherit = true).int ?: 0
set(value) {
setProperty(LAYER_KEY, value)
}
@ -136,7 +135,7 @@ public enum class RotationOrder {
*/
public var Solid.rotationOrder: RotationOrder
get() = getProperty(Solid.ROTATION_ORDER_KEY).enum<RotationOrder>() ?: RotationOrder.XYZ
set(value) = setProperty(Solid.ROTATION_ORDER_KEY, value.name.asValue())
set(value) = setPropertyValue(Solid.ROTATION_ORDER_KEY, value.name.asValue())
/**
@ -144,7 +143,7 @@ public var Solid.rotationOrder: RotationOrder
*/
public var Solid.detail: Int?
get() = getProperty(DETAIL_KEY, false).int
set(value) = setProperty(DETAIL_KEY, value?.asValue())
set(value) = setPropertyValue(DETAIL_KEY, value?.asValue())
/**
* If this property is true, the object will be ignored on render.
@ -152,7 +151,7 @@ public var Solid.detail: Int?
*/
public var Vision.ignore: Boolean?
get() = getProperty(IGNORE_KEY, false).boolean
set(value) = setProperty(IGNORE_KEY, value?.asValue())
set(value) = setPropertyValue(IGNORE_KEY, value?.asValue())
//var VisualObject.selected: Boolean?
// get() = getProperty(SELECTED_KEY).boolean
@ -182,7 +181,7 @@ internal fun point(name: Name, default: Float): ReadWriteProperty<Solid, Point3D
override fun setValue(thisRef: Solid, property: KProperty<*>, value: Point3D?) {
if (value == null) {
thisRef.setProperty(name, null)
thisRef.setPropertyNode(name, null)
} else {
thisRef.setProperty(name + X_KEY, value.x)
thisRef.setProperty(name + Y_KEY, value.y)

View File

@ -2,17 +2,17 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionChange
@Serializable
@SerialName("solid")
public open class SolidBase : VisionBase(), Solid {
override val descriptor: NodeDescriptor get() = Solid.descriptor
override val descriptor: MetaDescriptor get() = Solid.descriptor
override fun update(change: VisionChange) {
override fun change(change: VisionChange) {
updatePosition(change.properties)
super.update(change)
super.change(change)
}
}

View File

@ -2,7 +2,7 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.visionforge.*
@ -40,7 +40,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
}
override val descriptor: NodeDescriptor get() = Solid.descriptor
override val descriptor: MetaDescriptor get() = Solid.descriptor
/**
* Get a prototype redirecting the request to the parent if prototype is not found.
@ -60,9 +60,9 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
override fun createGroup(): SolidGroup = SolidGroup()
override fun update(change: VisionChange) {
override fun change(change: VisionChange) {
updatePosition(change.properties)
super.update(change)
super.change(change)
}
public companion object {

View File

@ -1,8 +1,8 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.attributes
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
@ -26,6 +26,8 @@ public class SolidMaterial : Scheme() {
*/
public val specularColor: ColorAccessor = ColorAccessor(this, SPECULAR_COLOR_KEY)
public val emissiveColor: ColorAccessor = ColorAccessor(this, "emissiveColor".asName())
/**
* Opacity
*/
@ -48,43 +50,37 @@ public class SolidMaterial : Scheme() {
public val WIREFRAME_KEY: Name = "wireframe".asName()
public val MATERIAL_WIREFRAME_KEY: Name = MATERIAL_KEY + WIREFRAME_KEY
public override val descriptor: NodeDescriptor by lazy {
public override val descriptor: MetaDescriptor by lazy {
//must be lazy to avoid initialization bug
NodeDescriptor {
MetaDescriptor {
inherited = true
usesStyles = true
value(COLOR_KEY) {
value(COLOR_KEY, ValueType.STRING, ValueType.NUMBER) {
inherited = true
usesStyles = true
type(ValueType.STRING, ValueType.NUMBER)
widgetType = "color"
}
value(SPECULAR_COLOR_KEY) {
value(SPECULAR_COLOR_KEY, ValueType.STRING, ValueType.NUMBER) {
inherited = true
usesStyles = true
type(ValueType.STRING, ValueType.NUMBER)
widgetType = "color"
hide()
}
value(OPACITY_KEY) {
value(OPACITY_KEY, ValueType.NUMBER) {
inherited = true
usesStyles = true
type(ValueType.NUMBER)
default(1.0)
attributes {
this["min"] = 0.0
this["max"] = 1.0
this["step"] = 0.1
}
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.1
widgetType = "slider"
}
value(WIREFRAME_KEY) {
value(WIREFRAME_KEY, ValueType.BOOLEAN) {
inherited = true
usesStyles = true
type(ValueType.BOOLEAN)
default(false)
}
}
@ -94,21 +90,23 @@ public class SolidMaterial : Scheme() {
public val Solid.color: ColorAccessor
get() = ColorAccessor(
allProperties(inherit = true),
meta(inherit = true),
MATERIAL_COLOR_KEY
)
public var Solid.material: SolidMaterial?
get() = getProperty(MATERIAL_KEY, inherit = true).node?.let { SolidMaterial.read(it) }
set(value) = setProperty(MATERIAL_KEY, value?.rootNode)
get() = getProperty(MATERIAL_KEY, inherit = true)?.let { SolidMaterial.read(it) }
set(value) = setPropertyNode(MATERIAL_KEY, value?.meta)
@VisionBuilder
public fun Solid.material(builder: SolidMaterial.() -> Unit) {
ownProperties.getChild(MATERIAL_KEY).update(SolidMaterial, builder)
configure(MATERIAL_KEY){
updateWith(SolidMaterial,builder)
}
}
public var Solid.opacity: Number?
get() = getProperty(MATERIAL_OPACITY_KEY, inherit = true).number
set(value) {
setProperty(MATERIAL_OPACITY_KEY, value?.asValue())
setPropertyValue(MATERIAL_OPACITY_KEY, value?.asValue())
}

View File

@ -4,11 +4,11 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.MetaItem
import space.kscience.dataforge.meta.asMetaItem
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.visionforge.*
@ -37,7 +37,7 @@ private fun SolidReference.getRefProperty(
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) {
): Meta? = if (!inherit && !includeStyles && !includeDefaults) {
getOwnProperty(name)
} else {
buildList {
@ -88,9 +88,9 @@ public class SolidReferenceGroup(
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults)
): Meta? = getRefProperty(name, inherit, includeStyles, includeDefaults)
override val descriptor: NodeDescriptor get() = prototype.descriptor
override val descriptor: MetaDescriptor get() = prototype.descriptor
/**
@ -118,11 +118,15 @@ public class SolidReferenceGroup(
ReferenceChild(owner, refName + key.asName())
} ?: emptyMap()
override fun getOwnProperty(name: Name): MetaItem? =
override fun getOwnProperty(name: Name): Meta? =
owner.getOwnProperty(childPropertyName(refName, name))
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
owner.setProperty(childPropertyName(refName, name), item, notify)
override fun setPropertyNode(name: Name, node: Meta?, notify: Boolean) {
owner.setPropertyNode(childPropertyName(refName, name), node, notify)
}
override fun setPropertyValue(name: Name, value: Value?, notify: Boolean) {
owner.setPropertyValue(childPropertyName(refName, name), value, notify)
}
override fun getProperty(
@ -130,7 +134,7 @@ public class SolidReferenceGroup(
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults)
): Meta? = getRefProperty(name, inherit, includeStyles, includeDefaults)
override var parent: VisionGroup?
get() {
@ -155,13 +159,13 @@ public class SolidReferenceGroup(
owner.invalidateProperty(childPropertyName(refName, propertyName))
}
override fun update(change: VisionChange) {
override fun change(change: VisionChange) {
change.properties?.let {
updateProperties(Name.EMPTY, it.asMetaItem())
updateProperties(Name.EMPTY, it)
}
}
override val descriptor: NodeDescriptor get() = prototype.descriptor
override val descriptor: MetaDescriptor get() = prototype.descriptor
}
@ -184,7 +188,7 @@ public fun SolidGroup.ref(
public fun SolidGroup.ref(
name: String,
obj: Solid,
templateName: Name = name.toName(),
templateName: Name = Name.parse(name),
): SolidReferenceGroup {
val existing = getPrototype(templateName)
if (existing == null) {

View File

@ -95,13 +95,13 @@ public fun MutablePoint3D.normalizeInPlace() {
z /= norm
}
internal fun ItemProvider.point3D(default: Float = 0f) = object : Point3D {
internal fun MetaProvider.point3D(default: Float = 0f) = object : Point3D {
override val x: Float by float(default)
override val y: Float by float(default)
override val z: Float by float(default)
}
public fun Point3D.toMeta(): MetaBuilder = Meta {
public fun Point3D.toMeta(): Meta = Meta {
X_KEY put x
Y_KEY put y
Z_KEY put z
@ -115,7 +115,7 @@ internal fun Meta.toVector(default: Float = 0f) = Point3D(
)
internal fun Solid.updatePosition(meta: Meta?) {
meta[Solid.POSITION_KEY].node?.toVector()?.let { position = it }
meta[Solid.ROTATION_KEY].node?.toVector()?.let { rotation = it }
meta[Solid.SCALE_KEY].node?.toVector(1f)?.let { scale = it }
meta?.get(Solid.POSITION_KEY)?.toVector()?.let { position = it }
meta?.get(Solid.ROTATION_KEY)?.toVector()?.let { rotation = it }
meta?.get(Solid.SCALE_KEY)?.toVector(1f)?.let { scale = it }
}

View File

@ -3,7 +3,7 @@ package space.kscience.visionforge.solid.specifications
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.double
import space.kscience.visionforge.value
@ -16,8 +16,8 @@ public class Axes : Scheme() {
public const val AXIS_SIZE: Double = 1000.0
public const val AXIS_WIDTH: Double = 3.0
override val descriptor: NodeDescriptor by lazy {
NodeDescriptor {
override val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
value(Axes::visible){
default(false)
}

View File

@ -2,7 +2,7 @@ package space.kscience.visionforge.solid.specifications
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.int
import space.kscience.visionforge.value
@ -27,8 +27,8 @@ public class Camera : Scheme() {
public const val FAR_CLIP: Double = 10000.0
public const val FIELD_OF_VIEW: Int = 75
override val descriptor: NodeDescriptor by lazy {
NodeDescriptor {
override val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
value(Camera::fov){
default(FIELD_OF_VIEW)
}

View File

@ -1,8 +1,7 @@
package space.kscience.visionforge.solid.specifications
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.attributes
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.hide
@ -16,30 +15,26 @@ public class Clipping : Scheme() {
public var z: Double? by double()
public companion object : SchemeSpec<Clipping>(::Clipping) {
override val descriptor: NodeDescriptor = NodeDescriptor {
override val descriptor: MetaDescriptor = MetaDescriptor {
value(Clipping::x) {
widgetType = "range"
attributes {
set("min", 0.0)
set("max", 1.0)
set("step", 0.01)
}
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
}
value(Clipping::y) {
widgetType = "range"
attributes {
set("min", 0.0)
set("max", 1.0)
set("step", 0.01)
}
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
}
value(Clipping::z) {
widgetType = "range"
attributes {
set("min", 0.0)
set("max", 1.0)
set("step", 0.01)
}
attributes["min"] = 0.0
attributes["max"] = 1.0
attributes["step"] = 0.01
}
}
}
@ -55,7 +50,7 @@ public class CanvasSize : Scheme() {
public var maxHeight: Number by number { maxSize }
public companion object : SchemeSpec<CanvasSize>(::CanvasSize) {
override val descriptor: NodeDescriptor = NodeDescriptor {
override val descriptor: MetaDescriptor = MetaDescriptor {
value(CanvasSize::minSize)
value(CanvasSize::minWith)
value(CanvasSize::minHeight)
@ -82,8 +77,8 @@ public class Canvas3DOptions : Scheme() {
public companion object : SchemeSpec<Canvas3DOptions>(::Canvas3DOptions) {
override val descriptor: NodeDescriptor by lazy {
NodeDescriptor {
override val descriptor: MetaDescriptor by lazy {
MetaDescriptor {
scheme(Canvas3DOptions::axes, Axes)
scheme(Canvas3DOptions::light, Light)
@ -104,7 +99,7 @@ public class Canvas3DOptions : Scheme() {
multiple = true
default(listOf(0))
widgetType = "multiSelect"
allow(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
allowedValues(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
}
scheme(Canvas3DOptions::clipping, Clipping)
}

View File

@ -1,12 +1,9 @@
package space.kscience.visionforge.solid.transform
import space.kscience.dataforge.meta.itemSequence
import space.kscience.dataforge.meta.update
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.MutableVisionGroup
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.meta
import space.kscience.visionforge.*
import space.kscience.visionforge.solid.*
private operator fun Number.plus(other: Number) = toFloat() + other.toFloat()
@ -24,10 +21,8 @@ internal fun Vision.updateFrom(other: Vision): Vision {
scaleX *= other.scaleX
scaleY *= other.scaleY
scaleZ *= other.scaleZ
other.meta.itemSequence().forEach { (name, item) ->
if (getProperty(name) == null) {
setProperty(name, item)
}
configure{
update(other.meta())
}
}
return this

View File

@ -1,9 +1,8 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.MetaItemNode
import space.kscience.dataforge.meta.getIndexed
import space.kscience.dataforge.meta.node
import space.kscience.dataforge.meta.toMetaItem
import space.kscience.dataforge.meta.toMeta
import space.kscience.dataforge.misc.DFExperimental
import kotlin.test.Test
import kotlin.test.assertEquals
@ -29,9 +28,9 @@ class ConvexTest {
val convex = group.children.values.first() as Convex
val json = Solids.jsonForSolids.encodeToJsonElement(Convex.serializer(), convex)
val meta = json.toMetaItem().node!!
val meta = json.toMeta()
val points = meta.getIndexed("points").values.map { (it as MetaItemNode<*>).node.point3D() }
val points = meta.getIndexed("points").values.map { it.point3D() }
assertEquals(8, points.count())
assertEquals(8, convex.points.size)

View File

@ -1,6 +1,5 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
@ -16,7 +15,7 @@ class DescriptorTest {
val axesSize = descriptor["axes.size"]
assertNotNull(axesSize)
assertTrue {
ValueType.NUMBER in (axesSize as ValueDescriptor).type!!
ValueType.NUMBER in axesSize.valueTypes!!
}
}
}

View File

@ -12,7 +12,7 @@ class PropertyTest {
fun testInheritedProperty() {
var box: Box? = null
val group = SolidGroup().apply {
setProperty("test", 22)
setPropertyNode("test", 22)
group {
box = box(100, 100, 100)
}

View File

@ -1,7 +1,6 @@
package space.kscience.visionforge.solid
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import space.kscience.visionforge.MutableVisionGroup
import space.kscience.visionforge.get
import space.kscience.visionforge.meta
@ -14,7 +13,7 @@ import kotlin.test.assertEquals
*/
fun SolidGroup.refGroup(
name: String,
templateName: Name = name.toName(),
templateName: Name = Name.parse(name),
block: MutableVisionGroup.() -> Unit
): SolidReferenceGroup {
val group = SolidGroup().apply(block)
@ -33,7 +32,7 @@ class SerializationTest {
val string = Solids.encodeToString(cube)
println(string)
val newCube = Solids.decodeFromString(string)
assertEquals(cube.meta, newCube.meta)
assertEquals(cube.meta(), newCube.meta())
}
@Test
@ -45,7 +44,7 @@ class SerializationTest {
}
val group = SolidGroup {
ref("cube", cube)
refGroup("pg", "pg.content".toName()){
refGroup("pg", Name.parse("pg.content")) {
sphere(50) {
x = -100
}
@ -54,7 +53,7 @@ class SerializationTest {
val string = Solids.encodeToString(group)
println(string)
val reconstructed = Solids.decodeFromString(string) as SolidGroup
assertEquals(group["cube"]?.meta, reconstructed["cube"]?.meta)
assertEquals(group["cube"]?.meta(), reconstructed["cube"]?.meta())
}
}

View File

@ -2,8 +2,9 @@ package space.kscience.visionforge.solid
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.MetaItem
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.asValue
import space.kscience.visionforge.VisionChange
import space.kscience.visionforge.get
import kotlin.test.Test
@ -24,10 +25,10 @@ class VisionUpdateTest {
color(123)
box(100,100,100)
}
propertyChanged("top".toName(), SolidMaterial.MATERIAL_COLOR_KEY, MetaItem.of("red"))
propertyChanged("origin".toName(), SolidMaterial.MATERIAL_COLOR_KEY, MetaItem.of("red"))
propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
propertyChanged("origin".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
}
targetVision.update(dif)
targetVision.change(dif)
assertTrue { targetVision["top"] is SolidGroup }
assertEquals("red", (targetVision["origin"] as Solid).color.string) // Should work
assertEquals("#00007b", (targetVision["top"] as Solid).color.string) // new item always takes precedence
@ -40,8 +41,8 @@ class VisionUpdateTest {
color(123)
box(100,100,100)
}
propertyChanged("top".toName(), SolidMaterial.MATERIAL_COLOR_KEY, MetaItem.of("red"))
propertyChanged("origin".toName(), SolidMaterial.MATERIAL_COLOR_KEY, MetaItem.of("red"))
propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
propertyChanged("origin".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue()))
}
val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change)
println(serialized)

View File

@ -90,7 +90,7 @@ public fun Mesh.applyEdges(obj: Solid) {
MeshThreeFactory.EDGES_MATERIAL_KEY,
inherit = true,
includeStyles = true
).node,
),
true
)
if (edges == null) {

View File

@ -168,10 +168,10 @@ public class ThreeCanvas(
}
//Clipping planes
options.onChange(this@ThreeCanvas) { name, _, _ ->
options.meta.onChange(this@ThreeCanvas) { name->
if (name.startsWith(Canvas3DOptions::clipping.name.asName())) {
val clipping = options.clipping
if (!clipping.isEmpty()) {
if (!clipping.meta.isEmpty()) {
renderer.localClippingEnabled = true
boundingBox?.let { boundingBox ->
val xClippingPlane = clipping.x?.let {
@ -212,9 +212,9 @@ public class ThreeCanvas(
private fun Object3D.fullName(): Name {
if (root == null) error("Can't resolve element name without the root")
return if (parent == root) {
name.toName()
Name.parse(name)
} else {
(parent?.fullName() ?: Name.EMPTY) + name.toName()
(parent?.fullName() ?: Name.EMPTY) + Name.parse(name)
}
}
@ -237,7 +237,7 @@ public class ThreeCanvas(
private fun buildLight(spec: Light?): info.laht.threekt.lights.Light = AmbientLight(0x404040)
private fun addControls(element: Node, controls: Controls) {
when (controls["type"].string) {
when (controls.meta["type"].string) {
"trackball" -> TrackballControls(camera, element)
else -> OrbitControls(camera, element)
}

View File

@ -4,7 +4,6 @@ import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.math.Color
import info.laht.threekt.objects.LineSegments
import space.kscience.dataforge.meta.node
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.PolyLine
import space.kscience.visionforge.solid.color
@ -20,7 +19,10 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
setFromPoints(Array(obj.points.size) { obj.points[it].toVector() })
}
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true)
val material = ThreeMaterials.getLineMaterial(
obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY),
true
)
material.linewidth = obj.thickness.toDouble()
material.color = obj.color.string?.let { Color(it) } ?: DEFAULT_LINE_COLOR

View File

@ -13,7 +13,7 @@ import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.string
import space.kscience.visionforge.Colors
import space.kscience.visionforge.Vision
import space.kscience.visionforge.ownProperties
import space.kscience.visionforge.meta
import space.kscience.visionforge.solid.SolidMaterial
@ -41,7 +41,7 @@ public object ThreeMaterials {
private val lineMaterialCache = HashMap<Meta, LineBasicMaterial>()
private fun buildLineMaterial(meta: Meta): LineBasicMaterial = LineBasicMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_LINE_COLOR
color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_LINE_COLOR
opacity = meta[SolidMaterial.OPACITY_KEY].double ?: 1.0
transparent = opacity < 1.0
linewidth = meta["thickness"].double ?: 1.0
@ -59,11 +59,12 @@ public object ThreeMaterials {
private val materialCache = HashMap<Meta, Material>()
internal fun buildMaterial(meta: Meta): Material {
val material = SolidMaterial.read(meta)
return meta[SolidMaterial.SPECULAR_COLOR_KEY]?.let { specularColor ->
MeshPhongMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
specular = specularColor.getColor()
emissive = specular
color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR
specular = specularColor.threeColor()
emissive = material.emissiveColor.item?.threeColor() ?: specular
reflectivity = 0.5
refractionRatio = 1.0
shininess = 100.0
@ -73,7 +74,7 @@ public object ThreeMaterials {
needsUpdate = true
}
} ?: MeshBasicMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR
opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0
transparent = opacity < 1.0
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
@ -93,23 +94,20 @@ public object ThreeMaterials {
/**
* Infer color based on meta item
*/
public fun MetaItem.getColor(): Color {
return when (this) {
is MetaItemValue -> if (this.value.type == ValueType.NUMBER) {
public fun Meta.threeColor(): Color {
return value?.let { value ->
if (value.type == ValueType.NUMBER) {
val int = value.int
Color(int)
} else {
Color(this.value.string)
Color(value.string)
}
is MetaItemNode -> {
Color(
node[Colors.RED_KEY]?.int ?: 0,
node[Colors.GREEN_KEY]?.int ?: 0,
node[Colors.BLUE_KEY]?.int ?: 0
} ?: Color(
this[Colors.RED_KEY]?.int ?: 0,
this[Colors.GREEN_KEY]?.int ?: 0,
this[Colors.BLUE_KEY]?.int ?: 0
)
}
}
}
private var Material.cached: Boolean
get() = userData["cached"] == true
@ -119,7 +117,7 @@ private var Material.cached: Boolean
public fun Mesh.updateMaterial(vision: Vision) {
//val meta = vision.getProperty(SolidMaterial.MATERIAL_KEY, inherit = true).node
val ownMaterialMeta = vision.ownProperties[SolidMaterial.MATERIAL_KEY]
val ownMaterialMeta = vision.meta()[SolidMaterial.MATERIAL_KEY]
val parentMaterialMeta = vision.parent?.getProperty(
SolidMaterial.MATERIAL_KEY,
inherit = true,
@ -135,7 +133,7 @@ public fun Mesh.updateMaterial(vision: Vision) {
inherit = false,
includeStyles = true,
includeDefaults = false
).node?.let {
)?.let {
ThreeMaterials.cacheMaterial(it)
} ?: ThreeMaterials.DEFAULT
}
@ -143,7 +141,7 @@ public fun Mesh.updateMaterial(vision: Vision) {
vision.getProperty(
SolidMaterial.MATERIAL_KEY,
inherit = true
).node?.let {
)?.let {
ThreeMaterials.buildMaterial(it)
} ?: ThreeMaterials.DEFAULT
}
@ -162,7 +160,7 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
inherit = true,
includeStyles = true,
includeDefaults = false
)?.getColor() ?: ThreeMaterials.DEFAULT_COLOR
)?.threeColor() ?: ThreeMaterials.DEFAULT_COLOR
material.needsUpdate = true
}
SolidMaterial.MATERIAL_OPACITY_KEY -> {

View File

@ -143,7 +143,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
element,
vision as? Solid ?: error("Solid expected but ${vision::class} found"),
).apply {
options.update(meta)
options.meta.update(meta)
}
}
@ -196,7 +196,3 @@ internal fun Object3D.findChild(name: Name): Object3D? {
else -> findChild(name.tokens.first().asName())?.findChild(name.cutFirst())
}
}
public fun Context.withThreeJs(): Context = apply {
plugins.fetch(ThreePlugin)
}

View File

@ -3,9 +3,9 @@ package space.kscience.visionforge.solid.three
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.Mesh
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.cutFirst
import space.kscience.dataforge.names.firstOrNull
import space.kscience.dataforge.names.toName
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidReferenceGroup
@ -49,7 +49,7 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
obj.onPropertyChange(three.updateScope) { name->
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
val childName = name.firstOrNull()?.index?.let(Name::parse) ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst()
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found")

Some files were not shown because too many files have changed in this diff Show More