forked from kscience/visionforge
commit
f0f117d4fc
@ -4,13 +4,14 @@
|
||||
### Added
|
||||
- Server module
|
||||
- Change collector
|
||||
- Customizable accessors for colors
|
||||
|
||||
### Changed
|
||||
- Vision does not implement ItemProvider anymore. Property changes are done via `getProperty`/`setProperty` and `property` delegate.
|
||||
- Point3D and Point2D are made separate classes instead of expect/actual (to split up different engines.
|
||||
- JavaFX support moved to a separate module
|
||||
- Threejs support moved to a separate module
|
||||
- \[Breaking change!\] Stylesheets are moved into properties under `@stylesheet` key
|
||||
- \[Format breaking change!\] Stylesheets are moved into properties under `@stylesheet` key
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
@ -2,8 +2,8 @@ plugins {
|
||||
id("ru.mipt.npm.project")
|
||||
}
|
||||
|
||||
val dataforgeVersion by extra("0.2.1-dev-4")
|
||||
val ktorVersion by extra("1.4.3")
|
||||
val dataforgeVersion by extra("0.3.0-dev")
|
||||
val ktorVersion by extra("1.5.0")
|
||||
val htmlVersion by extra("0.7.2")
|
||||
val kotlinWrappersVersion by extra("pre.129-kotlin-1.4.20")
|
||||
val fxVersion by extra("14")
|
||||
|
@ -2,6 +2,7 @@ package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.describedProperties
|
||||
import hep.dataforge.vision.editor.VisualObjectEditorFragment
|
||||
import hep.dataforge.vision.editor.VisualObjectTreeFragment
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
@ -26,7 +27,7 @@ class GDMLView : View() {
|
||||
}
|
||||
|
||||
private val propertyEditor = VisualObjectEditorFragment {
|
||||
it.allProperties
|
||||
it.describedProperties
|
||||
}.apply {
|
||||
descriptorProperty.set(SolidMaterial.descriptor)
|
||||
itemProperty.bind(treeFragment.selectedProperty)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.meta.setItem
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vision.gdml.readFile
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
|
@ -1,7 +1,6 @@
|
||||
package ru.mipt.npm.muon.monitor
|
||||
|
||||
import hep.dataforge.vision.removeAll
|
||||
import hep.dataforge.vision.setProperty
|
||||
import hep.dataforge.vision.solid.*
|
||||
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
|
||||
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
|
||||
@ -53,7 +52,6 @@ class Model {
|
||||
detector(it)
|
||||
}
|
||||
}
|
||||
|
||||
tracks = group("tracks")
|
||||
}
|
||||
|
||||
@ -63,7 +61,6 @@ class Model {
|
||||
|
||||
fun reset() {
|
||||
map.values.forEach {
|
||||
it.config
|
||||
it.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null)
|
||||
}
|
||||
tracks.removeAll()
|
||||
|
@ -9,8 +9,8 @@ import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.bootstrap.canvasControls
|
||||
import hep.dataforge.vision.bootstrap.card
|
||||
import hep.dataforge.vision.bootstrap.gridRow
|
||||
import hep.dataforge.vision.bootstrap.visionPropertyEditor
|
||||
import hep.dataforge.vision.react.ThreeCanvasComponent
|
||||
import hep.dataforge.vision.react.configEditor
|
||||
import hep.dataforge.vision.react.flexColumn
|
||||
import hep.dataforge.vision.react.objectTree
|
||||
import hep.dataforge.vision.solid.specifications.Camera
|
||||
@ -193,12 +193,7 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
||||
else -> root[selected]
|
||||
}
|
||||
if (selectedObject != null) {
|
||||
configEditor(
|
||||
selectedObject.config,
|
||||
selectedObject.descriptor,
|
||||
default = selectedObject.allProperties,
|
||||
key = selected
|
||||
)
|
||||
visionPropertyEditor(selectedObject, key = selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package ru.mipt.npm.sat
|
||||
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.style
|
||||
import hep.dataforge.vision.useStyle
|
||||
import kotlin.math.PI
|
||||
|
||||
internal fun visionOfSatellite(
|
||||
@ -12,7 +15,18 @@ internal fun visionOfSatellite(
|
||||
ySegmentSize: Number = xSegmentSize,
|
||||
fiberDiameter: Number = 1.0,
|
||||
): SolidGroup = SolidGroup {
|
||||
opacity = 0.3
|
||||
val transparent by style {
|
||||
this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3
|
||||
}
|
||||
|
||||
val red by style {
|
||||
this[SolidMaterial.MATERIAL_COLOR_KEY] = "red"
|
||||
}
|
||||
|
||||
val blue by style {
|
||||
this[SolidMaterial.MATERIAL_COLOR_KEY] = "blue"
|
||||
}
|
||||
|
||||
val totalXSize = xSegments * xSegmentSize.toDouble()
|
||||
val totalYSize = ySegments * ySegmentSize.toDouble()
|
||||
for (layer in 1..layers) {
|
||||
@ -20,6 +34,7 @@ internal fun visionOfSatellite(
|
||||
for (i in 1..xSegments) {
|
||||
for (j in 1..ySegments) {
|
||||
box(xSegmentSize, ySegmentSize, layerHeight, name = "segment[$i,$j]") {
|
||||
useStyle(transparent)
|
||||
z = (layer - 0.5) * layerHeight.toDouble()
|
||||
x = (i - 0.5) * xSegmentSize.toDouble()
|
||||
y = (j - 0.5) * ySegmentSize.toDouble()
|
||||
@ -29,23 +44,21 @@ internal fun visionOfSatellite(
|
||||
group("fibers") {
|
||||
for (i in 1..xSegments) {
|
||||
cylinder(fiberDiameter, totalYSize) {
|
||||
useStyle(red)
|
||||
rotationX = PI / 2
|
||||
z = (layer - 1.0) * layerHeight.toDouble() + fiberDiameter.toDouble()
|
||||
x = (i - 0.5) * xSegmentSize.toDouble()
|
||||
y = totalYSize / 2
|
||||
|
||||
color("red")
|
||||
}
|
||||
}
|
||||
|
||||
for (j in 1..ySegments) {
|
||||
cylinder(fiberDiameter, totalXSize) {
|
||||
useStyle(blue)
|
||||
rotationY = PI / 2
|
||||
z = (layer) * layerHeight.toDouble() - fiberDiameter.toDouble()
|
||||
y = (j - 0.5) * xSegmentSize.toDouble()
|
||||
x = totalXSize / 2
|
||||
|
||||
color("blue")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package ru.mipt.npm.sat
|
||||
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.color
|
||||
import hep.dataforge.vision.solid.invoke
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.three.server.*
|
||||
import hep.dataforge.vision.visionManager
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
@ -15,10 +15,14 @@ import kotlinx.html.h1
|
||||
import kotlin.random.Random
|
||||
|
||||
fun main() {
|
||||
val satContext = Global.context("sat") {
|
||||
plugin(SolidManager)
|
||||
}
|
||||
|
||||
//Create a geometry
|
||||
val sat = visionOfSatellite(ySegments = 3)
|
||||
|
||||
val server = visionManager.serve {
|
||||
val server = satContext.visionManager.serve {
|
||||
//use client library
|
||||
useThreeJs()
|
||||
//use css
|
||||
@ -39,10 +43,11 @@ fun main() {
|
||||
val randomI = Random.nextInt(1, 4)
|
||||
val randomJ = Random.nextInt(1, 4)
|
||||
val target = "layer[$randomLayer].segment[$randomI,$randomJ]".toName()
|
||||
(sat[target] as? Solid)?.color("red")
|
||||
delay(300)
|
||||
(sat[target] as? Solid)?.color("green")
|
||||
delay(10)
|
||||
val targetVision = sat[target] as Solid
|
||||
targetVision.color("red")
|
||||
delay(1000)
|
||||
targetVision.color.clear()
|
||||
delay(500)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,15 +39,18 @@ fun Page<Solid>.showcase() {
|
||||
demo("shapes", "Basic shapes") {
|
||||
box(100.0, 100.0, 100.0) {
|
||||
z = -110.0
|
||||
color("teal")
|
||||
}
|
||||
sphere(50.0) {
|
||||
x = 110
|
||||
detail = 16
|
||||
color("red")
|
||||
}
|
||||
tube(50, height = 10, innerRadius = 25, angle = PI) {
|
||||
y = 110
|
||||
detail = 16
|
||||
rotationX = PI / 4
|
||||
color("blue")
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +58,7 @@ fun Page<Solid>.showcase() {
|
||||
val group = group {
|
||||
box(100, 100, 100) {
|
||||
z = 110.0
|
||||
opacity = 0.5
|
||||
}
|
||||
|
||||
box(100, 100, 100) {
|
||||
@ -143,8 +147,7 @@ fun Page<Solid>.showcaseCSG() {
|
||||
detail = 32
|
||||
}
|
||||
material {
|
||||
color(Colors.red)
|
||||
wireframe = false
|
||||
color(Colors.pink)
|
||||
}
|
||||
}
|
||||
composite(CompositeType.UNION) {
|
||||
@ -154,8 +157,8 @@ fun Page<Solid>.showcaseCSG() {
|
||||
sphere(50){
|
||||
detail = 32
|
||||
}
|
||||
color(Colors.lightgreen)
|
||||
opacity = 0.3
|
||||
color("lightgreen")
|
||||
opacity = 0.7
|
||||
}
|
||||
composite(CompositeType.SUBTRACT) {
|
||||
y = -300
|
||||
@ -165,7 +168,7 @@ fun Page<Solid>.showcaseCSG() {
|
||||
sphere(50){
|
||||
detail = 32
|
||||
}
|
||||
color(Colors.teal)
|
||||
color("teal")
|
||||
opacity = 0.7
|
||||
}
|
||||
}
|
||||
@ -173,9 +176,11 @@ fun Page<Solid>.showcaseCSG() {
|
||||
demo("CSG.custom", "CSG with manually created object") {
|
||||
intersect {
|
||||
tube(60, 10) {
|
||||
detail = 64
|
||||
detail = 32
|
||||
}
|
||||
box(100, 100, 100)
|
||||
color("red")
|
||||
opacity = 0.5
|
||||
}
|
||||
}
|
||||
}
|
@ -9,9 +9,7 @@ import hep.dataforge.vision.set
|
||||
import hep.dataforge.vision.setProperty
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.Solid.Companion.GEOMETRY_KEY
|
||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
||||
import hep.dataforge.vision.solid.three.*
|
||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.BoxBufferGeometry
|
||||
@ -31,11 +29,9 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
||||
scaleX = xSize
|
||||
scaleY = ySize
|
||||
scaleZ = zSize
|
||||
config[MeshThreeFactory.EDGES_ENABLED_KEY] = false
|
||||
config[MeshThreeFactory.WIREFRAME_ENABLED_KEY] = false
|
||||
}
|
||||
|
||||
override fun render(): Object3D {
|
||||
override fun render(three: ThreePlugin): Object3D {
|
||||
val xSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||
val ySize = getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||
val zSize = getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||
@ -44,9 +40,10 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
||||
//JS sometimes tries to pass Geometry as BufferGeometry
|
||||
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
||||
|
||||
val mesh = Mesh(geometry, getMaterial(this@VariableBox, true)).apply {
|
||||
val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply {
|
||||
updateMaterial(this@VariableBox)
|
||||
applyEdges(this@VariableBox)
|
||||
applyWireFrame(this@VariableBox)
|
||||
//applyWireFrame(this@VariableBox)
|
||||
|
||||
//set position for mesh
|
||||
updatePosition(this@VariableBox)
|
||||
@ -60,7 +57,7 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
||||
mesh.scale.set(xSize, ySize, zSize)
|
||||
|
||||
//add listener to object properties
|
||||
onPropertyChange(mesh) { name ->
|
||||
onPropertyChange(three.context) { name ->
|
||||
when {
|
||||
name.startsWith(GEOMETRY_KEY) -> {
|
||||
val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
|
||||
@ -69,14 +66,12 @@ internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeV
|
||||
mesh.scale.set(newXSize, newYSize, newZSize)
|
||||
mesh.updateMatrix()
|
||||
}
|
||||
name.startsWith(MeshThreeFactory.WIREFRAME_KEY) -> mesh.applyWireFrame(this@VariableBox)
|
||||
name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
|
||||
name.startsWith(MATERIAL_COLOR_KEY)->{
|
||||
mesh.material = getMaterial(this, true)
|
||||
}
|
||||
//name.startsWith(MATERIAL_COLOR_KEY) -> mesh.updateMaterialProperty(this, name)
|
||||
else -> mesh.updateProperty(this@VariableBox, name)
|
||||
}
|
||||
}
|
||||
|
||||
return mesh
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,16 @@ repositories{
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
js(IR) {
|
||||
browser {
|
||||
webpackTask {
|
||||
this.outputFileName = "js/visionforge-playground.js"
|
||||
}
|
||||
}
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
jvm{
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = "11"
|
||||
@ -20,30 +30,39 @@ kotlin {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
js(IR) {
|
||||
browser {
|
||||
|
||||
afterEvaluate {
|
||||
val jsBrowserDistribution by tasks.getting
|
||||
|
||||
tasks.getByName<ProcessResources>("jvmProcessResources") {
|
||||
dependsOn(jsBrowserDistribution)
|
||||
afterEvaluate {
|
||||
from(jsBrowserDistribution)
|
||||
}
|
||||
}
|
||||
binaries.executable()
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(project(":visionforge-solid"))
|
||||
implementation(project(":visionforge-gdml"))
|
||||
implementation(project(":visionforge-plotly"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
val jsMain by getting{
|
||||
dependencies {
|
||||
implementation(project(":ui:bootstrap"))
|
||||
implementation(project(":visionforge-threejs"))
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting{
|
||||
dependencies {
|
||||
implementation(project(":visionforge-server"))
|
||||
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
|
||||
implementation(project(":visionforge-threejs:visionforge-threejs-server"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
playground/src/commonMain/kotlin/visionContext.kt
Normal file
2
playground/src/commonMain/kotlin/visionContext.kt
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
|
@ -1,58 +1,67 @@
|
||||
import hep.dataforge.Application
|
||||
import hep.dataforge.startApplication
|
||||
import hep.dataforge.vision.bootstrap.visionPropertyEditor
|
||||
import hep.dataforge.vision.react.ThreeCanvasComponent
|
||||
import hep.dataforge.vision.react.objectTree
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import kotlinx.browser.document
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.RBuilder
|
||||
import react.child
|
||||
import react.dom.div
|
||||
import react.dom.render
|
||||
|
||||
fun RBuilder.threeCanvas(object3D: Solid, options: Canvas3DOptions.() -> Unit = {}) {
|
||||
child(ThreeCanvasComponent) {
|
||||
attrs {
|
||||
this.obj = object3D
|
||||
this.options = Canvas3DOptions(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayGroundApp : Application {
|
||||
|
||||
override fun start(state: Map<String, Any>) {
|
||||
|
||||
val element =
|
||||
document.getElementById("app") as? HTMLElement ?: error("Element with id 'canvas' not found on page")
|
||||
|
||||
val obj = SolidGroup().apply {
|
||||
box(100, 100, 100, name = "A")
|
||||
group("B") {
|
||||
position = Point3D(120, 0, 0)
|
||||
box(100, 100, 100, name = "C")
|
||||
}
|
||||
}
|
||||
|
||||
render(element) {
|
||||
div("row") {
|
||||
div("col-3") {
|
||||
objectTree(obj)
|
||||
}
|
||||
div("col-6") {
|
||||
threeCanvas(obj)
|
||||
}
|
||||
div("col-3") {
|
||||
visionPropertyEditor(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.vision.client.VisionClient
|
||||
import hep.dataforge.vision.client.renderAllVisions
|
||||
import hep.dataforge.vision.plotly.PlotlyPlugin
|
||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||
import kotlinx.browser.window
|
||||
|
||||
//fun RBuilder.threeCanvas(object3D: Solid, options: Canvas3DOptions.() -> Unit = {}) {
|
||||
// child(ThreeCanvasComponent) {
|
||||
// attrs {
|
||||
// this.obj = object3D
|
||||
// this.options = Canvas3DOptions(options)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private class PlayGroundApp : Application {
|
||||
//
|
||||
// override fun start(state: Map<String, Any>) {
|
||||
//
|
||||
// val element =
|
||||
// document.getElementById("app") as? HTMLElement ?: error("Element with id 'canvas' not found on page")
|
||||
//
|
||||
// val obj = SolidGroup().apply {
|
||||
// box(100, 100, 100, name = "A")
|
||||
// group("B") {
|
||||
// position = Point3D(120, 0, 0)
|
||||
// box(100, 100, 100, name = "C")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// render(element) {
|
||||
// div("row") {
|
||||
// div("col-3") {
|
||||
// objectTree(obj)
|
||||
// }
|
||||
// div("col-6") {
|
||||
// threeCanvas(obj)
|
||||
// }
|
||||
// div("col-3") {
|
||||
// visionPropertyEditor(obj)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
public val visionContext: Context = Global.context("VISION") {
|
||||
plugin(ThreePlugin)
|
||||
plugin(PlotlyPlugin)
|
||||
plugin(VisionClient)
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
fun main() {
|
||||
startApplication(::PlayGroundApp)
|
||||
//Loading three-js renderer
|
||||
val clientManager = visionContext.plugins.fetch(VisionClient)
|
||||
|
||||
//Fetch from server and render visions for all outputs
|
||||
window.onload = {
|
||||
clientManager.renderAllVisions()
|
||||
}
|
||||
//startApplication(::PlayGroundApp)
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.vision.ResourceLocation
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.html.fragment
|
||||
import kotlinx.html.h1
|
||||
import java.nio.file.Paths
|
||||
import kotlin.random.Random
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
fun main() {
|
||||
|
||||
val random = Random(112233)
|
||||
val fragment = VisionManager.fragment {
|
||||
h1 { +"Happy new year!" }
|
||||
vision {
|
||||
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())
|
||||
specularColor(random.nextInt())
|
||||
}
|
||||
detail = 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visionContext.makeVisionFile(
|
||||
fragment,
|
||||
Paths.get("randomSpheres.html"),
|
||||
resourceLocation = ResourceLocation.EMBED
|
||||
)
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.vision.ResourceLocation
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.html.HtmlVisionFragment
|
||||
import hep.dataforge.vision.makeVisionFile
|
||||
import hep.dataforge.vision.scriptHeader
|
||||
import hep.dataforge.vision.three.server.VisionServer
|
||||
import hep.dataforge.vision.three.server.useScript
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
/**
|
||||
* A global vision context used to resolve different vision renderers
|
||||
*/
|
||||
@DFExperimental
|
||||
public val visionContext: Context = Global.context("VISION") {
|
||||
plugin(VisionManager)
|
||||
plugin(SolidManager)
|
||||
}
|
||||
|
||||
public fun VisionServer.usePlayground(): Unit {
|
||||
useScript("js/visionforge-playground.js")
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public fun Context.makeVisionFile(
|
||||
fragment: HtmlVisionFragment,
|
||||
path: Path? = null,
|
||||
title: String = "VisionForge page",
|
||||
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
|
||||
show: Boolean = true,
|
||||
): Unit = makeVisionFile(fragment, path = path, title = title, show = show) { actualPath ->
|
||||
scriptHeader("js/visionforge-playground.js", actualPath, resourceLocation)
|
||||
}
|
@ -4,8 +4,6 @@ import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.vision.ResourceLocation
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.html.fragment
|
||||
import hep.dataforge.vision.three.server.makeFile
|
||||
import hep.dataforge.vision.three.server.solid
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
fun main() {
|
||||
@ -17,5 +15,5 @@ fun main() {
|
||||
}
|
||||
}
|
||||
|
||||
fragment.makeFile(resourceLocation = ResourceLocation.LOCAL)
|
||||
visionContext.makeVisionFile(fragment = fragment, resourceLocation = ResourceLocation.SYSTEM)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
pluginManagement {
|
||||
val kotlinVersion = "1.4.20"
|
||||
val toolsVersion = "0.7.0"
|
||||
val kotlinVersion = "1.4.21"
|
||||
val toolsVersion = "0.7.1"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@ -33,7 +33,7 @@ rootProject.name = "visionforge"
|
||||
include(
|
||||
// ":ui",
|
||||
":ui:react",
|
||||
":ui:ring",
|
||||
// ":ui:ring",
|
||||
// ":ui:material",
|
||||
":ui:bootstrap",
|
||||
":visionforge-core",
|
||||
@ -43,6 +43,7 @@ include(
|
||||
":visionforge-threejs:visionforge-threejs-server",
|
||||
":visionforge-gdml",
|
||||
":visionforge-server",
|
||||
":visionforge-plotly",
|
||||
":demo:spatial-showcase",
|
||||
":demo:gdml",
|
||||
":demo:muon-monitor",
|
||||
|
@ -51,11 +51,7 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
|
||||
else -> (vision as? VisionGroup)?.get(selected)
|
||||
}
|
||||
if (selectedObject != null) {
|
||||
visionPropertyEditor(
|
||||
selectedObject,
|
||||
default = selectedObject.allProperties,
|
||||
key = selected
|
||||
)
|
||||
visionPropertyEditor(selectedObject, key = selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,7 +65,7 @@ public fun RBuilder.threeControls(
|
||||
canvas: ThreeCanvas,
|
||||
selected: Name?,
|
||||
onSelect: (Name) -> Unit = {},
|
||||
builder: TabBuilder.() -> Unit = {}
|
||||
builder: TabBuilder.() -> Unit = {},
|
||||
): ReactElement = child(ThreeControls) {
|
||||
attrs {
|
||||
this.canvas = canvas
|
||||
|
@ -1,30 +1,38 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.getStyle
|
||||
import hep.dataforge.vision.react.configEditor
|
||||
import hep.dataforge.vision.*
|
||||
import hep.dataforge.vision.react.metaViewer
|
||||
import hep.dataforge.vision.react.propertyEditor
|
||||
import hep.dataforge.vision.solid.SolidReference
|
||||
import org.w3c.dom.Element
|
||||
import react.RBuilder
|
||||
import react.dom.render
|
||||
|
||||
public fun RBuilder.visionPropertyEditor(
|
||||
item: Vision,
|
||||
descriptor: NodeDescriptor? = item.descriptor,
|
||||
default: Meta? = null,
|
||||
key: Any? = null
|
||||
vision: Vision,
|
||||
descriptor: NodeDescriptor? = vision.descriptor,
|
||||
key: Any? = null,
|
||||
) {
|
||||
|
||||
card("Properties") {
|
||||
configEditor(item.config, descriptor, default, key)
|
||||
propertyEditor(
|
||||
provider = vision.ownProperties,
|
||||
defaultProvider = vision.allProperties(),
|
||||
updateFlow = vision.propertyChanges,
|
||||
descriptor = descriptor,
|
||||
key = key)
|
||||
}
|
||||
val styles = if (vision is SolidReference) {
|
||||
(vision.styles + vision.prototype.styles).distinct()
|
||||
} else {
|
||||
vision.styles
|
||||
}
|
||||
val styles = item.styles
|
||||
if (styles.isNotEmpty()) {
|
||||
card("Styles") {
|
||||
accordion("styles") {
|
||||
styles.forEach { styleName ->
|
||||
val style = item.getStyle(styleName)
|
||||
val style = vision.getStyle(styleName)
|
||||
if (style != null) {
|
||||
entry(styleName) {
|
||||
metaViewer(style)
|
||||
@ -39,7 +47,6 @@ public fun RBuilder.visionPropertyEditor(
|
||||
public fun Element.visionPropertyEditor(
|
||||
item: Vision,
|
||||
descriptor: NodeDescriptor? = item.descriptor,
|
||||
default: Meta? = null
|
||||
): Unit = render(this) {
|
||||
visionPropertyEditor(item, descriptor, default)
|
||||
visionPropertyEditor(item, descriptor = descriptor)
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package hep.dataforge.vision.react
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.NodeItem
|
||||
import hep.dataforge.meta.ValueItem
|
||||
import hep.dataforge.meta.descriptors.ItemDescriptor
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.descriptors.defaultItem
|
||||
@ -51,7 +52,7 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
|
||||
}
|
||||
|
||||
when (actualItem) {
|
||||
is MetaItem.NodeItem -> {
|
||||
is NodeItem -> {
|
||||
styledDiv {
|
||||
css {
|
||||
+TreeStyles.treeLeaf
|
||||
@ -108,7 +109,7 @@ private fun RBuilder.metaViewerItem(props: MetaViewerProps) {
|
||||
}
|
||||
}
|
||||
}
|
||||
is MetaItem.ValueItem -> {
|
||||
is ValueItem -> {
|
||||
styledDiv {
|
||||
css {
|
||||
+TreeStyles.treeLeaf
|
||||
|
@ -7,6 +7,16 @@ import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.lastOrNull
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.vision.hidden
|
||||
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.html.js.onClickFunction
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.events.Event
|
||||
@ -14,55 +24,75 @@ import react.*
|
||||
import react.dom.render
|
||||
import styled.*
|
||||
|
||||
public external interface ConfigEditorItemProps : RProps {
|
||||
public external interface PropertyEditorProps : RProps {
|
||||
|
||||
/**
|
||||
* Root config object - always non null
|
||||
*/
|
||||
public var root: Config
|
||||
public var provider: MutableItemProvider
|
||||
|
||||
/**
|
||||
* Full path to the displayed node in [root]. Could be empty
|
||||
* Provide default item (greyed out if used)
|
||||
*/
|
||||
public var name: Name
|
||||
public var defaultProvider: ItemProvider?
|
||||
|
||||
/**
|
||||
* Root default
|
||||
* Full path to the displayed node in [provider]. Could be empty
|
||||
*/
|
||||
public var default: Meta?
|
||||
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>?
|
||||
}
|
||||
|
||||
private val ConfigEditorItem: FunctionalComponent<ConfigEditorItemProps> =
|
||||
private val PropertyEditorItem: FunctionalComponent<PropertyEditorProps> =
|
||||
functionalComponent("ConfigEditorItem") { props ->
|
||||
configEditorItem(props)
|
||||
propertyEditorItem(props)
|
||||
}
|
||||
|
||||
private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
||||
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
|
||||
var expanded: Boolean by useState { true }
|
||||
var item: MetaItem<Config>? by useState { props.root[props.name] }
|
||||
val descriptorItem: ItemDescriptor? = props.descriptor?.get(props.name)
|
||||
val defaultItem = props.default?.get(props.name)
|
||||
var actualItem: MetaItem<Meta>? by useState { item ?: defaultItem ?: descriptorItem?.defaultItem() }
|
||||
val itemName = props.name ?: Name.EMPTY
|
||||
val descriptorItem: ItemDescriptor? =
|
||||
useMemo({ props.descriptor?.get(itemName) }, arrayOf(props.descriptor, itemName))
|
||||
|
||||
val token = props.name.lastOrNull()?.toString() ?: "Properties"
|
||||
var item: MetaItem? by useState { props.provider.getItem(itemName) }
|
||||
|
||||
if (descriptorItem?.hidden == true) return //fail fast for hidden property
|
||||
|
||||
var actualItem: MetaItem? by useState {
|
||||
item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
|
||||
}
|
||||
|
||||
val token = itemName.lastOrNull()?.toString() ?: "Properties"
|
||||
|
||||
fun update() {
|
||||
item = props.root[props.name]
|
||||
actualItem = item ?: defaultItem ?: descriptorItem?.defaultItem()
|
||||
item = props.provider.getItem(itemName)
|
||||
actualItem = item ?: props.defaultProvider?.getItem(itemName) ?: descriptorItem?.defaultItem()
|
||||
}
|
||||
|
||||
useEffectWithCleanup(listOf(props.root)) {
|
||||
props.root.onChange(this) { name, _, _ ->
|
||||
if (name == props.name) {
|
||||
if (props.updateFlow != null) {
|
||||
useEffectWithCleanup(listOf(props.provider, props.updateFlow)) {
|
||||
val updateJob = props.updateFlow!!.onEach { updatedName ->
|
||||
if (updatedName == props.name) {
|
||||
update()
|
||||
}
|
||||
}.launchIn(props.scope ?: GlobalScope)
|
||||
return@useEffectWithCleanup { updateJob.cancel() }
|
||||
}
|
||||
return@useEffectWithCleanup { props.root.removeListener(this) }
|
||||
}
|
||||
|
||||
val expanderClick: (Event) -> Unit = {
|
||||
@ -71,21 +101,19 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
||||
|
||||
val valueChanged: (Value?) -> Unit = {
|
||||
if (it == null) {
|
||||
props.root.remove(props.name)
|
||||
props.provider.remove(itemName)
|
||||
} else {
|
||||
props.root[props.name] = it
|
||||
props.provider[itemName] = it
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
val removeClick: (Event) -> Unit = {
|
||||
props.root.remove(props.name)
|
||||
props.provider.remove(itemName)
|
||||
update()
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (actualItem is MetaItem.NodeItem) {
|
||||
if (actualItem is NodeItem) {
|
||||
styledDiv {
|
||||
css {
|
||||
+TreeStyles.treeLeaf
|
||||
@ -121,7 +149,6 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
||||
add(NameToken(it))
|
||||
}
|
||||
item?.node?.items?.keys?.let { addAll(it) }
|
||||
defaultItem?.node?.items?.keys?.let { addAll(it) }
|
||||
}
|
||||
|
||||
keys.filter { !it.body.startsWith("@") }.forEach { token ->
|
||||
@ -129,12 +156,11 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
||||
css {
|
||||
+TreeStyles.treeItem
|
||||
}
|
||||
child(ConfigEditorItem) {
|
||||
child(PropertyEditorItem) {
|
||||
attrs {
|
||||
this.key = props.name.toString()
|
||||
this.root = props.root
|
||||
this.name = props.name + token
|
||||
this.default = props.default
|
||||
this.provider = props.provider
|
||||
this.name = itemName + token
|
||||
this.descriptor = props.descriptor
|
||||
}
|
||||
}
|
||||
@ -166,7 +192,7 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
||||
+TreeStyles.resizeableInput
|
||||
}
|
||||
valueChooser(
|
||||
props.name,
|
||||
itemName,
|
||||
actualItem,
|
||||
descriptorItem as? ValueDescriptor,
|
||||
valueChanged
|
||||
@ -190,63 +216,68 @@ private fun RBuilder.configEditorItem(props: ConfigEditorItemProps) {
|
||||
}
|
||||
}
|
||||
|
||||
public external interface ConfigEditorProps : RProps {
|
||||
public var id: Name
|
||||
public var root: Config
|
||||
public var default: Meta?
|
||||
public var descriptor: NodeDescriptor?
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ConfigEditor: FunctionalComponent<ConfigEditorProps> = functionalComponent("ConfigEditor") { props ->
|
||||
child(ConfigEditorItem) {
|
||||
public val PropertyEditor: FunctionalComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
|
||||
child(PropertyEditorItem) {
|
||||
attrs {
|
||||
this.key = ""
|
||||
this.root = props.root
|
||||
this.provider = props.provider
|
||||
this.defaultProvider = props.defaultProvider
|
||||
this.name = Name.EMPTY
|
||||
this.default = props.default
|
||||
this.descriptor = props.descriptor
|
||||
this.scope = props.scope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun RBuilder.propertyEditor(
|
||||
provider: MutableItemProvider,
|
||||
defaultProvider: ItemProvider?,
|
||||
updateFlow: Flow<Name>? = null,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
scope: CoroutineScope? = null,
|
||||
key: Any? = null,
|
||||
) {
|
||||
child(PropertyEditor) {
|
||||
attrs {
|
||||
this.provider = provider
|
||||
this.defaultProvider = defaultProvider
|
||||
this.updateFlow = updateFlow
|
||||
this.descriptor = descriptor
|
||||
this.key = key?.toString() ?: ""
|
||||
this.scope = scope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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,
|
||||
key: Any? = null,
|
||||
scope: CoroutineScope? = null,
|
||||
): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key)
|
||||
|
||||
public fun Element.configEditor(
|
||||
config: Config,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
default: Meta? = null,
|
||||
key: Any? = null,
|
||||
) {
|
||||
render(this) {
|
||||
child(ConfigEditor) {
|
||||
attrs {
|
||||
this.key = key?.toString() ?: ""
|
||||
this.root = config
|
||||
this.descriptor = descriptor
|
||||
this.default = default
|
||||
scope: CoroutineScope? = null,
|
||||
): Unit = render(this) {
|
||||
configEditor(config, default, descriptor, key, scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun RBuilder.configEditor(
|
||||
config: Config,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
default: Meta? = null,
|
||||
key: Any? = null,
|
||||
) {
|
||||
child(ConfigEditor) {
|
||||
attrs {
|
||||
this.key = key?.toString() ?: ""
|
||||
this.root = config
|
||||
this.descriptor = descriptor
|
||||
this.default = default
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
//public fun RBuilder.configEditor(
|
||||
// obj: Configurable,
|
||||
// descriptor: NodeDescriptor?,
|
||||
// default: Meta? = null,
|
||||
// key: Any? = null
|
||||
//): Unit = configEditor(obj.config, descriptor, default, key)
|
@ -37,8 +37,7 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
|
||||
if (canvas == null) {
|
||||
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
||||
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
||||
val newCanvas: ThreeCanvas =
|
||||
three.createCanvas(element, props.options ?: Canvas3DOptions.empty())
|
||||
val newCanvas: ThreeCanvas = three.createCanvas(element, props.options ?: Canvas3DOptions.empty())
|
||||
props.canvasCallback?.invoke(newCanvas)
|
||||
canvas = newCanvas
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
/**
|
||||
* Style the caret/arrow
|
||||
*/
|
||||
public val treeCaret by css {
|
||||
public val treeCaret: RuleSet by css {
|
||||
cursor = Cursor.pointer
|
||||
userSelect = UserSelect.none
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
@ -32,7 +32,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
/**
|
||||
* Rotate the caret/arrow icon when clicked on (using JavaScript)
|
||||
*/
|
||||
val treeCaredDown by css {
|
||||
public val treeCaredDown:RuleSet by css {
|
||||
before {
|
||||
content = "\u25B6".quoted
|
||||
color = Color.black
|
||||
@ -42,7 +42,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
}
|
||||
}
|
||||
|
||||
val treeItem by css {
|
||||
public val treeItem:RuleSet by css {
|
||||
alignItems = Align.center
|
||||
paddingLeft = 10.px
|
||||
borderLeftStyle = BorderStyle.dashed
|
||||
@ -53,27 +53,27 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
borderBottomColor = Color.lightGray
|
||||
}
|
||||
|
||||
val treeLeaf by css {
|
||||
public val treeLeaf:RuleSet by css {
|
||||
display = Display.flex
|
||||
flexDirection = FlexDirection.row
|
||||
flexWrap = FlexWrap.nowrap
|
||||
//alignItems = Align.center
|
||||
}
|
||||
|
||||
val treeLabel by css {
|
||||
public val treeLabel:RuleSet by css {
|
||||
overflow = Overflow.hidden
|
||||
flex(flexGrow = 1.0, flexShrink = 1.0)
|
||||
}
|
||||
|
||||
val treeLabelInactive by css {
|
||||
public val treeLabelInactive: RuleSet by css {
|
||||
color = Color.lightGray
|
||||
}
|
||||
|
||||
val treeLabelSelected by css {
|
||||
public val treeLabelSelected:RuleSet by css {
|
||||
backgroundColor = Color.lightBlue
|
||||
}
|
||||
|
||||
val linkButton by css {
|
||||
public val linkButton:RuleSet by css {
|
||||
backgroundColor = Color.white
|
||||
border = "none"
|
||||
padding(left = 4.pt, right = 4.pt, top = 0.pt, bottom = 0.pt)
|
||||
@ -86,7 +86,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
}
|
||||
}
|
||||
|
||||
val removeButton by css {
|
||||
public val removeButton:RuleSet by css {
|
||||
backgroundColor = Color.white
|
||||
borderStyle = BorderStyle.solid
|
||||
borderRadius = 2.px
|
||||
@ -104,7 +104,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
}
|
||||
}
|
||||
|
||||
val resizeableInput by css {
|
||||
public val resizeableInput: RuleSet by css {
|
||||
overflow = Overflow.hidden
|
||||
maxWidth = 120.pt
|
||||
flex(flexGrow = 2.0, flexShrink = 2.0, flexBasis = 60.pt)
|
||||
|
@ -9,142 +9,166 @@ import hep.dataforge.vision.widgetType
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onKeyDownFunction
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
import org.w3c.dom.events.Event
|
||||
import react.*
|
||||
import react.dom.defaultValue
|
||||
import react.dom.option
|
||||
import styled.styledInput
|
||||
import styled.styledSelect
|
||||
|
||||
public external interface ValueChooserProps : RProps {
|
||||
var item: MetaItem<*>?
|
||||
var descriptor: ValueDescriptor?
|
||||
var valueChanged: ((Value?) -> Unit)?
|
||||
}
|
||||
|
||||
public external interface ValueChooserState : RState {
|
||||
var rawInput: Boolean?
|
||||
public var item: MetaItem?
|
||||
public var descriptor: ValueDescriptor?
|
||||
public var valueChanged: ((Value?) -> Unit)?
|
||||
}
|
||||
|
||||
@JsExport
|
||||
class ValueChooserComponent(props: ValueChooserProps) : RComponent<ValueChooserProps, ValueChooserState>(props) {
|
||||
private val element = createRef<HTMLElement>()
|
||||
|
||||
private fun getValue(): Value? {
|
||||
val element = element.current ?: return null//state.element ?: return null
|
||||
return when (element) {
|
||||
is HTMLInputElement -> if (element.type == "checkbox") {
|
||||
if (element.checked) True else False
|
||||
} else {
|
||||
element.value.asValue()
|
||||
}
|
||||
is HTMLSelectElement -> element.value.asValue()
|
||||
else -> error("Unknown event target: $element")
|
||||
}
|
||||
}
|
||||
|
||||
private val commit: (Event) -> Unit = { _ ->
|
||||
props.valueChanged?.invoke(getValue())
|
||||
}
|
||||
|
||||
private val keyDown: (Event) -> Unit = { event ->
|
||||
public val StringValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
functionalComponent("StringValueChooser") { props ->
|
||||
var value by useState(props.item.string ?: "")
|
||||
val keyDown: (Event) -> Unit = { event ->
|
||||
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||
commit(event)
|
||||
value = (event.target as HTMLInputElement).value
|
||||
if(value!= props.item.string) {
|
||||
props.valueChanged?.invoke(value.asValue())
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldComponentUpdate(
|
||||
nextProps: ValueChooserProps,
|
||||
nextState: ValueChooserState
|
||||
): Boolean = nextProps.item !== props.item
|
||||
|
||||
override fun componentDidUpdate(prevProps: ValueChooserProps, prevState: ValueChooserState, snapshot: Any) {
|
||||
(element.current as? HTMLInputElement)?.let { element ->
|
||||
if (element.type == "checkbox") {
|
||||
element.defaultChecked = props.item?.boolean ?: false
|
||||
} else {
|
||||
element.defaultValue = props.item?.string ?: ""
|
||||
}
|
||||
element.indeterminate = props.item == null
|
||||
val handleChange: (Event) -> Unit = {
|
||||
value = (it.target as HTMLInputElement).value
|
||||
}
|
||||
}
|
||||
|
||||
private fun RBuilder.stringInput() = styledInput(type = InputType.text) {
|
||||
styledInput(type = InputType.text) {
|
||||
attrs {
|
||||
this.defaultValue = props.item?.string ?: ""
|
||||
this.value = value
|
||||
onKeyDownFunction = keyDown
|
||||
onChangeFunction = handleChange
|
||||
}
|
||||
}
|
||||
ref = element
|
||||
}
|
||||
|
||||
override fun RBuilder.render() {
|
||||
val descriptor = props.descriptor
|
||||
val type = descriptor?.type?.firstOrNull()
|
||||
when {
|
||||
state.rawInput == true -> stringInput()
|
||||
descriptor?.widgetType == "color" -> styledInput(type = InputType.color) {
|
||||
ref = element
|
||||
attrs {
|
||||
this.defaultValue = props.item?.value?.let { value ->
|
||||
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
|
||||
else value.string
|
||||
} ?: "#000000"
|
||||
onChangeFunction = commit
|
||||
@JsExport
|
||||
public val BooleanValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
functionalComponent("BooleanValueChooser") { props ->
|
||||
var checkedValue by useState(props.item.boolean ?: false)
|
||||
val handleChange: (Event) -> Unit = {
|
||||
val newValue = (it.target as HTMLInputElement).checked
|
||||
checkedValue = newValue
|
||||
props.valueChanged?.invoke(newValue.asValue())
|
||||
}
|
||||
}
|
||||
type == ValueType.BOOLEAN -> {
|
||||
styledInput(type = InputType.checkBox) {
|
||||
ref = element
|
||||
attrs {
|
||||
defaultChecked = props.item?.boolean ?: false
|
||||
onChangeFunction = commit
|
||||
this.attributes["indeterminate"] = (checkedValue == null).toString()
|
||||
checked = checkedValue
|
||||
onChangeFunction = handleChange
|
||||
}
|
||||
}
|
||||
}
|
||||
type == ValueType.NUMBER -> styledInput(type = InputType.number) {
|
||||
ref = element
|
||||
|
||||
@JsExport
|
||||
public val NumberValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
functionalComponent("NumberValueChooser") { props ->
|
||||
var value by useState(props.item.string ?: "")
|
||||
val keyDown: (Event) -> Unit = { event ->
|
||||
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||
value = (event.target as HTMLInputElement).value
|
||||
val number = value.toDoubleOrNull()
|
||||
if (number == null) {
|
||||
console.error("The input value $value is not a number")
|
||||
} else {
|
||||
props.valueChanged?.invoke(number.asValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
val handleChange: (Event) -> Unit = {
|
||||
value = (it.target as HTMLInputElement).value
|
||||
}
|
||||
styledInput(type = InputType.number) {
|
||||
attrs {
|
||||
descriptor.attributes["step"].string?.let {
|
||||
this.value = value
|
||||
onKeyDownFunction = keyDown
|
||||
onChangeFunction = handleChange
|
||||
props.descriptor?.attributes?.get("step").string?.let {
|
||||
step = it
|
||||
}
|
||||
descriptor.attributes["min"].string?.let {
|
||||
props.descriptor?.attributes?.get("min").string?.let {
|
||||
min = it
|
||||
}
|
||||
descriptor.attributes["max"].string?.let {
|
||||
props.descriptor?.attributes?.get("max").string?.let {
|
||||
max = it
|
||||
}
|
||||
defaultValue = props.item?.string ?: ""
|
||||
onKeyDownFunction = keyDown
|
||||
}
|
||||
}
|
||||
descriptor?.allowedValues?.isNotEmpty() ?: false -> styledSelect {
|
||||
descriptor!!.allowedValues.forEach {
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ComboValueChooser: FunctionalComponent<ValueChooserProps> =
|
||||
functionalComponent("ComboValueChooser") { props ->
|
||||
var selected by useState(props.item.string ?: "")
|
||||
val handleChange: (Event) -> Unit = {
|
||||
selected = (it.target as HTMLSelectElement).value
|
||||
props.valueChanged?.invoke(selected.asValue())
|
||||
}
|
||||
styledSelect {
|
||||
props.descriptor?.allowedValues?.forEach {
|
||||
option {
|
||||
+it.string
|
||||
}
|
||||
}
|
||||
ref = element
|
||||
attrs {
|
||||
this.value = props.item?.string ?: ""
|
||||
multiple = false
|
||||
onChangeFunction = commit
|
||||
onChangeFunction = handleChange
|
||||
}
|
||||
}
|
||||
else -> stringInput()
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ColorValueChooser: FunctionalComponent<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())
|
||||
}
|
||||
styledInput(type = InputType.color) {
|
||||
attrs {
|
||||
this.value = value
|
||||
onChangeFunction = handleChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ValueChooser: FunctionalComponent<ValueChooserProps> = functionalComponent("ValueChooser") { props ->
|
||||
val rawInput by useState(false)
|
||||
|
||||
val descriptor = props.descriptor
|
||||
val type = descriptor?.type?.firstOrNull()
|
||||
|
||||
when {
|
||||
rawInput -> child(StringValueChooser, props)
|
||||
descriptor?.widgetType == "color" -> child(ColorValueChooser, props)
|
||||
type == ValueType.BOOLEAN -> child(BooleanValueChooser, props)
|
||||
type == ValueType.NUMBER -> child(NumberValueChooser, props)
|
||||
descriptor?.allowedValues?.isNotEmpty() ?: false -> child(ComboValueChooser, props)
|
||||
//TODO handle lists
|
||||
else -> child(StringValueChooser, props)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RBuilder.valueChooser(
|
||||
name: Name,
|
||||
item: MetaItem<*>?,
|
||||
item: MetaItem?,
|
||||
descriptor: ValueDescriptor? = null,
|
||||
callback: (Value?) -> Unit
|
||||
callback: (Value?) -> Unit,
|
||||
) {
|
||||
child(ValueChooserComponent::class) {
|
||||
child(ValueChooser) {
|
||||
attrs {
|
||||
key = name.toString()
|
||||
this.item = item
|
||||
|
@ -1,8 +1,6 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.number
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.int
|
||||
import hep.dataforge.values.string
|
||||
@ -192,9 +190,9 @@ public object Colors {
|
||||
/**
|
||||
* Convert color represented as Meta to string of format #rrggbb
|
||||
*/
|
||||
fun fromMeta(item: MetaItem<*>): String {
|
||||
fun fromMeta(item: MetaItem): String {
|
||||
return when (item) {
|
||||
is MetaItem.NodeItem<*> -> {
|
||||
is NodeItem -> {
|
||||
val node = item.node
|
||||
rgbToString(
|
||||
node[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||
@ -202,7 +200,7 @@ public object Colors {
|
||||
node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
|
||||
)
|
||||
}
|
||||
is MetaItem.ValueItem -> {
|
||||
is ValueItem -> {
|
||||
if (item.value.type == ValueType.NUMBER) {
|
||||
rgbToString(item.value.int)
|
||||
} else {
|
||||
|
@ -0,0 +1,33 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
/**
|
||||
* A reference to a style defined in a specific container
|
||||
*/
|
||||
public class StyleReference(public val owner: VisionGroup, public val name: String)
|
||||
|
||||
private tailrec fun styleIsDefined(vision: Vision, reference: StyleReference): Boolean = when {
|
||||
reference.owner === vision -> true
|
||||
vision.parent == null -> false
|
||||
else -> styleIsDefined(vision.parent!!, reference)
|
||||
}
|
||||
|
||||
@VisionBuilder
|
||||
public fun Vision.useStyle(reference: StyleReference) {
|
||||
//check that style is defined in a parent
|
||||
//check(styleIsDefined(this, reference)) { "Style reference does not belong to a Vision parent" }
|
||||
useStyle(reference.name)
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
@VisionBuilder
|
||||
public fun VisionGroup.style(builder: MetaBuilder.() -> Unit): ReadOnlyProperty<Any?, StyleReference> =
|
||||
ReadOnlyProperty { _, property ->
|
||||
val styleName = property.name
|
||||
styleSheet.define(styleName, Meta(builder))
|
||||
StyleReference(this, styleName)
|
||||
}
|
@ -5,13 +5,14 @@ import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A container for styles
|
||||
*/
|
||||
public inline class StyleSheet(private val owner: VisionGroup) {
|
||||
|
||||
private val styleNode get() = owner.properties?.get(STYLESHEET_KEY).node
|
||||
private val styleNode get() = owner.getOwnProperty(STYLESHEET_KEY).node
|
||||
|
||||
public val items: Map<NameToken, Meta>? get() = styleNode?.items?.mapValues { it.value.node ?: Meta.EMPTY }
|
||||
|
||||
@ -21,11 +22,7 @@ public inline class StyleSheet(private val owner: VisionGroup) {
|
||||
* Define a style without notifying owner
|
||||
*/
|
||||
public fun define(key: String, style: Meta?) {
|
||||
if (style == null) {
|
||||
styleNode?.remove(key)
|
||||
} else {
|
||||
owner.config[STYLESHEET_KEY + key] = style
|
||||
}
|
||||
owner.setProperty(STYLESHEET_KEY + key, style)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,7 +40,7 @@ public inline class StyleSheet(private val owner: VisionGroup) {
|
||||
* Create and set a style
|
||||
*/
|
||||
public operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
|
||||
val newStyle = get(key)?.edit(builder) ?: Meta(builder)
|
||||
val newStyle = get(key)?.builder()?.apply(builder) ?: Meta(builder)
|
||||
set(key, newStyle.seal())
|
||||
}
|
||||
|
||||
@ -58,7 +55,9 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
|
||||
val tokens: Collection<Name> =
|
||||
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
|
||||
.map { it.asName() }
|
||||
tokens.forEach { parent?.propertyChanged(it) }
|
||||
parent?.scope?.launch {
|
||||
tokens.forEach { parent?.notifyPropertyChanged(it) }
|
||||
}
|
||||
}
|
||||
if (this is VisionGroup) {
|
||||
for (obj in this) {
|
||||
@ -68,6 +67,15 @@ 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() = getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList()
|
||||
set(value) {
|
||||
setProperty(Vision.STYLE_KEY, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* A stylesheet for this group and its descendants. Stylesheet is not applied directly,
|
||||
* but instead is just a repository for named configurations.
|
||||
@ -78,7 +86,7 @@ public val VisionGroup.styleSheet: StyleSheet get() = StyleSheet(this)
|
||||
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
|
||||
*/
|
||||
public fun Vision.useStyle(name: String) {
|
||||
styles = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + name
|
||||
styles = (getOwnProperty(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
|
||||
}
|
||||
|
||||
|
||||
@ -86,12 +94,12 @@ 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? =
|
||||
properties?.get(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name)
|
||||
getOwnProperty(StyleSheet.STYLESHEET_KEY + name).node ?: parent?.getStyle(name)
|
||||
|
||||
/**
|
||||
* Resolve an item in all style layers
|
||||
*/
|
||||
public fun Vision.getStyleItems(name: Name): Sequence<MetaItem<*>> {
|
||||
public fun Vision.getStyleItems(name: Name): Sequence<MetaItem> {
|
||||
return styles.asSequence().map {
|
||||
getStyle(it)
|
||||
}.map {
|
||||
@ -103,3 +111,5 @@ public fun Vision.getStyleItems(name: Name): Sequence<MetaItem<*>> {
|
||||
* Collect all styles for this object in a single laminate
|
||||
*/
|
||||
public val Vision.allStyles: Laminate get() = Laminate(styles.mapNotNull(::getStyle))
|
||||
|
||||
|
||||
|
@ -1,22 +1,28 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.MutableItemProvider
|
||||
import hep.dataforge.meta.descriptors.Described
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.descriptors.get
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vision.Vision.Companion.TYPE
|
||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
/**
|
||||
* A root type for display hierarchy
|
||||
*/
|
||||
@Type(TYPE)
|
||||
public interface Vision : Configurable, Described {
|
||||
public interface Vision : Described {
|
||||
|
||||
/**
|
||||
* The parent object of this one. If null, this one is a root.
|
||||
@ -25,46 +31,69 @@ public interface Vision : Configurable, Described {
|
||||
public var parent: VisionGroup?
|
||||
|
||||
/**
|
||||
* Nullable version of [config] used to check if this [Vision] has custom properties
|
||||
* Properties belonging to this [Vision] potentially including artificial properties
|
||||
*/
|
||||
public val properties: Config?
|
||||
@Transient
|
||||
public val meta: Meta
|
||||
|
||||
/**
|
||||
* All properties including styles and prototypes if present, including inherited ones
|
||||
* A coroutine scope for asynchronous calls and locks
|
||||
*/
|
||||
public val allProperties: Laminate
|
||||
public val scope: CoroutineScope get() = parent?.scope ?: GlobalScope
|
||||
|
||||
/**
|
||||
* Get property (including styles). [inherit] toggles parent node property lookup
|
||||
* A fast accessor method to get own property (no inheritance or styles).
|
||||
* Should be equivalent to `getProperty(name,false,false,false)`.
|
||||
*/
|
||||
public fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>?
|
||||
public fun getOwnProperty(name: Name): MetaItem?
|
||||
|
||||
/**
|
||||
* Trigger property invalidation event. If [name] is empty, notify that the whole object is changed
|
||||
* Get property.
|
||||
* @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.
|
||||
*/
|
||||
public fun propertyChanged(name: Name): Unit
|
||||
public fun getProperty(
|
||||
name: Name,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
): MetaItem?
|
||||
|
||||
|
||||
/**
|
||||
* Add listener triggering on property change
|
||||
* Set the property value
|
||||
*/
|
||||
public fun onPropertyChange(owner: Any?, action: (Name) -> Unit): Unit
|
||||
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
|
||||
|
||||
/**
|
||||
* Remove change listeners with given owner.
|
||||
* Subscribe on property updates. The subscription is bound to the given [scope] and canceled when the scope is canceled
|
||||
*/
|
||||
public fun removeChangeListener(owner: Any?)
|
||||
public fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit)
|
||||
|
||||
/**
|
||||
* List of names of styles applied to this object. Order matters. Not inherited.
|
||||
* 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 var styles: List<String>
|
||||
get() = properties[STYLE_KEY]?.stringList ?: emptyList()
|
||||
set(value) {
|
||||
config[STYLE_KEY] = value
|
||||
@DFExperimental
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
public val propertyChanges: Flow<Name>
|
||||
get() = callbackFlow<Name> {
|
||||
coroutineScope {
|
||||
onPropertyChange(this) {
|
||||
send(it)
|
||||
}
|
||||
awaitClose { cancel() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update this vision using external meta. Children are not updated.
|
||||
* Notify all listeners that a property has been changed and should be invalidated
|
||||
*/
|
||||
public suspend fun notifyPropertyChanged(propertyName: Name): Unit
|
||||
|
||||
/**
|
||||
* Update this vision using a dif represented by [VisionChange].
|
||||
*/
|
||||
public fun update(change: VisionChange)
|
||||
|
||||
@ -78,60 +107,61 @@ public interface Vision : Configurable, Described {
|
||||
}
|
||||
}
|
||||
|
||||
public fun Vision.asyncNotifyPropertyChange(propertyName: Name) {
|
||||
scope.launch {
|
||||
notifyPropertyChanged(propertyName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 != false),
|
||||
includeStyles = includeStyles ?: (descriptor?.get(name)?.usesStyles == true),
|
||||
includeDefaults = includeDefaults
|
||||
)
|
||||
|
||||
override fun setItem(name: Name, item: MetaItem?): Unit = setProperty(name, item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get [Vision] property using key as a String
|
||||
*/
|
||||
public fun Vision.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? =
|
||||
getProperty(key.toName(), inherit)
|
||||
public fun Vision.getProperty(
|
||||
key: String,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
): MetaItem? = getProperty(key.toName(), inherit, includeStyles, includeDefaults)
|
||||
|
||||
/**
|
||||
* A convenience method to pair [getProperty]
|
||||
*/
|
||||
public fun Vision.setProperty(key: Name, value: Any?) {
|
||||
config[key] = value
|
||||
public fun Vision.setProperty(key: Name, item: Any?) {
|
||||
setProperty(key, MetaItem.of(item))
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to pair [getProperty]
|
||||
*/
|
||||
public fun Vision.setProperty(key: String, value: Any?) {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Control visibility of the element
|
||||
*/
|
||||
public var Vision.visible: Boolean?
|
||||
get() = getProperty(VISIBLE_KEY).boolean
|
||||
set(value) = config.setValue(VISIBLE_KEY, value?.asValue())
|
||||
|
||||
///**
|
||||
// * Convenience delegate for properties
|
||||
// */
|
||||
//public fun Vision.property(
|
||||
// default: MetaItem<*>? = null,
|
||||
// key: Name? = null,
|
||||
// inherit: Boolean = true,
|
||||
//): MutableItemDelegate =
|
||||
// object : ReadWriteProperty<Any?, MetaItem<*>?> {
|
||||
// override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
|
||||
// val name = key ?: property.name.toName()
|
||||
// return getProperty(name, inherit) ?: default
|
||||
// }
|
||||
//
|
||||
// override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
|
||||
// val name = key ?: property.name.toName()
|
||||
// setProperty(name, value)
|
||||
// }
|
||||
// }
|
||||
|
||||
public fun Vision.props(inherit: Boolean = true): MutableItemProvider = object : MutableItemProvider {
|
||||
override fun getItem(name: Name): MetaItem<*>? {
|
||||
return getProperty(name, inherit)
|
||||
}
|
||||
|
||||
override fun setItem(name: Name, item: MetaItem<*>?) {
|
||||
setProperty(name, item)
|
||||
}
|
||||
|
||||
public fun Vision.setProperty(key: String, item: Any?) {
|
||||
setProperty(key.toName(), MetaItem.of(item))
|
||||
}
|
@ -3,108 +3,134 @@ package hep.dataforge.vision
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.descriptors.defaultItem
|
||||
import hep.dataforge.meta.descriptors.defaultMeta
|
||||
import hep.dataforge.meta.descriptors.get
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vision.Vision.Companion.STYLE_KEY
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlin.jvm.Synchronized
|
||||
|
||||
internal data class PropertyListener(
|
||||
val owner: Any? = null,
|
||||
val action: (name: Name) -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* A full base implementation for a [Vision]
|
||||
* @param properties Object own properties excluding styles and inheritance
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("vision")
|
||||
public open class VisionBase : Vision {
|
||||
public open class VisionBase(internal var properties: Config? = null) : Vision {
|
||||
|
||||
init {
|
||||
//used during deserialization only
|
||||
properties?.onChange(this) { name, oldItem, newItem ->
|
||||
if (oldItem != newItem) {
|
||||
scope.launch {
|
||||
notifyPropertyChanged(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transient
|
||||
override var parent: VisionGroup? = null
|
||||
|
||||
/**
|
||||
* Object own properties excluding styles and inheritance
|
||||
*/
|
||||
override var properties: Config? = null
|
||||
protected set
|
||||
override val meta: Meta get() = properties ?: Meta.EMPTY
|
||||
|
||||
override val descriptor: NodeDescriptor? get() = null
|
||||
|
||||
protected fun updateStyles(names: List<String>) {
|
||||
names.mapNotNull { getStyle(it) }.asSequence()
|
||||
.flatMap { it.items.asSequence() }
|
||||
.distinctBy { it.key }
|
||||
.forEach {
|
||||
propertyChanged(it.key.asName())
|
||||
@Synchronized
|
||||
protected fun getOrCreateConfig(): Config {
|
||||
if (properties == null) {
|
||||
val newProperties = Config()
|
||||
newProperties.onChange(this) { name, oldItem, newItem ->
|
||||
if (oldItem != newItem) {
|
||||
scope.launch {
|
||||
notifyPropertyChanged(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
properties = newProperties
|
||||
}
|
||||
return properties!!
|
||||
}
|
||||
|
||||
/**
|
||||
* The config is initialized and assigned on-demand.
|
||||
* To avoid unnecessary allocations, one should access [getAllProperties] via [getProperty] instead.
|
||||
* A fast accessor method to get own property (no inheritance or styles
|
||||
*/
|
||||
override val config: Config by lazy {
|
||||
properties ?: Config().also { config ->
|
||||
properties = config.also {
|
||||
it.onChange(this) { name, _, _ -> propertyChanged(name) }
|
||||
}
|
||||
}
|
||||
override fun getOwnProperty(name: Name): MetaItem? {
|
||||
return properties?.getItem(name)
|
||||
}
|
||||
|
||||
@Transient
|
||||
private val listeners = HashSet<PropertyListener>()
|
||||
|
||||
override fun propertyChanged(name: Name) {
|
||||
if (name == STYLE_KEY) {
|
||||
updateStyles(properties?.get(STYLE_KEY)?.stringList ?: emptyList())
|
||||
}
|
||||
for (listener in listeners) {
|
||||
listener.action(name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPropertyChange(owner: Any?, action: (Name) -> Unit) {
|
||||
listeners.add(PropertyListener(owner, action))
|
||||
}
|
||||
|
||||
override fun removeChangeListener(owner: Any?) {
|
||||
listeners.removeAll { owner == null || it.owner == owner }
|
||||
}
|
||||
|
||||
/**
|
||||
* All available properties in a layered form
|
||||
*/
|
||||
override val allProperties: Laminate
|
||||
get() = Laminate(
|
||||
properties,
|
||||
allStyles,
|
||||
parent?.allProperties,
|
||||
descriptor?.defaultMeta(),
|
||||
)
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
||||
yield(properties?.get(name))
|
||||
override fun getProperty(
|
||||
name: Name,
|
||||
inherit: Boolean,
|
||||
includeStyles: Boolean,
|
||||
includeDefaults: Boolean,
|
||||
): MetaItem? = sequence {
|
||||
yield(getOwnProperty(name))
|
||||
if (includeStyles) {
|
||||
yieldAll(getStyleItems(name))
|
||||
}
|
||||
if (inherit) {
|
||||
yield(parent?.getProperty(name, inherit))
|
||||
yield(parent?.getProperty(name, inherit, includeStyles, includeDefaults))
|
||||
}
|
||||
yield(descriptor?.get(name)?.defaultItem())
|
||||
}.merge()
|
||||
|
||||
/**
|
||||
* Reset all properties to their default values
|
||||
*/
|
||||
public fun resetProperties() {
|
||||
properties?.removeListener(this)
|
||||
properties = null
|
||||
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
|
||||
getOrCreateConfig().setItem(name, item)
|
||||
if (notify) {
|
||||
scope.launch {
|
||||
notifyPropertyChanged(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val descriptor: NodeDescriptor? get() = null
|
||||
|
||||
private suspend fun updateStyles(names: List<String>) {
|
||||
names.mapNotNull { getStyle(it) }.asSequence()
|
||||
.flatMap { it.items.asSequence() }
|
||||
.distinctBy { it.key }
|
||||
.forEach {
|
||||
notifyPropertyChanged(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 onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
|
||||
propertyInvalidationFlow.onEach(callback).launchIn(scope)
|
||||
}
|
||||
|
||||
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||
if (propertyName == STYLE_KEY) {
|
||||
updateStyles(styles)
|
||||
}
|
||||
propertyInvalidationFlow.emit(propertyName)
|
||||
}
|
||||
|
||||
override fun update(change: VisionChange) {
|
||||
change.propertyChange[Name.EMPTY]?.let {
|
||||
config.update(it)
|
||||
change.properties?.let {
|
||||
updateProperties(Name.EMPTY, it.asMetaItem())
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,6 +141,21 @@ public open class VisionBase : Vision {
|
||||
multiple = true
|
||||
}
|
||||
}
|
||||
|
||||
public fun Vision.updateProperties(at: Name, item: MetaItem) {
|
||||
when (item) {
|
||||
is ValueItem -> {
|
||||
if (item.value == Null) {
|
||||
setProperty(at, null)
|
||||
} else
|
||||
setProperty(at, item)
|
||||
}
|
||||
is NodeItem -> item.node.items.forEach { (token, childItem) ->
|
||||
updateProperties(at + token, childItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,14 @@ package hep.dataforge.vision
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.Null
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.serialization.*
|
||||
import kotlin.jvm.Synchronized
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
@ -14,29 +18,42 @@ import kotlin.time.Duration
|
||||
*/
|
||||
public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
|
||||
|
||||
private val propertyChange = HashMap<Name, Config>()
|
||||
private val childrenChange = HashMap<Name, Vision?>()
|
||||
private var reset: Boolean = false
|
||||
private var vision: Vision? = null
|
||||
private val propertyChange = Config()
|
||||
private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
|
||||
|
||||
public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty()
|
||||
public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
|
||||
|
||||
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem<*>?) {
|
||||
propertyChange
|
||||
.getOrPut(visionName) { Config() }
|
||||
.setItem(propertyName, item)
|
||||
@Synchronized
|
||||
private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
|
||||
children.getOrPut(visionName) { VisionChangeBuilder() }
|
||||
|
||||
public fun propertyChanged(visionName: Name, propertyName: Name, item: MetaItem?) {
|
||||
if (visionName == Name.EMPTY) {
|
||||
//Write property removal as [Null]
|
||||
propertyChange[propertyName] = (item ?: Null.asMetaItem())
|
||||
} else {
|
||||
getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun set(name: Name, child: Vision?) {
|
||||
childrenChange[name] = child
|
||||
getOrPutChild(name).apply {
|
||||
vision = child
|
||||
reset = vision == null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Isolate collected changes by creating detached copies of given visions
|
||||
*/
|
||||
public fun isolate(manager: VisionManager): VisionChange = VisionChange(
|
||||
propertyChange.mapValues { it.value.seal() },
|
||||
childrenChange.mapValues { it.value?.isolate(manager) }
|
||||
reset,
|
||||
vision?.isolate(manager),
|
||||
if (propertyChange.isEmpty()) null else propertyChange.seal(),
|
||||
if (children.isEmpty()) null else children.mapValues { it.value.isolate(manager) }
|
||||
)
|
||||
//TODO optimize isolation for visions without parents?
|
||||
}
|
||||
|
||||
private fun Vision.isolate(manager: VisionManager): Vision {
|
||||
@ -47,16 +64,11 @@ private fun Vision.isolate(manager: VisionManager): Vision {
|
||||
|
||||
@Serializable
|
||||
public data class VisionChange(
|
||||
val propertyChange: Map<Name, @Serializable(MetaSerializer::class) Meta>,
|
||||
val childrenChange: Map<Name, Vision?>,
|
||||
) {
|
||||
public fun isEmpty(): Boolean = propertyChange.isEmpty() && childrenChange.isEmpty()
|
||||
|
||||
/**
|
||||
* A shortcut to the top level property dif
|
||||
*/
|
||||
public val properties: Meta? get() = propertyChange[Name.EMPTY]
|
||||
}
|
||||
public val reset: Boolean = false,
|
||||
public val vision: Vision? = null,
|
||||
@Serializable(MetaSerializer::class) public val properties: Meta? = null,
|
||||
public val children: Map<Name, VisionChange>? = null,
|
||||
)
|
||||
|
||||
public inline fun VisionChange(manager: VisionManager, block: VisionChangeBuilder.() -> Unit): VisionChange =
|
||||
VisionChangeBuilder().apply(block).isolate(manager)
|
||||
@ -69,17 +81,10 @@ private fun CoroutineScope.collectChange(
|
||||
) {
|
||||
|
||||
//Collect properties change
|
||||
source.config.onChange(this) { propertyName, oldItem, newItem ->
|
||||
if (oldItem != newItem) {
|
||||
launch {
|
||||
source.onPropertyChange(this) { propertyName ->
|
||||
val newItem = source.getProperty(propertyName, inherit = false, includeStyles = false, includeDefaults = false)
|
||||
collector().propertyChanged(name, propertyName, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coroutineContext[Job]?.invokeOnCompletion {
|
||||
source.config.removeListener(this)
|
||||
}
|
||||
|
||||
if (source is VisionGroup) {
|
||||
//Subscribe for children changes
|
||||
@ -89,17 +94,12 @@ private fun CoroutineScope.collectChange(
|
||||
|
||||
//Subscribe for structure change
|
||||
if (source is MutableVisionGroup) {
|
||||
source.onStructureChange(this) { token, before, after ->
|
||||
before?.removeChangeListener(this)
|
||||
(before as? MutableVisionGroup)?.removeStructureChangeListener(this)
|
||||
source.structureChanges.onEach { (token, _, after) ->
|
||||
if (after != null) {
|
||||
collectChange(name + token, after, collector)
|
||||
}
|
||||
collector()[name + token] = after
|
||||
}
|
||||
coroutineContext[Job]?.invokeOnCompletion {
|
||||
source.removeStructureChangeListener(this)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,7 +111,12 @@ public fun Vision.flowChanges(
|
||||
): Flow<VisionChange> = flow {
|
||||
|
||||
var collector = VisionChangeBuilder()
|
||||
manager.context.collectChange(Name.EMPTY, this@flowChanges) { collector }
|
||||
coroutineScope {
|
||||
collectChange(Name.EMPTY, this@flowChanges) { collector }
|
||||
|
||||
//Send initial vision state
|
||||
val initialChange = VisionChange(vision = isolate(manager))
|
||||
emit(initialChange)
|
||||
|
||||
while (currentCoroutineContext().isActive) {
|
||||
//Wait for changes to accumulate
|
||||
@ -125,3 +130,4 @@ public fun Vision.flowChanges(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.provider.Provider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
public interface VisionContainer<out V : Vision> {
|
||||
public operator fun get(name: Name): V?
|
||||
@ -44,16 +45,6 @@ public interface VisionGroup : Provider, Vision, VisionContainer<Vision> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fix for serialization bug that writes all proper parents inside the tree after deserialization
|
||||
*/
|
||||
public fun attachChildren() {
|
||||
children.values.forEach {
|
||||
it.parent = this
|
||||
(it as? VisionGroup)?.attachChildren()
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public const val STYLE_TARGET: String = "style"
|
||||
}
|
||||
@ -75,19 +66,12 @@ public interface VisionContainerBuilder<in V : Vision> {
|
||||
*/
|
||||
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
|
||||
|
||||
/**
|
||||
* Add listener for children structure change.
|
||||
* @param owner the handler to properly remove listeners
|
||||
* @param action First argument of the action is the name of changed child. Second argument is the new value of the object.
|
||||
*/
|
||||
public fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit)
|
||||
public data class StructureChange(val token: NameToken, val before: Vision?, val after: Vision?)
|
||||
|
||||
/**
|
||||
* Remove children change listener
|
||||
* Flow structure changes of this group. Unconsumed changes are discarded
|
||||
*/
|
||||
public fun removeStructureChangeListener(owner: Any?)
|
||||
|
||||
// public operator fun set(name: Name, child: Vision?)
|
||||
public val structureChanges: Flow<StructureChange>
|
||||
}
|
||||
|
||||
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(str.toName())
|
||||
|
@ -1,7 +1,9 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.configure
|
||||
import hep.dataforge.names.*
|
||||
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
|
||||
@ -9,71 +11,45 @@ import kotlinx.serialization.Transient
|
||||
|
||||
/**
|
||||
* Abstract implementation of mutable group of [Vision]
|
||||
*
|
||||
* @param childrenInternal Internal mutable container for group children
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("vision.group")
|
||||
public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
|
||||
/**
|
||||
* Internal mutable container for group children
|
||||
* TODO made protected due to [https://github.com/Kotlin/kotlinx.serialization/issues/1200]
|
||||
*/
|
||||
@SerialName("children")
|
||||
protected val childrenInternal: MutableMap<NameToken, Vision> = LinkedHashMap()
|
||||
public open class VisionGroupBase(
|
||||
@SerialName("children") internal val childrenInternal: MutableMap<NameToken, Vision> = LinkedHashMap(),
|
||||
) : VisionBase(), MutableVisionGroup {
|
||||
|
||||
/**
|
||||
* A map of top level named children
|
||||
*/
|
||||
override val children: Map<NameToken, Vision> get() = childrenInternal
|
||||
|
||||
override fun propertyChanged(name: Name) {
|
||||
super.propertyChanged(name)
|
||||
for (obj in this) {
|
||||
obj.propertyChanged(name)
|
||||
init {
|
||||
childrenInternal.values.forEach {
|
||||
it.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
private data class StructureChangeListener(
|
||||
val owner: Any?,
|
||||
val callback: (token: NameToken, before: Vision?, after: Vision?) -> Unit,
|
||||
)
|
||||
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||
super.notifyPropertyChanged(propertyName)
|
||||
for (obj in this) {
|
||||
obj.notifyPropertyChanged(propertyName)
|
||||
}
|
||||
}
|
||||
|
||||
@Transient
|
||||
private val structureChangeListeners = HashSet<StructureChangeListener>()
|
||||
private val _structureChanges: MutableSharedFlow<MutableVisionGroup.StructureChange> = MutableSharedFlow()
|
||||
|
||||
/**
|
||||
* Add listener for children change
|
||||
*/
|
||||
override fun onStructureChange(owner: Any?, action: (token: NameToken, before: Vision?, after: Vision?) -> Unit) {
|
||||
structureChangeListeners.add(
|
||||
StructureChangeListener(
|
||||
owner,
|
||||
action
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove children change listener
|
||||
*/
|
||||
override fun removeStructureChangeListener(owner: Any?) {
|
||||
structureChangeListeners.removeAll { owner == null || it.owner === owner }
|
||||
}
|
||||
override val structureChanges: SharedFlow<MutableVisionGroup.StructureChange> get() = _structureChanges
|
||||
|
||||
/**
|
||||
* Propagate children change event upwards
|
||||
*/
|
||||
protected fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
|
||||
structureChangeListeners.forEach { it.callback(name, before, after) }
|
||||
private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
|
||||
scope.launch {
|
||||
_structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child with given name token
|
||||
*/
|
||||
public fun removeChild(token: NameToken): Vision? {
|
||||
val removed = childrenInternal.remove(token)
|
||||
removed?.parent = null
|
||||
return removed
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,14 +67,24 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
/**
|
||||
* Set parent for given child and attach it
|
||||
*/
|
||||
private fun attachChild(token: NameToken, child: Vision) {
|
||||
if (child.parent == null) {
|
||||
private fun attachChild(token: NameToken, child: Vision?) {
|
||||
val before = children[token]
|
||||
when {
|
||||
child == null -> {
|
||||
childrenInternal.remove(token)
|
||||
}
|
||||
child.parent == null -> {
|
||||
child.parent = this
|
||||
childrenInternal[token] = child
|
||||
} else if (child.parent !== this) {
|
||||
}
|
||||
child.parent !== this -> {
|
||||
error("Can't reassign existing parent for $child")
|
||||
}
|
||||
}
|
||||
if (before != child) {
|
||||
childrenChanged(token, before, child)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively create a child group
|
||||
@ -133,14 +119,8 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
}
|
||||
name.length == 1 -> {
|
||||
val token = name.tokens.first()
|
||||
val before = children[token]
|
||||
if (child == null) {
|
||||
removeChild(token)
|
||||
} else {
|
||||
attachChild(token, child)
|
||||
}
|
||||
childrenChanged(token, before, child)
|
||||
}
|
||||
else -> {
|
||||
//TODO add safety check
|
||||
val parent = (get(name.cutLast()) as? MutableVisionGroup) ?: createGroups(name.cutLast())
|
||||
@ -150,18 +130,12 @@ public open class VisionGroupBase : VisionBase(), MutableVisionGroup {
|
||||
}
|
||||
|
||||
override fun update(change: VisionChange) {
|
||||
//update stylesheet
|
||||
// val changeStyleSheet = change.styleSheet
|
||||
// if (changeStyleSheet != null) {
|
||||
// styleSheet {
|
||||
// update(changeStyleSheet)
|
||||
// }
|
||||
// }
|
||||
change.propertyChange.forEach {(childName,configChange)->
|
||||
get(childName)?.configure(configChange)
|
||||
change.children?.forEach { (name, change) ->
|
||||
when {
|
||||
change.reset -> set(name, null)
|
||||
change.vision != null -> set(name, change.vision)
|
||||
else -> get(name)?.update(change)
|
||||
}
|
||||
change.childrenChange.forEach { (name, child) ->
|
||||
set(name, child)
|
||||
}
|
||||
super.update(change)
|
||||
}
|
||||
|
@ -30,16 +30,11 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
serializersModule = this@VisionManager.serializersModule
|
||||
}
|
||||
|
||||
public fun decodeFromString(string: String): Vision = jsonFormat.decodeFromString(visionSerializer, string).also {
|
||||
(it as? VisionGroup)?.attachChildren()
|
||||
}
|
||||
public fun decodeFromString(string: String): Vision = jsonFormat.decodeFromString(visionSerializer, string)
|
||||
|
||||
public fun encodeToString(vision: Vision): String = jsonFormat.encodeToString(visionSerializer, vision)
|
||||
|
||||
public fun decodeFromJson(json: JsonElement): Vision =
|
||||
jsonFormat.decodeFromJsonElement(visionSerializer, json).also {
|
||||
(it as? VisionGroup)?.attachChildren()
|
||||
}
|
||||
public fun decodeFromJson(json: JsonElement): Vision = jsonFormat.decodeFromJsonElement(visionSerializer, json)
|
||||
|
||||
public fun encodeToJsonElement(vision: Vision): JsonElement =
|
||||
jsonFormat.encodeToJsonElement(visionSerializer, vision)
|
||||
|
@ -0,0 +1,18 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.names.Name
|
||||
|
||||
/**
|
||||
* Property containers are used to create a symmetric behaviors for vision properties and style builders
|
||||
*/
|
||||
public interface VisionPropertyContainer<out T> {
|
||||
public fun getProperty(
|
||||
name: Name,
|
||||
inherit: Boolean = false,
|
||||
includeStyles: Boolean = true,
|
||||
includeDefaults: Boolean = true,
|
||||
): MetaItem?
|
||||
|
||||
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
|
||||
}
|
@ -73,7 +73,7 @@ public abstract class VisionTagConsumer<R>(
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
public inline fun <T> TagConsumer<T>.vision(
|
||||
name: String,
|
||||
name: String = DEFAULT_VISION_NAME,
|
||||
visionProvider: VisionOutput.() -> Vision,
|
||||
): T = vision(name.toName(), visionProvider)
|
||||
|
||||
@ -103,5 +103,7 @@ public abstract class VisionTagConsumer<R>(
|
||||
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
||||
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
||||
public const val DEFAULT_ENDPOINT: String = "."
|
||||
|
||||
public const val DEFAULT_VISION_NAME = "vision"
|
||||
}
|
||||
}
|
@ -18,9 +18,10 @@ public fun FlowContent.embedVisionFragment(
|
||||
val consumer = object : VisionTagConsumer<Any?>(consumer, idPrefix) {
|
||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||
script {
|
||||
type = "text/json"
|
||||
attributes["class"] = OUTPUT_DATA_CLASS
|
||||
unsafe {
|
||||
+manager.encodeToString(vision)
|
||||
+"\n${manager.encodeToString(vision)}\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,28 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.Laminate
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.asValue
|
||||
|
||||
@DslMarker
|
||||
public annotation class VisionBuilder
|
||||
|
||||
|
||||
public fun Sequence<MetaItem<*>?>.merge(): MetaItem<*>? {
|
||||
return when (val first = firstOrNull { it != null }) {
|
||||
public fun Sequence<MetaItem?>.merge(): MetaItem? = when (val first = firstOrNull { it != null }) {
|
||||
null -> null
|
||||
is MetaItem.ValueItem -> first //fast search for first entry if it is value
|
||||
is MetaItem.NodeItem -> {
|
||||
is ValueItem -> first //fast search for first entry if it is value
|
||||
is NodeItem -> {
|
||||
//merge nodes if first encountered node is meta
|
||||
val laminate: Laminate = Laminate(mapNotNull { it.node }.toList())
|
||||
MetaItem.NodeItem(laminate)
|
||||
}
|
||||
NodeItem(laminate)
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun <reified E : Enum<E>> NodeDescriptor.enum(key: Name, default: E?): Unit = value(key) {
|
||||
type(ValueType.STRING)
|
||||
default?.let {
|
||||
default(default)
|
||||
}
|
||||
allowedValues = enumValues<E>().map { it.asValue() }
|
||||
}
|
||||
/**
|
||||
* 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))
|
@ -1,27 +0,0 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.ValueDescriptor
|
||||
import hep.dataforge.meta.descriptors.attributes
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget" key of [ValueDescriptor]
|
||||
*/
|
||||
public var ValueDescriptor.widget: Meta
|
||||
get() = attributes["widget"].node ?: Meta.EMPTY
|
||||
set(value) {
|
||||
attributes {
|
||||
set("widget", value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget.type" key of [ValueDescriptor]
|
||||
*/
|
||||
public var ValueDescriptor.widgetType: String?
|
||||
get() = attributes["widget.type"].string
|
||||
set(value) {
|
||||
attributes{
|
||||
set("widget.type", value)
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.ItemDescriptor
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.descriptors.ValueDescriptor
|
||||
import hep.dataforge.meta.descriptors.attributes
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.asValue
|
||||
|
||||
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
|
||||
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"
|
||||
|
||||
public var ItemDescriptor.inherited: Boolean
|
||||
get() = attributes[INHERITED_DESCRIPTOR_ATTRIBUTE].boolean ?: false
|
||||
set(value) = attributes {
|
||||
set(INHERITED_DESCRIPTOR_ATTRIBUTE, value)
|
||||
}
|
||||
|
||||
public var ItemDescriptor.usesStyles: Boolean
|
||||
get() = attributes[STYLE_DESCRIPTOR_ATTRIBUTE].boolean ?: true
|
||||
set(value) = attributes {
|
||||
set(STYLE_DESCRIPTOR_ATTRIBUTE, value)
|
||||
}
|
||||
|
||||
|
||||
public val Vision.describedProperties: Meta
|
||||
get() = Meta {
|
||||
descriptor?.items?.forEach { (key, descriptor) ->
|
||||
key put getProperty(key, inherit = descriptor.inherited)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget" key of [ValueDescriptor]
|
||||
*/
|
||||
public var ValueDescriptor.widget: Meta
|
||||
get() = attributes["widget"].node ?: Meta.EMPTY
|
||||
set(value) {
|
||||
attributes {
|
||||
set("widget", value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget.type" key of [ValueDescriptor]
|
||||
*/
|
||||
public var ValueDescriptor.widgetType: String?
|
||||
get() = attributes["widget.type"].string
|
||||
set(value) {
|
||||
attributes {
|
||||
set("widget.type", value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, this item is hidden in property editor. Default is false
|
||||
*/
|
||||
public val ItemDescriptor.hidden: Boolean
|
||||
get() = attributes["widget.hide"].boolean ?: false
|
||||
|
||||
public fun ItemDescriptor.hide(): Unit = attributes {
|
||||
set("widget.hide", true)
|
||||
}
|
||||
|
||||
|
||||
public inline fun <reified E : Enum<E>> NodeDescriptor.enum(
|
||||
key: Name,
|
||||
default: E?,
|
||||
crossinline modifier: ValueDescriptor.() -> Unit = {},
|
||||
): Unit = value(key) {
|
||||
type(ValueType.STRING)
|
||||
default?.let {
|
||||
default(default)
|
||||
}
|
||||
allowedValues = enumValues<E>().map { it.asValue() }
|
||||
modifier()
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package hep.dataforge.vision.html
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.meta.configure
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.vision.VisionBase
|
||||
import hep.dataforge.vision.configure
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.stream.createHTML
|
||||
import kotlin.test.Test
|
||||
@ -35,7 +35,7 @@ class HtmlTagTest {
|
||||
div {
|
||||
h2 { +"Properties" }
|
||||
ul {
|
||||
vision.properties?.items?.forEach {
|
||||
(vision as? VisionBase)?.meta?.items?.forEach {
|
||||
li {
|
||||
a { +it.key.toString() }
|
||||
p { +it.value.toString() }
|
||||
|
@ -18,6 +18,7 @@ import org.w3c.dom.WebSocket
|
||||
import org.w3c.dom.asList
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.dom.url.URL
|
||||
import kotlin.collections.set
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class VisionClient : AbstractPlugin() {
|
||||
@ -80,15 +81,19 @@ public class VisionClient : AbstractPlugin() {
|
||||
renderVision(element, embeddedVision, outputMeta)
|
||||
}
|
||||
|
||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { attr ->
|
||||
|
||||
val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") {
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
|
||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let {
|
||||
|
||||
val fetchUrl = URL(endpoint).apply {
|
||||
searchParams.append("name", name)
|
||||
URL(endpoint).apply {
|
||||
pathname += "/vision"
|
||||
}
|
||||
} else {
|
||||
URL(attr.value)
|
||||
}.apply {
|
||||
searchParams.append("name", name)
|
||||
}
|
||||
|
||||
logger.info { "Fetching vision data from $fetchUrl" }
|
||||
window.fetch(fetchUrl).then { response ->
|
||||
@ -98,16 +103,22 @@ public class VisionClient : AbstractPlugin() {
|
||||
renderVision(element, vision, outputMeta)
|
||||
}
|
||||
} else {
|
||||
logger.error { "Failed to fetch initial vision state from $endpoint" }
|
||||
logger.error { "Failed to fetch initial vision state from $fetchUrl" }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let {
|
||||
|
||||
val wsUrl = URL(endpoint).apply {
|
||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
||||
val wsUrl = if (attr.value.isBlank() || attr.value == "auto") {
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
URL(endpoint).apply {
|
||||
pathname += "/ws"
|
||||
}
|
||||
} else {
|
||||
URL(attr.value)
|
||||
}.apply {
|
||||
protocol = "ws"
|
||||
searchParams.append("name", name)
|
||||
}
|
||||
@ -118,13 +129,18 @@ public class VisionClient : AbstractPlugin() {
|
||||
onmessage = { messageEvent ->
|
||||
val stringData: String? = messageEvent.data as? String
|
||||
if (stringData != null) {
|
||||
val dif = visionManager.jsonFormat.decodeFromString(
|
||||
val change = visionManager.jsonFormat.decodeFromString(
|
||||
VisionChange.serializer(),
|
||||
stringData
|
||||
)
|
||||
logger.debug { "Got update $dif for output with name $name" }
|
||||
visionMap[element]?.update(dif)
|
||||
?: logger.info { "Target vision for element $element with name $name not found" }
|
||||
|
||||
if (change.vision != null) {
|
||||
renderVision(element, change.vision, outputMeta)
|
||||
}
|
||||
|
||||
logger.debug { "Got update $change for output with name $name" }
|
||||
visionMap[element]?.update(change)
|
||||
?: console.info("Target vision for element $element with name $name not found")
|
||||
} else {
|
||||
console.error("WebSocket message data is not a string")
|
||||
}
|
||||
|
@ -35,30 +35,30 @@ public enum class ResourceLocation {
|
||||
EMBED
|
||||
}
|
||||
|
||||
internal const val DATAFORGE_ASSETS_PATH = ".dataforge/assets"
|
||||
internal const val VISIONFORGE_ASSETS_PATH = ".dataforge/vision/assets"
|
||||
|
||||
|
||||
/**
|
||||
* Check if the asset exists in given local location and put it there if it does not
|
||||
* @param
|
||||
*/
|
||||
internal fun checkOrStoreFile(basePath: Path, filePath: Path, resource: String): Path {
|
||||
val fullPath = basePath.resolveSibling(filePath).toAbsolutePath()
|
||||
internal fun checkOrStoreFile(htmlPath: Path, filePath: Path, resource: String): Path {
|
||||
val fullPath = htmlPath.resolveSibling(filePath).toAbsolutePath().resolve(resource)
|
||||
|
||||
if (Files.exists(fullPath)) {
|
||||
//TODO checksum
|
||||
} else {
|
||||
//TODO add logging
|
||||
|
||||
val bytes = VisionManager::class.java.getResourceAsStream(resource).readAllBytes()
|
||||
val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes()
|
||||
Files.createDirectories(fullPath.parent)
|
||||
Files.write(fullPath, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
|
||||
}
|
||||
|
||||
return if (basePath.isAbsolute && fullPath.startsWith(basePath)) {
|
||||
basePath.relativize(fullPath)
|
||||
return if (htmlPath.isAbsolute && fullPath.startsWith(htmlPath.parent)) {
|
||||
htmlPath.parent.relativize(fullPath)
|
||||
} else {
|
||||
filePath
|
||||
fullPath
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ internal fun embedScriptHeader(resource: String): HtmlFragment = {
|
||||
script {
|
||||
type = "text/javascript"
|
||||
unsafe {
|
||||
val bytes = VisionManager::class.java.getResourceAsStream(resource).readAllBytes()
|
||||
val bytes = VisionManager::class.java.getResourceAsStream("/$resource").readAllBytes()
|
||||
+bytes.toString(Charsets.UTF_8)
|
||||
}
|
||||
}
|
||||
@ -100,20 +100,20 @@ internal fun fileCssHeader(
|
||||
* Make a script header, automatically copying file to appropriate location
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun Context.Companion.scriptHeader(
|
||||
public fun Context.scriptHeader(
|
||||
scriptResource: String,
|
||||
basePath: Path,
|
||||
htmlPath: Path,
|
||||
resourceLocation: ResourceLocation,
|
||||
): HtmlFragment {
|
||||
val targetPath = when (resourceLocation) {
|
||||
ResourceLocation.LOCAL -> checkOrStoreFile(
|
||||
basePath,
|
||||
Path.of(DATAFORGE_ASSETS_PATH),
|
||||
htmlPath,
|
||||
Path.of(VISIONFORGE_ASSETS_PATH),
|
||||
scriptResource
|
||||
)
|
||||
ResourceLocation.SYSTEM -> checkOrStoreFile(
|
||||
Path.of("."),
|
||||
Path.of(System.getProperty("user.home")).resolve(DATAFORGE_ASSETS_PATH),
|
||||
Path.of(System.getProperty("user.home")).resolve(VISIONFORGE_ASSETS_PATH),
|
||||
scriptResource
|
||||
)
|
||||
ResourceLocation.EMBED -> null
|
||||
|
@ -1,9 +1,16 @@
|
||||
package hep.dataforge.vision
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.vision.html.*
|
||||
import kotlinx.html.*
|
||||
import hep.dataforge.vision.html.HtmlFragment
|
||||
import hep.dataforge.vision.html.HtmlVisionFragment
|
||||
import hep.dataforge.vision.html.embedVisionFragment
|
||||
import hep.dataforge.vision.html.fragment
|
||||
import kotlinx.html.body
|
||||
import kotlinx.html.head
|
||||
import kotlinx.html.meta
|
||||
import kotlinx.html.stream.createHTML
|
||||
import kotlinx.html.title
|
||||
import java.awt.Desktop
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
@ -13,27 +20,27 @@ import java.nio.file.Path
|
||||
* Make a file with the embedded vision data
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun HtmlVisionFragment.makeFile(
|
||||
manager: VisionManager,
|
||||
vararg headers: HtmlFragment,
|
||||
public fun Context.makeVisionFile(
|
||||
fragment: HtmlVisionFragment,
|
||||
path: Path? = null,
|
||||
title: String = "VisionForge page",
|
||||
show: Boolean = true,
|
||||
headerBuilder: (Path) -> HtmlFragment,
|
||||
) {
|
||||
val actualFile = path ?: Files.createTempFile("tempPlot", ".html")
|
||||
Files.createDirectories(actualFile.parent)
|
||||
val actualFile = path?.let {
|
||||
Path.of(System.getProperty("user.home")).resolve(path)
|
||||
} ?: Files.createTempFile("tempPlot", ".html")
|
||||
//Files.createDirectories(actualFile.parent)
|
||||
val htmlString = createHTML().apply {
|
||||
head {
|
||||
meta {
|
||||
charset = "utf-8"
|
||||
headers.forEach {
|
||||
fragment(it)
|
||||
}
|
||||
fragment(headerBuilder(actualFile))
|
||||
}
|
||||
title(title)
|
||||
}
|
||||
body {
|
||||
embedVisionFragment(manager, fragment = this@makeFile)
|
||||
embedVisionFragment(visionManager, fragment = fragment)
|
||||
}
|
||||
}.finalize()
|
||||
|
||||
|
@ -18,7 +18,7 @@ import tornadofx.*
|
||||
/**
|
||||
* A display for meta and descriptor
|
||||
*/
|
||||
sealed class FXMeta<M : MetaNode<M>> : Comparable<FXMeta<*>> {
|
||||
sealed class FXMeta<M : TypedMeta<M>> : Comparable<FXMeta<*>> {
|
||||
abstract val name: NameToken
|
||||
abstract val parent: FXMetaNode<M>?
|
||||
abstract val descriptionProperty: ObservableStringValue
|
||||
@ -35,7 +35,7 @@ sealed class FXMeta<M : MetaNode<M>> : Comparable<FXMeta<*>> {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <M : MetaNode<M>> root(
|
||||
fun <M : TypedMeta<M>> root(
|
||||
node: M,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
rootName: String = "root"
|
||||
@ -47,7 +47,7 @@ sealed class FXMeta<M : MetaNode<M>> : Comparable<FXMeta<*>> {
|
||||
}
|
||||
}
|
||||
|
||||
class FXMetaNode<M : MetaNode<M>>(
|
||||
class FXMetaNode<M : TypedMeta<M>>(
|
||||
override val name: NameToken,
|
||||
override val parent: FXMetaNode<M>?,
|
||||
nodeValue: M? = null,
|
||||
@ -89,7 +89,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
||||
init {
|
||||
bind(nodeProperty, descriptorProperty)
|
||||
|
||||
val listener: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit = { name, _, _ ->
|
||||
val listener: (Name, MetaItem?, MetaItem?) -> Unit = { name, _, _ ->
|
||||
if (name.length == 1) invalidate()
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
||||
val actualItem = node?.items?.get(token)
|
||||
val actualDescriptor = descriptor?.items?.get(token.body)
|
||||
|
||||
if (actualItem is MetaItem.NodeItem || actualDescriptor is NodeDescriptor) {
|
||||
if (actualItem is NodeItem || actualDescriptor is NodeDescriptor) {
|
||||
FXMetaNode(token, this@FXMetaNode)
|
||||
} else {
|
||||
FXMetaValue(token, this@FXMetaNode)
|
||||
@ -134,7 +134,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
||||
}
|
||||
}
|
||||
|
||||
public class FXMetaValue<M : MetaNode<M>>(
|
||||
public class FXMetaValue<M : TypedMeta<M>>(
|
||||
override val name: NameToken,
|
||||
override val parent: FXMetaNode<M>
|
||||
) : FXMeta<M>() {
|
||||
@ -151,10 +151,10 @@ public class FXMetaValue<M : MetaNode<M>>(
|
||||
//private val innerValueProperty = SimpleObjectProperty(value)
|
||||
|
||||
public val valueProperty = descriptorProperty.objectBinding { descriptor ->
|
||||
parent.node[name].value ?: descriptor?.default
|
||||
parent.node?.get(name).value ?: descriptor?.default
|
||||
}
|
||||
|
||||
override val hasValue: ObservableBooleanValue = parent.nodeProperty.booleanBinding { it[name] != null }
|
||||
override val hasValue: ObservableBooleanValue = parent.nodeProperty.booleanBinding { it?.get(name) != null }
|
||||
|
||||
public val value by valueProperty
|
||||
|
||||
@ -169,12 +169,12 @@ public fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
|
||||
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() }.max() ?: -1) + 1
|
||||
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.setNode(token.asName(), Meta.EMPTY)
|
||||
this.set(token.asName(), Meta.EMPTY)
|
||||
//FIXME possible concurrency bug
|
||||
get(token).node!!
|
||||
}
|
||||
|
@ -1,12 +1,8 @@
|
||||
package hep.dataforge.vision.editor
|
||||
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.getStyle
|
||||
import hep.dataforge.vision.setProperty
|
||||
import hep.dataforge.vision.*
|
||||
import javafx.beans.binding.Binding
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.Node
|
||||
@ -23,8 +19,8 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() {
|
||||
constructor(
|
||||
item: Vision?,
|
||||
descriptor: NodeDescriptor?,
|
||||
selector: (Vision) -> Config = { it.config }
|
||||
) : this(selector) {
|
||||
selector: (Vision) -> MutableItemProvider = { it.allProperties() },
|
||||
) : this({ it.describedProperties }) {
|
||||
this.item = item
|
||||
this.descriptorProperty.set(descriptor)
|
||||
}
|
||||
|
@ -43,9 +43,9 @@ class FX3DPlugin : AbstractPlugin() {
|
||||
}
|
||||
|
||||
fun buildNode(obj: Solid): Node {
|
||||
val binding = VisualObjectFXBinding(obj)
|
||||
val binding = VisualObjectFXBinding(this, obj)
|
||||
return when (obj) {
|
||||
is SolidReference -> referenceFactory(obj, binding)
|
||||
is SolidReferenceGroup -> referenceFactory(obj, binding)
|
||||
is SolidGroup -> {
|
||||
Group(obj.children.mapNotNull { (token, obj) ->
|
||||
(obj as? Solid)?.let {
|
||||
@ -71,7 +71,7 @@ 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)?.color()
|
||||
obj.getProperty(SolidMaterial.MATERIAL_COLOR_KEY, inherit = true)?.color()
|
||||
).apply {
|
||||
this.meshView.cullFace = CullFace.FRONT
|
||||
}
|
||||
@ -130,7 +130,7 @@ class FX3DPlugin : AbstractPlugin() {
|
||||
}
|
||||
|
||||
companion object : PluginFactory<FX3DPlugin> {
|
||||
override val tag = PluginTag("visual.fx3D", PluginTag.DATAFORGE_GROUP)
|
||||
override val tag = PluginTag("vision.fx3D", PluginTag.DATAFORGE_GROUP)
|
||||
override val type = FX3DPlugin::class
|
||||
override fun invoke(meta: Meta, context: Context) = FX3DPlugin()
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.double
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.int
|
||||
import hep.dataforge.values.string
|
||||
@ -35,9 +32,9 @@ public object FXMaterials {
|
||||
* Infer color based on meta item
|
||||
* @param opacity default opacity
|
||||
*/
|
||||
public fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
||||
public fun MetaItem.color(opacity: Double = 1.0): Color {
|
||||
return when (this) {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||
is ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||
val int = value.int
|
||||
val red = int and 0x00ff0000 shr 16
|
||||
val green = int and 0x0000ff00 shr 8
|
||||
@ -46,7 +43,7 @@ public fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
||||
} else {
|
||||
Color.web(this.value.string)
|
||||
}
|
||||
is MetaItem.NodeItem -> {
|
||||
is NodeItem -> {
|
||||
Color.rgb(
|
||||
node[Colors.RED_KEY]?.int ?: 0,
|
||||
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||
@ -60,11 +57,11 @@ public fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
||||
/**
|
||||
* Infer FX material based on meta item
|
||||
*/
|
||||
public fun MetaItem<*>?.material(): Material {
|
||||
public fun MetaItem?.material(): Material {
|
||||
return when (this) {
|
||||
null -> FXMaterials.GREY
|
||||
is MetaItem.ValueItem -> PhongMaterial(color())
|
||||
is MetaItem.NodeItem -> PhongMaterial().apply {
|
||||
is ValueItem -> PhongMaterial(color())
|
||||
is NodeItem -> 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
|
||||
|
@ -6,15 +6,15 @@ import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReference> {
|
||||
override val type: KClass<in SolidReference> get() = SolidReference::class
|
||||
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
|
||||
override val type: KClass<in SolidReferenceGroup> get() = SolidReferenceGroup::class
|
||||
|
||||
override fun invoke(obj: SolidReference, binding: VisualObjectFXBinding): Node {
|
||||
override fun invoke(obj: SolidReferenceGroup, binding: VisualObjectFXBinding): Node {
|
||||
val prototype = obj.prototype
|
||||
val node = plugin.buildNode(prototype)
|
||||
|
||||
obj.onPropertyChange(this) { name->
|
||||
if (name.firstOrNull()?.body == SolidReference.REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||
obj.onPropertyChange(plugin.context) { name->
|
||||
if (name.firstOrNull()?.body == SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||
val propertyName = name.cutFirst()
|
||||
val referenceChild = obj[childName] ?: error("Reference child with name '$childName' not found")
|
||||
|
@ -12,11 +12,11 @@ import tornadofx.*
|
||||
/**
|
||||
* A caching binding collection for [Vision] properties
|
||||
*/
|
||||
class VisualObjectFXBinding(val obj: Vision) {
|
||||
private val bindings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
|
||||
class VisualObjectFXBinding(val fx: FX3DPlugin, val obj: Vision) {
|
||||
private val bindings = HashMap<Name, ObjectBinding<MetaItem?>>()
|
||||
|
||||
init {
|
||||
obj.onPropertyChange(this) { name ->
|
||||
obj.onPropertyChange(fx.context) { name ->
|
||||
bindings.filter { it.key.startsWith(name) }.forEach { entry ->
|
||||
Platform.runLater {
|
||||
entry.value.invalidate()
|
||||
@ -31,10 +31,10 @@ class VisualObjectFXBinding(val obj: Vision) {
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
||||
operator fun get(key: Name): ObjectBinding<MetaItem?> {
|
||||
return bindings.getOrPut(key) {
|
||||
object : ObjectBinding<MetaItem<*>?>() {
|
||||
override fun computeValue(): MetaItem<*>? = obj.getProperty(key)
|
||||
object : ObjectBinding<MetaItem?>() {
|
||||
override fun computeValue(): MetaItem? = obj.getProperty(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,19 +42,19 @@ class VisualObjectFXBinding(val obj: Vision) {
|
||||
operator fun get(key: String) = get(key.toName())
|
||||
}
|
||||
|
||||
fun ObjectBinding<MetaItem<*>?>.value() = objectBinding { it.value }
|
||||
fun ObjectBinding<MetaItem<*>?>.string() = stringBinding { it.string }
|
||||
fun ObjectBinding<MetaItem<*>?>.number() = objectBinding { it.number }
|
||||
fun ObjectBinding<MetaItem<*>?>.double() = objectBinding { it.double }
|
||||
fun ObjectBinding<MetaItem<*>?>.float() = objectBinding { it.float }
|
||||
fun ObjectBinding<MetaItem<*>?>.int() = objectBinding { it.int }
|
||||
fun ObjectBinding<MetaItem<*>?>.long() = objectBinding { it.long }
|
||||
fun ObjectBinding<MetaItem<*>?>.node() = objectBinding { it.node }
|
||||
fun ObjectBinding<MetaItem?>.value() = objectBinding { it.value }
|
||||
fun ObjectBinding<MetaItem?>.string() = stringBinding { it.string }
|
||||
fun ObjectBinding<MetaItem?>.number() = objectBinding { it.number }
|
||||
fun ObjectBinding<MetaItem?>.double() = objectBinding { it.double }
|
||||
fun ObjectBinding<MetaItem?>.float() = objectBinding { it.float }
|
||||
fun ObjectBinding<MetaItem?>.int() = objectBinding { it.int }
|
||||
fun ObjectBinding<MetaItem?>.long() = objectBinding { it.long }
|
||||
fun ObjectBinding<MetaItem?>.node() = objectBinding { it.node }
|
||||
|
||||
fun ObjectBinding<MetaItem<*>?>.string(default: String) = stringBinding { it.string ?: default }
|
||||
fun ObjectBinding<MetaItem<*>?>.double(default: Double) = doubleBinding { it.double ?: default }
|
||||
fun ObjectBinding<MetaItem<*>?>.float(default: Float) = floatBinding { it.float ?: default }
|
||||
fun ObjectBinding<MetaItem<*>?>.int(default: Int) = integerBinding { it.int ?: default }
|
||||
fun ObjectBinding<MetaItem<*>?>.long(default: Long) = longBinding { it.long ?: default }
|
||||
fun ObjectBinding<MetaItem?>.string(default: String) = stringBinding { it.string ?: default }
|
||||
fun ObjectBinding<MetaItem?>.double(default: Double) = doubleBinding { it.double ?: default }
|
||||
fun ObjectBinding<MetaItem?>.float(default: Float) = floatBinding { it.float ?: default }
|
||||
fun ObjectBinding<MetaItem?>.int(default: Int) = integerBinding { it.int ?: default }
|
||||
fun ObjectBinding<MetaItem?>.long(default: Long) = longBinding { it.long ?: default }
|
||||
|
||||
fun <T> ObjectBinding<MetaItem<*>?>.transform(transform: (MetaItem<*>) -> T) = objectBinding { it?.let(transform) }
|
||||
fun <T> ObjectBinding<MetaItem?>.transform(transform: (MetaItem) -> T) = objectBinding { it?.let(transform) }
|
||||
|
@ -2,12 +2,12 @@ package hep.dataforge.vision.gdml
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.set
|
||||
import hep.dataforge.vision.setProperty
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
||||
import hep.dataforge.vision.styleSheet
|
||||
@ -38,7 +38,6 @@ public class GDMLTransformerSettings {
|
||||
|
||||
public var solidAction: (GDMLSolid) -> Action = { Action.PROTOTYPE }
|
||||
public var volumeAction: (GDMLGroup) -> Action = { Action.PROTOTYPE }
|
||||
|
||||
}
|
||||
|
||||
private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
||||
@ -51,13 +50,13 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
||||
private val proto = SolidGroup()
|
||||
|
||||
private val solids = proto.group(solidsName) {
|
||||
config["edges.enabled"] = false
|
||||
setProperty("edges.enabled", false)
|
||||
}
|
||||
|
||||
|
||||
private val referenceStore = HashMap<Name, MutableList<SolidReference>>()
|
||||
private val referenceStore = HashMap<Name, MutableList<SolidReferenceGroup>>()
|
||||
|
||||
private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReference {
|
||||
private fun proxySolid(root: GDML, group: SolidGroup, solid: GDMLSolid, name: String): SolidReferenceGroup {
|
||||
val templateName = solidsName + name
|
||||
if (proto[templateName] == null) {
|
||||
solids.addSolid(root, solid, name)
|
||||
@ -67,7 +66,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
||||
return ref
|
||||
}
|
||||
|
||||
private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReference {
|
||||
private fun proxyVolume(root: GDML, group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): SolidReferenceGroup {
|
||||
val templateName = volumesName + volume.name.asName()
|
||||
if (proto[templateName] == null) {
|
||||
proto[templateName] = volume(root, volume)
|
||||
@ -324,7 +323,7 @@ private class GDMLTransformer(val settings: GDMLTransformerSettings) {
|
||||
}
|
||||
}
|
||||
|
||||
fun finalize(final: SolidGroup): SolidGroup {
|
||||
private fun finalize(final: SolidGroup): SolidGroup {
|
||||
//final.prototypes = proto
|
||||
final.useStyle("GDML") {
|
||||
Solid.ROTATION_ORDER_KEY put RotationOrder.ZXY
|
||||
|
@ -1,17 +1,9 @@
|
||||
package hep.dataforge.vision.gdml
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.meta.sequence
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.*
|
||||
import hep.dataforge.meta.itemSequence
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.visitor.VisionVisitor
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import mu.KotlinLogging
|
||||
|
||||
public expect class Counter() {
|
||||
public fun get(): Int
|
||||
@ -24,6 +16,7 @@ private fun Point3D?.safePlus(other: Point3D?): Point3D? = if (this == null && o
|
||||
(this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0))
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
internal fun Vision.updateFrom(other: Vision): Vision {
|
||||
if (this is Solid && other is Solid) {
|
||||
position = position.safePlus(other.position)
|
||||
@ -33,9 +26,9 @@ internal fun Vision.updateFrom(other: Vision): Vision {
|
||||
scaleY = scaleY.toDouble() * other.scaleY.toDouble()
|
||||
scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble()
|
||||
}
|
||||
other.properties?.sequence()?.forEach { (name, item) ->
|
||||
if (properties?.getItem(name) == null) {
|
||||
config[name] = item
|
||||
other.meta.itemSequence().forEach { (name, item) ->
|
||||
if (getProperty(name) == null) {
|
||||
setProperty(name, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
visionforge-plotly/build.gradle.kts
Normal file
20
visionforge-plotly/build.gradle.kts
Normal file
@ -0,0 +1,20 @@
|
||||
plugins {
|
||||
id("ru.mipt.npm.mpp")
|
||||
}
|
||||
|
||||
kscience {
|
||||
useSerialization()
|
||||
}
|
||||
|
||||
val plotlyVersion = "0.3.1-dev"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project(":visionforge-core"))
|
||||
api("kscience.plotlykt:plotlykt-core:${plotlyVersion}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package hep.dataforge.vision.plotly
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.vision.VisionBase
|
||||
import hep.dataforge.vision.html.VisionOutput
|
||||
import kscience.plotly.Plot
|
||||
import kscience.plotly.Plotly
|
||||
|
||||
public class VisionOfPlotly(public val plot: Plot): VisionBase(plot.config)
|
||||
|
||||
@DFExperimental
|
||||
public inline fun VisionOutput.plotly(block: Plot.() -> Unit): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block))
|
@ -0,0 +1,33 @@
|
||||
package hep.dataforge.vision.plotly
|
||||
|
||||
import hep.dataforge.context.AbstractPlugin
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.PluginFactory
|
||||
import hep.dataforge.context.PluginTag
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.client.ElementVisionRenderer
|
||||
import kscience.plotly.PlotlyConfig
|
||||
import kscience.plotly.plot
|
||||
import org.w3c.dom.Element
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class PlotlyPlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
override fun rateVision(vision: Vision): Int =
|
||||
if (vision is VisionOfPlotly) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
|
||||
|
||||
override fun render(element: Element, vision: Vision, meta: Meta) {
|
||||
val plot = (vision as? VisionOfPlotly)?.plot ?: error("Only VisionOfPlotly visions are supported")
|
||||
val config = PlotlyConfig.read(meta)
|
||||
element.plot(plot, config)
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<PlotlyPlugin> {
|
||||
override val tag: PluginTag = PluginTag("vision.plotly", PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<PlotlyPlugin> = PlotlyPlugin::class
|
||||
override fun invoke(meta: Meta, context: Context): PlotlyPlugin = PlotlyPlugin()
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package hep.dataforge.vision.plotly
|
||||
|
||||
//public fun main() {
|
||||
// val visionContext: Context = Global.context("vision-client")
|
||||
//
|
||||
// //Loading three-js renderer
|
||||
// val threePlugin = visionContext.plugins.fetch(PlotlyPlugin)
|
||||
//
|
||||
// val clientManager = visionContext.plugins.fetch(VisionClient)
|
||||
//
|
||||
// //Fetch from server and render visions for all outputs
|
||||
// window.onload = {
|
||||
// clientManager.renderAllVisions()
|
||||
// }
|
||||
//}
|
@ -41,6 +41,13 @@ import java.awt.Desktop
|
||||
import java.net.URI
|
||||
import kotlin.time.milliseconds
|
||||
|
||||
public enum class VisionServerDataMode {
|
||||
EMBED,
|
||||
FETCH,
|
||||
CONNECT
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A ktor plugin container with given [routing]
|
||||
*/
|
||||
@ -52,6 +59,7 @@ public class VisionServer internal constructor(
|
||||
override val config: Config = Config()
|
||||
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
|
||||
public var cacheFragments: Boolean by config.boolean(true)
|
||||
public var dataMode: VisionServerDataMode = VisionServerDataMode.CONNECT
|
||||
|
||||
/**
|
||||
* a list of headers that should be applied to all pages
|
||||
@ -72,10 +80,23 @@ public class VisionServer internal constructor(
|
||||
val consumer = object : VisionTagConsumer<Any?>(consumer) {
|
||||
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
|
||||
visionMap[name] = vision
|
||||
|
||||
// Toggle updates
|
||||
attributes[OUTPUT_FETCH_ATTRIBUTE] = "true"
|
||||
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "true"
|
||||
// Toggle update mode
|
||||
when (dataMode) {
|
||||
VisionServerDataMode.EMBED -> {
|
||||
script {
|
||||
attributes["class"] = OUTPUT_DATA_CLASS
|
||||
unsafe {
|
||||
+visionManager.encodeToString(vision)
|
||||
}
|
||||
}
|
||||
}
|
||||
VisionServerDataMode.FETCH -> {
|
||||
attributes[OUTPUT_FETCH_ATTRIBUTE] = "auto"
|
||||
}
|
||||
VisionServerDataMode.CONNECT -> {
|
||||
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,10 +131,11 @@ public class VisionServer internal constructor(
|
||||
|
||||
application.log.debug("Opened server socket for $name")
|
||||
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered")
|
||||
|
||||
try {
|
||||
withContext(visionManager.context.coroutineContext) {
|
||||
vision.flowChanges(visionManager, updateInterval.milliseconds).collect { update ->
|
||||
val json = VisionManager.defaultJson.encodeToString(
|
||||
val json = visionManager.jsonFormat.encodeToString(
|
||||
VisionChange.serializer(),
|
||||
update
|
||||
)
|
||||
|
@ -0,0 +1,51 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.meta.MutableItemProvider
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.meta.value
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.values.string
|
||||
import hep.dataforge.vision.Colors
|
||||
import hep.dataforge.vision.VisionBuilder
|
||||
|
||||
@VisionBuilder
|
||||
public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) {
|
||||
public var value: Value?
|
||||
get() = parent.getItem(colorKey).value
|
||||
set(value) {
|
||||
parent[colorKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
public var ColorAccessor?.string: String?
|
||||
get() = this?.value?.string
|
||||
set(value) {
|
||||
this?.value = value?.asValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set [webcolor](https://en.wikipedia.org/wiki/Web_colors) as string
|
||||
*/
|
||||
public operator fun ColorAccessor?.invoke(webColor: String) {
|
||||
this?.value = webColor.asValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color as RGB integer
|
||||
*/
|
||||
public operator fun ColorAccessor?.invoke(rgb: Int) {
|
||||
this?.value = Colors.rgbToString(rgb).asValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color as RGB
|
||||
*/
|
||||
public operator fun ColorAccessor?.invoke(r: UByte, g: UByte, b: UByte) {
|
||||
this?.value = Colors.rgbToString(r, g, b).asValue()
|
||||
}
|
||||
|
||||
public fun ColorAccessor?.clear() {
|
||||
this?.value = null
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.meta.update
|
||||
@ -18,7 +17,7 @@ public enum class CompositeType {
|
||||
public class Composite(
|
||||
public val compositeType: CompositeType,
|
||||
public val first: Solid,
|
||||
public val second: Solid
|
||||
public val second: Solid,
|
||||
) : SolidBase(), Solid, VisionGroup {
|
||||
|
||||
init {
|
||||
@ -34,25 +33,25 @@ public class Composite(
|
||||
public inline fun VisionContainerBuilder<Solid>.composite(
|
||||
type: CompositeType,
|
||||
name: String = "",
|
||||
builder: SolidGroup.() -> Unit
|
||||
builder: SolidGroup.() -> Unit,
|
||||
): Composite {
|
||||
val group = SolidGroup().apply(builder)
|
||||
val children = group.children.values.filterIsInstance<Solid>()
|
||||
if (children.size != 2) error("Composite requires exactly two children")
|
||||
return Composite(type, children[0], children[1]).also {
|
||||
it.config.update(group.config)
|
||||
//it.material = group.material
|
||||
|
||||
return Composite(type, children[0], children[1]).also { composite ->
|
||||
composite.configure {
|
||||
update(group.meta)
|
||||
}
|
||||
if (group.position != null) {
|
||||
it.position = group.position
|
||||
composite.position = group.position
|
||||
}
|
||||
if (group.rotation != null) {
|
||||
it.rotation = group.rotation
|
||||
composite.rotation = group.rotation
|
||||
}
|
||||
if (group.scale != null) {
|
||||
it.scale = group.scale
|
||||
composite.scale = group.scale
|
||||
}
|
||||
set(name, it)
|
||||
set(name, composite)
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,5 +64,8 @@ public inline fun VisionContainerBuilder<Solid>.subtract(name: String = "", buil
|
||||
composite(CompositeType.SUBTRACT, name, builder = builder)
|
||||
|
||||
@VisionBuilder
|
||||
public inline fun VisionContainerBuilder<Solid>.intersect(name: String = "", builder: SolidGroup.() -> Unit): Composite =
|
||||
public inline fun VisionContainerBuilder<Solid>.intersect(
|
||||
name: String = "",
|
||||
builder: SolidGroup.() -> Unit,
|
||||
): Composite =
|
||||
composite(CompositeType.INTERSECT, name, builder = builder)
|
@ -6,7 +6,7 @@ import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vision.VisionBuilder
|
||||
import hep.dataforge.vision.VisionContainerBuilder
|
||||
import hep.dataforge.vision.props
|
||||
import hep.dataforge.vision.allProperties
|
||||
import hep.dataforge.vision.set
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -16,7 +16,7 @@ import kotlinx.serialization.Serializable
|
||||
public class PolyLine(public var points: List<Point3D>) : SolidBase(), Solid {
|
||||
|
||||
//var lineType by string()
|
||||
public var thickness: Number by props().number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)
|
||||
public var thickness: Number by allProperties(inherit = false).number(1.0, key = SolidMaterial.MATERIAL_KEY + THICKNESS_KEY)
|
||||
|
||||
public companion object {
|
||||
public val THICKNESS_KEY: Name = "thickness".asName()
|
||||
|
@ -1,18 +1,17 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.boolean
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.enum
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.*
|
||||
import hep.dataforge.vision.Vision.Companion.VISIBLE_KEY
|
||||
import hep.dataforge.vision.VisionBuilder
|
||||
import hep.dataforge.vision.enum
|
||||
import hep.dataforge.vision.layout.Output
|
||||
import hep.dataforge.vision.setProperty
|
||||
import hep.dataforge.vision.solid.Solid.Companion.DETAIL_KEY
|
||||
import hep.dataforge.vision.solid.Solid.Companion.IGNORE_KEY
|
||||
import hep.dataforge.vision.solid.Solid.Companion.LAYER_KEY
|
||||
@ -45,13 +44,13 @@ public interface Solid : Vision {
|
||||
public val Y_POSITION_KEY: Name = POSITION_KEY + Y_KEY
|
||||
public val Z_POSITION_KEY: Name = POSITION_KEY + Z_KEY
|
||||
|
||||
public val ROTATION: Name = "rotation".asName()
|
||||
public val ROTATION_KEY: Name = "rotation".asName()
|
||||
|
||||
public val X_ROTATION_KEY: Name = ROTATION + X_KEY
|
||||
public val Y_ROTATION_KEY: Name = ROTATION + Y_KEY
|
||||
public val Z_ROTATION_KEY: Name = ROTATION + Z_KEY
|
||||
public val X_ROTATION_KEY: Name = ROTATION_KEY + X_KEY
|
||||
public val Y_ROTATION_KEY: Name = ROTATION_KEY + Y_KEY
|
||||
public val Z_ROTATION_KEY: Name = ROTATION_KEY + Z_KEY
|
||||
|
||||
public val ROTATION_ORDER_KEY: Name = ROTATION + "order"
|
||||
public val ROTATION_ORDER_KEY: Name = ROTATION_KEY + "order"
|
||||
|
||||
public val SCALE_KEY: Name = "scale".asName()
|
||||
|
||||
@ -62,6 +61,7 @@ public interface Solid : Vision {
|
||||
public val descriptor: NodeDescriptor by lazy {
|
||||
NodeDescriptor {
|
||||
value(VISIBLE_KEY) {
|
||||
inherited = false
|
||||
type(ValueType.BOOLEAN)
|
||||
default(true)
|
||||
}
|
||||
@ -70,11 +70,14 @@ public interface Solid : Vision {
|
||||
value(Vision.STYLE_KEY) {
|
||||
type(ValueType.STRING)
|
||||
multiple = true
|
||||
hide()
|
||||
}
|
||||
|
||||
item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor)
|
||||
|
||||
enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ)
|
||||
enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +85,7 @@ public interface Solid : Vision {
|
||||
if (first.position != second.position) return false
|
||||
if (first.rotation != second.rotation) return false
|
||||
if (first.scale != second.scale) return false
|
||||
if (first.properties != second.properties) return false
|
||||
if (first.meta != second.meta) return false
|
||||
return true
|
||||
}
|
||||
|
||||
@ -90,7 +93,7 @@ public interface Solid : Vision {
|
||||
var result = +(solid.position?.hashCode() ?: 0)
|
||||
result = 31 * result + (solid.rotation?.hashCode() ?: 0)
|
||||
result = 31 * result + (solid.scale?.hashCode() ?: 0)
|
||||
result = 31 * result + (solid.properties?.hashCode() ?: 0)
|
||||
result = 31 * result + solid.allProperties().hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
@ -100,9 +103,9 @@ public interface Solid : Vision {
|
||||
* Get the layer number this solid belongs to. Return 0 if layer is not defined.
|
||||
*/
|
||||
public var Solid.layer: Int
|
||||
get() = properties?.getItem(LAYER_KEY).int ?: 0
|
||||
get() = allProperties().getItem(LAYER_KEY).int ?: 0
|
||||
set(value) {
|
||||
config[LAYER_KEY] = value.asValue()
|
||||
setProperty(LAYER_KEY, value)
|
||||
}
|
||||
|
||||
@VisionBuilder
|
||||
@ -153,21 +156,21 @@ public var Solid.x: Number
|
||||
get() = position?.x ?: 0f
|
||||
set(value) {
|
||||
position().x = value.toDouble()
|
||||
propertyChanged(Solid.X_POSITION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.X_POSITION_KEY)
|
||||
}
|
||||
|
||||
public var Solid.y: Number
|
||||
get() = position?.y ?: 0f
|
||||
set(value) {
|
||||
position().y = value.toDouble()
|
||||
propertyChanged(Solid.Y_POSITION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Y_POSITION_KEY)
|
||||
}
|
||||
|
||||
public var Solid.z: Number
|
||||
get() = position?.z ?: 0f
|
||||
set(value) {
|
||||
position().z = value.toDouble()
|
||||
propertyChanged(Solid.Z_POSITION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Z_POSITION_KEY)
|
||||
}
|
||||
|
||||
private fun Solid.rotation(): Point3D =
|
||||
@ -177,21 +180,21 @@ public var Solid.rotationX: Number
|
||||
get() = rotation?.x ?: 0f
|
||||
set(value) {
|
||||
rotation().x = value.toDouble()
|
||||
propertyChanged(Solid.X_ROTATION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.X_ROTATION_KEY)
|
||||
}
|
||||
|
||||
public var Solid.rotationY: Number
|
||||
get() = rotation?.y ?: 0f
|
||||
set(value) {
|
||||
rotation().y = value.toDouble()
|
||||
propertyChanged(Solid.Y_ROTATION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Y_ROTATION_KEY)
|
||||
}
|
||||
|
||||
public var Solid.rotationZ: Number
|
||||
get() = rotation?.z ?: 0f
|
||||
set(value) {
|
||||
rotation().z = value.toDouble()
|
||||
propertyChanged(Solid.Z_ROTATION_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Z_ROTATION_KEY)
|
||||
}
|
||||
|
||||
private fun Solid.scale(): Point3D =
|
||||
@ -201,19 +204,19 @@ public var Solid.scaleX: Number
|
||||
get() = scale?.x ?: 1f
|
||||
set(value) {
|
||||
scale().x = value.toDouble()
|
||||
propertyChanged(Solid.X_SCALE_KEY)
|
||||
asyncNotifyPropertyChange(Solid.X_SCALE_KEY)
|
||||
}
|
||||
|
||||
public var Solid.scaleY: Number
|
||||
get() = scale?.y ?: 1f
|
||||
set(value) {
|
||||
scale().y = value.toDouble()
|
||||
propertyChanged(Solid.Y_SCALE_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Y_SCALE_KEY)
|
||||
}
|
||||
|
||||
public var Solid.scaleZ: Number
|
||||
get() = scale?.z ?: 1f
|
||||
set(value) {
|
||||
scale().z = value.toDouble()
|
||||
propertyChanged(Solid.Z_SCALE_KEY)
|
||||
asyncNotifyPropertyChange(Solid.Z_SCALE_KEY)
|
||||
}
|
@ -35,6 +35,6 @@ internal fun Meta.toVector(default: Float = 0f) = Point3D(
|
||||
|
||||
internal fun Solid.updatePosition(meta: Meta?) {
|
||||
meta[Solid.POSITION_KEY].node?.toVector()?.let { position = it }
|
||||
meta[Solid.ROTATION].node?.toVector()?.let { rotation = it }
|
||||
meta[Solid.ROTATION_KEY].node?.toVector()?.let { rotation = it }
|
||||
meta[Solid.SCALE_KEY].node?.toVector(1f)?.let { scale = it }
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
@ -15,30 +15,35 @@ import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
public interface PrototypeHolder {
|
||||
public val parent: VisionGroup?
|
||||
public val prototypes: MutableVisionGroup?
|
||||
@VisionBuilder
|
||||
public fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit)
|
||||
|
||||
public fun getPrototype(name: Name): Solid?
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents 3-dimensional Visual Group
|
||||
* @param prototypes A container for templates visible inside this group
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("group.solid")
|
||||
public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
|
||||
public class SolidGroup(
|
||||
@Serializable(Prototypes.Companion::class) @SerialName("prototypes") private var prototypes: MutableVisionGroup? = null,
|
||||
) : VisionGroupBase(), Solid, PrototypeHolder {
|
||||
|
||||
override val descriptor: NodeDescriptor get() = Solid.descriptor
|
||||
|
||||
|
||||
/**
|
||||
* A container for templates visible inside this group
|
||||
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||
*/
|
||||
@Serializable(Prototypes.Companion::class)
|
||||
override var prototypes: MutableVisionGroup? = null
|
||||
private set
|
||||
override fun getPrototype(name: Name): Solid? =
|
||||
(prototypes?.get(name) as? Solid) ?: (parent as? PrototypeHolder)?.getPrototype(name)
|
||||
|
||||
/**
|
||||
* Create or edit prototype node as a group
|
||||
*/
|
||||
public fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit {
|
||||
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit {
|
||||
(prototypes ?: Prototypes().also {
|
||||
prototypes = it
|
||||
it.parent = this
|
||||
@ -51,13 +56,6 @@ public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
|
||||
|
||||
override var scale: Point3D? = null
|
||||
|
||||
override fun attachChildren() {
|
||||
prototypes?.parent = this
|
||||
prototypes?.attachChildren()
|
||||
super.attachChildren()
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * TODO add special static group to hold statics without propagation
|
||||
// */
|
||||
@ -80,19 +78,17 @@ public fun SolidGroup(block: SolidGroup.() -> Unit): SolidGroup {
|
||||
return SolidGroup().apply(block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||
*/
|
||||
public tailrec fun PrototypeHolder.getPrototype(name: Name): Solid? =
|
||||
prototypes?.get(name) as? Solid ?: (parent as? PrototypeHolder)?.getPrototype(name)
|
||||
|
||||
@VisionBuilder
|
||||
public fun VisionContainerBuilder<Vision>.group(name: Name = Name.EMPTY, action: SolidGroup.() -> Unit = {}): SolidGroup =
|
||||
public fun VisionContainerBuilder<Vision>.group(
|
||||
name: Name = Name.EMPTY,
|
||||
action: SolidGroup.() -> Unit = {},
|
||||
): SolidGroup =
|
||||
SolidGroup().apply(action).also { set(name, it) }
|
||||
|
||||
/**
|
||||
* Define a group with given [name], attach it to this parent and return it.
|
||||
*/
|
||||
@VisionBuilder
|
||||
public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup.() -> Unit = {}): SolidGroup =
|
||||
SolidGroup().apply(action).also { set(name, it) }
|
||||
|
||||
@ -102,27 +98,30 @@ public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup
|
||||
@Serializable(Prototypes.Companion::class)
|
||||
internal class Prototypes(
|
||||
children: Map<NameToken, Vision> = emptyMap(),
|
||||
) : VisionGroupBase(), PrototypeHolder {
|
||||
) : VisionGroupBase(children as? MutableMap<NameToken, Vision> ?: children.toMutableMap()), PrototypeHolder {
|
||||
|
||||
init {
|
||||
this.childrenInternal.putAll(children)
|
||||
}
|
||||
|
||||
override var properties: Config?
|
||||
get() = null
|
||||
set(_) {
|
||||
error("Can't define properties for prototypes block")
|
||||
}
|
||||
|
||||
override val prototypes: MutableVisionGroup get() = this
|
||||
|
||||
override fun attachChildren() {
|
||||
//used during deserialization only
|
||||
children.values.forEach {
|
||||
it.parent = parent
|
||||
(it as? VisionGroup)?.attachChildren()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOwnProperty(name: Name): MetaItem? = null
|
||||
|
||||
override fun getProperty(
|
||||
name: Name,
|
||||
inherit: Boolean,
|
||||
includeStyles: Boolean,
|
||||
includeDefaults: Boolean,
|
||||
): MetaItem? = null
|
||||
|
||||
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
|
||||
error("Can't ser property of prototypes container")
|
||||
}
|
||||
|
||||
override val descriptor: NodeDescriptor? = null
|
||||
|
||||
companion object : KSerializer<MutableVisionGroup> {
|
||||
|
||||
private val mapSerializer: KSerializer<Map<NameToken, Vision>> =
|
||||
@ -142,4 +141,10 @@ internal class Prototypes(
|
||||
mapSerializer.serialize(encoder, value.children)
|
||||
}
|
||||
}
|
||||
|
||||
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit) {
|
||||
apply(builder)
|
||||
}
|
||||
|
||||
override fun getPrototype(name: Name): Solid? = get(name) as? Solid
|
||||
}
|
||||
|
@ -4,11 +4,16 @@ import hep.dataforge.context.AbstractPlugin
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.PluginFactory
|
||||
import hep.dataforge.context.PluginTag
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.*
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionBase
|
||||
import hep.dataforge.vision.VisionGroupBase
|
||||
import hep.dataforge.vision.VisionManager
|
||||
import hep.dataforge.vision.VisionManager.Companion.VISION_SERIALIZER_MODULE_TARGET
|
||||
import hep.dataforge.vision.html.VisionOutput
|
||||
import kotlinx.serialization.PolymorphicSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
@ -30,13 +35,13 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<SolidManager> {
|
||||
override val tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP)
|
||||
override val tag: PluginTag = PluginTag(name = "vision.solid", group = PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<out SolidManager> = SolidManager::class
|
||||
override fun invoke(meta: Meta, context: Context): SolidManager = SolidManager(meta)
|
||||
|
||||
private fun PolymorphicModuleBuilder<Solid>.solids() {
|
||||
subclass(SolidGroup.serializer())
|
||||
subclass(SolidReference.serializer())
|
||||
subclass(SolidReferenceGroup.serializer())
|
||||
subclass(Composite.serializer())
|
||||
subclass(Tube.serializer())
|
||||
subclass(Box.serializer())
|
||||
@ -67,10 +72,9 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
|
||||
|
||||
public fun encodeToString(solid: Solid): String = jsonForSolids.encodeToString(PolymorphicSerializer(Vision::class), solid)
|
||||
|
||||
fun decodeFromString(str: String): Solid = jsonForSolids.decodeFromString(PolymorphicSerializer(Solid::class), str).also {
|
||||
if(it is VisionGroup){
|
||||
it.attachChildren()
|
||||
}
|
||||
}
|
||||
public fun decodeFromString(str: String): Solid = jsonForSolids.decodeFromString(PolymorphicSerializer(Solid::class), str)
|
||||
}
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block)
|
@ -6,53 +6,12 @@ import hep.dataforge.meta.descriptors.attributes
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.asValue
|
||||
import hep.dataforge.values.string
|
||||
import hep.dataforge.vision.Colors
|
||||
import hep.dataforge.vision.VisionBuilder
|
||||
import hep.dataforge.vision.setProperty
|
||||
import hep.dataforge.vision.*
|
||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
|
||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY
|
||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_OPACITY_KEY
|
||||
import hep.dataforge.vision.widgetType
|
||||
|
||||
@VisionBuilder
|
||||
public class ColorAccessor(private val parent: MutableItemProvider, private val colorKey: Name) {
|
||||
public var value: Value?
|
||||
get() = parent.getItem(colorKey).value
|
||||
set(value) {
|
||||
parent[colorKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
public var ColorAccessor?.string: String?
|
||||
get() = this?.value?.string
|
||||
set(value) {
|
||||
this?.value = value?.asValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set [webcolor](https://en.wikipedia.org/wiki/Web_colors) as string
|
||||
*/
|
||||
public operator fun ColorAccessor?.invoke(webColor: String) {
|
||||
this?.value = webColor.asValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color as RGB integer
|
||||
*/
|
||||
public operator fun ColorAccessor?.invoke(rgb: Int) {
|
||||
this?.value = rgb.asValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color as RGB
|
||||
*/
|
||||
public operator fun ColorAccessor?.invoke(r: UByte, g: UByte, b: UByte) {
|
||||
this?.value = Colors.rgbToString(r, g, b).asValue()
|
||||
}
|
||||
|
||||
@VisionBuilder
|
||||
public class SolidMaterial : Scheme() {
|
||||
@ -60,12 +19,12 @@ public class SolidMaterial : Scheme() {
|
||||
/**
|
||||
* Primary web-color for the material
|
||||
*/
|
||||
public var color: ColorAccessor = ColorAccessor(config, COLOR_KEY)
|
||||
public val color: ColorAccessor = ColorAccessor(this, COLOR_KEY)
|
||||
|
||||
/**
|
||||
* Specular color for phong material
|
||||
*/
|
||||
public var specularColor: ColorAccessor = ColorAccessor(config, SPECULAR_COLOR_KEY)
|
||||
public val specularColor: ColorAccessor = ColorAccessor(this, SPECULAR_COLOR_KEY)
|
||||
|
||||
/**
|
||||
* Opacity
|
||||
@ -92,11 +51,27 @@ public class SolidMaterial : Scheme() {
|
||||
public override val descriptor: NodeDescriptor by lazy {
|
||||
//must be lazy to avoid initialization bug
|
||||
NodeDescriptor {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
|
||||
value(COLOR_KEY) {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
type(ValueType.STRING, ValueType.NUMBER)
|
||||
widgetType = "color"
|
||||
}
|
||||
|
||||
value(SPECULAR_COLOR_KEY) {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
type(ValueType.STRING, ValueType.NUMBER)
|
||||
widgetType = "color"
|
||||
hide()
|
||||
}
|
||||
|
||||
value(OPACITY_KEY) {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
type(ValueType.NUMBER)
|
||||
default(1.0)
|
||||
attributes {
|
||||
@ -107,6 +82,8 @@ public class SolidMaterial : Scheme() {
|
||||
widgetType = "slider"
|
||||
}
|
||||
value(WIREFRAME_KEY) {
|
||||
inherited = true
|
||||
usesStyles = true
|
||||
type(ValueType.BOOLEAN)
|
||||
default(false)
|
||||
}
|
||||
@ -115,24 +92,23 @@ public class SolidMaterial : Scheme() {
|
||||
}
|
||||
}
|
||||
|
||||
public val Solid.color: ColorAccessor get() = ColorAccessor(config, MATERIAL_COLOR_KEY)
|
||||
public val Solid.color: ColorAccessor
|
||||
get() = ColorAccessor(
|
||||
allProperties(inherit = true),
|
||||
MATERIAL_COLOR_KEY
|
||||
)
|
||||
|
||||
public var Solid.material: SolidMaterial?
|
||||
get() = getProperty(MATERIAL_KEY).node?.let { SolidMaterial.read(it) }
|
||||
set(value) = setProperty(MATERIAL_KEY, value?.config)
|
||||
get() = getProperty(MATERIAL_KEY, inherit = true).node?.let { SolidMaterial.read(it) }
|
||||
set(value) = setProperty(MATERIAL_KEY, value?.rootNode)
|
||||
|
||||
@VisionBuilder
|
||||
public fun Solid.material(builder: SolidMaterial.() -> Unit) {
|
||||
val node = config[MATERIAL_KEY].node
|
||||
if (node != null) {
|
||||
SolidMaterial.update(node, builder)
|
||||
} else {
|
||||
config[MATERIAL_KEY] = SolidMaterial(builder)
|
||||
}
|
||||
ownProperties.getChild(MATERIAL_KEY).update(SolidMaterial, builder)
|
||||
}
|
||||
|
||||
public var Solid.opacity: Number?
|
||||
get() = getProperty(MATERIAL_OPACITY_KEY).number
|
||||
get() = getProperty(MATERIAL_OPACITY_KEY, inherit = true).number
|
||||
set(value) {
|
||||
setProperty(MATERIAL_OPACITY_KEY, value?.asValue())
|
||||
}
|
@ -3,43 +3,32 @@ package hep.dataforge.vision.solid
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.vision.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlin.collections.set
|
||||
|
||||
public abstract class AbstractReference : SolidBase(), VisionGroup {
|
||||
public abstract val prototype: Solid
|
||||
public interface SolidReference : Vision {
|
||||
public val prototype: Solid
|
||||
}
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? = sequence {
|
||||
yield(properties?.get(name))
|
||||
private fun SolidReference.getRefProperty(
|
||||
name: Name,
|
||||
inherit: Boolean,
|
||||
includeStyles: Boolean,
|
||||
includeDefaults: Boolean,
|
||||
): MetaItem? {
|
||||
return sequence {
|
||||
yield(getOwnProperty(name))
|
||||
if (includeStyles) {
|
||||
yieldAll(getStyleItems(name))
|
||||
yield(prototype.getProperty(name))
|
||||
}
|
||||
yield(prototype.getProperty(name, inherit, includeStyles, includeDefaults))
|
||||
if (inherit) {
|
||||
yield(parent?.getProperty(name, inherit))
|
||||
}
|
||||
}.merge()
|
||||
|
||||
override var styles: List<String>
|
||||
get() = (properties[Vision.STYLE_KEY]?.stringList ?: emptyList()) + prototype.styles
|
||||
set(value) {
|
||||
config[Vision.STYLE_KEY] = value
|
||||
}
|
||||
|
||||
override val allProperties: Laminate
|
||||
get() = Laminate(
|
||||
properties,
|
||||
allStyles,
|
||||
prototype.allProperties,
|
||||
parent?.allProperties,
|
||||
)
|
||||
|
||||
override fun attachChildren() {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
override val descriptor: NodeDescriptor get() = prototype.descriptor
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,9 +36,9 @@ public abstract class AbstractReference : SolidBase(), VisionGroup {
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("solid.ref")
|
||||
public class SolidReference(
|
||||
public class SolidReferenceGroup(
|
||||
public val templateName: Name,
|
||||
) : AbstractReference(), Solid {
|
||||
) : SolidBase(), SolidReference, VisionGroup {
|
||||
|
||||
/**
|
||||
* Recursively search for defined template in the parent
|
||||
@ -58,60 +47,103 @@ public class SolidReference(
|
||||
get() = (parent as? SolidGroup)?.getPrototype(templateName)
|
||||
?: error("Prototype with name $templateName not found in $parent")
|
||||
|
||||
@Transient
|
||||
private val propertyCache: HashMap<Name, Config> = HashMap()
|
||||
|
||||
|
||||
override val children: Map<NameToken, SolidReference.ReferenceChild>
|
||||
override val children: Map<NameToken, Vision>
|
||||
get() = (prototype as? VisionGroup)?.children
|
||||
?.filter { !it.key.toString().startsWith("@") }
|
||||
?.mapValues {
|
||||
ReferenceChild(it.key.asName())
|
||||
} ?: emptyMap()
|
||||
|
||||
private fun childPropertyName(childName: Name, propertyName: Name): Name {
|
||||
return NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
|
||||
private fun childToken(childName: Name): NameToken =
|
||||
NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString())
|
||||
|
||||
private fun childPropertyName(childName: Name, propertyName: Name): Name =
|
||||
childToken(childName) + propertyName
|
||||
|
||||
private fun getChildProperty(childName: Name, propertyName: Name): MetaItem? {
|
||||
return getOwnProperty(childPropertyName(childName, propertyName))
|
||||
}
|
||||
|
||||
private fun setChildProperty(childName: Name, propertyName: Name, item: MetaItem?, notify: Boolean) {
|
||||
setProperty(childPropertyName(childName, propertyName), item, notify)
|
||||
}
|
||||
|
||||
private fun prototypeFor(name: Name): Solid {
|
||||
return (prototype as? SolidGroup)?.get(name) as? Solid
|
||||
return if (name.isEmpty()) prototype else {
|
||||
(prototype as? SolidGroup)?.get(name) as? Solid
|
||||
?: error("Prototype with name $name not found in $this")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getProperty(
|
||||
name: Name,
|
||||
inherit: Boolean,
|
||||
includeStyles: Boolean,
|
||||
includeDefaults: Boolean,
|
||||
): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults)
|
||||
|
||||
override val descriptor: NodeDescriptor get() = prototype.descriptor
|
||||
|
||||
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
|
||||
|
||||
/**
|
||||
* A ProxyChild is created temporarily only to interact with properties, it does not store any values
|
||||
* (properties are stored in external cache) and created and destroyed on-demand).
|
||||
*/
|
||||
public inner class ReferenceChild(public val name: Name) : AbstractReference() {
|
||||
private inner class ReferenceChild(private val childName: Name) : SolidReference, VisionGroup {
|
||||
|
||||
override val prototype: Solid get() = prototypeFor(name)
|
||||
override val prototype: Solid get() = prototypeFor(childName)
|
||||
|
||||
override val children: Map<NameToken, Vision>
|
||||
get() = (prototype as? VisionGroup)?.children
|
||||
?.filter { !it.key.toString().startsWith("@") }
|
||||
?.mapValues { (key, _) ->
|
||||
ReferenceChild(name + key.asName())
|
||||
ReferenceChild(childName + key.asName())
|
||||
} ?: emptyMap()
|
||||
|
||||
override var properties: Config?
|
||||
get() = propertyCache[name]
|
||||
override val meta: Meta get() = TODO()// getChildProperty(childName, Name.EMPTY).node ?: Meta.EMPTY
|
||||
|
||||
override fun getOwnProperty(name: Name): MetaItem? = getChildProperty(childName, name)
|
||||
|
||||
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
|
||||
setChildProperty(childName, name, item, notify)
|
||||
}
|
||||
|
||||
override fun getProperty(
|
||||
name: Name,
|
||||
inherit: Boolean,
|
||||
includeStyles: Boolean,
|
||||
includeDefaults: Boolean,
|
||||
): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults)
|
||||
|
||||
override var parent: VisionGroup?
|
||||
get() {
|
||||
val parentName = childName.cutLast()
|
||||
return if (parentName.isEmpty()) this@SolidReferenceGroup else ReferenceChild(parentName)
|
||||
}
|
||||
set(value) {
|
||||
if (value == null) {
|
||||
propertyCache.remove(name)?.also {
|
||||
//Removing listener if it is present
|
||||
removeChangeListener(this@SolidReference)
|
||||
error("Setting a parent for a reference child is not possible")
|
||||
}
|
||||
} else {
|
||||
propertyCache[name] = value.also {
|
||||
onPropertyChange(this@SolidReference) { propertyName ->
|
||||
this@SolidReference.propertyChanged(childPropertyName(name, propertyName))
|
||||
|
||||
override fun onPropertyChange(scope: CoroutineScope, callback: suspend (Name) -> Unit) {
|
||||
this@SolidReferenceGroup.onPropertyChange(scope) { name ->
|
||||
if (name.startsWith(childToken(childName))) {
|
||||
callback(name.cutFirst())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun notifyPropertyChanged(propertyName: Name) {
|
||||
this@SolidReferenceGroup.notifyPropertyChanged(childPropertyName(childName, propertyName))
|
||||
}
|
||||
|
||||
override fun update(change: VisionChange) {
|
||||
change.properties?.let {
|
||||
updateProperties(Name.EMPTY, it.asMetaItem())
|
||||
}
|
||||
}
|
||||
|
||||
override val descriptor: NodeDescriptor get() = prototype.descriptor
|
||||
|
||||
}
|
||||
|
||||
public companion object {
|
||||
@ -120,13 +152,10 @@ public class SolidReference(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a vision prototype if it is a [SolidReference] or vision itself if it is not
|
||||
* Get a vision prototype if it is a [SolidReferenceGroup] or vision itself if it is not
|
||||
*/
|
||||
public val Vision.prototype: Vision
|
||||
get() = when (this) {
|
||||
is AbstractReference -> prototype
|
||||
else -> this
|
||||
}
|
||||
get() = if (this is SolidReference) prototype else this
|
||||
|
||||
/**
|
||||
* Create ref for existing prototype
|
||||
@ -134,16 +163,16 @@ public val Vision.prototype: Vision
|
||||
public fun SolidGroup.ref(
|
||||
templateName: Name,
|
||||
name: String = "",
|
||||
): SolidReference = SolidReference(templateName).also { set(name, it) }
|
||||
): SolidReferenceGroup = SolidReferenceGroup(templateName).also { set(name, it) }
|
||||
|
||||
/**
|
||||
* Add new [SolidReference] wrapping given object and automatically adding it to the prototypes
|
||||
* Add new [SolidReferenceGroup] wrapping given object and automatically adding it to the prototypes
|
||||
*/
|
||||
public fun SolidGroup.ref(
|
||||
name: String,
|
||||
obj: Solid,
|
||||
templateName: Name = name.toName(),
|
||||
): SolidReference {
|
||||
): SolidReferenceGroup {
|
||||
val existing = getPrototype(templateName)
|
||||
if (existing == null) {
|
||||
prototypes {
|
||||
|
@ -1,9 +1,12 @@
|
||||
package hep.dataforge.vision.solid.specifications
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.meta.Scheme
|
||||
import hep.dataforge.meta.SchemeSpec
|
||||
import hep.dataforge.meta.boolean
|
||||
import hep.dataforge.meta.double
|
||||
|
||||
public class Axes : Scheme() {
|
||||
public var visible: Boolean by boolean(!config.isEmpty())
|
||||
public var visible: Boolean by boolean(false)
|
||||
public var size: Double by double(AXIS_SIZE)
|
||||
public var width: Double by double(AXIS_WIDTH)
|
||||
|
||||
|
@ -4,9 +4,10 @@ import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
|
||||
public class Canvas3DOptions : Scheme() {
|
||||
public var axes: Axes by spec(Axes, Axes.empty())
|
||||
public var camera: Camera by spec(Camera, Camera.empty())
|
||||
public var controls: Controls by spec(Controls, Controls.empty())
|
||||
public var axes: Axes by spec(Axes)
|
||||
public var light: Light by spec(Light)
|
||||
public var camera: Camera by spec(Camera)
|
||||
public var controls: Controls by spec(Controls)
|
||||
|
||||
public var minSize: Int by int(400)
|
||||
public var minWith: Number by number { minSize }
|
||||
|
@ -0,0 +1,8 @@
|
||||
package hep.dataforge.vision.solid.specifications
|
||||
|
||||
import hep.dataforge.meta.Scheme
|
||||
import hep.dataforge.meta.SchemeSpec
|
||||
|
||||
public class Light : Scheme() {
|
||||
public companion object : SchemeSpec<Light>(::Light)
|
||||
}
|
@ -1,18 +1,15 @@
|
||||
package hep.dataforge.vision.solid.transform
|
||||
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vision.MutableVisionGroup
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.*
|
||||
import hep.dataforge.vision.solid.*
|
||||
|
||||
@DFExperimental
|
||||
internal fun mergeChild(parent: VisionGroup, child: Vision): Vision {
|
||||
return child.apply {
|
||||
|
||||
config.update(parent.config)
|
||||
configure(parent.meta)
|
||||
|
||||
//parent.properties?.let { config.update(it) }
|
||||
|
||||
@ -40,7 +37,7 @@ internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
|
||||
override fun SolidGroup.transformInPlace() {
|
||||
fun MutableVisionGroup.replaceChildren() {
|
||||
children.forEach { (childName, parent) ->
|
||||
if (parent is SolidReference) return@forEach //ignore refs
|
||||
if (parent is SolidReferenceGroup) return@forEach //ignore refs
|
||||
if (parent is MutableVisionGroup) {
|
||||
parent.replaceChildren()
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import hep.dataforge.names.asName
|
||||
import hep.dataforge.vision.MutableVisionGroup
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.SolidReference
|
||||
import hep.dataforge.vision.solid.SolidReferenceGroup
|
||||
|
||||
@DFExperimental
|
||||
internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||
@ -17,7 +17,7 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||
counter.forEach { (key, value) ->
|
||||
reducer[key] = (reducer[key] ?: 0) + value
|
||||
}
|
||||
} else if (obj is SolidReference) {
|
||||
} else if (obj is SolidReferenceGroup) {
|
||||
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
|
||||
}
|
||||
|
||||
@ -26,9 +26,11 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
|
||||
}
|
||||
|
||||
private fun MutableVisionGroup.unref(name: Name) {
|
||||
(this as? SolidGroup)?.prototypes?.set(name, null)
|
||||
children.filter { (it.value as? SolidReference)?.templateName == name }.forEach { (key, value) ->
|
||||
val reference = value as SolidReference
|
||||
(this as? SolidGroup)?.prototypes{
|
||||
set(name, null)
|
||||
}
|
||||
children.filter { (it.value as? SolidReferenceGroup)?.templateName == name }.forEach { (key, value) ->
|
||||
val reference = value as SolidReferenceGroup
|
||||
val newChild = mergeChild(reference, reference.prototype)
|
||||
newChild.parent = null
|
||||
set(key.asName(), newChild) // replace proxy with merged object
|
||||
|
@ -0,0 +1,29 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.vision.Colors
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CompositeTest {
|
||||
|
||||
@Test
|
||||
fun testCompositeBuilder(){
|
||||
lateinit var composite: Composite
|
||||
SolidGroup {
|
||||
composite = composite(CompositeType.INTERSECT) {
|
||||
y = 300
|
||||
box(100, 100, 100) {
|
||||
z = 50
|
||||
}
|
||||
sphere(50) {
|
||||
detail = 32
|
||||
}
|
||||
material {
|
||||
color("pink")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("pink", composite.color.string)
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ class ConvexTest {
|
||||
val json = SolidManager.jsonForSolids.encodeToJsonElement(Convex.serializer(), convex)
|
||||
val meta = json.toMetaItem().node!!
|
||||
|
||||
val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D() }
|
||||
val points = meta.getIndexed("points").values.map { (it as NodeItem<*>).node.point3D() }
|
||||
assertEquals(8, points.count())
|
||||
|
||||
assertEquals(8, convex.points.size)
|
||||
|
@ -1,10 +1,8 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vision.styleSheet
|
||||
import hep.dataforge.vision.useStyle
|
||||
import hep.dataforge.vision.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ -14,12 +12,12 @@ class PropertyTest {
|
||||
fun testInheritedProperty() {
|
||||
var box: Box? = null
|
||||
val group = SolidGroup().apply {
|
||||
config["test"] = 22
|
||||
setProperty("test", 22)
|
||||
group {
|
||||
box = box(100, 100, 100)
|
||||
}
|
||||
}
|
||||
assertEquals(22, box?.getProperty("test".asName()).int)
|
||||
assertEquals(22, box?.getProperty("test", inherit = true).int)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -37,7 +35,7 @@ class PropertyTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(22, box?.getProperty("test".asName()).int)
|
||||
assertEquals(22, box?.getProperty("test").int)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -60,7 +58,7 @@ class PropertyTest {
|
||||
|
||||
@Test
|
||||
fun testReferenceStyleProperty() {
|
||||
var box: SolidReference? = null
|
||||
var box: SolidReferenceGroup? = null
|
||||
val group = SolidGroup{
|
||||
styleSheet {
|
||||
set("testStyle") {
|
||||
|
@ -15,7 +15,7 @@ fun SolidGroup.refGroup(
|
||||
name: String,
|
||||
templateName: Name = name.toName(),
|
||||
block: MutableVisionGroup.() -> Unit
|
||||
): SolidReference {
|
||||
): SolidReferenceGroup {
|
||||
val group = SolidGroup().apply(block)
|
||||
return ref(name, group, templateName)
|
||||
}
|
||||
@ -32,7 +32,7 @@ class SerializationTest {
|
||||
val string = SolidManager.encodeToString(cube)
|
||||
println(string)
|
||||
val newCube = SolidManager.decodeFromString(string)
|
||||
assertEquals(cube.config, newCube.config)
|
||||
assertEquals(cube.meta, newCube.meta)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -53,7 +53,7 @@ class SerializationTest {
|
||||
val string = SolidManager.encodeToString(group)
|
||||
println(string)
|
||||
val reconstructed = SolidManager.decodeFromString(string) as SolidGroup
|
||||
assertEquals(group["cube"]?.config, reconstructed["cube"]?.config)
|
||||
assertEquals(group["cube"]?.meta, reconstructed["cube"]?.meta)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package hep.dataforge.vision.solid
|
||||
|
||||
import hep.dataforge.vision.get
|
||||
import hep.dataforge.vision.style
|
||||
import hep.dataforge.vision.styles
|
||||
import hep.dataforge.vision.useStyle
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SolidReferenceTest {
|
||||
val groupWithReference = SolidGroup {
|
||||
val referenceStyle by style {
|
||||
SolidMaterial.MATERIAL_COLOR_KEY put "red"
|
||||
}
|
||||
ref("test", Box(100f,100f,100f).apply {
|
||||
color("blue")
|
||||
useStyle(referenceStyle)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testReferenceProperty(){
|
||||
assertEquals("blue", (groupWithReference["test"] as Solid).color.string)
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ class VisionUpdateTest {
|
||||
targetVision.update(dif)
|
||||
assertTrue { targetVision["top"] is SolidGroup }
|
||||
assertEquals("red", (targetVision["origin"] as Solid).color.string) // Should work
|
||||
assertEquals("#00007b", (targetVision["top"] as SolidGroup).color.string) // new item always takes precedence
|
||||
assertEquals("#00007b", (targetVision["top"] as Solid).color.string) // new item always takes precedence
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -45,7 +45,7 @@ class VisionUpdateTest {
|
||||
val serialized = visionManager.jsonFormat.encodeToString(VisionChange.serializer(), change)
|
||||
println(serialized)
|
||||
val reconstructed = visionManager.jsonFormat.decodeFromString(VisionChange.serializer(), serialized)
|
||||
assertEquals(change.propertyChange,reconstructed.propertyChange)
|
||||
assertEquals(change.properties,reconstructed.properties)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -9,10 +9,8 @@ import hep.dataforge.names.startsWith
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.SolidMaterial
|
||||
import hep.dataforge.vision.solid.layer
|
||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.geometries.EdgesGeometry
|
||||
import info.laht.threekt.geometries.WireframeGeometry
|
||||
import info.laht.threekt.objects.LineSegments
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlin.reflect.KClass
|
||||
@ -21,14 +19,14 @@ import kotlin.reflect.KClass
|
||||
* Basic geometry-based factory
|
||||
*/
|
||||
public abstract class MeshThreeFactory<in T : Solid>(
|
||||
override val type: KClass<in T>
|
||||
override val type: KClass<in T>,
|
||||
) : ThreeFactory<T> {
|
||||
/**
|
||||
* Build a geometry for an object
|
||||
*/
|
||||
public abstract fun buildGeometry(obj: T): BufferGeometry
|
||||
|
||||
override fun invoke(obj: T): Mesh {
|
||||
override fun invoke(three: ThreePlugin, obj: T): Mesh {
|
||||
val geometry = buildGeometry(obj)
|
||||
|
||||
//JS sometimes tries to pass Geometry as BufferGeometry
|
||||
@ -36,58 +34,66 @@ public abstract class MeshThreeFactory<in T : Solid>(
|
||||
|
||||
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
|
||||
|
||||
val mesh = Mesh(geometry, null).apply{
|
||||
val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply {
|
||||
matrixAutoUpdate = false
|
||||
//set position for mesh
|
||||
updatePosition(obj)
|
||||
}.applyProperties(obj)
|
||||
|
||||
//add listener to object properties
|
||||
obj.onPropertyChange(this) { name ->
|
||||
obj.onPropertyChange(three.updateScope) { name ->
|
||||
when {
|
||||
name.startsWith(Solid.GEOMETRY_KEY) -> {
|
||||
val oldGeometry = mesh.geometry as BufferGeometry
|
||||
val newGeometry = buildGeometry(obj)
|
||||
oldGeometry.attributes = newGeometry.attributes
|
||||
mesh.applyWireFrame(obj)
|
||||
//mesh.applyWireFrame(obj)
|
||||
mesh.applyEdges(obj)
|
||||
newGeometry.dispose()
|
||||
}
|
||||
name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
||||
//name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
||||
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
||||
else -> mesh.updateProperty(obj, name)
|
||||
}
|
||||
}
|
||||
|
||||
return mesh
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public val EDGES_KEY: Name = "edges".asName()
|
||||
public val WIREFRAME_KEY: Name = "wireframe".asName()
|
||||
//public val WIREFRAME_KEY: Name = "wireframe".asName()
|
||||
public val ENABLED_KEY: Name = "enabled".asName()
|
||||
public val EDGES_ENABLED_KEY: Name = EDGES_KEY + ENABLED_KEY
|
||||
public val EDGES_MATERIAL_KEY: Name = EDGES_KEY + SolidMaterial.MATERIAL_KEY
|
||||
public val WIREFRAME_ENABLED_KEY: Name = WIREFRAME_KEY + ENABLED_KEY
|
||||
public val WIREFRAME_MATERIAL_KEY: Name = WIREFRAME_KEY + SolidMaterial.MATERIAL_KEY
|
||||
//public val WIREFRAME_ENABLED_KEY: Name = WIREFRAME_KEY + ENABLED_KEY
|
||||
//public val WIREFRAME_MATERIAL_KEY: Name = WIREFRAME_KEY + SolidMaterial.MATERIAL_KEY
|
||||
}
|
||||
}
|
||||
|
||||
fun Mesh.applyProperties(obj: Solid): Mesh = apply{
|
||||
material = getMaterial(obj, true)
|
||||
internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
|
||||
updateMaterial(obj)
|
||||
applyEdges(obj)
|
||||
applyWireFrame(obj)
|
||||
//applyWireFrame(obj)
|
||||
layers.enable(obj.layer)
|
||||
children.forEach {
|
||||
it.layers.enable(obj.layer)
|
||||
}
|
||||
}
|
||||
|
||||
fun Mesh.applyEdges(obj: Solid) {
|
||||
public fun Mesh.applyEdges(obj: Solid) {
|
||||
val edges = children.find { it.name == "@edges" } as? LineSegments
|
||||
//inherited edges definition, enabled by default
|
||||
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
|
||||
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY, inherit = true, includeStyles = true).boolean != false) {
|
||||
val bufferGeometry = geometry as? BufferGeometry ?: return
|
||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true)
|
||||
val material = ThreeMaterials.getLineMaterial(
|
||||
obj.getProperty(
|
||||
MeshThreeFactory.EDGES_MATERIAL_KEY,
|
||||
inherit = true,
|
||||
includeStyles = true
|
||||
).node,
|
||||
true
|
||||
)
|
||||
if (edges == null) {
|
||||
add(
|
||||
LineSegments(
|
||||
@ -108,22 +114,23 @@ fun Mesh.applyEdges(obj: Solid) {
|
||||
}
|
||||
}
|
||||
|
||||
fun Mesh.applyWireFrame(obj: Solid) {
|
||||
children.find { it.name == "@wireframe" }?.let {
|
||||
remove(it)
|
||||
(it as LineSegments).dispose()
|
||||
}
|
||||
//inherited wireframe definition, disabled by default
|
||||
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
||||
val bufferGeometry = geometry as? BufferGeometry ?: return
|
||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true)
|
||||
add(
|
||||
LineSegments(
|
||||
WireframeGeometry(bufferGeometry),
|
||||
material
|
||||
).apply {
|
||||
name = "@wireframe"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
//public fun Mesh.applyWireFrame(obj: Solid) {
|
||||
// children.find { it.name == "@wireframe" }?.let {
|
||||
// remove(it)
|
||||
// (it as LineSegments).dispose()
|
||||
// }
|
||||
// //inherited wireframe definition, disabled by default
|
||||
// if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
||||
// val bufferGeometry = geometry as? BufferGeometry ?: return
|
||||
// val material =
|
||||
// ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node, true)
|
||||
// add(
|
||||
// LineSegments(
|
||||
// WireframeGeometry(bufferGeometry),
|
||||
// material
|
||||
// ).apply {
|
||||
// name = "@wireframe"
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
//}
|
@ -1,6 +1,6 @@
|
||||
package hep.dataforge.vision.solid.three
|
||||
|
||||
import hep.dataforge.meta.getItem
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
@ -20,6 +20,7 @@ import info.laht.threekt.external.controls.OrbitControls
|
||||
import info.laht.threekt.external.controls.TrackballControls
|
||||
import info.laht.threekt.geometries.EdgesGeometry
|
||||
import info.laht.threekt.helpers.AxesHelper
|
||||
import info.laht.threekt.lights.AmbientLight
|
||||
import info.laht.threekt.materials.LineBasicMaterial
|
||||
import info.laht.threekt.math.Vector2
|
||||
import info.laht.threekt.objects.LineSegments
|
||||
@ -50,8 +51,11 @@ public class ThreeCanvas(
|
||||
public var axes: AxesHelper = AxesHelper(options.axes.size.toInt()).apply { visible = options.axes.visible }
|
||||
private set
|
||||
|
||||
private var light = buildLight(options.light)
|
||||
|
||||
private val scene: Scene = Scene().apply {
|
||||
add(axes)
|
||||
add(light)
|
||||
}
|
||||
|
||||
public var camera: PerspectiveCamera = buildCamera(options.camera)
|
||||
@ -66,6 +70,7 @@ public class ThreeCanvas(
|
||||
}
|
||||
|
||||
private val canvas = (renderer.domElement as HTMLCanvasElement).apply {
|
||||
className += "three-canvas"
|
||||
width = 600
|
||||
height = 600
|
||||
style.apply {
|
||||
@ -130,7 +135,8 @@ public class ThreeCanvas(
|
||||
}
|
||||
}
|
||||
|
||||
public fun attach(element: Element) {
|
||||
internal fun attach(element: Element) {
|
||||
check(element.getElementsByClassName("three-canvas").length == 0){"Three canvas already created in this element"}
|
||||
element.appendChild(canvas)
|
||||
updateSize()
|
||||
}
|
||||
@ -163,6 +169,7 @@ public class ThreeCanvas(
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildLight(spec: Light): info.laht.threekt.lights.Light = AmbientLight(0x404040)
|
||||
|
||||
private fun buildCamera(spec: Camera) = PerspectiveCamera(
|
||||
spec.fov,
|
||||
@ -176,7 +183,7 @@ public class ThreeCanvas(
|
||||
}
|
||||
|
||||
private fun addControls(element: Node, controls: Controls) {
|
||||
when (controls.getItem("type").string) {
|
||||
when (controls.get("type").string) {
|
||||
"trackball" -> TrackballControls(camera, element)
|
||||
else -> OrbitControls(camera, element)
|
||||
}
|
||||
@ -189,8 +196,10 @@ public class ThreeCanvas(
|
||||
}
|
||||
|
||||
public override fun render(vision: Solid) {
|
||||
//clear old root
|
||||
clear()
|
||||
scene.children.find { it.name == "@root" }?.let {
|
||||
//Throw error is something is already rendered here
|
||||
error("Root object already is present in the canvas")
|
||||
}
|
||||
|
||||
val object3D = three.buildObject3D(vision)
|
||||
object3D.name = "@root"
|
||||
|
@ -22,7 +22,7 @@ import kotlin.reflect.KClass
|
||||
public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
|
||||
override val type: KClass<in SolidLabel> get() = SolidLabel::class
|
||||
|
||||
override fun invoke(obj: SolidLabel): Object3D {
|
||||
override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
|
||||
val canvas = document.createElement("canvas") as HTMLCanvasElement
|
||||
val context = canvas.getContext("2d") as CanvasRenderingContext2D
|
||||
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"
|
||||
|
@ -2,11 +2,10 @@ package hep.dataforge.vision.solid.three
|
||||
|
||||
import hep.dataforge.vision.solid.Convex
|
||||
import info.laht.threekt.external.geometries.ConvexBufferGeometry
|
||||
import info.laht.threekt.math.Vector3
|
||||
|
||||
public object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
|
||||
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
|
||||
@Suppress("USELESS_CAST") val vectors = obj.points.toTypedArray() as Array<Vector3>
|
||||
val vectors = obj.points.map { it.toVector() }.toTypedArray()
|
||||
return ConvexBufferGeometry(vectors)
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_KEY
|
||||
import hep.dataforge.vision.solid.three.ThreeFactory.Companion.TYPE
|
||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
||||
import hep.dataforge.vision.visible
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
@ -22,7 +21,7 @@ public interface ThreeFactory<in T : Vision> {
|
||||
|
||||
public val type: KClass<in T>
|
||||
|
||||
public operator fun invoke(obj: T): Object3D
|
||||
public operator fun invoke(three: ThreePlugin, obj: T): Object3D
|
||||
|
||||
public companion object {
|
||||
public const val TYPE: String = "threeFactory"
|
||||
@ -47,10 +46,10 @@ public fun Object3D.updatePosition(obj: Vision) {
|
||||
*/
|
||||
public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
|
||||
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
||||
this.material = getMaterial(source, false)
|
||||
updateMaterialProperty(source, propertyName)
|
||||
} else if (
|
||||
propertyName.startsWith(Solid.POSITION_KEY)
|
||||
|| propertyName.startsWith(Solid.ROTATION)
|
||||
|| propertyName.startsWith(Solid.ROTATION_KEY)
|
||||
|| propertyName.startsWith(Solid.SCALE_KEY)
|
||||
) {
|
||||
//update position of mesh using this object
|
||||
|
@ -1,8 +1,8 @@
|
||||
package hep.dataforge.vision.solid.three
|
||||
|
||||
|
||||
import hep.dataforge.context.logger
|
||||
import hep.dataforge.vision.solid.SolidLabel
|
||||
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.TextBufferGeometry
|
||||
import info.laht.threekt.objects.Mesh
|
||||
@ -15,17 +15,19 @@ import kotlin.reflect.KClass
|
||||
public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
|
||||
override val type: KClass<in SolidLabel> get() = SolidLabel::class
|
||||
|
||||
override fun invoke(obj: SolidLabel): Object3D {
|
||||
override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
|
||||
val textGeo = TextBufferGeometry(obj.text, jsObject {
|
||||
font = obj.fontFamily
|
||||
size = 20
|
||||
height = 1
|
||||
curveSegments = 1
|
||||
})
|
||||
return Mesh(textGeo, getMaterial(obj,true)).apply {
|
||||
return Mesh(textGeo, ThreeMaterials.DEFAULT).apply {
|
||||
updateMaterial(obj)
|
||||
updatePosition(obj)
|
||||
obj.onPropertyChange(this@ThreeLabelFactory) { _ ->
|
||||
obj.onPropertyChange(three.updateScope) { _ ->
|
||||
//TODO
|
||||
three.logger.warn { "Label parameter change not implemented" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import kotlin.reflect.KClass
|
||||
public object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||
override val type: KClass<PolyLine> get() = PolyLine::class
|
||||
|
||||
override fun invoke(obj: PolyLine): Object3D {
|
||||
override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D {
|
||||
val geometry = Geometry().apply {
|
||||
vertices = Array(obj.points.size) { obj.points[it].toVector() }
|
||||
}
|
||||
@ -28,7 +28,7 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||
updatePosition(obj)
|
||||
//layers.enable(obj.layer)
|
||||
//add listener to object properties
|
||||
obj.onPropertyChange(this) { propertyName ->
|
||||
obj.onPropertyChange(three.updateScope) { propertyName ->
|
||||
updateProperty(obj, propertyName)
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,27 @@
|
||||
package hep.dataforge.vision.solid.three
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.int
|
||||
import hep.dataforge.values.string
|
||||
import hep.dataforge.vision.Colors
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.allStyles
|
||||
import hep.dataforge.vision.solid.SolidMaterial
|
||||
import info.laht.threekt.materials.LineBasicMaterial
|
||||
import info.laht.threekt.materials.Material
|
||||
import info.laht.threekt.materials.MeshBasicMaterial
|
||||
import info.laht.threekt.materials.MeshPhongMaterial
|
||||
import info.laht.threekt.math.Color
|
||||
import info.laht.threekt.objects.Mesh
|
||||
|
||||
|
||||
public object ThreeMaterials {
|
||||
public val DEFAULT_COLOR: Color = Color(Colors.darkgreen)
|
||||
public val DEFAULT: MeshBasicMaterial = MeshBasicMaterial().apply {
|
||||
color.set(DEFAULT_COLOR)
|
||||
cached = true
|
||||
}
|
||||
public val DEFAULT_LINE_COLOR: Color = Color(Colors.black)
|
||||
public val DEFAULT_LINE: LineBasicMaterial = LineBasicMaterial().apply {
|
||||
@ -54,11 +58,15 @@ public object ThreeMaterials {
|
||||
|
||||
private val materialCache = HashMap<Meta, Material>()
|
||||
|
||||
private fun buildMaterial(meta: Meta): Material {
|
||||
internal fun buildMaterial(meta: Meta): Material {
|
||||
return if (meta[SolidMaterial.SPECULAR_COLOR_KEY] != null) {
|
||||
MeshPhongMaterial().apply {
|
||||
color = meta[SolidMaterial.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
||||
specular = meta[SolidMaterial.SPECULAR_COLOR_KEY]!!.getColor()
|
||||
emissive = specular
|
||||
reflectivity = 1.0
|
||||
refractionRatio = 1.0
|
||||
shininess = 100.0
|
||||
opacity = meta[SolidMaterial.OPACITY_KEY]?.double ?: 1.0
|
||||
transparent = opacity < 1.0
|
||||
wireframe = meta[SolidMaterial.WIREFRAME_KEY].boolean ?: false
|
||||
@ -75,29 +83,40 @@ public object ThreeMaterials {
|
||||
}
|
||||
}
|
||||
|
||||
public fun getMaterial(vision3D: Vision, cache: Boolean): Material {
|
||||
val meta = vision3D.getProperty(SolidMaterial.MATERIAL_KEY).node ?: return DEFAULT
|
||||
return if (cache) {
|
||||
materialCache.getOrPut(meta) { buildMaterial(meta) }
|
||||
} else {
|
||||
buildMaterial(meta)
|
||||
internal fun cacheMeta(meta: Meta): Material = materialCache.getOrPut(meta) {
|
||||
buildMaterial(meta).apply {
|
||||
cached = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// internal fun getMaterial(vision: Vision, cache: Boolean): Material {
|
||||
// val meta = vision.getProperty(SolidMaterial.MATERIAL_KEY, inherit = true).node ?: return DEFAULT
|
||||
// return if (cache) {
|
||||
// materialCache.getOrPut(meta) {
|
||||
// buildMaterial(meta).apply {
|
||||
// cached = true
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// buildMaterial(meta)
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer color based on meta item
|
||||
*/
|
||||
public fun MetaItem<*>.getColor(): Color {
|
||||
public fun MetaItem.getColor(): Color {
|
||||
return when (this) {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||
is ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||
val int = value.int
|
||||
Color(int)
|
||||
} else {
|
||||
Color(this.value.string)
|
||||
}
|
||||
is MetaItem.NodeItem -> {
|
||||
is NodeItem -> {
|
||||
Color(
|
||||
node[Colors.RED_KEY]?.int ?: 0,
|
||||
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||
@ -107,3 +126,76 @@ public fun MetaItem<*>.getColor(): Color {
|
||||
}
|
||||
}
|
||||
|
||||
private var Material.cached: Boolean
|
||||
get() = userData["cached"] == true
|
||||
set(value) {
|
||||
userData["cached"] = value
|
||||
}
|
||||
|
||||
public fun Mesh.updateMaterial(vision: Vision) {
|
||||
//val meta = vision.getProperty(SolidMaterial.MATERIAL_KEY, inherit = true).node
|
||||
val ownMaterialMeta = vision.getOwnProperty(SolidMaterial.MATERIAL_KEY)
|
||||
val stylesMaterialMeta = vision.allStyles[SolidMaterial.MATERIAL_KEY]
|
||||
val parentMaterialMeta = vision.parent?.getProperty(
|
||||
SolidMaterial.MATERIAL_KEY,
|
||||
inherit = true,
|
||||
includeStyles = false,
|
||||
includeDefaults = false
|
||||
)
|
||||
|
||||
material = when {
|
||||
ownMaterialMeta == null && stylesMaterialMeta == null && parentMaterialMeta == null -> {
|
||||
//use default is not material properties are defined
|
||||
ThreeMaterials.DEFAULT
|
||||
}
|
||||
ownMaterialMeta == null && parentMaterialMeta == null -> {
|
||||
//If material is style-based, use cached
|
||||
ThreeMaterials.cacheMeta(stylesMaterialMeta.node ?: Meta.EMPTY)
|
||||
}
|
||||
else -> {
|
||||
vision.getProperty(SolidMaterial.MATERIAL_KEY).node?.let {
|
||||
ThreeMaterials.buildMaterial(it)
|
||||
} ?: ThreeMaterials.DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
|
||||
if (material.cached || propertyName == SolidMaterial.MATERIAL_KEY) {
|
||||
//generate a new material since cached material should not be changed
|
||||
updateMaterial(vision)
|
||||
} else {
|
||||
when (propertyName) {
|
||||
SolidMaterial.MATERIAL_COLOR_KEY -> {
|
||||
material.asDynamic().color = vision.getProperty(
|
||||
SolidMaterial.MATERIAL_COLOR_KEY,
|
||||
inherit = true,
|
||||
includeStyles = true,
|
||||
includeDefaults = false
|
||||
)?.getColor() ?: ThreeMaterials.DEFAULT_COLOR
|
||||
material.needsUpdate = true
|
||||
}
|
||||
SolidMaterial.MATERIAL_OPACITY_KEY -> {
|
||||
val opacity = vision.getProperty(
|
||||
SolidMaterial.MATERIAL_OPACITY_KEY,
|
||||
inherit = true,
|
||||
includeStyles = true,
|
||||
includeDefaults = false
|
||||
).double ?: 1.0
|
||||
material.asDynamic().opacity = opacity
|
||||
material.transparent = opacity < 1.0
|
||||
material.needsUpdate = true
|
||||
}
|
||||
SolidMaterial.MATERIAL_WIREFRAME_KEY -> {
|
||||
material.asDynamic().wireframe = vision.getProperty(
|
||||
SolidMaterial.MATERIAL_WIREFRAME_KEY,
|
||||
inherit = true,
|
||||
includeStyles = true,
|
||||
includeDefaults = false
|
||||
).boolean ?: false
|
||||
material.needsUpdate = true
|
||||
}
|
||||
else -> console.warn("Unrecognized material property: $propertyName")
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,9 @@ import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.visible
|
||||
import info.laht.threekt.core.Object3D
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import kotlin.collections.set
|
||||
@ -22,7 +25,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
|
||||
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
|
||||
private val compositeFactory = ThreeCompositeFactory(this)
|
||||
private val refFactory = ThreeReferenceFactory(this)
|
||||
|
||||
//TODO generate a separate supervisor update scope
|
||||
internal val updateScope: CoroutineScope get() = context
|
||||
|
||||
init {
|
||||
//Add specialized factories here
|
||||
@ -41,10 +46,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
as ThreeFactory<Solid>?
|
||||
}
|
||||
|
||||
public fun buildObject3D(obj: Solid): Object3D {
|
||||
return when (obj) {
|
||||
is ThreeVision -> obj.render()
|
||||
is SolidReference -> refFactory(obj)
|
||||
public fun buildObject3D(obj: Solid): Object3D = when (obj) {
|
||||
is ThreeVision -> obj.render(this)
|
||||
is SolidReferenceGroup -> ThreeReferenceFactory(this, obj)
|
||||
is SolidGroup -> {
|
||||
val group = ThreeGroup()
|
||||
obj.children.forEach { (token, child) ->
|
||||
@ -63,10 +67,10 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
updatePosition(obj)
|
||||
//obj.onChildrenChange()
|
||||
|
||||
obj.onPropertyChange(this) { name ->
|
||||
obj.onPropertyChange(updateScope) { name ->
|
||||
if (
|
||||
name.startsWith(Solid.POSITION_KEY) ||
|
||||
name.startsWith(Solid.ROTATION) ||
|
||||
name.startsWith(Solid.ROTATION_KEY) ||
|
||||
name.startsWith(Solid.SCALE_KEY)
|
||||
) {
|
||||
//update position of mesh using this object
|
||||
@ -76,15 +80,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
obj.onStructureChange(this) { nameToken, _, child ->
|
||||
// if (name.isEmpty()) {
|
||||
// logger.error { "Children change with empty name on $group" }
|
||||
// return@onChildrenChange
|
||||
// }
|
||||
|
||||
// val parentName = name.cutLast()
|
||||
// val childName = name.last()!!
|
||||
|
||||
obj.structureChanges.onEach { (nameToken, _, child) ->
|
||||
//removing old object
|
||||
findChild(nameToken.asName())?.let { oldChild ->
|
||||
oldChild.parent?.remove(oldChild)
|
||||
@ -99,21 +95,20 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
logger.error(ex) { "Failed to render $child" }
|
||||
}
|
||||
}
|
||||
}.launchIn(updateScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Composite -> compositeFactory(obj)
|
||||
is Composite -> compositeFactory(this, obj)
|
||||
else -> {
|
||||
//find specialized factory for this type if it is present
|
||||
val factory: ThreeFactory<Solid>? = findObjectFactory(obj::class)
|
||||
when {
|
||||
factory != null -> factory(obj)
|
||||
obj is GeometrySolid -> ThreeShapeFactory(obj)
|
||||
factory != null -> factory(this, obj)
|
||||
obj is GeometrySolid -> ThreeShapeFactory(this, obj)
|
||||
else -> error("Renderer for ${obj::class} not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun createCanvas(
|
||||
element: Element,
|
||||
@ -133,14 +128,24 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
return if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
|
||||
}
|
||||
|
||||
public fun renderSolid(
|
||||
element: Element,
|
||||
vision: Solid,
|
||||
options: Canvas3DOptions,
|
||||
): ThreeCanvas = createCanvas(element, options).apply {
|
||||
render(vision)
|
||||
}
|
||||
|
||||
override fun render(element: Element, vision: Vision, meta: Meta) {
|
||||
createCanvas(element, Canvas3DOptions.read(meta)).render(
|
||||
vision as? Solid ?: error("Solid expected but ${vision::class} is found")
|
||||
renderSolid(
|
||||
element,
|
||||
vision as? Solid ?: error("Solid expected but ${vision::class} is found"),
|
||||
Canvas3DOptions.read(meta)
|
||||
)
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<ThreePlugin> {
|
||||
override val tag: PluginTag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
|
||||
override val tag: PluginTag = PluginTag("vision.threejs", PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<ThreePlugin> = ThreePlugin::class
|
||||
override fun invoke(meta: Meta, context: Context): ThreePlugin = ThreePlugin()
|
||||
}
|
||||
@ -150,7 +155,7 @@ public fun ThreePlugin.render(
|
||||
element: HTMLElement,
|
||||
obj: Solid,
|
||||
options: Canvas3DOptions.() -> Unit = {},
|
||||
): ThreeCanvas = createCanvas(element, Canvas3DOptions(options)).apply { render(obj) }
|
||||
): ThreeCanvas = renderSolid(element, obj, Canvas3DOptions(options))
|
||||
|
||||
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
||||
object3D.name = token.toString()
|
||||
|
@ -1,20 +1,22 @@
|
||||
package hep.dataforge.vision.solid.three
|
||||
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.names.cutFirst
|
||||
import hep.dataforge.names.firstOrNull
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.SolidReference
|
||||
import hep.dataforge.vision.solid.SolidReference.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
|
||||
import hep.dataforge.vision.solid.SolidMaterial
|
||||
import hep.dataforge.vision.solid.SolidReferenceGroup
|
||||
import hep.dataforge.vision.solid.SolidReferenceGroup.Companion.REFERENCE_CHILD_PROPERTY_PREFIX
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory<SolidReference> {
|
||||
public object ThreeReferenceFactory : ThreeFactory<SolidReferenceGroup> {
|
||||
private val cache = HashMap<Solid, Object3D>()
|
||||
|
||||
override val type: KClass<SolidReference> = SolidReference::class
|
||||
override val type: KClass<SolidReferenceGroup> = SolidReferenceGroup::class
|
||||
|
||||
private fun Object3D.replicate(): Object3D {
|
||||
return when (this) {
|
||||
@ -30,7 +32,7 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(obj: SolidReference): Object3D {
|
||||
override fun invoke(three: ThreePlugin, obj: SolidReferenceGroup): Object3D {
|
||||
val template = obj.prototype
|
||||
val cachedObject = cache.getOrPut(template) {
|
||||
three.buildObject3D(template)
|
||||
@ -40,10 +42,13 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
|
||||
object3D.updatePosition(obj)
|
||||
|
||||
if(object3D is Mesh){
|
||||
//object3D.material = ThreeMaterials.buildMaterial(obj.getProperty(SolidMaterial.MATERIAL_KEY).node!!)
|
||||
object3D.applyProperties(obj)
|
||||
}
|
||||
|
||||
obj.onPropertyChange(this) { name ->
|
||||
//TODO apply child properties
|
||||
|
||||
obj.onPropertyChange(three.updateScope) { name->
|
||||
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.firstOrNull()?.index?.toName() ?: error("Wrong syntax for reference child property: '$name'")
|
||||
val propertyName = name.cutFirst()
|
||||
@ -55,6 +60,7 @@ public class ThreeReferenceFactory(public val three: ThreePlugin) : ThreeFactory
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return object3D
|
||||
}
|
||||
}
|
@ -7,5 +7,5 @@ import info.laht.threekt.core.Object3D
|
||||
* A custom visual object that has its own Three.js renderer
|
||||
*/
|
||||
public abstract class ThreeVision : SolidBase() {
|
||||
public abstract fun render(): Object3D
|
||||
public abstract fun render(three: ThreePlugin): Object3D
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import kotlin.math.PI
|
||||
|
||||
public val Solid.euler: Euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
||||
|
||||
public val MetaItem<*>.vector: Vector3 get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f)
|
||||
public val MetaItem.vector: Vector3 get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f)
|
||||
|
||||
public fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
||||
package info.laht.threekt.helpers
|
||||
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.lights.HemiSphereLight
|
||||
import info.laht.threekt.lights.HemisphereLight
|
||||
import info.laht.threekt.lights.Light
|
||||
|
||||
/**
|
||||
@ -39,7 +39,7 @@ import info.laht.threekt.lights.Light
|
||||
* @param color (optional) if this is not the set the helper will take the color of the light.
|
||||
*/
|
||||
external class HemisphereLightHelper(
|
||||
light: HemiSphereLight,
|
||||
light: HemisphereLight,
|
||||
size: Number,
|
||||
color: Int = definedExternally
|
||||
) : Object3D {
|
||||
|
@ -34,7 +34,7 @@ import info.laht.threekt.math.Color
|
||||
*
|
||||
* This light cannot be used to cast shadows.
|
||||
*/
|
||||
external class HemiSphereLight(
|
||||
external class HemisphereLight(
|
||||
skyColor: Int = definedExternally,
|
||||
groundColor: Int = definedExternally,
|
||||
intensity: Number = definedExternally
|
||||
@ -42,6 +42,6 @@ external class HemiSphereLight(
|
||||
|
||||
var groundColor: Color
|
||||
|
||||
fun copy(light: HemiSphereLight): HemiSphereLight
|
||||
fun copy(light: HemisphereLight): HemisphereLight
|
||||
|
||||
}
|
@ -111,7 +111,7 @@ open external class Material {
|
||||
|
||||
var visible: Boolean
|
||||
|
||||
var userData: Map<String, Any>
|
||||
var userData: dynamic
|
||||
|
||||
/**
|
||||
* Specifies that the material needs to be updated at the WebGL level. Set it to true if you made changes that need to be reflected in WebGL.
|
||||
|
@ -1,8 +0,0 @@
|
||||
package hep.dataforge.vision.three.server
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.vision.VisionManager
|
||||
|
||||
public expect val visionContext: Context
|
||||
|
||||
public val visionManager: VisionManager get() = visionContext.plugins.fetch(VisionManager)
|
@ -1,46 +1,18 @@
|
||||
package hep.dataforge.vision.three.server
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.vision.client.VisionClient
|
||||
import hep.dataforge.vision.client.renderAllVisions
|
||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||
import kotlinx.browser.window
|
||||
|
||||
//FIXME check plugin loading in JS
|
||||
//public actual val visionContext: Context = Global.context("vision-client") {
|
||||
// //Loading three-js renderer
|
||||
// plugin(ThreePlugin)
|
||||
//}
|
||||
|
||||
public actual val visionContext: Context = Global.context("vision-client").apply {
|
||||
//Loading three-js renderer
|
||||
plugins.fetch(ThreePlugin)
|
||||
}
|
||||
|
||||
public val clientManager: VisionClient get() = visionContext.plugins.fetch(VisionClient)
|
||||
|
||||
|
||||
///**
|
||||
// * Render all visions in the document using registered renderers
|
||||
// */
|
||||
//@JsExport
|
||||
//public fun renderVisions() {
|
||||
// //Fetch from server and render visions for all outputs
|
||||
// window.onload = {
|
||||
// clientManager.renderAllVisions()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Render all visions in a given element, using registered renderers
|
||||
// */
|
||||
//@JsExport
|
||||
//public fun renderAllVisionsAt(element: Element) {
|
||||
// clientManager.renderAllVisionsAt(element)
|
||||
//}
|
||||
|
||||
public fun main() {
|
||||
//Loading three-js renderer
|
||||
val visionContext = Global.context("threejs") {
|
||||
plugin(ThreePlugin)
|
||||
}
|
||||
val clientManager = visionContext.plugins.fetch(VisionClient)
|
||||
|
||||
//Fetch from server and render visions for all outputs
|
||||
window.onload = {
|
||||
clientManager.renderAllVisions()
|
||||
|
@ -1,45 +1,25 @@
|
||||
package hep.dataforge.vision.three.server
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.meta.DFExperimental
|
||||
import hep.dataforge.vision.ResourceLocation
|
||||
import hep.dataforge.vision.html.HtmlVisionFragment
|
||||
import hep.dataforge.vision.html.VisionOutput
|
||||
import hep.dataforge.vision.makeFile
|
||||
import hep.dataforge.vision.makeVisionFile
|
||||
import hep.dataforge.vision.scriptHeader
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
public actual val visionContext: Context = Global.context("vision-server") {
|
||||
//Loading solid manager for the backend (it does not know about three
|
||||
plugin(SolidManager)
|
||||
}
|
||||
|
||||
public fun VisionServer.useThreeJs(): Unit {
|
||||
useScript("js/visionforge-three.js")
|
||||
// header {
|
||||
// script {
|
||||
// unsafe {
|
||||
// +"renderThreeVisions()"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block)
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
public fun HtmlVisionFragment.makeFile(
|
||||
public fun Context.makeVisionFile(
|
||||
fragment: HtmlVisionFragment,
|
||||
path: Path? = null,
|
||||
title: String = "VisionForge page",
|
||||
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
|
||||
show: Boolean = true,
|
||||
) {
|
||||
val actualPath = path ?: Files.createTempFile("tempPlot", ".html")
|
||||
val scriptHeader = Context.scriptHeader("/js/visionforge-three.js", actualPath, resourceLocation)
|
||||
makeFile(visionManager, path = path, show = show, title = title, headers = arrayOf(scriptHeader))
|
||||
): Unit = makeVisionFile(fragment, path = path, title = title, show = show) { actualPath ->
|
||||
scriptHeader("js/visionforge-three.js", actualPath, resourceLocation)
|
||||
}
|
Loading…
Reference in New Issue
Block a user