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
361 changed files with 3640 additions and 11836 deletions

2
.gitignore vendored
View File

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

View File

@ -2,8 +2,16 @@
## [Unreleased]
### Added
- Context receivers flag
- MeshLine for thick lines
### 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

View File

@ -1,14 +1,14 @@
plugins {
id("ru.mipt.npm.gradle.project")
id("org.jetbrains.kotlinx.kover") version "0.5.0-RC"
id("space.kscience.gradle.project")
// 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")
allprojects{
group = "space.kscience"
version = "0.2.0"
version = "0.3.0-dev-2"
}
subprojects {
@ -19,6 +19,12 @@ subprojects {
mavenCentral()
maven("https://maven.jzy3d.org/releases")
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>{
kotlinOptions{
freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers"
}
}
}
ksciencePublish {
@ -31,4 +37,9 @@ apiValidation {
ignoredPackages.add("info.laht.threekt")
}
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 {
id("ru.mipt.npm.gradle.mpp")
id("space.kscience.gradle.mpp")
}
kscience{

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import kotlinx.serialization.modules.subclass
private fun <T> jsonRootDeserializer(
tSerializer: KSerializer<T>,
builder: (JsonElement) -> T
builder: (JsonElement) -> T,
): DeserializationStrategy<T> = object :
DeserializationStrategy<T> {
private val jsonElementSerializer = JsonElement.serializer()
@ -46,6 +46,7 @@ private object RootDecoder {
private val refCache: List<RefEntry>,
) : KSerializer<T> by tSerializer {
@OptIn(ExperimentalSerializationApi::class)
@Suppress("UNCHECKED_CAST")
override fun deserialize(decoder: Decoder): T {
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)
@OptIn(ExperimentalSerializationApi::class)
fun unrefSerializersModule(
refCache: List<RefEntry>
refCache: List<RefEntry>,
): SerializersModule = SerializersModule {
contextual(TObjArray::class) {
@ -197,11 +197,13 @@ private object RootDecoder {
fillCache(it)
}
}
is JsonArray -> {
element.forEach {
fillCache(it)
}
}
else -> {
//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.asName
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.MutableVisionContainer
import space.kscience.visionforge.solid.*
import kotlin.math.PI
import kotlin.math.atan2
@ -132,7 +133,7 @@ private fun buildGroup(volume: TGeoVolume): SolidGroup {
return if (volume is TGeoVolumeAssemblyRef) {
buildGroup(volume.value)
} else {
SolidGroup {
SolidGroup().apply {
volume.fShape?.let { addShape(it) }
volume.fNodes?.let {
it.arr.forEach { obj ->
@ -160,7 +161,7 @@ private fun SolidGroup.volume(volume: TGeoVolume, name: String? = null, cache: B
name = combinedName,
obj = group,
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 {
fNodes.arr.forEach {
public fun MutableVisionContainer<Solid>.rootGeo(tGeoManager: TGeoManager): SolidGroup = solidGroup {
tGeoManager.fNodes.arr.forEach {
node(it)
}
}

View File

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

View File

@ -1,13 +1,11 @@
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.values.asValue
import space.kscience.dataforge.values.string
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.get
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.getChild
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.material
@ -20,8 +18,8 @@ class GDMLVisionTest {
@Test
fun testCubesStyles(){
val segment = cubes["composite-000.segment-0"] as Solid
println(segment.computeProperties().getValue(Vision.STYLE_KEY))
val segment = cubes.children.getChild("composite-000.segment-0") as Solid
println(segment.properties.getValue(Vision.STYLE_KEY))
// println(segment.computePropertyNode(SolidMaterial.MATERIAL_KEY))
// println(segment.computeProperty(SolidMaterial.MATERIAL_COLOR_KEY))
@ -35,7 +33,7 @@ class GDMLVisionTest {
fun testPrototypeProperty() {
val child = cubes[Name.of("composite-000","segment-0")]
assertNotNull(child)
child.setProperty(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
assertEquals("red", child.getPropertyValue(SolidMaterial.MATERIAL_COLOR_KEY)?.string)
child.properties.setValue(SolidMaterial.MATERIAL_COLOR_KEY, "red".asValue())
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.dom.h2
import react.fc
import react.useMemo
import react.useState
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.names.Name
import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromString
import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.markLayers
import space.kscience.visionforge.gdml.toVision
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.solid.Solid
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.set
import styled.css
import styled.styledDiv
external interface GDMLAppProps : Props {
var context: Context
var solids: Solids
var vision: Solid?
var selected: Name?
}
@JsExport
val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager }
var deferredVision: Deferred<Solid?> by useState {
CompletableDeferred(props.vision)
}
@ -50,12 +49,15 @@ val GDMLApp = fc<GDMLAppProps>("GDMLApp") { props ->
name.endsWith(".gdml") || name.endsWith(".xml") -> {
val gdml = Gdml.decodeFromString(data)
gdml.toVision().apply {
setAsRoot(visionManager)
setAsRoot(props.solids.visionManager)
console.info("Marking layers for file $name")
markLayers()
ambientLight {
color.set(Colors.white)
}
}
}
name.endsWith(".json") -> visionManager.decodeFromString(data)
name.endsWith(".json") -> props.solids.visionManager.decodeFromString(data)
else -> {
window.alert("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) {
attrs {
this.context = props.context
this.solids = props.solids
this.builderOfSolid = deferredVision
this.selected = props.selected
tab("Load") {

View File

@ -2,11 +2,17 @@ package space.kscience.visionforge.gdml.demo
import kotlinx.browser.document
import kotlinx.css.*
import react.dom.render
import react.dom.client.createRoot
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Application
import space.kscience.visionforge.Colors
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.startApplication
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")
render(element) {
createRoot(element).render {
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))
attrs {
this.context = context
this.solids = context.fetch(Solids)
this.vision = vision
}
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
plugins {
id("ru.mipt.npm.gradle.mpp")
id("space.kscience.gradle.mpp")
application
}
@ -21,21 +21,13 @@ kotlin {
useCommonJs()
browser {
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 {
commonMain {
dependencies {
@ -45,8 +37,10 @@ kotlin {
jvmMain {
dependencies {
implementation("org.apache.commons:commons-math3:3.6.1")
implementation(npmlibs.ktor.server.cio)
implementation(npmlibs.ktor.serialization)
implementation("io.ktor:ktor-server-cio:${ktorVersion}")
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 {
@ -63,6 +57,23 @@ application {
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 {
// main {
// 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.LOWER_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.removeAll
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.solid.*
import kotlin.collections.set
import kotlin.math.PI
class Model(val manager: VisionManager) {
private val map = HashMap<String, SolidGroup>()
private val events = HashSet<Event>()
private fun SolidGroup.pixel(pixel: SC1) {
val group = group(pixel.name) {
private fun MutableVisionContainer<Solid>.pixel(pixel: SC1) {
val group = solidGroup(pixel.name) {
position = Point3D(pixel.center.x, pixel.center.y, pixel.center.z)
box(pixel.xSize, pixel.ySize, pixel.zSize)
label(pixel.name) {
@ -27,7 +27,7 @@ class Model(val manager: VisionManager) {
}
private fun SolidGroup.detector(detector: SC16) {
group(detector.name) {
solidGroup(detector.name) {
detector.pixels.forEach {
pixel(it)
}
@ -39,40 +39,39 @@ class Model(val manager: VisionManager) {
val root: SolidGroup = SolidGroup().apply {
setAsRoot(this@Model.manager)
material {
wireframe
color("darkgreen")
color.set("darkgreen")
}
rotationX = PI / 2
group("bottom") {
solidGroup("bottom") {
Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach {
detector(it)
}
}
group("middle") {
solidGroup("middle") {
Monitor.detectors.filter { it.center.z == CENTRAL_LAYER_Z }.forEach {
detector(it)
}
}
group("top") {
solidGroup("top") {
Monitor.detectors.filter { it.center.z == UPPER_LAYER_Z }.forEach {
detector(it)
}
}
tracks = group("tracks")
tracks = solidGroup("tracks")
}
private fun highlight(pixel: String) {
println("highlight $pixel")
map[pixel]?.color?.invoke("blue")
map[pixel]?.color.set("blue")
}
fun reset() {
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) {
@ -84,7 +83,7 @@ class Model(val manager: VisionManager) {
event.track?.let {
tracks.polyline(*it.toTypedArray(), name = "track[${event.id}]") {
thickness = 4
color("red")
color.set("red")
}
}
}

View File

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

View File

@ -1,11 +1,13 @@
package ru.mipt.npm.muon.monitor
import kotlinx.browser.document
import react.dom.render
import react.dom.client.createRoot
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.visionforge.Application
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.startApplication
@ -16,16 +18,17 @@ private class MMDemoApp : Application {
val context = Context("MM-demo") {
plugin(ThreePlugin)
}
val visionManager = context.fetch(VisionManager)
val model = Model(visionManager)
val element = document.getElementById("app") ?: error("Element with id 'app' not found on page")
render(element) {
createRoot(element).render {
child(MMApp) {
attrs {
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">-->
<title>Three js demo for particle physics</title>
<script type="text/javascript" src="muon-monitor.js"></script>
<link rel="stylesheet" href="css/custom-bootstrap.css">
</head>
<body class="application">
<div class="container-fluid max-vh-100" id = "app"> </div>

View File

@ -1,24 +1,22 @@
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.HttpStatusCode
import io.ktor.http.content.resources
import io.ktor.http.content.static
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.get
import io.ktor.serialization.json
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.application.log
import io.ktor.server.cio.CIO
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 ru.mipt.npm.muon.monitor.Model
import ru.mipt.npm.muon.monitor.sim.Cos2TrackGenerator
@ -40,8 +38,6 @@ fun Application.module(context: Context = Global) {
environment.log.info("Current directory: $currentDir")
val solidManager = context.fetch(Solids)
install(DefaultHeaders)
install(CallLogging)
install(ContentNegotiation) {
json()
}

View File

@ -32,7 +32,7 @@ kotlin {
kotlinOptions {
jvmTarget = "11"
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 {

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.examples
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.models.ScatterMode
import space.kscience.plotly.models.TextPosition

View File

@ -1,13 +1,20 @@
package space.kscience.visionforge.examples
import space.kscience.gdml.GdmlShowCase
import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.ResourceLocation
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") {
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.solid.Solids
import space.kscience.visionforge.solid.color
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.visible
import java.nio.file.Path
@ -229,7 +229,7 @@ fun main() = makeVisionFile(Path.of("curves.html"), resourceLocation = ResourceL
visible = false
}
if(solid.name.startsWith("gas")){
color("green")
color.set("green")
} else {
//make all solids semi-transparent
transparent()

View File

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

View File

@ -1,12 +1,12 @@
package space.kscience.visionforge.examples
import ru.mipt.npm.root.DGeoManager
import ru.mipt.npm.root.rootGeo
import ru.mipt.npm.root.serialization.TGeoManager
import ru.mipt.npm.root.toSolid
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
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 java.nio.file.Paths
import java.util.zip.ZipInputStream
@ -34,7 +34,7 @@ fun main() {
println(it)
}
val solid = geo.toSolid()
val solid = Solids.rootGeo(geo)
Paths.get("BM@N.vf.json").writeText(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.solid.box
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.material
import space.kscience.visionforge.solid.set
import space.kscience.visionforge.solid.solid
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
@ -11,7 +11,7 @@ fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
solid {
box(100, 100, 100)
material {
emissiveColor("red")
emissiveColor.set("red")
}
}
}

View File

@ -1,6 +1,6 @@
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.valueRow
import space.kscience.visionforge.html.ResourceLocation

View File

@ -1,5 +1,5 @@
plugins {
id("ru.mipt.npm.gradle.jvm")
id("space.kscience.gradle.jvm")
application
}
@ -15,7 +15,7 @@ group = "ru.mipt.npm"
dependencies{
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 {

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import ru.mipt.npm.gradle.DependencyConfiguration
import ru.mipt.npm.gradle.FXModule
import space.kscience.gradle.DependencyConfiguration
import space.kscience.gradle.FXModule
plugins {
id("ru.mipt.npm.gradle.mpp")
id("space.kscience.gradle.mpp")
application
}
@ -40,5 +40,5 @@ kotlin {
}
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.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.solid.Solids
public interface VisionLayout<in V: Vision> {
val solids: Solids
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 {
"title" put title
}
val vision = SolidGroup(block)
val vision = solids.solidGroup {
block()
ambientLight {
color.set(Colors.white)
}
}
render(Name.parse(name), vision, meta)
}
@ -39,30 +44,31 @@ val canvasOptions = Canvas3DOptions {
@OptIn(DelicateCoroutinesApi::class)
fun VisionLayout<Solid>.showcase() {
demo("shapes", "Basic shapes") {
ambientLight()
box(100.0, 100.0, 100.0) {
z = -110.0
color("teal")
color.set("teal")
}
sphere(50.0) {
x = 110
detail = 16
color("red")
color.set("red")
}
tube(50, height = 10, innerRadius = 25, angle = PI) {
y = 110
detail = 16
rotationX = PI / 4
color("blue")
color.set("blue")
}
sphereLayer(50, 40, theta = PI / 2) {
rotationX = -PI * 3 / 4
z = 110
color(Colors.pink)
color.set(Colors.pink)
}
}
demo("dynamic", "Dynamic properties") {
val group = group {
val group = solidGroup {
box(100, 100, 100) {
z = 110.0
opacity = 0.5
@ -72,7 +78,7 @@ fun VisionLayout<Solid>.showcase() {
visible = false
x = 110.0
//override color for this cube
color(1530)
color.set(1530)
GlobalScope.launch(Dispatchers.Main) {
while (isActive) {
@ -87,19 +93,19 @@ fun VisionLayout<Solid>.showcase() {
val random = Random(111)
while (isActive) {
delay(1000)
group.color(random.nextInt(0, Int.MAX_VALUE))
group.color.set(random.nextInt(0, Int.MAX_VALUE))
}
}
}
demo("rotation", "Rotations") {
box(100, 100, 100)
group {
solidGroup {
x = 200
rotationY = PI / 4
box(100, 100, 100) {
rotationZ = PI / 4
color(Colors.red)
color.set(Colors.red)
}
}
}
@ -112,7 +118,7 @@ fun VisionLayout<Solid>.showcase() {
for (i in 0..100) {
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
}
}
@ -121,13 +127,13 @@ fun VisionLayout<Solid>.showcase() {
sphere(100) {
detail = 32
opacity = 0.4
color(Colors.blue)
color.set(Colors.blue)
}
repeat(20) {
polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) {
thickness = 3.0
rotationX = it * PI2 / 20
color(Colors.green)
color.set(Colors.green)
//rotationY = it * PI2 / 20
}
}
@ -154,7 +160,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
detail = 32
}
material {
color(Colors.pink)
color.set(Colors.pink)
}
}
composite(CompositeType.UNION) {
@ -164,7 +170,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
sphere(50) {
detail = 32
}
color("lightgreen")
color.set("lightgreen")
opacity = 0.7
}
composite(CompositeType.SUBTRACT) {
@ -175,7 +181,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
sphere(50) {
detail = 32
}
color("teal")
color.set("teal")
opacity = 0.7
}
}
@ -186,7 +192,7 @@ fun VisionLayout<Solid>.showcaseCSG() {
detail = 32
}
box(100, 100, 100)
color("red")
color.set("red")
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.names.Name
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.ThreePlugin
@ -27,6 +28,8 @@ class ThreeDemoGrid(element: Element) : VisionLayout<Solid> {
private val three = Global.fetch(ThreePlugin)
override val solids: Solids get() = three.solids
init {
element.clear()
element.append {

View File

@ -1,20 +1,18 @@
package space.kscience.visionforge.solid.demo
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.BoxGeometry
import info.laht.threekt.objects.Mesh
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.asValue
import space.kscience.visionforge.onPropertyChange
import space.kscience.visionforge.set
import space.kscience.visionforge.setProperty
import space.kscience.visionforge.setChild
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.layer
import space.kscience.visionforge.solid.three.*
import three.core.Object3D
import three.geometries.BoxGeometry
import three.objects.Mesh
import kotlin.math.max
internal fun SolidGroup.varBox(
@ -22,7 +20,7 @@ internal fun SolidGroup.varBox(
ySize: Number,
name: String = "",
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() {
@ -44,13 +42,13 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
it.layers.enable(this@VariableBox.layer)
}
}
mesh.scale.z = meta[VALUE].number?.toDouble() ?: 1.0
mesh.scale.z = properties.getValue(VALUE)?.number?.toDouble() ?: 1.0
//add listener to object properties
onPropertyChange { name ->
when {
name == VALUE -> {
val value = meta.get(VALUE).int ?: 0
val value = properties.getValue(VALUE)?.int ?: 0
val size = value.toFloat() / 255f * 20f
mesh.scale.z = size.toDouble()
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)
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)
}
}
@ -70,9 +69,9 @@ internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision
}
var value: Int
get() = meta[VALUE].int ?: 0
get() = properties.getValue(VALUE)?.int ?: 0
set(value) {
setProperty(VALUE, value.asValue())
properties.setValue(VALUE, value.asValue())
}
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.FXCanvas3D
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids
import tornadofx.*
class FXDemoGrid : View(title = "DataForge-vis FX demo"), VisionLayout<Solid> {
private val outputs = FXCollections.observableHashMap<Name, FXCanvas3D>()
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)
override val solids: Solids get() = fx3d.solids
override fun render(name: Name, vision: Solid, meta: Meta) {
outputs.getOrPut(name) { FXCanvas3D(fx3d, canvasOptions) }.render(vision)

View File

@ -2,10 +2,10 @@ package space.kscience.visionforge.demo
import javafx.geometry.Orientation
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.node
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.editor.FXMetaModel
import space.kscience.visionforge.editor.MetaViewer
import space.kscience.visionforge.editor.MutableMetaEditor

View File

@ -3,7 +3,7 @@
![](../docs/images/hierarchy.png)
### 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
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
`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:
* own styles
* prototypes

View File

@ -59,7 +59,7 @@ box(10, 10, 10, name = "small box"){
rotation = Point3D(0, 0, 0)
}
```
![](../docs/images/small-box.png)
![](../images/small-box.png)
The `big box` will have properties with custom values.
```kotlin
@ -72,7 +72,7 @@ box(40, 40, 40, name = "big box"){
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.
Here is the function `main` with both boxes.
@ -111,8 +111,8 @@ fun main(){
}
}
```
![](../docs/images/two-boxes-1.png)
![](../docs/images/two-boxes-2.png)
![](../images/two-boxes-1.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.***
@ -142,8 +142,8 @@ polyline(Point3D(30, 20, 10), Point3D(30, -100, 30), Point3D(30, -100, 30), Poin
}
```
![](../docs/images/polyline-points.png)
![](../docs/images/polyline-points-2.png)
![](../images/polyline-points.png)
![](../images/polyline-points-2.png)
### 2) Box
@ -165,7 +165,7 @@ Let's create just usual `box` with equal ribs.
color("pink")
}
```
![](../docs/images/box.png)
![](../images/box.png)
Now, let's make `box` with bigger `y` value.
```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.
![](../docs/images/high-box.png)
![](../images/high-box.png)
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.
![](../docs/images/wide-box.png)
![](../images/wide-box.png)
### 3) Sphere
@ -206,7 +206,7 @@ As for `radius`, it has `Float` type, and, as you can guess, it sets the radius
color("blue")
}
```
![](../docs/images/sphere.png)
![](../images/sphere.png)
### 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`
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.
@ -239,7 +239,7 @@ Let's make classic parallelepiped.
color("green")
}
```
![](../docs/images/classic-hexagon.png)
![](../images/classic-hexagon.png)
Now, let's make a custom hexagon.
@ -258,7 +258,7 @@ hexagon(
color("brown")
}
```
![](../docs/images/custom-hexagon.png)
![](../images/custom-hexagon.png)
### 3) Cone
It takes in six values: `bottomRadius`, `height`, `upperRadius`, `startAngle`, `angle`, and `name`.
@ -274,8 +274,8 @@ Let's build a classic cone:
color("beige")
}
```
![](../docs/images/cone-1.png)
![](../docs/images/cone-2.png)
![](../images/cone-1.png)
![](../images/cone-2.png)
First of all, we have to try to build a frustum cone:
```kotlin
@ -283,7 +283,7 @@ cone(60, 80, name = "cone") {
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:
@ -292,8 +292,8 @@ cone(60, 80, angle = PI, name = "cone") {
color(0u, 0u, 200u)
}
```
![](../docs/images/cone-segment-1.png)
![](../docs/images/cone-segment-2.png)
![](../images/cone-segment-1.png)
![](../images/cone-segment-2.png)
Finally, the segment of frustum cone is left for a try:
```kotlin
@ -301,7 +301,7 @@ cone(60, 100, 20, PI*3/4, angle = PI/3, name = "cone") {
color(190u, 0u, 0u)
}
```
![](../docs/images/frustum-cone-segment.png)
![](../images/frustum-cone-segment.png)
### 4) Cone Surface
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)
}
```
![](../docs/images/cone-surface-1.png)
![](../docs/images/cone-surface-2.png)
![](../images/cone-surface-1.png)
![](../images/cone-surface-2.png)
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)
}
```
![](../docs/images/cone-surface-fragment.png)
![](../docs/images/cone-surface-fragment-2.png)
![](../images/cone-surface-fragment.png)
![](../images/cone-surface-fragment-2.png)
### 5) Cylinder
@ -344,8 +344,8 @@ cylinder(40, 100, "cylinder"){
color("indigo")
}
```
![](../docs/images/cylinder-1.png)
![](../docs/images/cylinder-2.png)
![](../images/cylinder-1.png)
![](../images/cylinder-2.png)
### 6) Tube
`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
}
```
![](../docs/images/tube.png)
![](../images/tube.png)
This is an example of tube fragment:
@ -365,7 +365,7 @@ tube(50, 40, 20, 0f, PI, name = "fragmented tube"){
color("white")
}
```
![](../docs/images/tube-fragment.png)
![](../images/tube-fragment.png)
### 7) Extruded
`extruded` is set by two values: `shape`, and `layer`.

View File

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

View File

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

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@ public external interface TabPaneProps : PropsWithChildren {
public val TabPane: FC<TabPaneProps> = fc("TabPane") { props ->
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()
} ?: emptyArray()

View File

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

View File

@ -2,13 +2,17 @@ package space.kscience.visionforge.bootstrap
import org.w3c.dom.Element
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.isEmpty
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
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.propertyEditor
import space.kscience.visionforge.react.render
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
@ -19,12 +23,26 @@ public fun RBuilder.visionPropertyEditor(
) {
card("Properties") {
propertyEditor(
ownProperties = vision.meta,
allProperties = vision.computeProperties(),
descriptor = descriptor,
key = key
)
child(PropertyEditor) {
attrs {
this.key = key?.toString()
this.meta = vision.properties.root()
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) {
(vision.styles + vision.prototype.styles).distinct()
@ -50,6 +68,6 @@ public fun RBuilder.visionPropertyEditor(
public fun Element.visionPropertyEditor(
item: Vision,
descriptor: MetaDescriptor? = item.descriptor,
): Unit = render(this) {
): Unit = createRoot(this).render {
visionPropertyEditor(item, descriptor = descriptor)
}

View File

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

View File

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

View File

@ -1,13 +1,18 @@
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.properties.TextDecoration
import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
import org.w3c.dom.events.Event
import react.*
import react.dom.attrs
import react.dom.render
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRequirement
@ -19,17 +24,30 @@ import styled.styledButton
import styled.styledDiv
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 {
/**
* Root config object - always non-null
*/
public var meta: ObservableMutableMeta
public var meta: MutableMeta
/**
* Provide default item (greyed out if used)
*/
public var withDefault: MetaProvider
public var getPropertyState: (Name) -> EditorPropertyState
public var scope: CoroutineScope
public var updates: Flow<Name>
/**
* 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) {
var expanded: Boolean by useState { props.expanded ?: true }
val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) }
var property: MutableMeta by useState { props.meta.getOrCreate(props.name) }
var editorPropertyState: EditorPropertyState by useState { props.getPropertyState(props.name) }
val keys = useMemo(descriptor) {
buildSet {
@ -70,17 +90,19 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() {
ownProperty = props.meta.getOrCreate(props.name)
property = props.meta.getOrCreate(props.name)
editorPropertyState = props.getPropertyState(props.name)
}
useEffect(props.meta) {
props.meta.onChange(props) { updatedName ->
val job = props.updates.onEach { updatedName ->
if (updatedName == props.name) {
update()
}
}
}.launchIn(props.scope)
cleanup {
props.meta.removeListener(props)
job.cancel()
}
}
@ -115,7 +137,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
styledSpan {
css {
+TreeStyles.treeLabel
if (ownProperty.isEmpty()) {
if (editorPropertyState != EditorPropertyState.Defined) {
+TreeStyles.treeLabelInactive
}
}
@ -131,8 +153,12 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
ValueChooser {
attrs {
this.descriptor = descriptor
this.meta = ownProperty
this.actual = props.withDefault.getMeta(props.name) ?: ownProperty
this.state = editorPropertyState
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"
attrs {
if (ownProperty.isEmpty()) {
if (editorPropertyState!= EditorPropertyState.Defined) {
disabled = true
} else {
onClickFunction = removeClick
@ -179,9 +205,11 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
attrs {
this.key = props.name.toString()
this.meta = props.meta
this.withDefault = props.withDefault
this.name = props.name + token
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)
@ -197,44 +225,51 @@ public val PropertyEditor: FC<PropertyEditorProps> = fc("PropertyEditor") { prop
attrs {
this.key = ""
this.meta = props.meta
this.withDefault = props.withDefault
this.name = Name.EMPTY
this.descriptor = props.descriptor
this.expanded = props.expanded
this.scope = props.scope
this.getPropertyState = props.getPropertyState
this.updates = props.updates
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
public fun RBuilder.propertyEditor(
ownProperties: ObservableMutableMeta,
allProperties: MetaProvider = ownProperties,
scope: CoroutineScope,
properties: ObservableMutableMeta,
descriptor: MetaDescriptor? = null,
key: Any? = null,
expanded: Boolean? = null,
) {
child(PropertyEditor) {
attrs {
this.meta = ownProperties
this.withDefault = allProperties
this.meta = properties
this.descriptor = descriptor
this.key = key?.toString() ?: ""
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.fc
import react.useState
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.descriptors.ValueRequirement
import space.kscience.dataforge.meta.double
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.values.asValue
import styled.css
import styled.styledInput
@JsExport
public val RangeValueChooser: FC<ValueChooserProps> = fc("RangeValueChooser") { props ->
var innerValue by useState(props.actual.double)
var rangeDisabled: Boolean by useState(props.meta.value == null)
var innerValue by useState(props.value?.double)
var rangeDisabled: Boolean by useState(props.state != EditorPropertyState.Defined)
val handleDisable: (Event) -> Unit = {
val checkBoxValue = (it.target as HTMLInputElement).checked
rangeDisabled = !checkBoxValue
props.meta.value = if (!checkBoxValue) {
null
} else {
innerValue?.asValue()
}
props.onValueChange(
if (!checkBoxValue) {
null
} else {
innerValue?.asValue()
}
)
}
val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).value
props.meta.value = newValue.toDoubleOrNull()?.asValue()
props.onValueChange(newValue.toDoubleOrNull()?.asValue())
innerValue = newValue.toDoubleOrNull()
}

View File

@ -16,6 +16,7 @@ import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.asSequence
import space.kscience.visionforge.isEmpty
import styled.css
import styled.styledDiv
@ -61,7 +62,7 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
//display as node if any child is visible
if (obj is VisionGroup) {
flexRow {
if (obj.children.any { !it.key.body.startsWith("@") }) {
if (obj.children.keys.any { !it.body.startsWith("@") }) {
styledSpan {
css {
+TreeStyles.treeCaret
@ -81,9 +82,9 @@ private fun RBuilder.visionTree(props: ObjectTreeProps): Unit {
css {
+TreeStyles.tree
}
obj.children.entries
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisionGroup)?.isEmpty() ?: true } // ignore empty groups
obj.children.asSequence()
.filter { !it.first.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.second as? VisionGroup)?.children?.isEmpty() ?: true } // ignore empty groups
.forEach { (childToken, child) ->
styledDiv {
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.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.string
import space.kscience.visionforge.Colors
import space.kscience.visionforge.widgetType
import styled.css
import styled.styledInput
import styled.styledSelect
import three.math.Color
public external interface ValueChooserProps : Props {
public var descriptor: MetaDescriptor?
public var meta: ObservableMutableMeta
public var actual: Meta
public var state: EditorPropertyState
public var value: Value?
public var onValueChange: (Value?) -> Unit
}
@JsExport
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 ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
value = (event.target as HTMLInputElement).value
props.meta.value = value.asValue()
props.onValueChange(value.asValue())
}
}
val handleChange: (Event) -> Unit = {
@ -63,7 +61,7 @@ public val StringValueChooser: FC<ValueChooserProps> = fc("StringValueChooser")
public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser") { props ->
val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).checked
props.meta.value = newValue.asValue()
props.onValueChange(newValue.asValue())
}
styledInput(type = InputType.checkBox) {
css {
@ -71,7 +69,7 @@ public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser"
}
attrs {
//this.attributes["indeterminate"] = (props.item == null).toString()
checked = props.actual.boolean ?: false
checked = props.value?.boolean ?: false
onChangeFunction = handleChange
}
}
@ -79,7 +77,7 @@ public val BooleanValueChooser: FC<ValueChooserProps> = fc("BooleanValueChooser"
@JsExport
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 ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
innerValue = (event.target as HTMLInputElement).value
@ -87,7 +85,7 @@ public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser")
if (number == null) {
console.error("The input value $innerValue is not a number")
} else {
props.meta.value = number.asValue()
props.onValueChange(number.asValue())
}
}
}
@ -117,10 +115,10 @@ public val NumberValueChooser: FC<ValueChooserProps> = fc("NumberValueChooser")
@JsExport
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 = {
selected = (it.target as HTMLSelectElement).value
props.meta.value = selected.asValue()
props.onValueChange(selected.asValue())
}
styledSelect {
css {
@ -132,7 +130,7 @@ public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") {
}
}
attrs {
this.value = props.actual.string ?: ""
this.value = props.value?.string ?: ""
multiple = false
onChangeFunction = handleChange
}
@ -142,7 +140,7 @@ public val ComboValueChooser: FC<ValueChooserProps> = fc("ComboValueChooser") {
@JsExport
public val ColorValueChooser: FC<ValueChooserProps> = fc("ColorValueChooser") { props ->
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) {
css {
@ -150,9 +148,9 @@ public val ColorValueChooser: FC<ValueChooserProps> = fc("ColorValueChooser") {
margin(0.px)
}
attrs {
this.value = props.actual.value?.let { value ->
this.value = props.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string
else "#" + Color(value.string).getHexString()
} ?: "#000000"
onChangeFunction = handleChange
}

View File

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

View File

@ -5,39 +5,46 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.css.*
import react.*
import react.dom.b
import react.dom.div
import react.dom.p
import react.dom.span
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.NameToken
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.length
import space.kscience.visionforge.*
import space.kscience.visionforge.react.ThreeCanvasComponent
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.react.*
import space.kscience.visionforge.solid.Solid
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 styled.css
import styled.styledDiv
public external interface ThreeCanvasWithControlsProps : Props {
public var context: Context
public var solids: Solids
public var builderOfSolid: Deferred<Solid?>
public var selected: Name?
public var options: Canvas3DOptions?
public var additionalTabs: Map<String, RBuilder.() -> Unit>?
}
private val ThreeCanvasWithControlsProps.context get() = solids.context
public fun ThreeCanvasWithControlsProps.solid(block: SolidGroup.() -> Unit) {
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) {
additionalTabs = (additionalTabs ?: emptyMap()) + (title to block)
}
@ -77,14 +84,14 @@ public fun RBuilder.nameCrumbs(name: Name?, link: (Name) -> Unit): Unit = styled
@JsExport
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)
useEffect {
props.context.launch {
solid = props.builderOfSolid.await()
//ensure that the solid is properly rooted
if(solid?.parent == null){
if (solid?.parent == null) {
solid?.setAsRoot(props.context.visionManager)
}
}
@ -104,7 +111,7 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
selected?.let {
when {
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 }
}
IslandContent {
propertyEditor(
ownProperties = vision.meta,
allProperties = vision.computeProperties(),
descriptor = vision.descriptor,
key = selected
)
child(PropertyEditor) {
attrs {
this.key = selected.toString()
this.meta = vision.properties.root()
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 org.w3c.dom.Element
import react.child
import react.dom.client.createRoot
import space.kscience.dataforge.context.AbstractPlugin
import space.kscience.dataforge.context.Context
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.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision
import space.kscience.visionforge.react.render
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.three.ThreePlugin
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
override fun render(element: Element, vision: Vision, meta: Meta) {
react.dom.render(element) {
createRoot(element).render {
child(ThreeCanvasWithControls) {
attrs {
this.context = this@ThreeWithControlsPlugin.context
this.solids = three.solids
this.builderOfSolid = context.async { vision as Solid}
}
}
@ -45,6 +46,7 @@ public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer {
public companion object : PluginFactory<ThreeWithControlsPlugin> {
override val tag: PluginTag = PluginTag("vision.threejs.withControls", PluginTag.DATAFORGE_GROUP)
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 react.RBuilder
import react.dom.client.createRoot
import react.dom.p
import react.dom.render
import react.key
import ringui.Island
import ringui.SmartTabs
import ringui.Tab
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.get
import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.react.*
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
@ -30,12 +30,25 @@ public fun RBuilder.ringPropertyEditor(
flexColumn {
Island("Properties") {
propertyEditor(
ownProperties = vision.meta,
allProperties = vision.computeProperties(),
descriptor = descriptor,
key = key
)
child(PropertyEditor) {
attrs {
this.key = key?.toString()
this.meta = vision.properties.root()
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()) {
@ -72,6 +85,6 @@ public fun RBuilder.ringPropertyEditor(
public fun Element.ringPropertyEditor(
item: Vision,
descriptor: MetaDescriptor? = item.descriptor,
): Unit = render(this) {
): Unit = createRoot(this).render {
ringPropertyEditor(item, descriptor = descriptor)
}

View File

@ -1,5 +1,7 @@
package space.kscience.visionforge.ring
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.css.BorderStyle
import kotlinx.css.Color
import kotlinx.css.padding
@ -18,7 +20,6 @@ import react.fc
import ringui.Island
import ringui.SmartTabs
import ringui.Tab
import space.kscience.dataforge.meta.withDefault
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.encodeToString
@ -52,6 +53,7 @@ internal external interface CanvasControlsProps : Props {
public var vision: Vision?
}
@OptIn(DelicateCoroutinesApi::class)
internal val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn {
flexRow {
@ -75,8 +77,8 @@ internal val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { pr
}
}
propertyEditor(
ownProperties = props.options.meta,
allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
scope = props.vision?.manager?.context ?: GlobalScope,
properties = props.options.meta,
descriptor = Canvas3DOptions.descriptor,
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 synthetic fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta;
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 {
@ -241,8 +241,8 @@ public abstract interface class space/kscience/visionforge/Vision : space/kscien
public fun getManager ()Lspace/kscience/visionforge/VisionManager;
public abstract fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta;
public abstract fun getParent ()Lspace/kscience/visionforge/VisionGroup;
public abstract fun getPropertyValue (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 abstract fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)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 setParent (Lspace/kscience/visionforge/VisionGroup;)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;
public fun getParent ()Lspace/kscience/visionforge/VisionGroup;
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 setParent (Lspace/kscience/visionforge/VisionGroup;)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 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 synthetic fun getPropertyValue$default (Lspace/kscience/visionforge/Vision;Ljava/lang/String;ZZZILjava/lang/Object;)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 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 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
@ -499,8 +499,8 @@ public abstract class space/kscience/visionforge/VisionPlugin : space/kscience/d
public abstract interface class space/kscience/visionforge/VisionPropertyContainer {
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 static synthetic fun getPropertyValue$default (Lspace/kscience/visionforge/VisionPropertyContainer;Lspace/kscience/dataforge/names/Name;ZZZILjava/lang/Object;)Lspace/kscience/dataforge/values/Value;
public abstract fun getProperty (Lspace/kscience/dataforge/names/Name;ZZZ)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 {

View File

@ -1,5 +1,5 @@
plugins {
id("ru.mipt.npm.gradle.mpp")
id("space.kscience.gradle.mpp")
}
val dataforgeVersion: String by rootProject.extra
@ -13,6 +13,11 @@ kotlin {
api("org.jetbrains.kotlin-wrappers:kotlin-css")
}
}
commonTest{
dependencies{
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${space.kscience.gradle.KScienceVersions.coroutinesVersion}")
}
}
jsMain {
dependencies {
api("org.jetbrains.kotlin-wrappers:kotlin-extensions")
@ -28,5 +33,5 @@ kscience{
}
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
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.number
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.string
import space.kscience.dataforge.meta.*
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
*/
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 {
reference.owner === vision -> true
@ -18,14 +18,14 @@ private tailrec fun styleIsDefined(vision: Vision, reference: StyleReference): B
}
@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(styleIsDefined(this, reference)) { "Style reference does not belong to a Vision parent" }
useStyle(reference.name)
useStyle(reference.name, notify)
}
@VisionBuilder
public fun VisionGroup.style(
public fun Vision.style(
styleKey: String? = null,
builder: MutableMeta.() -> Unit,
): ReadOnlyProperty<Any?, StyleReference> = ReadOnlyProperty { _, property ->
@ -35,7 +35,7 @@ public fun VisionGroup.style(
}
@VisionBuilder
public fun <T : Scheme> VisionGroup.style(
public fun <T : Scheme> Vision.style(
spec: Specification<T>,
styleKey: String? = null,
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.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.stringList
import kotlin.jvm.JvmInline
/**
* A container for styles
*/
@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)
@ -26,7 +23,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
* Define a style without notifying owner
*/
public fun define(key: String, style: Meta?) {
owner.meta.setMeta(STYLESHEET_KEY + key, style)
owner.properties.setProperty(STYLESHEET_KEY + key, style)
}
/**
@ -43,7 +40,7 @@ public value class StyleSheet(private val owner: VisionGroup) {
/**
* Create and set a style
*/
public operator fun set(key: String, builder: MutableMeta.() -> Unit) {
public fun update(key: String, builder: MutableMeta.() -> Unit) {
val newStyle = get(key)?.toMutableMeta()?.apply(builder) ?: Meta(builder)
set(key, newStyle.seal())
}
@ -59,49 +56,48 @@ internal fun Vision.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?)
val tokens: Collection<Name> =
((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
.map { it.asName() }
tokens.forEach { parent?.invalidateProperty(it) }
tokens.forEach { parent?.properties?.invalidate(it) }
}
if (this is VisionGroup) {
for (obj in this) {
obj.styleChanged(key, oldStyle, newStyle)
}
children?.forEach { _, vision ->
vision.styleChanged(key, oldStyle, newStyle)
}
}
/**
* List of names of styles applied to this object. Order matters. Not inherited.
*/
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) {
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,
* 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) {
styles = (meta.getMeta(Vision.STYLE_KEY)?.stringList ?: emptyList()) + name
public fun Vision.useStyle(name: String, notify: Boolean = true) {
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? =
meta.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name)
public fun Vision.getStyle(name: String): Meta? =
properties.own?.getMeta(StyleSheet.STYLESHEET_KEY + name) ?: parent?.getStyle(name)
/**
* Resolve a property from all styles
*/
public fun Vision.getStyleProperty(name: Name): Value? = styles.firstNotNullOfOrNull { getStyle(it)?.get(name)?.value }
public fun Vision.getStyleProperty(name: Name): Meta? = styles.firstNotNullOfOrNull { getStyle(it)?.get(name) }
/**
* Resolve an item in all style layers

View File

@ -1,147 +1,73 @@
package space.kscience.visionforge
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import space.kscience.dataforge.meta.asValue
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.boolean
import space.kscience.visionforge.AbstractVisionGroup.Companion.updateProperties
import space.kscience.visionforge.Vision.Companion.TYPE
import kotlin.reflect.KProperty1
/**
* A root type for display hierarchy
*/
@Type(TYPE)
public interface Vision : Described, Configurable {
public interface Vision : Described {
/**
* 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
*/
public val manager: VisionManager? get() = parent?.manager
/**
* This Vision own properties (ignoring inheritance, styles and defaults)
*/
override val meta: ObservableMutableMeta
/**
* 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)
public val properties: MutableVisionProperties
/**
* 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?
public companion object {
public const val TYPE: String = "vision"
public val STYLE_KEY: Name = "@style".asName()
public const val STYLE_TARGET: String = "style"
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
* if it should include inherited properties etc.
* Control visibility of the element
*/
@OptIn(ExperimentalCoroutinesApi::class)
@DFExperimental
public val Vision.propertyChanges: Flow<Name>
get() = callbackFlow {
meta.onChange(this) { name ->
launch {
send(name)
}
}
awaitClose {
meta.removeListener(this)
}
public var Vision.visible: Boolean?
get() = properties.getValue(Vision.VISIBLE_KEY)?.boolean
set(value) {
properties.setValue(Vision.VISIBLE_KEY, value?.asValue())
}
/**
* Subscribe on property updates. The subscription is bound to the given scope and canceled when the scope is canceled
*/
public fun Vision.onPropertyChange(callback: Meta.(Name) -> Unit) {
meta.onChange(null, callback)
}
/**
* Get [Vision] property using key as a String
*/
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))
}
}
}
public fun Vision.onPropertyChange(
scope: CoroutineScope? = manager?.context,
callback: (Name) -> Unit
): Job = properties.changes.onEach {
callback(it)
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))

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.serialization.Serializable
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.isEmpty
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.values.Null
import kotlin.jvm.Synchronized
import kotlin.time.Duration
/**
* 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
val manager = this.manager ?: return this
//TODO replace by efficient deep copy
val json = manager.encodeToJsonElement(this)
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 val propertyChange = MutableMeta()
private var propertyChange = MutableMeta()
private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
public fun isEmpty(): Boolean = propertyChange.isEmpty() && propertyChange.isEmpty() && children.isEmpty()
@Synchronized
private fun getOrPutChild(visionName: Name): VisionChangeBuilder =
children.getOrPut(visionName) { VisionChangeBuilder() }
children.getOrPut(visionName) { VisionChangeBuilder(manager) }
public fun propertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
if (visionName == Name.EMPTY) {
//Write property removal as [Null]
propertyChange[propertyName] = (item ?: Meta(Null))
if (propertyName.isEmpty()) {
propertyChange = item?.toMutableMeta() ?: MutableMeta()
} else {
propertyChange[propertyName] = (item ?: Meta(Null))
}
} else {
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")
getOrPutChild(name).apply {
vision = child
reset = vision == null
vision = child ?: NullVision
}
}
@ -62,32 +83,28 @@ public class VisionChangeBuilder : VisionContainerBuilder<Vision> {
* Isolate collected changes by creating detached copies of given visions
*/
public fun deepCopy(): VisionChange = VisionChange(
reset,
vision?.deepCopy(),
vision?.deepCopy(manager),
if (propertyChange.isEmpty()) null else propertyChange.seal(),
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
* @param vision a new value for vision content. If the Vision is to be removed should be [NullVision]
* @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.
*/
@Serializable
public data class VisionChange(
public val delete: Boolean = false,
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 inline fun VisionChange(block: VisionChangeBuilder.() -> Unit): VisionChange =
VisionChangeBuilder().apply(block).deepCopy()
public inline fun VisionManager.VisionChange(block: VisionChangeBuilder.() -> Unit): VisionChange =
VisionChangeBuilder(this).apply(block).deepCopy()
@OptIn(DFExperimental::class)
private fun CoroutineScope.collectChange(
name: Name,
source: Vision,
@ -95,29 +112,26 @@ private fun CoroutineScope.collectChange(
) {
//Collect properties change
source.onPropertyChange { propertyName ->
val newItem = source.meta[propertyName]
source.onPropertyChange(this) { propertyName ->
val newItem = source.properties.own?.get(propertyName)
collector().propertyChanged(name, propertyName, newItem)
}
if (source is VisionGroup) {
//Subscribe for children changes
source.children.forEach { (token, child) ->
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)
}
val children = source.children
//Subscribe for children changes
children?.forEach { token, child ->
collectChange(name + token, child, collector)
}
//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(
collectionDuration: Duration,
): Flow<VisionChange> = flow {
val manager = manager ?: error("Orphan vision could not collect changes")
var collector = VisionChangeBuilder()
var collector = VisionChangeBuilder(manager)
coroutineScope {
collectChange(Name.EMPTY, this@flowChanges) { collector }
//Send initial vision state
val initialChange = VisionChange(vision = deepCopy())
val initialChange = VisionChange(vision = deepCopy(manager))
emit(initialChange)
while (currentCoroutineContext().isActive) {
@ -143,7 +158,7 @@ public fun Vision.flowChanges(
//emit changes
emit(collector.deepCopy())
//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
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.provider.Provider
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.ValueType
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.Name
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 operator fun get(name: Name): V?
public interface VisionGroup : Vision {
public val children: VisionChildren
}
/**
* 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>
public interface MutableVisionGroup : VisionGroup {
override val defaultTarget: String get() = Vision.TYPE
override val children: MutableVisionChildren
/**
* 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 fun createGroup(): MutableVisionGroup
}
/**
* 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?)
}
public val Vision.children: VisionChildren? get() = (this as? VisionGroup)?.children
/**
* Mutable version of [VisionGroup]
* A full base implementation for a [Vision]
*/
public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision> {
public fun onStructureChanged(owner: Any?, block: VisionGroup.(Name) -> Unit)
@Serializable
public abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
public fun removeStructureListener(owner: Any?)
}
/**
* Flow structure changes of this group. Unconsumed changes are discarded
*/
@OptIn(ExperimentalCoroutinesApi::class)
@DFExperimental
public val MutableVisionGroup.structureChanges: Flow<Name>
get() = callbackFlow {
meta.onChange(this) { name ->
launch {
send(name)
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)
else -> children.getChild(name)?.update(change)
}
}
awaitClose {
removeStructureListener(this)
change.properties?.let {
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 =
set(token.asName(), child)
init {
childrenInternal?.forEach { it.value.parent = this }
}
public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String?, child: V?): Unit =
set(key?.let(Name::parse), child)
override val children: MutableVisionChildren by lazy {
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 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
/**
* Combined [SerializersModule] for all registered visions
*/
public val serializersModule: SerializersModule
get() = SerializersModule {
public val serializersModule: SerializersModule by lazy {
SerializersModule {
include(defaultSerialModule)
context.gather<SerializersModule>(VISION_SERIALIZER_MODULE_TARGET).values.forEach {
include(it)
}
}
}
public val jsonFormat: Json
get() = Json(defaultJson) {
@ -57,19 +58,23 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
public fun encodeToMeta(vision: Vision, descriptor: MetaDescriptor? = null): Meta =
encodeToJsonElement(vision).toMeta(descriptor)
override fun setChild(name: Name?, child: Vision?) {
child?.setAsRoot(this)
}
public companion object : PluginFactory<VisionManager> {
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)
override val type: KClass<out VisionManager> = VisionManager::class
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 {
polymorphic(Vision::class) {
default { VisionBase.serializer() }
subclass(VisionBase.serializer())
subclass(VisionGroupBase.serializer())
default { SimpleVisionGroup.serializer() }
subclass(NullVision.serializer())
subclass(SimpleVisionGroup.serializer())
subclass(VisionOfNumberField.serializer())
subclass(VisionOfTextField.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 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
* @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 fetchDataUrl fetch data after first render from 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 val formId: String,
) : VisionOfHtmlInput() {
public var values: Meta? by meta.node()
public var values: Meta? by mutableProperties.node()
}
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.number
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
public abstract class VisionOfHtmlInput : VisionBase() {
public var disabled: Boolean by meta.boolean(false)
public abstract class VisionOfHtmlInput : AbstractVision() {
public var disabled: Boolean by mutableProperties.boolean { false }
}
@Serializable
@ -18,7 +22,7 @@ public class VisionOfTextField(
public val label: String? = null,
public val name: String? = null,
) : VisionOfHtmlInput() {
public var text: String? by meta.string()
public var text: String? by mutableProperties.string()
}
@Serializable
@ -27,7 +31,7 @@ public class VisionOfCheckbox(
public val label: String? = null,
public val name: String? = null,
) : VisionOfHtmlInput() {
public var checked: Boolean? by meta.boolean()
public var checked: Boolean? by mutableProperties.boolean()
}
@Serializable
@ -36,7 +40,7 @@ public class VisionOfNumberField(
public val label: String? = null,
public val name: String? = null,
) : VisionOfHtmlInput() {
public var value: Number? by meta.number()
public var value: Number? by mutableProperties.number()
}
@Serializable
@ -48,6 +52,6 @@ public class VisionOfRangeField(
public val label: String? = null,
public val name: String? = null,
) : 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 {
id = resolveId(name)
classes = setOf(OUTPUT_CLASS)
vision.setAsRoot(manager)
if (vision.parent == null) {
vision.setAsRoot(manager)
}
attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString()
if (!outputMeta.isEmpty()) {
//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.descriptors.*
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.meta.set
private const val INHERITED_DESCRIPTOR_ATTRIBUTE = "inherited"
private const val STYLE_DESCRIPTOR_ATTRIBUTE = "useStyles"

View File

@ -1,6 +1,5 @@
package space.kscience.visionforge.visitor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@ -11,7 +10,6 @@ import space.kscience.visionforge.Vision
import kotlin.reflect.KClass
@OptIn(ExperimentalCoroutinesApi::class)
public suspend fun <T> Vision.flowStatistics(statistics: (Name, Vision) -> T): Flow<T> = callbackFlow<T> {
val visitor = object : VisionVisitor {
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.plus
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.children
import space.kscience.visionforge.forEach
public interface VisionVisitor {
/**
@ -19,30 +20,30 @@ public interface VisionVisitor {
/**
* 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
}
public fun skip(name: Name, vision: Vision): Boolean = false
public companion object{
public companion object {
private fun CoroutineScope.visitTreeAsync(
visionVisitor: VisionVisitor,
name: Name,
vision: Vision
vision: Vision,
): Job = launch {
if (visionVisitor.skip(name, vision)) return@launch
visionVisitor.visit(name, vision)
if (vision is VisionGroup) {
visionVisitor.visitChildren(name, vision)
for ((token, child) in vision.children) {
visitTreeAsync(visionVisitor, name + token, child)
}
visionVisitor.visitChildren(name, vision)
vision.children?.forEach { token, child ->
visitTreeAsync(visionVisitor, name + token, child)
}
}
/**
* Recursively visit this [Vision] and all children
*/

View File

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

View File

@ -1,44 +1,123 @@
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.values.asValue
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class VisionPropertyTest {
@Test
fun testPropertyWrite(){
val vision = VisionBase()
vision.meta["fff"] = 2
vision.meta["fff.ddd"] = false
assertEquals(2, vision.meta["fff"]?.int)
assertEquals(false, vision.meta["fff.ddd"]?.boolean)
private class TestScheme : Scheme() {
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
fun testPropertyEdit(){
val vision = VisionBase()
vision.meta.getOrCreate("fff.ddd").apply {
fun testPropertyEdit() {
val vision = manager.group()
vision.properties.getProperty("fff.ddd").apply {
value = 2.asValue()
}
assertEquals(2, vision.meta["fff.ddd"]?.int)
assertNotEquals(true, vision.meta["fff.ddd"]?.boolean)
}
internal class TestScheme: Scheme(){
var ddd by int()
companion object: SchemeSpec<TestScheme>(::TestScheme)
assertEquals(2, vision.properties.getValue("fff.ddd")?.int)
assertNotEquals(true, vision.properties.getValue("fff.ddd")?.boolean)
}
@Test
fun testPropertyUpdate(){
val vision = VisionBase()
vision.meta.getOrCreate("fff").updateWith(TestScheme){
fun testPropertyUpdate() {
val vision = manager.group()
vision.properties.getProperty("fff").updateWith(TestScheme) {
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) {
if (vision != null) {
vision.setAsRoot(visionManager)
val renderer = findRendererFor(vision)
?: error("Could not find renderer for ${visionManager.encodeToString(vision)}")
renderer.render(element, vision, outputMeta)
@ -115,7 +116,7 @@ public class VisionClient : AbstractPlugin() {
onopen = {
feedbackJob = vision.flowChanges(
feedbackAggregationTime.milliseconds
feedbackAggregationTime.milliseconds,
).onEach { change ->
send(visionManager.encodeToString(change))
}.launchIn(visionManager.context)
@ -203,8 +204,7 @@ public class VisionClient : AbstractPlugin() {
) else super.content(target)
public companion object : PluginFactory<VisionClient> {
override fun invoke(meta: Meta, context: Context): VisionClient = VisionClient()
override fun build(context: Context, meta: Meta): VisionClient = VisionClient()
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 {
id("ru.mipt.npm.gradle.jvm")
id("space.kscience.gradle.jvm")
}
val dataforgeVersion: String by rootProject.extra
val fxVersion: String by rootProject.extra
kscience{
useFx(ru.mipt.npm.gradle.FXModule.CONTROLS, version = fxVersion)
useFx(space.kscience.gradle.FXModule.CONTROLS, version = fxVersion)
}
dependencies {
@ -15,12 +15,12 @@ dependencies {
api("org.fxyz3d:fxyz3d:0.5.4") {
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") {
exclude(module = "slf4j-simple")
}
}
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> {
override val type: KClass<out FXPlugin> = FXPlugin::class
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.paint.Color
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.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.*
/**

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