From 91e7e8573edf6a44649210024b62d36cdf4a8f90 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 9 Jun 2021 11:48:03 +0300 Subject: [PATCH] Yet another css overhaul. --- build.gradle.kts | 2 +- .../visionforge/gdml/demo/GDMLAppComponent.kt | 70 ++--------- .../src/main/kotlin/JsPlaygroundApp.kt | 4 +- .../src/jsMain/kotlin/playgroundMain.kt | 4 +- .../src/jsMain/kotlin/gdmlJupyter.kt | 4 +- .../visionforge/react/RangeValueChooser.kt | 8 -- .../visionforge/react/ThreeCanvasComponent.kt | 12 +- .../ThreeViewWithControls.kt | 23 +++- ...Controls.kt => ThreeWithControlsPlugin.kt} | 10 +- .../solid/specifications/Canvas3DOptions.kt | 52 ++++---- .../visionforge/solid/three/ThreeCanvas.kt | 114 +++++++++--------- 11 files changed, 131 insertions(+), 172 deletions(-) rename ui/ring/src/main/kotlin/space.kscience.visionforge.ring/{ThreeWithControls.kt => ThreeWithControlsPlugin.kt} (81%) diff --git a/build.gradle.kts b/build.gradle.kts index 9fd660c8..4170628c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,7 +20,7 @@ allprojects { } group = "space.kscience" - version = "0.2.0-dev-18" + version = "0.2.0-dev-19" } subprojects { diff --git a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt index cf115e5e..d26a5ad8 100644 --- a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt +++ b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt @@ -1,29 +1,20 @@ package space.kscience.visionforge.gdml.demo import kotlinx.browser.window -import kotlinx.css.height -import kotlinx.css.vh import org.w3c.files.FileReader import org.w3c.files.get import react.* -import react.dom.h1 -import ringui.grid.ringCol -import ringui.grid.ringGrid -import ringui.grid.ringRow 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.bootstrap.nameCrumbs import space.kscience.visionforge.gdml.toVision -import space.kscience.visionforge.react.ThreeCanvasComponent -import space.kscience.visionforge.react.flexColumn -import space.kscience.visionforge.ring.ringThreeControls +import space.kscience.visionforge.ring.ThreeCanvasWithControls +import space.kscience.visionforge.ring.tab import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solids -import space.kscience.visionforge.solid.specifications.Canvas3DOptions -import styled.css +import styled.styledDiv external interface GDMLAppProps : RProps { var context: Context @@ -33,19 +24,7 @@ external interface GDMLAppProps : RProps { @JsExport val GDMLApp = functionalComponent("GDMLApp") { props -> - var selected by useState { props.selected } var vision: Solid? by useState { props.vision } - - val onSelect: (Name?) -> Unit = { - selected = it - } - - val options = useMemo { - Canvas3DOptions.invoke { - this.onSelect = onSelect - } - } - val visionManager = useMemo(props.context) { props.context.fetch(Solids).visionManager } fun loadData(name: String, data: String) { @@ -64,43 +43,16 @@ val GDMLApp = functionalComponent("GDMLApp") { props -> vision = parsedVision as? Solid ?: error("Parsed vision is not a solid") } - - ringGrid { - ringRow { - ringCol { - attrs { - lg = 9 - } - flexColumn { - css { - height = 100.vh - } - h1 { +"GDML/JSON loader demo" } - //canvas - - child(ThreeCanvasComponent) { - attrs { - this.context = props.context - this.solid = vision - this.selected = selected - this.options = options - } - } - } - - } - ringCol { - attrs { - lg = 3 - } - flexColumn { - css { - height = 100.vh - } + styledDiv { + child(ThreeCanvasWithControls) { + attrs { + this.context = props.context + this.solid = vision + this.selected = props.selected + tab("Load") { fileDrop("(drag file here)") { files -> val file = files?.get(0) if (file != null) { - FileReader().apply { onload = { val string = result as String @@ -110,8 +62,6 @@ val GDMLApp = functionalComponent("GDMLApp") { props -> } } } - nameCrumbs(selected, "World", onSelect) - ringThreeControls(options, vision, selected, onSelect) } } } diff --git a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt index 8fa66e74..b43ce062 100644 --- a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt +++ b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt @@ -11,7 +11,7 @@ import space.kscience.visionforge.Application import space.kscience.visionforge.VisionClient import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.ring.ThreeCanvasWithControls -import space.kscience.visionforge.ring.ThreeWithControls +import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.startApplication import styled.css import styled.styledDiv @@ -21,7 +21,7 @@ private class JsPlaygroundApp : Application { override fun start(state: Map) { val playgroundContext = Context { - plugin(ThreeWithControls) + plugin(ThreeWithControlsPlugin) plugin(VisionClient) } diff --git a/demo/playground/src/jsMain/kotlin/playgroundMain.kt b/demo/playground/src/jsMain/kotlin/playgroundMain.kt index 42cb97e2..71431841 100644 --- a/demo/playground/src/jsMain/kotlin/playgroundMain.kt +++ b/demo/playground/src/jsMain/kotlin/playgroundMain.kt @@ -1,10 +1,10 @@ import space.kscience.dataforge.misc.DFExperimental import space.kscience.visionforge.plotly.PlotlyPlugin -import space.kscience.visionforge.ring.ThreeWithControls +import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.runVisionClient @DFExperimental fun main() = runVisionClient { plugin(PlotlyPlugin) - plugin(ThreeWithControls) + plugin(ThreeWithControlsPlugin) } \ No newline at end of file diff --git a/jupyter/visionforge-gdml-jupyter/src/jsMain/kotlin/gdmlJupyter.kt b/jupyter/visionforge-gdml-jupyter/src/jsMain/kotlin/gdmlJupyter.kt index 3356c20d..fef274a2 100644 --- a/jupyter/visionforge-gdml-jupyter/src/jsMain/kotlin/gdmlJupyter.kt +++ b/jupyter/visionforge-gdml-jupyter/src/jsMain/kotlin/gdmlJupyter.kt @@ -1,12 +1,12 @@ package space.kscience.visionforge.gdml.jupyter import space.kscience.dataforge.misc.DFExperimental -import space.kscience.visionforge.ring.ThreeWithControls +import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.runVisionClient @DFExperimental @JsExport fun main(): Unit = runVisionClient { - plugin(ThreeWithControls) + plugin(ThreeWithControlsPlugin) } diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt index 035ca3e7..abeeaa86 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/RangeValueChooser.kt @@ -1,8 +1,5 @@ package space.kscience.visionforge.react -import kotlinx.css.margin -import kotlinx.css.padding -import kotlinx.css.px import kotlinx.html.InputType import kotlinx.html.js.onChangeFunction import org.w3c.dom.HTMLInputElement @@ -15,7 +12,6 @@ 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 @@ -42,10 +38,6 @@ public val RangeValueChooser: FunctionalComponent = flexRow { styledInput(type = InputType.checkBox) { - css{ - padding(0.px) - margin(0.px) - } attrs { defaultChecked = rangeDisabled.not() onChangeFunction = handleDisable diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt index c2e7e8d6..33b5754f 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/ThreeCanvasComponent.kt @@ -1,8 +1,6 @@ package space.kscience.visionforge.react -import kotlinx.css.FlexBasis -import kotlinx.css.flexBasis -import kotlinx.css.flexGrow +import kotlinx.css.* import org.w3c.dom.Element import org.w3c.dom.HTMLElement import react.* @@ -39,7 +37,7 @@ public val ThreeCanvasComponent: FunctionalComponent = functio useEffect(listOf(props.solid, props.options, elementRef)) { if (canvas == null) { val element = elementRef.current as? HTMLElement ?: error("Canvas element not found") - canvas = three.getOrCreateCanvas(element, props.options ?: Canvas3DOptions()) + canvas = ThreeCanvas(three, element, props.options ?: Canvas3DOptions()) } } @@ -55,8 +53,10 @@ public val ThreeCanvasComponent: FunctionalComponent = functio styledDiv { css { - flexGrow = 1.0 - flexBasis = FlexBasis.fill + maxWidth = 100.vw + maxHeight = 100.vh + display = Display.block + bottom = 0.px } ref = elementRef } diff --git a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt index 5336de06..60734c63 100644 --- a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt +++ b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeViewWithControls.kt @@ -2,9 +2,11 @@ package space.kscience.visionforge.ring import kotlinx.css.* import react.* +import ringui.grid.RowPosition import ringui.grid.ringCol import ringui.grid.ringGrid import ringui.grid.ringRow +import ringui.tabs.ringTab import space.kscience.dataforge.context.Context import space.kscience.dataforge.names.Name import space.kscience.visionforge.react.ThreeCanvasComponent @@ -17,6 +19,11 @@ public external interface ThreeCanvasWithControlsProps : RProps { public var context: Context public var solid: Solid? public var selected: Name? + public var additionalTabs: Map Unit>? +} + +public fun ThreeCanvasWithControlsProps.tab(title: String, block: RBuilder.()->Unit){ + additionalTabs = (additionalTabs?: emptyMap()) + (title to block) } @JsExport @@ -35,11 +42,15 @@ public val ThreeCanvasWithControls: (props: ThreeCanvasWithControlsProps) -> dyn styledDiv { css { height = 100.pct + width = 100.pct maxHeight = 100.vh maxWidth = 100.vw } ringGrid { ringRow { + attrs { + start = RowPosition.sm + } ringCol { attrs { xs = 12 @@ -50,12 +61,11 @@ public val ThreeCanvasWithControls: (props: ThreeCanvasWithControlsProps) -> dyn child(ThreeCanvasComponent) { attrs { this.context = props.context - this.solid = props.solid as? Solid + this.solid = props.solid this.selected = selected this.options = options } } - } ringCol { attrs { @@ -67,11 +77,14 @@ public val ThreeCanvasWithControls: (props: ThreeCanvasWithControlsProps) -> dyn styledDiv { css { padding(top = 4.px) + width = 100.pct //border(1.px, BorderStyle.solid, Color.lightGray) - height = 100.pct - overflowY = Overflow.auto } - ringThreeControls(options, props.solid, selected, onSelect) + ringThreeControls(options, props.solid, selected, onSelect) { + props.additionalTabs?.forEach { (title, builder) -> + ringTab(title, title, builder) + } + } } } } diff --git a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControls.kt b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt similarity index 81% rename from ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControls.kt rename to ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt index 52586fc5..c4cf64a7 100644 --- a/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControls.kt +++ b/ui/ring/src/main/kotlin/space.kscience.visionforge.ring/ThreeWithControlsPlugin.kt @@ -15,7 +15,7 @@ import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.three.ThreePlugin import kotlin.reflect.KClass -public class ThreeWithControls : AbstractPlugin(), ElementVisionRenderer { +public class ThreeWithControlsPlugin : AbstractPlugin(), ElementVisionRenderer { public val three: ThreePlugin by require(ThreePlugin) override val tag: PluginTag get() = Companion.tag @@ -27,7 +27,7 @@ public class ThreeWithControls : AbstractPlugin(), ElementVisionRenderer { react.dom.render(element) { child(ThreeCanvasWithControls) { attrs { - this.context = this@ThreeWithControls.context + this.context = this@ThreeWithControlsPlugin.context this.solid = vision as? Solid } } @@ -41,9 +41,9 @@ public class ThreeWithControls : AbstractPlugin(), ElementVisionRenderer { } } - public companion object : PluginFactory { + public companion object : PluginFactory { override val tag: PluginTag = PluginTag("vision.threejs.withControls", PluginTag.DATAFORGE_GROUP) - override val type: KClass = ThreeWithControls::class - override fun invoke(meta: Meta, context: Context): ThreeWithControls = ThreeWithControls() + override val type: KClass = ThreeWithControlsPlugin::class + override fun invoke(meta: Meta, context: Context): ThreeWithControlsPlugin = ThreeWithControlsPlugin() } } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt index 1e23d1ad..11dd5800 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Canvas3DOptions.kt @@ -45,12 +45,7 @@ public class Clipping : Scheme() { } } -public class Canvas3DOptions : Scheme() { - public var axes: Axes by spec(Axes) - public var light: Light by spec(Light) - public var camera: Camera by spec(Camera) - public var controls: Controls by spec(Controls) - +public class CanvasSize : Scheme() { public var minSize: Int by int(400) public var minWith: Number by number { minSize } public var minHeight: Number by number { minSize } @@ -59,6 +54,26 @@ public class Canvas3DOptions : Scheme() { public var maxWith: Number by number { maxSize } public var maxHeight: Number by number { maxSize } + public companion object : SchemeSpec(::CanvasSize) { + override val descriptor: NodeDescriptor = NodeDescriptor { + value(CanvasSize::minSize) + value(CanvasSize::minWith) + value(CanvasSize::minHeight) + value(CanvasSize::maxSize) + value(CanvasSize::maxWith) + value(CanvasSize::maxHeight) + } + } +} + +public class Canvas3DOptions : Scheme() { + public var axes: Axes by spec(Axes) + public var light: Light by spec(Light) + public var camera: Camera by spec(Camera) + public var controls: Controls by spec(Controls) + + public var size: CanvasSize by spec(CanvasSize) + public var layers: List by numberList(0) public var clipping: Clipping by spec(Clipping) @@ -71,30 +86,19 @@ public class Canvas3DOptions : Scheme() { NodeDescriptor { scheme(Canvas3DOptions::axes, Axes) scheme(Canvas3DOptions::light, Light) + scheme(Canvas3DOptions::camera, Camera) { hide() } + scheme(Canvas3DOptions::controls, Controls) { hide() } - value(Canvas3DOptions::minSize) { - hide() - } - value(Canvas3DOptions::minWith) { - hide() - } - value(Canvas3DOptions::minHeight) { - hide() - } - value(Canvas3DOptions::maxSize) { - hide() - } - value(Canvas3DOptions::maxWith) { - hide() - } - value(Canvas3DOptions::maxHeight) { + + scheme(Canvas3DOptions::size, CanvasSize) { hide() } + value(Canvas3DOptions::layers) { type(ValueType.NUMBER) multiple = true @@ -108,8 +112,8 @@ public class Canvas3DOptions : Scheme() { } } -public fun Canvas3DOptions.computeWidth(external: Number): Int = +public fun CanvasSize.computeWidth(external: Number): Int = (external.toInt()).coerceIn(minWith.toInt()..maxWith.toInt()) -public fun Canvas3DOptions.computeHeight(external: Number): Int = +public fun CanvasSize.computeHeight(external: Number): Int = (external.toInt()).coerceIn(minHeight.toInt()..maxHeight.toInt()) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index 525bf402..f61aaebf 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -20,6 +20,7 @@ import info.laht.threekt.objects.Mesh import info.laht.threekt.scenes.Scene import org.w3c.dom.Element import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLElement import org.w3c.dom.Node import org.w3c.dom.events.MouseEvent import space.kscience.dataforge.context.info @@ -40,10 +41,8 @@ import kotlin.math.sin public class ThreeCanvas( public val three: ThreePlugin, public val element: Element, - public val options: Canvas3DOptions = Canvas3DOptions() + public val options: Canvas3DOptions = Canvas3DOptions(), ) { - - private var boundingBox: Box3? = null private var root: Object3D? = null set(value) { @@ -102,40 +101,10 @@ public class ThreeCanvas( antialias = true }.apply { setClearColor(Colors.skyblue, 1) - //Clipping planes - options.onChange(this@ThreeCanvas) { name, _, _ -> - if (name.startsWith(Canvas3DOptions::clipping.name.asName())) { - localClippingEnabled = true - val clipping = options.clipping - if(!clipping.isEmpty()) { - boundingBox?.let { boundingBox -> - val xClippingPlane = clipping.x?.let { - val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it - Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue) - - } - val yClippingPlane = clipping.y?.let { - val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it - Plane(Vector3(0.0, -1.0, 0.0), absoluteValue) - } - - val zClippingPlane = clipping.z?.let { - val absoluteValue = boundingBox.min.z + (boundingBox.max.z - boundingBox.min.z) * it - Plane(Vector3(0.0, 0.0, -1.0), absoluteValue) - } - clippingPlanes = listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray() - } - } - } else { - localClippingEnabled = false - } - } } private val canvas = (renderer.domElement as HTMLCanvasElement).apply { className += "three-canvas" - width = 600 - height = 600 style.apply { width = "100%" height = "100%" @@ -143,35 +112,29 @@ public class ThreeCanvas( } } + /** + * Force camera aspect ration and renderer size recalculation + */ + private fun updateSize() { + val width = element.clientWidth + val height = element.clientHeight + canvas.width = width + canvas.height = height + renderer.setSize(width, height, false) + camera.aspect = width.toDouble() / height.toDouble() + camera.updateProjectionMatrix() + } + + + /** + * Attach canvas to given [HTMLElement] + */ init { check(element.getElementsByClassName("three-canvas").length == 0) { "Three canvas already created in this element" } element.appendChild(canvas) updateSize() - } - - /** - * Force camera aspect ration and renderer size recalculation - */ - public fun updateSize() { - val width = canvas.clientWidth - val height = canvas.clientHeight - canvas.style.apply { - minWidth = "${options.minWith.toInt()}px" - maxWidth = "${options.maxWith.toInt()}px" - minHeight = "${options.minHeight.toInt()}px" - maxHeight = "${options.maxHeight.toInt()}px" - } - renderer.setSize(width, height, false) - camera.aspect = width.toDouble() / height.toDouble() - camera.updateProjectionMatrix() - } - - /** - * Attach canvas to given [HTMLElement] - */ - init { canvas.addEventListener("pointerdown", { val picked = pick() options.onSelect?.invoke(picked?.fullName()) @@ -186,7 +149,7 @@ public class ThreeCanvas( } }, false) - canvas.onresize = { + (element as? HTMLElement)?.onresize = { updateSize() } @@ -203,6 +166,43 @@ public class ThreeCanvas( renderer.render(scene, camera) } + + //Clipping planes + options.onChange(this@ThreeCanvas) { name, _, _ -> + if (name.startsWith(Canvas3DOptions::clipping.name.asName())) { + val clipping = options.clipping + if (!clipping.isEmpty()) { + renderer.localClippingEnabled = true + boundingBox?.let { boundingBox -> + val xClippingPlane = clipping.x?.let { + val absoluteValue = boundingBox.min.x + (boundingBox.max.x - boundingBox.min.x) * it + Plane(Vector3(-1.0, 0.0, 0.0), absoluteValue) + + } + val yClippingPlane = clipping.y?.let { + val absoluteValue = boundingBox.min.y + (boundingBox.max.y - boundingBox.min.y) * it + Plane(Vector3(0.0, -1.0, 0.0), absoluteValue) + } + + val zClippingPlane = clipping.z?.let { + val absoluteValue = boundingBox.min.z + (boundingBox.max.z - boundingBox.min.z) * it + Plane(Vector3(0.0, 0.0, -1.0), absoluteValue) + } + renderer.clippingPlanes = listOfNotNull(xClippingPlane, yClippingPlane, zClippingPlane).toTypedArray() + } + } else { + renderer.localClippingEnabled = false + } + } else if (name.startsWith(Canvas3DOptions::size.name.asName())) { + canvas.style.apply { + minWidth = "${options.size.minWith.toInt()}px" + maxWidth = "${options.size.maxWith.toInt()}px" + minHeight = "${options.size.minHeight.toInt()}px" + maxHeight = "${options.size.maxHeight.toInt()}px" + } + } + + } } /**