Fixes and bouncing ball demo.

This commit is contained in:
Alexander Nozik 2021-08-15 18:15:17 +03:00
parent f99a359e24
commit af327c17e3
16 changed files with 214 additions and 215 deletions

View File

@ -1,4 +1,8 @@
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import react.child import react.child
import react.dom.render import react.dom.render
@ -17,6 +21,7 @@ import space.kscience.visionforge.solid.*
import space.kscience.visionforge.startApplication import space.kscience.visionforge.startApplication
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
import kotlin.math.sqrt
import kotlin.random.Random import kotlin.random.Random
private class JsPlaygroundApp : Application { private class JsPlaygroundApp : Application {
@ -31,9 +36,38 @@ private class JsPlaygroundApp : Application {
val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page") val element = document.getElementById("playground") ?: error("Element with id 'playground' not found on page")
val bouncingSphere = SolidGroup {
sphere(5.0, "ball") {
detail = 16
color("red")
val h = 100.0
y = h
GlobalScope.launch {
val g = 10.0
val dt = 0.1
var time = 0.0
var velocity = 0.0
while (isActive) {
delay(20)
time += dt
velocity -= g * dt
y = y.toDouble() + velocity * dt
if (y.toDouble() <= 2.5){
velocity = sqrt(2*g*h)
}
}
}
}
box(200, 5, 200, name = "floor"){
y = -2.5
}
}
val visionOfD0 = GdmlShowCase.babyIaxo().toVision() val visionOfD0 = GdmlShowCase.babyIaxo().toVision()
val random = Random(112233) val random = Random(112233)
val visionOfSpheres = SolidGroup { val visionOfSpheres = SolidGroup {
repeat(100) { repeat(100) {
sphere(5, name = "sphere[$it]") { sphere(5, name = "sphere[$it]") {
@ -56,7 +90,16 @@ private class JsPlaygroundApp : Application {
height = 100.vh height = 100.vh
width = 100.vw width = 100.vw
} }
SmartTabs("D0") { SmartTabs("gravity") {
Tab("gravity") {
child(ThreeCanvasWithControls) {
attrs {
context = playgroundContext
solid = bouncingSphere
}
}
}
Tab("D0") { Tab("D0") {
child(ThreeCanvasWithControls) { child(ThreeCanvasWithControls) {
attrs { attrs {
@ -73,8 +116,8 @@ private class JsPlaygroundApp : Application {
} }
} }
} }
Tab("plotly"){ Tab("plotly") {
Plotly{ Plotly {
attrs { attrs {
context = playgroundContext context = playgroundContext
plot = space.kscience.plotly.Plotly.plot { plot = space.kscience.plotly.Plotly.plot {

View File

@ -66,7 +66,7 @@ public val CanvasControls: FunctionComponent<CanvasControlsProps> = functionalCo
} }
} }
propertyEditor( propertyEditor(
ownProperties = props.canvasOptions, ownProperties = props.canvasOptions.meta,
allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode), allProperties = props.canvasOptions.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
descriptor = Canvas3DOptions.descriptor, descriptor = Canvas3DOptions.descriptor,
expanded = false expanded = false

View File

@ -4,10 +4,13 @@ import org.w3c.dom.Element
import react.RBuilder import react.RBuilder
import react.dom.render import react.dom.render
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.* import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.metaViewer import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidReference import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
public fun RBuilder.visionPropertyEditor( public fun RBuilder.visionPropertyEditor(
vision: Vision, vision: Vision,
@ -19,7 +22,6 @@ public fun RBuilder.visionPropertyEditor(
propertyEditor( propertyEditor(
ownProperties = vision.meta, ownProperties = vision.meta,
allProperties = vision.computeProperties(), allProperties = vision.computeProperties(),
updateFlow = vision.propertyChanges,
descriptor = descriptor, descriptor = descriptor,
key = key key = key
) )

View File

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

View File

@ -1,14 +1,5 @@
package space.kscience.visionforge.react package space.kscience.visionforge.react
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.css.* import kotlinx.css.*
import kotlinx.css.properties.TextDecoration import kotlinx.css.properties.TextDecoration
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
@ -22,7 +13,6 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.ValueRequirement import space.kscience.dataforge.meta.descriptors.ValueRequirement
import space.kscience.dataforge.meta.descriptors.get import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.visionforge.hidden import space.kscience.visionforge.hidden
import styled.css import styled.css
import styled.styledButton import styled.styledButton
@ -32,17 +22,17 @@ import styled.styledSpan
public external interface PropertyEditorProps : RProps { public external interface PropertyEditorProps : RProps {
/** /**
* Root config object - always non null * Root config object - always non-null
*/ */
public var ownProperties: MutableMetaProvider public var meta: ObservableMutableMeta
/** /**
* Provide default item (greyed out if used) * Provide default item (greyed out if used)
*/ */
public var allProperties: MetaProvider? public var withDefault: MetaProvider
/** /**
* Full path to the displayed node in [ownProperties]. Could be empty * Full path to the displayed node in [meta]. Could be empty
*/ */
public var name: Name public var name: Name
@ -51,16 +41,6 @@ public external interface PropertyEditorProps : RProps {
*/ */
public var descriptor: MetaDescriptor? public var descriptor: MetaDescriptor?
/**
* A coroutine scope for updates
*/
public var scope: CoroutineScope?
/**
* Flow names of updated properties
*/
public var updateFlow: Flow<Name>?
/** /**
* Initial expanded state * Initial expanded state
*/ */
@ -68,54 +48,17 @@ public external interface PropertyEditorProps : RProps {
} }
private val PropertyEditorItem: FunctionComponent<PropertyEditorProps> = private val PropertyEditorItem: FunctionComponent<PropertyEditorProps> =
functionalComponent("ConfigEditorItem") { props -> functionalComponent("PropertyEditorItem") { props ->
propertyEditorItem(props) propertyEditorItem(props)
} }
private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) { private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
var expanded: Boolean by useState { props.expanded ?: true } var expanded: Boolean by useState { props.expanded ?: true }
val descriptor: MetaDescriptor? = props.descriptor?.get(props.name) val descriptor: MetaDescriptor? = useMemo(props.descriptor, props.name) { props.descriptor?.get(props.name) }
var ownProperty: Meta? by useState { props.ownProperties.getMeta(props.name) } var ownProperty: ObservableMutableMeta by useState { props.meta.getOrCreate(props.name) }
val actualMeta = props.allProperties?.getMeta(props.name)
val token = props.name.lastOrNull()?.toString() ?: "Properties" val keys = useMemo(descriptor) {
buildSet {
fun update() {
ownProperty = props.ownProperties.getMeta(props.name)
}
if (props.updateFlow != null) {
useEffect(props.ownProperties, props.updateFlow) {
val updateJob = props.updateFlow!!.onEach { updatedName ->
if (updatedName == props.name) {
update()
}
}.launchIn(props.scope ?: GlobalScope)
cleanup {
updateJob.cancel()
}
}
}
val expanderClick: (Event) -> Unit = {
expanded = !expanded
}
val valueChanged: (Value?) -> Unit = {
if (it == null) {
props.ownProperties.remove(props.name)
} else {
props.ownProperties.setValue(props.name, it)
}
update()
}
val removeClick: (Event) -> Unit = {
props.ownProperties.remove(props.name)
update()
}
val keys = buildSet {
descriptor?.children?.filterNot { descriptor?.children?.filterNot {
it.key.startsWith("@") || it.value.hidden it.key.startsWith("@") || it.value.hidden
}?.forEach { }?.forEach {
@ -123,12 +66,41 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
} }
//ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) } //ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
} }
}
val token = props.name.lastOrNull()?.toString() ?: "Properties"
fun update() {
ownProperty = props.meta.getOrCreate(props.name)
}
useEffect(props.meta) {
props.meta.onChange(props) { updatedName ->
if (updatedName == props.name) {
update()
}
}
cleanup {
props.meta.removeListener(props)
}
}
val expanderClick: (Event) -> Unit = {
expanded = !expanded
}
val removeClick: (Event) -> Unit = {
props.meta.remove(props.name)
update()
}
flexRow { flexRow {
css { css {
alignItems = Align.center alignItems = Align.center
} }
if(keys.isNotEmpty()) { if (keys.isNotEmpty()) {
styledSpan { styledSpan {
css { css {
+TreeStyles.treeCaret +TreeStyles.treeCaret
@ -144,25 +116,26 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
styledSpan { styledSpan {
css { css {
+TreeStyles.treeLabel +TreeStyles.treeLabel
if (ownProperty == null) { if (ownProperty.isEmpty()) {
+TreeStyles.treeLabelInactive +TreeStyles.treeLabelInactive
} }
} }
+token +token
} }
if(!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) { if (!props.name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) {
styledDiv { styledDiv {
css { css {
//+TreeStyles.resizeableInput //+TreeStyles.resizeableInput
width = 160.px width = 160.px
margin(1.px, 5.px) margin(1.px, 5.px)
} }
valueChooser( ValueChooser{
props.name, attrs {
actualMeta, this.descriptor = descriptor
descriptor, this.meta = ownProperty
valueChanged this.actual = props.withDefault.getMeta(props.name) ?: ownProperty
) }
}
} }
styledButton { styledButton {
@ -184,7 +157,7 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
} }
+"\u00D7" +"\u00D7"
attrs { attrs {
if (ownProperty == null) { if (ownProperty.isEmpty()) {
disabled = true disabled = true
} else { } else {
onClickFunction = removeClick onClickFunction = removeClick
@ -206,8 +179,8 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
child(PropertyEditorItem) { child(PropertyEditorItem) {
attrs { attrs {
this.key = props.name.toString() this.key = props.name.toString()
this.ownProperties = props.ownProperties this.meta = props.meta
this.allProperties = props.allProperties this.withDefault = props.withDefault
this.name = props.name + token this.name = props.name + token
this.descriptor = props.descriptor this.descriptor = props.descriptor
} }
@ -217,74 +190,52 @@ private fun RBuilder.propertyEditorItem(props: PropertyEditorProps) {
} }
} }
} }
} }
@JsExport @JsExport
public val PropertyEditor: FunctionComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props -> public val PropertyEditor: FunctionComponent<PropertyEditorProps> = functionalComponent("PropertyEditor") { props ->
child(PropertyEditorItem) { child(PropertyEditorItem) {
attrs { attrs {
this.key = "" this.key = ""
this.ownProperties = props.ownProperties this.meta = props.meta
this.allProperties = props.allProperties this.withDefault = props.withDefault
this.name = Name.EMPTY this.name = Name.EMPTY
this.descriptor = props.descriptor this.descriptor = props.descriptor
this.scope = props.scope
this.expanded = props.expanded this.expanded = props.expanded
} }
} }
} }
public fun RBuilder.propertyEditor( public fun RBuilder.propertyEditor(
ownProperties: MutableMetaProvider, ownProperties: ObservableMutableMeta,
allProperties: MetaProvider? = ownProperties, allProperties: MetaProvider = ownProperties,
updateFlow: Flow<Name>? = null,
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
scope: CoroutineScope? = null,
key: Any? = null, key: Any? = null,
expanded: Boolean? = null expanded: Boolean? = null
) { ) {
child(PropertyEditor) { child(PropertyEditor) {
attrs { attrs {
this.ownProperties = ownProperties this.meta = ownProperties
this.allProperties = allProperties this.withDefault = allProperties
this.updateFlow = updateFlow
this.descriptor = descriptor this.descriptor = descriptor
this.key = key?.toString() ?: "" this.key = key?.toString() ?: ""
this.scope = scope
this.expanded = expanded this.expanded = expanded
} }
} }
} }
@OptIn(ExperimentalCoroutinesApi::class)
private fun ObservableMutableMeta.flowUpdates(): Flow<Name> = callbackFlow {
onChange(this) { name ->
launch {
send(name)
}
}
awaitClose {
removeListener(this)
}
}
public fun RBuilder.configEditor( public fun RBuilder.configEditor(
config: ObservableMutableMeta, config: ObservableMutableMeta,
default: MetaProvider? = null, default: MetaProvider = config,
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
key: Any? = null, key: Any? = null,
scope: CoroutineScope? = null, ): Unit = propertyEditor(config, default, descriptor, key = key)
): Unit = propertyEditor(config, default, config.flowUpdates(), descriptor, scope, key = key)
public fun Element.configEditor( public fun Element.configEditor(
config: ObservableMutableMeta, config: ObservableMutableMeta,
default: Meta = config,
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
default: Meta? = null,
key: Any? = null, key: Any? = null,
scope: CoroutineScope? = null,
): Unit = render(this) { ): Unit = render(this) {
configEditor(config, default, descriptor, key, scope) configEditor(config, default, descriptor, key = key)
} }

View File

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

View File

@ -13,14 +13,13 @@ import org.w3c.dom.events.Event
import react.* import react.*
import react.dom.attrs import react.dom.attrs
import react.dom.option import react.dom.option
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.boolean
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.allowedValues import space.kscience.dataforge.meta.descriptors.allowedValues
import space.kscience.dataforge.meta.get import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.meta.string import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.names.Name import space.kscience.dataforge.values.int
import space.kscience.dataforge.values.* import space.kscience.dataforge.values.string
import space.kscience.visionforge.Colors import space.kscience.visionforge.Colors
import space.kscience.visionforge.widgetType import space.kscience.visionforge.widgetType
import styled.css import styled.css
@ -28,23 +27,19 @@ import styled.styledInput
import styled.styledSelect import styled.styledSelect
public external interface ValueChooserProps : RProps { public external interface ValueChooserProps : RProps {
public var item: Meta?
public var descriptor: MetaDescriptor? public var descriptor: MetaDescriptor?
public var meta: ObservableMutableMeta
//public var nullable: Boolean? public var actual: Meta
public var valueChanged: ((Value?) -> Unit)?
} }
@JsExport @JsExport
public val StringValueChooser: FunctionComponent<ValueChooserProps> = public val StringValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("StringValueChooser") { props -> functionalComponent("StringValueChooser") { props ->
var value by useState(props.item.string ?: "") var value by useState(props.actual.string ?: "")
val keyDown: (Event) -> Unit = { event -> val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { if (event.type == "keydown" && event.asDynamic().key == "Enter") {
value = (event.target as HTMLInputElement).value value = (event.target as HTMLInputElement).value
if (value != props.item.string) { props.meta.value = value.asValue()
props.valueChanged?.invoke(value.asValue())
}
} }
} }
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
@ -67,7 +62,7 @@ public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("BooleanValueChooser") { props -> functionalComponent("BooleanValueChooser") { props ->
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
val newValue = (it.target as HTMLInputElement).checked val newValue = (it.target as HTMLInputElement).checked
props.valueChanged?.invoke(newValue.asValue()) props.meta.value = newValue.asValue()
} }
styledInput(type = InputType.checkBox) { styledInput(type = InputType.checkBox) {
css { css {
@ -75,7 +70,7 @@ public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
} }
attrs { attrs {
//this.attributes["indeterminate"] = (props.item == null).toString() //this.attributes["indeterminate"] = (props.item == null).toString()
defaultChecked = props.item.boolean ?: false defaultChecked = props.actual.boolean ?: false
onChangeFunction = handleChange onChangeFunction = handleChange
} }
} }
@ -84,7 +79,7 @@ public val BooleanValueChooser: FunctionComponent<ValueChooserProps> =
@JsExport @JsExport
public val NumberValueChooser: FunctionComponent<ValueChooserProps> = public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("NumberValueChooser") { props -> functionalComponent("NumberValueChooser") { props ->
var innerValue by useState(props.item.string ?: "") var innerValue by useState(props.actual.string ?: "")
val keyDown: (Event) -> Unit = { event -> val keyDown: (Event) -> Unit = { event ->
if (event.type == "keydown" && event.asDynamic().key == "Enter") { if (event.type == "keydown" && event.asDynamic().key == "Enter") {
innerValue = (event.target as HTMLInputElement).value innerValue = (event.target as HTMLInputElement).value
@ -92,7 +87,7 @@ public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
if (number == null) { if (number == null) {
console.error("The input value $innerValue is not a number") console.error("The input value $innerValue is not a number")
} else { } else {
props.valueChanged?.invoke(number.asValue()) props.meta.value = number.asValue()
} }
} }
} }
@ -123,10 +118,10 @@ public val NumberValueChooser: FunctionComponent<ValueChooserProps> =
@JsExport @JsExport
public val ComboValueChooser: FunctionComponent<ValueChooserProps> = public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("ComboValueChooser") { props -> functionalComponent("ComboValueChooser") { props ->
var selected by useState(props.item.string ?: "") var selected by useState(props.actual.string ?: "")
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
selected = (it.target as HTMLSelectElement).value selected = (it.target as HTMLSelectElement).value
props.valueChanged?.invoke(selected.asValue()) props.meta.value = selected.asValue()
} }
styledSelect { styledSelect {
css { css {
@ -138,7 +133,7 @@ public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
} }
} }
attrs { attrs {
this.value = props.item?.string ?: "" this.value = props.actual.string ?: ""
multiple = false multiple = false
onChangeFunction = handleChange onChangeFunction = handleChange
} }
@ -149,14 +144,14 @@ public val ComboValueChooser: FunctionComponent<ValueChooserProps> =
public val ColorValueChooser: FunctionComponent<ValueChooserProps> = public val ColorValueChooser: FunctionComponent<ValueChooserProps> =
functionalComponent("ColorValueChooser") { props -> functionalComponent("ColorValueChooser") { props ->
var value by useState( var value by useState(
props.item?.value?.let { value -> props.actual.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string else value.string
} ?: "#000000" } ?: "#000000"
) )
val handleChange: (Event) -> Unit = { val handleChange: (Event) -> Unit = {
value = (it.target as HTMLInputElement).value value = (it.target as HTMLInputElement).value
props.valueChanged?.invoke(value.asValue()) props.meta.value = value.asValue()
} }
styledInput(type = InputType.color) { styledInput(type = InputType.color) {
css { css {
@ -189,19 +184,3 @@ public val ValueChooser: FunctionComponent<ValueChooserProps> = functionalCompon
else -> child(StringValueChooser, props) else -> child(StringValueChooser, props)
} }
} }
internal fun RBuilder.valueChooser(
name: Name,
item: Meta?,
descriptor: MetaDescriptor? = null,
callback: (Value?) -> Unit,
) {
child(ValueChooser) {
attrs {
key = name.toString()
this.item = item
this.descriptor = descriptor
this.valueChanged = callback
}
}
}

View File

@ -15,7 +15,6 @@ import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.length import space.kscience.dataforge.names.length
import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.computeProperties import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.propertyChanges
import space.kscience.visionforge.react.ThreeCanvasComponent import space.kscience.visionforge.react.ThreeCanvasComponent
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow import space.kscience.visionforge.react.flexRow
@ -137,7 +136,6 @@ public val ThreeCanvasWithControls: FunctionComponent<ThreeCanvasWithControlsPro
propertyEditor( propertyEditor(
ownProperties = vision.meta, ownProperties = vision.meta,
allProperties = vision.computeProperties(), allProperties = vision.computeProperties(),
updateFlow = vision.propertyChanges,
descriptor = vision.descriptor, descriptor = vision.descriptor,
key = selected key = selected
) )

View File

@ -8,11 +8,14 @@ import ringui.Island
import ringui.SmartTabs import ringui.SmartTabs
import ringui.Tab import ringui.Tab
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.visionforge.* import space.kscience.visionforge.Vision
import space.kscience.visionforge.computeProperties
import space.kscience.visionforge.getStyle
import space.kscience.visionforge.react.flexColumn import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.metaViewer import space.kscience.visionforge.react.metaViewer
import space.kscience.visionforge.react.propertyEditor import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidReference import space.kscience.visionforge.solid.SolidReference
import space.kscience.visionforge.styles
public fun RBuilder.ringPropertyEditor( public fun RBuilder.ringPropertyEditor(
vision: Vision, vision: Vision,
@ -30,7 +33,6 @@ public fun RBuilder.ringPropertyEditor(
propertyEditor( propertyEditor(
ownProperties = vision.meta, ownProperties = vision.meta,
allProperties = vision.computeProperties(), allProperties = vision.computeProperties(),
updateFlow = vision.propertyChanges,
descriptor = descriptor, descriptor = descriptor,
key = key key = key
) )

View File

@ -72,7 +72,7 @@ internal val CanvasControls: FunctionComponent<CanvasControlsProps> = functional
} }
} }
propertyEditor( propertyEditor(
ownProperties = props.options, ownProperties = props.options.meta,
allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode), allProperties = props.options.meta.withDefault(Canvas3DOptions.descriptor.defaultNode),
descriptor = Canvas3DOptions.descriptor, descriptor = Canvas3DOptions.descriptor,
expanded = false expanded = false

View File

@ -12,10 +12,12 @@ import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.boolean import space.kscience.dataforge.values.boolean
import space.kscience.visionforge.Vision.Companion.TYPE import space.kscience.visionforge.Vision.Companion.TYPE
import kotlin.reflect.KProperty1
/** /**
* A root type for display hierarchy * A root type for display hierarchy
@ -129,3 +131,17 @@ public var Vision.visible: Boolean?
get() = getPropertyValue(Vision.VISIBLE_KEY)?.boolean get() = getPropertyValue(Vision.VISIBLE_KEY)?.boolean
set(value) = meta.setValue(Vision.VISIBLE_KEY, value?.asValue()) set(value) = meta.setValue(Vision.VISIBLE_KEY, value?.asValue())
public fun <V : Vision, T> V.useProperty(
property: KProperty1<V, T>,
owner: Any? = null,
callBack: V.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
meta.onChange(owner) { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}
}

View File

@ -44,7 +44,7 @@ public open class VisionBase(
} }
@Transient @Transient
private val listeners = HashSet<MetaListener>() private val listeners: MutableList<MetaListener> = mutableListOf()
private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta { private inner class VisionProperties(val pathName: Name) : ObservableMutableMeta {

View File

@ -11,7 +11,6 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBase import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.setProperty
@Serializable @Serializable
@SerialName("vision.markup") @SerialName("vision.markup")
@ -20,15 +19,11 @@ public class VisionOfMarkup(
) : VisionBase() { ) : VisionBase() {
//FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix //FIXME to be removed after https://github.com/Kotlin/kotlinx.serialization/issues/1602 fix
override var properties: MutableMeta? = null protected override var properties: MutableMeta? = null
//TODO add templates //TODO add templates
public var content: String? public var content: String? by meta.string(CONTENT_PROPERTY_KEY)
get() = meta.getMeta(CONTENT_PROPERTY_KEY).string
set(value) {
setProperty(CONTENT_PROPERTY_KEY, value)
}
public companion object { public companion object {
public val CONTENT_PROPERTY_KEY: Name = "content".asName() public val CONTENT_PROPERTY_KEY: Name = "content".asName()

View File

@ -1,16 +1,19 @@
package space.kscience.visionforge.markup package space.kscience.visionforge.markup
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.dom.clear
import kotlinx.html.dom.append
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
import org.w3c.dom.Element import org.w3c.dom.Element
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.visionforge.ElementVisionRenderer import space.kscience.visionforge.*
import space.kscience.visionforge.Vision import space.kscience.visionforge.markup.VisionOfMarkup.Companion.COMMONMARK_FORMAT
import space.kscience.visionforge.VisionClient import space.kscience.visionforge.markup.VisionOfMarkup.Companion.GFM_FORMAT
import space.kscience.visionforge.VisionPlugin
import kotlin.reflect.KClass import kotlin.reflect.KClass
public class MarkupPlugin : VisionPlugin(), ElementVisionRenderer { public class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
@ -26,8 +29,20 @@ public class MarkupPlugin : VisionPlugin(), ElementVisionRenderer {
override fun render(element: Element, vision: Vision, meta: Meta) { override fun render(element: Element, vision: Vision, meta: Meta) {
require(vision is VisionOfMarkup) { "The vision is not a markup vision" } require(vision is VisionOfMarkup) { "The vision is not a markup vision" }
val div = document.createElement("div") val div = document.createElement("div")
val flavour = when (vision.format) {
COMMONMARK_FORMAT -> CommonMarkFlavourDescriptor()
GFM_FORMAT -> GFMFlavourDescriptor()
//TODO add new formats via plugins
else-> error("Format ${vision.format} not recognized")
}
vision.useProperty(VisionOfMarkup::content) {
div.clear()
div.append {
markdown(flavour) { vision.content ?: "" }
}
}
element.append(div) element.append(div)
TODO()
} }
public companion object : PluginFactory<MarkupPlugin> { public companion object : PluginFactory<MarkupPlugin> {

View File

@ -5,7 +5,6 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.scheme import space.kscience.dataforge.meta.descriptors.scheme
import space.kscience.dataforge.meta.descriptors.value import space.kscience.dataforge.meta.descriptors.value
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ValueType
import space.kscience.visionforge.hide import space.kscience.visionforge.hide
import space.kscience.visionforge.widgetType import space.kscience.visionforge.widgetType
@ -21,20 +20,21 @@ public class Clipping : Scheme() {
attributes["min"] = 0.0 attributes["min"] = 0.0
attributes["max"] = 1.0 attributes["max"] = 1.0
attributes["step"] = 0.01 attributes["step"] = 0.01
default(1.0)
} }
value(Clipping::y) { value(Clipping::y) {
widgetType = "range" widgetType = "range"
attributes["min"] = 0.0 attributes["min"] = 0.0
attributes["max"] = 1.0 attributes["max"] = 1.0
attributes["step"] = 0.01 attributes["step"] = 0.01
default(1.0)
} }
value(Clipping::z) { value(Clipping::z) {
widgetType = "range" widgetType = "range"
attributes["min"] = 0.0 attributes["min"] = 0.0
attributes["max"] = 1.0 attributes["max"] = 1.0
attributes["step"] = 0.01 attributes["step"] = 0.01
default(1.0)
} }
} }
} }

View File

@ -168,16 +168,13 @@ public class ThreeCanvas(
} }
//Clipping planes //Clipping planes
options.meta.onChange(this@ThreeCanvas) { name-> options.useProperty(Canvas3DOptions::clipping){clipping ->
if (name.startsWith(Canvas3DOptions::clipping.name.asName())) {
val clipping = options.clipping
if (!clipping.meta.isEmpty()) { if (!clipping.meta.isEmpty()) {
renderer.localClippingEnabled = true renderer.localClippingEnabled = true
boundingBox?.let { boundingBox -> boundingBox?.let { boundingBox ->
val xClippingPlane = clipping.x?.let { val xClippingPlane = clipping.x?.let {
val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it
Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue) Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue)
} }
val yClippingPlane = clipping.y?.let { val yClippingPlane = clipping.y?.let {
val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it
@ -194,7 +191,9 @@ public class ThreeCanvas(
} else { } else {
renderer.localClippingEnabled = false renderer.localClippingEnabled = false
} }
} else if (name.startsWith(Canvas3DOptions::size.name.asName())) { }
options.useProperty(Canvas3DOptions::size){
canvas.style.apply { canvas.style.apply {
minWidth = "${options.size.minWith.toInt()}px" minWidth = "${options.size.minWith.toInt()}px"
maxWidth = "${options.size.maxWith.toInt()}px" maxWidth = "${options.size.maxWith.toInt()}px"
@ -204,7 +203,6 @@ public class ThreeCanvas(
} }
} }
}
/** /**
* Resolve full name of the object relative to the global root * Resolve full name of the object relative to the global root