Merge branch 'dev' into tutorial

This commit is contained in:
kiruma524 2021-08-17 19:09:37 +03:00 committed by GitHub
commit efec462d98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
137 changed files with 2609 additions and 2166 deletions

33
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,33 @@
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

View File

@ -1,19 +0,0 @@
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

28
.github/workflows/pages.yml vendored Normal file
View File

@ -0,0 +1,28 @@
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

View File

@ -1,26 +1,21 @@
plugins { plugins {
id("ru.mipt.npm.gradle.project") 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.4.3") val dataforgeVersion by extra("0.5.1")
val fxVersion by extra("11") val fxVersion by extra("11")
allprojects { allprojects {
repositories { repositories {
mavenLocal()
mavenCentral() mavenCentral()
jcenter()
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://maven.jzy3d.org/releases") maven("https://maven.jzy3d.org/releases")
} }
group = "space.kscience" group = "space.kscience"
version = "0.2.0-dev-22" version = "0.2.0-dev-23"
} }
subprojects { subprojects {
@ -29,7 +24,7 @@ subprojects {
} }
} }
ksciencePublish{ ksciencePublish {
github("visionforge") github("visionforge")
space() space()
sonatype() sonatype()

View File

@ -16,7 +16,7 @@ kotlin {
jvm { jvm {
withJava() withJava()
} }
js{ js {
useCommonJs() useCommonJs()
browser { browser {
commonWebpackConfig { commonWebpackConfig {
@ -34,6 +34,7 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
implementation(project(":visionforge-fx")) implementation(project(":visionforge-fx"))
implementation("ch.qos.logback:logback-classic:1.2.5")
} }
} }
jsMain { jsMain {
@ -53,5 +54,5 @@ application {
val convertGdmlToJson by tasks.creating(JavaExec::class) { val convertGdmlToJson by tasks.creating(JavaExec::class) {
group = "application" group = "application"
classpath = sourceSets["main"].runtimeClasspath classpath = sourceSets["main"].runtimeClasspath
main = "space.kscience.dataforge.vis.spatial.gdml.demo.SaveToJsonKt" mainClass.set("space.kscience.dataforge.vis.spatial.gdml.demo.SaveToJsonKt")
} }

View File

@ -1,32 +1,41 @@
package space.kscience.visionforge.gdml package space.kscience.visionforge.gdml
import space.kscience.dataforge.meta.string import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.values.asValue import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.string
import space.kscience.gdml.GdmlShowCase 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.setProperty
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.material
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
class GDMLVisionTest { class GDMLVisionTest {
private val cubes = GdmlShowCase.cubes().toVision()
// @Test @Test
// fun testCubesStyles(){ fun testCubesStyles(){
// val cubes = gdml.toVision() val segment = cubes["composite-000.segment-0"] as Solid
// val segment = cubes["composite000.segment_0".toName()] as Solid println(segment.computeProperties().getValue(Vision.STYLE_KEY))
// println(segment.styles) // println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY))
// println(segment.material) // println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY))
// }
println(segment.material?.meta)
//println(Solids.encodeToString(cubes))
}
@Test @Test
fun testPrototypeProperty() { fun testPrototypeProperty() {
val vision = GdmlShowCase.cubes().toVision() val child = cubes[Name.of("composite-000","segment-0")]
val child = vision["composite-000.segment-0".toName()]
assertNotNull(child) assertNotNull(child)
child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue()) child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string) assertEquals("red", child.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.string)
} }
} }

View File

@ -1,6 +1,12 @@
package space.kscience.visionforge.gdml.demo package space.kscience.visionforge.gdml.demo
import kotlinx.browser.window import kotlinx.browser.window
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.css.height
import kotlinx.css.pt
import kotlinx.css.vh
import org.w3c.files.File
import org.w3c.files.FileReader import org.w3c.files.FileReader
import org.w3c.files.get import org.w3c.files.get
import react.* import react.*
@ -17,6 +23,8 @@ import space.kscience.visionforge.ring.tab
import space.kscience.visionforge.root import space.kscience.visionforge.root
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import styled.css
import styled.styledDiv
external interface GDMLAppProps : RProps { external interface GDMLAppProps : RProps {
var context: Context var context: Context
@ -27,52 +35,62 @@ external interface GDMLAppProps : RProps {
@JsExport @JsExport
val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props -> val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager } val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager }
var vision: Solid? by useState { props.vision?.apply { root(visionManager) } } var deferredVision: Deferred<Solid?> by useState {
CompletableDeferred(props.vision)
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()
}
}
name.endsWith(".json") -> visionManager.decodeFromString(data)
else -> {
window.alert("File extension is not recognized: $name")
error("File extension is not recognized: $name")
}
}
vision = parsedVision as? Solid ?: error("Parsed vision is not a solid")
} }
child(ThreeCanvasWithControls) { fun readFileAsync(file: File): Deferred<Solid?> {
attrs { val deferred = CompletableDeferred<Solid?>()
this.context = props.context FileReader().apply {
this.solid = vision onload = {
this.selected = props.selected val data = result as String
tab("Load") { val name = file.name
h2 { val parsedVision = when {
+"Drag and drop .gdml or .json VisionForge files here" 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")
}
} }
fileDrop("(drag file here)") { files -> deferred.complete(parsedVision as? Solid ?: error("Parsed vision is not a solid"))
val file = files?.get(0) }
if (file != null) { readAsText(file)
FileReader().apply { }
onload = {
val string = result as String return deferred
loadData(file.name, string) }
}
readAsText(file) styledDiv {
css {
height = 100.vh - 12.pt
}
child(ThreeCanvasWithControls) {
attrs {
this.context = props.context
this.builderOfSolid = deferredVision
this.selected = props.selected
tab("Load") {
h2 {
+"Drag and drop .gdml or .json VisionForge files here"
}
fileDrop("(drag file here)") { files ->
val file = files?.get(0)
if (file != null) {
deferredVision = readFileAsync(file)
} }
} }
} }
} }
}
}
} }
} }

View File

@ -7,9 +7,8 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch import space.kscience.dataforge.context.fetch
import space.kscience.gdml.GdmlShowCase import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.VisionManager import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.describedProperties import space.kscience.visionforge.editor.VisionEditorFragment
import space.kscience.visionforge.editor.VisualObjectEditorFragment import space.kscience.visionforge.editor.VisionTreeFragment
import space.kscience.visionforge.editor.VisualObjectTreeFragment
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.solid.FX3DPlugin import space.kscience.visionforge.solid.FX3DPlugin
import space.kscience.visionforge.solid.FXCanvas3D import space.kscience.visionforge.solid.FXCanvas3D
@ -29,25 +28,22 @@ class GDMLView : View() {
private val visionManager = context.fetch(VisionManager) private val visionManager = context.fetch(VisionManager)
private val canvas = FXCanvas3D(fx3d) private val canvas = FXCanvas3D(fx3d)
private val treeFragment = VisualObjectTreeFragment().apply { private val treeFragment = VisionTreeFragment().apply {
this.itemProperty.bind(canvas.rootObjectProperty) this.itemProperty.bind(canvas.rootObjectProperty)
} }
private val propertyEditor = VisualObjectEditorFragment { private val propertyEditor = VisionEditorFragment().apply {
it.describedProperties
}.apply {
descriptorProperty.set(SolidMaterial.descriptor) descriptorProperty.set(SolidMaterial.descriptor)
itemProperty.bind(treeFragment.selectedProperty) visionProperty.bind(treeFragment.selectedProperty)
} }
override val root: Parent = borderpane { override val root: Parent = borderpane {
top { top {
buttonbar { buttonbar {
button("Load GDML/json") { button("Load GDML/json") {
action { action {
val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull() val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
if(file!= null) { if (file != null) {
runAsync { runAsync {
visionManager.readFile(file) as Solid visionManager.readFile(file) as Solid
} ui { } ui {

View File

@ -1,17 +1,35 @@
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import react.child import react.child
import react.dom.render import react.dom.render
import ringui.SmartTabs
import ringui.Tab
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.gdml.GdmlShowCase import space.kscience.plotly.models.Trace
import space.kscience.plotly.scatter
import space.kscience.visionforge.Application import space.kscience.visionforge.Application
import space.kscience.visionforge.VisionClient import space.kscience.visionforge.VisionClient
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.ring.ThreeWithControlsPlugin
import space.kscience.visionforge.ring.solid
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.startApplication import space.kscience.visionforge.startApplication
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import kotlin.math.sqrt
import kotlin.random.Random
fun Trace.appendXYLatest(x: Number, y: Number, history: Int = 400, xErr: Number? = null, yErr: Number? = null) {
this.x.numbers = (this.x.numbers + x).takeLast(history)
this.y.numbers = (this.y.numbers + y).takeLast(history)
xErr?.let { error_x.array = (error_x.array + xErr).takeLast(history) }
yErr?.let { error_y.array = (error_y.array + yErr).takeLast(history) }
}
private class JsPlaygroundApp : Application { private class JsPlaygroundApp : Application {
@ -20,24 +38,124 @@ private class JsPlaygroundApp : Application {
val playgroundContext = Context { val playgroundContext = Context {
plugin(ThreeWithControlsPlugin) plugin(ThreeWithControlsPlugin)
plugin(VisionClient) plugin(VisionClient)
plugin(PlotlyPlugin)
} }
val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page") val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page")
val visionOfD0 = GdmlShowCase.babyIaxo().toVision() val bouncingSphereTrace = Trace()
render(element) { render(element) {
styledDiv { styledDiv {
css{ css {
padding(0.pt) padding(0.pt)
margin(0.pt) margin(0.pt)
height = 100.vh height = 100.vh
width = 100.vw width = 100.vw
} }
child(ThreeCanvasWithControls) { SmartTabs("gravity") {
attrs { Tab("gravity") {
context = playgroundContext styledDiv {
solid = visionOfD0 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.appendXYLatest(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)
}
}
}
}
} }
} }
} }

View File

@ -0,0 +1,34 @@
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<PlotlyProps>("Plotly"){props ->
val elementRef = useRef<Element>(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
}
}

View File

@ -17,6 +17,14 @@ kotlin {
jvm { jvm {
withJava() withJava()
} }
js {
useCommonJs()
browser {
commonWebpackConfig {
cssSupport.enabled = false
}
}
}
afterEvaluate { afterEvaluate {
val jsBrowserDistribution by tasks.getting val jsBrowserDistribution by tasks.getting
@ -43,7 +51,7 @@ kotlin {
} }
jsMain { jsMain {
dependencies { dependencies {
implementation(project(":ui:bootstrap")) implementation(project(":ui:ring"))
implementation("io.ktor:ktor-client-js:$ktorVersion") implementation("io.ktor:ktor-client-js:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion") implementation("io.ktor:ktor-client-serialization:$ktorVersion")
implementation(project(":visionforge-threejs")) implementation(project(":visionforge-threejs"))

View File

@ -6,6 +6,7 @@ import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
import space.kscience.visionforge.VisionManager import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.removeAll import space.kscience.visionforge.removeAll
import space.kscience.visionforge.root import space.kscience.visionforge.root
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import kotlin.math.PI import kotlin.math.PI
@ -37,6 +38,10 @@ class Model(val manager: VisionManager) {
val root: SolidGroup = SolidGroup().apply { val root: SolidGroup = SolidGroup().apply {
root(this@Model.manager) root(this@Model.manager)
material {
wireframe
color("darkgreen")
}
rotationX = PI / 2 rotationX = PI / 2
group("bottom") { group("bottom") {
Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach { Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach {
@ -59,6 +64,7 @@ class Model(val manager: VisionManager) {
} }
private fun highlight(pixel: String) { private fun highlight(pixel: String) {
println("highlight $pixel")
map[pixel]?.color?.invoke("blue") map[pixel]?.color?.invoke("blue")
} }
@ -70,6 +76,7 @@ class Model(val manager: VisionManager) {
} }
fun displayEvent(event: Event) { fun displayEvent(event: Event) {
println("Received event: $event")
events.add(event) events.add(event)
event.hits.forEach { event.hits.forEach {
highlight(it) highlight(it)
@ -77,6 +84,7 @@ class Model(val manager: VisionManager) {
event.track?.let { event.track?.let {
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") { tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") {
thickness = 4 thickness = 4
color("red")
} }
} }
} }

View File

@ -98,7 +98,7 @@ class SC16(
} }
val offset = Point3D(-y, x, 0)//rotateDetector(Point3D(x, y, 0.0)); val offset = Point3D(-y, x, 0)//rotateDetector(Point3D(x, y, 0.0));
val pixelName = "${name}_${index}" val pixelName = "${name}_${index}"
SC1(pixelName, center + offset) SC1(pixelName, offset + center)
} }
} }
} }

View File

@ -2,29 +2,27 @@ package ru.mipt.npm.muon.monitor
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import react.* import react.*
import react.dom.* import react.dom.attrs
import react.dom.button
import react.dom.p
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.names.Name 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.bootstrap.canvasControls
import space.kscience.visionforge.bootstrap.card
import space.kscience.visionforge.bootstrap.gridRow
import space.kscience.visionforge.bootstrap.visionPropertyEditor
import space.kscience.visionforge.react.ThreeCanvasComponent
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.visionTree import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.tab
import space.kscience.visionforge.solid.specifications.Camera import space.kscience.visionforge.solid.specifications.Camera
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.edges
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import styled.styledSpan
import kotlin.math.PI import kotlin.math.PI
external interface MMAppProps : RProps { external interface MMAppProps : RProps {
@ -34,13 +32,9 @@ external interface MMAppProps : RProps {
var selected: Name? var selected: Name?
} }
@OptIn(DelicateCoroutinesApi::class)
@JsExport @JsExport
val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props -> val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
var selected by useState { props.selected }
val onSelect: (Name?) -> Unit = {
selected = it
}
val mmOptions = useMemo { val mmOptions = useMemo {
Canvas3DOptions { Canvas3DOptions {
@ -49,148 +43,219 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
latitude = PI / 6 latitude = PI / 6
azimuth = PI + PI / 6 azimuth = PI + PI / 6
} }
this.onSelect = onSelect
} }
} }
val root = props.model.root val root = useMemo(props.model) {
props.model.root.apply {
gridRow { edges()
flexColumn {
css {
+"col-lg-3"
+"order-lg-1"
+"order-2"
padding(0.px)
overflowY = Overflow.auto
height = 100.vh
}
//tree
card("Object tree") {
css {
flex(1.0, 1.0, FlexBasis.auto)
}
visionTree(root, selected, onSelect)
}
} }
flexColumn { }
css {
+"col-lg-6"
+"order-lg-2"
+"order-1"
height = 100.vh
}
h1("mx-auto page-header") {
+"Muon monitor demo"
}
//canvas
child(ThreeCanvasComponent) { var events: Set<Event> by useState(emptySet())
attrs {
this.context = props.context styledDiv {
this.solid = root css {
this.selected = selected height = 100.vh - 12.pt
this.options = mmOptions
}
}
} }
flexColumn { child(ThreeCanvasWithControls) {
css { attrs {
+"col-lg-3" this.context = props.context
+"order-3" this.builderOfSolid = CompletableDeferred(root)
padding(0.px) this.selected = props.selected
height = 100.vh this.options = mmOptions
} tab("Events") {
styledDiv { flexColumn {
css { flexRow {
flex(0.0, 1.0, FlexBasis.zero) button {
} +"Next"
//settings
card("Canvas configuration") {
canvasControls(mmOptions, root)
}
card("Events") {
button {
+"Next"
attrs {
onClickFunction = {
GlobalScope.launch {
val event = props.connection.get<Event>("http://localhost:8080/event")
props.model.displayEvent(event)
}
}
}
}
button {
+"Clear"
attrs {
onClickFunction = {
props.model.reset()
}
}
}
}
}
styledDiv {
css {
padding(0.px)
}
nav {
attrs {
attributes["aria-label"] = "breadcrumb"
}
ol("breadcrumb") {
li("breadcrumb-item") {
button(classes = "btn btn-link p-0") {
+"World"
attrs { attrs {
onClickFunction = { onClickFunction = {
selected = Name.EMPTY context.launch {
} val event = props.connection.get<Event>(
} "http://localhost:8080/event"
} )
} events = events + event
if (selected != null) { props.model.displayEvent(event)
val tokens = ArrayList<NameToken>(selected?.length ?: 1)
selected?.tokens?.forEach { token ->
tokens.add(token)
val fullName = Name(tokens.toList())
li("breadcrumb-item") {
button(classes = "btn btn-link p-0") {
+token.toString()
attrs {
onClickFunction = {
console.log("Selected = $fullName")
selected = fullName
}
} }
} }
} }
} }
button {
+"Clear"
attrs {
onClickFunction = {
events = emptySet()
props.model.reset()
}
}
}
}
}
events.forEach { event ->
p {
styledSpan {
+event.id.toString()
}
+" : "
styledSpan {
css{
color = Color.blue
}
+event.hits.toString()
}
} }
} }
} }
} }
styledDiv {
css {
overflowY = Overflow.auto
}
//properties
card("Properties") {
selected.let { selected ->
val selectedObject: Vision? = when {
selected == null -> null
selected.isEmpty() -> root
else -> root[selected]
}
if (selectedObject != null) {
visionPropertyEditor(selectedObject, key = selected)
}
}
}
}
}
}
} }
// var selected by useState { props.selected }
//
// val onSelect: (Name?) -> Unit = {
// selected = it
// }
//
//
// gridRow {
// flexColumn {
// css {
// +"col-lg-3"
// +"order-lg-1"
// +"order-2"
// padding(0.px)
// overflowY = Overflow.auto
// height = 100.vh
// }
// //tree
// card("Object tree") {
// css {
// flex(1.0, 1.0, FlexBasis.auto)
// }
// visionTree(root, selected, onSelect)
// }
// }
// flexColumn {
// css {
// +"col-lg-6"
// +"order-lg-2"
// +"order-1"
// height = 100.vh
// }
// h1("mx-auto page-header") {
// +"Muon monitor demo"
// }
// //canvas
//
// child(ThreeCanvasComponent) {
// attrs {
// this.context = props.context
// this.solid = root
// this.selected = selected
// this.options = mmOptions
// }
// }
// }
// flexColumn {
// css {
// +"col-lg-3"
// +"order-3"
// padding(0.px)
// height = 100.vh
// }
// styledDiv {
// css {
// flex(0.0, 1.0, FlexBasis.zero)
// }
// //settings
// card("Canvas configuration") {
// canvasControls(mmOptions, root)
// }
//
// card("Events") {
// button {
// +"Next"
// attrs {
// onClickFunction = {
// GlobalScope.launch {
// val event = props.connection.get<Event>("http://localhost:8080/event")
// props.model.displayEvent(event)
// }
// }
// }
// }
// button {
// +"Clear"
// attrs {
// onClickFunction = {
// props.model.reset()
// }
// }
// }
// }
// }
// styledDiv {
// css {
// padding(0.px)
// }
// nav {
// attrs {
// attributes["aria-label"] = "breadcrumb"
// }
// ol("breadcrumb") {
// li("breadcrumb-item") {
// button(classes = "btn btn-link p-0") {
// +"World"
// attrs {
// onClickFunction = {
// selected = Name.EMPTY
// }
// }
// }
// }
// if (selected != null) {
// val tokens = ArrayList<NameToken>(selected?.length ?: 1)
// selected?.tokens?.forEach { token ->
// tokens.add(token)
// val fullName = Name(tokens.toList())
// li("breadcrumb-item") {
// button(classes = "btn btn-link p-0") {
// +token.toString()
// attrs {
// onClickFunction = {
// console.log("Selected = $fullName")
// selected = fullName
// }
// }
// }
// }
// }
// }
// }
// }
// }
// styledDiv {
// css {
// overflowY = Overflow.auto
// }
// //properties
// card("Properties") {
// selected.let { selected ->
// val selectedObject: Vision? = when {
// selected == null -> null
// selected.isEmpty() -> root
// else -> root[selected]
// }
// if (selectedObject != null) {
// visionPropertyEditor(selectedObject, key = selected)
// }
// }
// }
// }
// }
//
// }
} }

View File

@ -7,18 +7,14 @@ import kotlinx.browser.document
import react.child import react.child
import react.dom.render import react.dom.render
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch import space.kscience.dataforge.context.fetch
import space.kscience.visionforge.Application import space.kscience.visionforge.Application
import space.kscience.visionforge.VisionManager import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.bootstrap.useBootstrap import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication import space.kscience.visionforge.startApplication
private class MMDemoApp : Application { private class MMDemoApp : Application {
private val visionManager = Global.fetch(VisionManager)
private val model = Model(visionManager)
private val connection = HttpClient { private val connection = HttpClient {
install(JsonFeature) { install(JsonFeature) {
serializer = KotlinxSerializer() serializer = KotlinxSerializer()
@ -26,15 +22,19 @@ private class MMDemoApp : Application {
} }
override fun start(state: Map<String, Any>) { override fun start(state: Map<String, Any>) {
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 element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
val context = Context("demo")
render(element) { render(element) {
child(MMApp) { child(MMApp) {
attrs { attrs {
this.model = this@MMDemoApp.model this.model = model
this.connection = this@MMDemoApp.connection this.connection = this@MMDemoApp.connection
this.context = context this.context = context
} }

View File

@ -16,10 +16,10 @@ import kotlin.random.Random
*/ */
internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) { internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) {
// val layer: Layer = findLayer(center.z); // val layer: Layer = findLayer(center.z);
private val upLayer = private val upLayer = findLayer(sc.center.z + sc.zSize / 2f)
findLayer(sc.center.z + sc.zSize / 2f)//Layer("${name}_up", center.z + zSize / 2.0); //Layer("${name}_up", center.z + zSize / 2.0);
private val bottomLayer = private val bottomLayer = findLayer(sc.center.z - sc.zSize / 2f)
findLayer(sc.center.z - sc.zSize / 2f)//Layer("${name}_bottom", center.z - zSize / 2.0); //Layer("${name}_bottom", center.z - zSize / 2.0);
private val centralLayer = findLayer(sc.center.z) private val centralLayer = findLayer(sc.center.z)
private val center = Vector3D(sc.center.x.toDouble(), sc.center.y.toDouble(), sc.center.z.toDouble()) 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<SC1, SC1Aux>() private val auxCache = HashMap<SC1, SC1Aux>()
fun SC1.isHit(track: Line): Boolean{ fun SC1.isHit(track: Line): Boolean {
return auxCache.getOrPut(this){ return auxCache.getOrPut(this) {
SC1Aux(this) SC1Aux(this)
}.isHit(track) }.isHit(track)
} }

View File

@ -0,0 +1,3 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules)

View File

@ -20,7 +20,7 @@ kotlin {
this.outputFileName = "js/visionforge-playground.js" this.outputFileName = "js/visionforge-playground.js"
} }
commonWebpackConfig { commonWebpackConfig {
sourceMaps = false sourceMaps = true
cssSupport.enabled = false cssSupport.enabled = false
} }
} }
@ -37,7 +37,7 @@ kotlin {
} }
afterEvaluate { afterEvaluate {
val jsBrowserDistribution by tasks.getting val jsBrowserDistribution = tasks.getByName("jsBrowserDevelopmentExecutableDistribution")
tasks.getByName<ProcessResources>("jvmProcessResources") { tasks.getByName<ProcessResources>("jvmProcessResources") {
dependsOn(jsBrowserDistribution) dependsOn(jsBrowserDistribution)
@ -67,6 +67,7 @@ kotlin {
val jvmMain by getting{ val jvmMain by getting{
dependencies { dependencies {
api(project(":visionforge-server")) api(project(":visionforge-server"))
api(project(":visionforge-markdown"))
api("ch.qos.logback:logback-classic:1.2.3") api("ch.qos.logback:logback-classic:1.2.3")
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
} }

View File

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

View File

@ -17,7 +17,7 @@ fun main() {
context.makeVisionFile( context.makeVisionFile(
Paths.get("randomSpheres.html"), Paths.get("randomSpheres.html"),
resourceLocation = ResourceLocation.EMBED resourceLocation = ResourceLocation.SYSTEM
) { ) {
h1 { +"Happy new year!" } h1 { +"Happy new year!" }
div { div {

View File

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

View File

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

View File

@ -17,6 +17,7 @@ internal fun visionOfSatellite(
ySegmentSize: Number = xSegmentSize, ySegmentSize: Number = xSegmentSize,
fiberDiameter: Number = 1.0, fiberDiameter: Number = 1.0,
): SolidGroup = SolidGroup { ): SolidGroup = SolidGroup {
color("darkgreen")
val transparent by style { val transparent by style {
this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3 this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3
} }

View File

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

View File

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

View File

@ -1,14 +1,10 @@
package space.kscience.visionforge.solid.demo package space.kscience.visionforge.solid.demo
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.invoke import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Colors import space.kscience.visionforge.Colors
import space.kscience.visionforge.VisionLayout
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.visible import space.kscience.visionforge.visible
@ -23,7 +19,7 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
"title" put title "title" put title
} }
val vision = SolidGroup(block) val vision = SolidGroup(block)
render(name.toName(), vision) render(Name.parse(name), vision, meta)
} }
val canvasOptions = Canvas3DOptions { val canvasOptions = Canvas3DOptions {
@ -40,6 +36,7 @@ val canvasOptions = Canvas3DOptions {
} }
} }
@OptIn(DelicateCoroutinesApi::class)
fun VisionLayout<Solid>.showcase() { fun VisionLayout<Solid>.showcase() {
demo("shapes", "Basic shapes") { demo("shapes", "Basic shapes") {
box(100.0, 100.0, 100.0) { box(100.0, 100.0, 100.0) {
@ -77,7 +74,7 @@ fun VisionLayout<Solid>.showcase() {
//override color for this cube //override color for this cube
color(1530) color(1530)
launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
while (isActive) { while (isActive) {
delay(500) delay(500)
visible = !(visible ?: false) visible = !(visible ?: false)
@ -86,7 +83,7 @@ fun VisionLayout<Solid>.showcase() {
} }
} }
launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
val random = Random(111) val random = Random(111)
while (isActive) { while (isActive) {
delay(1000) delay(1000)

View File

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

View File

@ -3,6 +3,7 @@ package space.kscience.visionforge.solid.demo
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.BoxGeometry import info.laht.threekt.geometries.BoxGeometry
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.number import space.kscience.dataforge.meta.number
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
@ -43,13 +44,13 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
it.layers.enable(this@VariableBox.layer) it.layers.enable(this@VariableBox.layer)
} }
} }
mesh.scale.z = getOwnProperty(VALUE).number?.toDouble() ?: 1.0 mesh.scale.z = meta[VALUE].number?.toDouble() ?: 1.0
//add listener to object properties //add listener to object properties
onPropertyChange(three.context) { name -> onPropertyChange { name ->
when { when {
name == VALUE -> { name == VALUE -> {
val value = getOwnProperty(VALUE).int ?: 0 val value = meta.get(VALUE).int ?: 0
val size = value.toFloat() / 255f * 20f val size = value.toFloat() / 255f * 20f
mesh.scale.z = size.toDouble() mesh.scale.z = size.toDouble()
mesh.position.z = size.toDouble() / 2 mesh.position.z = size.toDouble() / 2
@ -69,7 +70,7 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
} }
var value: Int var value: Int
get() = getOwnProperty(VALUE).int ?: 0 get() = meta[VALUE].int ?: 0
set(value) { set(value) {
setProperty(VALUE, value.asValue()) setProperty(VALUE, value.asValue())
} }

View File

@ -14,7 +14,7 @@ class FXDemoApp : App(FXDemoGrid::class) {
stage.height = 600.0 stage.height = 600.0
view.showcase() view.showcase()
view.showcaseCSG() //view.showcaseCSG()
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
@startuml @startuml
'https://plantuml.com/class-diagram 'https://plantuml.com/class-diagram
interface Vision{ interface Vision{
val parent: VisionGroup? val parent: VisionGroup?
fun getProperty(name):TypedMetaItem? fun getProperty(name):TypedMetaItem?
@ -14,6 +15,7 @@ Vision <- Solid
class VisionGroup{ class VisionGroup{
a group of visions a group of visions
} }
Vision <-- VisionGroup Vision <-- VisionGroup
class VisionBase{ class VisionBase{

Binary file not shown.

View File

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

2
gradlew vendored Normal file → Executable file
View File

@ -72,7 +72,7 @@ case "`uname`" in
Darwin* ) Darwin* )
darwin=true darwin=true
;; ;;
MINGW* ) MSYS* | MINGW* )
msys=true msys=true
;; ;;
NONSTOP* ) NONSTOP* )

View File

@ -1,6 +1,6 @@
pluginManagement { pluginManagement {
val toolsVersion = "0.10.0" val toolsVersion = "0.10.2"
repositories { repositories {
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
@ -33,6 +33,7 @@ include(
":visionforge-gdml", ":visionforge-gdml",
":visionforge-server", ":visionforge-server",
":visionforge-plotly", ":visionforge-plotly",
":visionforge-markdown",
":demo:solid-showcase", ":demo:solid-showcase",
":demo:gdml", ":demo:gdml",
":demo:muon-monitor", ":demo:muon-monitor",

View File

@ -14,6 +14,7 @@ import react.dom.attrs
import react.dom.button import react.dom.button
import space.kscience.dataforge.meta.withDefault import space.kscience.dataforge.meta.withDefault
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.propertyEditor import space.kscience.visionforge.react.propertyEditor
@ -43,19 +44,19 @@ public external interface CanvasControlsProps : RProps {
public var vision: Vision? public var vision: Vision?
} }
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props -> public val CanvasControls: FunctionComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
flexColumn { flexColumn {
flexRow { flexRow {
css { css {
border(1.px, BorderStyle.solid, Color.blue) border(1.px, BorderStyle.solid, Color.blue)
padding(4.px) padding(4.px)
} }
props.vision?.manager?.let { manager -> props.vision?.let{ vision ->
button { button {
+"Export" +"Export"
attrs { attrs {
onClickFunction = { onClickFunction = {
val json = manager.encodeToString(props.vision!!) val json = vision.encodeToString()
saveData(it, "object.json", "text/json") { saveData(it, "object.json", "text/json") {
json json
} }
@ -65,8 +66,8 @@ public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functional
} }
} }
propertyEditor( propertyEditor(
ownProperties = props.canvasOptions, ownProperties = props.canvasOptions.meta,
allProperties = props.canvasOptions.withDefault(Canvas3DOptions.descriptor.defaultMeta), allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
descriptor = Canvas3DOptions.descriptor, descriptor = Canvas3DOptions.descriptor,
expanded = false expanded = false
) )

View File

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

View File

@ -21,7 +21,7 @@ public external interface ThreeControlsProps : RProps {
} }
@JsExport @JsExport
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props -> public val ThreeControls: FunctionComponent<ThreeControlsProps> = functionalComponent { props ->
tabPane(if (props.selected != null) "Properties" else null) { tabPane(if (props.selected != null) "Properties" else null) {
tab("Canvas") { tab("Canvas") {
card("Canvas configuration") { card("Canvas configuration") {

View File

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

View File

@ -8,12 +8,10 @@ import react.*
import react.dom.a import react.dom.a
import react.dom.attrs import react.dom.attrs
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaItemNode import space.kscience.dataforge.meta.descriptors.MetaDescriptor
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.descriptors.get
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.isLeaf
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.lastOrNull import space.kscience.dataforge.names.lastOrNull
@ -41,18 +39,19 @@ public external interface MetaViewerProps : RProps {
/** /**
* Root descriptor * Root descriptor
*/ */
public var descriptor: NodeDescriptor? public var descriptor: MetaDescriptor?
} }
private val MetaViewerItem: FunctionalComponent<MetaViewerProps> = functionalComponent("MetaViewerItem") { props -> private val MetaViewerItem: FunctionComponent<MetaViewerProps> = functionalComponent("MetaViewerItem") { props ->
metaViewerItem(props) metaViewerItem(props)
} }
private fun RBuilder.metaViewerItem(props: MetaViewerProps) { private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
var expanded: Boolean by useState { true } var expanded: Boolean by useState { true }
val item = props.root[props.name] val item = props.root[props.name]
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name) val descriptorItem: MetaDescriptor? = props.descriptor?.get(props.name)
val actualItem = item ?: descriptorItem?.defaultValue val actualValue = item?.value ?: descriptorItem?.defaultValue
val actualMeta = item ?: descriptorItem?.defaultNode
val token = props.name.lastOrNull()?.toString() ?: props.rootName ?: "" val token = props.name.lastOrNull()?.toString() ?: props.rootName ?: ""
@ -60,90 +59,75 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
expanded = !expanded expanded = !expanded
} }
when (actualItem) { flexRow {
is MetaItemNode -> { css {
flexRow { alignItems = Align.center
}
if (actualMeta?.isLeaf == false) {
styledSpan {
css { css {
alignItems = Align.center +TreeStyles.treeCaret
} if (expanded) {
styledSpan { +TreeStyles.treeCaredDown
css {
+TreeStyles.treeCaret
if (expanded) {
+TreeStyles.treeCaredDown
}
}
attrs {
onClickFunction = expanderClick
} }
} }
styledSpan { attrs {
css { onClickFunction = expanderClick
+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 { styledSpan {
css { css {
alignItems = Align.center +TreeStyles.treeLabel
if (item == null) {
+TreeStyles.treeLabelInactive
} }
styledSpan { }
+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 {
css { css {
+TreeStyles.treeLabel +TreeStyles.treeItem
if (item == null) { }
+TreeStyles.treeLabelInactive child(MetaViewerItem) {
attrs {
this.key = props.name.toString()
this.root = props.root
this.name = props.name + token
this.descriptor = props.descriptor
} }
} }
+token //configEditor(props.root, props.name + token, props.descriptor, props.default)
}
styledDiv {
a {
+actualItem.value.toString()
}
} }
} }
} }
} }
} }
@JsExport @JsExport
public val MetaViewer: FunctionalComponent<MetaViewerProps> = public val MetaViewer: FunctionComponent<MetaViewerProps> =
functionalComponent<MetaViewerProps>("MetaViewer") { props -> functionalComponent<MetaViewerProps>("MetaViewer") { props ->
child(MetaViewerItem) { child(MetaViewerItem) {
attrs { attrs {
@ -155,7 +139,7 @@ public val MetaViewer: FunctionalComponent<MetaViewerProps> =
} }
} }
public fun RBuilder.metaViewer(meta: Meta, descriptor: NodeDescriptor? = null, key: Any? = null) { public fun RBuilder.metaViewer(meta: Meta, descriptor: MetaDescriptor? = null, key: Any? = null) {
child(MetaViewer) { child(MetaViewer) {
attrs { attrs {
this.key = key?.toString() ?: "" this.key = key?.toString() ?: ""

View File

@ -5,32 +5,28 @@ import org.w3c.dom.HTMLOptionElement
import org.w3c.dom.HTMLSelectElement import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.asList import org.w3c.dom.asList
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.FunctionalComponent import react.FunctionComponent
import react.dom.attrs import react.dom.attrs
import react.dom.option import react.dom.option
import react.dom.select import react.dom.select
import react.functionalComponent import react.functionalComponent
import react.useState import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.meta.value
import space.kscience.dataforge.values.asValue import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.string import space.kscience.dataforge.values.string
@JsExport @JsExport
public val MultiSelectChooser: FunctionalComponent<ValueChooserProps> = public val MultiSelectChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("MultiSelectChooser") { props -> functionalComponent("MultiSelectChooser") { props ->
var selectedItems by useState { props.item.value?.list ?: emptyList() }
val onChange: (Event) -> Unit = { event: Event -> 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() } .map { (it as HTMLOptionElement).value.asValue() }
props.valueChanged?.invoke(newSelected.asValue()) props.meta.value = newSelected.asValue()
selectedItems = newSelected
} }
select { select {
attrs { attrs {
multiple = true multiple = true
values = selectedItems.mapTo(HashSet()) { it.string } values = (props.actual.value?.list ?: emptyList()).mapTo(HashSet()) { it.string }
onChangeFunction = onChange onChangeFunction = onChange
} }
props.descriptor?.allowedValues?.forEach { optionValue -> props.descriptor?.allowedValues?.forEach { optionValue ->

View File

@ -1,14 +1,5 @@
package space.kscience.visionforge.react 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.*
import kotlinx.css.properties.TextDecoration import kotlinx.css.properties.TextDecoration
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
@ -18,15 +9,10 @@ import react.*
import react.dom.attrs import react.dom.attrs
import react.dom.render import react.dom.render
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.ItemDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.descriptors.ValueRequirement
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.get import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.*
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 space.kscience.visionforge.hidden
import styled.css import styled.css
import styled.styledButton import styled.styledButton
@ -36,34 +22,24 @@ import styled.styledSpan
public external interface PropertyEditorProps : RProps { public external interface PropertyEditorProps : RProps {
/** /**
* Root config object - always non null * Root config object - always non-null
*/ */
public var ownProperties: MutableItemProvider public var meta: ObservableMutableMeta
/** /**
* Provide default item (greyed out if used) * Provide default item (greyed out if used)
*/ */
public var allProperties: ItemProvider? public var withDefault: MetaProvider
/** /**
* Full path to the displayed node in [ownProperties]. Could be empty * Full path to the displayed node in [meta]. Could be empty
*/ */
public var name: Name public var name: Name
/** /**
* Root descriptor * Root descriptor
*/ */
public var descriptor: NodeDescriptor? public var descriptor: MetaDescriptor?
/**
* A coroutine scope for updates
*/
public var scope: CoroutineScope?
/**
* Flow names of updated properties
*/
public var updateFlow: Flow<Name>?
/** /**
* Initial expanded state * Initial expanded state
@ -71,67 +47,60 @@ public external interface PropertyEditorProps : RProps {
public var expanded: Boolean? public var expanded: Boolean?
} }
private val PropertyEditorItem: FunctionalComponent<PropertyEditorProps> = private val PropertyEditorItem: FunctionComponent<PropertyEditorProps> =
functionalComponent("ConfigEditorItem") { props -> functionalComponent("PropertyEditorItem") { props ->
propertyEditorItem(props) propertyEditorItem(props)
} }
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
var expanded: Boolean by useState { props.expanded ?: true } var expanded: Boolean by useState { props.expanded ?: true }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name) val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
var ownProperty: MetaItem? by useState { props.ownProperties.getItem(props.name) } var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) }
val actualItem: MetaItem? = props.allProperties?.getItem(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 token = props.name.lastOrNull()?.toString() ?: "Properties" val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() { fun update() {
ownProperty = props.ownProperties.getItem(props.name) ownProperty = props.meta.getOrCreate(props.name)
} }
if (props.updateFlow != null) { useEffect(props.meta) {
useEffect(props.ownProperties, props.updateFlow) { props.meta.onChange(props) { updatedName ->
val updateJob = props.updateFlow!!.onEach { updatedName -> if (updatedName == props.name) {
if (updatedName == props.name) { update()
update()
}
}.launchIn(props.scope ?: GlobalScope)
cleanup {
updateJob.cancel()
} }
} }
cleanup {
props.meta.removeListener(props)
}
} }
val expanderClick: (Event) -> Unit = { val expanderClick: (Event) -> Unit = {
expanded = !expanded expanded = !expanded
} }
val valueChanged: (Value?) -> Unit = {
if (it == null) {
props.ownProperties.remove(props.name)
} else {
props.ownProperties[props.name] = it
}
update()
}
val removeClick: (Event) -> Unit = { val removeClick: (Event) -> Unit = {
props.ownProperties.remove(props.name) props.meta.remove(props.name)
update() update()
} }
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) }
}
// Do not show nodes without visible children
if (keys.isEmpty()) return
flexRow {
flexRow {
css {
alignItems = Align.center
}
if (keys.isNotEmpty()) {
styledSpan { styledSpan {
css { css {
+TreeStyles.treeCaret +TreeStyles.treeCaret
@ -143,67 +112,30 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
onClickFunction = expanderClick onClickFunction = expanderClick
} }
} }
styledSpan {
css {
+TreeStyles.treeLabel
if (ownProperty == null) {
+TreeStyles.treeLabelInactive
}
}
+token
}
} }
if (expanded) { styledSpan {
flexColumn {
css {
+TreeStyles.tree
}
keys.forEach { token ->
styledDiv {
css {
+TreeStyles.treeItem
}
child(PropertyEditorItem) {
attrs {
this.key = props.name.toString()
this.ownProperties = props.ownProperties
this.allProperties = props.allProperties
this.name = props.name + token
this.descriptor = props.descriptor
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
}
} else {
flexRow {
css { css {
alignItems = Align.center +TreeStyles.treeLabel
} if (ownProperty.isEmpty()) {
styledSpan { +TreeStyles.treeLabelInactive
css {
+TreeStyles.treeLabel
if (ownProperty == null) {
+TreeStyles.treeLabelInactive
}
} }
+token
} }
+token
}
if (!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) {
styledDiv { styledDiv {
css { css {
//+TreeStyles.resizeableInput //+TreeStyles.resizeableInput
width = 160.px width = 160.px
margin(1.px, 5.px) margin(1.px, 5.px)
} }
valueChooser( ValueChooser{
props.name, attrs {
actualItem, this.descriptor = descriptor
descriptorItem as? ValueDescriptor, this.meta = ownProperty
valueChanged this.actual = props.withDefault.getMeta(props.name) ?: ownProperty
) }
}
} }
styledButton { styledButton {
@ -225,83 +157,85 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
} }
+"\u00D7" +"\u00D7"
attrs { attrs {
if (ownProperty == null) { if (ownProperty.isEmpty()) {
disabled = true disabled = true
} else { } else {
onClickFunction = removeClick 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 @JsExport
public val PropertyEditor: FunctionalComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props -> public val PropertyEditor: FunctionComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
child(PropertyEditorItem) { child(PropertyEditorItem) {
attrs { attrs {
this.key = "" this.key = ""
this.ownProperties = props.ownProperties this.meta = props.meta
this.allProperties = props.allProperties this.withDefault = props.withDefault
this.name = Name.EMPTY this.name = Name.EMPTY
this.descriptor = props.descriptor this.descriptor = props.descriptor
this.scope = props.scope
this.expanded = props.expanded this.expanded = props.expanded
} }
} }
} }
public fun RBuilder.propertyEditor( public fun RBuilder.propertyEditor(
ownProperties: MutableItemProvider, ownProperties: ObservableMutableMeta,
allProperties: ItemProvider? = ownProperties, allProperties: MetaProvider = ownProperties,
updateFlow: Flow<Name>? = null, descriptor: MetaDescriptor? = null,
descriptor: NodeDescriptor? = null,
scope: CoroutineScope? = null,
key: Any? = null, key: Any? = null,
expanded: Boolean? = null expanded: Boolean? = null
) { ) {
child(PropertyEditor) { child(PropertyEditor) {
attrs { attrs {
this.ownProperties = ownProperties this.meta = ownProperties
this.allProperties = allProperties this.withDefault = allProperties
this.updateFlow = updateFlow
this.descriptor = descriptor this.descriptor = descriptor
this.key = key?.toString() ?: "" this.key = key?.toString() ?: ""
this.scope = scope
this.expanded = expanded this.expanded = expanded
} }
} }
} }
@OptIn(ExperimentalCoroutinesApi::class)
private fun Config.flowUpdates(): Flow<Name> = callbackFlow {
onChange(this) { name, _, _ ->
launch {
send(name)
}
}
awaitClose {
removeListener(this)
}
}
public fun RBuilder.configEditor( public fun RBuilder.configEditor(
config: Config, config: ObservableMutableMeta,
default: ItemProvider? = null, default: MetaProvider = config,
descriptor: NodeDescriptor? = null, descriptor: MetaDescriptor? = null,
key: Any? = null, key: Any? = null,
scope: CoroutineScope? = null, ): Unit = propertyEditor(config, default, descriptor, key = key)
): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key)
public fun Element.configEditor( public fun Element.configEditor(
config: Config, config: ObservableMutableMeta,
descriptor: NodeDescriptor? = null, default: Meta = config,
default: Meta? = null, descriptor: MetaDescriptor? = null,
key: Any? = null, key: Any? = null,
scope: CoroutineScope? = null,
): Unit = render(this) { ): Unit = render(this) {
configEditor(config, default, descriptor, key, scope) configEditor(config, default, descriptor, key = key)
} }

View File

@ -6,10 +6,11 @@ import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction import kotlinx.html.js.onChangeFunction
import org.w3c.dom.HTMLInputElement import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.FunctionalComponent import react.FunctionComponent
import react.dom.attrs import react.dom.attrs
import react.functionalComponent import react.functionalComponent
import react.useState import react.useState
import space.kscience.dataforge.meta.descriptors.ValueRequirement
import space.kscience.dataforge.meta.double import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
@ -18,32 +19,34 @@ import styled.css
import styled.styledInput import styled.styledInput
@JsExport @JsExport
public val RangeValueChooser: FunctionalComponent<ValueChooserProps> = public val RangeValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("RangeValueChooser") { props -> functionalComponent("RangeValueChooser") { props ->
var innerValue by useState(props.item.double) var innerValue by useState(props.actual.double)
var rangeDisabled: Boolean by useState(props.item == null) var rangeDisabled: Boolean by useState(props.meta.value == null)
val handleDisable: (Event) -> Unit = { val handleDisable: (Event) -> Unit = {
val checkBoxValue = (it.target as HTMLInputElement).checked val checkBoxValue = (it.target as HTMLInputElement).checked
rangeDisabled = !checkBoxValue rangeDisabled = !checkBoxValue
if(!checkBoxValue) { props.meta.value = if(!checkBoxValue) {
props.valueChanged?.invoke(null) null
} else { } else {
props.valueChanged?.invoke(innerValue?.asValue()) innerValue?.asValue()
} }
} }
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).value val newValue = (it.target as HTMLInputElement).value
props.valueChanged?.invoke(newValue.toDoubleOrNull()?.asValue()) props.meta.value = newValue.toDoubleOrNull()?.asValue()
innerValue = newValue.toDoubleOrNull() innerValue = newValue.toDoubleOrNull()
} }
flexRow { flexRow {
styledInput(type = InputType.checkBox) { if(props.descriptor?.valueRequirement != ValueRequirement.REQUIRED) {
attrs { styledInput(type = InputType.checkBox) {
defaultChecked = rangeDisabled.not() attrs {
onChangeFunction = handleDisable defaultChecked = rangeDisabled.not()
onChangeFunction = handleDisable
}
} }
} }
@ -55,6 +58,7 @@ public val RangeValueChooser: FunctionalComponent<ValueChooserProps> =
disabled = rangeDisabled disabled = rangeDisabled
value = innerValue?.toString() ?: "" value = innerValue?.toString() ?: ""
onChangeFunction = handleChange onChangeFunction = handleChange
consumer.onTagEvent(this, "input", handleChange)
val minValue = props.descriptor?.attributes?.get("min").string val minValue = props.descriptor?.attributes?.get("min").string
minValue?.let { minValue?.let {
min = it min = it

View File

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

View File

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

View File

@ -14,9 +14,12 @@ import react.*
import react.dom.attrs import react.dom.attrs
import react.dom.option import react.dom.option
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.ValueDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.values.* 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.visionforge.Colors import space.kscience.visionforge.Colors
import space.kscience.visionforge.widgetType import space.kscience.visionforge.widgetType
import styled.css import styled.css
@ -24,29 +27,26 @@ import styled.styledInput
import styled.styledSelect import styled.styledSelect
public external interface ValueChooserProps : RProps { public external interface ValueChooserProps : RProps {
public var item: MetaItem? public var descriptor: MetaDescriptor?
public var descriptor: ValueDescriptor? public var meta: ObservableMutableMeta
//public var nullable: Boolean? public var actual: Meta
public var valueChanged: ((Value?) -> Unit)?
} }
@JsExport @JsExport
public val StringValueChooser: FunctionalComponent<ValueChooserProps> = public val StringValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("StringValueChooser") { props -> functionalComponent("StringValueChooser") { props ->
var value by useState(props.item.string ?: "") var value by useState(props.actual.string ?: "")
val keyDown: (Event) -> Unit = { event -> val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { if (event.type == "keydown" && event.asDynamic().key == "Enter") {
value = (event.target as HTMLInputElement).value value = (event.target as HTMLInputElement).value
if (value != props.item.string) { props.meta.value = value.asValue()
props.valueChanged?.invoke(value.asValue())
}
} }
} }
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
value = (it.target as HTMLInputElement).value value = (it.target as HTMLInputElement).value
} }
styledInput(type = InputType.text) { styledInput(type = InputType.text) {
css{ css {
width = 100.pct width = 100.pct
} }
attrs { attrs {
@ -58,28 +58,28 @@ public val StringValueChooser: FunctionalComponent<ValueChooserProps> =
} }
@JsExport @JsExport
public val BooleanValueChooser: FunctionalComponent<ValueChooserProps> = public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("BooleanValueChooser") { props -> functionalComponent("BooleanValueChooser") { props ->
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).checked val newValue = (it.target as HTMLInputElement).checked
props.valueChanged?.invoke(newValue.asValue()) props.meta.value = newValue.asValue()
} }
styledInput(type = InputType.checkBox) { styledInput(type = InputType.checkBox) {
css{ css {
width = 100.pct width = 100.pct
} }
attrs { attrs {
//this.attributes["indeterminate"] = (props.item == null).toString() //this.attributes["indeterminate"] = (props.item == null).toString()
defaultChecked = props.item.boolean ?: false checked = props.actual.boolean ?: false
onChangeFunction = handleChange onChangeFunction = handleChange
} }
} }
} }
@JsExport @JsExport
public val NumberValueChooser: FunctionalComponent<ValueChooserProps> = public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("NumberValueChooser") { props -> functionalComponent("NumberValueChooser") { props ->
var innerValue by useState(props.item.string ?: "") var innerValue by useState(props.actual.string ?: "")
val keyDown: (Event) -> Unit = { event -> val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { if (event.type == "keydown" && event.asDynamic().key == "Enter") {
innerValue = (event.target as HTMLInputElement).value innerValue = (event.target as HTMLInputElement).value
@ -87,7 +87,7 @@ public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
if (number == null) { if (number == null) {
console.error("The input value $innerValue is not a number") console.error("The input value $innerValue is not a number")
} else { } else {
props.valueChanged?.invoke(number.asValue()) props.meta.value = number.asValue()
} }
} }
} }
@ -95,7 +95,7 @@ public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
innerValue = (it.target as HTMLInputElement).value innerValue = (it.target as HTMLInputElement).value
} }
styledInput(type = InputType.number) { styledInput(type = InputType.number) {
css{ css {
width = 100.pct width = 100.pct
} }
attrs { attrs {
@ -116,15 +116,15 @@ public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
} }
@JsExport @JsExport
public val ComboValueChooser: FunctionalComponent<ValueChooserProps> = public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("ComboValueChooser") { props -> functionalComponent("ComboValueChooser") { props ->
var selected by useState(props.item.string ?: "") var selected by useState(props.actual.string ?: "")
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
selected = (it.target as HTMLSelectElement).value selected = (it.target as HTMLSelectElement).value
props.valueChanged?.invoke(selected.asValue()) props.meta.value = selected.asValue()
} }
styledSelect { styledSelect {
css{ css {
width = 100.pct width = 100.pct
} }
props.descriptor?.allowedValues?.forEach { props.descriptor?.allowedValues?.forEach {
@ -133,7 +133,7 @@ public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
} }
} }
attrs { attrs {
this.value = props.item?.string ?: "" this.value = props.actual.string ?: ""
multiple = false multiple = false
onChangeFunction = handleChange onChangeFunction = handleChange
} }
@ -141,36 +141,32 @@ public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
} }
@JsExport @JsExport
public val ColorValueChooser: FunctionalComponent<ValueChooserProps> = public val ColorValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("ColorValueChooser") { props -> functionalComponent("ColorValueChooser") { props ->
var value by useState(
props.item.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string
} ?: "#000000"
)
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
value = (it.target as HTMLInputElement).value props.meta.value = (it.target as HTMLInputElement).value.asValue()
props.valueChanged?.invoke(value.asValue())
} }
styledInput(type = InputType.color) { styledInput(type = InputType.color) {
css{ css {
width = 100.pct width = 100.pct
margin(0.px) margin(0.px)
} }
attrs { attrs {
this.value = value this.value = props.actual.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string
} ?: "#000000"
onChangeFunction = handleChange onChangeFunction = handleChange
} }
} }
} }
@JsExport @JsExport
public val ValueChooser: FunctionalComponent<ValueChooserProps> = functionalComponent("ValueChooser") { props -> public val ValueChooser: FunctionComponent<ValueChooserProps> = functionalComponent("ValueChooser") { props ->
val rawInput by useState(false) val rawInput by useState(false)
val descriptor = props.descriptor val descriptor = props.descriptor
val type = descriptor?.type?.firstOrNull() val type = descriptor?.valueTypes?.firstOrNull()
when { when {
rawInput -> child(StringValueChooser, props) rawInput -> child(StringValueChooser, props)
@ -184,19 +180,3 @@ public val ValueChooser: FunctionalComponent<ValueChooserProps> = functionalComp
else -> child(StringValueChooser, props) 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
}
}
}

View File

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

View File

@ -0,0 +1,19 @@
@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<String>
public var message: String
public var stop: Boolean
public var deterministic: Boolean
}
@JsName("default")
public external val Loader: ComponentClass<LoaderProps>

View File

@ -0,0 +1,16 @@
@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<LoaderScreenProps>

View File

@ -1,37 +1,45 @@
package space.kscience.visionforge.ring package space.kscience.visionforge.ring
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import react.* import react.*
import react.dom.div import react.dom.div
import react.dom.span import react.dom.span
import ringui.Island import ringui.*
import ringui.IslandContent
import ringui.IslandHeader
import ringui.Link
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.length import space.kscience.dataforge.names.length
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.allProperties import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.ownProperties
import space.kscience.visionforge.react.ThreeCanvasComponent import space.kscience.visionforge.react.ThreeCanvasComponent
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.propertyEditor import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
public external interface ThreeCanvasWithControlsProps : RProps { public external interface ThreeCanvasWithControlsProps : RProps {
public var context: Context public var context: Context
public var solid: Solid? public var builderOfSolid: Deferred<Solid?>
public var selected: Name? public var selected: Name?
public var options: Canvas3DOptions?
public var additionalTabs: Map<String, RBuilder.() -> Unit>? public var additionalTabs: Map<String, RBuilder.() -> Unit>?
} }
public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) {
builderOfSolid = context.async {
SolidGroup(block)
}
}
public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) { public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) {
additionalTabs = (additionalTabs ?: emptyMap()) + (title to block) additionalTabs = (additionalTabs ?: emptyMap()) + (title to block)
} }
@ -70,29 +78,37 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): ReactElement
} }
@JsExport @JsExport
public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsProps> = public val ThreeCanvasWithControls: FunctionComponent<ThreeCanvasWithControlsProps> =
functionalComponent("ThreeViewWithControls") { props -> functionalComponent("ThreeViewWithControls") { props ->
var selected by useState { props.selected } var selected by useState { props.selected }
var solid: Solid? by useState(null)
useEffect {
props.context.launch {
solid = props.builderOfSolid.await()
}
}
val onSelect: (Name?) -> Unit = { val onSelect: (Name?) -> Unit = {
selected = it selected = it
} }
val options = useMemo(props.context) { val options = useMemo(props.options) {
Canvas3DOptions.invoke { (props.options?: Canvas3DOptions()).apply {
this.onSelect = onSelect this.onSelect = onSelect
} }
} }
val selectedVision = useMemo(selected) { val selectedVision: Vision? = useMemo(props.builderOfSolid, selected) {
selected?.let { selected?.let {
when { when {
it.isEmpty() -> props.solid it.isEmpty() -> solid
else -> (props.solid as? VisionGroup)?.get(it) else -> (solid as? VisionGroup)?.get(it)
} }
} }
} }
flexRow { flexRow {
css { css {
height = 100.pct height = 100.pct
@ -109,35 +125,42 @@ public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsP
position = Position.relative position = Position.relative
} }
child(ThreeCanvasComponent) { if (solid == null) {
attrs { LoaderScreen {
this.context = props.context attrs {
this.solid = props.solid message = "Loading Three vision"
this.selected = selected }
this.options = options }
} else {
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.solid = solid
this.selected = selected
this.options = options
}
} }
} }
selectedVision?.let { vision -> selectedVision?.let { vision ->
styledDiv { styledDiv {
css{ css {
position = Position.absolute position = Position.absolute
top = 5.px top = 5.px
right = 5.px right = 5.px
width = 450.px width = 450.px
} }
Island{ Island {
IslandHeader{ IslandHeader {
attrs { attrs {
border = true border = true
} }
nameCrumbs(selected) { selected = it } nameCrumbs(selected) { selected = it }
} }
IslandContent{ IslandContent {
propertyEditor( propertyEditor(
ownProperties = vision.ownProperties, ownProperties = vision.meta,
allProperties = vision.allProperties(), allProperties = vision.computeProperties(),
updateFlow = vision.propertyChanges,
descriptor = vision.descriptor, descriptor = vision.descriptor,
key = selected key = selected
) )
@ -150,9 +173,12 @@ public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsP
css { css {
padding(4.px) padding(4.px)
minWidth = 400.px minWidth = 400.px
height = 100.pct
overflowY = Overflow.auto
flex(1.0, 10.0, FlexBasis("300px")) flex(1.0, 10.0, FlexBasis("300px"))
} }
ringThreeControls(options, props.solid, selected, onSelect, additionalTabs = props.additionalTabs) ringThreeControls(options, solid, selected, onSelect, additionalTabs = props.additionalTabs)
} }
} }
} }

View File

@ -1,5 +1,6 @@
package space.kscience.visionforge.ring package space.kscience.visionforge.ring
import kotlinx.coroutines.async
import org.w3c.dom.Element import org.w3c.dom.Element
import react.child import react.child
import space.kscience.dataforge.context.AbstractPlugin import space.kscience.dataforge.context.AbstractPlugin
@ -28,7 +29,7 @@ public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer {
child(ThreeCanvasWithControls) { child(ThreeCanvasWithControls) {
attrs { attrs {
this.context = this@ThreeWithControlsPlugin.context this.context = this@ThreeWithControlsPlugin.context
this.solid = vision as? Solid this.builderOfSolid = context.async { vision as Solid}
} }
} }
} }

View File

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

View File

@ -49,7 +49,7 @@ internal external interface CanvasControlsProps : RProps {
public var vision: Vision? public var vision: Vision?
} }
internal val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props -> internal val CanvasControls: FunctionComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
flexColumn { flexColumn {
flexRow { flexRow {
css { css {
@ -72,8 +72,8 @@ internal val CanvasControls: FunctionalComponent<CanvasControlsProps> = function
} }
} }
propertyEditor( propertyEditor(
ownProperties = props.options, ownProperties = props.options.meta,
allProperties = props.options.withDefault(Canvas3DOptions.descriptor.defaultMeta), allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
descriptor = Canvas3DOptions.descriptor, descriptor = Canvas3DOptions.descriptor,
expanded = false expanded = false
) )
@ -91,7 +91,7 @@ public external interface ThreeControlsProps : RProps {
} }
@JsExport @JsExport
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props -> public val ThreeControls: FunctionComponent<ThreeControlsProps> = functionalComponent { props ->
SmartTabs("Tree") { SmartTabs("Tree") {
props.vision?.let { props.vision?.let {
Tab("Tree") { Tab("Tree") {

View File

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

View File

@ -0,0 +1,84 @@
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<NameToken, Meta>
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)
}
}

View File

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

View File

@ -5,6 +5,9 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus 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 import kotlin.jvm.JvmInline
/** /**
@ -13,9 +16,9 @@ import kotlin.jvm.JvmInline
@JvmInline @JvmInline
public value class StyleSheet(private val owner: VisionGroup) { public value class StyleSheet(private val owner: VisionGroup) {
private val styleNode get() = owner.ownProperties[STYLESHEET_KEY].node private val styleNode: Meta? get() = owner.meta[STYLESHEET_KEY]
public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY } public val items: Map<NameToken, Meta>? get() = styleNode?.items
public operator fun get(key: String): Meta? = owner.getStyle(key) public operator fun get(key: String): Meta? = owner.getStyle(key)
@ -23,7 +26,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
* Define a style without notifying owner * Define a style without notifying owner
*/ */
public fun define(key: String, style: Meta?) { public fun define(key: String, style: Meta?) {
owner.setProperty(STYLESHEET_KEY + key, style) owner.meta.setMeta(STYLESHEET_KEY + key, style)
} }
/** /**
@ -40,7 +43,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
/** /**
* Create and set a style * Create and set a style
*/ */
public operator fun set(key: String, builder: MetaBuilder.() -> Unit) { public operator fun set(key: String, builder: MutableMeta.() -> Unit) {
val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder) val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder)
set(key, newStyle.seal()) set(key, newStyle.seal())
} }
@ -70,9 +73,9 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
* List of names of styles applied to this object. Order matters. Not inherited. * List of names of styles applied to this object. Order matters. Not inherited.
*/ */
public var Vision.styles: List<String> public var Vision.styles: List<String>
get() = ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList() get() = meta.getValue(Vision.STYLE_KEY)?.stringList ?: emptyList()
set(value) { set(value) {
setProperty(Vision.STYLE_KEY, value) meta.setValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
} }
/** /**
@ -85,7 +88,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. * 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) { public fun Vision.useStyle(name: String) {
styles = (ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name styles = (meta.getMeta(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
} }
@ -93,13 +96,18 @@ public fun Vision.useStyle(name: String) {
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision]. * 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? = public tailrec fun Vision.getStyle(name: String): Meta? =
ownProperties[StyleSheet.STYLESHEET_KEY + name].node ?: parent?.getStyle(name) 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 }
/** /**
* Resolve an item in all style layers * Resolve an item in all style layers
*/ */
public fun Vision.getStyleItems(name: Name): List<MetaItem> = styles.mapNotNull { public fun Vision.getStyleNodes(name: Name): List<Meta> = styles.mapNotNull {
getStyle(it)[name] getStyle(it)?.get(name)
} }

View File

@ -1,26 +1,29 @@
package space.kscience.visionforge package space.kscience.visionforge
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.Described import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.toName 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.visionforge.Vision.Companion.TYPE import space.kscience.visionforge.Vision.Companion.TYPE
import kotlin.coroutines.CoroutineContext import kotlin.reflect.KProperty1
import kotlin.coroutines.EmptyCoroutineContext
/** /**
* A root type for display hierarchy * A root type for display hierarchy
*/ */
@Type(TYPE) @Type(TYPE)
public interface Vision : Described, CoroutineScope { public interface Vision : Described, Configurable {
/** /**
* The parent object of this one. If null, this one is a root. * The parent object of this one. If null, this one is a root.
@ -32,42 +35,23 @@ public interface Vision : Described, CoroutineScope {
*/ */
public val manager: VisionManager? get() = parent?.manager public val manager: VisionManager? get() = parent?.manager
override val coroutineContext: CoroutineContext /**
get() = manager?.context?.coroutineContext ?: EmptyCoroutineContext * This Vision own properties (ignoring inheritance, styles and defaults
*/
override val meta: ObservableMutableMeta
/** /**
* Get property. * Get property value with given layer flags.
* @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false. * @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false.
* @param includeStyles toggles inclusion of. Null means inference from descriptor. Default is true. * @param includeStyles toggles inclusion of properties from styles. default is true
*/ */
public fun getProperty( public fun getPropertyValue(
name: Name, name: Name,
inherit: Boolean = false, inherit: Boolean = false,
includeStyles: Boolean = true, includeStyles: Boolean = true,
includeDefaults: Boolean = true, includeDefaults: Boolean = true,
): MetaItem? ): Value?
/**
* 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<Name>
/** /**
* Notify all listeners that a property has been changed and should be invalidated * Notify all listeners that a property has been changed and should be invalidated
@ -79,7 +63,7 @@ public interface Vision : Described, CoroutineScope {
*/ */
public fun update(change: VisionChange) public fun update(change: VisionChange)
override val descriptor: NodeDescriptor? override val descriptor: MetaDescriptor?
public companion object { public companion object {
public const val TYPE: String = "vision" public const val TYPE: String = "vision"
@ -90,66 +74,74 @@ public interface Vision : Described, CoroutineScope {
} }
/** /**
* Root property node * 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 Vision.meta: Meta get() = ownProperties[Name.EMPTY]?.node ?: Meta.EMPTY @OptIn(ExperimentalCoroutinesApi::class)
@DFExperimental
public val Vision.propertyChanges: Flow<Name>
get() = callbackFlow {
meta.onChange(this) { name ->
launch {
send(name)
}
}
awaitClose {
meta.removeListener(this)
}
}
/** /**
* Subscribe on property updates. The subscription is bound to the given [scope] and canceled when the scope is canceled * Subscribe on property updates. The subscription is bound to the given [scope] and canceled when the scope is canceled
*/ */
public fun Vision.onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) { public fun Vision.onPropertyChange(callback: Meta.(Name) -> Unit) {
propertyChanges.onEach(callback).launchIn(scope) meta.onChange(null, callback)
}
/**
* 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 * Get [Vision] property using key as a String
*/ */
public fun Vision.getProperty( public fun Vision.getPropertyValue(
key: String, key: String,
inherit: Boolean = false, inherit: Boolean = false,
includeStyles: Boolean = true, includeStyles: Boolean = true,
includeDefaults: Boolean = true, includeDefaults: Boolean = true,
): MetaItem? = getProperty(key.toName(), inherit, includeStyles, includeDefaults) ): Value? = getPropertyValue(Name.parse(key), inherit, includeStyles, includeDefaults)
/** /**
* A convenience method to pair [getProperty] * A convenience method to set property node or value. If Item is null, then node is removed, not a value
*/ */
public fun Vision.setProperty(key: Name, item: Any?) { public fun Vision.setProperty(name: Name, item: Any?) {
setProperty(key, MetaItem.of(item)) 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)
} }
/** /**
* A convenience method to pair [getProperty] * Control visibility of the element
*/ */
public fun Vision.setProperty(key: String, item: Any?) { public var Vision.visible: Boolean?
setProperty(key.toName(), MetaItem.of(item)) get() = getPropertyValue(Vision.VISIBLE_KEY)?.boolean
} set(value) = meta.setValue(Vision.VISIBLE_KEY, value?.asValue())
public fun <V : Vision, T> V.useProperty(
property: KProperty1<V, T>,
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))
}
}
}

View File

@ -1,136 +1,168 @@
package space.kscience.visionforge 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.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor 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.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.*
import space.kscience.dataforge.names.asName import space.kscience.dataforge.values.Value
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.ValueType import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.Vision.Companion.STYLE_KEY import space.kscience.visionforge.Vision.Companion.STYLE_KEY
import kotlin.jvm.Synchronized 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] * A full base implementation for a [Vision]
* @param properties Object own properties excluding styles and inheritance * @param parent the parent object for this vision. Could ve set later. Not serialized.
*/ */
@Serializable @Serializable
@SerialName("vision") @SerialName("vision")
public open class VisionBase( public open class VisionBase(
override @Transient var parent: VisionGroup? = null, @Transient override var parent: VisionGroup? = null,
protected var properties: Config? = null
) : Vision { ) : Vision {
@Transient
protected open var properties: MutableMeta? = null
@Synchronized @Synchronized
protected fun getOrCreateProperties(): Config { protected fun getOrCreateProperties(): MutableMeta {
if (properties == null) { if (properties == null) {
val newProperties = Config() val newProperties = MutableMeta()
properties = newProperties properties = newProperties
} }
return properties!! return properties!!
} }
/** @Transient
* A fast accessor method to get own property (no inheritance or styles) private val listeners: MutableList<MetaListener> = mutableListOf()
*/
override fun getOwnProperty(name: Name): MetaItem? = if (name == Name.EMPTY) { private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta {
properties?.asMetaItem()
} else { override val items: Map<NameToken, ObservableMutableMeta>
properties?.getItem(name) 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)
} }
override fun getProperty( override val meta: ObservableMutableMeta get() = VisionProperties(Name.EMPTY)
override fun getPropertyValue(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean,
includeStyles: Boolean, includeStyles: Boolean,
includeDefaults: Boolean, includeDefaults: Boolean,
): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) { ): Value? {
getOwnProperty(name) properties?.get(name)?.value?.let { return it }
} else { if (includeStyles) {
buildList { getStyleProperty(name)?.let { return it }
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 fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
val oldItem = properties?.getItem(name)
if(oldItem!= item) {
getOrCreateProperties().setItem(name, item)
if (notify) {
invalidateProperty(name)
}
} }
if (inherit) {
parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
}
if (includeDefaults) {
descriptor?.defaultNode?.get(name)?.value.let { return it }
}
return null
} }
override val descriptor: NodeDescriptor? get() = null override val descriptor: MetaDescriptor? get() = null
private suspend fun updateStyles(names: List<String>) {
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<Name> = MutableSharedFlow()
@DFExperimental
override val propertyChanges: Flow<Name>
get() = propertyInvalidationFlow
override fun invalidateProperty(propertyName: Name) { override fun invalidateProperty(propertyName: Name) {
launch { if (propertyName == STYLE_KEY) {
if (propertyName == STYLE_KEY) { styles.mapNotNull { getStyle(it) }.asSequence()
updateStyles(styles) .flatMap { it.items.asSequence() }
} .distinctBy { it.key }
propertyInvalidationFlow.emit(propertyName) .forEach {
invalidateProperty(it.key.asName())
}
} }
listeners.forEach { it.callback(properties ?: Meta.EMPTY, propertyName) }
} }
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
change.properties?.let { change.properties?.let {
updateProperties(Name.EMPTY, it.asMetaItem()) updateProperties(Name.EMPTY, it)
} }
} }
public companion object { public companion object {
public val descriptor: NodeDescriptor = NodeDescriptor { public val descriptor: MetaDescriptor = MetaDescriptor {
value(STYLE_KEY) { value(STYLE_KEY, ValueType.STRING) {
type(ValueType.STRING)
multiple = true multiple = true
} }
} }
public fun Vision.updateProperties(at: Name, item: MetaItem) { public fun Vision.updateProperties(at: Name, item: Meta) {
when (item) { meta.setValue(at, item.value)
is MetaItemValue -> { item.items.forEach { (token, item) ->
if (item.value == Null) { updateProperties(at + token, item)
setProperty(at, null)
} else
setProperty(at, item)
}
is MetaItemNode -> item.node.items.forEach { (token, childItem) ->
updateProperties(at + token, childItem)
}
} }
} }

View File

@ -21,7 +21,7 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
private var reset: Boolean = false private var reset: Boolean = false
private var vision: Vision? = null private var vision: Vision? = null
private val propertyChange = Config() private val propertyChange = MutableMeta()
private val children: HashMap<Name, VisionChangeBuilder> = HashMap() private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty() public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
@ -30,17 +30,17 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
private fun getOrPutChild(visionName: Name): VisionChangeBuilder = private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
children.getOrPut(visionName) { VisionChangeBuilder() } children.getOrPut(visionName) { VisionChangeBuilder() }
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem?) { public fun propertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
if (visionName == Name.EMPTY) { if (visionName == Name.EMPTY) {
//Write property removal as [Null] //Write property removal as [Null]
propertyChange[propertyName] = (item ?: Null.asMetaItem()) propertyChange[propertyName] = (item ?: Meta(Null))
} else { } else {
getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item) getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item)
} }
} }
override fun set(name: Name?, child: Vision?) { 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 { getOrPutChild(name).apply {
vision = child vision = child
reset = vision == null reset = vision == null
@ -82,6 +82,7 @@ public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilde
VisionChangeBuilder().apply(block).isolate(manager) VisionChangeBuilder().apply(block).isolate(manager)
@OptIn(DFExperimental::class)
private fun CoroutineScope.collectChange( private fun CoroutineScope.collectChange(
name: Name, name: Name,
source: Vision, source: Vision,
@ -89,8 +90,8 @@ private fun CoroutineScope.collectChange(
) { ) {
//Collect properties change //Collect properties change
source.onPropertyChange(this) { propertyName -> source.onPropertyChange { propertyName ->
val newItem = source.ownProperties[propertyName] val newItem = source.meta[propertyName]
collector().propertyChanged(name, propertyName, newItem) collector().propertyChanged(name, propertyName, newItem)
} }
@ -102,11 +103,13 @@ private fun CoroutineScope.collectChange(
//Subscribe for structure change //Subscribe for structure change
if (source is MutableVisionGroup) { if (source is MutableVisionGroup) {
source.structureChanges.onEach { (token, _, after) -> source.structureChanges.onEach { changedName ->
val after = source[changedName]
val fullName = name + changedName
if (after != null) { if (after != null) {
collectChange(name + token, after, collector) collectChange(fullName, after, collector)
} }
collector()[name + token] = after collector()[fullName] = after
}.launchIn(this) }.launchIn(this)
} }
} }

View File

@ -1,9 +1,17 @@
package space.kscience.visionforge package space.kscience.visionforge
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow 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.names.*
import space.kscience.dataforge.provider.Provider import space.kscience.dataforge.provider.Provider
@DslMarker
public annotation class VisionBuilder
public interface VisionContainer<out V : Vision> { public interface VisionContainer<out V : Vision> {
public operator fun get(name: Name): V? public operator fun get(name: Name): V?
} }
@ -66,21 +74,36 @@ public interface VisionContainerBuilder<in V : Vision> {
* Mutable version of [VisionGroup] * Mutable version of [VisionGroup]
*/ */
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> { public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
public fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit)
public data class StructureChange(val token: NameToken, val before: Vision?, val after: Vision?) public fun removeStructureListener(owner: Any?)
/**
* Flow structure changes of this group. Unconsumed changes are discarded
*/
public val structureChanges: Flow<StructureChange>
} }
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName())
/**
* Flow structure changes of this group. Unconsumed changes are discarded
*/
@OptIn(ExperimentalCoroutinesApi::class)
@DFExperimental
public val MutableVisionGroup.structureChanges: Flow<Name>
get() = callbackFlow {
meta.onChange(this) { name ->
launch {
send(name)
}
}
awaitClose {
removeStructureListener(this)
}
}
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(Name.parse(str))
public operator fun <V : Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit = public operator fun <V : Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit =
set(token.asName(), child) set(token.asName(), child)
public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String?, child: V?): Unit = public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String?, child: V?): Unit =
set(key?.toName(), child) set(key?.let(Name::parse), child)
public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null } public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null }

View File

@ -1,13 +1,12 @@
package space.kscience.visionforge 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.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import space.kscience.dataforge.names.* 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] * Abstract implementation of mutable group of [Vision]
@ -40,16 +39,24 @@ public open class VisionGroupBase(
} }
@Transient @Transient
private val _structureChanges: MutableSharedFlow<MutableVisionGroup.StructureChange> = MutableSharedFlow() private val structureListeners = HashSet<StructureChangeListener>()
override val structureChanges: SharedFlow<MutableVisionGroup.StructureChange> get() = _structureChanges @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 }
}
/** /**
* Propagate children change event upwards * Propagate children change event upwards
*/ */
private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) { protected fun childrenChanged(name: Name) {
launch { structureListeners.forEach {
_structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after)) it.callback(this, name)
} }
} }
@ -83,7 +90,12 @@ public open class VisionGroupBase(
} }
} }
if (before != child) { if (before != child) {
childrenChanged(token, before, child) childrenChanged(token.asName())
if (child is MutableVisionGroup) {
child.onStructureChanged(this) { changedName ->
this@VisionGroupBase.childrenChanged(token + changedName)
}
}
} }
} }
@ -151,6 +163,6 @@ internal class RootVisionGroup(override val manager: VisionManager) : VisionGrou
/** /**
* Designate this [VisionGroup] as a root group and assign a [VisionManager] as its parent * 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) parent = RootVisionGroup(manager)
} }

View File

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

View File

@ -1,35 +1,34 @@
package space.kscience.visionforge package space.kscience.visionforge
import space.kscience.dataforge.meta.Config import space.kscience.dataforge.meta.Configurable
import space.kscience.dataforge.meta.MetaItem import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.names.Name 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 * Property containers are used to create a symmetric behaviors for vision properties and style builders
*/ */
public interface VisionPropertyContainer<out V: Vision> { public interface VisionPropertyContainer<out V : Vision> {
public fun getProperty(
public val meta: MutableMeta
public fun getPropertyValue(
name: Name, name: Name,
inherit: Boolean = false, inherit: Boolean = false,
includeStyles: Boolean = true, includeStyles: Boolean = true,
includeDefaults: Boolean = true, includeDefaults: Boolean = true,
): MetaItem? ): Value?
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
} }
public open class SimpleVisionPropertyContainer<out V: Vision>(protected val config: Config): VisionPropertyContainer<V>{ public open class SimpleVisionPropertyContainer<out V : Vision>(
override fun getProperty( override val meta: ObservableMutableMeta,
) : VisionPropertyContainer<V>, Configurable {
override fun getPropertyValue(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean,
includeStyles: Boolean, includeStyles: Boolean,
includeDefaults: Boolean includeDefaults: Boolean
): MetaItem? = config[name] ): Value? = meta[name]?.value
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
config[name] = item
}
} }

View File

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

View File

@ -1,28 +0,0 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.values.asValue
@DslMarker
public annotation class VisionBuilder
public fun List<MetaItem?>.merge(): MetaItem? = when (val first = firstOrNull { it != null }) {
null -> null
is MetaItemValue -> first //fast search for first entry if it is value
is MetaItemNode -> {
//merge nodes if first encountered node is meta
val laminate: Laminate = Laminate(mapNotNull { it.node })
MetaItemNode(laminate)
}
}
/**
* 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))

View File

@ -1,67 +0,0 @@
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 <S : Scheme, reified T> NodeDescriptorBuilder.value(
property: KProperty1<S, T>,
noinline block: ValueDescriptorBuilder.() -> Unit = {},
) {
when (typeOf<T>()) {
typeOf<Number>(), typeOf<Int>(), typeOf<Double>(), typeOf<Short>(), typeOf<Long>(), typeOf<Float>() ->
value(property.name) {
type(ValueType.NUMBER)
block()
}
typeOf<Number?>(), typeOf<Int?>(), typeOf<Double?>(), typeOf<Short?>(), typeOf<Long?>(), typeOf<Float?>() ->
value(property.name) {
type(ValueType.NUMBER)
block()
}
typeOf<Boolean>() -> value(property.name) {
type(ValueType.BOOLEAN)
block()
}
typeOf<List<Number>>(), typeOf<List<Int>>(), typeOf<List<Double>>(), typeOf<List<Short>>(), typeOf<List<Long>>(), typeOf<List<Float>>(),
typeOf<IntArray>(), typeOf<DoubleArray>(), typeOf<ShortArray>(), typeOf<LongArray>(), typeOf<FloatArray>(),
-> value(property.name) {
type(ValueType.NUMBER)
multiple = true
block()
}
typeOf<String>() -> value(property.name) {
type(ValueType.STRING)
block()
}
typeOf<List<String>>(), typeOf<Array<String>>() -> 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 <S : Scheme, reified T : Scheme> NodeDescriptorBuilder.scheme(
property: KProperty1<S, T>,
spec: SchemeSpec<T>,
noinline block: NodeDescriptorBuilder.() -> Unit = {},
) {
spec.descriptor?.let { descriptor ->
item(property.name, descriptor.copy(block))
}
}

View File

@ -0,0 +1,91 @@
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<Any?, Meta?> = object : ReadWriteProperty<Any?, Meta?> {
// override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? =
// getProperty(name ?: Name.parse(property.name), inherit, includeStyles, includeDefaults)
//
// override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) {
// meta.setMeta(name ?: Name.parse(property.name), value)
// }
//}
//
//public fun <T> Vision.propertyNode(
// converter: MetaConverter<T>,
// name: Name? = null,
// inherit: Boolean = false,
// includeStyles: Boolean = true,
// includeDefaults: Boolean = true,
//): ReadWriteProperty<Any?, T?> = object : ReadWriteProperty<Any?, T?> {
// override fun getValue(thisRef: Any?, property: KProperty<*>): T? = getProperty(
// name ?: Name.parse(property.name),
// inherit,
// includeStyles,
// includeDefaults
// )?.let(converter::metaToObject)
//
// override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
// 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<Any?, Value?> = object : ReadWriteProperty<Any?, Value?> {
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 <T> Vision.propertyValue(
name: Name? = null,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
setter: (T) -> Value? = { it?.let(Value::of) },
getter: (Value?) -> T,
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T = 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<Any?, Number?> = propertyValue(name, inherit, includeStyles, includeDefaults) { it?.number }
public fun Vision.numberProperty(
name: Name? = null,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
default: () -> Number
): ReadWriteProperty<Any?, Number> = propertyValue(name, inherit, includeStyles, includeDefaults) {
it?.number ?: default()
}

View File

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

View File

@ -5,10 +5,14 @@ import kotlinx.html.stream.createHTML
import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.configure
import space.kscience.dataforge.meta.set import space.kscience.dataforge.meta.set
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.visionforge.* import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionManager
import kotlin.collections.set
import kotlin.test.Test import kotlin.test.Test
typealias HtmlVisionRenderer = FlowContent.(name: Name, vision: Vision, meta: Meta) -> Unit typealias HtmlVisionRenderer = FlowContent.(name: Name, vision: Vision, meta: Meta) -> Unit

View File

@ -0,0 +1,44 @@
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>(::TestScheme)
}
@Test
fun testPropertyUpdate(){
val vision = VisionBase()
vision.meta.getOrCreate("fff").updateWith(TestScheme){
ddd = 2
}
assertEquals(2, vision.meta["fff.ddd"]?.int)
}
}

View File

@ -1,7 +1,10 @@
package space.kscience.visionforge package space.kscience.visionforge
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.CoroutineScope
import kotlinx.dom.hasClass import kotlinx.dom.hasClass
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
public external val module: Module public external val module: Module
@ -25,7 +28,10 @@ public external interface Module {
* *
* Base interface for applications supporting Hot Module Replacement (HMR). * Base interface for applications supporting Hot Module Replacement (HMR).
*/ */
public interface Application { public interface Application: CoroutineScope {
override val coroutineContext: CoroutineContext get() = EmptyCoroutineContext
/** /**
* Starting point for an application. * Starting point for an application.
* @param state Initial state between Hot Module Replacement (HMR). * @param state Initial state between Hot Module Replacement (HMR).

View File

@ -14,17 +14,10 @@ dependencies {
api("no.tornado:tornadofx:1.7.20") 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") { api("org.fxyz3d:fxyz3d:0.5.4") {
exclude(module = "slf4j-simple") exclude(module = "slf4j-simple")
} }
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}") api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}")
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") { implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,90 @@
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<M : Meta>(
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<FXMetaModel<*>> {
private val existingNode = object: ObjectBinding<Meta?>() {
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<FXMetaModel<M>> = object : ListBinding<FXMetaModel<M>>() {
override fun computeValue(): ObservableList<FXMetaModel<M>> {
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<Value?> = 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 <M : Meta> root(
node: M,
descriptor: MetaDescriptor? = null,
defaultRoot: Meta? = null,
rootName: String = "root"
): FXMetaModel<M> = FXMetaModel(node, descriptor, defaultRoot, Name.EMPTY, title = rootName)
}
}

View File

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

View File

@ -0,0 +1,152 @@
/*
* 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<MutableMeta>,
//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<FXMetaModel<MutableMeta>> {
root = TreeItem(rootNode)
root.isExpanded = true
sortMode = TreeSortMode.ALL_DESCENDANTS
columnResizePolicy = TreeTableView.CONSTRAINED_RESIZE_POLICY
populate {
it.value.children
}
column("Name", FXMetaModel<MutableMeta>::title) {
setCellFactory {
object : TextFieldTreeTableCell<FXMetaModel<MutableMeta>, String>() {
override fun updateItem(item: String?, empty: Boolean) {
super.updateItem(item, empty)
contextMenu?.items?.removeIf { it.text == "Remove" }
val content = treeTableRow.item
if (!empty) {
if (treeTableRow.item != null) {
textFillProperty().bind(content.existsProperty.objectBinding {
if (it == true) {
Color.BLACK
} else {
Color.GRAY
}
})
if (content.exists) {
contextmenu {
item("Remove") {
action {
content.root.remove(content.pathName)
}
}
}
}
}
}
}
}
}
}
column("Value") { param: TreeTableColumn.CellDataFeatures<FXMetaModel<MutableMeta>, FXMetaModel<MutableMeta>> ->
param.value.valueProperty()
}.setCellFactory {
ValueCell()
}
column("Description") { param: TreeTableColumn.CellDataFeatures<FXMetaModel<MutableMeta>, String> ->
SimpleStringProperty(param.value.value.descriptor?.info ?: "")
}.setCellFactory { param: TreeTableColumn<FXMetaModel<MutableMeta>, String> ->
val cell = TreeTableCell<FXMetaModel<MutableMeta>, String>()
val text = Text()
cell.graphic = text
cell.prefHeight = Control.USE_COMPUTED_SIZE
text.wrappingWidthProperty().bind(param.widthProperty())
text.textProperty().bind(cell.itemProperty())
cell
}
}
}
private fun showNodeDialog(): String? {
val dialog = TextInputDialog()
dialog.title = "Node name selection"
dialog.contentText = "Enter a name for new node: "
dialog.headerText = null
val result = dialog.showAndWait()
return result.orElse(null)
}
private fun showValueDialog(): String? {
val dialog = TextInputDialog()
dialog.title = "Value name selection"
dialog.contentText = "Enter a name for new value: "
dialog.headerText = null
val result = dialog.showAndWait()
return result.orElse(null)
}
private inner class ValueCell : TreeTableCell<FXMetaModel<MutableMeta>, FXMetaModel<MutableMeta>?>() {
public override fun updateItem(item: FXMetaModel<MutableMeta>?, empty: Boolean) {
if (!empty) {
if (item != null) {
text = null
val chooser = ValueChooser.build(
Global,
item.valueProperty,
item.descriptor
) { 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"
}
}

View File

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

View File

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

View File

@ -10,10 +10,12 @@ import javafx.beans.value.ObservableValue
import javafx.scene.Node import javafx.scene.Node
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.ValueDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.meta.descriptors.validate
import space.kscience.dataforge.misc.Named import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.Null import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
import space.kscience.visionforge.widget import space.kscience.visionforge.widget
@ -41,8 +43,8 @@ public interface ValueChooser {
* *
* @return * @return
*/ */
public val descriptorProperty: ObjectProperty<ValueDescriptor?> public val descriptorProperty: ObjectProperty<MetaDescriptor?>
public var descriptor: ValueDescriptor? public var descriptor: MetaDescriptor?
public val valueProperty: ObjectProperty<Value?> public val valueProperty: ObjectProperty<Value?>
public var value: Value? public var value: Value?
@ -62,7 +64,7 @@ public interface ValueChooser {
public fun setCallback(callback: ValueCallback) public fun setCallback(callback: ValueCallback)
@Type("space.kscience..fx.valueChooserFactory") @Type("space.kscience.dataforge.vis.fx.valueChooserFactory")
public interface Factory : Named { public interface Factory : Named {
public operator fun invoke(meta: Meta = Meta.EMPTY): ValueChooser public operator fun invoke(meta: Meta = Meta.EMPTY): ValueChooser
} }
@ -70,7 +72,7 @@ public interface ValueChooser {
public companion object { public companion object {
private fun findWidgetByType(context: Context, type: String): Factory? { private fun findWidgetByType(context: Context, type: String): Factory? {
return when (type.toName()) { return when (Name.parse(type)) {
TextValueChooser.name -> TextValueChooser TextValueChooser.name -> TextValueChooser
ColorValueChooser.name -> ColorValueChooser ColorValueChooser.name -> ColorValueChooser
ComboBoxValueChooser.name -> ComboBoxValueChooser ComboBoxValueChooser.name -> ComboBoxValueChooser
@ -78,7 +80,7 @@ public interface ValueChooser {
} }
} }
private fun build(context: Context, descriptor: ValueDescriptor?): ValueChooser { private fun build(context: Context, descriptor: MetaDescriptor?): ValueChooser {
return if (descriptor == null) { return if (descriptor == null) {
TextValueChooser(); TextValueChooser();
} else { } else {
@ -92,7 +94,7 @@ public interface ValueChooser {
descriptor.widget descriptor.widget
) ?: TextValueChooser() ) ?: TextValueChooser()
} }
descriptor.allowedValues.isNotEmpty() -> ComboBoxValueChooser() !descriptor.allowedValues.isNullOrEmpty() -> ComboBoxValueChooser()
else -> TextValueChooser() else -> TextValueChooser()
} }
chooser.descriptor = descriptor chooser.descriptor = descriptor
@ -103,7 +105,7 @@ public interface ValueChooser {
public fun build( public fun build(
context: Context, context: Context,
value: ObservableValue<Value?>, value: ObservableValue<Value?>,
descriptor: ValueDescriptor? = null, descriptor: MetaDescriptor? = null,
setter: (Value) -> Unit, setter: (Value) -> Unit,
): ValueChooser { ): ValueChooser {
val chooser = build(context, descriptor) val chooser = build(context, descriptor)
@ -112,7 +114,7 @@ public interface ValueChooser {
chooser.setDisplayValue(it ?: Null) chooser.setDisplayValue(it ?: Null)
} }
chooser.setCallback { result -> chooser.setCallback { result ->
if (descriptor?.isAllowedValue(result) != false) { if (descriptor?.validate(result) != false) {
setter(result) setter(result)
ValueCallbackResponse(true, result, "OK") ValueCallbackResponse(true, result, "OK")
} else { } else {

View File

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

View File

@ -0,0 +1,61 @@
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<Vision> = SimpleObjectProperty<Vision>()
public var vision: Vision? by visionProperty
public val descriptorProperty: SimpleObjectProperty<MetaDescriptor> = SimpleObjectProperty<MetaDescriptor>()
private val configProperty: Binding<ObservableMutableMeta?> = visionProperty.objectBinding { vision ->
vision?.meta
}
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
it?.let { meta ->
val node:FXMetaModel<MutableMeta> = FXMetaModel(
meta,
vision?.descriptor,
vision?.computeProperties(),
Name.EMPTY,
"Vision properties"
)
MutableMetaEditor(node).root
}
}
private val styleBoxProperty: Binding<Node?> = 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)
}
}
}

View File

@ -30,7 +30,7 @@ private fun toTreeItem(vision: Vision, title: String): TreeItem<Pair<String, Vis
} }
public class VisualObjectTreeFragment : Fragment() { public class VisionTreeFragment : Fragment() {
public val itemProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>() public val itemProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
public var item: Vision? by itemProperty public var item: Vision? by itemProperty

View File

@ -1,74 +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.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<Vision> = SimpleObjectProperty<Vision>()
public var item: Vision? by itemProperty
public val descriptorProperty: SimpleObjectProperty<NodeDescriptor> = SimpleObjectProperty<NodeDescriptor>()
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<Config?> = 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<Node?> = configProperty.objectBinding(descriptorProperty) {
it?.let {
ConfigEditor(it, descriptorProperty.get()).root
}
}
private val styleBoxProperty: Binding<Node?> = 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)
}
}
}

View File

@ -16,6 +16,7 @@ import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.boolean import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.visionforge.computePropertyNode
import space.kscience.visionforge.solid.FX3DFactory.Companion.TYPE 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_KEY
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_WIREFRAME_KEY import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_WIREFRAME_KEY
@ -73,7 +74,7 @@ public class FX3DPlugin : AbstractPlugin() {
is PolyLine -> PolyLine3D( is PolyLine -> PolyLine3D(
obj.points.map { Point3D(it.x, it.y, it.z) }, obj.points.map { Point3D(it.x, it.y, it.z) },
obj.thickness.toFloat(), obj.thickness.toFloat(),
obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY, inherit = true)?.color() obj.computePropertyNode(SolidMaterial.MATERIAL_COLOR_KEY)?.color()
).apply { ).apply {
this.meshView.cullFace = CullFace.FRONT this.meshView.cullFace = CullFace.FRONT
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,44 @@
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<ProcessResources>("jvmProcessResources") {
dependsOn(jsBrowserDistribution)
from(jsBrowserDistribution)
}
sourceSets {
commonMain {
dependencies {
api(project(":visionforge-core"))
api("org.jetbrains:markdown:$markdownVersion")
}
}
}
}

View File

@ -0,0 +1,39 @@
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())
}
}

View File

@ -0,0 +1,25 @@
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 <T> TagConsumer<T>.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()
}
}
}

View File

@ -0,0 +1,53 @@
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<MarkupPlugin> {
override val tag: PluginTag = PluginTag("vision.markup", PluginTag.DATAFORGE_GROUP)
override val type: KClass<MarkupPlugin> = MarkupPlugin::class
override fun invoke(meta: Meta, context: Context): MarkupPlugin = MarkupPlugin()
}
}

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