diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 412460ae..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Gradle build - -on: - push: - branches: [ dev, master ] - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 40 - steps: - - uses: actions/checkout@v2 - - uses: DeLaGuardo/setup-graalvm@4.0 - with: - graalvm: 21.2.0 - java: java11 - arch: amd64 - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} - restore-keys: | - ${{ runner.os }}-gradle- - - uses: actions/cache@v2 - with: - path: ~/.konan - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} - restore-keys: | - ${{ runner.os }}-gradle- - - run: ./gradlew build --build-cache --no-daemon --stacktrace diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 00000000..15ef5105 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,19 @@ +name: Gradle build + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Make gradlew executable + run: chmod +x ./gradlew + - name: Build with Gradle + run: ./gradlew build diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index 134d3d48..00000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Dokka publication - -on: - push: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 40 - steps: - - uses: actions/checkout@v2 - - uses: DeLaGuardo/setup-graalvm@4.0 - with: - graalvm: 21.2.0 - java: java11 - arch: amd64 - - uses: actions/cache@v2 - with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} - restore-keys: | - ${{ runner.os }}-gradle- - - run: ./gradlew dokkaHtmlMultiModule --build-cache --no-daemon --no-parallel --stacktrace - - uses: JamesIves/github-pages-deploy-action@4.1.0 - with: - branch: gh-pages - folder: build/dokka/htmlMultiModule diff --git a/build.gradle.kts b/build.gradle.kts index 2dc7926a..4ab2a7d5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,21 +1,26 @@ plugins { id("ru.mipt.npm.gradle.project") -// kotlin("multiplatform") version "1.5.30-RC" apply false + + //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.1") +val dataforgeVersion by extra("0.4.3") val fxVersion by extra("11") allprojects { repositories { - mavenLocal() mavenCentral() + jcenter() maven("https://repo.kotlin.link") maven("https://maven.jzy3d.org/releases") } group = "space.kscience" - version = "0.2.0-dev-23" + version = "0.2.0-dev-22" } subprojects { @@ -24,7 +29,7 @@ subprojects { } } -ksciencePublish { +ksciencePublish{ github("visionforge") space() sonatype() diff --git a/demo/gdml/build.gradle.kts b/demo/gdml/build.gradle.kts index ead65970..391db4f9 100644 --- a/demo/gdml/build.gradle.kts +++ b/demo/gdml/build.gradle.kts @@ -16,7 +16,7 @@ kotlin { jvm { withJava() } - js { + js{ useCommonJs() browser { commonWebpackConfig { @@ -34,7 +34,6 @@ kotlin { jvmMain { dependencies { implementation(project(":visionforge-fx")) - implementation("ch.qos.logback:logback-classic:1.2.5") } } jsMain { @@ -54,5 +53,5 @@ application { val convertGdmlToJson by tasks.creating(JavaExec::class) { group = "application" classpath = sourceSets["main"].runtimeClasspath - mainClass.set("space.kscience.dataforge.vis.spatial.gdml.demo.SaveToJsonKt") + main = "space.kscience.dataforge.vis.spatial.gdml.demo.SaveToJsonKt" } \ No newline at end of file diff --git a/demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisionTest.kt b/demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisionTest.kt index 12918532..17bdc014 100644 --- a/demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisionTest.kt +++ b/demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisionTest.kt @@ -1,41 +1,32 @@ package space.kscience.visionforge.gdml -import space.kscience.dataforge.names.Name +import space.kscience.dataforge.meta.string +import space.kscience.dataforge.names.toName import space.kscience.dataforge.values.asValue -import space.kscience.dataforge.values.string import space.kscience.gdml.GdmlShowCase -import space.kscience.visionforge.Vision -import space.kscience.visionforge.computeProperties -import space.kscience.visionforge.get import space.kscience.visionforge.setProperty -import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.SolidMaterial -import space.kscience.visionforge.solid.material import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull class GDMLVisionTest { - private val cubes = GdmlShowCase.cubes().toVision() - @Test - fun testCubesStyles(){ - val segment = cubes["composite-000.segment-0"] as Solid - println(segment.computeProperties().getValue(Vision.STYLE_KEY)) -// println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY)) -// println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY)) - - println(segment.material?.meta) - - //println(Solids.encodeToString(cubes)) - } +// @Test +// fun testCubesStyles(){ +// val cubes = gdml.toVision() +// val segment = cubes["composite000.segment_0".toName()] as Solid +// println(segment.styles) +// println(segment.material) +// } @Test fun testPrototypeProperty() { - val child = cubes[Name.of("composite-000","segment-0")] + val vision = GdmlShowCase.cubes().toVision() + val child = vision["composite-000.segment-0".toName()] assertNotNull(child) child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue()) - assertEquals("red", child.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.string) + assertEquals("red", child.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string) } } \ No newline at end of file diff --git a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt index 0c52257d..4f6560b6 100644 --- a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt +++ b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt @@ -1,9 +1,6 @@ package space.kscience.visionforge.gdml.demo import kotlinx.browser.window -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred -import org.w3c.files.File import org.w3c.files.FileReader import org.w3c.files.get import react.* @@ -30,43 +27,32 @@ external interface GDMLAppProps : RProps { @JsExport val GDMLApp = functionalComponent("GDMLApp") { props -> val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager } - var deferredVision: Deferred by useState { - CompletableDeferred(props.vision) - } + var vision: Solid? by useState { props.vision?.apply { root(visionManager) } } - fun readFileAsync(file: File): Deferred { - val deferred = CompletableDeferred() - FileReader().apply { - onload = { - val data = result as String - val name = file.name - val parsedVision = when { - name.endsWith(".gdml") || name.endsWith(".xml") -> { - val gdml = Gdml.decodeFromString(data) - gdml.toVision().apply { - root(visionManager) - console.info("Marking layers for file $name") - markLayers() - } - } - name.endsWith(".json") -> visionManager.decodeFromString(data) - else -> { - window.alert("File extension is not recognized: $name") - error("File extension is not recognized: $name") - } + fun loadData(name: String, data: String) { + val parsedVision = when { + name.endsWith(".gdml") || name.endsWith(".xml") -> { + val gdml = Gdml.decodeFromString(data) + gdml.toVision().apply { + root(visionManager) + console.info("Marking layers for file $name") + markLayers() } - deferred.complete(parsedVision as? Solid ?: error("Parsed vision is not a solid")) } - readAsText(file) + name.endsWith(".json") -> visionManager.decodeFromString(data) + else -> { + window.alert("File extension is not recognized: $name") + error("File extension is not recognized: $name") + } } - return deferred + vision = parsedVision as? Solid ?: error("Parsed vision is not a solid") } child(ThreeCanvasWithControls) { attrs { this.context = props.context - this.builderOfSolid = deferredVision + this.solid = vision this.selected = props.selected tab("Load") { h2 { @@ -75,7 +61,13 @@ val GDMLApp = functionalComponent("GDMLApp") { props -> fileDrop("(drag file here)") { files -> val file = files?.get(0) if (file != null) { - deferredVision = readFileAsync(file) + FileReader().apply { + onload = { + val string = result as String + loadData(file.name, string) + } + readAsText(file) + } } } } diff --git a/demo/gdml/src/jvmMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlFxDemoApp.kt b/demo/gdml/src/jvmMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlFxDemoApp.kt index f2371f44..4fd1e2e7 100644 --- a/demo/gdml/src/jvmMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlFxDemoApp.kt +++ b/demo/gdml/src/jvmMain/kotlin/space/kscience/visionforge/gdml/demo/GdmlFxDemoApp.kt @@ -7,8 +7,9 @@ import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.fetch import space.kscience.gdml.GdmlShowCase import space.kscience.visionforge.VisionManager -import space.kscience.visionforge.editor.VisionEditorFragment -import space.kscience.visionforge.editor.VisionTreeFragment +import space.kscience.visionforge.describedProperties +import space.kscience.visionforge.editor.VisualObjectEditorFragment +import space.kscience.visionforge.editor.VisualObjectTreeFragment import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.solid.FX3DPlugin import space.kscience.visionforge.solid.FXCanvas3D @@ -28,22 +29,25 @@ class GDMLView : View() { private val visionManager = context.fetch(VisionManager) private val canvas = FXCanvas3D(fx3d) - private val treeFragment = VisionTreeFragment().apply { + private val treeFragment = VisualObjectTreeFragment().apply { this.itemProperty.bind(canvas.rootObjectProperty) } - private val propertyEditor = VisionEditorFragment().apply { + private val propertyEditor = VisualObjectEditorFragment { + it.describedProperties + }.apply { descriptorProperty.set(SolidMaterial.descriptor) - visionProperty.bind(treeFragment.selectedProperty) + itemProperty.bind(treeFragment.selectedProperty) } + override val root: Parent = borderpane { top { buttonbar { button("Load GDML/json") { action { val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull() - if (file != null) { + if(file!= null) { runAsync { visionManager.readFile(file) as Solid } ui { diff --git a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt index 55407e4f..ec28649c 100644 --- a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt +++ b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt @@ -1,28 +1,17 @@ import kotlinx.browser.document -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch import kotlinx.css.* import react.child import react.dom.render -import ringui.SmartTabs -import ringui.Tab import space.kscience.dataforge.context.Context -import space.kscience.plotly.models.Trace -import space.kscience.plotly.models.appendXY -import space.kscience.plotly.scatter +import space.kscience.gdml.GdmlShowCase import space.kscience.visionforge.Application import space.kscience.visionforge.VisionClient -import space.kscience.visionforge.plotly.PlotlyPlugin +import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeWithControlsPlugin -import space.kscience.visionforge.ring.solid -import space.kscience.visionforge.solid.* import space.kscience.visionforge.startApplication import styled.css import styled.styledDiv -import kotlin.math.sqrt -import kotlin.random.Random private class JsPlaygroundApp : Application { @@ -31,124 +20,24 @@ private class JsPlaygroundApp : Application { val playgroundContext = Context { plugin(ThreeWithControlsPlugin) plugin(VisionClient) - plugin(PlotlyPlugin) } val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page") - val bouncingSphereTrace = Trace() + val visionOfD0 = GdmlShowCase.babyIaxo().toVision() render(element) { styledDiv { - css { + css{ padding(0.pt) margin(0.pt) height = 100.vh width = 100.vw } - SmartTabs("gravity") { - Tab("gravity") { - styledDiv { - css { - height = 50.vh - } - child(ThreeCanvasWithControls) { - attrs { - context = playgroundContext - solid { - sphere(5.0, "ball") { - detail = 16 - color("red") - val h = 100.0 - y = h - launch { - val g = 10.0 - val dt = 0.1 - var time = 0.0 - var velocity = 0.0 - while (isActive) { - delay(20) - time += dt - velocity -= g * dt - y = y.toDouble() + velocity * dt - bouncingSphereTrace.appendXY(time, y) - if (y.toDouble() <= 2.5) { - //conservation of energy - velocity = sqrt(2 * g * h) - } - } - } - } - - box(200, 5, 200, name = "floor") { - y = -2.5 - } - } - } - } - } - styledDiv { - css { - height = 40.vh - } - - Plotly { - attrs { - context = playgroundContext - plot = space.kscience.plotly.Plotly.plot { - traces(bouncingSphereTrace) - } - } - } - } - } - -// Tab("D0") { -// child(ThreeCanvasWithControls) { -// attrs { -// context = playgroundContext -// solid = GdmlShowCase.babyIaxo().toVision() -// } -// } -// } - Tab("spheres") { - styledDiv { - css { - height = 90.vh - } - child(ThreeCanvasWithControls) { - val random = Random(112233) - attrs { - context = playgroundContext - solid { - repeat(100) { - sphere(5, name = "sphere[$it]") { - x = random.nextDouble(-300.0, 300.0) - y = random.nextDouble(-300.0, 300.0) - z = random.nextDouble(-300.0, 300.0) - material { - color(random.nextInt()) - } - detail = 16 - } - } - } - } - } - } - } - Tab("plotly") { - Plotly { - attrs { - context = playgroundContext - plot = space.kscience.plotly.Plotly.plot { - scatter { - x(1, 2, 3) - y(5, 8, 7) - } - } - } - } + child(ThreeCanvasWithControls) { + attrs { + context = playgroundContext + solid = visionOfD0 } } } diff --git a/demo/js-playground/src/main/kotlin/plotlyComponent.kt b/demo/js-playground/src/main/kotlin/plotlyComponent.kt deleted file mode 100644 index 9480c68b..00000000 --- a/demo/js-playground/src/main/kotlin/plotlyComponent.kt +++ /dev/null @@ -1,34 +0,0 @@ -import kotlinx.css.flex -import org.w3c.dom.Element -import org.w3c.dom.HTMLElement -import react.RProps -import react.functionalComponent -import react.useEffect -import react.useRef -import space.kscience.dataforge.context.Context -import space.kscience.plotly.Plot -import space.kscience.plotly.plot -import styled.css -import styled.styledDiv - -external interface PlotlyProps: RProps{ - var context: Context - var plot: Plot? -} - - -val Plotly = functionalComponent("Plotly"){props -> - val elementRef = useRef(null) - - useEffect(props.plot, elementRef) { - val element = elementRef.current as? HTMLElement ?: error("Plotly element not found") - props.plot?.let { element.plot(it)} - } - - styledDiv { - css { - flex(1.0) - } - ref = elementRef - } -} \ No newline at end of file diff --git a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt index 0c5e0af0..c485a1e3 100644 --- a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt +++ b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Model.kt @@ -6,7 +6,6 @@ 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 @@ -38,10 +37,6 @@ class Model(val manager: VisionManager) { val root: SolidGroup = SolidGroup().apply { root(this@Model.manager) - material { - wireframe - color("darkgreen") - } rotationX = PI / 2 group("bottom") { Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach { @@ -64,7 +59,6 @@ class Model(val manager: VisionManager) { } private fun highlight(pixel: String) { - println("highlight $pixel") map[pixel]?.color?.invoke("blue") } @@ -76,7 +70,6 @@ class Model(val manager: VisionManager) { } fun displayEvent(event: Event) { - println("Received event: $event") events.add(event) event.hits.forEach { highlight(it) @@ -84,7 +77,6 @@ class Model(val manager: VisionManager) { event.track?.let { tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") { thickness = 4 - color("red") } } } diff --git a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt index 3e1db5bc..17d5ac86 100644 --- a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt +++ b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt @@ -98,7 +98,7 @@ class SC16( } val offset = Point3D(-y, x, 0)//rotateDetector(Point3D(x, y, 0.0)); val pixelName = "${name}_${index}" - SC1(pixelName, offset + center) + SC1(pixelName, center + offset) } } } diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt index 8450cb4a..75b610ac 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMAppComponent.kt @@ -2,7 +2,6 @@ package ru.mipt.npm.muon.monitor import io.ktor.client.HttpClient import io.ktor.client.request.get -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.css.* @@ -24,7 +23,6 @@ import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.visionTree import space.kscience.visionforge.solid.specifications.Camera import space.kscience.visionforge.solid.specifications.Canvas3DOptions -import space.kscience.visionforge.solid.three.edges import styled.css import styled.styledDiv import kotlin.math.PI @@ -36,7 +34,6 @@ external interface MMAppProps : RProps { var selected: Name? } -@OptIn(DelicateCoroutinesApi::class) @JsExport val MMApp = functionalComponent("Muon monitor") { props -> var selected by useState { props.selected } @@ -56,11 +53,7 @@ val MMApp = functionalComponent("Muon monitor") { props -> } } - val root = useMemo(props.model) { - props.model.root.apply { - edges() - } - } + val root = props.model.root gridRow { flexColumn { diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt index 9daa6213..5c4a589a 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt @@ -7,15 +7,18 @@ import kotlinx.browser.document import react.child import react.dom.render import space.kscience.dataforge.context.Context +import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.fetch import space.kscience.visionforge.Application import space.kscience.visionforge.VisionManager import space.kscience.visionforge.bootstrap.useBootstrap -import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.startApplication private class MMDemoApp : Application { + private val visionManager = Global.fetch(VisionManager) + private val model = Model(visionManager) + private val connection = HttpClient { install(JsonFeature) { serializer = KotlinxSerializer() @@ -25,18 +28,13 @@ private class MMDemoApp : Application { override fun start(state: Map) { useBootstrap() - val context = Context("MM-demo"){ - plugin(ThreePlugin) - } - val visionManager = context.fetch(VisionManager) - - val model = Model(visionManager) - val element = document.getElementById("app") ?: error("Element with id 'app' not found on page") + + val context = Context("demo") render(element) { child(MMApp) { attrs { - this.model = model + this.model = this@MMDemoApp.model this.connection = this@MMDemoApp.connection this.context = context } diff --git a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/Pixel.kt b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/Pixel.kt index 7cd54417..579bca15 100644 --- a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/Pixel.kt +++ b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/Pixel.kt @@ -16,10 +16,10 @@ import kotlin.random.Random */ internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) { // val layer: Layer = findLayer(center.z); - private val upLayer = findLayer(sc.center.z + sc.zSize / 2f) - //Layer("${name}_up", center.z + zSize / 2.0); - private val bottomLayer = findLayer(sc.center.z - sc.zSize / 2f) - //Layer("${name}_bottom", center.z - zSize / 2.0); + private val upLayer = + findLayer(sc.center.z + sc.zSize / 2f)//Layer("${name}_up", center.z + zSize / 2.0); + private val bottomLayer = + findLayer(sc.center.z - sc.zSize / 2f)//Layer("${name}_bottom", center.z - zSize / 2.0); private val centralLayer = findLayer(sc.center.z) private val center = Vector3D(sc.center.x.toDouble(), sc.center.y.toDouble(), sc.center.z.toDouble()) @@ -115,8 +115,8 @@ internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) { private val auxCache = HashMap() -fun SC1.isHit(track: Line): Boolean { - return auxCache.getOrPut(this) { +fun SC1.isHit(track: Line): Boolean{ + return auxCache.getOrPut(this){ SC1Aux(this) }.isHit(track) } \ No newline at end of file diff --git a/demo/playground/build.gradle.kts b/demo/playground/build.gradle.kts index 68ea5f9a..7c99d6fa 100644 --- a/demo/playground/build.gradle.kts +++ b/demo/playground/build.gradle.kts @@ -20,7 +20,7 @@ kotlin { this.outputFileName = "js/visionforge-playground.js" } commonWebpackConfig { - sourceMaps = true + sourceMaps = false cssSupport.enabled = false } } @@ -37,7 +37,7 @@ kotlin { } afterEvaluate { - val jsBrowserDistribution = tasks.getByName("jsBrowserDevelopmentExecutableDistribution") + val jsBrowserDistribution by tasks.getting tasks.getByName("jvmProcessResources") { dependsOn(jsBrowserDistribution) @@ -67,7 +67,6 @@ 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") } diff --git a/demo/playground/src/jvmMain/kotlin/markdownDemo.kt b/demo/playground/src/jvmMain/kotlin/markdownDemo.kt deleted file mode 100644 index cb4b7cf0..00000000 --- a/demo/playground/src/jvmMain/kotlin/markdownDemo.kt +++ /dev/null @@ -1,91 +0,0 @@ -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") - } - } - } - } - } - } -} \ No newline at end of file diff --git a/demo/playground/src/jvmMain/kotlin/randomSpheres.kt b/demo/playground/src/jvmMain/kotlin/randomSpheres.kt index 0185bdc8..383f3d3a 100644 --- a/demo/playground/src/jvmMain/kotlin/randomSpheres.kt +++ b/demo/playground/src/jvmMain/kotlin/randomSpheres.kt @@ -17,7 +17,7 @@ fun main() { context.makeVisionFile( Paths.get("randomSpheres.html"), - resourceLocation = ResourceLocation.SYSTEM + resourceLocation = ResourceLocation.EMBED ) { h1 { +"Happy new year!" } div { diff --git a/demo/playground/src/jvmMain/kotlin/serverExtensions.kt b/demo/playground/src/jvmMain/kotlin/serverExtensions.kt index e4111afc..b71b1873 100644 --- a/demo/playground/src/jvmMain/kotlin/serverExtensions.kt +++ b/demo/playground/src/jvmMain/kotlin/serverExtensions.kt @@ -26,7 +26,7 @@ public fun Context.makeVisionFile( content: VisionTagConsumer<*>.() -> Unit ): Unit { val actualPath = page(title, content = content).makeFile(path) { actualPath -> - mapOf("playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath)) + mapOf("threeJs" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath)) } if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI()) } diff --git a/demo/playground/src/jvmMain/kotlin/simpleCube.kt b/demo/playground/src/jvmMain/kotlin/simpleCube.kt index f804228b..e98d1ff1 100644 --- a/demo/playground/src/jvmMain/kotlin/simpleCube.kt +++ b/demo/playground/src/jvmMain/kotlin/simpleCube.kt @@ -2,7 +2,9 @@ package space.kscience.visionforge.examples import space.kscience.dataforge.context.Context import space.kscience.visionforge.html.ResourceLocation -import space.kscience.visionforge.solid.* +import space.kscience.visionforge.solid.Solids +import space.kscience.visionforge.solid.box +import space.kscience.visionforge.solid.solid fun main() { val context = Context { @@ -13,9 +15,6 @@ fun main() { vision("canvas") { solid { box(100, 100, 100) - material { - emissiveColor("red") - } } } } diff --git a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt index 546c7b51..c747f89b 100644 --- a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt +++ b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/geometry.kt @@ -17,7 +17,6 @@ internal fun visionOfSatellite( ySegmentSize: Number = xSegmentSize, fiberDiameter: Number = 1.0, ): SolidGroup = SolidGroup { - color("darkgreen") val transparent by style { this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3 } diff --git a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt index 8b286e07..6d80c691 100644 --- a/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt +++ b/demo/sat-demo/src/main/kotlin/ru/mipt/npm/sat/satServer.kt @@ -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.Name +import space.kscience.dataforge.names.toName 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 = Name.parse("layer[$randomLayer].segment[$randomI,$randomJ]") + val target = "layer[$randomLayer].segment[$randomI,$randomJ]".toName() val targetVision = sat[target] as Solid targetVision.color("red") delay(1000) diff --git a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt b/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt index a0ab9273..f0235266 100644 --- a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt +++ b/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt @@ -1,10 +1,14 @@ package space.kscience.visionforge.solid.demo -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +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.Name +import space.kscience.dataforge.names.toName 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 @@ -19,7 +23,7 @@ fun VisionLayout.demo(name: String, title: String = name, block: SolidGro "title" put title } val vision = SolidGroup(block) - render(Name.parse(name), vision, meta) + render(name.toName(), vision) } val canvasOptions = Canvas3DOptions { @@ -36,7 +40,6 @@ val canvasOptions = Canvas3DOptions { } } -@OptIn(DelicateCoroutinesApi::class) fun VisionLayout.showcase() { demo("shapes", "Basic shapes") { box(100.0, 100.0, 100.0) { @@ -74,7 +77,7 @@ fun VisionLayout.showcase() { //override color for this cube color(1530) - GlobalScope.launch(Dispatchers.Main) { + launch(Dispatchers.Main) { while (isActive) { delay(500) visible = !(visible ?: false) @@ -83,7 +86,7 @@ fun VisionLayout.showcase() { } } - GlobalScope.launch(Dispatchers.Main) { + launch(Dispatchers.Main) { val random = Random(111) while (isActive) { delay(1000) diff --git a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/ThreeDemoGrid.kt b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/ThreeDemoGrid.kt index 6dcfa36f..bef5404f 100644 --- a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/ThreeDemoGrid.kt +++ b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/ThreeDemoGrid.kt @@ -15,6 +15,7 @@ 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 diff --git a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt index af828f46..22cac2c6 100644 --- a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt +++ b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt @@ -3,7 +3,6 @@ package space.kscience.visionforge.solid.demo import info.laht.threekt.core.Object3D import info.laht.threekt.geometries.BoxGeometry import info.laht.threekt.objects.Mesh -import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.number import space.kscience.dataforge.names.asName @@ -44,13 +43,13 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision it.layers.enable(this@VariableBox.layer) } } - mesh.scale.z = meta[VALUE].number?.toDouble() ?: 1.0 + mesh.scale.z = getOwnProperty(VALUE).number?.toDouble() ?: 1.0 //add listener to object properties - onPropertyChange { name -> + onPropertyChange(three.context) { name -> when { name == VALUE -> { - val value = meta.get(VALUE).int ?: 0 + val value = getOwnProperty(VALUE).int ?: 0 val size = value.toFloat() / 255f * 20f mesh.scale.z = size.toDouble() mesh.position.z = size.toDouble() / 2 @@ -70,7 +69,7 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision } var value: Int - get() = meta[VALUE].int ?: 0 + get() = getOwnProperty(VALUE).int ?: 0 set(value) { setProperty(VALUE, value.asValue()) } diff --git a/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoApp.kt b/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoApp.kt index 7f278d28..3ce92f0f 100644 --- a/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoApp.kt +++ b/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoApp.kt @@ -14,7 +14,7 @@ class FXDemoApp : App(FXDemoGrid::class) { stage.height = 600.0 view.showcase() - //view.showcaseCSG() + view.showcaseCSG() } } diff --git a/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoGrid.kt b/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoGrid.kt index ef03092a..4e727bb6 100644 --- a/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoGrid.kt +++ b/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoGrid.kt @@ -7,6 +7,7 @@ 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 diff --git a/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/MetaEditorDemo.kt b/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/MetaEditorDemo.kt index 3cdf058e..fc231c38 100644 --- a/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/MetaEditorDemo.kt +++ b/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/MetaEditorDemo.kt @@ -1,14 +1,13 @@ package space.kscience.visionforge.demo import javafx.geometry.Orientation -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.meta.Meta +import space.kscience.dataforge.meta.asConfig +import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.values.ValueType -import space.kscience.visionforge.editor.FXMetaModel +import space.kscience.visionforge.editor.ConfigEditor +import space.kscience.visionforge.editor.FXMeta import space.kscience.visionforge.editor.MetaViewer -import space.kscience.visionforge.editor.MutableMetaEditor import tornadofx.* @@ -16,7 +15,7 @@ class MetaEditorDemoApp : App(MetaEditorDemo::class) class MetaEditorDemo : View("Meta editor demo") { - val meta = MutableMeta { + val meta = Meta { "aNode" put { "innerNode" put { "innerValue" put true @@ -24,16 +23,18 @@ class MetaEditorDemo : View("Meta editor demo") { "b" put 223 "c" put "StringValue" } - } + }.asConfig() - val descriptor = MetaDescriptor { + val descriptor = NodeDescriptor { node("aNode") { info = "A root demo node" - value("b", ValueType.NUMBER) { + value("b") { info = "b number value" + type(ValueType.NUMBER) } node("otherNode") { - value("otherValue", ValueType.BOOLEAN) { + value("otherValue") { + type(ValueType.BOOLEAN) default(false) info = "default value" } @@ -45,13 +46,12 @@ class MetaEditorDemo : View("Meta editor demo") { } } - private val rootNode:FXMetaModel = FXMetaModel.root(meta, descriptor) + private val rootNode = FXMeta.root(meta, descriptor) - override val root = splitpane( - Orientation.HORIZONTAL, - MetaViewer(rootNode).root, - MutableMetaEditor(rootNode).root - ) + override val root = + splitpane(Orientation.HORIZONTAL, MetaViewer(rootNode).root, ConfigEditor( + rootNode + ).root) } fun main() { diff --git a/docs/design.md b/docs/design.md index c6b1b64b..17e52a35 100644 --- a/docs/design.md +++ b/docs/design.md @@ -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 `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). +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). 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: @@ -14,6 +14,6 @@ The actual layering scheme is more complicated. All objects support styling. The ### Intermediate representation -An important thing about VisionForge is that it does not strictly bound to a single format representation. +An important thing about VisionForge is that it does not strictly bound to a single format representation. ### Kotlin DSL for creating vision-graphs \ No newline at end of file diff --git a/docs/hierarchy.md b/docs/hierarchy.md new file mode 100644 index 00000000..a4097221 --- /dev/null +++ b/docs/hierarchy.md @@ -0,0 +1,17 @@ +# Hierarchy + +![](../docs/images/hierarchy.png) +**the image will be changed** + +### Vision + +* function `getProperty(name: Name, inherit: Boolean, includeStyles: Boolean, includeDefaults: Boolean)`. + +It gets properties of element with `name` identification. +`inherit` — toggles parent node property lookup. Null means inference from descriptor. Default is false. +`includeStyles` — toggles inclusion of. Null means inference from descriptor. Default is true. +`includeDefaults` — default is false. + +* function `setProperty(name: Name, item: MetaItem?, notify: Boolean = true)` + +Sets the `item` property to the element with the `name` identification. `notify` is a value which toggles the necessity of the change notification. Default is true. diff --git a/docs/images/hierarchy.png b/docs/images/hierarchy.png new file mode 100644 index 00000000..e202c6af Binary files /dev/null and b/docs/images/hierarchy.png differ diff --git a/docs/images/inheritance-1.png b/docs/images/inheritance-1.png new file mode 100644 index 00000000..3a1a9d73 Binary files /dev/null and b/docs/images/inheritance-1.png differ diff --git a/docs/images/inheritance-2-1-1.png b/docs/images/inheritance-2-1-1.png new file mode 100644 index 00000000..442e7314 Binary files /dev/null and b/docs/images/inheritance-2-1-1.png differ diff --git a/docs/images/inheritance-2-1-2.png b/docs/images/inheritance-2-1-2.png new file mode 100644 index 00000000..42f05ff2 Binary files /dev/null and b/docs/images/inheritance-2-1-2.png differ diff --git a/docs/images/inheritance-2-2-1.png b/docs/images/inheritance-2-2-1.png new file mode 100644 index 00000000..f0dca122 Binary files /dev/null and b/docs/images/inheritance-2-2-1.png differ diff --git a/docs/images/inheritance-2-2-2.png b/docs/images/inheritance-2-2-2.png new file mode 100644 index 00000000..f63f7036 Binary files /dev/null and b/docs/images/inheritance-2-2-2.png differ diff --git a/docs/images/inheritance-2-2-3.png b/docs/images/inheritance-2-2-3.png new file mode 100644 index 00000000..7c89ecde Binary files /dev/null and b/docs/images/inheritance-2-2-3.png differ diff --git a/docs/images/inheritance-2-2-4.png b/docs/images/inheritance-2-2-4.png new file mode 100644 index 00000000..771b9310 Binary files /dev/null and b/docs/images/inheritance-2-2-4.png differ diff --git a/docs/images/inheritance-2-2-5.png b/docs/images/inheritance-2-2-5.png new file mode 100644 index 00000000..3d496817 Binary files /dev/null and b/docs/images/inheritance-2-2-5.png differ diff --git a/docs/images/inheritance-properties.png b/docs/images/inheritance-properties.png new file mode 100644 index 00000000..0a719ea2 Binary files /dev/null and b/docs/images/inheritance-properties.png differ diff --git a/docs/images/inheritance-tree.png b/docs/images/inheritance-tree.png new file mode 100644 index 00000000..058df0f6 Binary files /dev/null and b/docs/images/inheritance-tree.png differ diff --git a/docs/inheritance b/docs/inheritance new file mode 100644 index 00000000..6063d63c --- /dev/null +++ b/docs/inheritance @@ -0,0 +1,68 @@ +## Inheritance + +Inheritance is an ability of an element to transfer a pack of properties to its `children` elements 'wrapped inside'. + +Properties have to be set in a specific order: +### Main properties' inheritance: +* styles +* parents +* parent's styles +* defaults + +As for `prototypes`, this property has to be set after styles, but before parents. So the order will be this: +### Reference properties' inheritance: +* styles +* prototypes +* parents +* parent's styles +* defaults +------------------------ +Let's take a closer look using a [Muon Monitor Visualization](demo/muon-monitor/README.md). +Running the demo, we will see this: + +![](../docs/images/inheritance-1.png) + +You can see a tree of elements on the left; 'World' is a `root`, 'bottom', 'middle', and 'top' are 'World's `children` and so on. + +![](../docs/images/inheritance-tree.png) + +On the right, there is a list with changeable properties. + +![](../docs/images/inheritance-properties.png) + +Properties, which can or cannot be inherited, are these: +* `visible` - toggles the visibility of an element. To be exact, the invisibility of an element is inheritable. + If a `parent` element is invisible, other elements are invisible as well, and they cannot be changed to visible mode. + ![](../docs/images/inheritance-2-1-1.png) + ![](../docs/images/inheritance-2-1-2.png) + +* `material` - a group of properties, which can be inherited and which can be changed in `children` elements. + * `color` - color of an element. + * `opacity` - a number from 0 to 1 which represents percents of opacity (0 for 0%, 1 for 100%). + * `wireframe` - toggles the wireframe mode. + + Let's see how elements of the `material` group inherit changing `color` property; ***other properties of this group inherit in the same way.*** + + Let's change color of 'World' element: + ![](../docs/images/inheritance-2-2-1.png) + It is a `parent`, so 'bottom', 'middle', and 'top' elements inherit this color. + + Now, let's change 'top's color: + ![](../docs/images/inheritance-2-2-2.png) + It changes only, 'bottom' and 'middle' stays the same. + + 'top' is a `parent` element as well: it has `children` - 'SC72', 'SC73', ... ,'SC80'. + Let's change the color of 'SC76': + ![](../docs/images/inheritance-2-2-3.png) + Again, only 'SC76' has changed among other 'siblings'. + + However, 'SC76' is a `parent` too. Let's change one of its `children` color (here we change 'SC76_5's color'): + ![](../docs/images/inheritance-2-2-4.png) + + As we can see, `color` is inheritable property, which can be changed in a custom way. + + If after all those changes we set at the 'World' element grey color, changes won't disappear: + ![](../docs/images/inheritance-2-2-5.png) + +* `rotation` - rotation of an element. Here, it is set by `x` value. It is inheritable and unable to be changed in `children` elements. +* `position` - position of an element, cannot be inherited. \ No newline at end of file diff --git a/docs/uml/Vision.puml b/docs/uml/Vision.puml index 079ca82c..eccbb81f 100644 --- a/docs/uml/Vision.puml +++ b/docs/uml/Vision.puml @@ -1,45 +1,106 @@ @startuml 'https://plantuml.com/class-diagram +interface Vision -interface Vision{ - val parent: Vision? - fun getProperty(name): Meta? - fun setProperty(name, value) -} +interface Solid +Vision <- Solid -class VisionBase{ - basic vision - implementation -} -Vision <|-- VisionBase +class VisionGroup +Vision <-- VisionGroup -interface VisionGroup{ - A group of Visions -} -Vision <|-- VisionGroup +class VisionBase +Vision <- VisionBase -interface Solid{ - The base for 3D geometry -} -Vision <|-- Solid +class SolidLabel +Solid <--- SolidLabel +SolidBase <-- SolidLabel class SolidGroup +Solid <--- SolidGroup +VisionGroupBase <-- SolidGroup -Solid <|-- SolidGroup -VisionGroup <|-- SolidGroup +class SolidBase +Solid <--- SolidBase +VisionBase <-- SolidBase -class Composite -Solid <|-- Composite -VisionGroup <|-- Composite -class Box -Solid <|-- Box - -class Tube -Solid <|-- Tube +class SphereLayer +SolidBase <-- SphereLayer +GeometrySolid <-- SphereLayer class Sphere -Solid <|-- Sphere +SolidBase <-- Sphere +GeometrySolid <-- Sphere +class Box +SolidBase <-- Box +Hexagon <-- Box + +class GenericHexagon +SolidBase <-- GenericHexagon +Hexagon <-- GenericHexagon + +class Extruded +SolidBase <-- Extruded +GeometrySolid <-- Extruded + + +class PolyLine +Solid <--- PolyLine +SolidBase <-- PolyLine + +interface GeometrySolid +Solid <--- GeometrySolid + + +interface Hexagon +GeometrySolid <-- Hexagon + +class ConeSegment +GeometrySolid <-- ConeSegment + +class ConeSurface +GeometrySolid <-- ConeSurface + + +class Convex +Solid <--- Convex +SolidBase <-- Convex + +class Composite +Solid <--- Composite +SolidBase <-- Composite + + +interface SolidReference +VisionGroup <---- SolidReference + +interface MutableVisionGroup +VisionGroup <---- MutableVisionGroup + +class SolidReferenceGroup +VisionGroup <-- SolidReferenceGroup +Solid <-- SolidReferenceGroup +VisionBase <-- SolidReferenceGroup +SolidReference <-- SolidReferenceGroup + +class ReferenceChild +VisionGroup <-- ReferenceChild +Solid <-- ReferenceChild +SolidReference <-- ReferenceChild + + +class VisionGroupBase +VisionBase <-- VisionGroupBase +MutableVisionGroup <-- VisionGroupBase + + + +class RootVisionGroup +VisionGroupBase <-- RootVisionGroup + + +class VisionOfPlotly +VisionBase <-- VisionOfPlotly @enduml \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f..e708b1c0 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 05679dc3..f371643e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 index 744e882e..4f906e0c --- a/gradlew +++ b/gradlew @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MSYS* | MINGW* ) + MINGW* ) msys=true ;; NONSTOP* ) diff --git a/settings.gradle.kts b/settings.gradle.kts index d88c9b81..6311841e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { - val toolsVersion = "0.10.2" + val toolsVersion = "0.10.0" repositories { maven("https://repo.kotlin.link") @@ -33,7 +33,6 @@ include( ":visionforge-gdml", ":visionforge-server", ":visionforge-plotly", - ":visionforge-markdown", ":demo:solid-showcase", ":demo:gdml", ":demo:muon-monitor", diff --git a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/outputConfig.kt b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/outputConfig.kt index 7c7e7b37..3423baf8 100644 --- a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/outputConfig.kt +++ b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/outputConfig.kt @@ -14,7 +14,6 @@ import react.dom.attrs import react.dom.button import space.kscience.dataforge.meta.withDefault import space.kscience.visionforge.Vision -import space.kscience.visionforge.encodeToString import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.react.propertyEditor @@ -44,19 +43,19 @@ public external interface CanvasControlsProps : RProps { public var vision: Vision? } -public val CanvasControls: FunctionComponent = functionalComponent("CanvasControls") { props -> +public val CanvasControls: FunctionalComponent = functionalComponent("CanvasControls") { props -> flexColumn { flexRow { css { border(1.px, BorderStyle.solid, Color.blue) padding(4.px) } - props.vision?.let{ vision -> + props.vision?.manager?.let { manager -> button { +"Export" attrs { onClickFunction = { - val json = vision.encodeToString() + val json = manager.encodeToString(props.vision!!) saveData(it, "object.json", "text/json") { json } @@ -66,8 +65,8 @@ public val CanvasControls: FunctionComponent = functionalCo } } propertyEditor( - ownProperties = props.canvasOptions.meta, - allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode), + ownProperties = props.canvasOptions, + allProperties = props.canvasOptions.withDefault(Canvas3DOptions.descriptor.defaultMeta), descriptor = Canvas3DOptions.descriptor, expanded = false ) diff --git a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/tabComponent.kt b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/tabComponent.kt index 987dbc0e..5ff0d5e8 100644 --- a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/tabComponent.kt +++ b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/tabComponent.kt @@ -18,7 +18,7 @@ public external class TabProps : RProps { } @JsExport -public val Tab: FunctionComponent = functionalComponent { props -> +public val Tab: FunctionalComponent = functionalComponent { props -> props.children() } @@ -27,7 +27,7 @@ public external class TabPaneProps : RProps { } @JsExport -public val TabPane: FunctionComponent = functionalComponent("TabPane") { props -> +public val TabPane: FunctionalComponent = functionalComponent("TabPane") { props -> var activeTab: String? by useState(props.activeTab) val children: Array = Children.map(props.children) { diff --git a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/threeControls.kt b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/threeControls.kt index 95219db1..e8166f35 100644 --- a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/threeControls.kt +++ b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/threeControls.kt @@ -21,7 +21,7 @@ public external interface ThreeControlsProps : RProps { } @JsExport -public val ThreeControls: FunctionComponent = functionalComponent { props -> +public val ThreeControls: FunctionalComponent = functionalComponent { props -> tabPane(if (props.selected != null) "Properties" else null) { tab("Canvas") { card("Canvas configuration") { diff --git a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/visionPropertyEditor.kt b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/visionPropertyEditor.kt index a65c1f42..7f28473e 100644 --- a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/visionPropertyEditor.kt +++ b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/visionPropertyEditor.kt @@ -3,25 +3,23 @@ package space.kscience.visionforge.bootstrap import org.w3c.dom.Element import react.RBuilder import react.dom.render -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.visionforge.Vision -import space.kscience.visionforge.computeProperties -import space.kscience.visionforge.getStyle +import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.visionforge.* 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: MetaDescriptor? = vision.descriptor, + descriptor: NodeDescriptor? = vision.descriptor, key: Any? = null, ) { card("Properties") { propertyEditor( - ownProperties = vision.meta, - allProperties = vision.computeProperties(), + ownProperties = vision.ownProperties, + allProperties = vision.allProperties(), + updateFlow = vision.propertyChanges, descriptor = descriptor, key = key ) @@ -49,7 +47,7 @@ public fun RBuilder.visionPropertyEditor( public fun Element.visionPropertyEditor( item: Vision, - descriptor: MetaDescriptor? = item.descriptor, + descriptor: NodeDescriptor? = item.descriptor, ): Unit = render(this) { visionPropertyEditor(item, descriptor = descriptor) } \ No newline at end of file diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/MetaViewer.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/MetaViewer.kt index 69683bc5..a3972612 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/MetaViewer.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/MetaViewer.kt @@ -8,10 +8,12 @@ import react.* import react.dom.a import react.dom.attrs import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.descriptors.MetaDescriptor +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.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 @@ -39,19 +41,18 @@ public external interface MetaViewerProps : RProps { /** * Root descriptor */ - public var descriptor: MetaDescriptor? + public var descriptor: NodeDescriptor? } -private val MetaViewerItem: FunctionComponent = functionalComponent("MetaViewerItem") { props -> +private val MetaViewerItem: FunctionalComponent = 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: MetaDescriptor? = props.descriptor?.get(props.name) - val actualValue = item?.value ?: descriptorItem?.defaultValue - val actualMeta = item ?: descriptorItem?.defaultNode + val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name) + val actualItem = item ?: descriptorItem?.defaultValue val token = props.name.lastOrNull()?.toString() ?: props.rootName ?: "" @@ -59,75 +60,90 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) { expanded = !expanded } - flexRow { - css { - alignItems = Align.center - } - if (actualMeta?.isLeaf == false) { - styledSpan { + when (actualItem) { + is MetaItemNode -> { + flexRow { css { - +TreeStyles.treeCaret - if (expanded) { - +TreeStyles.treeCaredDown - } + alignItems = Align.center } - attrs { - onClickFunction = expanderClick - } - } - } - - styledSpan { - css { - +TreeStyles.treeLabel - if (item == null) { - +TreeStyles.treeLabelInactive - } - } - +token - } - styledDiv { - a { - +actualValue.toString() - } - } - } - if (expanded) { - flexColumn { - css { - +TreeStyles.tree - } - val keys = buildSet { - descriptorItem?.children?.keys?.forEach { - add(NameToken(it)) - } - actualMeta!!.items.keys.let { addAll(it) } - } - - keys.filter { !it.body.startsWith("@") }.forEach { token -> - styledDiv { + styledSpan { css { - +TreeStyles.treeItem - } - child(MetaViewerItem) { - attrs { - this.key = props.name.toString() - this.root = props.root - this.name = props.name + token - this.descriptor = props.descriptor + +TreeStyles.treeCaret + if (expanded) { + +TreeStyles.treeCaredDown } } - //configEditor(props.root, props.name + token, props.descriptor, props.default) + attrs { + onClickFunction = expanderClick + } + } + styledSpan { + css { + +TreeStyles.treeLabel + if (item == null) { + +TreeStyles.treeLabelInactive + } + } + +token + } + } + if (expanded) { + flexColumn { + css { + +TreeStyles.tree + } + val keys = buildSet { + (descriptorItem as? NodeDescriptor)?.items?.keys?.forEach { + add(NameToken(it)) + } + actualItem.node.items.keys.let { addAll(it) } + } + + keys.filter { !it.body.startsWith("@") }.forEach { token -> + styledDiv { + css { + +TreeStyles.treeItem + } + child(MetaViewerItem) { + attrs { + this.key = props.name.toString() + this.root = props.root + this.name = props.name + token + this.descriptor = props.descriptor + } + } + //configEditor(props.root, props.name + token, props.descriptor, props.default) + } + } + } + } + } + is 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: FunctionComponent = +public val MetaViewer: FunctionalComponent = functionalComponent("MetaViewer") { props -> child(MetaViewerItem) { attrs { @@ -139,7 +155,7 @@ public val MetaViewer: FunctionComponent = } } -public fun RBuilder.metaViewer(meta: Meta, descriptor: MetaDescriptor? = null, key: Any? = null) { +public fun RBuilder.metaViewer(meta: Meta, descriptor: NodeDescriptor? = null, key: Any? = null) { child(MetaViewer) { attrs { this.key = key?.toString() ?: "" diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/MultiSelectChooser.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/MultiSelectChooser.kt index 84181340..a363fcd5 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/MultiSelectChooser.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/MultiSelectChooser.kt @@ -5,28 +5,32 @@ import org.w3c.dom.HTMLOptionElement import org.w3c.dom.HTMLSelectElement import org.w3c.dom.asList import org.w3c.dom.events.Event -import react.FunctionComponent +import react.FunctionalComponent import react.dom.attrs import react.dom.option import react.dom.select import react.functionalComponent -import space.kscience.dataforge.meta.descriptors.allowedValues +import react.useState +import space.kscience.dataforge.meta.value import space.kscience.dataforge.values.asValue import space.kscience.dataforge.values.string @JsExport -public val MultiSelectChooser: FunctionComponent = +public val MultiSelectChooser: FunctionalComponent = functionalComponent("MultiSelectChooser") { props -> + var selectedItems by useState { props.item.value?.list ?: emptyList() } + val onChange: (Event) -> Unit = { event: Event -> - val newSelected = (event.target as HTMLSelectElement).selectedOptions.asList() + val newSelected= (event.target as HTMLSelectElement).selectedOptions.asList() .map { (it as HTMLOptionElement).value.asValue() } - props.meta.value = newSelected.asValue() + props.valueChanged?.invoke(newSelected.asValue()) + selectedItems = newSelected } select { attrs { multiple = true - values = (props.actual.value?.list ?: emptyList()).mapTo(HashSet()) { it.string } + values = selectedItems.mapTo(HashSet()) { it.string } onChangeFunction = onChange } props.descriptor?.allowedValues?.forEach { optionValue -> diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/PropertyEditor.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/PropertyEditor.kt index 9ff77f2c..6e41555b 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/PropertyEditor.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/PropertyEditor.kt @@ -1,5 +1,14 @@ package space.kscience.visionforge.react +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.css.* import kotlinx.css.properties.TextDecoration import kotlinx.html.js.onClickFunction @@ -9,10 +18,15 @@ import react.* import react.dom.attrs import react.dom.render import space.kscience.dataforge.meta.* -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.ValueRequirement +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.get -import space.kscience.dataforge.names.* +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.values.Value import space.kscience.visionforge.hidden import styled.css import styled.styledButton @@ -22,24 +36,34 @@ import styled.styledSpan public external interface PropertyEditorProps : RProps { /** - * Root config object - always non-null + * Root config object - always non null */ - public var meta: ObservableMutableMeta + public var ownProperties: MutableItemProvider /** * Provide default item (greyed out if used) */ - public var withDefault: MetaProvider + public var allProperties: ItemProvider? /** - * Full path to the displayed node in [meta]. Could be empty + * Full path to the displayed node in [ownProperties]. Could be empty */ public var name: Name /** * Root descriptor */ - public var descriptor: MetaDescriptor? + public var descriptor: NodeDescriptor? + + /** + * A coroutine scope for updates + */ + public var scope: CoroutineScope? + + /** + * Flow names of updated properties + */ + public var updateFlow: Flow? /** * Initial expanded state @@ -47,60 +71,67 @@ public external interface PropertyEditorProps : RProps { public var expanded: Boolean? } -private val PropertyEditorItem: FunctionComponent = - functionalComponent("PropertyEditorItem") { props -> +private val PropertyEditorItem: FunctionalComponent = + functionalComponent("ConfigEditorItem") { props -> propertyEditorItem(props) } private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { var expanded: Boolean by useState { props.expanded ?: true } - val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) } - var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) } - - val keys = useMemo(descriptor) { - buildSet { - descriptor?.children?.filterNot { - it.key.startsWith("@") || it.value.hidden - }?.forEach { - add(NameToken(it.key)) - } - //ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) } - } - } + 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 token = props.name.lastOrNull()?.toString() ?: "Properties" fun update() { - ownProperty = props.meta.getOrCreate(props.name) + ownProperty = props.ownProperties.getItem(props.name) } - useEffect(props.meta) { - props.meta.onChange(props) { updatedName -> - if (updatedName == props.name) { - update() + if (props.updateFlow != null) { + useEffect(props.ownProperties, props.updateFlow) { + val updateJob = props.updateFlow!!.onEach { updatedName -> + if (updatedName == props.name) { + update() + } + }.launchIn(props.scope ?: GlobalScope) + cleanup { + updateJob.cancel() } } - cleanup { - props.meta.removeListener(props) - } } val expanderClick: (Event) -> Unit = { expanded = !expanded } - val removeClick: (Event) -> Unit = { - props.meta.remove(props.name) + val valueChanged: (Value?) -> Unit = { + if (it == null) { + props.ownProperties.remove(props.name) + } else { + props.ownProperties[props.name] = it + } update() } + val removeClick: (Event) -> Unit = { + props.ownProperties.remove(props.name) + update() + } - - flexRow { - css { - alignItems = Align.center + if (actualItem is MetaItemNode) { + val keys = buildSet { + (descriptorItem as? NodeDescriptor)?.items?.filterNot { + it.key.startsWith("@") || it.value.hidden + }?.forEach { + add(NameToken(it.key)) + } + ownProperty?.node?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) } } - if (keys.isNotEmpty()) { + // Do not show nodes without visible children + if (keys.isEmpty()) return + + flexRow { styledSpan { css { +TreeStyles.treeCaret @@ -112,30 +143,67 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { onClickFunction = expanderClick } } + styledSpan { + css { + +TreeStyles.treeLabel + if (ownProperty == null) { + +TreeStyles.treeLabelInactive + } + } + +token + } } - styledSpan { - css { - +TreeStyles.treeLabel - if (ownProperty.isEmpty()) { - +TreeStyles.treeLabelInactive + 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) + } } } - +token } - if (!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) { + } else { + flexRow { + css { + alignItems = Align.center + } + styledSpan { + css { + +TreeStyles.treeLabel + if (ownProperty == null) { + +TreeStyles.treeLabelInactive + } + } + +token + } + styledDiv { css { //+TreeStyles.resizeableInput width = 160.px margin(1.px, 5.px) } - ValueChooser{ - attrs { - this.descriptor = descriptor - this.meta = ownProperty - this.actual = props.withDefault.getMeta(props.name) ?: ownProperty - } - } + valueChooser( + props.name, + actualItem, + descriptorItem as? ValueDescriptor, + valueChanged + ) } styledButton { @@ -157,85 +225,83 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { } +"\u00D7" attrs { - if (ownProperty.isEmpty()) { + if (ownProperty == null) { disabled = true } else { onClickFunction = removeClick } } } - } - } - if (expanded) { - flexColumn { - css { - +TreeStyles.tree - } - keys.forEach { token -> - styledDiv { - css { - +TreeStyles.treeItem - } - child(PropertyEditorItem) { - attrs { - this.key = props.name.toString() - this.meta = props.meta - this.withDefault = props.withDefault - this.name = props.name + token - this.descriptor = props.descriptor - } - } - //configEditor(props.root, props.name + token, props.descriptor, props.default) - } - } + } } } + @JsExport -public val PropertyEditor: FunctionComponent = functionalComponent("PropertyEditor") { props -> +public val PropertyEditor: FunctionalComponent = functionalComponent("PropertyEditor") { props -> child(PropertyEditorItem) { attrs { this.key = "" - this.meta = props.meta - this.withDefault = props.withDefault + this.ownProperties = props.ownProperties + this.allProperties = props.allProperties this.name = Name.EMPTY this.descriptor = props.descriptor + this.scope = props.scope this.expanded = props.expanded } } } public fun RBuilder.propertyEditor( - ownProperties: ObservableMutableMeta, - allProperties: MetaProvider = ownProperties, - descriptor: MetaDescriptor? = null, + ownProperties: MutableItemProvider, + allProperties: ItemProvider? = ownProperties, + updateFlow: Flow? = null, + descriptor: NodeDescriptor? = null, + scope: CoroutineScope? = null, key: Any? = null, expanded: Boolean? = null ) { child(PropertyEditor) { attrs { - this.meta = ownProperties - this.withDefault = allProperties + this.ownProperties = ownProperties + this.allProperties = allProperties + this.updateFlow = updateFlow this.descriptor = descriptor this.key = key?.toString() ?: "" + this.scope = scope this.expanded = expanded } } } +@OptIn(ExperimentalCoroutinesApi::class) +private fun Config.flowUpdates(): Flow = callbackFlow { + onChange(this) { name, _, _ -> + launch { + send(name) + } + } + awaitClose { + removeListener(this) + } +} + + public fun RBuilder.configEditor( - config: ObservableMutableMeta, - default: MetaProvider = config, - descriptor: MetaDescriptor? = null, + config: Config, + default: ItemProvider? = null, + descriptor: NodeDescriptor? = null, key: Any? = null, -): Unit = propertyEditor(config, default, descriptor, key = key) + scope: CoroutineScope? = null, +): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key) public fun Element.configEditor( - config: ObservableMutableMeta, - default: Meta = config, - descriptor: MetaDescriptor? = null, + config: Config, + descriptor: NodeDescriptor? = null, + default: Meta? = null, key: Any? = null, + scope: CoroutineScope? = null, ): Unit = render(this) { - configEditor(config, default, descriptor, key = key) + configEditor(config, default, descriptor, key, scope) } \ No newline at end of file diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt index c753271f..2b11143b 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt @@ -6,11 +6,10 @@ import kotlinx.html.InputType import kotlinx.html.js.onChangeFunction import org.w3c.dom.HTMLInputElement import org.w3c.dom.events.Event -import react.FunctionComponent +import react.FunctionalComponent import react.dom.attrs import react.functionalComponent import react.useState -import space.kscience.dataforge.meta.descriptors.ValueRequirement import space.kscience.dataforge.meta.double import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.string @@ -19,34 +18,32 @@ import styled.css import styled.styledInput @JsExport -public val RangeValueChooser: FunctionComponent = +public val RangeValueChooser: FunctionalComponent = functionalComponent("RangeValueChooser") { props -> - var innerValue by useState(props.actual.double) - var rangeDisabled: Boolean by useState(props.meta.value == null) + var innerValue by useState(props.item.double) + var rangeDisabled: Boolean by useState(props.item == null) val handleDisable: (Event) -> Unit = { val checkBoxValue = (it.target as HTMLInputElement).checked rangeDisabled = !checkBoxValue - props.meta.value = if(!checkBoxValue) { - null + if(!checkBoxValue) { + props.valueChanged?.invoke(null) } else { - innerValue?.asValue() + props.valueChanged?.invoke(innerValue?.asValue()) } } val handleChange: (Event) -> Unit = { val newValue = (it.target as HTMLInputElement).value - props.meta.value = newValue.toDoubleOrNull()?.asValue() + props.valueChanged?.invoke(newValue.toDoubleOrNull()?.asValue()) innerValue = newValue.toDoubleOrNull() } flexRow { - if(props.descriptor?.valueRequirement != ValueRequirement.REQUIRED) { - styledInput(type = InputType.checkBox) { - attrs { - defaultChecked = rangeDisabled.not() - onChangeFunction = handleDisable - } + styledInput(type = InputType.checkBox) { + attrs { + defaultChecked = rangeDisabled.not() + onChangeFunction = handleDisable } } @@ -58,7 +55,6 @@ public val RangeValueChooser: FunctionComponent = disabled = rangeDisabled value = innerValue?.toString() ?: "" onChangeFunction = handleChange - consumer.onTagEvent(this, "input", handleChange) val minValue = props.descriptor?.attributes?.get("min").string minValue?.let { min = it diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt index 41034773..6dd563de 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt @@ -21,7 +21,7 @@ public external interface ThreeCanvasProps : RProps { public var selected: Name? } -public val ThreeCanvasComponent: FunctionComponent = functionalComponent( +public val ThreeCanvasComponent: FunctionalComponent = functionalComponent( "ThreeCanvasComponent" ) { props -> val elementRef = useRef(null) diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/VisionTree.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/VisionTree.kt index 6be045d6..bcb7e84a 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/VisionTree.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/VisionTree.kt @@ -107,7 +107,7 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit { } @JsExport -public val ObjectTree: FunctionComponent = functionalComponent("ObjectTree") { props -> +public val ObjectTree: FunctionalComponent = functionalComponent("ObjectTree") { props -> visionTree(props) } diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt index e29f0f8b..2147b191 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt @@ -14,12 +14,9 @@ import react.* import react.dom.attrs import react.dom.option import space.kscience.dataforge.meta.* -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.allowedValues -import space.kscience.dataforge.values.ValueType -import space.kscience.dataforge.values.asValue -import space.kscience.dataforge.values.int -import space.kscience.dataforge.values.string +import space.kscience.dataforge.meta.descriptors.ValueDescriptor +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.values.* import space.kscience.visionforge.Colors import space.kscience.visionforge.widgetType import styled.css @@ -27,26 +24,29 @@ import styled.styledInput import styled.styledSelect public external interface ValueChooserProps : RProps { - public var descriptor: MetaDescriptor? - public var meta: ObservableMutableMeta - public var actual: Meta + public var item: MetaItem? + public var descriptor: ValueDescriptor? + //public var nullable: Boolean? + public var valueChanged: ((Value?) -> Unit)? } @JsExport -public val StringValueChooser: FunctionComponent = +public val StringValueChooser: FunctionalComponent = functionalComponent("StringValueChooser") { props -> - var value by useState(props.actual.string ?: "") + var value by useState(props.item.string ?: "") val keyDown: (Event) -> Unit = { event -> if (event.type == "keydown" && event.asDynamic().key == "Enter") { value = (event.target as HTMLInputElement).value - props.meta.value = value.asValue() + if (value != props.item.string) { + props.valueChanged?.invoke(value.asValue()) + } } } val handleChange: (Event) -> Unit = { value = (it.target as HTMLInputElement).value } styledInput(type = InputType.text) { - css { + css{ width = 100.pct } attrs { @@ -58,28 +58,28 @@ public val StringValueChooser: FunctionComponent = } @JsExport -public val BooleanValueChooser: FunctionComponent = +public val BooleanValueChooser: FunctionalComponent = functionalComponent("BooleanValueChooser") { props -> val handleChange: (Event) -> Unit = { val newValue = (it.target as HTMLInputElement).checked - props.meta.value = newValue.asValue() + props.valueChanged?.invoke(newValue.asValue()) } styledInput(type = InputType.checkBox) { - css { + css{ width = 100.pct } attrs { //this.attributes["indeterminate"] = (props.item == null).toString() - defaultChecked = props.actual.boolean ?: false + defaultChecked = props.item.boolean ?: false onChangeFunction = handleChange } } } @JsExport -public val NumberValueChooser: FunctionComponent = +public val NumberValueChooser: FunctionalComponent = functionalComponent("NumberValueChooser") { props -> - var innerValue by useState(props.actual.string ?: "") + var innerValue by useState(props.item.string ?: "") val keyDown: (Event) -> Unit = { event -> if (event.type == "keydown" && event.asDynamic().key == "Enter") { innerValue = (event.target as HTMLInputElement).value @@ -87,7 +87,7 @@ public val NumberValueChooser: FunctionComponent = if (number == null) { console.error("The input value $innerValue is not a number") } else { - props.meta.value = number.asValue() + props.valueChanged?.invoke(number.asValue()) } } } @@ -95,7 +95,7 @@ public val NumberValueChooser: FunctionComponent = innerValue = (it.target as HTMLInputElement).value } styledInput(type = InputType.number) { - css { + css{ width = 100.pct } attrs { @@ -116,15 +116,15 @@ public val NumberValueChooser: FunctionComponent = } @JsExport -public val ComboValueChooser: FunctionComponent = +public val ComboValueChooser: FunctionalComponent = functionalComponent("ComboValueChooser") { props -> - var selected by useState(props.actual.string ?: "") + var selected by useState(props.item.string ?: "") val handleChange: (Event) -> Unit = { selected = (it.target as HTMLSelectElement).value - props.meta.value = selected.asValue() + props.valueChanged?.invoke(selected.asValue()) } styledSelect { - css { + css{ width = 100.pct } props.descriptor?.allowedValues?.forEach { @@ -133,7 +133,7 @@ public val ComboValueChooser: FunctionComponent = } } attrs { - this.value = props.actual.string ?: "" + this.value = props.item?.string ?: "" multiple = false onChangeFunction = handleChange } @@ -141,20 +141,20 @@ public val ComboValueChooser: FunctionComponent = } @JsExport -public val ColorValueChooser: FunctionComponent = +public val ColorValueChooser: FunctionalComponent = functionalComponent("ColorValueChooser") { props -> var value by useState( - props.actual.value?.let { value -> + props.item.value?.let { value -> if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) else value.string } ?: "#000000" ) val handleChange: (Event) -> Unit = { value = (it.target as HTMLInputElement).value - props.meta.value = value.asValue() + props.valueChanged?.invoke(value.asValue()) } styledInput(type = InputType.color) { - css { + css{ width = 100.pct margin(0.px) } @@ -166,11 +166,11 @@ public val ColorValueChooser: FunctionComponent = } @JsExport -public val ValueChooser: FunctionComponent = functionalComponent("ValueChooser") { props -> +public val ValueChooser: FunctionalComponent = functionalComponent("ValueChooser") { props -> val rawInput by useState(false) val descriptor = props.descriptor - val type = descriptor?.valueTypes?.firstOrNull() + val type = descriptor?.type?.firstOrNull() when { rawInput -> child(StringValueChooser, props) @@ -184,3 +184,19 @@ public val ValueChooser: FunctionComponent = functionalCompon else -> child(StringValueChooser, props) } } + +internal fun RBuilder.valueChooser( + name: Name, + item: MetaItem?, + descriptor: ValueDescriptor? = null, + callback: (Value?) -> Unit, +) { + child(ValueChooser) { + attrs { + key = name.toString() + this.item = item + this.descriptor = descriptor + this.valueChanged = callback + } + } +} diff --git a/ui/ring/build.gradle.kts b/ui/ring/build.gradle.kts index 84b4112f..4469b8cf 100644 --- a/ui/ring/build.gradle.kts +++ b/ui/ring/build.gradle.kts @@ -17,13 +17,13 @@ kotlin{ dependencies{ api(project(":ui:react")) - //api("ru.mipt.npm:ring-ui:0.1.0") - api("org.jetbrains.kotlin-wrappers:kotlin-ring-ui") + //TODO replace by kotlin-wrappers + api("ru.mipt.npm:ring-ui:0.1.0") 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("url-loader","4.1.1")) + compileOnly(npm("postcss-loader","5.2.0")) + compileOnly(npm("source-map-loader","2.0.1")) } \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/ringui/Loader.kt b/ui/ring/src/main/kotlin/ringui/Loader.kt deleted file mode 100644 index c58d51c1..00000000 --- a/ui/ring/src/main/kotlin/ringui/Loader.kt +++ /dev/null @@ -1,19 +0,0 @@ -@file:JsModule("@jetbrains/ring-ui/components/loader/loader") -@file:JsNonModule - -package ringui - -import react.ComponentClass -import react.dom.WithClassName - -// https://github.com/JetBrains/ring-ui/blob/master/components/loader/loader.js -public external interface LoaderProps : WithClassName { - public var size: Number - public var colors: Array - public var message: String - public var stop: Boolean - public var deterministic: Boolean -} - -@JsName("default") -public external val Loader: ComponentClass \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/ringui/LoaderScreen.kt b/ui/ring/src/main/kotlin/ringui/LoaderScreen.kt deleted file mode 100644 index 8d0bf578..00000000 --- a/ui/ring/src/main/kotlin/ringui/LoaderScreen.kt +++ /dev/null @@ -1,16 +0,0 @@ -@file:JsModule("@jetbrains/ring-ui/components/loader-screen/loader-screen") -@file:JsNonModule - -package ringui - -import react.ComponentClass -import react.dom.WithClassName - -// https://github.com/JetBrains/ring-ui/blob/master/components/loader-screen/loader-screen.js -public external interface LoaderScreenProps : WithClassName { - public var containerClassName: String - public var message: String -} - -@JsName("default") -public external val LoaderScreen: ComponentClass \ No newline at end of file diff --git a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt index 06cffdd0..7b427fba 100644 --- a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt +++ b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt @@ -1,44 +1,37 @@ package space.kscience.visionforge.ring -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async -import kotlinx.coroutines.launch import kotlinx.css.* import react.* import react.dom.div import react.dom.span -import ringui.* +import ringui.Island +import ringui.IslandContent +import ringui.IslandHeader +import ringui.Link import space.kscience.dataforge.context.Context import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.length -import space.kscience.visionforge.Vision import space.kscience.visionforge.VisionGroup -import space.kscience.visionforge.computeProperties +import space.kscience.visionforge.allProperties +import space.kscience.visionforge.ownProperties import space.kscience.visionforge.react.ThreeCanvasComponent import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.react.propertyEditor import space.kscience.visionforge.solid.Solid -import space.kscience.visionforge.solid.SolidGroup import space.kscience.visionforge.solid.specifications.Canvas3DOptions import styled.css import styled.styledDiv public external interface ThreeCanvasWithControlsProps : RProps { public var context: Context - public var builderOfSolid: Deferred + public var solid: Solid? public var selected: Name? public var additionalTabs: Map Unit>? } -public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) { - builderOfSolid = context.async { - SolidGroup(block) - } -} - public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) { additionalTabs = (additionalTabs ?: emptyMap()) + (title to block) } @@ -77,16 +70,9 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): ReactElement } @JsExport -public val ThreeCanvasWithControls: FunctionComponent = +public val ThreeCanvasWithControls: FunctionalComponent = functionalComponent("ThreeViewWithControls") { props -> var selected by useState { props.selected } - var solid: Solid? by useState(null) - - useEffect { - props.context.launch { - solid = props.builderOfSolid.await() - } - } val onSelect: (Name?) -> Unit = { selected = it @@ -98,16 +84,15 @@ public val ThreeCanvasWithControls: FunctionComponent solid - else -> (solid as? VisionGroup)?.get(it) + it.isEmpty() -> props.solid + else -> (props.solid as? VisionGroup)?.get(it) } } } - flexRow { css { height = 100.pct @@ -124,42 +109,35 @@ public val ThreeCanvasWithControls: FunctionComponent styledDiv { - css { + css{ position = Position.absolute top = 5.px right = 5.px width = 450.px } - Island { - IslandHeader { + Island{ + IslandHeader{ attrs { border = true } nameCrumbs(selected) { selected = it } } - IslandContent { + IslandContent{ propertyEditor( - ownProperties = vision.meta, - allProperties = vision.computeProperties(), + ownProperties = vision.ownProperties, + allProperties = vision.allProperties(), + updateFlow = vision.propertyChanges, descriptor = vision.descriptor, key = selected ) @@ -174,8 +152,7 @@ public val ThreeCanvasWithControls: FunctionComponent = functionalComponent("CanvasControls") { props -> +internal val CanvasControls: FunctionalComponent = functionalComponent("CanvasControls") { props -> flexColumn { flexRow { css { @@ -72,8 +72,8 @@ internal val CanvasControls: FunctionComponent = functional } } propertyEditor( - ownProperties = props.options.meta, - allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode), + ownProperties = props.options, + allProperties = props.options.withDefault(Canvas3DOptions.descriptor.defaultMeta), descriptor = Canvas3DOptions.descriptor, expanded = false ) @@ -91,7 +91,7 @@ public external interface ThreeControlsProps : RProps { } @JsExport -public val ThreeControls: FunctionComponent = functionalComponent { props -> +public val ThreeControls: FunctionalComponent = functionalComponent { props -> SmartTabs("Tree") { props.vision?.let { Tab("Tree") { diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Colors.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Colors.kt index 58be399e..3f228272 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Colors.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Colors.kt @@ -1,8 +1,6 @@ package space.kscience.visionforge -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.number +import space.kscience.dataforge.meta.* import space.kscience.dataforge.values.ValueType import space.kscience.dataforge.values.int import space.kscience.dataforge.values.string @@ -192,18 +190,25 @@ public object Colors { /** * Convert color represented as Meta to string of format #rrggbb */ - fun fromMeta(meta: Meta): String = meta.value?.let { value -> - //if value is present, use it - if (value.type == ValueType.NUMBER) { - rgbToString(value.int) - } else { - value.string + 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) + } else { + item.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 diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ComputedVisionProperties.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ComputedVisionProperties.kt deleted file mode 100644 index 925eecfe..00000000 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/ComputedVisionProperties.kt +++ /dev/null @@ -1,84 +0,0 @@ -package space.kscience.visionforge - -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.get -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.NameToken -import space.kscience.dataforge.names.plus -import space.kscience.dataforge.values.MutableValueProvider -import space.kscience.dataforge.values.Value - -private class ComputedVisionProperties( - val vision: Vision, - val pathName: Name, - val visionDescriptor: MetaDescriptor, - val parentInheritFlag: Boolean?, - val parentStylesFlag: Boolean? -) : Meta { - - val descriptor: MetaDescriptor? by lazy { visionDescriptor[pathName] } - - override val items: Map - get() { - val metaKeys = vision.meta.getMeta(pathName)?.items?.keys ?: emptySet() - val descriptorKeys = descriptor?.children?.map { NameToken(it.key) } ?: emptySet() - val inheritFlag = descriptor?.inherited ?: parentInheritFlag - val stylesFlag = descriptor?.usesStyles ?: parentStylesFlag - return (metaKeys + descriptorKeys).associateWith { - ComputedVisionProperties( - vision, - pathName + it, - visionDescriptor, - inheritFlag, - stylesFlag - ) - } - } - - override val value: Value? - get() { - val inheritFlag = descriptor?.inherited ?: parentInheritFlag ?: false - val stylesFlag = descriptor?.usesStyles ?: parentStylesFlag ?: true - return vision.getPropertyValue(pathName, inheritFlag, stylesFlag, true) - } - - override fun toString(): String = Meta.toString(this) - override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) - override fun hashCode(): Int = Meta.hashCode(this) -} - -/** - * Compute property node based on inheritance and style information from the descriptor - */ -public fun Vision.computeProperties(descriptor: MetaDescriptor? = this.descriptor): Meta = - if (descriptor == null) meta else ComputedVisionProperties(this, Name.EMPTY, descriptor, null, null) - -public fun Vision.computePropertyNode( - name: Name, - descriptor: MetaDescriptor? = this.descriptor -): Meta? = computeProperties(descriptor)[name] - -/** - * Compute the property based on the provided value descriptor. By default, use Vision own descriptor - */ -public fun Vision.computeProperty(name: Name, valueDescriptor: MetaDescriptor? = descriptor?.get(name)): Value? { - val inheritFlag = valueDescriptor?.inherited ?: false - val stylesFlag = valueDescriptor?.usesStyles ?: true - return getPropertyValue(name, inheritFlag, stylesFlag) -} - -/** - * Accessor to all vision properties - */ -public fun Vision.computePropertyValues( - descriptor: MetaDescriptor? = this.descriptor -): MutableValueProvider = object : MutableValueProvider { - override fun getValue(name: Name): Value? = computeProperty(name, descriptor?.get(name)) - - override fun setValue(name: Name, value: Value?) { - setProperty(name, value) - } -} - diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/StyleReference.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/StyleReference.kt index d5dfac6e..961e937a 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/StyleReference.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/StyleReference.kt @@ -1,7 +1,7 @@ package space.kscience.visionforge import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MutableMeta +import space.kscience.dataforge.meta.MetaBuilder 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: MutableMeta.() -> Unit, + builder: MetaBuilder.() -> Unit, ): ReadOnlyProperty = ReadOnlyProperty { _, property -> val styleName = styleKey ?: property.name styleSheet.define(styleName, Meta(builder)) diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/StyleSheet.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/StyleSheet.kt index 52deee3c..655cb0f3 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/StyleSheet.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/StyleSheet.kt @@ -5,9 +5,6 @@ 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.Value -import space.kscience.dataforge.values.asValue -import space.kscience.dataforge.values.stringList import kotlin.jvm.JvmInline /** @@ -16,9 +13,9 @@ import kotlin.jvm.JvmInline @JvmInline public value class StyleSheet(private val owner: VisionGroup) { - private val styleNode: Meta? get() = owner.meta[STYLESHEET_KEY] + private val styleNode get() = owner.ownProperties[STYLESHEET_KEY].node - public val items: Map? get() = styleNode?.items + public val items: Map? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY } public operator fun get(key: String): Meta? = owner.getStyle(key) @@ -26,7 +23,7 @@ public value class StyleSheet(private val owner: VisionGroup) { * Define a style without notifying owner */ public fun define(key: String, style: Meta?) { - owner.meta.setMeta(STYLESHEET_KEY + key, style) + owner.setProperty(STYLESHEET_KEY + key, style) } /** @@ -43,7 +40,7 @@ public value class StyleSheet(private val owner: VisionGroup) { /** * Create and set a style */ - public operator fun set(key: String, builder: MutableMeta.() -> Unit) { + public operator fun set(key: String, builder: MetaBuilder.() -> Unit) { val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder) set(key, newStyle.seal()) } @@ -73,9 +70,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 - get() = meta.getValue(Vision.STYLE_KEY)?.stringList ?: emptyList() + get() = ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList() set(value) { - meta.setValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue()) + setProperty(Vision.STYLE_KEY, value) } /** @@ -88,7 +85,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 = (meta.getMeta(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name + styles = (ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name } @@ -96,18 +93,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? = - meta.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name) - -/** - * Resolve a property from all styles - */ -public fun Vision.getStyleProperty(name: Name): Value? = styles.firstNotNullOfOrNull { getStyle(it)?.get(name)?.value } + ownProperties[StyleSheet.STYLESHEET_KEY + name].node ?: parent?.getStyle(name) /** * Resolve an item in all style layers */ -public fun Vision.getStyleNodes(name: Name): List = styles.mapNotNull { - getStyle(it)?.get(name) +public fun Vision.getStyleItems(name: Name): List = styles.mapNotNull { + getStyle(it)[name] } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Vision.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Vision.kt index 924dfdd1..07f063ca 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Vision.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/Vision.kt @@ -1,29 +1,26 @@ package space.kscience.visionforge -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.descriptors.Described -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.misc.DFExperimental +import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.get import space.kscience.dataforge.misc.Type import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName -import space.kscience.dataforge.names.startsWith -import space.kscience.dataforge.values.Value -import space.kscience.dataforge.values.asValue -import space.kscience.dataforge.values.boolean +import space.kscience.dataforge.names.toName import space.kscience.visionforge.Vision.Companion.TYPE -import kotlin.reflect.KProperty1 +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** * A root type for display hierarchy */ @Type(TYPE) -public interface Vision : Described, Configurable { +public interface Vision : Described, CoroutineScope { /** * The parent object of this one. If null, this one is a root. @@ -35,23 +32,42 @@ public interface Vision : Described, Configurable { */ public val manager: VisionManager? get() = parent?.manager - /** - * This Vision own properties (ignoring inheritance, styles and defaults - */ - override val meta: ObservableMutableMeta + override val coroutineContext: CoroutineContext + get() = manager?.context?.coroutineContext ?: EmptyCoroutineContext /** - * Get property value with given layer flags. + * Get property. * @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false. - * @param includeStyles toggles inclusion of properties from styles. default is true + * @param includeStyles toggles inclusion of. Null means inference from descriptor. Default is true. */ - public fun getPropertyValue( + public fun getProperty( name: Name, inherit: Boolean = false, includeStyles: Boolean = true, includeDefaults: Boolean = true, - ): Value? + ): MetaItem? + /** + * 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( + name, + inherit = false, + includeStyles = false, + includeDefaults = false + ) + + /** + * Set the property value + */ + public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true) + + /** + * Flow of property invalidation events. It does not contain property values after invalidation since it is not clear + * if it should include inherited properties etc. + */ + public val propertyChanges: Flow /** * Notify all listeners that a property has been changed and should be invalidated @@ -63,7 +79,7 @@ public interface Vision : Described, Configurable { */ public fun update(change: VisionChange) - override val descriptor: MetaDescriptor? + override val descriptor: NodeDescriptor? public companion object { public const val TYPE: String = "vision" @@ -74,74 +90,66 @@ public interface Vision : Described, Configurable { } /** - * Flow of property invalidation events. It does not contain property values after invalidation since it is not clear - * if it should include inherited properties etc. + * Root property node */ -@OptIn(ExperimentalCoroutinesApi::class) -@DFExperimental -public val Vision.propertyChanges: Flow - get() = callbackFlow { - meta.onChange(this) { name -> - launch { - send(name) - } - } - awaitClose { - meta.removeListener(this) - } - } +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 */ -public fun Vision.onPropertyChange(callback: Meta.(Name) -> Unit) { - meta.onChange(null, callback) +public fun Vision.onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) { + propertyChanges.onEach(callback).launchIn(scope) +} + + +/** + * 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, + 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 + ) + + override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item) } /** * Get [Vision] property using key as a String */ -public fun Vision.getPropertyValue( +public fun Vision.getProperty( key: String, inherit: Boolean = false, includeStyles: Boolean = true, includeDefaults: Boolean = true, -): Value? = getPropertyValue(Name.parse(key), inherit, includeStyles, includeDefaults) +): MetaItem? = getProperty(key.toName(), inherit, includeStyles, includeDefaults) /** - * A convenience method to set property node or value. If Item is null, then node is removed, not a value + * A convenience method to pair [getProperty] */ -public fun Vision.setProperty(name: Name, item: Any?) { - when (item) { - null -> meta.remove(name) - is Meta -> meta.setMeta(name, item) - is Value -> meta.setValue(name, item) - else -> meta.setValue(name, Value.of(item)) - } -} - -public fun Vision.setPropertyNode(key: String, item: Any?) { - setProperty(Name.parse(key), item) +public fun Vision.setProperty(key: Name, item: Any?) { + setProperty(key, MetaItem.of(item)) } /** - * Control visibility of the element + * A convenience method to pair [getProperty] */ -public var Vision.visible: Boolean? - get() = getPropertyValue(Vision.VISIBLE_KEY)?.boolean - set(value) = meta.setValue(Vision.VISIBLE_KEY, value?.asValue()) - - -public fun V.useProperty( - property: KProperty1, - owner: Any? = null, - callBack: V.(T) -> Unit, -) { - //Pass initial value. - callBack(property.get(this)) - meta.onChange(owner) { name -> - if (name.startsWith(property.name.asName())) { - callBack(property.get(this@useProperty)) - } - } -} \ No newline at end of file +public fun Vision.setProperty(key: String, item: Any?) { + setProperty(key.toName(), MetaItem.of(item)) +} diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionBase.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionBase.kt index 74f173ae..08927448 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionBase.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionBase.kt @@ -1,168 +1,136 @@ package space.kscience.visionforge +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.ObservableMutableMeta -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.value -import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.* +import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.misc.DFExperimental -import space.kscience.dataforge.names.* -import space.kscience.dataforge.values.Value +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.ValueType import space.kscience.visionforge.Vision.Companion.STYLE_KEY import kotlin.jvm.Synchronized -internal data class MetaListener( - val owner: Any? = null, - val callback: Meta.(name: Name) -> Unit, -) - /** * A full base implementation for a [Vision] - * @param parent the parent object for this vision. Could ve set later. Not serialized. + * @param properties Object own properties excluding styles and inheritance */ @Serializable @SerialName("vision") public open class VisionBase( - @Transient override var parent: VisionGroup? = null, + override @Transient var parent: VisionGroup? = null, + protected var properties: Config? = null ) : Vision { - @Transient - protected open var properties: MutableMeta? = null - @Synchronized - protected fun getOrCreateProperties(): MutableMeta { + protected fun getOrCreateProperties(): Config { if (properties == null) { - val newProperties = MutableMeta() + val newProperties = Config() properties = newProperties } return properties!! } - @Transient - private val listeners: MutableList = mutableListOf() - - private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta { - - override val items: Map - get() = properties?.get(pathName)?.items?.mapValues { entry -> - VisionProperties(pathName + entry.key) - } ?: emptyMap() - - override var value: Value? - get() = properties?.get(pathName)?.value - set(value) { - val oldValue = properties?.get(pathName)?.value - getOrCreateProperties().setValue(pathName, value) - if (oldValue != value) { - invalidate(Name.EMPTY) - } - } - - override fun getOrCreate(name: Name): ObservableMutableMeta = VisionProperties(pathName + name) - - override fun setMeta(name: Name, node: Meta?) { - getOrCreateProperties().setMeta(pathName + name, node) - invalidate(name) - } - - @DFExperimental - override fun attach(name: Name, node: ObservableMutableMeta) { - val ownProperties = getOrCreateProperties() - if (ownProperties is ObservableMutableMeta) { - ownProperties.attach(pathName + name, node) - } else { - ownProperties.setMeta(pathName + name, node) - node.onChange(this) { childName -> - ownProperties.setMeta(pathName + name + childName, this[childName]) - } - } - } - - override fun invalidate(name: Name) { - invalidateProperty(pathName + name) - } - - @Synchronized - override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { - if (pathName.isEmpty()) { - listeners.add((MetaListener(owner, callback))) - } else { - listeners.add(MetaListener(owner) { name -> - if (name.startsWith(pathName)) { - (this@MetaListener[pathName] ?: Meta.EMPTY).callback(name.removeHeadOrNull(pathName)!!) - } - }) - } - } - - @Synchronized - override fun removeListener(owner: Any?) { - listeners.removeAll { it.owner === owner } - } - - override fun toString(): String = Meta.toString(this) - override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) - override fun hashCode(): Int = Meta.hashCode(this) + /** + * A fast accessor method to get own property (no inheritance or styles) + */ + override fun getOwnProperty(name: Name): MetaItem? = if (name == Name.EMPTY) { + properties?.asMetaItem() + } else { + properties?.getItem(name) } - override val meta: ObservableMutableMeta get() = VisionProperties(Name.EMPTY) - - override fun getPropertyValue( + override fun getProperty( name: Name, inherit: Boolean, includeStyles: Boolean, includeDefaults: Boolean, - ): Value? { - properties?.get(name)?.value?.let { return it } - if (includeStyles) { - getStyleProperty(name)?.let { return it } - } - if (inherit) { - parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it } - } - if (includeDefaults) { - descriptor?.defaultNode?.get(name)?.value.let { return it } - } - return null + ): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) { + getOwnProperty(name) + } else { + buildList { + add(getOwnProperty(name)) + if (includeStyles) { + addAll(getStyleItems(name)) + } + if (inherit) { + add(parent?.getProperty(name, inherit, includeStyles, includeDefaults)) + } + if (includeDefaults) { + add(descriptor?.defaultMeta?.get(name)) + } + }.merge() } - override val descriptor: MetaDescriptor? get() = null + override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { + val oldItem = properties?.getItem(name) + if(oldItem!= item) { + getOrCreateProperties().setItem(name, item) + if (notify) { + invalidateProperty(name) + } + } + } + override val descriptor: NodeDescriptor? get() = null + + private suspend fun updateStyles(names: List) { + names.mapNotNull { getStyle(it) }.asSequence() + .flatMap { it.items.asSequence() } + .distinctBy { it.key } + .forEach { + invalidateProperty(it.key.asName()) + } + } + + //TODO check memory consumption for the flow + @Transient + private val propertyInvalidationFlow: MutableSharedFlow = MutableSharedFlow() + + @DFExperimental + override val propertyChanges: Flow + get() = propertyInvalidationFlow override fun invalidateProperty(propertyName: Name) { - if (propertyName == STYLE_KEY) { - styles.mapNotNull { getStyle(it) }.asSequence() - .flatMap { it.items.asSequence() } - .distinctBy { it.key } - .forEach { - invalidateProperty(it.key.asName()) - } + launch { + if (propertyName == STYLE_KEY) { + updateStyles(styles) + } + propertyInvalidationFlow.emit(propertyName) } - listeners.forEach { it.callback(properties ?: Meta.EMPTY, propertyName) } } override fun update(change: VisionChange) { change.properties?.let { - updateProperties(Name.EMPTY, it) + updateProperties(Name.EMPTY, it.asMetaItem()) } } public companion object { - public val descriptor: MetaDescriptor = MetaDescriptor { - value(STYLE_KEY, ValueType.STRING) { + public val descriptor: NodeDescriptor = NodeDescriptor { + value(STYLE_KEY) { + type(ValueType.STRING) multiple = true } } - public fun Vision.updateProperties(at: Name, item: Meta) { - meta.setValue(at, item.value) - item.items.forEach { (token, item) -> - updateProperties(at + token, item) + 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) + } } } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt index c4f18712..06ccb7bc 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt @@ -21,7 +21,7 @@ public class VisionChangeBuilder : VisionContainerBuilder { private var reset: Boolean = false private var vision: Vision? = null - private val propertyChange = MutableMeta() + private val propertyChange = Config() private val children: HashMap = HashMap() public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty() @@ -30,17 +30,17 @@ public class VisionChangeBuilder : VisionContainerBuilder { private fun getOrPutChild(visionName: Name): VisionChangeBuilder = children.getOrPut(visionName) { VisionChangeBuilder() } - public fun propertyChanged(visionName: Name, propertyName: Name, item: Meta?) { + public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem?) { if (visionName == Name.EMPTY) { //Write property removal as [Null] - propertyChange[propertyName] = (item ?: Meta(Null)) + propertyChange[propertyName] = (item ?: Null.asMetaItem()) } else { getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item) } } override fun set(name: Name?, child: Vision?) { - if (name == null) error("Static children are not allowed in VisionChange") + if(name == null) error("Static children are not allowed in VisionChange") getOrPutChild(name).apply { vision = child reset = vision == null @@ -82,7 +82,6 @@ public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilde VisionChangeBuilder().apply(block).isolate(manager) -@OptIn(DFExperimental::class) private fun CoroutineScope.collectChange( name: Name, source: Vision, @@ -90,8 +89,8 @@ private fun CoroutineScope.collectChange( ) { //Collect properties change - source.onPropertyChange { propertyName -> - val newItem = source.meta[propertyName] + source.onPropertyChange(this) { propertyName -> + val newItem = source.ownProperties[propertyName] collector().propertyChanged(name, propertyName, newItem) } @@ -103,13 +102,11 @@ private fun CoroutineScope.collectChange( //Subscribe for structure change if (source is MutableVisionGroup) { - source.structureChanges.onEach { changedName -> - val after = source[changedName] - val fullName = name + changedName + source.structureChanges.onEach { (token, _, after) -> if (after != null) { - collectChange(fullName, after, collector) + collectChange(name + token, after, collector) } - collector()[fullName] = after + collector()[name + token] = after }.launchIn(this) } } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt index 12fe243b..6b35d971 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt @@ -1,17 +1,9 @@ package space.kscience.visionforge -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.launch -import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.* import space.kscience.dataforge.provider.Provider -@DslMarker -public annotation class VisionBuilder - public interface VisionContainer { public operator fun get(name: Name): V? } @@ -74,36 +66,21 @@ public interface VisionContainerBuilder { * Mutable version of [VisionGroup] */ public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder { - public fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit) - public fun removeStructureListener(owner: Any?) + public data class StructureChange(val token: NameToken, val before: Vision?, val after: Vision?) + + /** + * Flow structure changes of this group. Unconsumed changes are discarded + */ + public val structureChanges: Flow } - -/** - * Flow structure changes of this group. Unconsumed changes are discarded - */ -@OptIn(ExperimentalCoroutinesApi::class) -@DFExperimental -public val MutableVisionGroup.structureChanges: Flow - get() = callbackFlow { - meta.onChange(this) { name -> - launch { - send(name) - } - } - awaitClose { - removeStructureListener(this) - } - } - - -public operator fun VisionContainer.get(str: String): V? = get(Name.parse(str)) +public operator fun VisionContainer.get(str: String): V? = get(str.toName()) public operator fun VisionContainerBuilder.set(token: NameToken, child: V?): Unit = set(token.asName(), child) public operator fun VisionContainerBuilder.set(key: String?, child: V?): Unit = - set(key?.let(Name::parse), child) + set(key?.toName(), child) public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroupBase.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroupBase.kt index 7280ae1f..933b55cd 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroupBase.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroupBase.kt @@ -1,12 +1,13 @@ package space.kscience.visionforge +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import space.kscience.dataforge.names.* -import kotlin.jvm.Synchronized -private class StructureChangeListener(val owner: Any?, val callback: VisionGroup.(Name) -> Unit) /** * Abstract implementation of mutable group of [Vision] @@ -39,24 +40,16 @@ public open class VisionGroupBase( } @Transient - private val structureListeners = HashSet() + private val _structureChanges: MutableSharedFlow = MutableSharedFlow() - @Synchronized - override fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit) { - structureListeners.add(StructureChangeListener(owner, block)) - } - - @Synchronized - override fun removeStructureListener(owner: Any?) { - structureListeners.removeAll { it.owner == owner } - } + override val structureChanges: SharedFlow get() = _structureChanges /** * Propagate children change event upwards */ - protected fun childrenChanged(name: Name) { - structureListeners.forEach { - it.callback(this, name) + private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) { + launch { + _structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after)) } } @@ -90,12 +83,7 @@ public open class VisionGroupBase( } } if (before != child) { - childrenChanged(token.asName()) - if (child is MutableVisionGroup) { - child.onStructureChanged(this) { changedName -> - this@VisionGroupBase.childrenChanged(token + changedName) - } - } + childrenChanged(token, before, child) } } @@ -163,6 +151,6 @@ internal class RootVisionGroup(override val manager: VisionManager) : VisionGrou /** * Designate this [VisionGroup] as a root group and assign a [VisionManager] as its parent */ -public fun Vision.root(manager: VisionManager) { +public fun Vision.root(manager: VisionManager){ parent = RootVisionGroup(manager) } \ No newline at end of file diff --git a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/VisionLayout.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionLayout.kt similarity index 69% rename from demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/VisionLayout.kt rename to visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionLayout.kt index 016f2ecf..1a0fddfc 100644 --- a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/VisionLayout.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionLayout.kt @@ -1,8 +1,7 @@ -package space.kscience.visionforge.solid.demo +package space.kscience.visionforge import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.names.Name -import space.kscience.visionforge.Vision public interface VisionLayout { public fun render(name: Name, vision: V, meta: Meta = Meta.EMPTY) diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt index 3f9bfaa8..4f01e839 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt @@ -8,10 +8,12 @@ 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.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.node import space.kscience.dataforge.meta.toJson -import space.kscience.dataforge.meta.toMeta +import space.kscience.dataforge.meta.toMetaItem import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.toName import kotlin.reflect.KClass public class VisionManager(meta: Meta) : AbstractPlugin(meta) { @@ -46,11 +48,12 @@ 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: MetaDescriptor? = null): Vision = + public fun decodeFromMeta(meta: Meta, descriptor: NodeDescriptor? = null): Vision = decodeFromJson(meta.toJson(descriptor)) - public fun encodeToMeta(vision: Vision, descriptor: MetaDescriptor? = null): Meta = - encodeToJsonElement(vision).toMeta(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 companion object : PluginFactory { override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP) @@ -86,7 +89,7 @@ public abstract class VisionPlugin(meta: Meta = Meta.EMPTY) : AbstractPlugin(met protected abstract val visionSerializersModule: SerializersModule override fun content(target: String): Map = when (target) { - VisionManager.VISION_SERIALIZER_MODULE_TARGET -> mapOf(Name.parse(tag.toString()) to visionSerializersModule) + VisionManager.VISION_SERIALIZER_MODULE_TARGET -> mapOf(tag.toString().toName() to visionSerializersModule) else -> super.content(target) } } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionPropertyContainer.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionPropertyContainer.kt index fed474fc..112295c9 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionPropertyContainer.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionPropertyContainer.kt @@ -1,34 +1,35 @@ package space.kscience.visionforge -import space.kscience.dataforge.meta.Configurable -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.ObservableMutableMeta +import space.kscience.dataforge.meta.Config +import space.kscience.dataforge.meta.MetaItem import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.set 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 */ -public interface VisionPropertyContainer { - - public val meta: MutableMeta - - public fun getPropertyValue( +public interface VisionPropertyContainer { + public fun getProperty( name: Name, inherit: Boolean = false, includeStyles: Boolean = true, includeDefaults: Boolean = true, - ): Value? + ): MetaItem? + + public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true) } -public open class SimpleVisionPropertyContainer( - override val meta: ObservableMutableMeta, -) : VisionPropertyContainer, Configurable { - override fun getPropertyValue( +public open class SimpleVisionPropertyContainer(protected val config: Config): VisionPropertyContainer{ + override fun getProperty( name: Name, inherit: Boolean, includeStyles: Boolean, includeDefaults: Boolean - ): Value? = meta[name]?.value + ): MetaItem? = config[name] + + override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { + config[name] = item + } + } \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionTagConsumer.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionTagConsumer.kt index 035b6a6e..287f32bc 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionTagConsumer.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/html/VisionTagConsumer.kt @@ -2,16 +2,14 @@ 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.NameToken -import space.kscience.dataforge.names.asName +import space.kscience.dataforge.names.toName import space.kscience.visionforge.Vision import space.kscience.visionforge.VisionManager -import space.kscience.visionforge.root import kotlin.collections.set @DslMarker @@ -27,7 +25,7 @@ public class VisionOutput @PublishedApi internal constructor(public val manager: //TODO expose a way to define required plugins. - public inline fun meta(block: MutableMeta.() -> Unit) { + public inline fun meta(block: MetaBuilder.() -> Unit) { this.meta = Meta(block) } } @@ -38,7 +36,7 @@ public class VisionOutput @PublishedApi internal constructor(public val manager: @VisionDSL public abstract class VisionTagConsumer( private val root: TagConsumer, - public val manager: VisionManager, + public val manager:VisionManager, private val idPrefix: String? = null, ) : TagConsumer by root { @@ -85,7 +83,6 @@ public abstract class VisionTagConsumer( ): T { val output = VisionOutput(manager) val vision = output.visionProvider() - vision.root(manager) return vision(name, vision, output.meta) } @@ -97,11 +94,11 @@ public abstract class VisionTagConsumer( public inline fun TagConsumer.vision( name: String = DEFAULT_VISION_NAME, visionProvider: VisionOutput.() -> Vision, - ): T = vision(Name.parse(name), visionProvider) + ): T = vision(name.toName(), visionProvider) public fun TagConsumer.vision( vision: Vision, - ): T = vision(NameToken("vision", vision.hashCode().toString()).asName(), vision) + ): T = vision("vision[${vision.hashCode()}]".toName(), vision) /** * Process the resulting object produced by [TagConsumer] diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/misc.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/misc.kt new file mode 100644 index 00000000..f471b234 --- /dev/null +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/misc.kt @@ -0,0 +1,28 @@ +package space.kscience.visionforge + +import space.kscience.dataforge.meta.* +import space.kscience.dataforge.values.asValue + +@DslMarker +public annotation class VisionBuilder + +public fun List.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) + } +} + +/** + * Control visibility of the element + */ +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)) \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/schemeDesctiptors.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/schemeDesctiptors.kt new file mode 100644 index 00000000..8f342b52 --- /dev/null +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/schemeDesctiptors.kt @@ -0,0 +1,67 @@ +package space.kscience.visionforge + +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.NodeDescriptorBuilder +import space.kscience.dataforge.meta.descriptors.ValueDescriptorBuilder +import space.kscience.dataforge.meta.toConfig +import space.kscience.dataforge.values.ValueType +import kotlin.reflect.KProperty1 +import kotlin.reflect.typeOf + +/** + * TODO to be moved into the core + */ +public inline fun NodeDescriptorBuilder.value( + property: KProperty1, + noinline block: ValueDescriptorBuilder.() -> Unit = {}, +) { + when (typeOf()) { + typeOf(), typeOf(), typeOf(), typeOf(), typeOf(), typeOf() -> + value(property.name) { + type(ValueType.NUMBER) + block() + } + typeOf(), typeOf(), typeOf(), typeOf(), typeOf(), typeOf() -> + value(property.name) { + type(ValueType.NUMBER) + block() + } + typeOf() -> value(property.name) { + type(ValueType.BOOLEAN) + block() + } + typeOf>(), typeOf>(), typeOf>(), typeOf>(), typeOf>(), typeOf>(), + typeOf(), typeOf(), typeOf(), typeOf(), typeOf(), + -> value(property.name) { + type(ValueType.NUMBER) + multiple = true + block() + } + typeOf() -> value(property.name) { + type(ValueType.STRING) + block() + } + typeOf>(), typeOf>() -> value(property.name) { + type(ValueType.STRING) + multiple = true + block() + } + else -> value(property.name, block) + } +} + +public fun NodeDescriptor.copy(block: NodeDescriptorBuilder.() -> Unit = {}): NodeDescriptor { + return NodeDescriptorBuilder(toMeta().toConfig()).apply(block) +} + +public inline fun NodeDescriptorBuilder.scheme( + property: KProperty1, + spec: SchemeSpec, + noinline block: NodeDescriptorBuilder.() -> Unit = {}, +) { + spec.descriptor?.let { descriptor -> + item(property.name, descriptor.copy(block)) + } +} \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/visionDelegates.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/visionDelegates.kt deleted file mode 100644 index 999bbb45..00000000 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/visionDelegates.kt +++ /dev/null @@ -1,91 +0,0 @@ -package space.kscience.visionforge - -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 = object : ReadWriteProperty { -// 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?) { -// meta.setMeta(name ?: Name.parse(property.name), value) -// } -//} -// -//public fun Vision.propertyNode( -// converter: MetaConverter, -// name: Name? = null, -// inherit: Boolean = false, -// includeStyles: Boolean = true, -// includeDefaults: Boolean = true, -//): ReadWriteProperty = object : ReadWriteProperty { -// 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?) { -// meta.setMeta(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 = object : ReadWriteProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): Value? = - getPropertyValue(name ?: Name.parse(property.name), inherit, includeStyles, includeDefaults) - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) { - meta.setValue(name ?: Name.parse(property.name), value) - } -} - -public fun 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 = object : ReadWriteProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): T = getPropertyValue( - name ?: Name.parse(property.name), - inherit, - includeStyles, - includeDefaults - ).let(getter) - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - meta.setValue(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 = 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 = propertyValue(name, inherit, includeStyles, includeDefaults) { - it?.number ?: default() -} \ No newline at end of file diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/visionDescriptor.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/visionDescriptor.kt index 15ef9229..c56b495e 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/visionDescriptor.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/visionDescriptor.kt @@ -2,54 +2,87 @@ 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.values.asValue private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited" private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles" -public val MetaDescriptor.inherited: Boolean? - get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean +public val ItemDescriptor.inherited: Boolean + get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false -public var MetaDescriptorBuilder.inherited: Boolean? - get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean - set(value) = attributes.set(INHERITED_DESCRIPTOR_ATTRIBUTE, value?.asValue()) +public var ItemDescriptorBuilder.inherited: Boolean + get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false + set(value) = attributes { + set(INHERITED_DESCRIPTOR_ATTRIBUTE, value) + } + +public val ItemDescriptor.usesStyles: Boolean + get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean ?: true + +public var ItemDescriptorBuilder.usesStyles: Boolean + get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean ?: true + set(value) = attributes { + set(STYLE_DESCRIPTOR_ATTRIBUTE, value) + } -public val MetaDescriptor.usesStyles: Boolean? - get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean +public val Vision.describedProperties: Meta + get() = Meta { + descriptor?.items?.forEach { (key, descriptor) -> + key put getProperty(key, inherit = descriptor.inherited) + } + } -public var MetaDescriptorBuilder.usesStyles: Boolean? - get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean - set(value) = attributes.set(STYLE_DESCRIPTOR_ATTRIBUTE, value?.asValue()) - -public val MetaDescriptor.widget: Meta - get() = attributes["widget"] ?: Meta.EMPTY +public val ValueDescriptor.widget: Meta + get() = attributes["widget"].node ?: Meta.EMPTY /** * Extension property to access the "widget" key of [ValueDescriptor] */ -public var MetaDescriptorBuilder.widget: Meta - get() = attributes["widget"] ?: Meta.EMPTY +public var ValueDescriptorBuilder.widget: Meta + get() = attributes["widget"].node ?: Meta.EMPTY set(value) { - attributes["widget"] = value + attributes { + set("widget", value) + } } -public val MetaDescriptor.widgetType: String? +public val ValueDescriptor.widgetType: String? get() = attributes["widget.type"].string /** - * Extension property to access the "widget.type" key of [MetaDescriptorBuilder] + * Extension property to access the "widget.type" key of [ValueDescriptor] */ -public var MetaDescriptorBuilder.widgetType: String? +public var ValueDescriptorBuilder.widgetType: String? get() = attributes["widget.type"].string set(value) { - attributes["widget.type"] = value?.asValue() + attributes { + set("widget.type", value) + } } /** * If true, this item is hidden in property editor. Default is false */ -public val MetaDescriptor.hidden: Boolean +public val ItemDescriptor.hidden: Boolean get() = attributes["widget.hide"].boolean ?: false -public fun MetaDescriptorBuilder.hide(): Unit = attributes.set("widget.hide", true) \ No newline at end of file +public fun ItemDescriptorBuilder.hide(): Unit = attributes { + set("widget.hide", true) +} + + +public inline fun > NodeDescriptorBuilder.enum( + key: Name, + default: E?, + crossinline modifier: ValueDescriptorBuilder.() -> Unit = {}, +): Unit = value(key) { + type(ValueType.STRING) + default?.let { + default(default) + } + allowedValues = enumValues().map { it.asValue() } + modifier() +} \ No newline at end of file diff --git a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt index 3572dd42..1199b89b 100644 --- a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt +++ b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/html/HtmlTagTest.kt @@ -5,14 +5,10 @@ import kotlinx.html.stream.createHTML import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.fetch import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.configure import space.kscience.dataforge.meta.set import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name -import space.kscience.visionforge.Vision -import space.kscience.visionforge.VisionBase -import space.kscience.visionforge.VisionManager -import kotlin.collections.set +import space.kscience.visionforge.* import kotlin.test.Test typealias HtmlVisionRenderer = FlowContent.(name: Name, vision: Vision, meta: Meta) -> Unit diff --git a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/meta/VisionPropertyTest.kt b/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/meta/VisionPropertyTest.kt deleted file mode 100644 index 40b4c96e..00000000 --- a/visionforge-core/src/commonTest/kotlin/space/kscience/visionforge/meta/VisionPropertyTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package space.kscience.visionforge.meta - -import space.kscience.dataforge.meta.* -import space.kscience.dataforge.values.asValue -import space.kscience.visionforge.VisionBase -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals - -class VisionPropertyTest { - @Test - fun testPropertyWrite(){ - val vision = VisionBase() - vision.meta["fff"] = 2 - vision.meta["fff.ddd"] = false - - assertEquals(2, vision.meta["fff"]?.int) - assertEquals(false, vision.meta["fff.ddd"]?.boolean) - } - - @Test - fun testPropertyEdit(){ - val vision = VisionBase() - vision.meta.getOrCreate("fff.ddd").apply { - value = 2.asValue() - } - assertEquals(2, vision.meta["fff.ddd"]?.int) - assertNotEquals(true, vision.meta["fff.ddd"]?.boolean) - } - - internal class TestScheme: Scheme(){ - var ddd by int() - companion object: SchemeSpec(::TestScheme) - } - - @Test - fun testPropertyUpdate(){ - val vision = VisionBase() - vision.meta.getOrCreate("fff").updateWith(TestScheme){ - ddd = 2 - } - assertEquals(2, vision.meta["fff.ddd"]?.int) - } -} \ No newline at end of file diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/Application.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/Application.kt index 5e1e2470..f9ba8e46 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/Application.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/Application.kt @@ -1,10 +1,7 @@ package space.kscience.visionforge import kotlinx.browser.document -import kotlinx.coroutines.CoroutineScope import kotlinx.dom.hasClass -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext public external val module: Module @@ -28,10 +25,7 @@ public external interface Module { * * Base interface for applications supporting Hot Module Replacement (HMR). */ -public interface Application: CoroutineScope { - - override val coroutineContext: CoroutineContext get() = EmptyCoroutineContext - +public interface Application { /** * Starting point for an application. * @param state Initial state between Hot Module Replacement (HMR). diff --git a/visionforge-fx/build.gradle.kts b/visionforge-fx/build.gradle.kts index 961d181d..09adb66a 100644 --- a/visionforge-fx/build.gradle.kts +++ b/visionforge-fx/build.gradle.kts @@ -14,10 +14,17 @@ dependencies { api("no.tornado:tornadofx:1.7.20") + api("de.jensd:fontawesomefx-fontawesome:4.7.0-11") { + exclude(group = "org.openjfx") + } + + api("de.jensd:fontawesomefx-commons:11.0") { + exclude(group = "org.openjfx") + } + api("org.fxyz3d:fxyz3d:0.5.4") { exclude(module = "slf4j-simple") } - api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}") implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") { diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/FXPlugin.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/FXPlugin.kt index d8210a13..5e7994ef 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/FXPlugin.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/FXPlugin.kt @@ -115,5 +115,5 @@ public class ApplicationSurrogate : App() { } public fun Context.display(width: Double = 800.0, height: Double = 600.0, component: () -> UIComponent) { - fetch(FXPlugin).display(component(), width, height) + plugins.fetch(FXPlugin).display(component(), width, height) } \ No newline at end of file diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ComboBoxValueChooser.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ComboBoxValueChooser.kt index a99599ee..a3d5c942 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ComboBoxValueChooser.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ComboBoxValueChooser.kt @@ -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? = null) override val name: Name = "combo".asName() override fun invoke(meta: Meta): ValueChooser = - ComboBoxValueChooser(meta["values"]?.value?.list) + ComboBoxValueChooser(meta["values"].value?.list) } } diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ConfigEditor.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ConfigEditor.kt new file mode 100644 index 00000000..76d32f57 --- /dev/null +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ConfigEditor.kt @@ -0,0 +1,189 @@ +/* + * 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, + 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> { + 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::name) { + setCellFactory { + object : TextFieldTreeTableCell, 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> -> + param.value.valueProperty() + }.setCellFactory { + ValueCell() + } + + column("Description") { param: TreeTableColumn.CellDataFeatures, String> -> param.value.value.descriptionProperty } + .setCellFactory { param: TreeTableColumn, String> -> + val cell = TreeTableCell, 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?>() { + + public override fun updateItem(item: FXMeta?, empty: Boolean) { + if (!empty) { + if (item != null) { + when (item) { + is FXMetaValue -> { + text = null + val chooser = ValueChooser.build( + Global, + item.valueProperty, + item.descriptor + ) { + item.set(it) + } + graphic = chooser.node + } + is FXMetaNode -> { + 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" + } +} diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/FXMeta.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/FXMeta.kt new file mode 100644 index 00000000..3e2cfc8d --- /dev/null +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/FXMeta.kt @@ -0,0 +1,223 @@ +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> : Comparable> { + abstract val name: NameToken + abstract val parent: FXMetaNode? + 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 > root( + node: M, + descriptor: NodeDescriptor? = null, + rootName: String = "root" + ): FXMetaNode = + FXMetaNode(NameToken(rootName), null, node, descriptor) + + fun root(node: Meta, descriptor: NodeDescriptor? = null, rootName: String = "root"): FXMetaNode = + root(node.seal(), descriptor, rootName) + } +} + +class FXMetaNode>( + override val name: NameToken, + override val parent: FXMetaNode?, + nodeValue: M? = null, + descriptorValue: NodeDescriptor? = null +) : FXMeta() { + + /** + * 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 = 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) -> Boolean = { cfg -> + !(cfg.descriptor?.attributes?.get(ConfigEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false) + } + + val children = object : ListBinding>() { + + 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> { + val nodeKeys = node?.items?.keys?.toSet() ?: emptySet() + val descriptorKeys = descriptor?.items?.keys?.map { NameToken(it) } ?: emptyList() + val keys: Set = 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>( + override val name: NameToken, + override val parent: FXMetaNode +) : FXMeta() { + + 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 > FXMetaNode.remove(name: NameToken) { + node?.remove(name.asName()) + children.invalidate() +} + +private fun > 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 > FXMetaNode.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 > FXMeta.remove() { + parent?.node?.remove(name.asName()) +} + +fun > FXMetaNode.addValue(key: String) { + val parent = getOrCreateNode() + if (descriptor?.multiple == true) { + parent.append(key, Null) + } else { + parent[key] = Null + } +} + +fun > FXMetaNode.addNode(key: String) { + val parent = getOrCreateNode() + if (descriptor?.multiple == true) { + parent.append(key, Meta.EMPTY) + } else { + parent[key] = Meta.EMPTY + } +} + +fun > FXMetaValue.set(value: Value?) { + if (descriptor?.multiple == true) { + parent.getOrCreateNode().append(this.name.body, value) + } else { + parent.getOrCreateNode()[this.name] = value + } +} \ No newline at end of file diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/FXMetaModel.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/FXMetaModel.kt deleted file mode 100644 index 7d8e71b2..00000000 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/FXMetaModel.kt +++ /dev/null @@ -1,90 +0,0 @@ -package space.kscience.visionforge.editor - -import javafx.beans.binding.Binding -import javafx.beans.binding.BooleanBinding -import javafx.beans.binding.ListBinding -import javafx.beans.binding.ObjectBinding -import javafx.collections.ObservableList -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.ObservableMeta -import space.kscience.dataforge.meta.boolean -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.get -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.names.* -import space.kscience.dataforge.values.Value -import tornadofx.* - -/** - * A display for meta and descriptor - */ -public class FXMetaModel( - public val root: M, - public val rootDescriptor: MetaDescriptor?, - public val defaultRoot: Meta?, - public val pathName: Name, - public val title: String = pathName.lastOrNull()?.toString() ?: "Meta" -) : Comparable> { - - private val existingNode = object: ObjectBinding() { - override fun computeValue(): Meta? = root[pathName] - } - - private val defaultNode: Meta? get() = defaultRoot?.getMeta(pathName) - - public val descriptor: MetaDescriptor? = rootDescriptor?.get(pathName) - - public val children: ListBinding> = object : ListBinding>() { - override fun computeValue(): ObservableList> { - val nodeKeys = existingNode.get()?.items?.keys?: emptySet() - val defaultKeys = defaultNode?.items?.keys ?: emptySet() - return (nodeKeys + defaultKeys).map { - FXMetaModel( - root, - rootDescriptor, - defaultRoot, - pathName + it - ) - }.filter(filter).asObservable() - } - } - - init { - //add listener to the root node if possible - if (root is ObservableMeta) { - root.onChange(this) { changed -> - if (changed.startsWith(pathName)) { - if (pathName.length == changed.length) existingNode.invalidate() - else if (changed.length == pathName.length + 1) children.invalidate() - } - } - } - } - - public val existsProperty: BooleanBinding = existingNode.isNotNull - - public val exists: Boolean by existsProperty - - public val valueProperty: Binding = existingNode.objectBinding { - existingNode.get()?.value ?: descriptor?.defaultValue - } - - override fun compareTo(other: FXMetaModel<*>): Int = if (this.exists == other.exists) { - this.pathName.toString().compareTo(other.pathName.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 root( - node: M, - descriptor: MetaDescriptor? = null, - defaultRoot: Meta? = null, - rootName: String = "root" - ): FXMetaModel = FXMetaModel(node, descriptor, defaultRoot, Name.EMPTY, title = rootName) - } -} \ No newline at end of file diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/MetaViewer.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/MetaViewer.kt index 4563ade5..1a822793 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/MetaViewer.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/MetaViewer.kt @@ -16,40 +16,45 @@ 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.* -public class MetaViewer( - private val rootNode: FXMetaModel, - title: String = "Meta viewer" -) : Fragment(title, dfIconView) { +class MetaViewer(val rootNode: FXMetaNode<*>, title: String = "Meta viewer") : Fragment(title, + dfIconView +) { + constructor(meta: Meta, title: String = "Meta viewer"): this( + FXMeta.root( + meta + ),title = title) - public constructor(meta: Meta, title: String = "Meta viewer") : this( - FXMetaModel.root(meta), title = title - ) - - override val root: BorderPane = borderpane { + override val root = borderpane { center { - treetableview> { + treetableview> { isShowRoot = false root = TreeItem(rootNode) populate { - val fxMeta = it.value - fxMeta.children + when (val fxMeta = it.value) { + is FXMetaNode -> { + fxMeta.children + } + is FXMetaValue -> null + } } root.isExpanded = true sortMode = TreeSortMode.ALL_DESCENDANTS columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY - column("Name", FXMetaModel<*>::title) - column, String>("Value") { cellDataFeatures -> - val item = cellDataFeatures.value.value - item.valueProperty.stringBinding { it?.string ?: "" } + column("Name", FXMeta<*>::name) + column, String>("Value") { cellDataFeatures -> + when (val item = cellDataFeatures.value.value) { + is FXMetaValue -> item.valueProperty.stringBinding { it?.string ?: "" } + is FXMetaNode -> SimpleStringProperty("[node]") + } } } } diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/MutableMetaEditor.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/MutableMetaEditor.kt deleted file mode 100644 index 231029cb..00000000 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/MutableMetaEditor.kt +++ /dev/null @@ -1,152 +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 javafx.beans.property.SimpleStringProperty -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.remove -import space.kscience.visionforge.dfIconView -import tornadofx.* - -/** - * A Configuration editor fragment - * - * @author Alexander Nozik - */ -public class MutableMetaEditor( - public val rootNode: FXMetaModel, - //public val allowNew: Boolean = true, - title: String = "Meta 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> { - root = TreeItem(rootNode) - root.isExpanded = true - sortMode = TreeSortMode.ALL_DESCENDANTS - columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY - populate { - it.value.children - } - column("Name", FXMetaModel::title) { - setCellFactory { - object : TextFieldTreeTableCell, 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.root.remove(content.pathName) - } - } - } - } - } - } - } - } - } - } - - column("Value") { param: TreeTableColumn.CellDataFeatures, FXMetaModel> -> - param.value.valueProperty() - }.setCellFactory { - ValueCell() - } - - column("Description") { param: TreeTableColumn.CellDataFeatures, String> -> - SimpleStringProperty(param.value.value.descriptor?.info ?: "") - }.setCellFactory { param: TreeTableColumn, String> -> - val cell = TreeTableCell, 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?>() { - - public override fun updateItem(item: FXMetaModel?, empty: Boolean) { - if (!empty) { - if (item != null) { - text = null - val chooser = ValueChooser.build( - Global, - item.valueProperty, - item.descriptor - ) { value -> - item.root.setValue(item.pathName, value) - } - graphic = chooser.node - - } 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" - } -} diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/TextValueChooser.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/TextValueChooser.kt index 9a1840ce..e7beac2b 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/TextValueChooser.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/TextValueChooser.kt @@ -10,7 +10,6 @@ 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.* @@ -86,7 +85,7 @@ public class TextValueChooser : ValueChooserBase() { } private fun validate(value: Value): Boolean { - return descriptor?.validate(value) ?: true + return descriptor?.isAllowedValue(value) ?: true } // @Override @@ -102,7 +101,7 @@ public class TextValueChooser : ValueChooserBase() { } } - public companion object : ValueChooser.Factory { + companion object : ValueChooser.Factory { override val name: Name = "text".asName() override fun invoke(meta: Meta): ValueChooser = TextValueChooser() diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueCallback.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueCallback.kt index a4e72871..32a0d741 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueCallback.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueCallback.kt @@ -13,11 +13,11 @@ import space.kscience.dataforge.values.Value * @param value Value after change * @param message Message on unsuccessful change */ -public class ValueCallbackResponse(public val success: Boolean, public val value: Value, public val message: String) +class ValueCallbackResponse(val success: Boolean, val value: Value, val message: String) /** * A callback for some visual object trying to change some value * @author [Alexander Nozik](mailto:altavir@gmail.com) */ -public typealias ValueCallback = (Value) -> ValueCallbackResponse +typealias ValueCallback = (Value) -> ValueCallbackResponse diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueChooser.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueChooser.kt index d4ce7bb5..8816a250 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueChooser.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueChooser.kt @@ -10,12 +10,10 @@ 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.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.allowedValues -import space.kscience.dataforge.meta.descriptors.validate +import space.kscience.dataforge.meta.descriptors.ValueDescriptor import space.kscience.dataforge.misc.Named import space.kscience.dataforge.misc.Type -import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.toName import space.kscience.dataforge.values.Null import space.kscience.dataforge.values.Value import space.kscience.visionforge.widget @@ -43,8 +41,8 @@ public interface ValueChooser { * * @return */ - public val descriptorProperty: ObjectProperty - public var descriptor: MetaDescriptor? + public val descriptorProperty: ObjectProperty + public var descriptor: ValueDescriptor? public val valueProperty: ObjectProperty public var value: Value? @@ -64,7 +62,7 @@ public interface ValueChooser { public fun setCallback(callback: ValueCallback) - @Type("space.kscience.dataforge.vis.fx.valueChooserFactory") + @Type("space.kscience..fx.valueChooserFactory") public interface Factory : Named { public operator fun invoke(meta: Meta = Meta.EMPTY): ValueChooser } @@ -72,7 +70,7 @@ public interface ValueChooser { public companion object { private fun findWidgetByType(context: Context, type: String): Factory? { - return when (Name.parse(type)) { + return when (type.toName()) { TextValueChooser.name -> TextValueChooser ColorValueChooser.name -> ColorValueChooser ComboBoxValueChooser.name -> ComboBoxValueChooser @@ -80,7 +78,7 @@ public interface ValueChooser { } } - private fun build(context: Context, descriptor: MetaDescriptor?): ValueChooser { + private fun build(context: Context, descriptor: ValueDescriptor?): ValueChooser { return if (descriptor == null) { TextValueChooser(); } else { @@ -94,7 +92,7 @@ public interface ValueChooser { descriptor.widget ) ?: TextValueChooser() } - !descriptor.allowedValues.isNullOrEmpty() -> ComboBoxValueChooser() + descriptor.allowedValues.isNotEmpty() -> ComboBoxValueChooser() else -> TextValueChooser() } chooser.descriptor = descriptor @@ -105,7 +103,7 @@ public interface ValueChooser { public fun build( context: Context, value: ObservableValue, - descriptor: MetaDescriptor? = null, + descriptor: ValueDescriptor? = null, setter: (Value) -> Unit, ): ValueChooser { val chooser = build(context, descriptor) @@ -114,7 +112,7 @@ public interface ValueChooser { chooser.setDisplayValue(it ?: Null) } chooser.setCallback { result -> - if (descriptor?.validate(result) != false) { + if (descriptor?.isAllowedValue(result) != false) { setter(result) ValueCallbackResponse(true, result, "OK") } else { diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueChooserBase.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueChooserBase.kt index e9b61886..98790f96 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueChooserBase.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/ValueChooserBase.kt @@ -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.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.ValueDescriptor import space.kscience.dataforge.values.Null import space.kscience.dataforge.values.Value import tornadofx.* @@ -21,10 +21,12 @@ import tornadofx.* public abstract class ValueChooserBase : ValueChooser { override val node: T by lazy { buildNode() } - final override val valueProperty: SimpleObjectProperty = SimpleObjectProperty(Null) - final override val descriptorProperty: SimpleObjectProperty = SimpleObjectProperty() + final override val valueProperty: SimpleObjectProperty = + SimpleObjectProperty(Null) + final override val descriptorProperty: SimpleObjectProperty = + SimpleObjectProperty() - override var descriptor: MetaDescriptor? by descriptorProperty + override var descriptor: ValueDescriptor? by descriptorProperty override var value: Value? by valueProperty public fun resetValue() { @@ -36,7 +38,7 @@ public abstract class ValueChooserBase : ValueChooser { * @return */ protected fun currentValue(): Value { - return value ?: descriptor?.defaultValue ?: Null + return value ?: descriptor?.default ?: Null } /** diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisionEditorFragment.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisionEditorFragment.kt deleted file mode 100644 index bf1033ba..00000000 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisionEditorFragment.kt +++ /dev/null @@ -1,61 +0,0 @@ -package space.kscience.visionforge.editor - -import javafx.beans.binding.Binding -import javafx.beans.property.SimpleObjectProperty -import javafx.scene.Node -import javafx.scene.Parent -import javafx.scene.layout.VBox -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.ObservableMutableMeta -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.names.Name -import space.kscience.visionforge.Vision -import space.kscience.visionforge.computeProperties -import space.kscience.visionforge.getStyle -import space.kscience.visionforge.styles -import tornadofx.* - -public class VisionEditorFragment : Fragment() { - - public val visionProperty: SimpleObjectProperty = SimpleObjectProperty() - public var vision: Vision? by visionProperty - public val descriptorProperty: SimpleObjectProperty = SimpleObjectProperty() - - private val configProperty: Binding = visionProperty.objectBinding { vision -> - vision?.meta - } - - private val configEditorProperty: Binding = configProperty.objectBinding(descriptorProperty) { - it?.let { meta -> - val node:FXMetaModel = FXMetaModel( - meta, - vision?.descriptor, - vision?.computeProperties(), - Name.EMPTY, - "Vision properties" - ) - MutableMetaEditor(node).root - } - } - - private val styleBoxProperty: Binding = configProperty.objectBinding { - VBox().apply { - vision?.styles?.forEach { styleName -> - val styleMeta = vision?.getStyle(styleName) - if (styleMeta != null) { - titledpane(styleName, node = MetaViewer(styleMeta).root) - } - } - } - } - - override val root: Parent = vbox { - titledpane("Properties", collapsible = false) { - contentProperty().bind(configEditorProperty) - } - titledpane("Styles", collapsible = false) { - visibleWhen(visionProperty.booleanBinding { it?.styles?.isNotEmpty() ?: false }) - contentProperty().bind(styleBoxProperty) - } - } -} \ No newline at end of file diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisualObjectEditorFragment.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisualObjectEditorFragment.kt new file mode 100644 index 00000000..a918a3a5 --- /dev/null +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisualObjectEditorFragment.kt @@ -0,0 +1,74 @@ +package space.kscience.visionforge.editor + +import javafx.beans.binding.Binding +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.visionforge.* +import tornadofx.* + +public class VisualObjectEditorFragment(public val selector: (Vision) -> Meta) : Fragment() { + + public val itemProperty: SimpleObjectProperty = SimpleObjectProperty() + public var item: Vision? by itemProperty + public val descriptorProperty: SimpleObjectProperty = SimpleObjectProperty() + + public constructor( + item: Vision?, + descriptor: NodeDescriptor?, + selector: (Vision) -> MutableItemProvider = { it.allProperties() }, + ) : this({ it.describedProperties }) { + this.item = item + this.descriptorProperty.set(descriptor) + } + + private var currentConfig: Config? = null + + private val configProperty: Binding = itemProperty.objectBinding { vision -> + if (vision == null) return@objectBinding null + val meta = selector(vision) + val config = Config().apply { + update(meta) + onChange(this@VisualObjectEditorFragment) { key, _, after -> + vision.setProperty(key, after) + } + } + //remember old config reference to cleanup listeners + currentConfig?.removeListener(this) + currentConfig = config + config + } + + private val configEditorProperty: Binding = configProperty.objectBinding(descriptorProperty) { + it?.let { + ConfigEditor(it, descriptorProperty.get()).root + } + } + + private val styleBoxProperty: Binding = configProperty.objectBinding { + VBox().apply { + item?.styles?.forEach { styleName -> + val styleMeta = item?.getStyle(styleName) + if (styleMeta != null) { + titledpane(styleName, node = MetaViewer(styleMeta).root) + } + } + } + } + + override val root: Parent = vbox { + titledpane("Properties", collapsible = false) { + contentProperty().bind(configEditorProperty) + } + titledpane("Styles", collapsible = false) { + visibleWhen(itemProperty.booleanBinding { it?.styles?.isNotEmpty() ?: false }) + contentProperty().bind(styleBoxProperty) + } + } +} \ No newline at end of file diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisionTreeFragment.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisualObjectTreeFragment.kt similarity index 97% rename from visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisionTreeFragment.kt rename to visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisualObjectTreeFragment.kt index 335b5a69..2fa6cee1 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisionTreeFragment.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/editor/VisualObjectTreeFragment.kt @@ -30,7 +30,7 @@ private fun toTreeItem(vision: Vision, title: String): TreeItem = SimpleObjectProperty() public var item: Vision? by itemProperty diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FX3DPlugin.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FX3DPlugin.kt index 9aed5d50..d46bab32 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FX3DPlugin.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FX3DPlugin.kt @@ -16,7 +16,6 @@ import space.kscience.dataforge.context.* import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.boolean import space.kscience.dataforge.misc.Type -import space.kscience.visionforge.computePropertyNode import space.kscience.visionforge.solid.FX3DFactory.Companion.TYPE import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_WIREFRAME_KEY @@ -74,7 +73,7 @@ public class FX3DPlugin : AbstractPlugin() { is PolyLine -> PolyLine3D( obj.points.map { Point3D(it.x, it.y, it.z) }, obj.thickness.toFloat(), - obj.computePropertyNode(SolidMaterial.MATERIAL_COLOR_KEY)?.color() + obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY, inherit = true)?.color() ).apply { this.meshView.cullFace = CullFace.FRONT } diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXMaterials.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXMaterials.kt index ca5e6583..aa02d1ca 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXMaterials.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXMaterials.kt @@ -3,15 +3,11 @@ 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.Meta -import space.kscience.dataforge.meta.double -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.int +import space.kscience.dataforge.meta.* 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 { @@ -30,41 +26,46 @@ public object FXMaterials { } public val BLUE: PhongMaterial = PhongMaterial(Color.BLUE) - } /** * Infer color based on meta item * @param opacity default opacity */ -public fun Meta.color(opacity: Double = 1.0): Color = 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(it.string) +public fun MetaItem.color(opacity: Double = 1.0): Color { + return when (this) { + is MetaItemValue -> if (this.value.type == ValueType.NUMBER) { + val int = value.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) + } + 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 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 +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 + } } } diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXReferenceFactory.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXReferenceFactory.kt index af00f7c5..022900df 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXReferenceFactory.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXReferenceFactory.kt @@ -2,10 +2,7 @@ package space.kscience.visionforge.solid import javafx.scene.Group import javafx.scene.Node -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.dataforge.names.* import space.kscience.visionforge.Vision import space.kscience.visionforge.onPropertyChange import kotlin.reflect.KClass @@ -17,9 +14,9 @@ public class FXReferenceFactory(public val plugin: FX3DPlugin) : FX3DFactory + obj.onPropertyChange(plugin.context) { name-> if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) { - val childName = name.firstOrNull()?.index?.let(Name::parse) ?: error("Wrong syntax for reference child property: '$name'") + val childName = name.firstOrNull()?.index?.toName() ?: 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") diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/VisualObjectFXBinding.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/VisualObjectFXBinding.kt index 607913d7..a2f41fbe 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/VisualObjectFXBinding.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/VisualObjectFXBinding.kt @@ -5,20 +5,20 @@ 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.computePropertyNode import space.kscience.visionforge.onPropertyChange import tornadofx.* /** * A caching binding collection for [Vision] properties */ -public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vision) { - private val bindings = HashMap>() +public class VisualObjectFXBinding(private val fx: FX3DPlugin, public val obj: Vision) { + private val bindings = HashMap>() init { - obj.onPropertyChange { name -> + obj.onPropertyChange(fx.context) { name -> bindings.filter { it.key.startsWith(name) }.forEach { entry -> Platform.runLater { entry.value.invalidate() @@ -33,29 +33,28 @@ public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vi } } - public operator fun get(key: Name): ObjectBinding { - return bindings.getOrPut(key) { - object : ObjectBinding() { - override fun computeValue(): Meta? = obj.computePropertyNode(key) - } + public operator fun get(key: Name): ObjectBinding = bindings.getOrPut(key) { + object : ObjectBinding() { + override fun computeValue(): MetaItem? = obj.getProperty(key) } } - public operator fun get(key: String): ObjectBinding = get(Name.parse(key)) + public operator fun get(key: String): ObjectBinding?> = get(key.toName()) } -public fun ObjectBinding.value(): Binding = objectBinding { it?.value } -public fun ObjectBinding.string(): StringBinding = stringBinding { it.string } -public fun ObjectBinding.number(): Binding = objectBinding { it.number } -public fun ObjectBinding.double(): Binding = objectBinding { it.double } -public fun ObjectBinding.float(): Binding = objectBinding { it.float } -public fun ObjectBinding.int(): Binding = objectBinding { it.int } -public fun ObjectBinding.long(): Binding = objectBinding { it.long } +public fun ObjectBinding.value(): Binding = objectBinding { it.value } +public fun ObjectBinding.string(): StringBinding = stringBinding { it.string } +public fun ObjectBinding.number(): Binding = objectBinding { it.number } +public fun ObjectBinding.double(): Binding = objectBinding { it.double } +public fun ObjectBinding.float(): Binding = objectBinding { it.float } +public fun ObjectBinding.int(): Binding = objectBinding { it.int } +public fun ObjectBinding.long(): Binding = objectBinding { it.long } +public fun ObjectBinding.node(): Binding = objectBinding { it.node } -public fun ObjectBinding.string(default: String): StringBinding = stringBinding { it.string ?: default } -public fun ObjectBinding.double(default: Double): DoubleBinding = doubleBinding { it.double ?: default } -public fun ObjectBinding.float(default: Float): FloatBinding = floatBinding { it.float ?: default } -public fun ObjectBinding.int(default: Int): IntegerBinding = integerBinding { it.int ?: default } -public fun ObjectBinding.long(default: Long): LongBinding = longBinding { it.long ?: default } +public fun ObjectBinding.string(default: String): StringBinding = stringBinding { it.string ?: default } +public fun ObjectBinding.double(default: Double): DoubleBinding = doubleBinding { it.double ?: default } +public fun ObjectBinding.float(default: Float): FloatBinding = floatBinding { it.float ?: default } +public fun ObjectBinding.int(default: Int): IntegerBinding = integerBinding { it.int ?: default } +public fun ObjectBinding.long(default: Long): LongBinding = longBinding { it.long ?: default } -public fun ObjectBinding.transform(transform: (Meta) -> T): Binding = objectBinding { it?.let(transform) } +public fun ObjectBinding.transform(transform: (MetaItem) -> T): Binding = objectBinding { it?.let(transform) } diff --git a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformer.kt b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformer.kt index 284d80cf..0bba2f1a 100644 --- a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformer.kt @@ -1,12 +1,12 @@ package space.kscience.visionforge.gdml import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MutableMeta +import space.kscience.dataforge.meta.MetaBuilder 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() - public fun Solid.registerAndUseStyle(name: String, builder: MutableMeta.() -> Unit) { - styleCache.getOrPut(Name.parse(name)) { + public fun Solid.registerAndUseStyle(name: String, builder: MetaBuilder.() -> Unit) { + styleCache.getOrPut(name.toName()) { Meta(builder) } useStyle(name) @@ -118,7 +118,7 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) { private val proto = SolidGroup() private val solids = proto.group(solidsName) { - setPropertyNode("edges.enabled", false) + setProperty("edges.enabled", false) } private val referenceStore = HashMap>() @@ -441,6 +441,20 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) { } final.useStyle(rootStyle) + //inline prototypes +// referenceStore.forEach { (protoName, list) -> +// val proxy = list.singleOrNull() ?: return@forEach +// val parent = proxy.parent as? MutableVisionGroup ?: return@forEach +// val token = parent.children.entries.find { it.value == proxy }?.key ?: error("Inconsistent reference cache") +// val prototype = proto[protoName] as? Solid ?: error("Inconsistent reference cache") +// prototype.parent = null +// parent[token] = prototype +// prototype.updateFrom(proxy) +// +// //FIXME update prototype +// proto[protoName] = null +// } + final.prototypes { proto.children.forEach { (token, item) -> item.parent = null diff --git a/visionforge-gdml/src/commonTest/kotlin/TestCubes.kt b/visionforge-gdml/src/commonTest/kotlin/TestCubes.kt index 0ca76fd1..9fb80095 100644 --- a/visionforge-gdml/src/commonTest/kotlin/TestCubes.kt +++ b/visionforge-gdml/src/commonTest/kotlin/TestCubes.kt @@ -1,7 +1,7 @@ package space.kscience.visionforge.gdml import space.kscience.dataforge.context.Context -import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.toName 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(Name.parse("solids.smallBox")) as? Box + val smallBoxPrototype = vision.getPrototype("solids.smallBox".toName()) 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(Name.parse("solids.smallBox")) as? Box + val smallBox = deserialized.getPrototype("solids.smallBox".toName()) as? Box assertNotNull(smallBox) assertEquals(30.0, smallBox.xSize.toDouble()) //println(testContext.visionManager.encodeToString(deserialized)) diff --git a/visionforge-gdml/src/jvmTest/kotlin/space/kscience/visionforge/gdml/TestConvertor.kt b/visionforge-gdml/src/jvmTest/kotlin/space/kscience/visionforge/gdml/TestConvertor.kt index a8a078a8..730adfa9 100644 --- a/visionforge-gdml/src/jvmTest/kotlin/space/kscience/visionforge/gdml/TestConvertor.kt +++ b/visionforge-gdml/src/jvmTest/kotlin/space/kscience/visionforge/gdml/TestConvertor.kt @@ -1,7 +1,7 @@ package space.kscience.visionforge.gdml import org.junit.jupiter.api.Test -import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.toName 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(Name.parse("solids.box"))) + assertNotNull(vision.getPrototype("solids.box".toName())) println(Solids.encodeToString(vision)) } diff --git a/visionforge-markdown/build.gradle.kts b/visionforge-markdown/build.gradle.kts deleted file mode 100644 index 210e4ee8..00000000 --- a/visionforge-markdown/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -plugins { - id("ru.mipt.npm.gradle.mpp") -} - -val markdownVersion = "0.2.4" - -kscience { - useSerialization() -} - -kotlin { - js { - //binaries.library() - binaries.executable() - browser { - webpackTask { - outputFileName = "js/visionforge-markdown.js" - } - } - } - - jvm { - val processResourcesTaskName = - compilations[org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.MAIN_COMPILATION_NAME] - .processResourcesTaskName - } - - - val jsBrowserDistribution by tasks.getting - - tasks.getByName("jvmProcessResources") { - dependsOn(jsBrowserDistribution) - from(jsBrowserDistribution) - } - - sourceSets { - commonMain { - dependencies { - api(project(":visionforge-core")) - api("org.jetbrains:markdown:$markdownVersion") - } - } - } -} \ No newline at end of file diff --git a/visionforge-markdown/src/commonMain/kotlin/space/kscience/visionforge/markup/VisionOfMarkup.kt b/visionforge-markdown/src/commonMain/kotlin/space/kscience/visionforge/markup/VisionOfMarkup.kt deleted file mode 100644 index 58708ab1..00000000 --- a/visionforge-markdown/src/commonMain/kotlin/space/kscience/visionforge/markup/VisionOfMarkup.kt +++ /dev/null @@ -1,39 +0,0 @@ -package space.kscience.visionforge.markup - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.polymorphic -import kotlinx.serialization.modules.subclass -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.string -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.asName -import space.kscience.visionforge.Vision -import space.kscience.visionforge.VisionBase - -@Serializable -@SerialName("vision.markup") -public class VisionOfMarkup( - public val format: String = COMMONMARK_FORMAT -) : VisionBase() { - - //FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix - protected override var properties: MutableMeta? = null - - //TODO add templates - - public var content: String? by meta.string(CONTENT_PROPERTY_KEY) - - public companion object { - public val CONTENT_PROPERTY_KEY: Name = "content".asName() - public const val COMMONMARK_FORMAT: String = "markdown.commonmark" - public const val GFM_FORMAT: String = "markdown.gfm" - } -} - -internal val markupSerializersModule = SerializersModule { - polymorphic(Vision::class) { - subclass(VisionOfMarkup.serializer()) - } -} \ No newline at end of file diff --git a/visionforge-markdown/src/commonMain/kotlin/space/kscience/visionforge/markup/markdown.kt b/visionforge-markdown/src/commonMain/kotlin/space/kscience/visionforge/markup/markdown.kt deleted file mode 100644 index 0ac353e4..00000000 --- a/visionforge-markdown/src/commonMain/kotlin/space/kscience/visionforge/markup/markdown.kt +++ /dev/null @@ -1,25 +0,0 @@ -package space.kscience.visionforge.markup - -import kotlinx.html.TagConsumer -import kotlinx.html.div -import kotlinx.html.unsafe -import org.intellij.markdown.flavours.MarkdownFlavourDescriptor -import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor -import org.intellij.markdown.html.HtmlGenerator -import org.intellij.markdown.parser.MarkdownParser - -/** - * Render markdown inside kotlinx-html tag - */ -public fun TagConsumer.markdown( - flavour: MarkdownFlavourDescriptor = CommonMarkFlavourDescriptor(), - block: () -> String -): T { - val src = block() - val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(src) - return div("visionforge-markdown") { - unsafe { - +HtmlGenerator(src, parsedTree, flavour).generateHtml() - } - } -} \ No newline at end of file diff --git a/visionforge-markdown/src/jsMain/kotlin/space/kscience/visionforge/markup/MarkupPlugin.kt b/visionforge-markdown/src/jsMain/kotlin/space/kscience/visionforge/markup/MarkupPlugin.kt deleted file mode 100644 index d26488c5..00000000 --- a/visionforge-markdown/src/jsMain/kotlin/space/kscience/visionforge/markup/MarkupPlugin.kt +++ /dev/null @@ -1,53 +0,0 @@ -package space.kscience.visionforge.markup - -import kotlinx.browser.document -import kotlinx.dom.clear -import kotlinx.html.dom.append -import kotlinx.serialization.modules.SerializersModule -import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor -import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor -import org.w3c.dom.Element -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.PluginFactory -import space.kscience.dataforge.context.PluginTag -import space.kscience.dataforge.meta.Meta -import space.kscience.visionforge.* -import space.kscience.visionforge.markup.VisionOfMarkup.Companion.COMMONMARK_FORMAT -import space.kscience.visionforge.markup.VisionOfMarkup.Companion.GFM_FORMAT -import kotlin.reflect.KClass - -public class MarkupPlugin : VisionPlugin(), ElementVisionRenderer { - public val visionClient: VisionClient by require(VisionClient) - override val tag: PluginTag get() = Companion.tag - override val visionSerializersModule: SerializersModule get() = markupSerializersModule - - override fun rateVision(vision: Vision): Int = when (vision) { - is VisionOfMarkup -> ElementVisionRenderer.DEFAULT_RATING - else -> ElementVisionRenderer.ZERO_RATING - } - - override fun render(element: Element, vision: Vision, meta: Meta) { - require(vision is VisionOfMarkup) { "The vision is not a markup vision" } - val div = document.createElement("div") - val flavour = when (vision.format) { - COMMONMARK_FORMAT -> CommonMarkFlavourDescriptor() - GFM_FORMAT -> GFMFlavourDescriptor() - //TODO add new formats via plugins - else-> error("Format ${vision.format} not recognized") - } - vision.useProperty(VisionOfMarkup::content) { - div.clear() - div.append { - markdown(flavour) { vision.content ?: "" } - - } - } - element.append(div) - } - - public companion object : PluginFactory { - override val tag: PluginTag = PluginTag("vision.markup", PluginTag.DATAFORGE_GROUP) - override val type: KClass = MarkupPlugin::class - override fun invoke(meta: Meta, context: Context): MarkupPlugin = MarkupPlugin() - } -} \ No newline at end of file diff --git a/visionforge-plotly/build.gradle.kts b/visionforge-plotly/build.gradle.kts index b0b5a9c2..b62876ab 100644 --- a/visionforge-plotly/build.gradle.kts +++ b/visionforge-plotly/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("ru.mipt.npm.gradle.mpp") } -val plotlyVersion = "0.5.0" +val plotlyVersion = "0.4.3" kscience { useSerialization() diff --git a/visionforge-plotly/src/commonMain/kotlin/space/kscience/visionforge/plotly/VisionOfPlotly.kt b/visionforge-plotly/src/commonMain/kotlin/space/kscience/visionforge/plotly/VisionOfPlotly.kt index 99df2e84..773ff4bf 100644 --- a/visionforge-plotly/src/commonMain/kotlin/space/kscience/visionforge/plotly/VisionOfPlotly.kt +++ b/visionforge-plotly/src/commonMain/kotlin/space/kscience/visionforge/plotly/VisionOfPlotly.kt @@ -2,23 +2,22 @@ package space.kscience.visionforge.plotly import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import space.kscience.dataforge.meta.MutableMeta +import space.kscience.dataforge.meta.Config import space.kscience.dataforge.misc.DFExperimental import space.kscience.plotly.Plot import space.kscience.plotly.Plotly import space.kscience.visionforge.VisionBase import space.kscience.visionforge.html.VisionOutput +import space.kscience.visionforge.root @Serializable @SerialName("vision.plotly") public class VisionOfPlotly private constructor() : VisionBase() { - //FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix - override var properties: MutableMeta? = null - public constructor(plot: Plot) : this() { - properties = plot.meta + properties = plot.config } - public val plot: Plot get() = Plot(meta) + + public val plot: Plot get() = Plot(properties ?: Config()) } public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this) @@ -26,4 +25,6 @@ public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this) @DFExperimental public inline fun VisionOutput.plotly( block: Plot.() -> Unit, -): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block)) \ No newline at end of file +): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block)).apply { + root(this@plotly.manager) +} \ No newline at end of file diff --git a/visionforge-plotly/src/jsMain/kotlin/space/kscience/visionforge/plotly/plotlyJs.kt b/visionforge-plotly/src/jsMain/kotlin/space/kscience/visionforge/plotly/plotlyJs.kt index e0cea767..be9e1e08 100644 --- a/visionforge-plotly/src/jsMain/kotlin/space/kscience/visionforge/plotly/plotlyJs.kt +++ b/visionforge-plotly/src/jsMain/kotlin/space/kscience/visionforge/plotly/plotlyJs.kt @@ -31,8 +31,8 @@ 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.meta) -// println(plot.data[0].toMeta()) + println(plot.config) + println(plot.data[0].toMeta()) element.plot(plot, config) } diff --git a/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt b/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt index 46cb2e66..2c9ed631 100644 --- a/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt +++ b/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt @@ -30,6 +30,7 @@ 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 @@ -53,12 +54,12 @@ public class VisionServer internal constructor( private val application: Application, private val rootRoute: String, ) : Configurable, CoroutineScope by application { - 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")) + override val config: Config = Config() + 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()) /** * a list of headers that should be applied to all pages @@ -130,7 +131,7 @@ 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.parse(name)] ?: error("Plot with id='$name' not registered") + val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered") launch { incoming.consumeEach { @@ -160,7 +161,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.parse(name)] + val vision: Vision? = visions[name.toName()] if (vision == null) { call.respond(HttpStatusCode.NotFound, "Vision with name '$name' not found") } else { @@ -231,7 +232,7 @@ public class VisionServer internal constructor( public companion object { public const val DEFAULT_PAGE: String = "/" - public val UPDATE_INTERVAL_KEY: Name = Name.parse("update.interval") + public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName() } } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt index 57f868f2..9232874c 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ColorAccessor.kt @@ -1,31 +1,26 @@ 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.names.Name -import space.kscience.dataforge.names.plus -import space.kscience.dataforge.values.* +import space.kscience.dataforge.values.Value +import space.kscience.dataforge.values.asValue +import space.kscience.dataforge.values.string import space.kscience.visionforge.Colors import space.kscience.visionforge.VisionBuilder @VisionBuilder -public class ColorAccessor( - private val provider: MutableValueProvider, - private val colorKey: Name -) : MutableValueProvider { +public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) { public var value: Value? - get() = provider.getValue(colorKey) + get() = parent.getItem(colorKey).value set(value) { - provider.setValue(colorKey, value) + parent[colorKey] = value } - - override fun getValue(name: Name): Value? = provider.getValue(colorKey + name) - - override fun setValue(name: Name, value: Value?) { - provider.setValue(colorKey + name, value) - } } public var ColorAccessor?.string: String? - get() = this?.value?.let { if(it == Null) null else it.string } + get() = this?.value?.string set(value) { this?.value = value?.asValue() } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt index 9debf612..d1d41d09 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt @@ -3,9 +3,7 @@ package space.kscience.visionforge.solid import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.update -import space.kscience.visionforge.VisionBuilder -import space.kscience.visionforge.VisionContainerBuilder -import space.kscience.visionforge.set +import space.kscience.visionforge.* public enum class CompositeType { SUM, // Dumb sum of meshes @@ -31,38 +29,34 @@ public inline fun VisionContainerBuilder.composite( val group = SolidGroup().apply(builder) val children = group.children.values.filterIsInstance() if (children.size != 2) error("Composite requires exactly two children") - val res = Composite(type, children[0], children[1]) - - res.meta.update(group.meta) - - if (group.position != null) { - res.position = group.position + return Composite(type, children[0], children[1]).also { composite -> + composite.configure { + update(group.meta) + } + if (group.position != null) { + composite.position = group.position + } + if (group.rotation != null) { + composite.rotation = group.rotation + } + if (group.scale != null) { + composite.scale = group.scale + } + set(name, composite) } - if (group.rotation != null) { - res.rotation = group.rotation - } - if (group.scale != null) { - res.scale = group.scale - } - - set(name, res) - return res } @VisionBuilder -public inline fun VisionContainerBuilder.union( - name: String? = null, - builder: SolidGroup.() -> Unit -): Composite = composite(CompositeType.UNION, name, builder = builder) +public inline fun VisionContainerBuilder.union(name: String? = null, builder: SolidGroup.() -> Unit): Composite = + composite(CompositeType.UNION, name, builder = builder) @VisionBuilder -public inline fun VisionContainerBuilder.subtract( - name: String? = null, - builder: SolidGroup.() -> Unit -): Composite = composite(CompositeType.SUBTRACT, name, builder = builder) +public inline fun VisionContainerBuilder.subtract(name: String? = null, builder: SolidGroup.() -> Unit): Composite = + composite(CompositeType.SUBTRACT, name, builder = builder) @VisionBuilder public inline fun VisionContainerBuilder.intersect( name: String? = null, builder: SolidGroup.() -> Unit, -): Composite = composite(CompositeType.INTERSECT, name, builder = builder) \ No newline at end of file +): Composite = + composite(CompositeType.INTERSECT, name, builder = builder) \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt index 30ecb575..ded328b6 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt @@ -2,9 +2,7 @@ package space.kscience.visionforge.solid import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.ObservableMutableMeta -import space.kscience.dataforge.meta.configure +import space.kscience.dataforge.meta.Config import space.kscience.visionforge.* import kotlin.math.PI import kotlin.math.cos @@ -99,7 +97,7 @@ public class ExtrudeBuilder( public var layers: MutableList = ArrayList(), - config: ObservableMutableMeta = MutableMeta() + config: Config = Config() ) : SimpleVisionPropertyContainer(config) { public fun shape(block: Shape2DBuilder.() -> Unit) { this.shape = Shape2DBuilder().apply(block).build() @@ -109,9 +107,7 @@ public class ExtrudeBuilder( layers.add(Layer(x.toFloat(), y.toFloat(), z.toFloat(), scale.toFloat())) } - internal fun build(): Extruded = Extruded(shape, layers).apply { - configure(this@ExtrudeBuilder.meta) - } + internal fun build(): Extruded = Extruded(shape, layers).apply { configure(config) } } @VisionBuilder diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt index d8aa937c..cd9f2cbd 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt @@ -2,12 +2,13 @@ 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.numberProperty +import space.kscience.visionforge.allProperties import space.kscience.visionforge.set @Serializable @@ -15,8 +16,8 @@ import space.kscience.visionforge.set public class PolyLine(public val points: List) : SolidBase(), Solid { //var lineType by string() - public var thickness: Number by numberProperty(name = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY) { 1.0 } - + public var thickness: Number by allProperties(inherit = false).number(1.0, + key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY) public companion object { public val THICKNESS_KEY: Name = "thickness".asName() diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt index 39ed06ac..a4c8e69c 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt @@ -1,11 +1,12 @@ package space.kscience.visionforge.solid import space.kscience.dataforge.meta.* -import space.kscience.dataforge.meta.descriptors.* +import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.plus -import space.kscience.dataforge.values.* +import space.kscience.dataforge.values.ValueType +import space.kscience.dataforge.values.asValue import space.kscience.visionforge.* import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY import space.kscience.visionforge.solid.Solid.Companion.DETAIL_KEY @@ -34,7 +35,7 @@ import kotlin.reflect.KProperty */ public interface Solid : Vision { - override val descriptor: MetaDescriptor get() = Companion.descriptor + override val descriptor: NodeDescriptor get() = Companion.descriptor public companion object { // val SELECTED_KEY = "selected".asName() @@ -68,37 +69,40 @@ 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: MetaDescriptor by lazy { - MetaDescriptor { - value(VISIBLE_KEY, ValueType.BOOLEAN) { + public val descriptor: NodeDescriptor by lazy { + NodeDescriptor { + value(VISIBLE_KEY) { inherited = false + type(ValueType.BOOLEAN) default(true) } - node(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial) - //TODO replace by descriptor merge - value(Vision.STYLE_KEY, ValueType.STRING) { + value(Vision.STYLE_KEY) { + type(ValueType.STRING) multiple = true hide() } - node(POSITION_KEY) { + node(POSITION_KEY){ hide() } - node(ROTATION_KEY) { + node(ROTATION_KEY){ hide() } - node(SCALE_KEY) { + node(SCALE_KEY){ hide() } - value(DETAIL_KEY, ValueType.NUMBER) { + value(DETAIL_KEY) { + type(ValueType.NUMBER) hide() } + item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor) + enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) { hide() } @@ -111,7 +115,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() = getPropertyValue(LAYER_KEY, inherit = true)?.int ?: 0 + get() = allProperties().getItem(LAYER_KEY).int ?: 0 set(value) { setProperty(LAYER_KEY, value) } @@ -131,24 +135,24 @@ public enum class RotationOrder { * Rotation order */ public var Solid.rotationOrder: RotationOrder - get() = getPropertyValue(Solid.ROTATION_ORDER_KEY)?.enum() ?: RotationOrder.XYZ - set(value) = meta.setValue(Solid.ROTATION_ORDER_KEY, value.name.asValue()) + get() = getProperty(Solid.ROTATION_ORDER_KEY).enum() ?: RotationOrder.XYZ + set(value) = setProperty(Solid.ROTATION_ORDER_KEY, value.name.asValue()) /** * Preferred number of polygons for displaying the object. If not defined, uses shape or renderer default. Not inherited */ public var Solid.detail: Int? - get() = getPropertyValue(DETAIL_KEY, false)?.int - set(value) = meta.setValue(DETAIL_KEY, value?.asValue()) + get() = getProperty(DETAIL_KEY, false).int + set(value) = setProperty(DETAIL_KEY, value?.asValue()) /** * If this property is true, the object will be ignored on render. * Property is not inherited. */ public var Vision.ignore: Boolean? - get() = getPropertyValue(IGNORE_KEY, false)?.boolean - set(value) = meta.setValue(IGNORE_KEY, value?.asValue()) + get() = getProperty(IGNORE_KEY, false).boolean + set(value) = setProperty(IGNORE_KEY, value?.asValue()) //var VisualObject.selected: Boolean? // get() = getProperty(SELECTED_KEY).boolean @@ -157,7 +161,7 @@ public var Vision.ignore: Boolean? internal fun float(name: Name, default: Number): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Solid, property: KProperty<*>): Number { - return thisRef.meta.getMeta(name)?.number ?: default + return thisRef.getOwnProperty(name)?.number ?: default } override fun setValue(thisRef: Solid, property: KProperty<*>, value: Number) { @@ -168,7 +172,7 @@ internal fun float(name: Name, default: Number): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Solid, property: KProperty<*>): Point3D? { - val item = thisRef.meta.getMeta(name) ?: return null + val item = thisRef.getOwnProperty(name) ?: return null return object : Point3D { override val x: Float get() = item[X_KEY]?.float ?: default override val y: Float get() = item[Y_KEY]?.float ?: default @@ -178,7 +182,7 @@ internal fun point(name: Name, default: Float): ReadWriteProperty, value: Point3D?) { if (value == null) { - thisRef.meta.setMeta(name, null) + thisRef.setProperty(name, null) } else { thisRef.setProperty(name + X_KEY, value.x) thisRef.setProperty(name + Y_KEY, value.y) diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidBase.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidBase.kt index 021079d3..d2fee33a 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidBase.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidBase.kt @@ -2,15 +2,17 @@ package space.kscience.visionforge.solid import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.descriptors.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.visionforge.VisionBase +import space.kscience.visionforge.VisionChange @Serializable @SerialName("solid") public open class SolidBase : VisionBase(), Solid { - //FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix - override var properties: MutableMeta? = null + override val descriptor: NodeDescriptor get() = Solid.descriptor - override val descriptor: MetaDescriptor get() = Solid.descriptor + override fun update(change: VisionChange) { + updatePosition(change.properties) + super.update(change) + } } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt index ec6dd5f1..b78ec4a5 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt @@ -2,8 +2,7 @@ package space.kscience.visionforge.solid import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.descriptors.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.NameToken import space.kscience.visionforge.* @@ -32,9 +31,6 @@ public interface PrototypeHolder { @SerialName("group.solid") public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder { - //FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix - override var properties: MutableMeta? = null - override val children: Map get() = super.childrenInternal.filter { it.key != PROTOTYPES_TOKEN } private var prototypes: MutableVisionGroup? @@ -44,7 +40,7 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder { } - override val descriptor: MetaDescriptor get() = Solid.descriptor + override val descriptor: NodeDescriptor get() = Solid.descriptor /** * Get a prototype redirecting the request to the parent if prototype is not found. diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidMaterial.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidMaterial.kt index fa5b0d76..aa6c9301 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidMaterial.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidMaterial.kt @@ -1,14 +1,13 @@ package space.kscience.visionforge.solid import space.kscience.dataforge.meta.* -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.value +import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.attributes import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.plus import space.kscience.dataforge.values.ValueType import space.kscience.dataforge.values.asValue -import space.kscience.dataforge.values.number import space.kscience.visionforge.* import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY @@ -20,14 +19,12 @@ public class SolidMaterial : Scheme() { /** * Primary web-color for the material */ - public val color: ColorAccessor = ColorAccessor(meta, COLOR_KEY) + public val color: ColorAccessor = ColorAccessor(this, COLOR_KEY) /** * Specular color for phong material */ - public val specularColor: ColorAccessor = ColorAccessor(meta, SPECULAR_COLOR_KEY) - - public val emissiveColor: ColorAccessor = ColorAccessor(meta, "emissiveColor".asName()) + public val specularColor: ColorAccessor = ColorAccessor(this, SPECULAR_COLOR_KEY) /** * Opacity @@ -51,33 +48,43 @@ 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: MetaDescriptor by lazy { + public override val descriptor: NodeDescriptor by lazy { //must be lazy to avoid initialization bug - MetaDescriptor { + NodeDescriptor { inherited = true + usesStyles = true - value(COLOR_KEY, ValueType.STRING, ValueType.NUMBER) { + value(COLOR_KEY) { inherited = true + usesStyles = true + type(ValueType.STRING, ValueType.NUMBER) widgetType = "color" } - value(SPECULAR_COLOR_KEY, ValueType.STRING, ValueType.NUMBER) { + value(SPECULAR_COLOR_KEY) { inherited = true + usesStyles = true + type(ValueType.STRING, ValueType.NUMBER) widgetType = "color" hide() } - value(OPACITY_KEY, ValueType.NUMBER) { + value(OPACITY_KEY) { inherited = true + usesStyles = true + type(ValueType.NUMBER) default(1.0) - attributes["min"] = 0.0 - attributes["max"] = 1.0 - attributes["step"] = 0.1 + attributes { + this["min"] = 0.0 + this["max"] = 1.0 + this["step"] = 0.1 + } widgetType = "slider" } - - value(WIREFRAME_KEY, ValueType.BOOLEAN) { + value(WIREFRAME_KEY) { inherited = true + usesStyles = true + type(ValueType.BOOLEAN) default(false) } } @@ -86,19 +93,22 @@ public class SolidMaterial : Scheme() { } public val Solid.color: ColorAccessor - get() = ColorAccessor(computePropertyValues(), MATERIAL_COLOR_KEY) + get() = ColorAccessor( + allProperties(inherit = true), + MATERIAL_COLOR_KEY + ) public var Solid.material: SolidMaterial? - get() = computePropertyNode(MATERIAL_KEY)?.let { SolidMaterial.read(it) } - set(value) = meta.setMeta(MATERIAL_KEY, value?.meta) + get() = getProperty(MATERIAL_KEY, inherit = true).node?.let { SolidMaterial.read(it) } + set(value) = setProperty(MATERIAL_KEY, value?.rootNode) @VisionBuilder public fun Solid.material(builder: SolidMaterial.() -> Unit) { - meta.getOrCreate(MATERIAL_KEY).updateWith(SolidMaterial, builder) + ownProperties.getChild(MATERIAL_KEY).update(SolidMaterial, builder) } public var Solid.opacity: Number? - get() = getPropertyValue(MATERIAL_OPACITY_KEY, inherit = true)?.number + get() = getProperty(MATERIAL_OPACITY_KEY, inherit = true).number set(value) { - meta.setValue(MATERIAL_OPACITY_KEY, value?.asValue()) + setProperty(MATERIAL_OPACITY_KEY, value?.asValue()) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt index d2080924..1cb5eac8 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt @@ -1,38 +1,22 @@ package space.kscience.visionforge.solid +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapNotNull import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.ObservableMutableMeta -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.MetaItem +import space.kscience.dataforge.meta.asMetaItem +import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.* -import space.kscience.dataforge.values.Value import space.kscience.visionforge.* public interface SolidReference : VisionGroup { /** - * The prototype for this reference. + * The prototype for this reference. Always returns a "real" prototype, not a reference */ public val prototype: Solid - - override fun getPropertyValue( - name: Name, - inherit: Boolean, - includeStyles: Boolean, - includeDefaults: Boolean - ): Value? { - meta[name]?.value?.let { return it } - if (includeStyles) { - getStyleProperty(name)?.let { return it } - } - prototype.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it } - if (inherit) { - parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it } - } - return null - } } @@ -47,6 +31,27 @@ public val Vision.unref: Solid else -> error("This Vision is neither Solid nor SolidReference") } + +private fun SolidReference.getRefProperty( + name: Name, + inherit: Boolean, + includeStyles: Boolean, + includeDefaults: Boolean, +): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) { + getOwnProperty(name) +} else { + buildList { + add(getOwnProperty(name)) + if (includeStyles) { + addAll(getStyleItems(name)) + } + add(prototype.getProperty(name, inherit, includeStyles, includeDefaults)) + if (inherit) { + add(parent?.getProperty(name, inherit)) + } + }.merge() +} + private fun childToken(childName: Name): NameToken = NameToken(SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) @@ -62,8 +67,6 @@ public class SolidReferenceGroup( public val refName: Name, ) : VisionBase(), SolidReference, VisionGroup, Solid { - override var properties: MutableMeta? = null - /** * Recursively search for defined template in the parent */ @@ -80,14 +83,14 @@ public class SolidReferenceGroup( ReferenceChild(this, it.key.asName()) } ?: emptyMap() - override fun getPropertyValue( + override fun getProperty( name: Name, inherit: Boolean, includeStyles: Boolean, - includeDefaults: Boolean - ): Value? = super.getPropertyValue(name, inherit, includeStyles, includeDefaults) + includeDefaults: Boolean, + ): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults) - override val descriptor: MetaDescriptor get() = prototype.descriptor + override val descriptor: NodeDescriptor get() = prototype.descriptor /** @@ -100,21 +103,14 @@ public class SolidReferenceGroup( ) : SolidReference, VisionGroup, Solid { override val prototype: Solid by lazy { - if (refName.isEmpty()) { - owner.prototype - } else { + if (refName.isEmpty()) owner.prototype else { val proto = (owner.prototype as? VisionGroup)?.get(refName) ?: error("Prototype with name $refName not found in SolidReferenceGroup ${owner.refName}") - proto as? Solid ?: error("Prototype with name $refName is ${proto::class} but expected Solid") -// proto.unref as? Solid -// ?: error("Prototype with name $refName is ${proto::class} but expected Solid") + proto.unref as? Solid + ?: error("Prototype with name $refName is ${proto::class} but expected Solid") } } - override val meta: ObservableMutableMeta by lazy { - owner.meta.getOrCreate(childToken(refName).asName()) - } - override val children: Map get() = (prototype as? VisionGroup)?.children ?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN } @@ -122,6 +118,20 @@ public class SolidReferenceGroup( ReferenceChild(owner, refName + key.asName()) } ?: emptyMap() + override fun getOwnProperty(name: Name): MetaItem? = + owner.getOwnProperty(childPropertyName(refName, name)) + + override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { + owner.setProperty(childPropertyName(refName, name), item, notify) + } + + override fun getProperty( + name: Name, + inherit: Boolean, + includeStyles: Boolean, + includeDefaults: Boolean, + ): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults) + override var parent: VisionGroup? get() { val parentName = refName.cutLast() @@ -131,17 +141,27 @@ public class SolidReferenceGroup( error("Setting a parent for a reference child is not possible") } + @DFExperimental + override val propertyChanges: Flow + get() = owner.propertyChanges.mapNotNull { name -> + if (name.startsWith(childToken(refName))) { + name.cutFirst() + } else { + null + } + } + override fun invalidateProperty(propertyName: Name) { owner.invalidateProperty(childPropertyName(refName, propertyName)) } override fun update(change: VisionChange) { change.properties?.let { - updateProperties(Name.EMPTY, it) + updateProperties(Name.EMPTY, it.asMetaItem()) } } - override val descriptor: MetaDescriptor get() = prototype.descriptor + override val descriptor: NodeDescriptor get() = prototype.descriptor } @@ -164,7 +184,7 @@ public fun SolidGroup.ref( public fun SolidGroup.ref( name: String, obj: Solid, - templateName: Name = Name.parse(name), + templateName: Name = name.toName(), ): SolidReferenceGroup { val existing = getPrototype(templateName) if (existing == null) { diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt index ca83f00f..67224699 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solids.kt @@ -69,4 +69,4 @@ public class Solids(meta: Meta) : VisionPlugin(meta) { @VisionBuilder @DFExperimental public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup = - SolidGroup().apply(block) + SolidGroup().apply(block).apply { root(this@solid.manager) } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt index 31611dc9..f5aa2d4a 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt @@ -95,13 +95,13 @@ public fun MutablePoint3D.normalizeInPlace() { z /= norm } -internal fun MetaProvider.point3D(default: Float = 0f) = object : Point3D { +internal fun ItemProvider.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(): Meta = Meta { +public fun Point3D.toMeta(): MetaBuilder = 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?.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 } + 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 } } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Axes.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Axes.kt index 485cc8bd..2f3e1645 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Axes.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Axes.kt @@ -3,9 +3,9 @@ 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.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.value +import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.double +import space.kscience.visionforge.value public class Axes : Scheme() { public var visible: Boolean by boolean(false) @@ -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: MetaDescriptor by lazy { - MetaDescriptor { + override val descriptor: NodeDescriptor by lazy { + NodeDescriptor { value(Axes::visible){ default(false) } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Camera.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Camera.kt index b8cb05d4..905fa234 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Camera.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Camera.kt @@ -2,10 +2,10 @@ package space.kscience.visionforge.solid.specifications import space.kscience.dataforge.meta.Scheme import space.kscience.dataforge.meta.SchemeSpec -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.value +import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.double import space.kscience.dataforge.meta.int +import space.kscience.visionforge.value import kotlin.math.PI public class Camera : Scheme() { @@ -27,24 +27,24 @@ public class Camera : Scheme() { public const val FAR_CLIP: Double = 10000.0 public const val FIELD_OF_VIEW: Int = 75 - override val descriptor: MetaDescriptor by lazy { - MetaDescriptor { - value(Camera::fov) { + override val descriptor: NodeDescriptor by lazy { + NodeDescriptor { + value(Camera::fov){ default(FIELD_OF_VIEW) } - value(Camera::nearClip) { + value(Camera::nearClip){ default(NEAR_CLIP) } - value(Camera::farClip) { + value(Camera::farClip){ default(FAR_CLIP) } - value(Camera::distance) { + value(Camera::distance){ default(INITIAL_DISTANCE) } - value(Camera::azimuth) { + value(Camera::azimuth){ default(INITIAL_AZIMUTH) } - value(Camera::latitude) { + value(Camera::latitude){ default(INITIAL_LATITUDE) } } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt index 4a819ff7..11dd5800 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt @@ -1,11 +1,13 @@ package space.kscience.visionforge.solid.specifications import space.kscience.dataforge.meta.* -import space.kscience.dataforge.meta.descriptors.MetaDescriptor -import space.kscience.dataforge.meta.descriptors.scheme -import space.kscience.dataforge.meta.descriptors.value +import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.attributes import space.kscience.dataforge.names.Name +import space.kscience.dataforge.values.ValueType import space.kscience.visionforge.hide +import space.kscience.visionforge.scheme +import space.kscience.visionforge.value import space.kscience.visionforge.widgetType public class Clipping : Scheme() { @@ -14,27 +16,30 @@ public class Clipping : Scheme() { public var z: Double? by double() public companion object : SchemeSpec(::Clipping) { - override val descriptor: MetaDescriptor = MetaDescriptor { + override val descriptor: NodeDescriptor = NodeDescriptor { value(Clipping::x) { widgetType = "range" - attributes["min"] = 0.0 - attributes["max"] = 1.0 - attributes["step"] = 0.01 - default(1.0) + attributes { + set("min", 0.0) + set("max", 1.0) + set("step", 0.01) + } } value(Clipping::y) { widgetType = "range" - attributes["min"] = 0.0 - attributes["max"] = 1.0 - attributes["step"] = 0.01 - default(1.0) + attributes { + set("min", 0.0) + set("max", 1.0) + set("step", 0.01) + } } value(Clipping::z) { widgetType = "range" - attributes["min"] = 0.0 - attributes["max"] = 1.0 - attributes["step"] = 0.01 - default(1.0) + attributes { + set("min", 0.0) + set("max", 1.0) + set("step", 0.01) + } } } } @@ -50,7 +55,7 @@ public class CanvasSize : Scheme() { public var maxHeight: Number by number { maxSize } public companion object : SchemeSpec(::CanvasSize) { - override val descriptor: MetaDescriptor = MetaDescriptor { + override val descriptor: NodeDescriptor = NodeDescriptor { value(CanvasSize::minSize) value(CanvasSize::minWith) value(CanvasSize::minHeight) @@ -77,22 +82,10 @@ public class Canvas3DOptions : Scheme() { public companion object : SchemeSpec(::Canvas3DOptions) { - override val descriptor: MetaDescriptor by lazy { - MetaDescriptor { + override val descriptor: NodeDescriptor by lazy { + NodeDescriptor { scheme(Canvas3DOptions::axes, Axes) - - value(Canvas3DOptions::layers) { - multiple = true - default(listOf(0)) - widgetType = "multiSelect" - allowedValues(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - } - - scheme(Canvas3DOptions::clipping, Clipping) - - scheme(Canvas3DOptions::light, Light){ - hide() - } + scheme(Canvas3DOptions::light, Light) scheme(Canvas3DOptions::camera, Camera) { hide() @@ -105,6 +98,15 @@ public class Canvas3DOptions : Scheme() { scheme(Canvas3DOptions::size, CanvasSize) { hide() } + + value(Canvas3DOptions::layers) { + type(ValueType.NUMBER) + multiple = true + default(listOf(0)) + widgetType = "multiSelect" + allow(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + } + scheme(Canvas3DOptions::clipping, Clipping) } } } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt index 39aadbf1..a51aaa25 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt @@ -1,10 +1,12 @@ package space.kscience.visionforge.solid.transform -import space.kscience.dataforge.meta.configure -import space.kscience.dataforge.meta.update +import space.kscience.dataforge.meta.itemSequence import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.asName -import space.kscience.visionforge.* +import space.kscience.visionforge.MutableVisionGroup +import space.kscience.visionforge.Vision +import space.kscience.visionforge.VisionGroup +import space.kscience.visionforge.meta import space.kscience.visionforge.solid.* private operator fun Number.plus(other: Number) = toFloat() + other.toFloat() @@ -22,8 +24,10 @@ internal fun Vision.updateFrom(other: Vision): Vision { scaleX *= other.scaleX scaleY *= other.scaleY scaleZ *= other.scaleZ - configure{ - update(other.meta) + other.meta.itemSequence().forEach { (name, item) -> + if (getProperty(name) == null) { + setProperty(name, item) + } } } return this diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/ConvexTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/ConvexTest.kt index a7133006..58cde48e 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/ConvexTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/ConvexTest.kt @@ -1,8 +1,9 @@ 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.toMeta +import space.kscience.dataforge.meta.toMetaItem import space.kscience.dataforge.misc.DFExperimental import kotlin.test.Test import kotlin.test.assertEquals @@ -28,9 +29,9 @@ class ConvexTest { val convex = group.children.values.first() as Convex val json = Solids.jsonForSolids.encodeToJsonElement(Convex.serializer(), convex) - val meta = json.toMeta() + val meta = json.toMetaItem().node!! - val points = meta.getIndexed("points").values.map { it.point3D() } + val points = meta.getIndexed("points").values.map { (it as MetaItemNode<*>).node.point3D() } assertEquals(8, points.count()) assertEquals(8, convex.points.size) diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/DescriptorTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/DescriptorTest.kt index 6f004f5c..95f6b23e 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/DescriptorTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/DescriptorTest.kt @@ -1,5 +1,6 @@ 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 @@ -15,7 +16,7 @@ class DescriptorTest { val axesSize = descriptor["axes.size"] assertNotNull(axesSize) assertTrue { - ValueType.NUMBER in axesSize.valueTypes!! + ValueType.NUMBER in (axesSize as ValueDescriptor).type!! } } } \ No newline at end of file diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/PropertyTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/PropertyTest.kt index ec58e4d2..1a9aa45b 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/PropertyTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/PropertyTest.kt @@ -1,54 +1,23 @@ package space.kscience.visionforge.solid -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.string +import space.kscience.dataforge.meta.int import space.kscience.dataforge.names.asName -import space.kscience.dataforge.values.int import space.kscience.visionforge.* import kotlin.test.Test import kotlin.test.assertEquals @Suppress("UNUSED_VARIABLE") class PropertyTest { - @Test - fun testColor(){ - val box = Box(10.0f, 10.0f,10.0f) - box.material { - //meta["color"] = "pink" - color("pink") - } - assertEquals("pink", box.meta["material.color"]?.string) - assertEquals("pink", box.color.string) - } - - @Test - fun testColorUpdate(){ - val box = Box(10.0f, 10.0f,10.0f) - - var c: String? = null - box.onPropertyChange { - if(it == SolidMaterial.MATERIAL_COLOR_KEY){ - c = box.color.string - } - } - - box.material { - color("pink") - } - - assertEquals("pink", c) - } - @Test fun testInheritedProperty() { var box: Box? = null val group = SolidGroup().apply { - setPropertyNode("test", 22) + setProperty("test", 22) group { box = box(100, 100, 100) } } - assertEquals(22, box?.getPropertyValue("test", inherit = true)?.int) + assertEquals(22, box?.getProperty("test", inherit = true).int) } @Test @@ -66,11 +35,11 @@ class PropertyTest { } } } - assertEquals(22, box?.getPropertyValue("test")?.int) + assertEquals(22, box?.getProperty("test").int) } @Test - fun testStyleColor() { + fun testColor() { var box: Box? = null val group = SolidGroup().apply { styleSheet { diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SerializationTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SerializationTest.kt index 8f5c3f57..6df7b285 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SerializationTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/SerializationTest.kt @@ -1,8 +1,10 @@ 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 import kotlin.test.Test import kotlin.test.assertEquals @@ -12,7 +14,7 @@ import kotlin.test.assertEquals */ fun SolidGroup.refGroup( name: String, - templateName: Name = Name.parse(name), + templateName: Name = name.toName(), block: MutableVisionGroup.() -> Unit ): SolidReferenceGroup { val group = SolidGroup().apply(block) @@ -28,7 +30,7 @@ class SerializationTest { x = 100 z = -100 } - val string = Solids.encodeToString(cube) + val string = Solids.encodeToString(cube) println(string) val newCube = Solids.decodeFromString(string) assertEquals(cube.meta, newCube.meta) @@ -41,10 +43,10 @@ class SerializationTest { x = 100 z = -100 } - val group = SolidGroup { + val group = SolidGroup{ ref("cube", cube) - refGroup("pg", Name.parse("pg.content")) { - sphere(50) { + refGroup("pg", "pg.content".toName()){ + sphere(50){ x = -100 } } diff --git a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/VisionUpdateTest.kt b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/VisionUpdateTest.kt index a08085d6..2e87adf8 100644 --- a/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/VisionUpdateTest.kt +++ b/visionforge-solid/src/commonTest/kotlin/space/kscience/visionforge/solid/VisionUpdateTest.kt @@ -2,9 +2,8 @@ package space.kscience.visionforge.solid import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.fetch -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.names.asName -import space.kscience.dataforge.values.asValue +import space.kscience.dataforge.meta.MetaItem +import space.kscience.dataforge.names.toName import space.kscience.visionforge.VisionChange import space.kscience.visionforge.get import kotlin.test.Test @@ -25,8 +24,8 @@ class VisionUpdateTest { color(123) box(100,100,100) } - propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue())) - propertyChanged("origin".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue())) + propertyChanged("top".toName(), SolidMaterial.MATERIAL_COLOR_KEY, MetaItem.of("red")) + propertyChanged("origin".toName(), SolidMaterial.MATERIAL_COLOR_KEY, MetaItem.of("red")) } targetVision.update(dif) assertTrue { targetVision["top"] is SolidGroup } @@ -41,12 +40,36 @@ class VisionUpdateTest { color(123) box(100,100,100) } - propertyChanged("top".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue())) - propertyChanged("origin".asName(), SolidMaterial.MATERIAL_COLOR_KEY, Meta("red".asValue())) + propertyChanged("top".toName(), SolidMaterial.MATERIAL_COLOR_KEY, MetaItem.of("red")) + propertyChanged("origin".toName(), SolidMaterial.MATERIAL_COLOR_KEY, MetaItem.of("red")) } val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change) println(serialized) val reconstructed = visionManager.jsonFormat.decodeFromString(VisionChange.serializer(), serialized) assertEquals(change.properties,reconstructed.properties) } + + @Test + fun testDeserialization(){ + val str = """ + { + "propertyChange": { + "layer[4]": { + "material": { + "color": 123 + } + }, + "layer[2]": { + "material": { + } + } + }, + "childrenChange": { + } + } + """.trimIndent() + + val reconstructed = visionManager.jsonFormat.decodeFromString(VisionChange.serializer(), str) + } + } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt index 8b1428ec..7cbd4d3d 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt @@ -4,20 +4,16 @@ import info.laht.threekt.core.BufferGeometry import info.laht.threekt.geometries.EdgesGeometry import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.Mesh -import space.kscience.dataforge.meta.updateWith +import space.kscience.dataforge.meta.boolean +import space.kscience.dataforge.meta.node import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.startsWith -import space.kscience.dataforge.values.boolean -import space.kscience.visionforge.computePropertyNode import space.kscience.visionforge.onPropertyChange -import space.kscience.visionforge.setProperty import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.layer -import space.kscience.visionforge.solid.three.MeshThreeFactory.Companion.EDGES_ENABLED_KEY -import space.kscience.visionforge.solid.three.MeshThreeFactory.Companion.EDGES_MATERIAL_KEY import kotlin.reflect.KClass /** @@ -44,7 +40,7 @@ public abstract class MeshThreeFactory( } //add listener to object properties - obj.onPropertyChange { name -> + obj.onPropertyChange(three.updateScope) { name -> when { name.startsWith(Solid.GEOMETRY_KEY) -> { val oldGeometry = mesh.geometry as BufferGeometry @@ -65,7 +61,6 @@ public abstract class MeshThreeFactory( public companion object { public val EDGES_KEY: Name = "edges".asName() - //public val WIREFRAME_KEY: Name = "wireframe".asName() public val ENABLED_KEY: Name = "enabled".asName() public val EDGES_ENABLED_KEY: Name = EDGES_KEY + ENABLED_KEY @@ -75,11 +70,6 @@ public abstract class MeshThreeFactory( } } -public fun Solid.edges(enabled: Boolean = true, block: SolidMaterial.() -> Unit = {}) { - setProperty(EDGES_ENABLED_KEY, enabled) - meta.getOrCreate(EDGES_MATERIAL_KEY).updateWith(SolidMaterial, block) -} - internal fun Mesh.applyProperties(obj: Solid): Mesh = apply { updateMaterial(obj) applyEdges(obj) @@ -93,9 +83,16 @@ internal fun Mesh.applyProperties(obj: Solid): Mesh = apply { public fun Mesh.applyEdges(obj: Solid) { val edges = children.find { it.name == "@edges" } as? LineSegments //inherited edges definition, enabled by default - if (obj.getPropertyValue(EDGES_ENABLED_KEY, inherit = true)?.boolean != false) { + if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY, inherit = true, includeStyles = true).boolean != false) { val bufferGeometry = geometry as? BufferGeometry ?: return - val material = ThreeMaterials.getLineMaterial(obj.computePropertyNode(EDGES_MATERIAL_KEY), true) + val material = ThreeMaterials.getLineMaterial( + obj.getProperty( + MeshThreeFactory.EDGES_MATERIAL_KEY, + inherit = true, + includeStyles = true + ).node, + true + ) if (edges == null) { add( LineSegments( diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index 0d223987..3afb6776 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -168,40 +168,42 @@ public class ThreeCanvas( } //Clipping planes - options.useProperty(Canvas3DOptions::clipping){clipping -> - if (!clipping.meta.isEmpty()) { - renderer.localClippingEnabled = true - boundingBox?.let { boundingBox -> - val xClippingPlane = clipping.x?.let { - val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it - Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue) - } - val yClippingPlane = clipping.y?.let { - val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it - Plane(Vector3(0.0, -1.0, 0.0), absoluteValue) - } + options.onChange(this@ThreeCanvas) { name, _, _ -> + if (name.startsWith(Canvas3DOptions::clipping.name.asName())) { + val clipping = options.clipping + if (!clipping.isEmpty()) { + renderer.localClippingEnabled = true + boundingBox?.let { boundingBox -> + val xClippingPlane = clipping.x?.let { + val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it + Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue) - val zClippingPlane = clipping.z?.let { - val absoluteValue = boundingBox.min.z + (boundingBox.max.z - boundingBox.min.z) * it - Plane(Vector3(0.0, 0.0, -1.0), absoluteValue) + } + val yClippingPlane = clipping.y?.let { + val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it + Plane(Vector3(0.0, -1.0, 0.0), absoluteValue) + } + + val zClippingPlane = clipping.z?.let { + val absoluteValue = boundingBox.min.z + (boundingBox.max.z - boundingBox.min.z) * it + Plane(Vector3(0.0, 0.0, -1.0), absoluteValue) + } + renderer.clippingPlanes = + listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray() } - renderer.clippingPlanes = - listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray() + } else { + renderer.localClippingEnabled = false + } + } else if (name.startsWith(Canvas3DOptions::size.name.asName())) { + canvas.style.apply { + minWidth = "${options.size.minWith.toInt()}px" + maxWidth = "${options.size.maxWith.toInt()}px" + minHeight = "${options.size.minHeight.toInt()}px" + maxHeight = "${options.size.maxHeight.toInt()}px" } - } else { - renderer.localClippingEnabled = false } - } - options.useProperty(Canvas3DOptions::size){ - canvas.style.apply { - minWidth = "${options.size.minWith.toInt()}px" - maxWidth = "${options.size.maxWith.toInt()}px" - minHeight = "${options.size.minHeight.toInt()}px" - maxHeight = "${options.size.maxHeight.toInt()}px" - } } - } /** @@ -210,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.parse(name) + name.toName() } else { - (parent?.fullName() ?: Name.EMPTY) + Name.parse(name) + (parent?.fullName() ?: Name.EMPTY) + name.toName() } } @@ -235,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.meta["type"].string) { + when (controls["type"].string) { "trackball" -> TrackballControls(camera, element) else -> OrbitControls(camera, element) } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt index 7dd30a34..4855abcd 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt @@ -12,7 +12,7 @@ import org.w3c.dom.CanvasTextBaseline import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.MIDDLE import space.kscience.visionforge.solid.SolidLabel -import space.kscience.visionforge.solid.SolidMaterial +import space.kscience.visionforge.solid.color import space.kscience.visionforge.solid.three.ThreeCanvas.Companion.DO_NOT_HIGHLIGHT_TAG import kotlin.reflect.KClass @@ -26,7 +26,7 @@ public object ThreeCanvasLabelFactory : ThreeFactory { val canvas = document.createElement("canvas") as HTMLCanvasElement val context = canvas.getContext("2d") as CanvasRenderingContext2D context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}" - context.fillStyle = obj.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.value ?: "black" + context.fillStyle = obj.color.value ?: "black" context.textBaseline = CanvasTextBaseline.MIDDLE val metrics = context.measureText(obj.text) //canvas.width = metrics.width.toInt() diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt index 5705459a..2cdd12d3 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt @@ -48,7 +48,7 @@ public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory }.apply { updatePosition(obj) applyProperties(obj) - obj.onPropertyChange { name -> + obj.onPropertyChange(three.updateScope) { name -> when { //name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) name.startsWith(MeshThreeFactory.EDGES_KEY) -> applyEdges(obj) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt index fc226de6..c1c5aa72 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt @@ -40,7 +40,7 @@ public class ThreeGeometryBuilder : GeometryBuilder { } override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) { - val actualNormal: Point3D = normal ?: ((vertex3 - vertex2) cross (vertex1 - vertex2)) + val actualNormal: Point3D = normal ?: (vertex3 - vertex2) cross (vertex1 - vertex2) indices.add( vertex(vertex1, actualNormal), vertex(vertex2, actualNormal), diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt index 34ee5ed1..d0c10605 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt @@ -27,7 +27,7 @@ public object ThreeLabelFactory : ThreeFactory { return Mesh(textGeo, ThreeMaterials.DEFAULT).apply { updateMaterial(obj) updatePosition(obj) - obj.onPropertyChange { _ -> + obj.onPropertyChange(three.updateScope) { _ -> //TODO three.logger.warn { "Label parameter change not implemented" } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt index ee36d74b..b2858fdb 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt @@ -4,14 +4,12 @@ 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.visionforge.computePropertyNode +import space.kscience.dataforge.meta.node import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.solid.PolyLine -import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.color import space.kscience.visionforge.solid.string import space.kscience.visionforge.solid.three.ThreeMaterials.DEFAULT_LINE_COLOR -import kotlin.math.ceil import kotlin.reflect.KClass public object ThreeLineFactory : ThreeFactory { @@ -19,15 +17,10 @@ public object ThreeLineFactory : ThreeFactory { override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D { val geometry = BufferGeometry().apply { - setFromPoints(Array((obj.points.size - 1) * 2) { - obj.points[ceil(it / 2.0).toInt()].toVector() - }) + setFromPoints(Array(obj.points.size) { obj.points[it].toVector() }) } - val material = ThreeMaterials.getLineMaterial( - obj.computePropertyNode(SolidMaterial.MATERIAL_KEY), - false - ) + val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true) material.linewidth = obj.thickness.toDouble() material.color = obj.color.string?.let { Color(it) } ?: DEFAULT_LINE_COLOR @@ -36,7 +29,7 @@ public object ThreeLineFactory : ThreeFactory { updatePosition(obj) //layers.enable(obj.layer) //add listener to object properties - obj.onPropertyChange { propertyName -> + obj.onPropertyChange(three.updateScope) { propertyName -> updateProperty(obj, propertyName) } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMaterials.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMaterials.kt index 522c0362..5f7ef051 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMaterials.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMaterials.kt @@ -3,18 +3,18 @@ package space.kscience.visionforge.solid.three import info.laht.threekt.materials.LineBasicMaterial import info.laht.threekt.materials.Material import info.laht.threekt.materials.MeshBasicMaterial +import info.laht.threekt.materials.MeshPhongMaterial import info.laht.threekt.math.Color import info.laht.threekt.objects.Mesh import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.asName -import space.kscience.dataforge.values.* +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.Vision -import space.kscience.visionforge.computePropertyNode -import space.kscience.visionforge.getStyleNodes +import space.kscience.visionforge.ownProperties import space.kscience.visionforge.solid.SolidMaterial -import space.kscience.visionforge.solid.SolidReference public object ThreeMaterials { @@ -41,7 +41,7 @@ public object ThreeMaterials { private val lineMaterialCache = HashMap() private fun buildLineMaterial(meta: Meta): LineBasicMaterial = LineBasicMaterial().apply { - color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_LINE_COLOR + color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_LINE_COLOR opacity = meta[SolidMaterial.OPACITY_KEY].double ?: 1.0 transparent = opacity < 1.0 linewidth = meta["thickness"].double ?: 1.0 @@ -58,60 +58,56 @@ public object ThreeMaterials { private val materialCache = HashMap() - internal fun buildMaterial(meta: Meta): Material = MeshBasicMaterial().apply { - 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 - needsUpdate = true + internal fun buildMaterial(meta: Meta): Material { + return meta[SolidMaterial.SPECULAR_COLOR_KEY]?.let { specularColor -> + MeshPhongMaterial().apply { + color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR + specular = specularColor.getColor() + emissive = specular + reflectivity = 0.5 + refractionRatio = 1.0 + shininess = 100.0 + opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0 + transparent = opacity < 1.0 + wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false + needsUpdate = true + } + } ?: MeshBasicMaterial().apply { + color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR + opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0 + transparent = opacity < 1.0 + wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false + needsUpdate = true + } + } -// val material = SolidMaterial.read(meta) -// return meta[SolidMaterial.SPECULAR_COLOR_KEY]?.let { specularColor -> -// MeshPhongMaterial().apply { -// color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR -// specular = specularColor.threeColor() -// emissive = material.emissiveColor.threeColor() ?: specular -// reflectivity = 0.5 -// refractionRatio = 1.0 -// shininess = 100.0 -// opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0 -// transparent = opacity < 1.0 -// wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false -// needsUpdate = true -// } -// } ?: MeshBasicMaterial().apply { -// 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 -// needsUpdate = true -// } internal fun cacheMaterial(meta: Meta): Material = materialCache.getOrPut(meta) { buildMaterial(meta).apply { cached = true } } + } /** - * Compute color + * Infer color based on meta item */ -public fun Meta.threeColor(): Color? { - val value = getValue(Name.EMPTY) - return if (isLeaf) { - when { - value == null -> null - value === Null -> null - value.type == ValueType.NUMBER -> Color(value.int) - else -> Color(value.string) +public fun MetaItem.getColor(): Color { + return when (this) { + is MetaItemValue -> if (this.value.type == ValueType.NUMBER) { + val int = value.int + Color(int) + } else { + Color(this.value.string) + } + is MetaItemNode -> { + Color( + node[Colors.RED_KEY]?.int ?: 0, + node[Colors.GREEN_KEY]?.int ?: 0, + node[Colors.BLUE_KEY]?.int ?: 0 + ) } - } else { - Color( - getValue(Colors.RED_KEY.asName())?.int ?: 0, - getValue(Colors.GREEN_KEY.asName())?.int ?: 0, - getValue(Colors.BLUE_KEY.asName())?.int ?: 0 - ) } } @@ -122,19 +118,35 @@ private var Material.cached: Boolean } public fun Mesh.updateMaterial(vision: Vision) { - val ownMaterialMeta = vision.meta.getMeta(SolidMaterial.MATERIAL_KEY) - if (ownMaterialMeta == null) { - if (vision is SolidReference && vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()) { - updateMaterial(vision.prototype) - } else { - material = vision.computePropertyNode(SolidMaterial.MATERIAL_KEY)?.let { + //val meta = vision.getProperty(SolidMaterial.MATERIAL_KEY, inherit = true).node + val ownMaterialMeta = vision.ownProperties[SolidMaterial.MATERIAL_KEY] + val parentMaterialMeta = vision.parent?.getProperty( + SolidMaterial.MATERIAL_KEY, + inherit = true, + includeStyles = false, + includeDefaults = false + ) + + material = when { + ownMaterialMeta == null && parentMaterialMeta == null -> { + //If material is style-based, use cached + vision.getProperty( + SolidMaterial.MATERIAL_KEY, + inherit = false, + includeStyles = true, + includeDefaults = false + ).node?.let { ThreeMaterials.cacheMaterial(it) } ?: ThreeMaterials.DEFAULT } - } else { - material = vision.computePropertyNode(SolidMaterial.MATERIAL_KEY)?.let { - ThreeMaterials.buildMaterial(it) - } ?: ThreeMaterials.DEFAULT + else -> { + vision.getProperty( + SolidMaterial.MATERIAL_KEY, + inherit = true + ).node?.let { + ThreeMaterials.buildMaterial(it) + } ?: ThreeMaterials.DEFAULT + } } } @@ -145,24 +157,32 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) { } else { when (propertyName) { SolidMaterial.MATERIAL_COLOR_KEY -> { - material.asDynamic().color = vision.computePropertyNode(SolidMaterial.MATERIAL_COLOR_KEY)?.threeColor() - ?: ThreeMaterials.DEFAULT_COLOR + material.asDynamic().color = vision.getProperty( + SolidMaterial.MATERIAL_COLOR_KEY, + inherit = true, + includeStyles = true, + includeDefaults = false + )?.getColor() ?: ThreeMaterials.DEFAULT_COLOR material.needsUpdate = true } SolidMaterial.MATERIAL_OPACITY_KEY -> { - val opacity = vision.getPropertyValue( + val opacity = vision.getProperty( SolidMaterial.MATERIAL_OPACITY_KEY, inherit = true, - )?.double ?: 1.0 + includeStyles = true, + includeDefaults = false + ).double ?: 1.0 material.opacity = opacity material.transparent = opacity < 1.0 material.needsUpdate = true } SolidMaterial.MATERIAL_WIREFRAME_KEY -> { - material.asDynamic().wireframe = vision.getPropertyValue( + material.asDynamic().wireframe = vision.getProperty( SolidMaterial.MATERIAL_WIREFRAME_KEY, inherit = true, - )?.boolean ?: false + includeStyles = true, + includeDefaults = false + ).boolean ?: false material.needsUpdate = true } else -> console.warn("Unrecognized material property: $propertyName") diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt index aa5c2a14..e6d85dc5 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt @@ -2,6 +2,8 @@ package space.kscience.visionforge.solid.three import info.laht.threekt.core.Object3D import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.w3c.dom.Element import org.w3c.dom.HTMLElement import space.kscience.dataforge.context.* @@ -13,7 +15,6 @@ import space.kscience.visionforge.Vision import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.specifications.Canvas3DOptions -import space.kscience.visionforge.solid.three.set import space.kscience.visionforge.visible import kotlin.collections.set import kotlin.reflect.KClass @@ -68,7 +69,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { updatePosition(obj) //obj.onChildrenChange() - obj.onPropertyChange { name -> + obj.onPropertyChange(updateScope) { name -> if ( name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.ROTATION_KEY) || @@ -81,11 +82,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } } - obj.onStructureChanged(this){ childName -> - val child = get(childName) - + obj.structureChanges.onEach { (nameToken, _, child) -> //removing old object - findChild(childName)?.let { oldChild -> + findChild(nameToken.asName())?.let { oldChild -> oldChild.parent?.remove(oldChild) } @@ -93,12 +92,12 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { if (child != null && child is Solid) { try { val object3D = buildObject3D(child) - set(childName, object3D) + set(nameToken, object3D) } catch (ex: Throwable) { logger.error(ex) { "Failed to render $child" } } } - } + }.launchIn(updateScope) } } is Composite -> compositeFactory(this, obj) @@ -144,7 +143,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { element, vision as? Solid ?: error("Solid expected but ${vision::class} found"), ).apply { - options.meta.update(meta) + options.update(meta) } } @@ -196,4 +195,8 @@ internal fun Object3D.findChild(name: Name): Object3D? { name.length == 1 -> this.children.find { it.name == name.tokens.first().toString() } else -> findChild(name.tokens.first().asName())?.findChild(name.cutFirst()) } +} + +public fun Context.withThreeJs(): Context = apply { + plugins.fetch(ThreePlugin) } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt index aa779e46..33fd3ccb 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt @@ -1,10 +1,11 @@ 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 @@ -18,7 +19,7 @@ public object ThreeReferenceFactory : ThreeFactory { private fun Object3D.replicate(): Object3D { return when (this) { - is Mesh -> Mesh(geometry, material).also { + is Mesh -> Mesh(geometry as BufferGeometry, material).also { it.applyMatrix4(matrix) } else -> clone(false) @@ -46,9 +47,9 @@ public object ThreeReferenceFactory : ThreeFactory { //TODO apply child properties - obj.onPropertyChange { name-> + obj.onPropertyChange(three.updateScope) { name-> if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) { - val childName = name.firstOrNull()?.index?.let(Name::parse) ?: error("Wrong syntax for reference child property: '$name'") + val childName = name.firstOrNull()?.index?.toName() ?: 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") diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/csg.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/csg.kt index ac2fefc0..50e48ee9 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/csg.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/csg.kt @@ -3,8 +3,7 @@ "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", - "EXTERNAL_DELEGATION", - "NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING" + "EXTERNAL_DELEGATION" ) @file:JsModule("three-csg-ts") diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt index ea609e58..9e78800b 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt @@ -8,7 +8,7 @@ import info.laht.threekt.math.Euler import info.laht.threekt.math.Vector3 import info.laht.threekt.objects.Mesh import info.laht.threekt.textures.Texture -import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.MetaItem import space.kscience.dataforge.meta.float import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.node @@ -17,7 +17,7 @@ import kotlin.math.PI public val Solid.euler: Euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name) -public val Meta.vector: Vector3 get() = Vector3(this["x"].float ?: 0f, this["y"].float ?: 0f, this["z"].float ?: 0f) +public val MetaItem.vector: Vector3 get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f) internal fun Double.toRadians() = this * PI / 180