Compare commits

..

25 Commits

Author SHA1 Message Date
67afa4e45b
Fix light 2022-08-15 09:47:56 +03:00
cb25dca34c
Refactor three package. Add MeshLine 2022-08-14 22:03:46 +03:00
98bb935de5
Optimizations... optimizations 2022-08-14 20:28:47 +03:00
e2f281debe
Optimizations... optimizations 2022-08-14 17:22:10 +03:00
ac651c4d50
Fix reference property resolution 2022-08-14 14:41:22 +03:00
846e87a44b
Fix flaky properties test 2022-08-14 14:25:44 +03:00
34fbb23c60
Cleanup vision root rules 2022-08-14 12:52:18 +03:00
eeec89f0e6
Fix property editor 2022-08-14 10:47:36 +03:00
43362f51f5
Fix styling for Prototypes 2022-08-13 20:47:03 +03:00
c586a2ea14
Fix (almost) property resolution 2022-08-13 18:17:22 +03:00
ecf4a6a198
Add property flows 2022-08-13 12:45:10 +03:00
0ea1ee056a
All tests pass 2022-08-12 22:16:06 +03:00
9221df785d
[WIP] Completed solid refactoring 2022-08-09 14:53:46 +03:00
47bde02488
[WIP] Completed solid refactoring 2022-08-09 12:49:01 +03:00
c71042ae06
[WIP] great refactoring in progress 2022-08-08 22:17:06 +03:00
9b1ca8332b
[WIP] great refactoring in progress 2022-08-07 20:33:05 +03:00
791d6d7a81
[WIP] great refactoring in progress 2022-08-04 21:36:00 +03:00
4b1149b99b
Migrate to new build tools and DF 0.6 2022-07-06 11:11:48 +03:00
86935ce52a
Fix light in GDML demo 2022-05-24 23:09:40 +03:00
ce02a18c09
Fix mesh conversion and lightning for examples 2022-05-24 23:00:10 +03:00
212d729afb
A prototype for context receivers 2022-04-15 12:46:05 +03:00
3198bad094
Update build. 2022-04-13 17:08:25 +03:00
9648533ac8
Update build tools 2022-04-13 15:02:35 +03:00
7ee40679b9
Update light descriptor 2022-01-28 14:17:37 +03:00
f828f86e29
Replace light model for 3ds 2022-01-27 12:10:00 +03:00
360 changed files with 3640 additions and 2635 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ build/
data/ data/
!gradle-wrapper.jar !gradle-wrapper.jar
/kotlin-js-store/yarn.lock

View File

@ -2,8 +2,16 @@
## [Unreleased] ## [Unreleased]
### Added ### Added
- Context receivers flag
- MeshLine for thick lines
### Changed ### Changed
- Visions **must** be rooted in order to subscribe to updates.
- Visions use flows instead of direct subscriptions.
- Radical change of inner workings of vision children and properties.
- Three package changed to `three`.
- Naming of Canvas3D options.
- Lights are added to the scene instead of 3D options.
### Deprecated ### Deprecated

View File

@ -1,14 +1,14 @@
plugins { plugins {
id("ru.mipt.npm.gradle.project") id("space.kscience.gradle.project")
id("org.jetbrains.kotlinx.kover") version "0.5.0-RC" // id("org.jetbrains.kotlinx.kover") version "0.5.0"
} }
val dataforgeVersion by extra("0.5.2") val dataforgeVersion by extra("0.6.0-dev-13")
val fxVersion by extra("11") val fxVersion by extra("11")
allprojects{ allprojects{
group = "space.kscience" group = "space.kscience"
version = "0.2.0" version = "0.3.0-dev-2"
} }
subprojects { subprojects {
@ -19,6 +19,12 @@ subprojects {
mavenCentral() mavenCentral()
maven("https://maven.jzy3d.org/releases") maven("https://maven.jzy3d.org/releases")
} }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>{
kotlinOptions{
freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers"
}
}
} }
ksciencePublish { ksciencePublish {
@ -32,3 +38,8 @@ apiValidation {
} }
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md") readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
//rootProject.extensions.configure<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension> {
// versions.webpackCli.version = "4.10.0"
//}

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
kscience{ kscience{

View File

@ -5,7 +5,6 @@ import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.Named import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.doubleArray
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
public fun MetaProvider.doubleArray( public fun MetaProvider.doubleArray(

View File

@ -1,10 +1,14 @@
package ru.mipt.npm.root package ru.mipt.npm.root
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.doubleArray
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.doubleArray import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.isEmpty import space.kscience.visionforge.isEmpty
import space.kscience.visionforge.set
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY import space.kscience.visionforge.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY
import kotlin.math.* import kotlin.math.*
@ -20,7 +24,7 @@ private fun degToRad(d: Double) = d * PI / 180.0
private data class RootToSolidContext( private data class RootToSolidContext(
val prototypeHolder: PrototypeHolder, val prototypeHolder: PrototypeHolder,
val currentLayer: Int = 0, val currentLayer: Int = 0,
val maxLayer: Int = 5 val maxLayer: Int = 5,
) )
// converting to XYZ to TaitBryan angles according to https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix // converting to XYZ to TaitBryan angles according to https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
@ -42,14 +46,17 @@ private fun Solid.useMatrix(matrix: DGeoMatrix?) {
"TGeoIdentity" -> { "TGeoIdentity" -> {
//do nothing //do nothing
} }
"TGeoTranslation" -> { "TGeoTranslation" -> {
val fTranslation by matrix.meta.doubleArray() val fTranslation by matrix.meta.doubleArray()
translate(fTranslation) translate(fTranslation)
} }
"TGeoRotation" -> { "TGeoRotation" -> {
val fRotationMatrix by matrix.meta.doubleArray() val fRotationMatrix by matrix.meta.doubleArray()
rotate(fRotationMatrix) rotate(fRotationMatrix)
} }
"TGeoCombiTrans" -> { "TGeoCombiTrans" -> {
val fTranslation by matrix.meta.doubleArray() val fTranslation by matrix.meta.doubleArray()
@ -58,6 +65,7 @@ private fun Solid.useMatrix(matrix: DGeoMatrix?) {
rotate(it.doubleArray) rotate(it.doubleArray)
} }
} }
"TGeoHMatrix" -> { "TGeoHMatrix" -> {
val fTranslation by matrix.meta.doubleArray() val fTranslation by matrix.meta.doubleArray()
val fRotationMatrix by matrix.meta.doubleArray() val fRotationMatrix by matrix.meta.doubleArray()
@ -73,7 +81,7 @@ private fun SolidGroup.addShape(
shape: DGeoShape, shape: DGeoShape,
context: RootToSolidContext, context: RootToSolidContext,
name: String? = shape.fName.ifEmpty { null }, name: String? = shape.fName.ifEmpty { null },
block: Solid.() -> Unit = {} block: Solid.() -> Unit = {},
) { ) {
when (shape.typename) { when (shape.typename) {
"TGeoCompositeShape" -> { "TGeoCompositeShape" -> {
@ -94,6 +102,7 @@ private fun SolidGroup.addShape(
} }
}.apply(block) }.apply(block)
} }
"TGeoXtru" -> { "TGeoXtru" -> {
val fNvert by shape.meta.int(0) val fNvert by shape.meta.int(0)
val fX by shape.meta.doubleArray() val fX by shape.meta.doubleArray()
@ -121,6 +130,7 @@ private fun SolidGroup.addShape(
} }
}.apply(block) }.apply(block)
} }
"TGeoTube" -> { "TGeoTube" -> {
val fRmax by shape.meta.double(0.0) val fRmax by shape.meta.double(0.0)
val fDz by shape.meta.double(0.0) val fDz by shape.meta.double(0.0)
@ -134,6 +144,7 @@ private fun SolidGroup.addShape(
block = block block = block
) )
} }
"TGeoTubeSeg" -> { "TGeoTubeSeg" -> {
val fRmax by shape.meta.double(0.0) val fRmax by shape.meta.double(0.0)
val fDz by shape.meta.double(0.0) val fDz by shape.meta.double(0.0)
@ -151,6 +162,7 @@ private fun SolidGroup.addShape(
block = block block = block
) )
} }
"TGeoPcon" -> { "TGeoPcon" -> {
val fDphi by shape.meta.double(0.0) val fDphi by shape.meta.double(0.0)
val fNz by shape.meta.int(2) val fNz by shape.meta.int(2)
@ -176,6 +188,7 @@ private fun SolidGroup.addShape(
TODO() TODO()
} }
} }
"TGeoPgon" -> { "TGeoPgon" -> {
//TODO add a inner polygone layer //TODO add a inner polygone layer
val fDphi by shape.meta.double(0.0) val fDphi by shape.meta.double(0.0)
@ -206,15 +219,18 @@ private fun SolidGroup.addShape(
} }
}.apply(block) }.apply(block)
} }
"TGeoShapeAssembly" -> { "TGeoShapeAssembly" -> {
val fVolume by shape.dObject(::DGeoVolume) val fVolume by shape.dObject(::DGeoVolume)
fVolume?.let { volume -> fVolume?.let { volume ->
addRootVolume(volume, context, block = block) addRootVolume(volume, context, block = block)
} }
} }
"TGeoBBox" -> { "TGeoBBox" -> {
box(shape.fDX * 2, shape.fDY * 2, shape.fDZ * 2, name = name, block = block) box(shape.fDX * 2, shape.fDY * 2, shape.fDZ * 2, name = name, block = block)
} }
"TGeoTrap" -> { "TGeoTrap" -> {
val fTheta by shape.meta.double(0.0) val fTheta by shape.meta.double(0.0)
val fPhi by shape.meta.double(0.0) val fPhi by shape.meta.double(0.0)
@ -242,17 +258,19 @@ private fun SolidGroup.addShape(
val node8 = Point3D(-fTl2, fH2, fDz) val node8 = Point3D(-fTl2, fH2, fDz)
hexagon(node1, node2, node3, node4, node5, node6, node7, node8, name) hexagon(node1, node2, node3, node4, node5, node6, node7, node8, name)
} }
"TGeoScaledShape" -> { "TGeoScaledShape" -> {
val fShape by shape.dObject(::DGeoShape) val fShape by shape.dObject(::DGeoShape)
val fScale by shape.dObject(::DGeoScale) val fScale by shape.dObject(::DGeoScale)
fShape?.let { scaledShape -> fShape?.let { scaledShape ->
group(name?.let { Name.parse(it) }) { solidGroup(name?.let { Name.parse(it) }) {
scale = Point3D(fScale?.x ?: 1.0, fScale?.y ?: 1.0, fScale?.z ?: 1.0) scale = Point3D(fScale?.x ?: 1.0, fScale?.y ?: 1.0, fScale?.z ?: 1.0)
addShape(scaledShape, context) addShape(scaledShape, context)
apply(block) apply(block)
} }
} }
} }
else -> { else -> {
TODO("A shape with type ${shape.typename} not implemented") TODO("A shape with type ${shape.typename} not implemented")
} }
@ -267,6 +285,7 @@ private fun SolidGroup.addRootNode(obj: DGeoNode, context: RootToSolidContext) {
val fMatrix by obj.dObject(::DGeoMatrix) val fMatrix by obj.dObject(::DGeoMatrix)
this.useMatrix(fMatrix) this.useMatrix(fMatrix)
} }
"TGeoNodeOffset" -> { "TGeoNodeOffset" -> {
val fOffset by obj.meta.double(0.0) val fOffset by obj.meta.double(0.0)
x = fOffset x = fOffset
@ -276,7 +295,7 @@ private fun SolidGroup.addRootNode(obj: DGeoNode, context: RootToSolidContext) {
} }
private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid? { private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid? {
val group = SolidGroup { val group = SolidGroup().apply {
//set current layer //set current layer
layer = context.currentLayer layer = context.currentLayer
val nodes = volume.fNodes val nodes = volume.fNodes
@ -301,10 +320,10 @@ private fun buildVolume(volume: DGeoVolume, context: RootToSolidContext): Solid?
} }
} }
} }
return if (group.isEmpty()) { return if (group.children.isEmpty()) {
null null
} else if (group.children.size == 1 && group.meta.isEmpty()) { } else if (group.items.size == 1 && group.properties.own == null) {
(group.children.values.first() as Solid).apply { parent = null } group.items.values.first().apply { parent = null }
} else { } else {
group group
} }
@ -317,7 +336,7 @@ private fun SolidGroup.addRootVolume(
context: RootToSolidContext, context: RootToSolidContext,
name: String? = null, name: String? = null,
cache: Boolean = true, cache: Boolean = true,
block: Solid.() -> Unit = {} block: Solid.() -> Unit = {},
) { ) {
val combinedName = if (volume.fName.isEmpty()) { val combinedName = if (volume.fName.isEmpty()) {
name name
@ -330,33 +349,33 @@ private fun SolidGroup.addRootVolume(
if (!cache) { if (!cache) {
val group = buildVolume(volume, context)?.apply { val group = buildVolume(volume, context)?.apply {
volume.fFillColor?.let { volume.fFillColor?.let {
meta[MATERIAL_COLOR_KEY] = RootColors[it] properties[MATERIAL_COLOR_KEY] = RootColors[it]
} }
block() block()
} }
set(combinedName?.let { Name.parse(it) }, group) setChild(combinedName?.let { Name.parse(it) }, group)
} else { } else {
val templateName = volumesName + volume.name val templateName = volumesName + volume.name
val existing = getPrototype(templateName) val existing = getPrototype(templateName)
if (existing == null) { if (existing == null) {
context.prototypeHolder.prototypes { context.prototypeHolder.prototypes {
val group = buildVolume(volume, context) val group = buildVolume(volume, context)
set(templateName, group) setChild(templateName, group)
} }
} }
ref(templateName, name).apply { ref(templateName, name).apply {
volume.fFillColor?.let { volume.fFillColor?.let {
meta[MATERIAL_COLOR_KEY] = RootColors[it] properties[MATERIAL_COLOR_KEY] = RootColors[it]
} }
block() block()
} }
} }
} }
public fun DGeoManager.toSolid(): SolidGroup = SolidGroup { public fun MutableVisionContainer<Solid>.rootGeo(dGeoManager: DGeoManager): SolidGroup = solidGroup {
val context = RootToSolidContext(this) val context = RootToSolidContext(this)
fNodes.forEach { node -> dGeoManager.fNodes.forEach { node ->
addRootNode(node, context) addRootNode(node, context)
} }
} }

View File

@ -14,7 +14,7 @@ import kotlinx.serialization.modules.subclass
private fun <T> jsonRootDeserializer( private fun <T> jsonRootDeserializer(
tSerializer: KSerializer<T>, tSerializer: KSerializer<T>,
builder: (JsonElement) -> T builder: (JsonElement) -> T,
): DeserializationStrategy<T> = object : ): DeserializationStrategy<T> = object :
DeserializationStrategy<T> { DeserializationStrategy<T> {
private val jsonElementSerializer = JsonElement.serializer() private val jsonElementSerializer = JsonElement.serializer()
@ -46,6 +46,7 @@ private object RootDecoder {
private val refCache: List<RefEntry>, private val refCache: List<RefEntry>,
) : KSerializer<T> by tSerializer { ) : KSerializer<T> by tSerializer {
@OptIn(ExperimentalSerializationApi::class)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun deserialize(decoder: Decoder): T { override fun deserialize(decoder: Decoder): T {
val input = decoder as JsonDecoder val input = decoder as JsonDecoder
@ -90,9 +91,8 @@ private object RootDecoder {
private fun <T> KSerializer<T>.unref(refCache: List<RefEntry>): KSerializer<T> = RootUnrefSerializer(this, refCache) private fun <T> KSerializer<T>.unref(refCache: List<RefEntry>): KSerializer<T> = RootUnrefSerializer(this, refCache)
@OptIn(ExperimentalSerializationApi::class)
fun unrefSerializersModule( fun unrefSerializersModule(
refCache: List<RefEntry> refCache: List<RefEntry>,
): SerializersModule = SerializersModule { ): SerializersModule = SerializersModule {
contextual(TObjArray::class) { contextual(TObjArray::class) {
@ -197,11 +197,13 @@ private object RootDecoder {
fillCache(it) fillCache(it)
} }
} }
is JsonArray -> { is JsonArray -> {
element.forEach { element.forEach {
fillCache(it) fillCache(it)
} }
} }
else -> { else -> {
//ignore primitives //ignore primitives
} }

View File

@ -3,6 +3,7 @@ package ru.mipt.npm.root.serialization
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.atan2 import kotlin.math.atan2
@ -132,7 +133,7 @@ private fun buildGroup(volume: TGeoVolume): SolidGroup {
return if (volume is TGeoVolumeAssemblyRef) { return if (volume is TGeoVolumeAssemblyRef) {
buildGroup(volume.value) buildGroup(volume.value)
} else { } else {
SolidGroup { SolidGroup().apply {
volume.fShape?.let { addShape(it) } volume.fShape?.let { addShape(it) }
volume.fNodes?.let { volume.fNodes?.let {
it.arr.forEach { obj -> it.arr.forEach { obj ->
@ -160,7 +161,7 @@ private fun SolidGroup.volume(volume: TGeoVolume, name: String? = null, cache: B
name = combinedName, name = combinedName,
obj = group, obj = group,
prototypeHolder = rootPrototypes, prototypeHolder = rootPrototypes,
templateName = volumesName + Name.parse(combinedName ?: "volume[${group.hashCode()}]") prototypeName = volumesName + Name.parse(combinedName ?: "volume[${group.hashCode()}]")
) )
} }
@ -180,8 +181,8 @@ private fun SolidGroup.volume(volume: TGeoVolume, name: String? = null, cache: B
// } // }
public fun TGeoManager.toSolid(): SolidGroup = SolidGroup { public fun MutableVisionContainer<Solid>.rootGeo(tGeoManager: TGeoManager): SolidGroup = solidGroup {
fNodes.arr.forEach { tGeoManager.fNodes.arr.forEach {
node(it) node(it)
} }
} }

View File

@ -1,8 +1,8 @@
import ru.mipt.npm.gradle.DependencyConfiguration import space.kscience.gradle.DependencyConfiguration
import ru.mipt.npm.gradle.FXModule import space.kscience.gradle.FXModule
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
application application
} }
@ -36,7 +36,7 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
implementation(project(":visionforge-fx")) implementation(project(":visionforge-fx"))
implementation("ch.qos.logback:logback-classic:1.2.5") implementation("ch.qos.logback:logback-classic:1.2.11")
} }
} }
jsMain { jsMain {

View File

@ -1,13 +1,11 @@
package space.kscience.visionforge.gdml package space.kscience.visionforge.gdml
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.string
import space.kscience.gdml.GdmlShowCase import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties import space.kscience.visionforge.getChild
import space.kscience.visionforge.get
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidMaterial import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.material import space.kscience.visionforge.solid.material
@ -20,8 +18,8 @@ class GDMLVisionTest {
@Test @Test
fun testCubesStyles(){ fun testCubesStyles(){
val segment = cubes["composite-000.segment-0"] as Solid val segment = cubes.children.getChild("composite-000.segment-0") as Solid
println(segment.computeProperties().getValue(Vision.STYLE_KEY)) println(segment.properties.getValue(Vision.STYLE_KEY))
// println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY)) // println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY))
// println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY)) // println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY))
@ -35,7 +33,7 @@ class GDMLVisionTest {
fun testPrototypeProperty() { fun testPrototypeProperty() {
val child = cubes[Name.of("composite-000","segment-0")] val child = cubes[Name.of("composite-000","segment-0")]
assertNotNull(child) assertNotNull(child)
child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue()) child.properties.setValue(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.string) assertEquals("red", child.properties.getProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
} }
} }

View File

@ -10,13 +10,11 @@ import org.w3c.files.get
import react.Props import react.Props
import react.dom.h2 import react.dom.h2
import react.fc import react.fc
import react.useMemo
import react.useState import react.useState
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.gdml.Gdml import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromString import space.kscience.gdml.decodeFromString
import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.markLayers import space.kscience.visionforge.gdml.markLayers
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeCanvasWithControls
@ -24,18 +22,19 @@ import space.kscience.visionforge.ring.tab
import space.kscience.visionforge.setAsRoot import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
external interface GDMLAppProps : Props { external interface GDMLAppProps : Props {
var context: Context var solids: Solids
var vision: Solid? var vision: Solid?
var selected: Name? var selected: Name?
} }
@JsExport @JsExport
val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props -> val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager }
var deferredVision: Deferred<Solid?> by useState { var deferredVision: Deferred<Solid?> by useState {
CompletableDeferred(props.vision) CompletableDeferred(props.vision)
} }
@ -50,12 +49,15 @@ val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
name.endsWith(".gdml") || name.endsWith(".xml") -> { name.endsWith(".gdml") || name.endsWith(".xml") -> {
val gdml = Gdml.decodeFromString(data) val gdml = Gdml.decodeFromString(data)
gdml.toVision().apply { gdml.toVision().apply {
setAsRoot(visionManager) setAsRoot(props.solids.visionManager)
console.info("Marking layers for file $name") console.info("Marking layers for file $name")
markLayers() markLayers()
ambientLight {
color.set(Colors.white)
}
} }
} }
name.endsWith(".json") -> visionManager.decodeFromString(data) name.endsWith(".json") -> props.solids.visionManager.decodeFromString(data)
else -> { else -> {
window.alert("File extension is not recognized: $name") window.alert("File extension is not recognized: $name")
error("File extension is not recognized: $name") error("File extension is not recognized: $name")
@ -76,7 +78,7 @@ val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
} }
child(ThreeCanvasWithControls) { child(ThreeCanvasWithControls) {
attrs { attrs {
this.context = props.context this.solids = props.solids
this.builderOfSolid = deferredVision this.builderOfSolid = deferredVision
this.selected = props.selected this.selected = props.selected
tab("Load") { tab("Load") {

View File

@ -2,11 +2,17 @@ package space.kscience.visionforge.gdml.demo
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.css.* import kotlinx.css.*
import react.dom.render import react.dom.client.createRoot
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.gdml.GdmlShowCase import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Application import space.kscience.visionforge.Application
import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication import space.kscience.visionforge.startApplication
import styled.injectGlobal import styled.injectGlobal
@ -39,12 +45,16 @@ private class GDMLDemoApp : Application {
val element = document.getElementById("application") ?: error("Element with id 'application' not found on page") val element = document.getElementById("application") ?: error("Element with id 'application' not found on page")
render(element) { createRoot(element).render {
child(GDMLApp) { child(GDMLApp) {
val vision = GdmlShowCase.cubes().toVision() val vision = GdmlShowCase.cubes().toVision().apply {
ambientLight {
color.set(Colors.white)
}
}
//println(context.plugins.fetch(VisionManager).encodeToString(vision)) //println(context.plugins.fetch(VisionManager).encodeToString(vision))
attrs { attrs {
this.context = context this.solids = context.fetch(Solids)
this.vision = vision this.vision = vision
} }
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
kscience{ kscience{

View File

@ -1,15 +1,17 @@
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.css.* import kotlinx.css.*
import react.child import react.dom.client.createRoot
import react.dom.render
import ringui.SmartTabs import ringui.SmartTabs
import ringui.Tab import ringui.Tab
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.plotly.models.Trace import space.kscience.plotly.models.Trace
import space.kscience.plotly.scatter import space.kscience.plotly.scatter
import space.kscience.visionforge.Application import space.kscience.visionforge.Application
import space.kscience.visionforge.Colors
import space.kscience.visionforge.VisionClient import space.kscience.visionforge.VisionClient
import space.kscience.visionforge.plotly.PlotlyPlugin import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.react.render
import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.ring.ThreeWithControlsPlugin
import space.kscience.visionforge.ring.solid import space.kscience.visionforge.ring.solid
@ -38,7 +40,7 @@ private class JsPlaygroundApp : Application {
val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page") val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page")
render(element) { createRoot(element).render {
styledDiv { styledDiv {
css { css {
padding(0.pt) padding(0.pt)
@ -48,9 +50,9 @@ private class JsPlaygroundApp : Application {
} }
SmartTabs("gravity") { SmartTabs("gravity") {
Tab("gravity") { Tab("gravity") {
GravityDemo{ GravityDemo {
attrs { attrs {
this.context = playgroundContext this.solids = playgroundContext.fetch(Solids)
} }
} }
} }
@ -71,15 +73,18 @@ private class JsPlaygroundApp : Application {
child(ThreeCanvasWithControls) { child(ThreeCanvasWithControls) {
val random = Random(112233) val random = Random(112233)
attrs { attrs {
context = playgroundContext solids = playgroundContext.fetch(Solids)
solid { solid {
ambientLight {
color.set(Colors.white)
}
repeat(100) { repeat(100) {
sphere(5, name = "sphere[$it]") { sphere(5, name = "sphere[$it]") {
x = random.nextDouble(-300.0, 300.0) x = random.nextDouble(-300.0, 300.0)
y = random.nextDouble(-300.0, 300.0) y = random.nextDouble(-300.0, 300.0)
z = random.nextDouble(-300.0, 300.0) z = random.nextDouble(-300.0, 300.0)
material { material {
color(random.nextInt()) color.set(random.nextInt())
} }
detail = 16 detail = 16
} }

View File

@ -4,9 +4,9 @@ import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import react.Props import react.Props
import react.fc import react.fc
import space.kscience.dataforge.context.Context
import space.kscience.plotly.layout import space.kscience.plotly.layout
import space.kscience.plotly.models.Trace import space.kscience.plotly.models.Trace
import space.kscience.visionforge.Colors
import space.kscience.visionforge.markup.VisionOfMarkup import space.kscience.visionforge.markup.VisionOfMarkup
import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeCanvasWithControls
@ -17,14 +17,14 @@ import styled.styledDiv
import kotlin.math.sqrt import kotlin.math.sqrt
external interface DemoProps : Props { external interface DemoProps : Props {
var context: Context var solids: Solids
} }
val GravityDemo = fc<DemoProps> { props -> val GravityDemo = fc<DemoProps> { props ->
val velocityTrace = Trace{ val velocityTrace = Trace {
name = "velocity" name = "velocity"
} }
val energyTrace = Trace{ val energyTrace = Trace {
name = "energy" name = "energy"
} }
val markup = VisionOfMarkup() val markup = VisionOfMarkup()
@ -39,14 +39,19 @@ val GravityDemo = fc<DemoProps> { props ->
} }
child(ThreeCanvasWithControls) { child(ThreeCanvasWithControls) {
attrs { attrs {
context = props.context solids = props.solids
solid { solid {
pointLight(200, 200, 200, name = "light"){
color.set(Colors.white)
}
ambientLight()
sphere(5.0, "ball") { sphere(5.0, "ball") {
detail = 16 detail = 16
color("red") color.set("red")
val h = 100.0 val h = 100.0
y = h y = h
context.launch { solids.context.launch {
val g = 10.0 val g = 10.0
val dt = 0.1 val dt = 0.1
var time = 0.0 var time = 0.0
@ -91,7 +96,7 @@ val GravityDemo = fc<DemoProps> { props ->
height = 50.vh - 50.pt height = 50.vh - 50.pt
} }
plotly { plotly {
traces(velocityTrace,energyTrace) traces(velocityTrace, energyTrace)
layout { layout {
xaxis.title = "time" xaxis.title = "time"
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
application application
} }
@ -21,21 +21,13 @@ kotlin {
useCommonJs() useCommonJs()
browser { browser {
commonWebpackConfig { commonWebpackConfig {
cssSupport.enabled = false cssSupport {
enabled = false
}
} }
} }
} }
afterEvaluate {
val jsBrowserDistribution by tasks.getting
tasks.getByName<ProcessResources>("jvmProcessResources") {
dependsOn(jsBrowserDistribution)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(jsBrowserDistribution)
}
}
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
@ -45,8 +37,10 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
implementation("org.apache.commons:commons-math3:3.6.1") implementation("org.apache.commons:commons-math3:3.6.1")
implementation(npmlibs.ktor.server.cio) implementation("io.ktor:ktor-server-cio:${ktorVersion}")
implementation(npmlibs.ktor.serialization) implementation("io.ktor:ktor-server-content-negotiation:${ktorVersion}")
implementation("io.ktor:ktor-serialization-kotlinx-json:${ktorVersion}")
implementation("ch.qos.logback:logback-classic:1.2.11")
} }
} }
jsMain { jsMain {
@ -63,6 +57,23 @@ application {
mainClass.set("ru.mipt.npm.muon.monitor.server.MMServerKt") mainClass.set("ru.mipt.npm.muon.monitor.server.MMServerKt")
} }
val jsBrowserDistribution by tasks.getting
val jsBrowserDevelopmentExecutableDistribution by tasks.getting
val devMode = rootProject.findProperty("visionforge.development") as? Boolean
?: rootProject.version.toString().contains("dev")
tasks.getByName<ProcessResources>("jvmProcessResources") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
if (devMode) {
dependsOn(jsBrowserDevelopmentExecutableDistribution)
from(jsBrowserDevelopmentExecutableDistribution)
} else {
dependsOn(jsBrowserDistribution)
from(jsBrowserDistribution)
}
}
//distributions { //distributions {
// main { // main {
// contents { // contents {

View File

@ -3,19 +3,19 @@ package ru.mipt.npm.muon.monitor
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.VisionManager import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.removeAll
import space.kscience.visionforge.setAsRoot import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import kotlin.collections.set
import kotlin.math.PI import kotlin.math.PI
class Model(val manager: VisionManager) { class Model(val manager: VisionManager) {
private val map = HashMap<String, SolidGroup>() private val map = HashMap<String, SolidGroup>()
private val events = HashSet<Event>() private val events = HashSet<Event>()
private fun SolidGroup.pixel(pixel: SC1) { private fun MutableVisionContainer<Solid>.pixel(pixel: SC1) {
val group = group(pixel.name) { val group = solidGroup(pixel.name) {
position = Point3D(pixel.center.x, pixel.center.y, pixel.center.z) position = Point3D(pixel.center.x, pixel.center.y, pixel.center.z)
box(pixel.xSize, pixel.ySize, pixel.zSize) box(pixel.xSize, pixel.ySize, pixel.zSize)
label(pixel.name) { label(pixel.name) {
@ -27,7 +27,7 @@ class Model(val manager: VisionManager) {
} }
private fun SolidGroup.detector(detector: SC16) { private fun SolidGroup.detector(detector: SC16) {
group(detector.name) { solidGroup(detector.name) {
detector.pixels.forEach { detector.pixels.forEach {
pixel(it) pixel(it)
} }
@ -39,40 +39,39 @@ class Model(val manager: VisionManager) {
val root: SolidGroup = SolidGroup().apply { val root: SolidGroup = SolidGroup().apply {
setAsRoot(this@Model.manager) setAsRoot(this@Model.manager)
material { material {
wireframe color.set("darkgreen")
color("darkgreen")
} }
rotationX = PI / 2 rotationX = PI / 2
group("bottom") { solidGroup("bottom") {
Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach { Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach {
detector(it) detector(it)
} }
} }
group("middle") { solidGroup("middle") {
Monitor.detectors.filter { it.center.z == CENTRAL_LAYER_Z }.forEach { Monitor.detectors.filter { it.center.z == CENTRAL_LAYER_Z }.forEach {
detector(it) detector(it)
} }
} }
group("top") { solidGroup("top") {
Monitor.detectors.filter { it.center.z == UPPER_LAYER_Z }.forEach { Monitor.detectors.filter { it.center.z == UPPER_LAYER_Z }.forEach {
detector(it) detector(it)
} }
} }
tracks = group("tracks") tracks = solidGroup("tracks")
} }
private fun highlight(pixel: String) { private fun highlight(pixel: String) {
println("highlight $pixel") println("highlight $pixel")
map[pixel]?.color?.invoke("blue") map[pixel]?.color.set("blue")
} }
fun reset() { fun reset() {
map.values.forEach { map.values.forEach {
it.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null) it.properties.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, null)
} }
tracks.removeAll() tracks.children.clear()
} }
fun displayEvent(event: Event) { fun displayEvent(event: Event) {
@ -84,7 +83,7 @@ class Model(val manager: VisionManager) {
event.track?.let { event.track?.let {
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") { tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") {
thickness = 4 thickness = 4
color("red") color.set("red")
} }
} }
} }

View File

@ -16,13 +16,16 @@ import react.dom.p
import react.fc import react.fc
import react.useMemo import react.useMemo
import react.useState import react.useState
import space.kscience.dataforge.context.Context import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Colors
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.tab import space.kscience.visionforge.ring.tab
import space.kscience.visionforge.solid.specifications.Camera import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.edges import space.kscience.visionforge.solid.three.edges
import styled.css import styled.css
@ -32,7 +35,7 @@ import kotlin.math.PI
external interface MMAppProps : Props { external interface MMAppProps : Props {
var model: Model var model: Model
var context: Context var solids: Solids
var selected: Name? var selected: Name?
} }
@ -42,17 +45,21 @@ val MMApp = fc<MMAppProps>("Muon monitor") { props ->
val mmOptions = useMemo { val mmOptions = useMemo {
Canvas3DOptions { Canvas3DOptions {
camera = Camera { camera {
distance = 2100.0 distance = 2100.0
latitude = PI / 6 latitude = PI / 6
azimuth = PI + PI / 6 azimuth = PI + PI / 6
} }
} }
} }
val root = useMemo(props.model) { val root = useMemo(props.model) {
props.model.root.apply { props.model.root.apply {
edges() edges()
ambientLight{
color.set(Colors.white)
}
} }
} }
@ -64,7 +71,7 @@ val MMApp = fc<MMAppProps>("Muon monitor") { props ->
} }
child(ThreeCanvasWithControls) { child(ThreeCanvasWithControls) {
attrs { attrs {
this.context = props.context this.solids = props.solids
this.builderOfSolid = CompletableDeferred(root) this.builderOfSolid = CompletableDeferred(root)
this.selected = props.selected this.selected = props.selected
this.options = mmOptions this.options = mmOptions
@ -75,7 +82,7 @@ val MMApp = fc<MMAppProps>("Muon monitor") { props ->
+"Next" +"Next"
attrs { attrs {
onClickFunction = { onClickFunction = {
context.launch { solids.context.launch {
val event = window.fetch( val event = window.fetch(
"http://localhost:8080/event", "http://localhost:8080/event",
RequestInit("GET") RequestInit("GET")

View File

@ -1,11 +1,13 @@
package ru.mipt.npm.muon.monitor package ru.mipt.npm.muon.monitor
import kotlinx.browser.document import kotlinx.browser.document
import react.dom.render import react.dom.client.createRoot
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch import space.kscience.dataforge.context.fetch
import space.kscience.visionforge.Application import space.kscience.visionforge.Application
import space.kscience.visionforge.VisionManager import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.startApplication import space.kscience.visionforge.startApplication
@ -16,16 +18,17 @@ private class MMDemoApp : Application {
val context = Context("MM-demo") { val context = Context("MM-demo") {
plugin(ThreePlugin) plugin(ThreePlugin)
} }
val visionManager = context.fetch(VisionManager) val visionManager = context.fetch(VisionManager)
val model = Model(visionManager) val model = Model(visionManager)
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page") val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
render(element) { createRoot(element).render {
child(MMApp) { child(MMApp) {
attrs { attrs {
this.model = model this.model = model
this.context = context this.solids = context.fetch(Solids)
} }
} }
} }

View File

@ -5,7 +5,6 @@
<!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">--> <!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">-->
<title>Three js demo for particle physics</title> <title>Three js demo for particle physics</title>
<script type="text/javascript" src="muon-monitor.js"></script> <script type="text/javascript" src="muon-monitor.js"></script>
<link rel="stylesheet" href="css/custom-bootstrap.css">
</head> </head>
<body class="application"> <body class="application">
<div class="container-fluid max-vh-100" id = "app"> </div> <div class="container-fluid max-vh-100" id = "app"> </div>

View File

@ -1,24 +1,22 @@
package ru.mipt.npm.muon.monitor.server package ru.mipt.npm.muon.monitor.server
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.application.log
import io.ktor.features.CallLogging
import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.content.resources import io.ktor.serialization.kotlinx.json.json
import io.ktor.http.content.static import io.ktor.server.application.Application
import io.ktor.response.respond import io.ktor.server.application.call
import io.ktor.response.respondText import io.ktor.server.application.install
import io.ktor.routing.Routing import io.ktor.server.application.log
import io.ktor.routing.get
import io.ktor.serialization.json
import io.ktor.server.cio.CIO import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer import io.ktor.server.engine.embeddedServer
import io.ktor.server.http.content.resources
import io.ktor.server.http.content.static
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.Routing
import io.ktor.server.routing.get
import org.apache.commons.math3.random.JDKRandomGenerator import org.apache.commons.math3.random.JDKRandomGenerator
import ru.mipt.npm.muon.monitor.Model import ru.mipt.npm.muon.monitor.Model
import ru.mipt.npm.muon.monitor.sim.Cos2TrackGenerator import ru.mipt.npm.muon.monitor.sim.Cos2TrackGenerator
@ -40,8 +38,6 @@ fun Application.module(context: Context = Global) {
environment.log.info("Current directory: $currentDir") environment.log.info("Current directory: $currentDir")
val solidManager = context.fetch(Solids) val solidManager = context.fetch(Solids)
install(DefaultHeaders)
install(CallLogging)
install(ContentNegotiation) { install(ContentNegotiation) {
json() json()
} }

View File

@ -32,7 +32,7 @@ kotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
freeCompilerArgs = freeCompilerArgs =
freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy" freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" + "-Xlambdas=indy" + "-Xcontext-receivers"
} }
} }
testRuns["test"].executionTask.configure { testRuns["test"].executionTask.configure {

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.examples package space.kscience.visionforge.examples
import kotlinx.html.h2 import kotlinx.html.h2
import space.kscience.dataforge.values.ValueType import space.kscience.dataforge.meta.ValueType
import space.kscience.plotly.layout import space.kscience.plotly.layout
import space.kscience.plotly.models.ScatterMode import space.kscience.plotly.models.ScatterMode
import space.kscience.plotly.models.TextPosition import space.kscience.plotly.models.TextPosition

View File

@ -1,13 +1,20 @@
package space.kscience.visionforge.examples package space.kscience.visionforge.examples
import space.kscience.gdml.GdmlShowCase import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM){ fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
vision("canvas") { vision("canvas") {
requirePlugin(Solids) requirePlugin(Solids)
GdmlShowCase.cubes().toVision() GdmlShowCase.cubes().toVision().also {
it.ambientLight {
color.set(Colors.white)
}
}
} }
} }

View File

@ -7,7 +7,7 @@ import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.color import space.kscience.visionforge.solid.color
import space.kscience.visionforge.solid.invoke import space.kscience.visionforge.solid.set
import space.kscience.visionforge.visible import space.kscience.visionforge.visible
import java.nio.file.Path import java.nio.file.Path
@ -229,7 +229,7 @@ fun main() = makeVisionFile(Path.of("curves.html"), resourceLocation = ResourceL
visible = false visible = false
} }
if(solid.name.startsWith("gas")){ if(solid.name.startsWith("gas")){
color("green") color.set("green")
} else { } else {
//make all solids semi-transparent //make all solids semi-transparent
transparent() transparent()

View File

@ -1,12 +1,21 @@
package space.kscience.visionforge.examples package space.kscience.visionforge.examples
import space.kscience.gdml.GdmlShowCase import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.gdml
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.solid
fun main() = makeVisionFile { fun main() = makeVisionFile {
vision("canvas") { vision("canvas") {
requirePlugin(Solids) requirePlugin(Solids)
GdmlShowCase.babyIaxo().toVision() solid {
ambientLight {
color.set(Colors.white)
}
gdml(GdmlShowCase.babyIaxo(), "D0")
}
} }
} }

View File

@ -2,6 +2,7 @@ package space.kscience.visionforge.examples
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.h1 import kotlinx.html.h1
import space.kscience.visionforge.Colors
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import java.nio.file.Paths import java.nio.file.Paths
@ -17,13 +18,16 @@ fun main() = makeVisionFile(
div { div {
vision { vision {
solid { solid {
ambientLight {
color.set(Colors.white)
}
repeat(100) { repeat(100) {
sphere(5, name = "sphere[$it]") { sphere(5, name = "sphere[$it]") {
x = random.nextDouble(-300.0, 300.0) x = random.nextDouble(-300.0, 300.0)
y = random.nextDouble(-300.0, 300.0) y = random.nextDouble(-300.0, 300.0)
z = random.nextDouble(-300.0, 300.0) z = random.nextDouble(-300.0, 300.0)
material { material {
color(random.nextInt()) color.set(random.nextInt())
} }
detail = 16 detail = 16
} }

View File

@ -1,12 +1,12 @@
package space.kscience.visionforge.examples package space.kscience.visionforge.examples
import ru.mipt.npm.root.DGeoManager import ru.mipt.npm.root.DGeoManager
import ru.mipt.npm.root.rootGeo
import ru.mipt.npm.root.serialization.TGeoManager import ru.mipt.npm.root.serialization.TGeoManager
import ru.mipt.npm.root.toSolid
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.isLeaf import space.kscience.dataforge.meta.isLeaf
import space.kscience.dataforge.values.string import space.kscience.dataforge.meta.string
import space.kscience.visionforge.solid.Solids import space.kscience.visionforge.solid.Solids
import java.nio.file.Paths import java.nio.file.Paths
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@ -34,7 +34,7 @@ fun main() {
println(it) println(it)
} }
val solid = geo.toSolid() val solid = Solids.rootGeo(geo)
Paths.get("BM@N.vf.json").writeText(Solids.encodeToString(solid)) Paths.get("BM@N.vf.json").writeText(Solids.encodeToString(solid))
//println(Solids.encodeToString(solid)) //println(Solids.encodeToString(solid))

View File

@ -2,8 +2,8 @@ package space.kscience.visionforge.examples
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.box import space.kscience.visionforge.solid.box
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.material import space.kscience.visionforge.solid.material
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.solid import space.kscience.visionforge.solid.solid
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) { fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
@ -11,7 +11,7 @@ fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
solid { solid {
box(100, 100, 100) box(100, 100, 100)
material { material {
emissiveColor("red") emissiveColor.set("red")
} }
} }
} }

View File

@ -1,6 +1,6 @@
package space.kscience.visionforge.examples package space.kscience.visionforge.examples
import space.kscience.dataforge.values.ValueType import space.kscience.dataforge.meta.ValueType
import space.kscience.tables.ColumnHeader import space.kscience.tables.ColumnHeader
import space.kscience.tables.valueRow import space.kscience.tables.valueRow
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.jvm") id("space.kscience.gradle.jvm")
application application
} }
@ -15,7 +15,7 @@ group = "ru.mipt.npm"
dependencies{ dependencies{
implementation(project(":visionforge-threejs:visionforge-threejs-server")) implementation(project(":visionforge-threejs:visionforge-threejs-server"))
implementation("ch.qos.logback:logback-classic:1.2.3") implementation("ch.qos.logback:logback-classic:1.2.11")
} }
application { application {

View File

@ -6,7 +6,7 @@ import space.kscience.visionforge.style
import space.kscience.visionforge.useStyle import space.kscience.visionforge.useStyle
import kotlin.math.PI import kotlin.math.PI
internal fun visionOfSatellite( internal fun Solids.visionOfSatellite(
layers: Int = 10, layers: Int = 10,
layerHeight: Number = 10, layerHeight: Number = 10,
xSegments: Int = 3, xSegments: Int = 3,
@ -14,8 +14,8 @@ internal fun visionOfSatellite(
xSegmentSize: Number = 30, xSegmentSize: Number = 30,
ySegmentSize: Number = xSegmentSize, ySegmentSize: Number = xSegmentSize,
fiberDiameter: Number = 1.0, fiberDiameter: Number = 1.0,
): SolidGroup = SolidGroup { ): SolidGroup = solidGroup {
color("darkgreen") color.set("darkgreen")
val transparent by style { val transparent by style {
this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3 this[SolidMaterial.MATERIAL_OPACITY_KEY] = 0.3
} }
@ -31,7 +31,7 @@ internal fun visionOfSatellite(
val totalXSize = xSegments * xSegmentSize.toDouble() val totalXSize = xSegments * xSegmentSize.toDouble()
val totalYSize = ySegments * ySegmentSize.toDouble() val totalYSize = ySegments * ySegmentSize.toDouble()
for (layer in 1..layers) { for (layer in 1..layers) {
group("layer[$layer]") { solidGroup("layer[$layer]") {
for (i in 1..xSegments) { for (i in 1..xSegments) {
for (j in 1..ySegments) { for (j in 1..ySegments) {
box(xSegmentSize, ySegmentSize, layerHeight, name = "segment[$i,$j]") { box(xSegmentSize, ySegmentSize, layerHeight, name = "segment[$i,$j]") {
@ -42,7 +42,7 @@ internal fun visionOfSatellite(
} }
} }
} }
group("fibers") { solidGroup("fibers") {
for (i in 1..xSegments) { for (i in 1..xSegments) {
cylinder(fiberDiameter, totalYSize) { cylinder(fiberDiameter, totalYSize) {
useStyle(red) useStyle(red)

View File

@ -5,7 +5,10 @@ import kotlinx.coroutines.*
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.h1 import kotlinx.html.h1
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Colors
import space.kscience.visionforge.html.Page import space.kscience.visionforge.html.Page
import space.kscience.visionforge.html.plus import space.kscience.visionforge.html.plus
import space.kscience.visionforge.server.close import space.kscience.visionforge.server.close
@ -17,13 +20,20 @@ import space.kscience.visionforge.visionManager
import kotlin.random.Random import kotlin.random.Random
@OptIn(DFExperimental::class)
fun main() { fun main() {
val satContext = Context("sat") { val satContext = Context("sat") {
plugin(Solids) plugin(Solids)
} }
val solids = satContext.fetch(Solids)
//Create a geometry //Create a geometry
val sat = visionOfSatellite(ySegments = 3) val sat = solids.visionOfSatellite(ySegments = 3).apply {
ambientLight {
color.set(Colors.white)
}
}
val server = satContext.visionManager.serve { val server = satContext.visionManager.serve {
page(header = Page.threeJsHeader + Page.styleSheetHeader("css/styles.css")) { page(header = Page.threeJsHeader + Page.styleSheetHeader("css/styles.css")) {
@ -44,7 +54,7 @@ fun main() {
val randomJ = Random.nextInt(1, 4) val randomJ = Random.nextInt(1, 4)
val target = Name.parse("layer[$randomLayer].segment[$randomI,$randomJ]") val target = Name.parse("layer[$randomLayer].segment[$randomI,$randomJ]")
val targetVision = sat[target] as Solid val targetVision = sat[target] as Solid
targetVision.color("red") targetVision.color.set("red")
delay(1000) delay(1000)
targetVision.color.clear() targetVision.color.clear()
delay(500) delay(500)

View File

@ -1,8 +1,8 @@
import ru.mipt.npm.gradle.DependencyConfiguration import space.kscience.gradle.DependencyConfiguration
import ru.mipt.npm.gradle.FXModule import space.kscience.gradle.FXModule
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
application application
} }
@ -40,5 +40,5 @@ kotlin {
} }
application { application {
mainClassName = "space.kscience.visionforge.solid.demo.FXDemoAppKt" mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt")
} }

View File

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

View File

@ -18,7 +18,12 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
val meta = Meta { val meta = Meta {
"title" put title "title" put title
} }
val vision = SolidGroup(block) val vision = solids.solidGroup {
block()
ambientLight {
color.set(Colors.white)
}
}
render(Name.parse(name), vision, meta) render(Name.parse(name), vision, meta)
} }
@ -39,30 +44,31 @@ val canvasOptions = Canvas3DOptions {
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun VisionLayout<Solid>.showcase() { fun VisionLayout<Solid>.showcase() {
demo("shapes", "Basic shapes") { demo("shapes", "Basic shapes") {
ambientLight()
box(100.0, 100.0, 100.0) { box(100.0, 100.0, 100.0) {
z = -110.0 z = -110.0
color("teal") color.set("teal")
} }
sphere(50.0) { sphere(50.0) {
x = 110 x = 110
detail = 16 detail = 16
color("red") color.set("red")
} }
tube(50, height = 10, innerRadius = 25, angle = PI) { tube(50, height = 10, innerRadius = 25, angle = PI) {
y = 110 y = 110
detail = 16 detail = 16
rotationX = PI / 4 rotationX = PI / 4
color("blue") color.set("blue")
} }
sphereLayer(50, 40, theta = PI / 2) { sphereLayer(50, 40, theta = PI / 2) {
rotationX = -PI * 3 / 4 rotationX = -PI * 3 / 4
z = 110 z = 110
color(Colors.pink) color.set(Colors.pink)
} }
} }
demo("dynamic", "Dynamic properties") { demo("dynamic", "Dynamic properties") {
val group = group { val group = solidGroup {
box(100, 100, 100) { box(100, 100, 100) {
z = 110.0 z = 110.0
opacity = 0.5 opacity = 0.5
@ -72,7 +78,7 @@ fun VisionLayout<Solid>.showcase() {
visible = false visible = false
x = 110.0 x = 110.0
//override color for this cube //override color for this cube
color(1530) color.set(1530)
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
while (isActive) { while (isActive) {
@ -87,19 +93,19 @@ fun VisionLayout<Solid>.showcase() {
val random = Random(111) val random = Random(111)
while (isActive) { while (isActive) {
delay(1000) delay(1000)
group.color(random.nextInt(0, Int.MAX_VALUE)) group.color.set(random.nextInt(0, Int.MAX_VALUE))
} }
} }
} }
demo("rotation", "Rotations") { demo("rotation", "Rotations") {
box(100, 100, 100) box(100, 100, 100)
group { solidGroup {
x = 200 x = 200
rotationY = PI / 4 rotationY = PI / 4
box(100, 100, 100) { box(100, 100, 100) {
rotationZ = PI / 4 rotationZ = PI / 4
color(Colors.red) color.set(Colors.red)
} }
} }
} }
@ -112,7 +118,7 @@ fun VisionLayout<Solid>.showcase() {
for (i in 0..100) { for (i in 0..100) {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i)) layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
} }
color(Colors.teal) color.set(Colors.teal)
rotationX = -PI / 2 rotationX = -PI / 2
} }
} }
@ -121,13 +127,13 @@ fun VisionLayout<Solid>.showcase() {
sphere(100) { sphere(100) {
detail = 32 detail = 32
opacity = 0.4 opacity = 0.4
color(Colors.blue) color.set(Colors.blue)
} }
repeat(20) { repeat(20) {
polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) { polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) {
thickness = 3.0 thickness = 3.0
rotationX = it * PI2 / 20 rotationX = it * PI2 / 20
color(Colors.green) color.set(Colors.green)
//rotationY = it * PI2 / 20 //rotationY = it * PI2 / 20
} }
} }
@ -154,7 +160,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
detail = 32 detail = 32
} }
material { material {
color(Colors.pink) color.set(Colors.pink)
} }
} }
composite(CompositeType.UNION) { composite(CompositeType.UNION) {
@ -164,7 +170,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
sphere(50) { sphere(50) {
detail = 32 detail = 32
} }
color("lightgreen") color.set("lightgreen")
opacity = 0.7 opacity = 0.7
} }
composite(CompositeType.SUBTRACT) { composite(CompositeType.SUBTRACT) {
@ -175,7 +181,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
sphere(50) { sphere(50) {
detail = 32 detail = 32
} }
color("teal") color.set("teal")
opacity = 0.7 opacity = 0.7
} }
} }
@ -186,7 +192,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
detail = 32 detail = 32
} }
box(100, 100, 100) box(100, 100, 100)
color("red") color.set("red")
opacity = 0.5 opacity = 0.5
} }
} }

View File

@ -16,6 +16,7 @@ import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.three.ThreeCanvas import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.solid.three.ThreePlugin
@ -27,6 +28,8 @@ class ThreeDemoGrid(element: Element) : VisionLayout<Solid> {
private val three = Global.fetch(ThreePlugin) private val three = Global.fetch(ThreePlugin)
override val solids: Solids get() = three.solids
init { init {
element.clear() element.clear()
element.append { element.append {

View File

@ -1,20 +1,18 @@
package space.kscience.visionforge.solid.demo package space.kscience.visionforge.solid.demo
import info.laht.threekt.core.Object3D import space.kscience.dataforge.meta.asValue
import info.laht.threekt.geometries.BoxGeometry
import info.laht.threekt.objects.Mesh
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.number import space.kscience.dataforge.meta.number
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.asValue
import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.set import space.kscience.visionforge.setChild
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.SolidGroup import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.layer import space.kscience.visionforge.solid.layer
import space.kscience.visionforge.solid.three.* import space.kscience.visionforge.solid.three.*
import three.core.Object3D
import three.geometries.BoxGeometry
import three.objects.Mesh
import kotlin.math.max import kotlin.math.max
internal fun SolidGroup.varBox( internal fun SolidGroup.varBox(
@ -22,7 +20,7 @@ internal fun SolidGroup.varBox(
ySize: Number, ySize: Number,
name: String = "", name: String = "",
action: VariableBox.() -> Unit = {}, action: VariableBox.() -> Unit = {},
): VariableBox = VariableBox(xSize, ySize).apply(action).also { set(name, it) } ): VariableBox = VariableBox(xSize, ySize).apply(action).also { setChild(name, it) }
internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision() { internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision() {
@ -44,13 +42,13 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
it.layers.enable(this@VariableBox.layer) it.layers.enable(this@VariableBox.layer)
} }
} }
mesh.scale.z = meta[VALUE].number?.toDouble() ?: 1.0 mesh.scale.z = properties.getValue(VALUE)?.number?.toDouble() ?: 1.0
//add listener to object properties //add listener to object properties
onPropertyChange { name -> onPropertyChange { name ->
when { when {
name == VALUE -> { name == VALUE -> {
val value = meta.get(VALUE).int ?: 0 val value = properties.getValue(VALUE)?.int ?: 0
val size = value.toFloat() / 255f * 20f val size = value.toFloat() / 255f * 20f
mesh.scale.z = size.toDouble() mesh.scale.z = size.toDouble()
mesh.position.z = size.toDouble() / 2 mesh.position.z = size.toDouble() / 2
@ -61,7 +59,8 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
material.color.setRGB(r.toFloat() / 256, g.toFloat() / 256, b.toFloat() / 256) material.color.setRGB(r.toFloat() / 256, g.toFloat() / 256, b.toFloat() / 256)
mesh.updateMatrix() mesh.updateMatrix()
} }
name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
name.startsWith(ThreeMeshFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
else -> mesh.updateProperty(this@VariableBox, name) else -> mesh.updateProperty(this@VariableBox, name)
} }
} }
@ -70,9 +69,9 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
} }
var value: Int var value: Int
get() = meta[VALUE].int ?: 0 get() = properties.getValue(VALUE)?.int ?: 0
set(value) { set(value) {
setProperty(VALUE, value.asValue()) properties.setValue(VALUE, value.asValue())
} }
companion object { companion object {

View File

@ -10,9 +10,11 @@ import space.kscience.dataforge.names.Name
import space.kscience.visionforge.solid.FX3DPlugin import space.kscience.visionforge.solid.FX3DPlugin
import space.kscience.visionforge.solid.FXCanvas3D import space.kscience.visionforge.solid.FXCanvas3D
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids
import tornadofx.* import tornadofx.*
class FXDemoGrid : View(title = "DataForge-vis FX demo"), VisionLayout<Solid> { class FXDemoGrid : View(title = "DataForge-vis FX demo"), VisionLayout<Solid> {
private val outputs = FXCollections.observableHashMap<Name, FXCanvas3D>() private val outputs = FXCollections.observableHashMap<Name, FXCanvas3D>()
override val root: Parent = borderpane { override val root: Parent = borderpane {
@ -24,6 +26,9 @@ class FXDemoGrid : View(title = "DataForge-vis FX demo"), VisionLayout<Solid> {
} }
private val fx3d = Global.fetch(FX3DPlugin) private val fx3d = Global.fetch(FX3DPlugin)
override val solids: Solids get() = fx3d.solids
override fun render(name: Name, vision: Solid, meta: Meta) { override fun render(name: Name, vision: Solid, meta: Meta) {
outputs.getOrPut(name) { FXCanvas3D(fx3d, canvasOptions) }.render(vision) outputs.getOrPut(name) { FXCanvas3D(fx3d, canvasOptions) }.render(vision)

View File

@ -2,10 +2,10 @@ package space.kscience.visionforge.demo
import javafx.geometry.Orientation import javafx.geometry.Orientation
import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ValueType
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.node import space.kscience.dataforge.meta.descriptors.node
import space.kscience.dataforge.meta.descriptors.value import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.editor.FXMetaModel import space.kscience.visionforge.editor.FXMetaModel
import space.kscience.visionforge.editor.MetaViewer import space.kscience.visionforge.editor.MetaViewer
import space.kscience.visionforge.editor.MutableMetaEditor import space.kscience.visionforge.editor.MutableMetaEditor

View File

@ -3,7 +3,7 @@
![](../docs/images/hierarchy.png) ![](../docs/images/hierarchy.png)
### Vision ### Vision
* function `getPropertyValue(name: Name, inherit: Boolean = false, includeStyles: Boolean = true, includeDefaults: Boolean = true)` - get property value with given layer flags. * function `getProperty(name: Name, inherit: Boolean = false, includeStyles: Boolean = true, includeDefaults: Boolean = true)` - get property value with given layer flags.
* function `setProperty(name: Name, item: Any?)` - a convenient method to set property node or value. If `item` is null, then node is removed, not a value * function `setProperty(name: Name, item: Any?)` - a convenient method to set property node or value. If `item` is null, then node is removed, not a value
Sets the `item` property to the element with the `name` identification. Sets the `item` property to the element with the `name` identification.

View File

@ -7,7 +7,7 @@ Properties, which can be inherited by objects, are `styles`, `prototypes` (if th
All values of `styles` property are contained in class `StyleSheet`, where they all are defined at `Group`s level. The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and All values of `styles` property are contained in class `StyleSheet`, where they all are defined at `Group`s level. The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and
`SolidReference` class helps to reuse a template object. `SolidReference` class helps to reuse a template object.
The order of inheritance of properties is set in function `getPropertyValue` in `VisionBase` class. The order of inheritance of properties is set in function `getProperty` in `VisionBase` class.
The order is this: The order is this:
* own styles * own styles
* prototypes * prototypes

View File

@ -59,7 +59,7 @@ box(10, 10, 10, name = "small box"){
rotation = Point3D(0, 0, 0) rotation = Point3D(0, 0, 0)
} }
``` ```
![](../docs/images/small-box.png) ![](../images/small-box.png)
The `big box` will have properties with custom values. The `big box` will have properties with custom values.
```kotlin ```kotlin
@ -72,7 +72,7 @@ box(40, 40, 40, name = "big box"){
rotation = Point3D(60, 80, 0) rotation = Point3D(60, 80, 0)
} }
``` ```
![](../docs/images/big-rotated-box.png) ![](../images/big-rotated-box.png)
If we compare these boxes, we will see all differences. If we compare these boxes, we will see all differences.
Here is the function `main` with both boxes. Here is the function `main` with both boxes.
@ -111,8 +111,8 @@ fun main(){
} }
} }
``` ```
![](../docs/images/two-boxes-1.png) ![](../images/two-boxes-1.png)
![](../docs/images/two-boxes-2.png) ![](../images/two-boxes-2.png)
***There is plenty of other properties, especially those, which you can create by yourself. Here we mention just a small part.*** ***There is plenty of other properties, especially those, which you can create by yourself. Here we mention just a small part.***
@ -142,8 +142,8 @@ polyline(Point3D(30, 20, 10), Point3D(30, -100, 30), Point3D(30, -100, 30), Poin
} }
``` ```
![](../docs/images/polyline-points.png) ![](../images/polyline-points.png)
![](../docs/images/polyline-points-2.png) ![](../images/polyline-points-2.png)
### 2) Box ### 2) Box
@ -165,7 +165,7 @@ Let's create just usual `box` with equal ribs.
color("pink") color("pink")
} }
``` ```
![](../docs/images/box.png) ![](../images/box.png)
Now, let's make `box` with bigger `y` value. Now, let's make `box` with bigger `y` value.
```kotlin ```kotlin
@ -175,7 +175,7 @@ Now, let's make `box` with bigger `y` value.
``` ```
As you can see, only the rib of `y-axis` differs from other ribs. As you can see, only the rib of `y-axis` differs from other ribs.
![](../docs/images/high-box.png) ![](../images/high-box.png)
For a final trial, let's create a `box` with a bigger `x` value. For a final trial, let's create a `box` with a bigger `x` value.
@ -189,7 +189,7 @@ For a final trial, let's create a `box` with a bigger `x` value.
``` ```
Predictably, only the `x-axis` rib is bigger than other ribs. Predictably, only the `x-axis` rib is bigger than other ribs.
![](../docs/images/wide-box.png) ![](../images/wide-box.png)
### 3) Sphere ### 3) Sphere
@ -206,7 +206,7 @@ As for `radius`, it has `Float` type, and, as you can guess, it sets the radius
color("blue") color("blue")
} }
``` ```
![](../docs/images/sphere.png) ![](../images/sphere.png)
### 4) Hexagon ### 4) Hexagon
@ -220,7 +220,7 @@ It is solid which has six edges. It is set by eight values: `node1`,..., `node8`
5) Edge with vertices `node1`, `node5`, `node8`, `node4` 5) Edge with vertices `node1`, `node5`, `node8`, `node4`
6) Edge with vertices `node8`, `node5`, `node6`, `node7` 6) Edge with vertices `node8`, `node5`, `node6`, `node7`
![](../docs/images/scheme.png) ![](../images/scheme.png)
As the hexagon takes in specific points, we understand that this solid cannot be moved, it is fixed in space, and it can't make pivots. As the hexagon takes in specific points, we understand that this solid cannot be moved, it is fixed in space, and it can't make pivots.
@ -239,7 +239,7 @@ Let's make classic parallelepiped.
color("green") color("green")
} }
``` ```
![](../docs/images/classic-hexagon.png) ![](../images/classic-hexagon.png)
Now, let's make a custom hexagon. Now, let's make a custom hexagon.
@ -258,7 +258,7 @@ hexagon(
color("brown") color("brown")
} }
``` ```
![](../docs/images/custom-hexagon.png) ![](../images/custom-hexagon.png)
### 3) Cone ### 3) Cone
It takes in six values: `bottomRadius`, `height`, `upperRadius`, `startAngle`, `angle`, and `name`. It takes in six values: `bottomRadius`, `height`, `upperRadius`, `startAngle`, `angle`, and `name`.
@ -274,8 +274,8 @@ Let's build a classic cone:
color("beige") color("beige")
} }
``` ```
![](../docs/images/cone-1.png) ![](../images/cone-1.png)
![](../docs/images/cone-2.png) ![](../images/cone-2.png)
First of all, we have to try to build a frustum cone: First of all, we have to try to build a frustum cone:
```kotlin ```kotlin
@ -283,7 +283,7 @@ cone(60, 80, name = "cone") {
color(0u, 40u, 0u) color(0u, 40u, 0u)
} }
``` ```
![](../docs/images/frustum-cone.png) ![](../images/frustum-cone.png)
Now, we need to make a try to build a cone segment: Now, we need to make a try to build a cone segment:
@ -292,8 +292,8 @@ cone(60, 80, angle = PI, name = "cone") {
color(0u, 0u, 200u) color(0u, 0u, 200u)
} }
``` ```
![](../docs/images/cone-segment-1.png) ![](../images/cone-segment-1.png)
![](../docs/images/cone-segment-2.png) ![](../images/cone-segment-2.png)
Finally, the segment of frustum cone is left for a try: Finally, the segment of frustum cone is left for a try:
```kotlin ```kotlin
@ -301,7 +301,7 @@ cone(60, 100, 20, PI*3/4, angle = PI/3, name = "cone") {
color(190u, 0u, 0u) color(190u, 0u, 0u)
} }
``` ```
![](../docs/images/frustum-cone-segment.png) ![](../images/frustum-cone-segment.png)
### 4) Cone Surface ### 4) Cone Surface
This solid is set by seven values:`bottomOuterRadius`, `bottomInnerRadius`, `height`, `topOuterRadius`, `topInnerRadius`, `startAngle`, and `angle`. This solid is set by seven values:`bottomOuterRadius`, `bottomInnerRadius`, `height`, `topOuterRadius`, `topInnerRadius`, `startAngle`, and `angle`.
@ -318,8 +318,8 @@ Let's build usual cone surface with almost all properties set:
rotation = Point3D(2, 50, -9) rotation = Point3D(2, 50, -9)
} }
``` ```
![](../docs/images/cone-surface-1.png) ![](../images/cone-surface-1.png)
![](../docs/images/cone-surface-2.png) ![](../images/cone-surface-2.png)
Now, let's create a cone surface and set all it's properties: Now, let's create a cone surface and set all it's properties:
@ -329,8 +329,8 @@ coneSurface(30, 25, 10, 10, 8,0f, pi*3/4, name = "cone surface") {
rotation = Point3D(2, 50, -9) rotation = Point3D(2, 50, -9)
} }
``` ```
![](../docs/images/cone-surface-fragment.png) ![](../images/cone-surface-fragment.png)
![](../docs/images/cone-surface-fragment-2.png) ![](../images/cone-surface-fragment-2.png)
### 5) Cylinder ### 5) Cylinder
@ -344,8 +344,8 @@ cylinder(40, 100, "cylinder"){
color("indigo") color("indigo")
} }
``` ```
![](../docs/images/cylinder-1.png) ![](../images/cylinder-1.png)
![](../docs/images/cylinder-2.png) ![](../images/cylinder-2.png)
### 6) Tube ### 6) Tube
`tube` takes in `radius`, `height`, `innerRadius`, `startAngle`, `angle`, and `name`. *All values are familiar from `cone`, and `coneSurface` solids.* `tube` takes in `radius`, `height`, `innerRadius`, `startAngle`, `angle`, and `name`. *All values are familiar from `cone`, and `coneSurface` solids.*
@ -356,7 +356,7 @@ tube(50, 40, 20, name = "usual tube"){
opacity = 0.4 opacity = 0.4
} }
``` ```
![](../docs/images/tube.png) ![](../images/tube.png)
This is an example of tube fragment: This is an example of tube fragment:
@ -365,7 +365,7 @@ tube(50, 40, 20, 0f, PI, name = "fragmented tube"){
color("white") color("white")
} }
``` ```
![](../docs/images/tube-fragment.png) ![](../images/tube-fragment.png)
### 7) Extruded ### 7) Extruded
`extruded` is set by two values: `shape`, and `layer`. `extruded` is set by two values: `shape`, and `layer`.

View File

@ -3,7 +3,7 @@
interface Vision{ interface Vision{
val parent: VisionGroup? val parent: VisionGroup?
fun getPropertyValue(name,inherit,includeStyles,includeDefaults): Value? fun getProperty(name,inherit,includeStyles,includeDefaults): Value?
} }
interface Solid{ interface Solid{
@ -81,7 +81,7 @@ Solid <--- Composite
interface SolidReference{ interface SolidReference{
val prototype: Solid val prototype: Solid
fun getPropertyValue(name,inherit,includeStyles,includeDefaults): Value? fun getProperty(name,inherit,includeStyles,includeDefaults): Value?
} }
VisionGroup <---- SolidReference VisionGroup <---- SolidReference
SolidReferenceGroup -- SolidReference SolidReferenceGroup -- SolidReference
@ -91,7 +91,7 @@ class SolidReferenceGroup{
var properties: MutableMeta? var properties: MutableMeta?
val prototype: Solid val prototype: Solid
val children: Map<NameToken, Vision> val children: Map<NameToken, Vision>
fun getPropertyValue(name,inherit,includeStyles,includeDefaults): Value? fun getProperty(name,inherit,includeStyles,includeDefaults): Value?
} }
VisionBase <-- SolidReferenceGroup VisionBase <-- SolidReferenceGroup
VisionGroup <-- SolidReferenceGroup VisionGroup <-- SolidReferenceGroup

View File

@ -1,12 +1,9 @@
kotlin.code.style=official kotlin.code.style=official
kotlin.mpp.stability.nowarn=true kotlin.mpp.stability.nowarn=true
kotlin.jupyter.add.scanner=false kotlin.jupyter.add.scanner=false
#kotlin.incremental.js.ir=true
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1G
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4G
publishing.github=false toolsVersion=0.12.0-kotlin-1.7.20-Beta
publishing.sonatype=false
toolsVersion=0.11.1-kotlin-1.6.10

View File

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

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
id("org.jetbrains.kotlin.jupyter.api") id("org.jetbrains.kotlin.jupyter.api")
} }
@ -21,5 +21,5 @@ kotlin {
} }
readme { readme {
maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
description = "Jupyter api artifact for GDML rendering" description = "Jupyter api artifact for GDML rendering"
@ -56,5 +56,5 @@ kscience {
} }
readme { readme {
maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
} }

View File

@ -12,13 +12,14 @@ pluginManagement {
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
} }
plugins { plugins {
id("ru.mipt.npm.gradle.project") version toolsVersion id("space.kscience.gradle.project") version toolsVersion
id("ru.mipt.npm.gradle.mpp") version toolsVersion id("space.kscience.gradle.mpp") version toolsVersion
id("ru.mipt.npm.gradle.jvm") version toolsVersion id("space.kscience.gradle.jvm") version toolsVersion
id("ru.mipt.npm.gradle.js") version toolsVersion id("space.kscience.gradle.js") version toolsVersion
} }
} }
@ -34,7 +35,7 @@ dependencyResolutionManagement {
versionCatalogs { versionCatalogs {
create("npmlibs") { create("npmlibs") {
from("ru.mipt.npm:version-catalog:$toolsVersion") from("space.kscience:version-catalog:$toolsVersion")
} }
} }
} }

View File

@ -1,6 +1,6 @@
plugins { plugins {
kotlin("js") kotlin("js")
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra

View File

@ -1,5 +1,7 @@
package space.kscience.visionforge.bootstrap package space.kscience.visionforge.bootstrap
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.css.BorderStyle import kotlinx.css.BorderStyle
import kotlinx.css.Color import kotlinx.css.Color
import kotlinx.css.padding import kotlinx.css.padding
@ -15,7 +17,6 @@ import react.RBuilder
import react.dom.attrs import react.dom.attrs
import react.dom.button import react.dom.button
import react.fc import react.fc
import space.kscience.dataforge.meta.withDefault
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString import space.kscience.visionforge.encodeToString
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexColumn
@ -47,6 +48,7 @@ public external interface CanvasControlsProps : Props {
public var vision: Vision? public var vision: Vision?
} }
public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props -> public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn { flexColumn {
flexRow { flexRow {
@ -68,9 +70,10 @@ public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { prop
} }
} }
} }
@OptIn(DelicateCoroutinesApi::class)
propertyEditor( propertyEditor(
ownProperties = props.canvasOptions.meta, scope = props.vision?.manager?.context ?: GlobalScope,
allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode), properties = props.canvasOptions.meta,
descriptor = Canvas3DOptions.descriptor, descriptor = Canvas3DOptions.descriptor,
expanded = false expanded = false
) )

View File

@ -30,7 +30,7 @@ public external interface TabPaneProps : PropsWithChildren {
public val TabPane: FC<TabPaneProps> = fc("TabPane") { props -> public val TabPane: FC<TabPaneProps> = fc("TabPane") { props ->
var activeTab: String? by useState(props.activeTab) var activeTab: String? by useState(props.activeTab)
val children: Array<out ReactElement?> = Children.map(props.children) { val children: Array<out ReactElement<*>?> = Children.map(props.children) {
it.asElementOrNull() it.asElementOrNull()
} ?: emptyArray() } ?: emptyArray()

View File

@ -10,8 +10,8 @@ import react.fc
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.react.visionTree import space.kscience.visionforge.react.visionTree
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
@ -51,7 +51,7 @@ public val ThreeControls: FC<ThreeControlsProps> = fc { props ->
val selectedObject: Vision? = when { val selectedObject: Vision? = when {
selected == null -> null selected == null -> null
selected.isEmpty() -> props.vision selected.isEmpty() -> props.vision
else -> (props.vision as? VisionGroup)?.get(selected) else -> (props.vision as? SolidGroup)?.get(selected)
} }
if (selectedObject != null) { if (selectedObject != null) {
visionPropertyEditor(selectedObject, key = selected) visionPropertyEditor(selectedObject, key = selected)

View File

@ -2,13 +2,17 @@ package space.kscience.visionforge.bootstrap
import org.w3c.dom.Element import org.w3c.dom.Element
import react.RBuilder import react.RBuilder
import react.dom.render import react.dom.client.createRoot
import react.key
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.isEmpty
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.EditorPropertyState
import space.kscience.visionforge.react.PropertyEditor
import space.kscience.visionforge.react.metaViewer import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor import space.kscience.visionforge.react.render
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.SolidReference import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles import space.kscience.visionforge.styles
@ -19,12 +23,26 @@ public fun RBuilder.visionPropertyEditor(
) { ) {
card("Properties") { card("Properties") {
propertyEditor( child(PropertyEditor) {
ownProperties = vision.meta, attrs {
allProperties = vision.computeProperties(), this.key = key?.toString()
descriptor = descriptor, this.meta = vision.properties.root()
key = key this.updates = vision.properties.changes
) this.descriptor = descriptor
this.scope = vision.manager?.context ?: error("Orphan vision could not be observed")
this.getPropertyState = { name ->
val ownMeta = vision.properties.own?.getMeta(name)
if (ownMeta != null && !ownMeta.isEmpty()) {
EditorPropertyState.Defined
} else if (vision.properties.root().getValue(name) != null) {
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
} }
val styles = if (vision is SolidReference) { val styles = if (vision is SolidReference) {
(vision.styles + vision.prototype.styles).distinct() (vision.styles + vision.prototype.styles).distinct()
@ -50,6 +68,6 @@ public fun RBuilder.visionPropertyEditor(
public fun Element.visionPropertyEditor( public fun Element.visionPropertyEditor(
item: Vision, item: Vision,
descriptor: MetaDescriptor? = item.descriptor, descriptor: MetaDescriptor? = item.descriptor,
): Unit = render(this) { ): Unit = createRoot(this).render {
visionPropertyEditor(item, descriptor = descriptor) visionPropertyEditor(item, descriptor = descriptor)
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
dependencies{ dependencies{

View File

@ -10,22 +10,22 @@ import react.dom.attrs
import react.dom.option import react.dom.option
import react.dom.select import react.dom.select
import react.fc import react.fc
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.descriptors.allowedValues import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.values.asValue import space.kscience.dataforge.meta.string
import space.kscience.dataforge.values.string
@JsExport @JsExport
public val MultiSelectChooser: FC<ValueChooserProps> = fc("MultiSelectChooser") { props -> public val MultiSelectChooser: FC<ValueChooserProps> = fc("MultiSelectChooser") { props ->
val onChange: (Event) -> Unit = { event: Event -> val onChange: (Event) -> Unit = { event: Event ->
val newSelected = (event.target as HTMLSelectElement).selectedOptions.asList() val newSelected = (event.target as HTMLSelectElement).selectedOptions.asList()
.map { (it as HTMLOptionElement).value.asValue() } .map { (it as HTMLOptionElement).value.asValue() }
props.meta.value = newSelected.asValue() props.onValueChange(newSelected.asValue())
} }
select { select {
attrs { attrs {
multiple = true multiple = true
values = (props.actual.value?.list ?: emptyList()).mapTo(HashSet()) { it.string } values = (props.value?.list ?: emptyList()).mapTo(HashSet()) { it.string }
onChangeFunction = onChange onChangeFunction = onChange
} }
props.descriptor?.allowedValues?.forEach { optionValue -> props.descriptor?.allowedValues?.forEach { optionValue ->

View File

@ -1,13 +1,18 @@
package space.kscience.visionforge.react package space.kscience.visionforge.react
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import kotlinx.css.properties.TextDecoration import kotlinx.css.properties.TextDecoration
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.* import react.*
import react.dom.attrs import react.dom.attrs
import react.dom.render
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRequirement import space.kscience.dataforge.meta.descriptors.ValueRequirement
@ -19,17 +24,30 @@ import styled.styledButton
import styled.styledDiv import styled.styledDiv
import styled.styledSpan import styled.styledSpan
/**
* The display state of a property
*/
public sealed class EditorPropertyState {
public object Defined : EditorPropertyState()
public class Default(public val source: String = "unknown") : EditorPropertyState()
public object Undefined : EditorPropertyState()
}
public external interface PropertyEditorProps : Props { public external interface PropertyEditorProps : Props {
/** /**
* Root config object - always non-null * Root config object - always non-null
*/ */
public var meta: ObservableMutableMeta public var meta: MutableMeta
/** public var getPropertyState: (Name) -> EditorPropertyState
* Provide default item (greyed out if used)
*/ public var scope: CoroutineScope
public var withDefault: MetaProvider
public var updates: Flow<Name>
/** /**
* Full path to the displayed node in [meta]. Could be empty * Full path to the displayed node in [meta]. Could be empty
@ -54,7 +72,9 @@ private val PropertyEditorItem: FC<PropertyEditorProps> = fc("PropertyEditorItem
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
var expanded: Boolean by useState { props.expanded ?: true } var expanded: Boolean by useState { props.expanded ?: true }
val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) } val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) } var property: MutableMeta by useState { props.meta.getOrCreate(props.name) }
var editorPropertyState: EditorPropertyState by useState { props.getPropertyState(props.name) }
val keys = useMemo(descriptor) { val keys = useMemo(descriptor) {
buildSet { buildSet {
@ -70,17 +90,19 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
val token = props.name.lastOrNull()?.toString() ?: "Properties" val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() { fun update() {
ownProperty = props.meta.getOrCreate(props.name) property = props.meta.getOrCreate(props.name)
editorPropertyState = props.getPropertyState(props.name)
} }
useEffect(props.meta) { useEffect(props.meta) {
props.meta.onChange(props) { updatedName -> val job = props.updates.onEach { updatedName ->
if (updatedName == props.name) { if (updatedName == props.name) {
update() update()
} }
} }.launchIn(props.scope)
cleanup { cleanup {
props.meta.removeListener(props) job.cancel()
} }
} }
@ -115,7 +137,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
styledSpan { styledSpan {
css { css {
+TreeStyles.treeLabel +TreeStyles.treeLabel
if (ownProperty.isEmpty()) { if (editorPropertyState != EditorPropertyState.Defined) {
+TreeStyles.treeLabelInactive +TreeStyles.treeLabelInactive
} }
} }
@ -131,8 +153,12 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
ValueChooser { ValueChooser {
attrs { attrs {
this.descriptor = descriptor this.descriptor = descriptor
this.meta = ownProperty this.state = editorPropertyState
this.actual = props.withDefault.getMeta(props.name) ?: ownProperty this.value = property.value
this.onValueChange = {
property.value = it
editorPropertyState = props.getPropertyState(props.name)
}
} }
} }
} }
@ -156,7 +182,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
} }
+"\u00D7" +"\u00D7"
attrs { attrs {
if (ownProperty.isEmpty()) { if (editorPropertyState!= EditorPropertyState.Defined) {
disabled = true disabled = true
} else { } else {
onClickFunction = removeClick onClickFunction = removeClick
@ -179,9 +205,11 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
attrs { attrs {
this.key = props.name.toString() this.key = props.name.toString()
this.meta = props.meta this.meta = props.meta
this.withDefault = props.withDefault
this.name = props.name + token this.name = props.name + token
this.descriptor = props.descriptor this.descriptor = props.descriptor
this.scope = props.scope
this.getPropertyState = { props.getPropertyState(props.name + token) }
this.updates = props.updates
} }
} }
//configEditor(props.root, props.name + token, props.descriptor, props.default) //configEditor(props.root, props.name + token, props.descriptor, props.default)
@ -197,44 +225,51 @@ public val PropertyEditor: FC<PropertyEditorProps> = fc("PropertyEditor") { prop
attrs { attrs {
this.key = "" this.key = ""
this.meta = props.meta this.meta = props.meta
this.withDefault = props.withDefault
this.name = Name.EMPTY this.name = Name.EMPTY
this.descriptor = props.descriptor this.descriptor = props.descriptor
this.expanded = props.expanded this.expanded = props.expanded
this.scope = props.scope
this.getPropertyState = props.getPropertyState
this.updates = props.updates
} }
} }
} }
@OptIn(ExperimentalCoroutinesApi::class)
public fun RBuilder.propertyEditor( public fun RBuilder.propertyEditor(
ownProperties: ObservableMutableMeta, scope: CoroutineScope,
allProperties: MetaProvider = ownProperties, properties: ObservableMutableMeta,
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
key: Any? = null, key: Any? = null,
expanded: Boolean? = null, expanded: Boolean? = null,
) { ) {
child(PropertyEditor) { child(PropertyEditor) {
attrs { attrs {
this.meta = ownProperties this.meta = properties
this.withDefault = allProperties
this.descriptor = descriptor this.descriptor = descriptor
this.key = key?.toString() ?: "" this.key = key?.toString() ?: ""
this.expanded = expanded this.expanded = expanded
this.scope = scope
this.getPropertyState = { name ->
if (properties[name] != null) {
EditorPropertyState.Defined
} else if (descriptor?.get(name)?.defaultValue != null) {
EditorPropertyState.Default("descriptor")
} else {
EditorPropertyState.Undefined
}
}
this.updates = callbackFlow {
properties.onChange(scope) { name ->
scope.launch {
send(name)
}
}
invokeOnClose {
properties.removeListener(scope)
}
}
} }
} }
} }
public fun RBuilder.configEditor(
config: ObservableMutableMeta,
default: MetaProvider = config,
descriptor: MetaDescriptor? = null,
key: Any? = null,
): Unit = propertyEditor(config, default, descriptor, key = key)
public fun Element.configEditor(
config: ObservableMutableMeta,
default: Meta = config,
descriptor: MetaDescriptor? = null,
key: Any? = null,
): Unit = render(this) {
configEditor(config, default, descriptor, key = key)
}

View File

@ -10,32 +10,34 @@ import react.FC
import react.dom.attrs import react.dom.attrs
import react.fc import react.fc
import react.useState import react.useState
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.descriptors.ValueRequirement import space.kscience.dataforge.meta.descriptors.ValueRequirement
import space.kscience.dataforge.meta.double import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.values.asValue
import styled.css import styled.css
import styled.styledInput import styled.styledInput
@JsExport @JsExport
public val RangeValueChooser: FC<ValueChooserProps> = fc("RangeValueChooser") { props -> public val RangeValueChooser: FC<ValueChooserProps> = fc("RangeValueChooser") { props ->
var innerValue by useState(props.actual.double) var innerValue by useState(props.value?.double)
var rangeDisabled: Boolean by useState(props.meta.value == null) var rangeDisabled: Boolean by useState(props.state != EditorPropertyState.Defined)
val handleDisable: (Event) -> Unit = { val handleDisable: (Event) -> Unit = {
val checkBoxValue = (it.target as HTMLInputElement).checked val checkBoxValue = (it.target as HTMLInputElement).checked
rangeDisabled = !checkBoxValue rangeDisabled = !checkBoxValue
props.meta.value = if (!checkBoxValue) { props.onValueChange(
null if (!checkBoxValue) {
} else { null
innerValue?.asValue() } else {
} innerValue?.asValue()
}
)
} }
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).value val newValue = (it.target as HTMLInputElement).value
props.meta.value = newValue.toDoubleOrNull()?.asValue() props.onValueChange(newValue.toDoubleOrNull()?.asValue())
innerValue = newValue.toDoubleOrNull() innerValue = newValue.toDoubleOrNull()
} }

View File

@ -16,6 +16,7 @@ import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.asSequence
import space.kscience.visionforge.isEmpty import space.kscience.visionforge.isEmpty
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
@ -61,7 +62,7 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
//display as node if any child is visible //display as node if any child is visible
if (obj is VisionGroup) { if (obj is VisionGroup) {
flexRow { flexRow {
if (obj.children.any { !it.key.body.startsWith("@") }) { if (obj.children.keys.any { !it.body.startsWith("@") }) {
styledSpan { styledSpan {
css { css {
+TreeStyles.treeCaret +TreeStyles.treeCaret
@ -81,9 +82,9 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
css { css {
+TreeStyles.tree +TreeStyles.tree
} }
obj.children.entries obj.children.asSequence()
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children .filter { !it.first.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisionGroup)?.isEmpty() ?: true } // ignore empty groups .sortedBy { (it.second as? VisionGroup)?.children?.isEmpty() ?: true } // ignore empty groups
.forEach { (childToken, child) -> .forEach { (childToken, child) ->
styledDiv { styledDiv {
css { css {

View File

@ -0,0 +1,10 @@
package space.kscience.visionforge.react
import react.Props
import react.RBuilder
import react.createElement
import react.dom.client.Root
public fun Root.render(block: RBuilder.() -> Unit) {
render(createElement<Props>(block))
}

View File

@ -19,29 +19,27 @@ import react.useState
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.allowedValues import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.string
import space.kscience.visionforge.Colors import space.kscience.visionforge.Colors
import space.kscience.visionforge.widgetType import space.kscience.visionforge.widgetType
import styled.css import styled.css
import styled.styledInput import styled.styledInput
import styled.styledSelect import styled.styledSelect
import three.math.Color
public external interface ValueChooserProps : Props { public external interface ValueChooserProps : Props {
public var descriptor: MetaDescriptor? public var descriptor: MetaDescriptor?
public var meta: ObservableMutableMeta public var state: EditorPropertyState
public var actual: Meta public var value: Value?
public var onValueChange: (Value?) -> Unit
} }
@JsExport @JsExport
public val StringValueChooser: FC<ValueChooserProps> = fc("StringValueChooser") { props -> public val StringValueChooser: FC<ValueChooserProps> = fc("StringValueChooser") { props ->
var value by useState(props.actual.string ?: "") var value by useState(props.value?.string ?: "")
val keyDown: (Event) -> Unit = { event -> val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { if (event.type == "keydown" && event.asDynamic().key == "Enter") {
value = (event.target as HTMLInputElement).value value = (event.target as HTMLInputElement).value
props.meta.value = value.asValue() props.onValueChange(value.asValue())
} }
} }
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
@ -63,7 +61,7 @@ public val StringValueChooser: FC<ValueChooserProps> = fc("StringValueChooser")
public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser") { props -> public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser") { props ->
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).checked val newValue = (it.target as HTMLInputElement).checked
props.meta.value = newValue.asValue() props.onValueChange(newValue.asValue())
} }
styledInput(type = InputType.checkBox) { styledInput(type = InputType.checkBox) {
css { css {
@ -71,7 +69,7 @@ public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser"
} }
attrs { attrs {
//this.attributes["indeterminate"] = (props.item == null).toString() //this.attributes["indeterminate"] = (props.item == null).toString()
checked = props.actual.boolean ?: false checked = props.value?.boolean ?: false
onChangeFunction = handleChange onChangeFunction = handleChange
} }
} }
@ -79,7 +77,7 @@ public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser"
@JsExport @JsExport
public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser") { props -> public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser") { props ->
var innerValue by useState(props.actual.string ?: "") var innerValue by useState(props.value?.string ?: "")
val keyDown: (Event) -> Unit = { event -> val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { if (event.type == "keydown" && event.asDynamic().key == "Enter") {
innerValue = (event.target as HTMLInputElement).value innerValue = (event.target as HTMLInputElement).value
@ -87,7 +85,7 @@ public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser")
if (number == null) { if (number == null) {
console.error("The input value $innerValue is not a number") console.error("The input value $innerValue is not a number")
} else { } else {
props.meta.value = number.asValue() props.onValueChange(number.asValue())
} }
} }
} }
@ -117,10 +115,10 @@ public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser")
@JsExport @JsExport
public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") { props -> public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") { props ->
var selected by useState(props.actual.string ?: "") var selected by useState(props.value?.string ?: "")
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
selected = (it.target as HTMLSelectElement).value selected = (it.target as HTMLSelectElement).value
props.meta.value = selected.asValue() props.onValueChange(selected.asValue())
} }
styledSelect { styledSelect {
css { css {
@ -132,7 +130,7 @@ public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") {
} }
} }
attrs { attrs {
this.value = props.actual.string ?: "" this.value = props.value?.string ?: ""
multiple = false multiple = false
onChangeFunction = handleChange onChangeFunction = handleChange
} }
@ -142,7 +140,7 @@ public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") {
@JsExport @JsExport
public val ColorValueChooser: FC<ValueChooserProps> = fc("ColorValueChooser") { props -> public val ColorValueChooser: FC<ValueChooserProps> = fc("ColorValueChooser") { props ->
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
props.meta.value = (it.target as HTMLInputElement).value.asValue() props.onValueChange((it.target as HTMLInputElement).value.asValue())
} }
styledInput(type = InputType.color) { styledInput(type = InputType.color) {
css { css {
@ -150,9 +148,9 @@ public val ColorValueChooser: FC<ValueChooserProps> = fc("ColorValueChooser") {
margin(0.px) margin(0.px)
} }
attrs { attrs {
this.value = props.actual.value?.let { value -> this.value = props.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string else "#" + Color(value.string).getHexString()
} ?: "#000000" } ?: "#000000"
onChangeFunction = handleChange onChangeFunction = handleChange
} }

View File

@ -1,11 +1,11 @@
plugins { plugins {
id("ru.mipt.npm.gradle.js") id("space.kscience.gradle.js")
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra
kotlin{ kotlin{
js{ js(IR){
useCommonJs() useCommonJs()
browser { browser {
commonWebpackConfig { commonWebpackConfig {

View File

@ -5,39 +5,46 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import react.* import react.*
import react.dom.b
import react.dom.div import react.dom.div
import react.dom.p
import react.dom.span import react.dom.span
import ringui.* import ringui.*
import space.kscience.dataforge.context.Context import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.isEmpty import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.length import space.kscience.dataforge.names.length
import space.kscience.visionforge.* import space.kscience.visionforge.*
import space.kscience.visionforge.react.ThreeCanvasComponent import space.kscience.visionforge.react.*
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidGroup import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.solidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
public external interface ThreeCanvasWithControlsProps : Props { public external interface ThreeCanvasWithControlsProps : Props {
public var context: Context public var solids: Solids
public var builderOfSolid: Deferred<Solid?> public var builderOfSolid: Deferred<Solid?>
public var selected: Name? public var selected: Name?
public var options: Canvas3DOptions? public var options: Canvas3DOptions?
public var additionalTabs: Map<String, RBuilder.() -> Unit>? public var additionalTabs: Map<String, RBuilder.() -> Unit>?
} }
private val ThreeCanvasWithControlsProps.context get() = solids.context
public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) { public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) {
builderOfSolid = context.async { builderOfSolid = context.async {
SolidGroup(block) solids.solidGroup(null, block)
} }
} }
public fun ThreeCanvasWithControlsProps.options(block: Canvas3DOptions.() -> Unit) {
options = Canvas3DOptions(block)
}
public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) { public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.() -> Unit) {
additionalTabs = (additionalTabs ?: emptyMap()) + (title to block) additionalTabs = (additionalTabs ?: emptyMap()) + (title to block)
} }
@ -77,14 +84,14 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): Unit = styled
@JsExport @JsExport
public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("ThreeViewWithControls") { props -> public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("ThreeViewWithControls") { props ->
var selected by useState { props.selected } var selected: Name? by useState { props.selected }
var solid: Solid? by useState(null) var solid: Solid? by useState(null)
useEffect { useEffect {
props.context.launch { props.context.launch {
solid = props.builderOfSolid.await() solid = props.builderOfSolid.await()
//ensure that the solid is properly rooted //ensure that the solid is properly rooted
if(solid?.parent == null){ if (solid?.parent == null) {
solid?.setAsRoot(props.context.visionManager) solid?.setAsRoot(props.context.visionManager)
} }
} }
@ -104,7 +111,7 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
selected?.let { selected?.let {
when { when {
it.isEmpty() -> solid it.isEmpty() -> solid
else -> (solid as? VisionGroup)?.get(it) else -> (solid as? SolidGroup)?.get(it)
} }
} }
} }
@ -160,12 +167,31 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
nameCrumbs(selected) { selected = it } nameCrumbs(selected) { selected = it }
} }
IslandContent { IslandContent {
propertyEditor( child(PropertyEditor) {
ownProperties = vision.meta, attrs {
allProperties = vision.computeProperties(), this.key = selected.toString()
descriptor = vision.descriptor, this.meta = vision.properties.root()
key = selected this.updates = vision.properties.changes
) this.descriptor = vision.descriptor
this.scope = props.context
this.getPropertyState = { name ->
if (vision.properties.own?.get(name) != null) {
EditorPropertyState.Defined
} else if (vision.properties.root()[name] != null) {
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
vision.styles.takeIf { it.isNotEmpty() }?.let { styles ->
p {
b { +"Styles: " }
+styles.joinToString(separator = ", ")
}
}
} }
} }
} }

View File

@ -2,7 +2,7 @@ package space.kscience.visionforge.ring
import kotlinx.coroutines.async import kotlinx.coroutines.async
import org.w3c.dom.Element import org.w3c.dom.Element
import react.child import react.dom.client.createRoot
import space.kscience.dataforge.context.AbstractPlugin import space.kscience.dataforge.context.AbstractPlugin
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory import space.kscience.dataforge.context.PluginFactory
@ -12,6 +12,7 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.visionforge.ElementVisionRenderer import space.kscience.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.solid.three.ThreePlugin
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -25,10 +26,10 @@ public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer {
if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING * 2 else ElementVisionRenderer.ZERO_RATING if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING * 2 else ElementVisionRenderer.ZERO_RATING
override fun render(element: Element, vision: Vision, meta: Meta) { override fun render(element: Element, vision: Vision, meta: Meta) {
react.dom.render(element) { createRoot(element).render {
child(ThreeCanvasWithControls) { child(ThreeCanvasWithControls) {
attrs { attrs {
this.context = this@ThreeWithControlsPlugin.context this.solids = three.solids
this.builderOfSolid = context.async { vision as Solid} this.builderOfSolid = context.async { vision as Solid}
} }
} }
@ -45,6 +46,7 @@ public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer {
public companion object : PluginFactory<ThreeWithControlsPlugin> { public companion object : PluginFactory<ThreeWithControlsPlugin> {
override val tag: PluginTag = PluginTag("vision.threejs.withControls", PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag("vision.threejs.withControls", PluginTag.DATAFORGE_GROUP)
override val type: KClass<ThreeWithControlsPlugin> = ThreeWithControlsPlugin::class override val type: KClass<ThreeWithControlsPlugin> = ThreeWithControlsPlugin::class
override fun invoke(meta: Meta, context: Context): ThreeWithControlsPlugin = ThreeWithControlsPlugin()
override fun build(context: Context, meta: Meta): ThreeWithControlsPlugin = ThreeWithControlsPlugin()
} }
} }

View File

@ -2,18 +2,18 @@ package space.kscience.visionforge.ring
import org.w3c.dom.Element import org.w3c.dom.Element
import react.RBuilder import react.RBuilder
import react.dom.client.createRoot
import react.dom.p import react.dom.p
import react.dom.render import react.key
import ringui.Island import ringui.Island
import ringui.SmartTabs import ringui.SmartTabs
import ringui.Tab import ringui.Tab
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.get
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.*
import space.kscience.visionforge.react.metaViewer import space.kscience.visionforge.root
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidReference import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles import space.kscience.visionforge.styles
@ -30,12 +30,25 @@ public fun RBuilder.ringPropertyEditor(
flexColumn { flexColumn {
Island("Properties") { Island("Properties") {
propertyEditor( child(PropertyEditor) {
ownProperties = vision.meta, attrs {
allProperties = vision.computeProperties(), this.key = key?.toString()
descriptor = descriptor, this.meta = vision.properties.root()
key = key this.updates = vision.properties.changes
) this.descriptor = descriptor
this.scope = vision.manager?.context ?: error("Orphan vision could not be observed")
this.getPropertyState = {name->
if(vision.properties.own?.get(name)!= null){
EditorPropertyState.Defined
} else if(vision.properties.root()[name] != null){
// TODO differentiate
EditorPropertyState.Default()
} else {
EditorPropertyState.Undefined
}
}
}
}
} }
if (styles.isNotEmpty()) { if (styles.isNotEmpty()) {
@ -72,6 +85,6 @@ public fun RBuilder.ringPropertyEditor(
public fun Element.ringPropertyEditor( public fun Element.ringPropertyEditor(
item: Vision, item: Vision,
descriptor: MetaDescriptor? = item.descriptor, descriptor: MetaDescriptor? = item.descriptor,
): Unit = render(this) { ): Unit = createRoot(this).render {
ringPropertyEditor(item, descriptor = descriptor) ringPropertyEditor(item, descriptor = descriptor)
} }

View File

@ -1,5 +1,7 @@
package space.kscience.visionforge.ring package space.kscience.visionforge.ring
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.css.BorderStyle import kotlinx.css.BorderStyle
import kotlinx.css.Color import kotlinx.css.Color
import kotlinx.css.padding import kotlinx.css.padding
@ -18,7 +20,6 @@ import react.fc
import ringui.Island import ringui.Island
import ringui.SmartTabs import ringui.SmartTabs
import ringui.Tab import ringui.Tab
import space.kscience.dataforge.meta.withDefault
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString import space.kscience.visionforge.encodeToString
@ -52,6 +53,7 @@ internal external interface CanvasControlsProps : Props {
public var vision: Vision? public var vision: Vision?
} }
@OptIn(DelicateCoroutinesApi::class)
internal val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props -> internal val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn { flexColumn {
flexRow { flexRow {
@ -75,8 +77,8 @@ internal val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { pr
} }
} }
propertyEditor( propertyEditor(
ownProperties = props.options.meta, scope = props.vision?.manager?.context ?: GlobalScope,
allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode), properties = props.options.meta,
descriptor = Canvas3DOptions.descriptor, descriptor = Canvas3DOptions.descriptor,
expanded = false expanded = false
) )

View File

@ -183,7 +183,7 @@ public class space/kscience/visionforge/SimpleVisionPropertyContainer : space/ks
public fun <init> (Lspace/kscience/dataforge/meta/ObservableMutableMeta;)V public fun <init> (Lspace/kscience/dataforge/meta/ObservableMutableMeta;)V
public synthetic fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta; public synthetic fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta;
public fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta; public fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta;
public fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value; public fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
} }
public final class space/kscience/visionforge/StyleReference { public final class space/kscience/visionforge/StyleReference {
@ -241,8 +241,8 @@ public abstract interface class space/kscience/visionforge/Vision : space/kscien
public fun getManager ()Lspace/kscience/visionforge/VisionManager; public fun getManager ()Lspace/kscience/visionforge/VisionManager;
public abstract fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta; public abstract fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta;
public abstract fun getParent ()Lspace/kscience/visionforge/VisionGroup; public abstract fun getParent ()Lspace/kscience/visionforge/VisionGroup;
public abstract fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value; public abstract fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getPropertyValue$default (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value; public static synthetic fun getProperty$default (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
public abstract fun invalidateProperty (Lspace/kscience/dataforge/names/Name;)V public abstract fun invalidateProperty (Lspace/kscience/dataforge/names/Name;)V
public abstract fun setParent (Lspace/kscience/visionforge/VisionGroup;)V public abstract fun setParent (Lspace/kscience/visionforge/VisionGroup;)V
public abstract fun update (Lspace/kscience/visionforge/VisionChange;)V public abstract fun update (Lspace/kscience/visionforge/VisionChange;)V
@ -266,7 +266,7 @@ public class space/kscience/visionforge/VisionBase : space/kscience/visionforge/
protected final fun getOrCreateProperties ()Lspace/kscience/dataforge/meta/MutableMeta; protected final fun getOrCreateProperties ()Lspace/kscience/dataforge/meta/MutableMeta;
public fun getParent ()Lspace/kscience/visionforge/VisionGroup; public fun getParent ()Lspace/kscience/visionforge/VisionGroup;
protected final fun getProperties ()Lspace/kscience/dataforge/meta/MutableMeta; protected final fun getProperties ()Lspace/kscience/dataforge/meta/MutableMeta;
public fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value; public fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public fun invalidateProperty (Lspace/kscience/dataforge/names/Name;)V public fun invalidateProperty (Lspace/kscience/dataforge/names/Name;)V
public fun setParent (Lspace/kscience/visionforge/VisionGroup;)V public fun setParent (Lspace/kscience/visionforge/VisionGroup;)V
protected final fun setProperties (Lspace/kscience/dataforge/meta/MutableMeta;)V protected final fun setProperties (Lspace/kscience/dataforge/meta/MutableMeta;)V
@ -446,8 +446,8 @@ public final class space/kscience/visionforge/VisionGroupKt {
public final class space/kscience/visionforge/VisionKt { public final class space/kscience/visionforge/VisionKt {
public static final fun getPropertyChanges (Lspace/kscience/visionforge/Vision;)Lkotlinx/coroutines/flow/Flow; public static final fun getPropertyChanges (Lspace/kscience/visionforge/Vision;)Lkotlinx/coroutines/flow/Flow;
public static final fun getPropertyValue (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZ)Lspace/kscience/dataforge/values/Value; public static final fun getProperty (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getPropertyValue$default (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value; public static synthetic fun getProperty$default (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
public static final fun getVisible (Lspace/kscience/visionforge/Vision;)Ljava/lang/Boolean; public static final fun getVisible (Lspace/kscience/visionforge/Vision;)Ljava/lang/Boolean;
public static final fun onPropertyChange (Lspace/kscience/visionforge/Vision;Lkotlin/jvm/functions/Function2;)V public static final fun onPropertyChange (Lspace/kscience/visionforge/Vision;Lkotlin/jvm/functions/Function2;)V
public static final fun setProperty (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/names/Name;Ljava/lang/Object;)V public static final fun setProperty (Lspace/kscience/visionforge/Vision;Lspace/kscience/dataforge/names/Name;Ljava/lang/Object;)V
@ -499,8 +499,8 @@ public abstract class space/kscience/visionforge/VisionPlugin : space/kscience/d
public abstract interface class space/kscience/visionforge/VisionPropertyContainer { public abstract interface class space/kscience/visionforge/VisionPropertyContainer {
public abstract fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta; public abstract fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta;
public abstract fun getPropertyValue (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value; public abstract fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)Lspace/kscience/dataforge/values/Value;
public static synthetic fun getPropertyValue$default (Lspace/kscience/visionforge/VisionPropertyContainer;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value; public static synthetic fun getProperty$default (Lspace/kscience/visionforge/VisionPropertyContainer;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
} }
public final class space/kscience/visionforge/html/HeadersKt { public final class space/kscience/visionforge/html/HeadersKt {

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("ru.mipt.npm.gradle.mpp") id("space.kscience.gradle.mpp")
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra
@ -13,6 +13,11 @@ kotlin {
api("org.jetbrains.kotlin-wrappers:kotlin-css") api("org.jetbrains.kotlin-wrappers:kotlin-css")
} }
} }
commonTest{
dependencies{
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${space.kscience.gradle.KScienceVersions.coroutinesVersion}")
}
}
jsMain { jsMain {
dependencies { dependencies {
api("org.jetbrains.kotlin-wrappers:kotlin-extensions") api("org.jetbrains.kotlin-wrappers:kotlin-extensions")
@ -28,5 +33,5 @@ kscience{
} }
readme{ readme{
maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT maturity = space.kscience.gradle.Maturity.DEVELOPMENT
} }

View File

@ -0,0 +1,30 @@
package space.kscience.visionforge
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
@Serializable
public abstract class AbstractVision : Vision {
@Transient
override var parent: Vision? = null
@SerialName("properties")
protected var propertiesInternal: MutableMeta? = null
final override val properties: MutableVisionProperties by lazy {
object : AbstractVisionProperties(this) {
override var properties: MutableMeta?
get() = propertiesInternal
set(value) {
propertiesInternal = value
}
}
}
override val descriptor: MetaDescriptor? get() = null
}

View File

@ -1,11 +1,6 @@
package space.kscience.visionforge package space.kscience.visionforge
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.string
import kotlin.math.max import kotlin.math.max
/** /**

View File

@ -1,84 +0,0 @@
package space.kscience.visionforge
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.MutableValueProvider
import space.kscience.dataforge.values.Value
private class ComputedVisionProperties(
val vision: Vision,
val pathName: Name,
val visionDescriptor: MetaDescriptor,
val parentInheritFlag: Boolean?,
val parentStylesFlag: Boolean?
) : Meta {
val descriptor: MetaDescriptor? by lazy { visionDescriptor[pathName] }
override val items: Map<NameToken, Meta>
get() {
val metaKeys = vision.meta.getMeta(pathName)?.items?.keys ?: emptySet()
val descriptorKeys = descriptor?.children?.map { NameToken(it.key) } ?: emptySet()
val inheritFlag = descriptor?.inherited ?: parentInheritFlag
val stylesFlag = descriptor?.usesStyles ?: parentStylesFlag
return (metaKeys + descriptorKeys).associateWith {
ComputedVisionProperties(
vision,
pathName + it,
visionDescriptor,
inheritFlag,
stylesFlag
)
}
}
override val value: Value?
get() {
val inheritFlag = descriptor?.inherited ?: parentInheritFlag ?: false
val stylesFlag = descriptor?.usesStyles ?: parentStylesFlag ?: true
return vision.getPropertyValue(pathName, inheritFlag, stylesFlag, true)
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
/**
* Compute property node based on inheritance and style information from the descriptor
*/
public fun Vision.computeProperties(descriptor: MetaDescriptor? = this.descriptor): Meta =
if (descriptor == null) meta else ComputedVisionProperties(this, Name.EMPTY, descriptor, null, null)
public fun Vision.computePropertyNode(
name: Name,
descriptor: MetaDescriptor? = this.descriptor
): Meta? = computeProperties(descriptor)[name]
/**
* Compute the property based on the provided value descriptor. By default, use Vision own descriptor
*/
public fun Vision.computeProperty(name: Name, valueDescriptor: MetaDescriptor? = descriptor?.get(name)): Value? {
val inheritFlag = valueDescriptor?.inherited ?: false
val stylesFlag = valueDescriptor?.usesStyles ?: true
return getPropertyValue(name, inheritFlag, stylesFlag)
}
/**
* Accessor to all vision properties
*/
public fun Vision.computePropertyValues(
descriptor: MetaDescriptor? = this.descriptor
): MutableValueProvider = object : MutableValueProvider {
override fun getValue(name: Name): Value? = computeProperty(name, descriptor?.get(name))
override fun setValue(name: Name, value: Value?) {
setProperty(name, value)
}
}

View File

@ -9,7 +9,7 @@ import kotlin.properties.ReadOnlyProperty
/** /**
* A reference to a style defined in a specific container * A reference to a style defined in a specific container
*/ */
public class StyleReference(public val owner: VisionGroup, public val name: String) public class StyleReference(public val owner: Vision, public val name: String)
private tailrec fun styleIsDefined(vision: Vision, reference: StyleReference): Boolean = when { private tailrec fun styleIsDefined(vision: Vision, reference: StyleReference): Boolean = when {
reference.owner === vision -> true reference.owner === vision -> true
@ -18,14 +18,14 @@ private tailrec fun styleIsDefined(vision: Vision, reference: StyleReference): B
} }
@VisionBuilder @VisionBuilder
public fun Vision.useStyle(reference: StyleReference) { public fun Vision.useStyle(reference: StyleReference, notify: Boolean = true) {
//check that style is defined in a parent //check that style is defined in a parent
//check(styleIsDefined(this, reference)) { "Style reference does not belong to a Vision parent" } //check(styleIsDefined(this, reference)) { "Style reference does not belong to a Vision parent" }
useStyle(reference.name) useStyle(reference.name, notify)
} }
@VisionBuilder @VisionBuilder
public fun VisionGroup.style( public fun Vision.style(
styleKey: String? = null, styleKey: String? = null,
builder: MutableMeta.() -> Unit, builder: MutableMeta.() -> Unit,
): ReadOnlyProperty<Any?, StyleReference> = ReadOnlyProperty { _, property -> ): ReadOnlyProperty<Any?, StyleReference> = ReadOnlyProperty { _, property ->
@ -35,7 +35,7 @@ public fun VisionGroup.style(
} }
@VisionBuilder @VisionBuilder
public fun <T : Scheme> VisionGroup.style( public fun <T : Scheme> Vision.style(
spec: Specification<T>, spec: Specification<T>,
styleKey: String? = null, styleKey: String? = null,
builder: T.() -> Unit, builder: T.() -> Unit,

View File

@ -5,20 +5,17 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.stringList
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
/** /**
* A container for styles * A container for styles
*/ */
@JvmInline @JvmInline
public value class StyleSheet(private val owner: VisionGroup) { public value class StyleSheet(private val owner: Vision) {
private val styleNode: Meta? get() = owner.meta[STYLESHEET_KEY] private val styleNode: Meta get() = owner.properties.getProperty(STYLESHEET_KEY)
public val items: Map<NameToken, Meta>? get() = styleNode?.items public val items: Map<NameToken, Meta> get() = styleNode.items
public operator fun get(key: String): Meta? = owner.getStyle(key) public operator fun get(key: String): Meta? = owner.getStyle(key)
@ -26,7 +23,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
* Define a style without notifying owner * Define a style without notifying owner
*/ */
public fun define(key: String, style: Meta?) { public fun define(key: String, style: Meta?) {
owner.meta.setMeta(STYLESHEET_KEY + key, style) owner.properties.setProperty(STYLESHEET_KEY + key, style)
} }
/** /**
@ -43,7 +40,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
/** /**
* Create and set a style * Create and set a style
*/ */
public operator fun set(key: String, builder: MutableMeta.() -> Unit) { public fun update(key: String, builder: MutableMeta.() -> Unit) {
val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder) val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder)
set(key, newStyle.seal()) set(key, newStyle.seal())
} }
@ -59,49 +56,48 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
val tokens: Collection<Name> = val tokens: Collection<Name> =
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet())) ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
.map { it.asName() } .map { it.asName() }
tokens.forEach { parent?.invalidateProperty(it) } tokens.forEach { parent?.properties?.invalidate(it) }
} }
if (this is VisionGroup) { children?.forEach { _, vision ->
for (obj in this) { vision.styleChanged(key, oldStyle, newStyle)
obj.styleChanged(key, oldStyle, newStyle)
}
} }
} }
/** /**
* List of names of styles applied to this object. Order matters. Not inherited. * List of names of styles applied to this object. Order matters. Not inherited.
*/ */
public var Vision.styles: List<String> public var Vision.styles: List<String>
get() = meta.getValue(Vision.STYLE_KEY)?.stringList ?: emptyList() get() = properties.getValue(Vision.STYLE_KEY, inherit = false, includeStyles = false)?.stringList ?: emptyList()
set(value) { set(value) {
meta.setValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue()) properties.setValue(Vision.STYLE_KEY, value.map { it.asValue() }.asValue())
} }
/** /**
* A stylesheet for this group and its descendants. Stylesheet is not applied directly, * A stylesheet for this group and its descendants. Stylesheet is not applied directly,
* but instead is just a repository for named configurations. * but instead is just a repository for named configurations.
*/ */
public val VisionGroup.styleSheet: StyleSheet get() = StyleSheet(this) public val Vision.styleSheet: StyleSheet get() = StyleSheet(this)
/** /**
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment. * Add style name to the list of styles to be resolved later.
* The style with given name does not necessary exist at the moment.
*/ */
public fun Vision.useStyle(name: String) { public fun Vision.useStyle(name: String, notify: Boolean = true) {
styles = (meta.getMeta(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name val newStyle = properties.own?.get(Vision.STYLE_KEY)?.value?.list?.plus(name.asValue()) ?: listOf(name.asValue())
properties.setValue(Vision.STYLE_KEY, newStyle.asValue(), notify)
} }
/** /**
* Find a style with given name for given [Vision]. The style is not necessary applied to this [Vision]. * Resolve a style with given name for given [Vision]. The style is not necessarily applied to this [Vision].
*/ */
public tailrec fun Vision.getStyle(name: String): Meta? = public fun Vision.getStyle(name: String): Meta? =
meta.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name) properties.own?.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name)
/** /**
* Resolve a property from all styles * Resolve a property from all styles
*/ */
public fun Vision.getStyleProperty(name: Name): Value? = styles.firstNotNullOfOrNull { getStyle(it)?.get(name)?.value } public fun Vision.getStyleProperty(name: Name): Meta? = styles.firstNotNullOfOrNull { getStyle(it)?.get(name) }
/** /**
* Resolve an item in all style layers * Resolve an item in all style layers

View File

@ -1,147 +1,73 @@
package space.kscience.visionforge package space.kscience.visionforge
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.descriptors.Described import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.startsWith import space.kscience.visionforge.AbstractVisionGroup.Companion.updateProperties
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.boolean
import space.kscience.visionforge.Vision.Companion.TYPE import space.kscience.visionforge.Vision.Companion.TYPE
import kotlin.reflect.KProperty1
/** /**
* A root type for display hierarchy * A root type for display hierarchy
*/ */
@Type(TYPE) @Type(TYPE)
public interface Vision : Described, Configurable { public interface Vision : Described {
/** /**
* The parent object of this one. If null, this one is a root. * The parent object of this one. If null, this one is a root.
*/ */
public var parent: VisionGroup? public var parent: Vision?
/** /**
* Owner [VisionManager]. Used to define coroutine scope a serialization * Owner [VisionManager]. Used to define coroutine scope a serialization
*/ */
public val manager: VisionManager? get() = parent?.manager public val manager: VisionManager? get() = parent?.manager
/**
* This Vision own properties (ignoring inheritance, styles and defaults)
*/
override val meta: ObservableMutableMeta
/** public val properties: MutableVisionProperties
* Get property value with given layer flags.
* @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false.
* @param includeStyles toggles inclusion of properties from styles. default is true
*/
public fun getPropertyValue(
name: Name,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): Value?
/**
* Notify all listeners that a property has been changed and should be invalidated
*/
public fun invalidateProperty(propertyName: Name)
/** /**
* Update this vision using a dif represented by [VisionChange]. * Update this vision using a dif represented by [VisionChange].
*/ */
public fun update(change: VisionChange) public fun update(change: VisionChange) {
change.properties?.let {
updateProperties(it, Name.EMPTY)
}
}
override val descriptor: MetaDescriptor? override val descriptor: MetaDescriptor?
public companion object { public companion object {
public const val TYPE: String = "vision" public const val TYPE: String = "vision"
public val STYLE_KEY: Name = "@style".asName() public val STYLE_KEY: Name = "@style".asName()
public const val STYLE_TARGET: String = "style"
public val VISIBLE_KEY: Name = "visible".asName() public val VISIBLE_KEY: Name = "visible".asName()
} }
} }
/** /**
* Flow of property invalidation events. It does not contain property values after invalidation since it is not clear * Control visibility of the element
* if it should include inherited properties etc.
*/ */
@OptIn(ExperimentalCoroutinesApi::class) public var Vision.visible: Boolean?
@DFExperimental get() = properties.getValue(Vision.VISIBLE_KEY)?.boolean
public val Vision.propertyChanges: Flow<Name> set(value) {
get() = callbackFlow { properties.setValue(Vision.VISIBLE_KEY, value?.asValue())
meta.onChange(this) { name ->
launch {
send(name)
}
}
awaitClose {
meta.removeListener(this)
}
} }
/** /**
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled * Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
*/ */
public fun Vision.onPropertyChange(callback: Meta.(Name) -> Unit) { public fun Vision.onPropertyChange(
meta.onChange(null, callback) scope: CoroutineScope? = manager?.context,
} callback: (Name) -> Unit
): Job = properties.changes.onEach {
/** callback(it)
* Get [Vision] property using key as a String }.launchIn(scope ?: error("Orphan Vision can't observe properties"))
*/
public fun Vision.getPropertyValue(
key: String,
inherit: Boolean = false,
includeStyles: Boolean = true,
includeDefaults: Boolean = true,
): Value? = getPropertyValue(Name.parse(key), inherit, includeStyles, includeDefaults)
/**
* A convenience method to set property node or value. If Item is null, then node is removed, not a value
*/
public fun Vision.setProperty(name: Name, item: Any?) {
when (item) {
null -> meta.remove(name)
is Meta -> meta.setMeta(name, item)
is Value -> meta.setValue(name, item)
else -> meta.setValue(name, Value.of(item))
}
}
public fun Vision.setPropertyNode(key: String, item: Any?) {
setProperty(Name.parse(key), item)
}
/**
* Control visibility of the element
*/
public var Vision.visible: Boolean?
get() = getPropertyValue(Vision.VISIBLE_KEY)?.boolean
set(value) = meta.setValue(Vision.VISIBLE_KEY, value?.asValue())
public fun <V : Vision, T> V.useProperty(
property: KProperty1<V, T>,
owner: Any? = null,
callBack: V.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
meta.onChange(owner) { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}
}

View File

@ -1,176 +0,0 @@
package space.kscience.visionforge
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
import kotlin.jvm.Synchronized
internal data class MetaListener(
val owner: Any? = null,
val callback: Meta.(name: Name) -> Unit,
)
/**
* A full base implementation for a [Vision]
* @param parent the parent object for this vision. Could've set later. Not serialized.
*/
@Serializable
@SerialName("vision")
public open class VisionBase(
@Transient override var parent: VisionGroup? = null,
@EncodeDefault protected var properties: MutableMeta? = null,
) : Vision {
@Synchronized
protected fun getOrCreateProperties(): MutableMeta {
if (properties == null) {
val newProperties = MutableMeta()
properties = newProperties
}
return properties!!
}
@Transient
private val listeners: MutableList<MetaListener> = mutableListOf()
private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta {
override val items: Map<NameToken, ObservableMutableMeta>
get() = properties?.get(pathName)?.items?.mapValues { entry ->
VisionProperties(pathName + entry.key)
} ?: emptyMap()
override var value: Value?
get() = properties?.get(pathName)?.value
set(value) {
val oldValue = properties?.get(pathName)?.value
getOrCreateProperties().setValue(pathName, value)
if (oldValue != value) {
invalidate(Name.EMPTY)
}
}
override fun getOrCreate(name: Name): ObservableMutableMeta = VisionProperties(pathName + name)
override fun setMeta(name: Name, node: Meta?) {
getOrCreateProperties().setMeta(pathName + name, node)
invalidate(name)
}
@DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) {
val ownProperties = getOrCreateProperties()
if (ownProperties is ObservableMutableMeta) {
ownProperties.attach(pathName + name, node)
} else {
ownProperties.setMeta(pathName + name, node)
node.onChange(this) { childName ->
ownProperties.setMeta(pathName + name + childName, this[childName])
}
}
}
override fun invalidate(name: Name) {
invalidateProperty(pathName + name)
}
@Synchronized
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
if (pathName.isEmpty()) {
listeners.add((MetaListener(owner, callback)))
} else {
listeners.add(MetaListener(owner) { name ->
if (name.startsWith(pathName)) {
(this@MetaListener[pathName] ?: Meta.EMPTY).callback(name.removeHeadOrNull(pathName)!!)
}
})
}
}
@Synchronized
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
final override val meta: ObservableMutableMeta get() = VisionProperties(Name.EMPTY)
override fun getPropertyValue(
name: Name,
inherit: Boolean,
includeStyles: Boolean,
includeDefaults: Boolean,
): Value? {
properties?.get(name)?.value?.let { return it }
if (includeStyles) {
getStyleProperty(name)?.let { return it }
}
if (inherit) {
parent?.getPropertyValue(name, inherit, includeStyles, includeDefaults)?.let { return it }
}
if (includeDefaults) {
descriptor?.defaultNode?.get(name)?.value.let { return it }
}
return null
}
override val descriptor: MetaDescriptor? get() = null
override fun invalidateProperty(propertyName: Name) {
if (propertyName == STYLE_KEY) {
styles.mapNotNull { getStyle(it) }.asSequence()
.flatMap { it.items.asSequence() }
.distinctBy { it.key }
.forEach {
invalidateProperty(it.key.asName())
}
}
listeners.forEach { it.callback(properties ?: Meta.EMPTY, propertyName) }
}
override fun update(change: VisionChange) {
change.properties?.let {
updateProperties(Name.EMPTY, it)
}
}
public companion object {
public val descriptor: MetaDescriptor = MetaDescriptor {
value(STYLE_KEY, ValueType.STRING) {
multiple = true
}
}
public fun Vision.updateProperties(at: Name, item: Meta) {
meta.setValue(at, item.value)
item.items.forEach { (token, item) ->
updateProperties(at + token, item)
}
}
}
}
//fun VisualObject.findStyle(styleName: Name): Meta? {
// if (this is VisualGroup) {
// val style = resolveStyle(styleName)
// if (style != null) return style
// }
// return parent?.findStyle(styleName)
//}

View File

@ -7,54 +7,75 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Null
import kotlin.jvm.Synchronized import kotlin.jvm.Synchronized
import kotlin.time.Duration import kotlin.time.Duration
/** /**
* Create a deep copy of given Vision without external connections. * Create a deep copy of given Vision without external connections.
*/ */
private fun Vision.deepCopy(): Vision { private fun Vision.deepCopy(manager: VisionManager): Vision {
if (this is NullVision) return NullVision
//Assuming that unrooted visions are already isolated //Assuming that unrooted visions are already isolated
val manager = this.manager ?: return this
//TODO replace by efficient deep copy //TODO replace by efficient deep copy
val json = manager.encodeToJsonElement(this) val json = manager.encodeToJsonElement(this)
return manager.decodeFromJson(json) return manager.decodeFromJson(json)
} }
/** /**
* An update for a [Vision] or a [VisionGroup] * A vision used only in change propagation and showing that the target should be removed
*/ */
public class VisionChangeBuilder : VisionContainerBuilder<Vision> { @Serializable
public object NullVision : Vision {
override var parent: Vision?
get() = null
set(_) {
error("Can't set parent for null vision")
}
override val properties: MutableVisionProperties get() = error("Can't get properties of `NullVision`")
override val descriptor: MetaDescriptor? = null
}
/**
* An update for a [Vision]
*/
public class VisionChangeBuilder(private val manager: VisionManager) : MutableVisionContainer<Vision> {
private var reset: Boolean = false
private var vision: Vision? = null private var vision: Vision? = null
private val propertyChange = MutableMeta() private var propertyChange = MutableMeta()
private val children: HashMap<Name, VisionChangeBuilder> = HashMap() private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty() public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
@Synchronized @Synchronized
private fun getOrPutChild(visionName: Name): VisionChangeBuilder = private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
children.getOrPut(visionName) { VisionChangeBuilder() } children.getOrPut(visionName) { VisionChangeBuilder(manager) }
public fun propertyChanged(visionName: Name, propertyName: Name, item: Meta?) { public fun propertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
if (visionName == Name.EMPTY) { if (visionName == Name.EMPTY) {
//Write property removal as [Null] //Write property removal as [Null]
propertyChange[propertyName] = (item ?: Meta(Null)) if (propertyName.isEmpty()) {
propertyChange = item?.toMutableMeta() ?: MutableMeta()
} else {
propertyChange[propertyName] = (item ?: Meta(Null))
}
} else { } else {
getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item) getOrPutChild(visionName).propertyChanged(Name.EMPTY, propertyName, item)
} }
} }
override fun set(name: Name?, child: Vision?) { override fun setChild(name: Name?, child: Vision?) {
if (name == null) error("Static children are not allowed in VisionChange") if (name == null) error("Static children are not allowed in VisionChange")
getOrPutChild(name).apply { getOrPutChild(name).apply {
vision = child vision = child ?: NullVision
reset = vision == null
} }
} }
@ -62,32 +83,28 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
* Isolate collected changes by creating detached copies of given visions * Isolate collected changes by creating detached copies of given visions
*/ */
public fun deepCopy(): VisionChange = VisionChange( public fun deepCopy(): VisionChange = VisionChange(
reset, vision?.deepCopy(manager),
vision?.deepCopy(),
if (propertyChange.isEmpty()) null else propertyChange.seal(), if (propertyChange.isEmpty()) null else propertyChange.seal(),
if (children.isEmpty()) null else children.mapValues { it.value.deepCopy() } if (children.isEmpty()) null else children.mapValues { it.value.deepCopy() }
) )
} }
/** /**
* @param delete flag showing that this vision child should be removed * @param vision a new value for vision content. If the Vision is to be removed should be [NullVision]
* @param vision a new value for vision content
* @param properties updated properties * @param properties updated properties
* @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [delete] flag to true. * @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [delete] flag to true.
*/ */
@Serializable @Serializable
public data class VisionChange( public data class VisionChange(
public val delete: Boolean = false,
public val vision: Vision? = null, public val vision: Vision? = null,
@Serializable(MetaSerializer::class) public val properties: Meta? = null, public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null, public val children: Map<Name, VisionChange>? = null,
) )
public inline fun VisionChange(block: VisionChangeBuilder.() -> Unit): VisionChange = public inline fun VisionManager.VisionChange(block: VisionChangeBuilder.() -> Unit): VisionChange =
VisionChangeBuilder().apply(block).deepCopy() VisionChangeBuilder(this).apply(block).deepCopy()
@OptIn(DFExperimental::class)
private fun CoroutineScope.collectChange( private fun CoroutineScope.collectChange(
name: Name, name: Name,
source: Vision, source: Vision,
@ -95,29 +112,26 @@ private fun CoroutineScope.collectChange(
) { ) {
//Collect properties change //Collect properties change
source.onPropertyChange { propertyName -> source.onPropertyChange(this) { propertyName ->
val newItem = source.meta[propertyName] val newItem = source.properties.own?.get(propertyName)
collector().propertyChanged(name, propertyName, newItem) collector().propertyChanged(name, propertyName, newItem)
} }
if (source is VisionGroup) { val children = source.children
//Subscribe for children changes //Subscribe for children changes
source.children.forEach { (token, child) -> children?.forEach { token, child ->
collectChange(name + token, child, collector) collectChange(name + token, child, collector)
}
//Subscribe for structure change
if (source is MutableVisionGroup) {
source.structureChanges.onEach { changedName ->
val after = source[changedName]
val fullName = name + changedName
if (after != null) {
collectChange(fullName, after, collector)
}
collector()[fullName] = after
}.launchIn(this)
}
} }
//Subscribe for structure change
children?.changes?.onEach { changedName ->
val after = children[changedName]
val fullName = name + changedName
if (after != null) {
collectChange(fullName, after, collector)
}
collector().setChild(fullName, after)
}?.launchIn(this)
} }
/** /**
@ -126,13 +140,14 @@ private fun CoroutineScope.collectChange(
public fun Vision.flowChanges( public fun Vision.flowChanges(
collectionDuration: Duration, collectionDuration: Duration,
): Flow<VisionChange> = flow { ): Flow<VisionChange> = flow {
val manager = manager ?: error("Orphan vision could not collect changes")
var collector = VisionChangeBuilder() var collector = VisionChangeBuilder(manager)
coroutineScope { coroutineScope {
collectChange(Name.EMPTY, this@flowChanges) { collector } collectChange(Name.EMPTY, this@flowChanges) { collector }
//Send initial vision state //Send initial vision state
val initialChange = VisionChange(vision = deepCopy()) val initialChange = VisionChange(vision = deepCopy(manager))
emit(initialChange) emit(initialChange)
while (currentCoroutineContext().isActive) { while (currentCoroutineContext().isActive) {
@ -143,7 +158,7 @@ public fun Vision.flowChanges(
//emit changes //emit changes
emit(collector.deepCopy()) emit(collector.deepCopy())
//Reset the collector //Reset the collector
collector = VisionChangeBuilder() collector = VisionChangeBuilder(manager)
} }
} }
} }

View File

@ -0,0 +1,217 @@
package space.kscience.visionforge
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import space.kscience.dataforge.names.*
import space.kscience.visionforge.VisionChildren.Companion.STATIC_TOKEN_BODY
import kotlin.jvm.Synchronized
@DslMarker
public annotation class VisionBuilder
public interface VisionContainer<out V : Vision> {
public fun getChild(name: Name): V?
}
public interface MutableVisionContainer<in V : Vision> {
//TODO add documentation
public fun setChild(name: Name?, child: V?)
}
/**
* A serializable representation of [Vision] children container
*/
public interface VisionChildren : VisionContainer<Vision> {
public val group: Vision?
public val keys: Set<NameToken>
public val values: Iterable<Vision> get() = keys.map { get(it)!! }
public val changes: Flow<Name>
public operator fun get(token: NameToken): Vision?
override fun getChild(name: Name): Vision? = when (name.length) {
0 -> group
1 -> get(name.first())
else -> get(name.first())?.children?.getChild(name.cutFirst())
}
public companion object {
public const val STATIC_TOKEN_BODY: String = "@static"
public fun empty(owner: Vision): VisionChildren = object : VisionChildren {
override val group: Vision get() = owner
override val keys: Set<NameToken> get() = emptySet()
override val changes: Flow<Name> get() = emptyFlow()
override fun get(token: NameToken): Vision? = null
}
}
}
public operator fun VisionChildren.get(name: Name): Vision? = getChild(name)
public operator fun VisionChildren.get(name: String): Vision? = getChild(name)
public fun VisionChildren.isEmpty(): Boolean = keys.isEmpty()
public inline fun VisionChildren.forEach(block: (NameToken, Vision) -> Unit) {
keys.forEach { block(it, get(it)!!) }
}
public interface MutableVisionChildren : VisionChildren, MutableVisionContainer<Vision> {
public override val group: MutableVisionGroup
public operator fun set(token: NameToken, value: Vision?)
override fun setChild(name: Name?, child: Vision?) {
when {
name == null -> {
if (child != null) {
static(child)
}
}
name.isEmpty() -> error("Empty names are not allowed in VisionGroup::set")
name.length == 1 -> {
val token = name.tokens.first()
set(token, child)
}
else -> {
val currentParent = get(name.first())
if (currentParent != null && currentParent !is MutableVisionGroup) error("Can't assign a child to $currentParent")
val parent: MutableVisionGroup = currentParent as? MutableVisionGroup ?: group.createGroup().also {
set(name.first(), it)
}
parent.children.setChild(name.cutFirst(), child)
}
}
}
public fun clear()
}
public operator fun MutableVisionChildren.set(name: Name?, vision: Vision?) {
setChild(name, vision)
}
public operator fun MutableVisionChildren.set(name: String?, vision: Vision?) {
setChild(name, vision)
}
/**
* Add a static child. Statics could not be found by name, removed or replaced. Changing statics also do not trigger events.
*/
public fun MutableVisionChildren.static(child: Vision) {
set(NameToken(STATIC_TOKEN_BODY, index = child.hashCode().toString()), child)
}
public fun VisionChildren.asSequence(): Sequence<Pair<NameToken, Vision>> = sequence {
keys.forEach { yield(it to get(it)!!) }
}
public operator fun VisionChildren.iterator(): Iterator<Pair<NameToken, Vision>> = asSequence().iterator()
public fun <V : Vision> VisionContainer<V>.getChild(str: String): V? = getChild(Name.parse(str))
public fun <V : Vision> MutableVisionContainer<V>.setChild(
str: String?, vision: V?,
): Unit = setChild(str?.parseAsName(), vision)
internal abstract class VisionChildrenImpl(
override val group: MutableVisionGroup,
) : MutableVisionChildren {
private val updateJobs = HashMap<NameToken, Job>()
abstract var items: MutableMap<NameToken, Vision>?
@Synchronized
private fun buildItems(): MutableMap<NameToken, Vision> {
if (items == null) {
items = LinkedHashMap()
}
return items!!
}
private val scope: CoroutineScope? get() = group.manager?.context
override val keys: Set<NameToken> get() = items?.keys ?: emptySet()
override fun get(token: NameToken): Vision? = items?.get(token)
private val _changes = MutableSharedFlow<Name>()
override val changes: SharedFlow<Name> get() = _changes
private fun onChange(name: Name) {
scope?.launch {
_changes.emit(name)
}
}
override operator fun set(token: NameToken, value: Vision?) {
//fast return if value equals existing
if (value == get(token)) return
val currentUpdateJob = updateJobs[token]
if (currentUpdateJob != null) {
currentUpdateJob.cancel()
updateJobs.remove(token)
}
if (value == null) {
items?.remove(token)
} else {
(items ?: buildItems())[token] = value
//check if parent already exists and is different from the current one
if (value.parent != null && value.parent != group) error("Can't reassign parent Vision for $value")
//set parent
value.parent = group
//start update jobs (only if the vision is rooted)
scope?.let { scope ->
val job = value.children?.changes?.onEach {
onChange(token + it)
}?.launchIn(scope)
if (job != null) {
updateJobs[token] = job
}
}
}
onChange(token.asName())
}
override fun clear() {
items?.forEach { set(it.key, null) }
// if (!items.isNullOrEmpty()) {
// updateJobs.values.forEach {
// it.cancel()
// }
// updateJobs.clear()
// items?.clear()
// }
}
}
//
//internal object VisionChildrenContainerSerializer : KSerializer<MutableVisionChildren> {
// private val mapSerializer = serializer<Map<NameToken, Vision>>()
//
// override val descriptor: SerialDescriptor = mapSerializer.descriptor
//
// override fun deserialize(decoder: Decoder): MutableVisionChildren {
// val map = decoder.decodeSerializableValue(mapSerializer)
// return VisionChildrenImpl(map)
// }
//
// override fun serialize(encoder: Encoder, value: MutableVisionChildren) {
// val map = value.keys.associateWith { value[it]!! }
// encoder.encodeSerializableValue(mapSerializer, map)
// }
//
//}

View File

@ -1,109 +1,119 @@
package space.kscience.visionforge package space.kscience.visionforge
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.serialization.SerialName
import kotlinx.coroutines.channels.awaitClose import kotlinx.serialization.Serializable
import kotlinx.coroutines.flow.Flow import space.kscience.dataforge.meta.Meta
import kotlinx.coroutines.flow.callbackFlow import space.kscience.dataforge.meta.ValueType
import kotlinx.coroutines.launch import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.Name
import space.kscience.dataforge.provider.Provider import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.Vision.Companion.STYLE_KEY
@DslMarker
public annotation class VisionBuilder
public interface VisionContainer<out V : Vision> { public interface VisionGroup : Vision {
public operator fun get(name: Name): V? public val children: VisionChildren
} }
/** public interface MutableVisionGroup : VisionGroup {
* Represents a group of [Vision] instances
*/
public interface VisionGroup : Provider, Vision, VisionContainer<Vision> {
/**
* A map of top level named children
*/
public val children: Map<NameToken, Vision>
override val defaultTarget: String get() = Vision.TYPE override val children: MutableVisionChildren
/** public fun createGroup(): MutableVisionGroup
* A map of direct children for specific target
* (currently "visual" or "style")
*/
override fun content(target: String): Map<Name, Any> =
when (target) {
Vision.TYPE -> children.flatMap { (key, value) ->
val res: Map<Name, Any> = if (value is VisionGroup) {
value.content(target).mapKeys { key + it.key }
} else {
mapOf(key.asName() to value)
}
res.entries
}.associate { it.toPair() }
STYLE_TARGET -> styleSheet.items?.mapKeys { it.key.asName() } ?: emptyMap()
else -> emptyMap()
}
public override operator fun get(name: Name): Vision? {
return when {
name.isEmpty() -> this
name.length == 1 -> children[name.tokens.first()]
else -> (children[name.tokens.first()] as? VisionGroup)?.get(name.cutFirst())
}
}
public companion object {
public const val STYLE_TARGET: String = "style"
}
} }
/** public val Vision.children: VisionChildren? get() = (this as? VisionGroup)?.children
* Iterate over children of this group
*/
public operator fun VisionGroup.iterator(): Iterator<Vision> = children.values.iterator()
public fun VisionGroup.isEmpty(): Boolean = this.children.isEmpty()
public interface VisionContainerBuilder<in V : Vision> {
//TODO add documentation
public operator fun set(name: Name?, child: V?)
}
/** /**
* Mutable version of [VisionGroup] * A full base implementation for a [Vision]
*/ */
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> { @Serializable
public fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit) public abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
public fun removeStructureListener(owner: Any?) override fun update(change: VisionChange) {
} change.children?.forEach { (name, change) ->
when {
change.vision == NullVision -> children.setChild(name, null)
/** change.vision != null -> children.setChild(name, change.vision)
* Flow structure changes of this group. Unconsumed changes are discarded else -> children.getChild(name)?.update(change)
*/
@OptIn(ExperimentalCoroutinesApi::class)
@DFExperimental
public val MutableVisionGroup.structureChanges: Flow<Name>
get() = callbackFlow {
meta.onChange(this) { name ->
launch {
send(name)
} }
} }
awaitClose { change.properties?.let {
removeStructureListener(this) updateProperties(it, Name.EMPTY)
} }
} }
@SerialName("children")
protected var childrenInternal: MutableMap<NameToken, Vision>? = null
public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(Name.parse(str))
public operator fun <V : Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit = init {
set(token.asName(), child) childrenInternal?.forEach { it.value.parent = this }
}
public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String?, child: V?): Unit = override val children: MutableVisionChildren by lazy {
set(key?.let(Name::parse), child) object : VisionChildrenImpl(this) {
override var items: MutableMap<NameToken, Vision>?
get() = this@AbstractVisionGroup.childrenInternal
set(value) {
this@AbstractVisionGroup.childrenInternal = value
}
}
}
public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null } abstract override fun createGroup(): AbstractVisionGroup
public companion object {
public val descriptor: MetaDescriptor = MetaDescriptor {
value(STYLE_KEY, ValueType.STRING) {
multiple = true
}
}
public fun Vision.updateProperties(item: Meta, name: Name = Name.EMPTY) {
properties.setValue(name, item.value)
item.items.forEach { (token, item) ->
updateProperties(item, name + token)
}
}
}
}
/**
* A simple vision group that just holds children. Nothing else.
*/
@Serializable
@SerialName("vision.group")
public class SimpleVisionGroup : AbstractVisionGroup(), MutableVisionContainer<Vision> {
override fun createGroup(): SimpleVisionGroup = SimpleVisionGroup()
override fun setChild(name: Name?, child: Vision?) {
children.setChild(name, child)
}
}
@VisionBuilder
public inline fun MutableVisionContainer<Vision>.group(
name: Name? = null,
builder: SimpleVisionGroup.() -> Unit = {},
): SimpleVisionGroup = SimpleVisionGroup().also { setChild(name, it) }.apply(builder)
/**
* Define a group with given [name], attach it to this parent and return it.
*/
@VisionBuilder
public inline fun MutableVisionContainer<Vision>.group(
name: String,
builder: SimpleVisionGroup.() -> Unit = {},
): SimpleVisionGroup = group(name.parseAsName(), builder)
//fun VisualObject.findStyle(styleName: Name): Meta? {
// if (this is VisualGroup) {
// val style = resolveStyle(styleName)
// if (style != null) return style
// }
// return parent?.findStyle(styleName)
//}

View File

@ -1,168 +0,0 @@
package space.kscience.visionforge
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import space.kscience.dataforge.names.*
import kotlin.jvm.Synchronized
private class StructureChangeListener(val owner: Any?, val callback: VisionGroup.(Name) -> Unit)
/**
* Abstract implementation of mutable group of [Vision]
*
* @param childrenInternal Internal mutable container for group children
*/
@Serializable
@SerialName("vision.group")
public open class VisionGroupBase(
@EncodeDefault @SerialName("children") protected val childrenInternal: MutableMap<NameToken, Vision> = LinkedHashMap(),
) : VisionBase(), MutableVisionGroup {
/**
* A map of top level named children
*/
override val children: Map<NameToken, Vision> get() = childrenInternal
init {
childrenInternal.forEach { (token, child) ->
if (child.parent != null && child.parent != this) error("Can't reassign existing parent for child $token")
child.parent = this
}
}
override fun invalidateProperty(propertyName: Name) {
super.invalidateProperty(propertyName)
for (obj in this) {
obj.invalidateProperty(propertyName)
}
}
@Transient
private val structureListeners = HashSet<StructureChangeListener>()
@Synchronized
override fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit) {
structureListeners.add(StructureChangeListener(owner, block))
}
@Synchronized
override fun removeStructureListener(owner: Any?) {
structureListeners.removeAll { it.owner == owner }
}
/**
* Propagate children change event upwards
*/
protected fun childrenChanged(name: Name) {
structureListeners.forEach {
it.callback(this, name)
}
}
/**
* Add a static child. Statics could not be found by name, removed or replaced. Changing statics also do not trigger events.
*/
protected open fun addStatic(child: Vision): Unit {
attachChild(NameToken("@static", index = child.hashCode().toString()), child)
}
/**
* Create a vision group of the same type as this vision group. Do not attach it.
*/
protected open fun createGroup(): VisionGroupBase = VisionGroupBase()
/**
* Set parent for given child and attach it
*/
private fun attachChild(token: NameToken, child: Vision?) {
val before = childrenInternal[token]
when {
child == null -> {
childrenInternal.remove(token)
}
child.parent == null -> {
child.parent = this
childrenInternal[token] = child
}
child.parent !== this -> {
error("Can't reassign existing parent for child $token")
}
}
if (before != child) {
childrenChanged(token.asName())
if (child is MutableVisionGroup) {
child.onStructureChanged(this) { changedName ->
this@VisionGroupBase.childrenChanged(token + changedName)
}
}
}
}
/**
* Recursively create a child group
*/
private fun createGroups(name: Name): VisionGroupBase = when {
name.isEmpty() -> error("Should be unreachable")
name.length == 1 -> {
val token = name.tokens.first()
when (val current = children[token]) {
null -> createGroup().also { child ->
attachChild(token, child)
}
is VisionGroupBase -> current
else -> error("Can't create group with name $name because it exists and not a group")
}
}
else -> createGroups(name.tokens.first().asName()).createGroups(name.cutFirst())
}
/**
* Add named or unnamed child to the group. If key is null the child is considered unnamed. Both key and value are not
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
*/
override fun set(name: Name?, child: Vision?): Unit {
when {
name == null -> {
if (child != null) {
addStatic(child)
}
}
name.isEmpty() -> error("Empty names are not allowed in VisionGroup::set")
name.length == 1 -> {
val token = name.tokens.first()
attachChild(token, child)
}
else -> {
//TODO add safety check
val parent = (get(name.cutLast()) as? MutableVisionGroup) ?: createGroups(name.cutLast())
parent[name.tokens.last().asName()] = child
}
}
}
override fun update(change: VisionChange) {
change.children?.forEach { (name, change) ->
when {
change.delete -> set(name, null)
change.vision != null -> set(name, change.vision)
else -> get(name)?.update(change)
}
}
super.update(change)
}
}
/**
* Non-serializable root group used to propagate manager to its children
*/
internal class RootVisionGroup(override val manager: VisionManager) : VisionGroupBase()
/**
* Designate this [VisionGroup] as a root and assign a [VisionManager] as its parent
*/
public fun Vision.setAsRoot(manager: VisionManager) {
if (parent != null) error("Vision $this already has a parent. It could not be set as root")
parent = RootVisionGroup(manager)
}

View File

@ -19,19 +19,20 @@ import space.kscience.visionforge.html.VisionOfNumberField
import space.kscience.visionforge.html.VisionOfTextField import space.kscience.visionforge.html.VisionOfTextField
import kotlin.reflect.KClass import kotlin.reflect.KClass
public class VisionManager(meta: Meta) : AbstractPlugin(meta) { public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionContainer<Vision> {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag
/** /**
* Combined [SerializersModule] for all registered visions * Combined [SerializersModule] for all registered visions
*/ */
public val serializersModule: SerializersModule public val serializersModule: SerializersModule by lazy {
get() = SerializersModule { SerializersModule {
include(defaultSerialModule) include(defaultSerialModule)
context.gather<SerializersModule>(VISION_SERIALIZER_MODULE_TARGET).values.forEach { context.gather<SerializersModule>(VISION_SERIALIZER_MODULE_TARGET).values.forEach {
include(it) include(it)
} }
} }
}
public val jsonFormat: Json public val jsonFormat: Json
get() = Json(defaultJson) { get() = Json(defaultJson) {
@ -57,19 +58,23 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
public fun encodeToMeta(vision: Vision, descriptor: MetaDescriptor? = null): Meta = public fun encodeToMeta(vision: Vision, descriptor: MetaDescriptor? = null): Meta =
encodeToJsonElement(vision).toMeta(descriptor) encodeToJsonElement(vision).toMeta(descriptor)
override fun setChild(name: Name?, child: Vision?) {
child?.setAsRoot(this)
}
public companion object : PluginFactory<VisionManager> { public companion object : PluginFactory<VisionManager> {
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
override val type: KClass<out VisionManager> = VisionManager::class override val type: KClass<out VisionManager> = VisionManager::class
public const val VISION_SERIALIZER_MODULE_TARGET: String = "visionSerializerModule" public const val VISION_SERIALIZER_MODULE_TARGET: String = "visionSerializerModule"
override fun invoke(meta: Meta, context: Context): VisionManager = VisionManager(meta) override fun build(context: Context, meta: Meta): VisionManager = VisionManager(meta)
private val defaultSerialModule: SerializersModule = SerializersModule { private val defaultSerialModule: SerializersModule = SerializersModule {
polymorphic(Vision::class) { polymorphic(Vision::class) {
default { VisionBase.serializer() } default { SimpleVisionGroup.serializer() }
subclass(VisionBase.serializer()) subclass(NullVision.serializer())
subclass(VisionGroupBase.serializer()) subclass(SimpleVisionGroup.serializer())
subclass(VisionOfNumberField.serializer()) subclass(VisionOfNumberField.serializer())
subclass(VisionOfTextField.serializer()) subclass(VisionOfTextField.serializer())
subclass(VisionOfCheckbox.serializer()) subclass(VisionOfCheckbox.serializer())
@ -108,4 +113,19 @@ public abstract class VisionPlugin(meta: Meta = Meta.EMPTY) : AbstractPlugin(met
public val Context.visionManager: VisionManager get() = fetch(VisionManager) public val Context.visionManager: VisionManager get() = fetch(VisionManager)
public fun Vision.encodeToString(): String = public fun Vision.encodeToString(): String =
manager?.encodeToString(this) ?: error("VisionManager not defined in Vision") manager?.encodeToString(this) ?: error("Orphan vision could not be encoded")
/**
* A root vision attached to [VisionManager]
*/
public class RootVision(override val manager: VisionManager) : AbstractVisionGroup() {
override fun createGroup(): SimpleVisionGroup = SimpleVisionGroup()
}
/**
* Designate this [Vision] as a root and assign a [VisionManager] as its parent
*/
public fun Vision.setAsRoot(manager: VisionManager) {
if (parent != null) error("Vision $this already has a parent. It could not be set as root")
parent = RootVision(manager)
}

View File

@ -0,0 +1,296 @@
package space.kscience.visionforge
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.Transient
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.*
import kotlin.jvm.Synchronized
public interface VisionProperties {
/**
* Raw Visions own properties without styles, defaults, etc.
*/
public val own: Meta?
public val descriptor: MetaDescriptor?
public fun getValue(
name: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Value?
/**
* Get property with given layer flags.
* @param inherit toggles parent node property lookup. Null means inference from descriptor.
* @param includeStyles toggles inclusion of properties from styles.
*/
public fun getProperty(
name: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Meta
public val changes: Flow<Name>
/**
* Notify all listeners that a property has been changed and should be invalidated.
* This method does not check that the property has actually changed.
*/
public fun invalidate(propertyName: Name)
}
public interface MutableVisionProperties : VisionProperties {
override fun getProperty(
name: Name,
inherit: Boolean?,
includeStyles: Boolean?,
): MutableMeta = VisionPropertiesItem(
this,
name,
inherit,
includeStyles,
)
public fun setProperty(
name: Name,
node: Meta?,
notify: Boolean = true,
)
public fun setValue(
name: Name,
value: Value?,
notify: Boolean = true,
)
}
public fun MutableVisionProperties.remove(name: Name) {
setProperty(name, null)
}
public fun MutableVisionProperties.remove(name: String) {
remove(name.parseAsName())
}
@VisionBuilder
public operator fun MutableVisionProperties.invoke(block: MutableMeta.() -> Unit) {
root(inherit = false, includeStyles = false).apply(block)
}
private class VisionPropertiesItem(
val properties: MutableVisionProperties,
val nodeName: Name,
val inherit: Boolean? = null,
val useStyles: Boolean? = null,
val default: Meta? = null,
) : MutableMeta {
val descriptor: MetaDescriptor? by lazy { properties.descriptor?.get(nodeName) }
override val items: Map<NameToken, MutableMeta>
get() {
val metaKeys = properties.own?.getMeta(nodeName)?.items?.keys ?: emptySet()
val descriptorKeys = descriptor?.children?.map { NameToken(it.key) } ?: emptySet()
val defaultKeys = default?.get(nodeName)?.items?.keys ?: emptySet()
val inheritFlag = descriptor?.inherited ?: inherit
val stylesFlag = descriptor?.usesStyles ?: useStyles
return (metaKeys + descriptorKeys + defaultKeys).associateWith {
VisionPropertiesItem(
properties,
nodeName + it,
inheritFlag,
stylesFlag,
default
)
}
}
override var value: Value?
get() {
val inheritFlag = descriptor?.inherited ?: inherit ?: false
val stylesFlag = descriptor?.usesStyles ?: useStyles ?: true
return properties.getValue(nodeName, inheritFlag, stylesFlag) ?: default?.getValue(nodeName)
}
set(value) {
properties.setValue(nodeName, value)
}
override fun getOrCreate(name: Name): MutableMeta = VisionPropertiesItem(
properties,
nodeName + name,
inherit,
useStyles,
default
)
override fun setMeta(name: Name, node: Meta?) {
properties.setProperty(nodeName + name, node)
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
public abstract class AbstractVisionProperties(
private val vision: Vision,
) : MutableVisionProperties {
override val descriptor: MetaDescriptor? get() = vision.descriptor
protected abstract var properties: MutableMeta?
override val own: Meta? get() = properties
@Synchronized
protected fun getOrCreateProperties(): MutableMeta {
if (properties == null) {
//TODO check performance issues
val newProperties = MutableMeta()
properties = newProperties
}
return properties!!
}
override fun getValue(
name: Name,
inherit: Boolean?,
includeStyles: Boolean?,
): Value? {
own?.get(name)?.value?.let { return it }
val descriptor = descriptor?.get(name)
val stylesFlag = includeStyles ?: descriptor?.usesStyles ?: true
if (stylesFlag) {
vision.getStyleProperty(name)?.value?.let { return it }
}
val inheritFlag = inherit ?: descriptor?.inherited ?: false
if (inheritFlag) {
vision.parent?.properties?.getValue(name, inheritFlag, stylesFlag)?.let { return it }
}
return descriptor?.defaultValue
}
override fun setProperty(name: Name, node: Meta?, notify: Boolean) {
//TODO check old value?
if (name.isEmpty()) {
properties = node?.asMutableMeta()
} else if (node == null) {
properties?.setMeta(name, node)
} else {
getOrCreateProperties().setMeta(name, node)
}
if (notify) {
invalidate(name)
}
}
override fun setValue(name: Name, value: Value?, notify: Boolean) {
//TODO check old value?
if (value == null) {
properties?.getMeta(name)?.value = null
} else {
getOrCreateProperties().setValue(name, value)
}
if (notify) {
invalidate(name)
}
}
@Transient
protected val changesInternal: MutableSharedFlow<Name> = MutableSharedFlow<Name>()
override val changes: SharedFlow<Name> get() = changesInternal
override fun invalidate(propertyName: Name) {
//send update signal
@OptIn(DelicateCoroutinesApi::class)
(vision.manager?.context ?: GlobalScope).launch {
changesInternal.emit(propertyName)
}
//notify children if there are any
if (vision is VisionGroup) {
vision.children.values.forEach {
it.properties.invalidate(propertyName)
}
}
// update styles
if (propertyName == Vision.STYLE_KEY) {
vision.styles.asSequence()
.mapNotNull { vision.getStyle(it) }
.flatMap { it.items.asSequence() }
.distinctBy { it.key }
.forEach {
invalidate(it.key.asName())
}
}
}
}
public fun VisionProperties.getValue(
name: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Value? = getValue(name.parseAsName(), inherit, includeStyles)
/**
* Get [Vision] property using key as a String
*/
public fun VisionProperties.getProperty(
name: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Meta = getProperty(name.parseAsName(), inherit, includeStyles)
/**
* The root property node with given inheritance and style flags
* @param inherit - inherit properties from the [Vision] parent. If null, infer from descriptor
* @param includeStyles - include style information. If null, infer from descriptor
*/
public fun MutableVisionProperties.root(
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): MutableMeta = getProperty(Name.EMPTY, inherit, includeStyles)
/**
* Get [Vision] property using key as a String
*/
public fun MutableVisionProperties.getProperty(
name: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): MutableMeta = getProperty(name.parseAsName(), inherit, includeStyles)
public operator fun MutableVisionProperties.set(name: Name, value: Number): Unit =
setValue(name, value.asValue())
public operator fun MutableVisionProperties.set(name: String, value: Number): Unit =
set(name.parseAsName(), value)
public operator fun MutableVisionProperties.set(name: Name, value: Boolean): Unit =
setValue(name, value.asValue())
public operator fun MutableVisionProperties.set(name: String, value: Boolean): Unit =
set(name.parseAsName(), value)
public operator fun MutableVisionProperties.set(name: Name, value: String): Unit =
setValue(name, value.asValue())
public operator fun MutableVisionProperties.set(name: String, value: String): Unit =
set(name.parseAsName(), value)

View File

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

View File

@ -0,0 +1,55 @@
package space.kscience.visionforge
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.Value
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.startsWith
/**
* Create a flow of a specific property
*/
public fun Vision.flowProperty(
propertyName: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Flow<Meta> = flow {
//Pass initial value.
emit(properties.getProperty(propertyName, inherit, includeStyles))
properties.changes.collect { name ->
if (name.startsWith(propertyName)) {
emit(properties.getProperty(propertyName, inherit, includeStyles))
}
}
}
public fun Vision.flowProperty(
propertyName: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Flow<Meta> = flowProperty(propertyName.parseAsName(), inherit, includeStyles)
/**
* Flow the value of specific property
*/
public fun Vision.flowPropertyValue(
propertyName: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Flow<Value?> = flow {
//Pass initial value.
emit(properties.getValue(propertyName, inherit, includeStyles))
properties.changes.collect { name ->
if (name.startsWith(propertyName)) {
emit(properties.getValue(propertyName, inherit, includeStyles))
}
}
}
public fun Vision.flowPropertyValue(
propertyName: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
): Flow<Value?> = flowPropertyValue(propertyName.parseAsName(), inherit, includeStyles)

View File

@ -22,7 +22,7 @@ internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
/** /**
* Render a fragment in the given consumer and return a map of extracted visions * Render a fragment in the given consumer and return a map of extracted visions
* @param manager a VisionManager used for serialization * @param context a context used to create a vision fragment
* @param embedData embed Vision initial state in the HTML * @param embedData embed Vision initial state in the HTML
* @param fetchDataUrl fetch data after first render from given url * @param fetchDataUrl fetch data after first render from given url
* @param fetchUpdatesUrl receive push updates from the server at given url * @param fetchUpdatesUrl receive push updates from the server at given url

View File

@ -15,7 +15,7 @@ import space.kscience.dataforge.meta.node
public class VisionOfHtmlForm( public class VisionOfHtmlForm(
public val formId: String, public val formId: String,
) : VisionOfHtmlInput() { ) : VisionOfHtmlInput() {
public var values: Meta? by meta.node() public var values: Meta? by mutableProperties.node()
} }
public class HtmlFormFragment internal constructor( public class HtmlFormFragment internal constructor(

View File

@ -5,11 +5,15 @@ import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.boolean import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.number import space.kscience.dataforge.meta.number
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.visionforge.VisionBase import space.kscience.dataforge.names.Name
import space.kscience.visionforge.*
//TODO replace by something
internal val Vision.mutableProperties get() = properties.getProperty(Name.EMPTY, false, false)
@Serializable @Serializable
public abstract class VisionOfHtmlInput : VisionBase() { public abstract class VisionOfHtmlInput : AbstractVision() {
public var disabled: Boolean by meta.boolean(false) public var disabled: Boolean by mutableProperties.boolean { false }
} }
@Serializable @Serializable
@ -18,7 +22,7 @@ public class VisionOfTextField(
public val label: String? = null, public val label: String? = null,
public val name: String? = null, public val name: String? = null,
) : VisionOfHtmlInput() { ) : VisionOfHtmlInput() {
public var text: String? by meta.string() public var text: String? by mutableProperties.string()
} }
@Serializable @Serializable
@ -27,7 +31,7 @@ public class VisionOfCheckbox(
public val label: String? = null, public val label: String? = null,
public val name: String? = null, public val name: String? = null,
) : VisionOfHtmlInput() { ) : VisionOfHtmlInput() {
public var checked: Boolean? by meta.boolean() public var checked: Boolean? by mutableProperties.boolean()
} }
@Serializable @Serializable
@ -36,7 +40,7 @@ public class VisionOfNumberField(
public val label: String? = null, public val label: String? = null,
public val name: String? = null, public val name: String? = null,
) : VisionOfHtmlInput() { ) : VisionOfHtmlInput() {
public var value: Number? by meta.number() public var value: Number? by mutableProperties.number()
} }
@Serializable @Serializable
@ -48,6 +52,6 @@ public class VisionOfRangeField(
public val label: String? = null, public val label: String? = null,
public val name: String? = null, public val name: String? = null,
) : VisionOfHtmlInput() { ) : VisionOfHtmlInput() {
public var value: Number? by meta.number() public var value: Number? by mutableProperties.number()
} }

View File

@ -86,7 +86,9 @@ public abstract class VisionTagConsumer<R>(
): T = div { ): T = div {
id = resolveId(name) id = resolveId(name)
classes = setOf(OUTPUT_CLASS) classes = setOf(OUTPUT_CLASS)
vision.setAsRoot(manager) if (vision.parent == null) {
vision.setAsRoot(manager)
}
attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString() attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString()
if (!outputMeta.isEmpty()) { if (!outputMeta.isEmpty()) {
//Hard-code output configuration //Hard-code output configuration

View File

@ -0,0 +1,54 @@
package space.kscience.visionforge
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.startsWith
import kotlin.reflect.KProperty1
/**
* Call [callBack] on initial value of the property and then on all subsequent values after change
*/
public fun Vision.useProperty(
propertyName: Name,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
scope: CoroutineScope? = manager?.context,
callBack: (Meta) -> Unit,
): Job {
//Pass initial value.
callBack(properties.getProperty(propertyName, inherit, includeStyles))
return properties.changes.onEach { name ->
if (name.startsWith(propertyName)) {
callBack(properties.getProperty(propertyName, inherit, includeStyles))
}
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))
}
public fun Vision.useProperty(
propertyName: String,
inherit: Boolean? = null,
includeStyles: Boolean? = null,
scope: CoroutineScope? = manager?.context,
callBack: (Meta) -> Unit,
): Job = useProperty(propertyName.parseAsName(), inherit, includeStyles, scope, callBack)
public fun <V : Vision, T> V.useProperty(
property: KProperty1<V, T>,
scope: CoroutineScope? = manager?.context,
callBack: V.(T) -> Unit,
): Job {
//Pass initial value.
callBack(property.get(this))
return properties.changes.onEach { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))
}

View File

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

View File

@ -2,7 +2,7 @@ package space.kscience.visionforge
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.* import space.kscience.dataforge.meta.descriptors.*
import space.kscience.dataforge.values.asValue import space.kscience.dataforge.meta.set
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited" private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles" private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"

View File

@ -1,6 +1,5 @@
package space.kscience.visionforge.visitor package space.kscience.visionforge.visitor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -11,7 +10,6 @@ import space.kscience.visionforge.Vision
import kotlin.reflect.KClass import kotlin.reflect.KClass
@OptIn(ExperimentalCoroutinesApi::class)
public suspend fun <T> Vision.flowStatistics(statistics: (Name, Vision) -> T): Flow<T> = callbackFlow<T> { public suspend fun <T> Vision.flowStatistics(statistics: (Name, Vision) -> T): Flow<T> = callbackFlow<T> {
val visitor = object : VisionVisitor { val visitor = object : VisionVisitor {
override suspend fun visit(name: Name, vision: Vision){ override suspend fun visit(name: Name, vision: Vision){

View File

@ -6,7 +6,8 @@ import kotlinx.coroutines.launch
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.children
import space.kscience.visionforge.forEach
public interface VisionVisitor { public interface VisionVisitor {
/** /**
@ -19,30 +20,30 @@ public interface VisionVisitor {
/** /**
* Rearrange children of given group * Rearrange children of given group
*/ */
public suspend fun visitChildren(name: Name, group: VisionGroup) { public suspend fun visitChildren(name: Name, group: Vision) {
//Do nothing by default //Do nothing by default
} }
public fun skip(name: Name, vision: Vision): Boolean = false public fun skip(name: Name, vision: Vision): Boolean = false
public companion object{ public companion object {
private fun CoroutineScope.visitTreeAsync( private fun CoroutineScope.visitTreeAsync(
visionVisitor: VisionVisitor, visionVisitor: VisionVisitor,
name: Name, name: Name,
vision: Vision vision: Vision,
): Job = launch { ): Job = launch {
if (visionVisitor.skip(name, vision)) return@launch if (visionVisitor.skip(name, vision)) return@launch
visionVisitor.visit(name, vision) visionVisitor.visit(name, vision)
if (vision is VisionGroup) {
visionVisitor.visitChildren(name, vision)
for ((token, child) in vision.children) { visionVisitor.visitChildren(name, vision)
visitTreeAsync(visionVisitor, name + token, child)
} vision.children?.forEach { token, child ->
visitTreeAsync(visionVisitor, name + token, child)
} }
} }
/** /**
* Recursively visit this [Vision] and all children * Recursively visit this [Vision] and all children
*/ */

View File

@ -4,13 +4,9 @@ import kotlinx.html.*
import kotlinx.html.stream.createHTML import kotlinx.html.stream.createHTML
import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.Global
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.configure
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision import space.kscience.visionforge.*
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.VisionManager
import kotlin.collections.set import kotlin.collections.set
import kotlin.test.Test import kotlin.test.Test
@ -34,9 +30,10 @@ fun FlowContent.renderVisionFragment(
@DFExperimental @DFExperimental
class HtmlTagTest { private fun VisionOutput.base(block: VisionGroup.() -> Unit) = context.visionManager.group().apply(block)
fun VisionOutput.base(block: VisionBase.() -> Unit) = VisionBase().apply(block) @DFExperimental
class HtmlTagTest {
val fragment: HtmlVisionFragment = { val fragment: HtmlVisionFragment = {
div { div {
@ -46,10 +43,8 @@ class HtmlTagTest {
"metaProperty" put 87 "metaProperty" put 87
} }
base { base {
configure { properties["myProp"] = 82
set("myProp", 82) properties["otherProp"] = false
set("otherProp", false)
}
} }
} }
} }
@ -59,7 +54,7 @@ class HtmlTagTest {
div { div {
h2 { +"Properties" } h2 { +"Properties" }
ul { ul {
(vision as? VisionBase)?.meta?.items?.forEach { vision.properties.own?.items?.forEach {
li { li {
a { +it.key.toString() } a { +it.key.toString() }
p { +it.value.toString() } p { +it.value.toString() }

View File

@ -1,44 +1,123 @@
package space.kscience.visionforge.meta package space.kscience.visionforge.meta
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.values.asValue import space.kscience.visionforge.*
import space.kscience.visionforge.VisionBase
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
class VisionPropertyTest {
@Test
fun testPropertyWrite(){
val vision = VisionBase()
vision.meta["fff"] = 2
vision.meta["fff.ddd"] = false
assertEquals(2, vision.meta["fff"]?.int) private class TestScheme : Scheme() {
assertEquals(false, vision.meta["fff.ddd"]?.boolean) var ddd by int()
companion object : SchemeSpec<TestScheme>(::TestScheme)
}
@OptIn(ExperimentalCoroutinesApi::class)
internal class VisionPropertyTest {
private val manager = Global.fetch(VisionManager)
@Test
fun testPropertyWrite() {
val vision = manager.group()
vision.properties["fff"] = 2
vision.properties["fff.ddd"] = false
assertEquals(2, vision.properties.getValue("fff")?.int)
assertEquals(false, vision.properties.getValue("fff.ddd")?.boolean)
} }
@Test @Test
fun testPropertyEdit(){ fun testPropertyEdit() {
val vision = VisionBase() val vision = manager.group()
vision.meta.getOrCreate("fff.ddd").apply { vision.properties.getProperty("fff.ddd").apply {
value = 2.asValue() value = 2.asValue()
} }
assertEquals(2, vision.meta["fff.ddd"]?.int) assertEquals(2, vision.properties.getValue("fff.ddd")?.int)
assertNotEquals(true, vision.meta["fff.ddd"]?.boolean) assertNotEquals(true, vision.properties.getValue("fff.ddd")?.boolean)
}
internal class TestScheme: Scheme(){
var ddd by int()
companion object: SchemeSpec<TestScheme>(::TestScheme)
} }
@Test @Test
fun testPropertyUpdate(){ fun testPropertyUpdate() {
val vision = VisionBase() val vision = manager.group()
vision.meta.getOrCreate("fff").updateWith(TestScheme){ vision.properties.getProperty("fff").updateWith(TestScheme) {
ddd = 2 ddd = 2
} }
assertEquals(2, vision.meta["fff.ddd"]?.int) assertEquals(2, vision.properties.getValue("fff.ddd")?.int)
}
@Test
fun testChildrenPropertyPropagation() = runTest(dispatchTimeoutMs = 200) {
val group = Global.fetch(VisionManager).group {
properties {
"test" put 11
}
group("child") {
properties {
"test" put 22
}
}
}
val child = group.children["child"]!!
val deferred: CompletableDeferred<Value?> = CompletableDeferred()
var callCounter = 0
val subscription = child.useProperty("test", inherit = true) {
deferred.complete(it.value)
callCounter++
}
assertEquals(22, deferred.await()?.int)
assertEquals(1, callCounter)
child.properties.remove("test")
assertEquals(11, child.properties.getProperty("test", inherit = true).int)
// assertEquals(11, deferred.await()?.int)
// assertEquals(2, callCounter)
subscription.cancel()
}
@Test
fun testChildrenPropertyFlow() = runTest(dispatchTimeoutMs = 200) {
val group = Global.fetch(VisionManager).group {
properties {
"test" put 11
}
group("child") {
properties {
"test" put 22
}
}
}
val child = group.children["child"]!!
launch {
val list = child.flowPropertyValue("test", inherit = true).take(3).map { it?.int }.toList()
assertEquals(22, list.first())
//assertEquals(11, list[1]) //a race
assertEquals(33, list.last())
}
//wait for subscription to be created
delay(5)
child.properties.remove("test")
group.properties["test"] = 33
} }
} }

View File

@ -65,6 +65,7 @@ public class VisionClient : AbstractPlugin() {
private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) { private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) {
if (vision != null) { if (vision != null) {
vision.setAsRoot(visionManager)
val renderer = findRendererFor(vision) val renderer = findRendererFor(vision)
?: error("Could not find renderer for ${visionManager.encodeToString(vision)}") ?: error("Could not find renderer for ${visionManager.encodeToString(vision)}")
renderer.render(element, vision, outputMeta) renderer.render(element, vision, outputMeta)
@ -115,7 +116,7 @@ public class VisionClient : AbstractPlugin() {
onopen = { onopen = {
feedbackJob = vision.flowChanges( feedbackJob = vision.flowChanges(
feedbackAggregationTime.milliseconds feedbackAggregationTime.milliseconds,
).onEach { change -> ).onEach { change ->
send(visionManager.encodeToString(change)) send(visionManager.encodeToString(change))
}.launchIn(visionManager.context) }.launchIn(visionManager.context)
@ -203,8 +204,7 @@ public class VisionClient : AbstractPlugin() {
) else super.content(target) ) else super.content(target)
public companion object : PluginFactory<VisionClient> { public companion object : PluginFactory<VisionClient> {
override fun build(context: Context, meta: Meta): VisionClient = VisionClient()
override fun invoke(meta: Meta, context: Context): VisionClient = VisionClient()
override val tag: PluginTag = PluginTag(name = "vision.client", group = PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag(name = "vision.client", group = PluginTag.DATAFORGE_GROUP)

View File

@ -0,0 +1,96 @@
package space.kscience.visionforge.html
import kotlinx.html.*
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.isEmpty
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.visionManager
/**
* Rendering context for visions in HTML
*/
public interface HtmlVisionContext : ContextAware {
/**
* Generate div id for vision div tag
*/
public fun generateId(name: Name): String = "vision[$name]"
/**
* Render vision at given [DIV]
*/
public fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta)
}
public typealias HtmlVisionContextFragment = context(HtmlVisionContext) TagConsumer<*>.() -> Unit
context(HtmlVisionContext)
public fun HtmlVisionContextFragment(content: TagConsumer<*>.() -> Unit): HtmlVisionFragment = content
context(HtmlVisionContext)
private fun <T> TagConsumer<T>.vision(
visionManager: VisionManager,
name: Name,
vision: Vision,
outputMeta: Meta = Meta.EMPTY,
): T = div {
id = generateId(name)
classes = setOf(VisionTagConsumer.OUTPUT_CLASS)
vision.setAsRoot(visionManager)
attributes[VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE] = name.toString()
if (!outputMeta.isEmpty()) {
//Hard-code output configuration
script {
attributes["class"] = VisionTagConsumer.OUTPUT_META_CLASS
unsafe {
+visionManager.jsonFormat.encodeToString(MetaSerializer, outputMeta)
}
}
}
renderVision(name, vision, outputMeta)
}
context(HtmlVisionContext)
private fun <T> TagConsumer<T>.vision(
name: Name,
vision: Vision,
outputMeta: Meta = Meta.EMPTY,
): T = vision(context.visionManager, name, vision, outputMeta)
/**
* Insert a vision in this HTML.
*/
context(HtmlVisionContext)
@DFExperimental
@VisionDSL
public fun <T> TagConsumer<T>.vision(
name: Name? = null,
visionProvider: VisionOutput.() -> Vision,
): T {
val output = VisionOutput(context, name)
val vision = output.visionProvider()
val actualName =
name ?: NameToken(VisionTagConsumer.DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
return vision(output.buildVisionManager(), actualName, vision, output.meta)
}
/**
* Insert a vision in this HTML.
*/
context(HtmlVisionContext)
@DFExperimental
@VisionDSL
public fun <T> TagConsumer<T>.vision(
name: String?,
visionProvider: VisionOutput.() -> Vision,
): T = vision(name?.parseAsName(), visionProvider)

View File

@ -1,12 +1,12 @@
plugins { plugins {
id("ru.mipt.npm.gradle.jvm") id("space.kscience.gradle.jvm")
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra
val fxVersion: String by rootProject.extra val fxVersion: String by rootProject.extra
kscience{ kscience{
useFx(ru.mipt.npm.gradle.FXModule.CONTROLS, version = fxVersion) useFx(space.kscience.gradle.FXModule.CONTROLS, version = fxVersion)
} }
dependencies { dependencies {
@ -15,12 +15,12 @@ dependencies {
api("org.fxyz3d:fxyz3d:0.5.4") { api("org.fxyz3d:fxyz3d:0.5.4") {
exclude(module = "slf4j-simple") exclude(module = "slf4j-simple")
} }
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}") api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${space.kscience.gradle.KScienceVersions.coroutinesVersion}")
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") { implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {
exclude(module = "slf4j-simple") exclude(module = "slf4j-simple")
} }
} }
readme{ readme{
maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE maturity = space.kscience.gradle.Maturity.PROTOTYPE
} }

View File

@ -95,8 +95,9 @@ public class FXPlugin(meta: Meta = Meta.EMPTY) : AbstractPlugin(meta) {
public companion object : PluginFactory<FXPlugin> { public companion object : PluginFactory<FXPlugin> {
override val type: KClass<out FXPlugin> = FXPlugin::class override val type: KClass<out FXPlugin> = FXPlugin::class
override val tag: PluginTag = PluginTag("vis.fx", group = PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag("vis.fx", group = PluginTag.DATAFORGE_GROUP)
override fun invoke(meta: Meta, context: Context): FXPlugin =
FXPlugin(meta) override fun build(context: Context, meta: Meta): FXPlugin = FXPlugin(meta)
} }
} }

View File

@ -3,13 +3,9 @@ package space.kscience.visionforge.editor
import javafx.scene.control.ColorPicker import javafx.scene.control.ColorPicker
import javafx.scene.paint.Color import javafx.scene.paint.Color
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.string
import tornadofx.* import tornadofx.*
/** /**

View File

@ -8,14 +8,10 @@ package space.kscience.visionforge.editor
import javafx.collections.FXCollections import javafx.collections.FXCollections
import javafx.scene.control.ComboBox import javafx.scene.control.ComboBox
import javafx.util.StringConverter import javafx.util.StringConverter
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.allowedValues import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.parseValue
import space.kscience.dataforge.values.string
import java.util.* import java.util.*
public class ComboBoxValueChooser(public val values: Collection<Value>? = null) : ValueChooserBase<ComboBox<Value>>() { public class ComboBoxValueChooser(public val values: Collection<Value>? = null) : ValueChooserBase<ComboBox<Value>>() {

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