Finish ThreeCancas encapsulation

This commit is contained in:
Alexander Nozik 2021-06-06 20:57:39 +03:00
parent 359eb05f83
commit 288307eaa8
17 changed files with 122 additions and 143 deletions

View File

@ -11,7 +11,6 @@ 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.Vision
import space.kscience.visionforge.bootstrap.gridRow
import space.kscience.visionforge.bootstrap.nameCrumbs
import space.kscience.visionforge.gdml.toVision
@ -21,27 +20,31 @@ import space.kscience.visionforge.ring.ringThreeControls
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import styled.css
import styled.styledDiv
external interface GDMLAppProps : RProps {
var context: Context
var rootVision: Vision?
var vision: Solid?
var selected: Name?
}
@JsExport
val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
var selected by useState { props.selected }
var canvas: ThreeCanvas? by useState { null }
var vision: Vision? by useState { props.rootVision }
var vision: Solid? by useState { props.vision }
val onSelect: (Name?) -> Unit = {
selected = it
}
val visionManager = useMemo({ props.context.fetch(Solids).visionManager }, arrayOf(props.context))
val options = useMemo {
Canvas3DOptions.invoke {
this.onSelect = onSelect
}
}
val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager }
fun loadData(name: String, data: String) {
val parsedVision = when {
@ -56,7 +59,7 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
}
}
vision = parsedVision
vision = parsedVision as? Solid ?: error("Parsed vision is not a solid")
}
gridRow {
@ -78,14 +81,9 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.obj = vision as? Solid
this.obj = vision
this.selected = selected
this.options = Canvas3DOptions.invoke {
this.onSelect = onSelect
}
this.canvasCallback = {
canvas = it
}
this.options = options
}
}
@ -111,9 +109,7 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
}
}
}
canvas?.let {
ringThreeControls(it, selected, onSelect)
}
ringThreeControls(options, props.vision, selected, onSelect)
}
}
}

View File

@ -29,7 +29,7 @@ private class GDMLDemoApp : Application {
//println(context.plugins.fetch(VisionManager).encodeToString(vision))
attrs {
this.context = context
this.rootVision = vision
this.vision = vision
}
}
}

View File

@ -6,11 +6,8 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.css.*
import kotlinx.html.js.onClickFunction
import react.RProps
import react.child
import react.*
import react.dom.*
import react.functionalComponent
import react.useState
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
@ -26,7 +23,6 @@ import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.objectTree
import space.kscience.visionforge.solid.specifications.Camera
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import styled.css
import styled.styledDiv
import kotlin.math.PI
@ -49,12 +45,17 @@ private val canvasConfig = Canvas3DOptions {
@JsExport
val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
var selected by useState { props.selected }
var canvas: ThreeCanvas? by useState { null }
val onSelect: (Name?) -> Unit = {
selected = it
}
val options = useMemo {
Canvas3DOptions.invoke {
this.onSelect = onSelect
}
}
val root = props.model.root
gridRow {
@ -95,9 +96,6 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
this.options = canvasConfig.apply {
this.onSelect = onSelect
}
this.canvasCallback = {
canvas = it
}
}
}
}
@ -113,11 +111,10 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
flex(0.0, 1.0, FlexBasis.zero)
}
//settings
canvas?.let {
card("Canvas configuration") {
canvasControls(it)
}
card("Canvas configuration") {
canvasControls(options, root)
}
card("Events") {
button {
+"Next"

View File

@ -19,7 +19,6 @@ import space.kscience.visionforge.VisionLayout
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.solid.three.configure
class ThreeDemoGrid(element: Element) : VisionLayout<Solid> {
private lateinit var navigationElement: HTMLElement
@ -71,7 +70,7 @@ class ThreeDemoGrid(element: Element) : VisionLayout<Solid> {
}
}
val element = document.getElementById("output-$name") ?: error("Element not found")
three.getOrCreateCanvas(element).also { it.configure(canvasOptions) }
three.getOrCreateCanvas(element, canvasOptions)
}.render(vision)
}
}

View File

@ -13,12 +13,11 @@ import react.*
import react.dom.attrs
import react.dom.button
import space.kscience.dataforge.meta.withDefault
import space.kscience.visionforge.Vision
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import styled.css
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
@ -30,37 +29,33 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl
fileSaver.saveAs(blob, fileName)
}
public fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement {
public fun RBuilder.canvasControls(canvasOptions: Canvas3DOptions, vision: Vision?): ReactElement {
return child(CanvasControls) {
attrs {
this.canvas = canvas
this.canvasOptions = canvasOptions
this.vision = vision
}
}
}
public external interface CanvasControlsProps : RProps {
public var canvas: ThreeCanvas
public var canvasOptions: Canvas3DOptions
public var vision: Vision?
}
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
val visionManager = useMemo(
{ props.canvas.three.solids.visionManager },
arrayOf(props.canvas)
)
flexColumn {
flexRow {
css{
css {
border(1.px, BorderStyle.solid, Color.blue)
padding(4.px)
}
button {
+"Export"
attrs {
onClickFunction = {
val json = (props.canvas.content as? SolidGroup)?.let { group ->
visionManager.encodeToString(group)
}
if (json != null) {
props.vision?.manager?.let { manager ->
button {
+"Export"
attrs {
onClickFunction = {
val json = manager.encodeToString(props.vision!!)
saveData(it, "object.json", "text/json") {
json
}
@ -70,8 +65,8 @@ public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functional
}
}
propertyEditor(
ownProperties = props.canvas.options,
allProperties = props.canvas.options.withDefault(Canvas3DOptions.descriptor.defaultMeta),
ownProperties = props.canvasOptions,
allProperties = props.canvasOptions.withDefault(Canvas3DOptions.descriptor.defaultMeta),
descriptor = Canvas3DOptions.descriptor,
expanded = false
)

View File

@ -9,23 +9,23 @@ import space.kscience.dataforge.names.isEmpty
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.react.objectTree
import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import styled.css
import styled.styledDiv
public external interface ThreeControlsProps : RProps {
public var canvas: ThreeCanvas
public var canvasOptions: Canvas3DOptions
public var vision: Vision?
public var selected: Name?
public var onSelect: (Name) -> Unit
}
@JsExport
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props ->
val vision = props.canvas.content
tabPane(if (props.selected != null) "Properties" else null) {
tab("Canvas") {
card("Canvas configuration") {
canvasControls(props.canvas)
canvasControls(props.canvasOptions, props.vision)
}
}
tab("Tree") {
@ -38,7 +38,7 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
css {
flex(1.0, 1.0, FlexBasis.inherit)
}
props.canvas.content?.let {
props.vision?.let {
objectTree(it, props.selected, props.onSelect)
}
}
@ -47,8 +47,8 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
props.selected.let { selected ->
val selectedObject: Vision? = when {
selected == null -> null
selected.isEmpty() -> vision
else -> (vision as? VisionGroup)?.get(selected)
selected.isEmpty() -> props.vision
else -> (props.vision as? VisionGroup)?.get(selected)
}
if (selectedObject != null) {
visionPropertyEditor(selectedObject, key = selected)
@ -62,13 +62,15 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
}
public fun RBuilder.threeControls(
canvas: ThreeCanvas,
canvasOptions: Canvas3DOptions,
vision: Vision?,
selected: Name?,
onSelect: (Name) -> Unit = {},
builder: TabBuilder.() -> Unit = {},
): ReactElement = child(ThreeControls) {
attrs {
this.canvas = canvas
this.canvasOptions = canvasOptions
this.vision = vision
this.selected = selected
this.onSelect = onSelect
}

View File

@ -14,16 +14,14 @@ import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.solid.three.configure
import styled.css
import styled.styledDiv
public external interface ThreeCanvasProps : RProps {
public var context: Context
public var options: Canvas3DOptions
public var obj: Solid?
public var options: Canvas3DOptions?
public var selected: Name?
public var canvasCallback: ((ThreeCanvas?) -> Unit)?
}
public external interface ThreeCanvasState : RState {
@ -42,20 +40,13 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
useEffect(listOf(props.obj, props.options, elementRef)) {
if (canvas == null) {
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
val newCanvas: ThreeCanvas = three.getOrCreateCanvas(element)
props.options?.let {
newCanvas.configure(it)
}
props.canvasCallback?.invoke(newCanvas)
canvas = newCanvas
canvas = three.getOrCreateCanvas(element, props.options)
}
}
useEffect(listOf(canvas, props.obj)) {
props.obj?.let { obj ->
if (canvas?.content != obj) {
canvas?.render(obj)
}
canvas?.render(obj)
}
}

View File

@ -1,10 +1,7 @@
package space.kscience.visionforge.ring
import kotlinx.css.*
import react.RProps
import react.child
import react.functionalComponent
import react.useState
import react.*
import ringui.grid.ringCol
import ringui.grid.ringGrid
import ringui.grid.ringRow
@ -14,25 +11,27 @@ import space.kscience.visionforge.Vision
import space.kscience.visionforge.react.ThreeCanvasComponent
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import styled.css
import styled.styledDiv
public external interface GdmlViewProps : RProps {
public external interface ThreeWithControlsProps : RProps {
public var context: Context
public var rootVision: Vision?
public var vision: Vision?
public var selected: Name?
}
@JsExport
public val ThreeViewWithControls: (props: GdmlViewProps) -> dynamic =
functionalComponent<GdmlViewProps>("ThreeViewWithControls") { props ->
public val ThreeViewWithControls: (props: ThreeWithControlsProps) -> dynamic =
functionalComponent("ThreeViewWithControls") { props ->
var selected by useState { props.selected }
var canvas: ThreeCanvas? by useState { null }
val onSelect: (Name?) -> Unit = {
selected = it
}
val options = useMemo {
Canvas3DOptions.invoke {
this.onSelect = onSelect
}
}
styledDiv {
css {
@ -50,14 +49,9 @@ public val ThreeViewWithControls: (props: GdmlViewProps) -> dynamic =
child(ThreeCanvasComponent) {
attrs {
this.context = props.context
this.obj = props.rootVision as? Solid
this.obj = props.vision as? Solid
this.selected = selected
this.options = Canvas3DOptions.invoke {
this.onSelect = onSelect
}
this.canvasCallback = {
canvas = it
}
this.options = options
}
}
@ -76,9 +70,7 @@ public val ThreeViewWithControls: (props: GdmlViewProps) -> dynamic =
height = 100.pct
overflowY = Overflow.auto
}
canvas?.let {
ringThreeControls(it, selected, onSelect)
}
ringThreeControls(options, props.vision, selected, onSelect)
}
}
}

View File

@ -28,7 +28,7 @@ public class ThreeWithControls : AbstractPlugin(), ElementVisionRenderer {
child(ThreeViewWithControls) {
attrs {
this.context = this@ThreeWithControls.context
this.rootVision = vision
this.vision = vision
}
}
}

View File

@ -18,13 +18,12 @@ 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.encodeToString
import space.kscience.visionforge.react.flexColumn
import space.kscience.visionforge.react.flexRow
import space.kscience.visionforge.react.objectTree
import space.kscience.visionforge.react.propertyEditor
import space.kscience.visionforge.solid.SolidGroup
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import styled.css
import styled.styledDiv
@ -37,48 +36,45 @@ internal fun saveData(event: Event, fileName: String, mimeType: String = "text/p
fileSaver.saveAs(blob, fileName)
}
internal fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement {
internal fun RBuilder.canvasControls(options: Canvas3DOptions, vision: Vision?): ReactElement {
return child(CanvasControls) {
attrs {
this.canvas = canvas
this.options = options
this.vision = vision
}
}
}
internal external interface CanvasControlsProps : RProps {
public var canvas: ThreeCanvas
public var options: Canvas3DOptions
public var vision: Vision?
}
internal val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
val visionManager = useMemo(
{ props.canvas.three.solids.visionManager },
arrayOf(props.canvas)
)
flexColumn {
flexRow {
css {
border(1.px, BorderStyle.solid, Color.blue)
padding(4.px)
}
button {
+"Export"
attrs {
onClickFunction = {
val json = (props.canvas.content as? SolidGroup)?.let { group ->
visionManager.encodeToString(group)
}
if (json != null) {
props.vision?.let { vision ->
button {
+"Export"
attrs {
onClickFunction = {
val json = vision.encodeToString()
saveData(it, "object.json", "text/json") {
json
}
}
}
}
}
}
propertyEditor(
ownProperties = props.canvas.options,
allProperties = props.canvas.options.withDefault(Canvas3DOptions.descriptor.defaultMeta),
ownProperties = props.options,
allProperties = props.options.withDefault(Canvas3DOptions.descriptor.defaultMeta),
descriptor = Canvas3DOptions.descriptor,
expanded = false
)
@ -88,18 +84,18 @@ internal val CanvasControls: FunctionalComponent<CanvasControlsProps> = function
public external interface ThreeControlsProps : RProps {
public var canvas: ThreeCanvas
public var canvasOptions: Canvas3DOptions
public var vision: Vision?
public var selected: Name?
public var onSelect: (Name) -> Unit
}
@JsExport
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props ->
val vision = props.canvas.content
ringSmartTabs(if (props.selected != null) "Properties" else null) {
ringTab("Canvas") {
ringIsland("Canvas configuration") {
canvasControls(props.canvas)
canvasControls(props.canvasOptions, props.vision)
}
}
ringTab("Tree") {
@ -113,7 +109,7 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
css {
flex(1.0, 1.0, FlexBasis.inherit)
}
props.canvas.content?.let {
props.vision?.let {
objectTree(it, props.selected, props.onSelect)
}
}
@ -123,8 +119,8 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
props.selected.let { selected ->
val selectedObject: Vision? = when {
selected == null -> null
selected.isEmpty() -> vision
else -> (vision as? VisionGroup)?.get(selected)
selected.isEmpty() -> props.vision
else -> (props.vision as? VisionGroup)?.get(selected)
}
if (selectedObject != null) {
ringPropertyEditor(selectedObject, key = selected)
@ -136,13 +132,15 @@ public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalCo
}
public fun RBuilder.ringThreeControls(
canvas: ThreeCanvas,
canvasOptions: Canvas3DOptions,
vision: Vision?,
selected: Name?,
onSelect: (Name) -> Unit = {},
builder: RBuilder.() -> Unit = {},
): ReactElement = child(ThreeControls) {
attrs {
this.canvas = canvas
this.canvasOptions = canvasOptions
this.vision = vision
this.selected = selected
this.onSelect = onSelect
}

View File

@ -27,6 +27,9 @@ public interface Vision : Described, CoroutineScope {
*/
public var parent: VisionGroup?
/**
* Owner [VisionManager]. Used to define coroutine scope a serialization
*/
public val manager: VisionManager? get() = parent?.manager
override val coroutineContext: CoroutineContext
@ -69,7 +72,7 @@ public interface Vision : Described, CoroutineScope {
/**
* Notify all listeners that a property has been changed and should be invalidated
*/
public fun invalidateProperty(propertyName: Name): Unit
public fun invalidateProperty(propertyName: Name)
/**
* Update this vision using a dif represented by [VisionChange].

View File

@ -92,7 +92,6 @@ public open class VisionBase : Vision {
}
}
//TODO check memory consumption for the flow
@Transient
private val propertyInvalidationFlow: MutableSharedFlow<Name> = MutableSharedFlow()

View File

@ -64,9 +64,15 @@ private fun Vision.isolate(manager: VisionManager): Vision {
return manager.decodeFromJson(json)
}
/**
* @param void flag showing that this vision child should be removed
* @param vision a new value for vision content
* @param properties updated properties
* @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [void] flag to true.
*/
@Serializable
public data class VisionChange(
public val reset: Boolean = false,
public val void: Boolean = false,
public val vision: Vision? = null,
@Serializable(MetaSerializer::class) public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null,

View File

@ -134,7 +134,7 @@ public open class VisionGroupBase(
override fun update(change: VisionChange) {
change.children?.forEach { (name, change) ->
when {
change.reset -> set(name, null)
change.void -> set(name, null)
change.vision != null -> set(name, change.vision)
else -> get(name)?.update(change)
}

View File

@ -95,4 +95,7 @@ public abstract class VisionPlugin(meta: Meta = Meta.EMPTY) : AbstractPlugin(met
/**
* Fetch a [VisionManager] from this plugin or create a child plugin with a [VisionManager]
*/
public val Context.visionManager: VisionManager get() = fetch(VisionManager)
public val Context.visionManager: VisionManager get() = fetch(VisionManager)
public fun Vision.encodeToString(): String =
manager?.encodeToString(this) ?: error("VisionManager not defined in Vision")

View File

@ -39,9 +39,8 @@ import kotlin.math.sin
*/
public class ThreeCanvas(
public val three: ThreePlugin,
public val options: Canvas3DOptions = Canvas3DOptions()
) {
public val options = Canvas3DOptions()
private var boundingBox: Box3? = null
private var root: Object3D? = null
set(value) {
@ -52,9 +51,6 @@ public class ThreeCanvas(
private val raycaster = Raycaster()
private val mousePosition: Vector2 = Vector2()
public var content: Solid? = null
private set
private val scene: Scene = Scene().apply {
options.useProperty(Canvas3DOptions::axes, this) { axesConfig ->
getObjectByName(AXES_NAME)?.let { remove(it) }
@ -251,7 +247,6 @@ public class ThreeCanvas(
val object3D = three.buildObject3D(vision)
object3D.name = "@root"
scene.add(object3D)
content = vision
root = object3D
}
@ -315,9 +310,9 @@ public class ThreeCanvas(
}
}
public fun ThreeCanvas.configure(options: Canvas3DOptions) {
this.options.update(options.toMeta())
options.onChange(this) { name, _, newItem ->
options[name] = newItem
}
}
//public fun ThreeCanvas.configure(newOptions: Canvas3DOptions) {
// this.options.update(newOptions.toMeta())
// options.onChange(this) { name, _, newItem ->
// this.options[name] = newItem
// }
//}

View File

@ -116,9 +116,12 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
public fun getOrCreateCanvas(
element: Element,
): ThreeCanvas = canvasCache.getOrPut(element){ThreeCanvas(this).apply {
attach(element)
}}
options: Canvas3DOptions = Canvas3DOptions(),
): ThreeCanvas = canvasCache.getOrPut(element) {
ThreeCanvas(this, options).apply {
attach(element)
}
}
override fun content(target: String): Map<Name, Any> {
return when (target) {