Yet another css overhaul.

This commit is contained in:
Alexander Nozik 2021-06-09 11:48:03 +03:00
parent dfc0ffda38
commit 91e7e8573e
11 changed files with 131 additions and 172 deletions

View File

@ -20,7 +20,7 @@ allprojects {
}
group = "space.kscience"
version = "0.2.0-dev-18"
version = "0.2.0-dev-19"
}
subprojects {

View File

@ -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<GDMLAppProps>("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<GDMLAppProps>("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<GDMLAppProps>("GDMLApp") { props ->
}
}
}
nameCrumbs(selected, "World", onSelect)
ringThreeControls(options, vision, selected, onSelect)
}
}
}

View File

@ -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<String, Any>) {
val playgroundContext = Context {
plugin(ThreeWithControls)
plugin(ThreeWithControlsPlugin)
plugin(VisionClient)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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<ValueChooserProps> =
flexRow {
styledInput(type = InputType.checkBox) {
css{
padding(0.px)
margin(0.px)
}
attrs {
defaultChecked = rangeDisabled.not()
onChangeFunction = handleDisable

View File

@ -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<ThreeCanvasProps> = 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<ThreeCanvasProps> = functio
styledDiv {
css {
flexGrow = 1.0
flexBasis = FlexBasis.fill
maxWidth = 100.vw
maxHeight = 100.vh
display = Display.block
bottom = 0.px
}
ref = elementRef
}

View File

@ -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<String, RBuilder.() -> 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)
}
}
}
}
}

View File

@ -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<ThreeWithControls> {
public companion object : PluginFactory<ThreeWithControlsPlugin> {
override val tag: PluginTag = PluginTag("vision.threejs.withControls", PluginTag.DATAFORGE_GROUP)
override val type: KClass<ThreeWithControls> = ThreeWithControls::class
override fun invoke(meta: Meta, context: Context): ThreeWithControls = ThreeWithControls()
override val type: KClass<ThreeWithControlsPlugin> = ThreeWithControlsPlugin::class
override fun invoke(meta: Meta, context: Context): ThreeWithControlsPlugin = ThreeWithControlsPlugin()
}
}

View File

@ -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>(::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<Number> 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())

View File

@ -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"
}
}
}
}
/**