Raw version of hierarchy.md #63

Closed
kiruma524 wants to merge 14 commits from dev into dev
137 changed files with 2609 additions and 2166 deletions
Showing only changes of commit fc8db7886e - Show all commits

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 {
id("ru.mipt.npm.gradle.project")
//Override kotlin version
// val kotlinVersion = "1.5.20-RC"
// kotlin("multiplatform") version(kotlinVersion) apply false
// kotlin("jvm") version(kotlinVersion) apply false
// kotlin("js") version(kotlinVersion) apply false
// kotlin("multiplatform") version "1.5.30-RC" apply false
}
val dataforgeVersion by extra("0.4.3")
val dataforgeVersion by extra("0.5.1")
val fxVersion by extra("11")
allprojects {
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven("https://repo.kotlin.link")
maven("https://maven.jzy3d.org/releases")
}
group = "space.kscience"
version = "0.2.0-dev-22"
version = "0.2.0-dev-23"
}
subprojects {
@ -29,7 +24,7 @@ subprojects {
}
}
ksciencePublish{
ksciencePublish {
github("visionforge")
space()
sonatype()

View File

@ -16,7 +16,7 @@ kotlin {
jvm {
withJava()
}
js{
js {
useCommonJs()
browser {
commonWebpackConfig {
@ -34,6 +34,7 @@ kotlin {
jvmMain {
dependencies {
implementation(project(":visionforge-fx"))
implementation("ch.qos.logback:logback-classic:1.2.5")
}
}
jsMain {
@ -53,5 +54,5 @@ application {
val convertGdmlToJson by tasks.creating(JavaExec::class) {
group = "application"
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
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.string
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.get
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.material
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class GDMLVisionTest {
private val cubes = GdmlShowCase.cubes().toVision()
// @Test
// fun testCubesStyles(){
// val cubes = gdml.toVision()
// val segment = cubes["composite000.segment_0".toName()] as Solid
// println(segment.styles)
// println(segment.material)
// }
@Test
fun testCubesStyles(){
val segment = cubes["composite-000.segment-0"] as Solid
println(segment.computeProperties().getValue(Vision.STYLE_KEY))
// println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY))
// println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY))
println(segment.material?.meta)
//println(Solids.encodeToString(cubes))
}
@Test
fun testPrototypeProperty() {
val vision = GdmlShowCase.cubes().toVision()
val child = vision["composite-000.segment-0".toName()]
val child = cubes[Name.of("composite-000","segment-0")]
assertNotNull(child)
child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
assertEquals("red", child.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.string)
}
}

View File

@ -1,6 +1,12 @@
package space.kscience.visionforge.gdml.demo
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.get
import react.*
@ -17,6 +23,8 @@ import space.kscience.visionforge.ring.tab
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids
import styled.css
import styled.styledDiv
external interface GDMLAppProps : RProps {
var context: Context
@ -27,52 +35,62 @@ external interface GDMLAppProps : RProps {
@JsExport
val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager }
var vision: Solid? by useState { props.vision?.apply { root(visionManager) } }
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")
var deferredVision: Deferred<Solid?> by useState {
CompletableDeferred(props.vision)
}
child(ThreeCanvasWithControls) {
attrs {
this.context = props.context
this.solid = vision
this.selected = props.selected
tab("Load") {
h2 {
+"Drag and drop .gdml or .json VisionForge files here"
fun readFileAsync(file: File): Deferred<Solid?> {
val deferred = CompletableDeferred<Solid?>()
FileReader().apply {
onload = {
val data = result as String
val name = file.name
val parsedVision = when {
name.endsWith(".gdml") || name.endsWith(".xml") -> {
val gdml = Gdml.decodeFromString(data)
gdml.toVision().apply {
root(visionManager)
console.info("Marking layers for file $name")
markLayers()
}
}
name.endsWith(".json") -> visionManager.decodeFromString(data)
else -> {
window.alert("File extension is not recognized: $name")
error("File extension is not recognized: $name")
}
}
fileDrop("(drag file here)") { files ->
val file = files?.get(0)
if (file != null) {
FileReader().apply {
onload = {
val string = result as String
loadData(file.name, string)
}
readAsText(file)
deferred.complete(parsedVision as? Solid ?: error("Parsed vision is not a solid"))
}
readAsText(file)
}
return deferred
}
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.gdml.GdmlShowCase
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.describedProperties
import space.kscience.visionforge.editor.VisualObjectEditorFragment
import space.kscience.visionforge.editor.VisualObjectTreeFragment
import space.kscience.visionforge.editor.VisionEditorFragment
import space.kscience.visionforge.editor.VisionTreeFragment
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.solid.FX3DPlugin
import space.kscience.visionforge.solid.FXCanvas3D
@ -29,25 +28,22 @@ class GDMLView : View() {
private val visionManager = context.fetch(VisionManager)
private val canvas = FXCanvas3D(fx3d)
private val treeFragment = VisualObjectTreeFragment().apply {
private val treeFragment = VisionTreeFragment().apply {
this.itemProperty.bind(canvas.rootObjectProperty)
}
private val propertyEditor = VisualObjectEditorFragment {
it.describedProperties
}.apply {
private val propertyEditor = VisionEditorFragment().apply {
descriptorProperty.set(SolidMaterial.descriptor)
itemProperty.bind(treeFragment.selectedProperty)
visionProperty.bind(treeFragment.selectedProperty)
}
override val root: Parent = borderpane {
top {
buttonbar {
button("Load GDML/json") {
action {
val file = chooseFile("Select a GDML/json file", filters = fileNameFilter).firstOrNull()
if(file!= null) {
if (file != null) {
runAsync {
visionManager.readFile(file) as Solid
} ui {

View File

@ -1,17 +1,35 @@
import kotlinx.browser.document
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.css.*
import react.child
import react.dom.render
import ringui.SmartTabs
import ringui.Tab
import space.kscience.dataforge.context.Context
import space.kscience.gdml.GdmlShowCase
import space.kscience.plotly.models.Trace
import space.kscience.plotly.scatter
import space.kscience.visionforge.Application
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.ThreeWithControlsPlugin
import space.kscience.visionforge.ring.solid
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.startApplication
import styled.css
import styled.styledDiv
import kotlin.math.sqrt
import kotlin.random.Random
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 {
@ -20,24 +38,124 @@ private class JsPlaygroundApp : Application {
val playgroundContext = Context {
plugin(ThreeWithControlsPlugin)
plugin(VisionClient)
plugin(PlotlyPlugin)
}
val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page")
val visionOfD0 = GdmlShowCase.babyIaxo().toVision()
val bouncingSphereTrace = Trace()
render(element) {
styledDiv {
css{
css {
padding(0.pt)
margin(0.pt)
height = 100.vh
width = 100.vw
}
child(ThreeCanvasWithControls) {
attrs {
context = playgroundContext
solid = visionOfD0
SmartTabs("gravity") {
Tab("gravity") {
styledDiv {
css {
height = 50.vh
}
child(ThreeCanvasWithControls) {
attrs {
context = playgroundContext
solid {
sphere(5.0, "ball") {
detail = 16
color("red")
val h = 100.0
y = h
launch {
val g = 10.0
val dt = 0.1
var time = 0.0
var velocity = 0.0
while (isActive) {
delay(20)
time += dt
velocity -= g * dt
y = y.toDouble() + velocity * dt
bouncingSphereTrace.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 {
withJava()
}
js {
useCommonJs()
browser {
commonWebpackConfig {
cssSupport.enabled = false
}
}
}
afterEvaluate {
val jsBrowserDistribution by tasks.getting
@ -43,7 +51,7 @@ kotlin {
}
jsMain {
dependencies {
implementation(project(":ui:bootstrap"))
implementation(project(":ui:ring"))
implementation("io.ktor:ktor-client-js:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
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.removeAll
import space.kscience.visionforge.root
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.*
import kotlin.math.PI
@ -37,6 +38,10 @@ class Model(val manager: VisionManager) {
val root: SolidGroup = SolidGroup().apply {
root(this@Model.manager)
material {
wireframe
color("darkgreen")
}
rotationX = PI / 2
group("bottom") {
Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach {
@ -59,6 +64,7 @@ class Model(val manager: VisionManager) {
}
private fun highlight(pixel: String) {
println("highlight $pixel")
map[pixel]?.color?.invoke("blue")
}
@ -70,6 +76,7 @@ class Model(val manager: VisionManager) {
}
fun displayEvent(event: Event) {
println("Received event: $event")
events.add(event)
event.hits.forEach {
highlight(it)
@ -77,6 +84,7 @@ class Model(val manager: VisionManager) {
event.track?.let {
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") {
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 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.request.get
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.css.*
import kotlinx.html.js.onClickFunction
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.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.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.Canvas3DOptions
import space.kscience.visionforge.solid.three.edges
import styled.css
import styled.styledDiv
import styled.styledSpan
import kotlin.math.PI
external interface MMAppProps : RProps {
@ -34,13 +32,9 @@ external interface MMAppProps : RProps {
var selected: Name?
}
@OptIn(DelicateCoroutinesApi::class)
@JsExport
val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
var selected by useState { props.selected }
val onSelect: (Name?) -> Unit = {
selected = it
}
val mmOptions = useMemo {
Canvas3DOptions {
@ -49,148 +43,219 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
latitude = PI / 6
azimuth = PI + PI / 6
}
this.onSelect = onSelect
}
}
val root = props.model.root
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)
}
val root = useMemo(props.model) {
props.model.root.apply {
edges()
}
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
}
}
var events: Set<Event> by useState(emptySet())
styledDiv {
css {
height = 100.vh - 12.pt
}
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"
child(ThreeCanvasWithControls) {
attrs {
this.context = props.context
this.builderOfSolid = CompletableDeferred(root)
this.selected = props.selected
this.options = mmOptions
tab("Events") {
flexColumn {
flexRow {
button {
+"Next"
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
}
context.launch {
val event = props.connection.get<Event>(
"http://localhost:8080/event"
)
events = events + event
props.model.displayEvent(event)
}
}
}
}
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.dom.render
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.visionforge.Application
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.bootstrap.useBootstrap
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication
private class MMDemoApp : Application {
private val visionManager = Global.fetch(VisionManager)
private val model = Model(visionManager)
private val connection = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer()
@ -26,15 +22,19 @@ private class MMDemoApp : Application {
}
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 context = Context("demo")
render(element) {
child(MMApp) {
attrs {
this.model = this@MMDemoApp.model
this.model = model
this.connection = this@MMDemoApp.connection
this.context = context
}

View File

@ -16,10 +16,10 @@ import kotlin.random.Random
*/
internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) {
// val layer: Layer = findLayer(center.z);
private val upLayer =
findLayer(sc.center.z + sc.zSize / 2f)//Layer("${name}_up", center.z + zSize / 2.0);
private val bottomLayer =
findLayer(sc.center.z - sc.zSize / 2f)//Layer("${name}_bottom", center.z - zSize / 2.0);
private val upLayer = findLayer(sc.center.z + sc.zSize / 2f)
//Layer("${name}_up", center.z + zSize / 2.0);
private val bottomLayer = findLayer(sc.center.z - sc.zSize / 2f)
//Layer("${name}_bottom", center.z - zSize / 2.0);
private val centralLayer = findLayer(sc.center.z)
private val center = Vector3D(sc.center.x.toDouble(), sc.center.y.toDouble(), sc.center.z.toDouble())
@ -115,8 +115,8 @@ internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) {
private val auxCache = HashMap<SC1, SC1Aux>()
fun SC1.isHit(track: Line): Boolean{
return auxCache.getOrPut(this){
fun SC1.isHit(track: Line): Boolean {
return auxCache.getOrPut(this) {
SC1Aux(this)
}.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"
}
commonWebpackConfig {
sourceMaps = false
sourceMaps = true
cssSupport.enabled = false
}
}
@ -37,7 +37,7 @@ kotlin {
}
afterEvaluate {
val jsBrowserDistribution by tasks.getting
val jsBrowserDistribution = tasks.getByName("jsBrowserDevelopmentExecutableDistribution")
tasks.getByName<ProcessResources>("jvmProcessResources") {
dependsOn(jsBrowserDistribution)
@ -67,6 +67,7 @@ kotlin {
val jvmMain by getting{
dependencies {
api(project(":visionforge-server"))
api(project(":visionforge-markdown"))
api("ch.qos.logback:logback-classic:1.2.3")
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ class FXDemoApp : App(FXDemoGrid::class) {
stage.height = 600.0
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.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.VisionLayout
import space.kscience.visionforge.solid.FX3DPlugin
import space.kscience.visionforge.solid.FXCanvas3D
import space.kscience.visionforge.solid.Solid

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

2
gradlew vendored Normal file → Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,5 @@
package space.kscience.visionforge.react
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.css.*
import kotlinx.css.properties.TextDecoration
import kotlinx.html.js.onClickFunction
@ -18,15 +9,10 @@ import react.*
import react.dom.attrs
import react.dom.render
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRequirement
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.lastOrNull
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.names.*
import space.kscience.visionforge.hidden
import styled.css
import styled.styledButton
@ -36,34 +22,24 @@ import styled.styledSpan
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)
*/
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
/**
* Root descriptor
*/
public var descriptor: NodeDescriptor?
/**
* A coroutine scope for updates
*/
public var scope: CoroutineScope?
/**
* Flow names of updated properties
*/
public var updateFlow: Flow<Name>?
public var descriptor: MetaDescriptor?
/**
* Initial expanded state
@ -71,67 +47,60 @@ public external interface PropertyEditorProps : RProps {
public var expanded: Boolean?
}
private val PropertyEditorItem: FunctionalComponent<PropertyEditorProps> =
functionalComponent("ConfigEditorItem") { props ->
private val PropertyEditorItem: FunctionComponent<PropertyEditorProps> =
functionalComponent("PropertyEditorItem") { props ->
propertyEditorItem(props)
}
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
var expanded: Boolean by useState { props.expanded ?: true }
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
var ownProperty: MetaItem? by useState { props.ownProperties.getItem(props.name) }
val actualItem: MetaItem? = props.allProperties?.getItem(props.name)
val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) }
val keys = useMemo(descriptor) {
buildSet {
descriptor?.children?.filterNot {
it.key.startsWith("@") || it.value.hidden
}?.forEach {
add(NameToken(it.key))
}
//ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
}
}
val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() {
ownProperty = props.ownProperties.getItem(props.name)
ownProperty = props.meta.getOrCreate(props.name)
}
if (props.updateFlow != null) {
useEffect(props.ownProperties, props.updateFlow) {
val updateJob = props.updateFlow!!.onEach { updatedName ->
if (updatedName == props.name) {
update()
}
}.launchIn(props.scope ?: GlobalScope)
cleanup {
updateJob.cancel()
useEffect(props.meta) {
props.meta.onChange(props) { updatedName ->
if (updatedName == props.name) {
update()
}
}
cleanup {
props.meta.removeListener(props)
}
}
val expanderClick: (Event) -> Unit = {
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 = {
props.ownProperties.remove(props.name)
props.meta.remove(props.name)
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 {
css {
+TreeStyles.treeCaret
@ -143,67 +112,30 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
onClickFunction = expanderClick
}
}
styledSpan {
css {
+TreeStyles.treeLabel
if (ownProperty == null) {
+TreeStyles.treeLabelInactive
}
}
+token
}
}
if (expanded) {
flexColumn {
css {
+TreeStyles.tree
}
keys.forEach { token ->
styledDiv {
css {
+TreeStyles.treeItem
}
child(PropertyEditorItem) {
attrs {
this.key = props.name.toString()
this.ownProperties = props.ownProperties
this.allProperties = props.allProperties
this.name = props.name + token
this.descriptor = props.descriptor
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
}
} else {
flexRow {
styledSpan {
css {
alignItems = Align.center
}
styledSpan {
css {
+TreeStyles.treeLabel
if (ownProperty == null) {
+TreeStyles.treeLabelInactive
}
+TreeStyles.treeLabel
if (ownProperty.isEmpty()) {
+TreeStyles.treeLabelInactive
}
+token
}
+token
}
if (!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) {
styledDiv {
css {
//+TreeStyles.resizeableInput
width = 160.px
margin(1.px, 5.px)
}
valueChooser(
props.name,
actualItem,
descriptorItem as? ValueDescriptor,
valueChanged
)
ValueChooser{
attrs {
this.descriptor = descriptor
this.meta = ownProperty
this.actual = props.withDefault.getMeta(props.name) ?: ownProperty
}
}
}
styledButton {
@ -225,83 +157,85 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
}
+"\u00D7"
attrs {
if (ownProperty == null) {
if (ownProperty.isEmpty()) {
disabled = true
} else {
onClickFunction = removeClick
}
}
}
}
}
if (expanded) {
flexColumn {
css {
+TreeStyles.tree
}
keys.forEach { token ->
styledDiv {
css {
+TreeStyles.treeItem
}
child(PropertyEditorItem) {
attrs {
this.key = props.name.toString()
this.meta = props.meta
this.withDefault = props.withDefault
this.name = props.name + token
this.descriptor = props.descriptor
}
}
//configEditor(props.root, props.name + token, props.descriptor, props.default)
}
}
}
}
}
@JsExport
public val PropertyEditor: FunctionalComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
public val PropertyEditor: FunctionComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
child(PropertyEditorItem) {
attrs {
this.key = ""
this.ownProperties = props.ownProperties
this.allProperties = props.allProperties
this.meta = props.meta
this.withDefault = props.withDefault
this.name = Name.EMPTY
this.descriptor = props.descriptor
this.scope = props.scope
this.expanded = props.expanded
}
}
}
public fun RBuilder.propertyEditor(
ownProperties: MutableItemProvider,
allProperties: ItemProvider? = ownProperties,
updateFlow: Flow<Name>? = null,
descriptor: NodeDescriptor? = null,
scope: CoroutineScope? = null,
ownProperties: ObservableMutableMeta,
allProperties: MetaProvider = ownProperties,
descriptor: MetaDescriptor? = null,
key: Any? = null,
expanded: Boolean? = null
) {
child(PropertyEditor) {
attrs {
this.ownProperties = ownProperties
this.allProperties = allProperties
this.updateFlow = updateFlow
this.meta = ownProperties
this.withDefault = allProperties
this.descriptor = descriptor
this.key = key?.toString() ?: ""
this.scope = scope
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(
config: Config,
default: ItemProvider? = null,
descriptor: NodeDescriptor? = null,
config: ObservableMutableMeta,
default: MetaProvider = config,
descriptor: MetaDescriptor? = null,
key: Any? = null,
scope: CoroutineScope? = null,
): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key)
): Unit = propertyEditor(config, default, descriptor, key = key)
public fun Element.configEditor(
config: Config,
descriptor: NodeDescriptor? = null,
default: Meta? = null,
config: ObservableMutableMeta,
default: Meta = config,
descriptor: MetaDescriptor? = null,
key: Any? = null,
scope: CoroutineScope? = null,
): 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 org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event
import react.FunctionalComponent
import react.FunctionComponent
import react.dom.attrs
import react.functionalComponent
import react.useState
import space.kscience.dataforge.meta.descriptors.ValueRequirement
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
@ -18,32 +19,34 @@ import styled.css
import styled.styledInput
@JsExport
public val RangeValueChooser: FunctionalComponent<ValueChooserProps> =
public val RangeValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("RangeValueChooser") { props ->
var innerValue by useState(props.item.double)
var rangeDisabled: Boolean by useState(props.item == null)
var innerValue by useState(props.actual.double)
var rangeDisabled: Boolean by useState(props.meta.value == null)
val handleDisable: (Event) -> Unit = {
val checkBoxValue = (it.target as HTMLInputElement).checked
rangeDisabled = !checkBoxValue
if(!checkBoxValue) {
props.valueChanged?.invoke(null)
props.meta.value = if(!checkBoxValue) {
null
} else {
props.valueChanged?.invoke(innerValue?.asValue())
innerValue?.asValue()
}
}
val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).value
props.valueChanged?.invoke(newValue.toDoubleOrNull()?.asValue())
props.meta.value = newValue.toDoubleOrNull()?.asValue()
innerValue = newValue.toDoubleOrNull()
}
flexRow {
styledInput(type = InputType.checkBox) {
attrs {
defaultChecked = rangeDisabled.not()
onChangeFunction = handleDisable
if(props.descriptor?.valueRequirement != ValueRequirement.REQUIRED) {
styledInput(type = InputType.checkBox) {
attrs {
defaultChecked = rangeDisabled.not()
onChangeFunction = handleDisable
}
}
}
@ -55,6 +58,7 @@ public val RangeValueChooser: FunctionalComponent<ValueChooserProps> =
disabled = rangeDisabled
value = innerValue?.toString() ?: ""
onChangeFunction = handleChange
consumer.onTagEvent(this, "input", handleChange)
val minValue = props.descriptor?.attributes?.get("min").string
minValue?.let {
min = it

View File

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

View File

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

View File

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

View File

@ -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
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.css.*
import react.*
import react.dom.div
import react.dom.span
import ringui.Island
import ringui.IslandContent
import ringui.IslandHeader
import ringui.Link
import ringui.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.length
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.allProperties
import space.kscience.visionforge.ownProperties
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.react.ThreeCanvasComponent
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
import styled.styledDiv
public external interface ThreeCanvasWithControlsProps : RProps {
public var context: Context
public var solid: Solid?
public var builderOfSolid: Deferred<Solid?>
public var selected: Name?
public var options: Canvas3DOptions?
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) {
additionalTabs = (additionalTabs ?: emptyMap()) + (title to block)
}
@ -70,29 +78,37 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): ReactElement
}
@JsExport
public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsProps> =
public val ThreeCanvasWithControls: FunctionComponent<ThreeCanvasWithControlsProps> =
functionalComponent("ThreeViewWithControls") { props ->
var selected by useState { props.selected }
var solid: Solid? by useState(null)
useEffect {
props.context.launch {
solid = props.builderOfSolid.await()
}
}
val onSelect: (Name?) -> Unit = {
selected = it
}
val options = useMemo(props.context) {
Canvas3DOptions.invoke {
val options = useMemo(props.options) {
(props.options?: Canvas3DOptions()).apply {
this.onSelect = onSelect
}
}
val selectedVision = useMemo(selected) {
val selectedVision: Vision? = useMemo(props.builderOfSolid, selected) {
selected?.let {
when {
it.isEmpty() -> props.solid
else -> (props.solid as? VisionGroup)?.get(it)
it.isEmpty() -> solid
else -> (solid as? VisionGroup)?.get(it)
}
}
}
flexRow {
css {
height = 100.pct
@ -109,35 +125,42 @@ public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsP
position = Position.relative
}
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.solid = props.solid
this.selected = selected
this.options = options
if (solid == null) {
LoaderScreen {
attrs {
message = "Loading Three vision"
}
}
} else {
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.solid = solid
this.selected = selected
this.options = options
}
}
}
selectedVision?.let { vision ->
styledDiv {
css{
css {
position = Position.absolute
top = 5.px
right = 5.px
width = 450.px
}
Island{
IslandHeader{
Island {
IslandHeader {
attrs {
border = true
}
nameCrumbs(selected) { selected = it }
}
IslandContent{
IslandContent {
propertyEditor(
ownProperties = vision.ownProperties,
allProperties = vision.allProperties(),
updateFlow = vision.propertyChanges,
ownProperties = vision.meta,
allProperties = vision.computeProperties(),
descriptor = vision.descriptor,
key = selected
)
@ -150,9 +173,12 @@ public val ThreeCanvasWithControls: FunctionalComponent<ThreeCanvasWithControlsP
css {
padding(4.px)
minWidth = 400.px
height = 100.pct
overflowY = Overflow.auto
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
import kotlinx.coroutines.async
import org.w3c.dom.Element
import react.child
import space.kscience.dataforge.context.AbstractPlugin
@ -28,7 +29,7 @@ public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer {
child(ThreeCanvasWithControls) {
attrs {
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.SmartTabs
import ringui.Tab
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.visionforge.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
public fun RBuilder.ringPropertyEditor(
vision: Vision,
descriptor: NodeDescriptor? = vision.descriptor,
descriptor: MetaDescriptor? = vision.descriptor,
key: Any? = null,
) {
val styles = if (vision is SolidReference) {
@ -28,9 +31,8 @@ public fun RBuilder.ringPropertyEditor(
flexColumn {
Island("Properties") {
propertyEditor(
ownProperties = vision.ownProperties,
allProperties = vision.allProperties(),
updateFlow = vision.propertyChanges,
ownProperties = vision.meta,
allProperties = vision.computeProperties(),
descriptor = descriptor,
key = key
)
@ -69,7 +71,7 @@ public fun RBuilder.ringPropertyEditor(
public fun Element.ringPropertyEditor(
item: Vision,
descriptor: NodeDescriptor? = item.descriptor,
descriptor: MetaDescriptor? = item.descriptor,
): Unit = render(this) {
ringPropertyEditor(item, descriptor = descriptor)
}

View File

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

View File

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

View File

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

View File

@ -5,6 +5,9 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.stringList
import kotlin.jvm.JvmInline
/**
@ -13,9 +16,9 @@ import kotlin.jvm.JvmInline
@JvmInline
public value class StyleSheet(private val owner: VisionGroup) {
private val styleNode get() = owner.ownProperties[STYLESHEET_KEY].node
private val styleNode: Meta? get() = owner.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)
@ -23,7 +26,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
* Define a style without notifying owner
*/
public fun define(key: String, style: Meta?) {
owner.setProperty(STYLESHEET_KEY + key, style)
owner.meta.setMeta(STYLESHEET_KEY + key, style)
}
/**
@ -40,7 +43,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
/**
* Create and set a style
*/
public operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
public operator fun set(key: String, builder: MutableMeta.() -> Unit) {
val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder)
set(key, newStyle.seal())
}
@ -70,9 +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.
*/
public var Vision.styles: List<String>
get() = ownProperties[Vision.STYLE_KEY]?.stringList ?: emptyList()
get() = meta.getValue(Vision.STYLE_KEY)?.stringList ?: emptyList()
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.
*/
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].
*/
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
*/
public fun Vision.getStyleItems(name: Name): List<MetaItem> = styles.mapNotNull {
getStyle(it)[name]
public fun Vision.getStyleNodes(name: Name): List<Meta> = styles.mapNotNull {
getStyle(it)?.get(name)
}

View File

@ -1,26 +1,29 @@
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.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.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 kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.reflect.KProperty1
/**
* A root type for display hierarchy
*/
@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.
@ -32,42 +35,23 @@ public interface Vision : Described, CoroutineScope {
*/
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 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,
inherit: Boolean = false,
includeStyles: 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
@ -79,7 +63,7 @@ public interface Vision : Described, CoroutineScope {
*/
public fun update(change: VisionChange)
override val descriptor: NodeDescriptor?
override val descriptor: MetaDescriptor?
public companion object {
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
*/
public fun Vision.onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
propertyChanges.onEach(callback).launchIn(scope)
}
/**
* Own properties, excluding inheritance, styles and descriptor
*/
public val Vision.ownProperties: MutableItemProvider
get() = object : MutableItemProvider {
override fun getItem(name: Name): MetaItem? = getOwnProperty(name)
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
}
/**
* Convenient accessor for all properties of a vision.
* @param inherit - inherit property value from the parent by default. If null, inheritance is inferred from descriptor
*/
public fun Vision.allProperties(
inherit: Boolean? = null,
includeStyles: Boolean? = null,
includeDefaults: Boolean = true,
): MutableItemProvider = object : MutableItemProvider {
override fun getItem(name: Name): MetaItem? = getProperty(
name,
inherit = inherit ?: (descriptor?.get(name)?.inherited == true),
includeStyles = includeStyles ?: (descriptor?.get(name)?.usesStyles != false),
includeDefaults = includeDefaults
)
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
public fun Vision.onPropertyChange(callback: Meta.(Name) -> Unit) {
meta.onChange(null, callback)
}
/**
* Get [Vision] property using key as a String
*/
public fun Vision.getProperty(
public fun Vision.getPropertyValue(
key: String,
inherit: Boolean = false,
includeStyles: 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?) {
setProperty(key, MetaItem.of(item))
public fun Vision.setProperty(name: Name, item: Any?) {
when (item) {
null -> meta.remove(name)
is Meta -> meta.setMeta(name, item)
is Value -> meta.setValue(name, item)
else -> meta.setValue(name, Value.of(item))
}
}
public fun Vision.setPropertyNode(key: String, item: Any?) {
setProperty(Name.parse(key), item)
}
/**
* A convenience method to pair [getProperty]
* Control visibility of the element
*/
public fun Vision.setProperty(key: String, item: Any?) {
setProperty(key.toName(), MetaItem.of(item))
}
public var Vision.visible: Boolean?
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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
import kotlin.jvm.Synchronized
internal data class MetaListener(
val owner: Any? = null,
val callback: Meta.(name: Name) -> Unit,
)
/**
* 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
@SerialName("vision")
public open class VisionBase(
override @Transient var parent: VisionGroup? = null,
protected var properties: Config? = null
@Transient override var parent: VisionGroup? = null,
) : Vision {
@Transient
protected open var properties: MutableMeta? = null
@Synchronized
protected fun getOrCreateProperties(): Config {
protected fun getOrCreateProperties(): MutableMeta {
if (properties == null) {
val newProperties = Config()
val newProperties = MutableMeta()
properties = newProperties
}
return properties!!
}
/**
* A fast accessor method to get own property (no inheritance or styles)
*/
override fun getOwnProperty(name: Name): MetaItem? = if (name == Name.EMPTY) {
properties?.asMetaItem()
} else {
properties?.getItem(name)
@Transient
private val listeners: MutableList<MetaListener> = mutableListOf()
private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta {
override val items: Map<NameToken, ObservableMutableMeta>
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,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) {
getOwnProperty(name)
} else {
buildList {
add(getOwnProperty(name))
if (includeStyles) {
addAll(getStyleItems(name))
}
if (inherit) {
add(parent?.getProperty(name, inherit, includeStyles, includeDefaults))
}
if (includeDefaults) {
add(descriptor?.defaultMeta?.get(name))
}
}.merge()
}
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
val oldItem = properties?.getItem(name)
if(oldItem!= item) {
getOrCreateProperties().setItem(name, item)
if (notify) {
invalidateProperty(name)
}
): Value? {
properties?.get(name)?.value?.let { return it }
if (includeStyles) {
getStyleProperty(name)?.let { return it }
}
if (inherit) {
parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
}
if (includeDefaults) {
descriptor?.defaultNode?.get(name)?.value.let { return it }
}
return null
}
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) {
launch {
if (propertyName == STYLE_KEY) {
updateStyles(styles)
}
propertyInvalidationFlow.emit(propertyName)
if (propertyName == STYLE_KEY) {
styles.mapNotNull { getStyle(it) }.asSequence()
.flatMap { it.items.asSequence() }
.distinctBy { it.key }
.forEach {
invalidateProperty(it.key.asName())
}
}
listeners.forEach { it.callback(properties ?: Meta.EMPTY, propertyName) }
}
override fun update(change: VisionChange) {
change.properties?.let {
updateProperties(Name.EMPTY, it.asMetaItem())
updateProperties(Name.EMPTY, it)
}
}
public companion object {
public val descriptor: NodeDescriptor = NodeDescriptor {
value(STYLE_KEY) {
type(ValueType.STRING)
public val descriptor: MetaDescriptor = MetaDescriptor {
value(STYLE_KEY, ValueType.STRING) {
multiple = true
}
}
public fun Vision.updateProperties(at: Name, item: MetaItem) {
when (item) {
is MetaItemValue -> {
if (item.value == Null) {
setProperty(at, null)
} else
setProperty(at, item)
}
is MetaItemNode -> item.node.items.forEach { (token, childItem) ->
updateProperties(at + token, childItem)
}
public fun Vision.updateProperties(at: Name, item: Meta) {
meta.setValue(at, item.value)
item.items.forEach { (token, item) ->
updateProperties(at + token, item)
}
}

View File

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

View File

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

View File

@ -1,13 +1,12 @@
package space.kscience.visionforge
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.names.*
import kotlin.jvm.Synchronized
private class StructureChangeListener(val owner: Any?, val callback: VisionGroup.(Name) -> Unit)
/**
* Abstract implementation of mutable group of [Vision]
@ -40,16 +39,24 @@ public open class VisionGroupBase(
}
@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
*/
private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
launch {
_structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after))
protected fun childrenChanged(name: Name) {
structureListeners.forEach {
it.callback(this, name)
}
}
@ -83,7 +90,12 @@ public open class VisionGroupBase(
}
}
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
*/
public fun Vision.root(manager: VisionManager){
public fun Vision.root(manager: VisionManager) {
parent = RootVisionGroup(manager)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -5,10 +5,14 @@ import kotlinx.html.stream.createHTML
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.configure
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.*
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionManager
import kotlin.collections.set
import kotlin.test.Test
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
import kotlinx.browser.document
import kotlinx.coroutines.CoroutineScope
import kotlinx.dom.hasClass
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
public external val module: Module
@ -25,7 +28,10 @@ public external interface Module {
*
* 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.
* @param state Initial state between Hot Module Replacement (HMR).

View File

@ -14,17 +14,10 @@ dependencies {
api("no.tornado:tornadofx:1.7.20")
api("de.jensd:fontawesomefx-fontawesome:4.7.0-11") {
exclude(group = "org.openjfx")
}
api("de.jensd:fontawesomefx-commons:11.0") {
exclude(group = "org.openjfx")
}
api("org.fxyz3d:fxyz3d:0.5.4") {
exclude(module = "slf4j-simple")
}
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}")
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ package space.kscience.visionforge.editor
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.Node
import org.slf4j.LoggerFactory
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import tornadofx.*
@ -21,12 +21,10 @@ import tornadofx.*
public abstract class ValueChooserBase<out T : Node> : ValueChooser {
override val node: T by lazy { buildNode() }
final override val valueProperty: SimpleObjectProperty<Value> =
SimpleObjectProperty<Value>(Null)
final override val descriptorProperty: SimpleObjectProperty<ValueDescriptor> =
SimpleObjectProperty<ValueDescriptor>()
final override val valueProperty: SimpleObjectProperty<Value> = SimpleObjectProperty<Value>(Null)
final override val descriptorProperty: SimpleObjectProperty<MetaDescriptor> = SimpleObjectProperty<MetaDescriptor>()
override var descriptor: ValueDescriptor? by descriptorProperty
override var descriptor: MetaDescriptor? by descriptorProperty
override var value: Value? by valueProperty
public fun resetValue() {
@ -38,7 +36,7 @@ public abstract class ValueChooserBase<out T : Node> : ValueChooser {
* @return
*/
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 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.boolean
import space.kscience.dataforge.misc.Type
import space.kscience.visionforge.computePropertyNode
import space.kscience.visionforge.solid.FX3DFactory.Companion.TYPE
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_KEY
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_WIREFRAME_KEY
@ -73,7 +74,7 @@ public class FX3DPlugin : AbstractPlugin() {
is PolyLine -> PolyLine3D(
obj.points.map { Point3D(it.x, it.y, it.z) },
obj.thickness.toFloat(),
obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY, inherit = true)?.color()
obj.computePropertyNode(SolidMaterial.MATERIAL_COLOR_KEY)?.color()
).apply {
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.Material
import javafx.scene.paint.PhongMaterial
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.string
import space.kscience.visionforge.Colors
import space.kscience.visionforge.solid.FXMaterials.GREY
public object FXMaterials {
public val RED: PhongMaterial = PhongMaterial().apply {
@ -26,46 +30,41 @@ public object FXMaterials {
}
public val BLUE: PhongMaterial = PhongMaterial(Color.BLUE)
}
/**
* Infer color based on meta item
* @param opacity default opacity
*/
public fun MetaItem.color(opacity: Double = 1.0): Color {
return when (this) {
is MetaItemValue -> if (this.value.type == ValueType.NUMBER) {
val int = value.int
val red = int and 0x00ff0000 shr 16
val green = int and 0x0000ff00 shr 8
val blue = int and 0x000000ff
Color.rgb(red, green, blue, opacity)
} else {
Color.web(this.value.string)
}
is MetaItemNode -> {
Color.rgb(
node[Colors.RED_KEY]?.int ?: 0,
node[Colors.GREEN_KEY]?.int ?: 0,
node[Colors.BLUE_KEY]?.int ?: 0,
node[SolidMaterial.OPACITY_KEY]?.double ?: opacity
)
}
public fun Meta.color(opacity: Double = 1.0): Color = value?.let {
if (it.type == ValueType.NUMBER) {
val int = it.int
val red = int and 0x00ff0000 shr 16
val green = int and 0x0000ff00 shr 8
val blue = int and 0x000000ff
Color.rgb(red, green, blue, opacity)
} else {
Color.web(it.string)
}
}
} ?: Color.rgb(
this[Colors.RED_KEY]?.int ?: 0,
this[Colors.GREEN_KEY]?.int ?: 0,
this[Colors.BLUE_KEY]?.int ?: 0,
this[SolidMaterial.OPACITY_KEY]?.double ?: opacity
)
/**
* Infer FX material based on meta item
*/
public fun MetaItem?.material(): Material {
return when (this) {
null -> FXMaterials.GREY
is MetaItemValue -> PhongMaterial(color())
is MetaItemNode -> PhongMaterial().apply {
val opacity = node[SolidMaterial.OPACITY_KEY].double ?: 1.0
diffuseColor = node[SolidMaterial.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
specularColor = node[SolidMaterial.SPECULAR_COLOR_KEY]?.color(opacity) ?: Color.WHITE
}
public fun Meta?.material(): Material {
if (this == null) return GREY
return value?.let {
PhongMaterial(color())
} ?: PhongMaterial().apply {
val opacity = get(SolidMaterial.OPACITY_KEY).double ?: 1.0
diffuseColor = get(SolidMaterial.COLOR_KEY)?.color(opacity) ?: Color.DARKGREY
specularColor = get(SolidMaterial.SPECULAR_COLOR_KEY)?.color(opacity) ?: Color.WHITE
}
}

View File

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

View File

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

View File

@ -1,12 +1,12 @@
package space.kscience.visionforge.gdml
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.toName
import space.kscience.gdml.*
import space.kscience.visionforge.*
import space.kscience.visionforge.html.VisionOutput
@ -41,8 +41,8 @@ public class GdmlTransformer {
internal val styleCache = HashMap<Name, Meta>()
public fun Solid.registerAndUseStyle(name: String, builder: MetaBuilder.() -> Unit) {
styleCache.getOrPut(name.toName()) {
public fun Solid.registerAndUseStyle(name: String, builder: MutableMeta.() -> Unit) {
styleCache.getOrPut(Name.parse(name)) {
Meta(builder)
}
useStyle(name)
@ -118,7 +118,7 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
private val proto = SolidGroup()
private val solids = proto.group(solidsName) {
setProperty("edges.enabled", false)
setPropertyNode("edges.enabled", false)
}
private val referenceStore = HashMap<Name, MutableList<SolidReferenceGroup>>()
@ -441,20 +441,6 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
}
final.useStyle(rootStyle)
//inline prototypes
// referenceStore.forEach { (protoName, list) ->
// val proxy = list.singleOrNull() ?: return@forEach
// val parent = proxy.parent as? MutableVisionGroup ?: return@forEach
// val token = parent.children.entries.find { it.value == proxy }?.key ?: error("Inconsistent reference cache")
// val prototype = proto[protoName] as? Solid ?: error("Inconsistent reference cache")
// prototype.parent = null
// parent[token] = prototype
// prototype.updateFrom(proxy)
//
// //FIXME update prototype
// proto[protoName] = null
// }
final.prototypes {
proto.children.forEach { (token, item) ->
item.parent = null

View File

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

View File

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

View File

@ -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