forked from kscience/visionforge
controls redesign
This commit is contained in:
parent
5b8d298ac6
commit
fdc221dfa1
@ -2,29 +2,31 @@ package hep.dataforge.vision.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.bootstrap.*
|
||||
import hep.dataforge.vision.bootstrap.card
|
||||
import hep.dataforge.vision.bootstrap.nameCrumbs
|
||||
import hep.dataforge.vision.bootstrap.threeControls
|
||||
import hep.dataforge.vision.gdml.toVision
|
||||
import hep.dataforge.vision.react.objectTree
|
||||
import hep.dataforge.vision.react.ThreeCanvasComponent
|
||||
import hep.dataforge.vision.react.flexColumn
|
||||
import hep.dataforge.vision.react.flexRow
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.css.FlexBasis
|
||||
import kotlinx.css.Overflow
|
||||
import kotlinx.css.flex
|
||||
import kotlinx.css.overflow
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.border
|
||||
import kscience.gdml.GDML
|
||||
import kscience.gdml.decodeFromString
|
||||
import org.w3c.files.FileReader
|
||||
import org.w3c.files.get
|
||||
import react.*
|
||||
import react.RProps
|
||||
import react.child
|
||||
import react.dom.h1
|
||||
import react.functionalComponent
|
||||
import react.useState
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
external interface GDMLAppProps : RProps {
|
||||
var context: Context
|
||||
@ -46,7 +48,7 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
||||
var canvas: ThreeCanvas? by useState { null }
|
||||
var vision: Vision? by useState { props.rootObject }
|
||||
|
||||
val select: (Name?) -> Unit = {
|
||||
val onSelect: (Name?) -> Unit = {
|
||||
selected = it
|
||||
}
|
||||
|
||||
@ -67,20 +69,56 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
||||
vision = parsedVision
|
||||
}
|
||||
|
||||
gridColumn {
|
||||
flexColumn {
|
||||
css {
|
||||
height = 100.pct
|
||||
width = 100.pct
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
+"page-header"
|
||||
+"justify-content-center"
|
||||
}
|
||||
h1 { +"GDML/JSON loader demo" }
|
||||
}
|
||||
flexRow {
|
||||
css {
|
||||
flex(1.0, 1.0, FlexBasis.auto)
|
||||
flexWrap = FlexWrap.wrap
|
||||
}
|
||||
|
||||
flexColumn {
|
||||
css {
|
||||
flex(1.0, 1.0, FlexBasis.auto)
|
||||
margin(10.px)
|
||||
}
|
||||
nameCrumbs(selected, "World", onSelect)
|
||||
|
||||
//canvas
|
||||
styledDiv {
|
||||
css {
|
||||
flex(1.0, 1.0, FlexBasis.auto)
|
||||
}
|
||||
h1 { +"GDML/JSON loader demo" }
|
||||
gridRow {
|
||||
css {
|
||||
+"p-1"
|
||||
overflow = Overflow.auto
|
||||
child(ThreeCanvasComponent) {
|
||||
attrs {
|
||||
this.context = props.context
|
||||
this.obj = vision as? Solid
|
||||
this.selected = selected
|
||||
this.clickCallback = onSelect
|
||||
this.canvasCallback = {
|
||||
canvas = it
|
||||
}
|
||||
gridColumn(3, maxSize = GridMaxSize.XL) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
flexColumn {
|
||||
css {
|
||||
+"order-2"
|
||||
+"order-xl-1"
|
||||
minWidth = 500.px
|
||||
maxHeight = 100.pct
|
||||
flex(1.0, 1.0, FlexBasis.zero)
|
||||
margin(left = 4.px, right = 4.px)
|
||||
border(1.px, BorderStyle.solid, Color.lightGray)
|
||||
}
|
||||
card("Load data") {
|
||||
fileDrop("(drag file here)") { files ->
|
||||
@ -97,61 +135,8 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
||||
}
|
||||
}
|
||||
}
|
||||
//tree
|
||||
card("Object tree") {
|
||||
vision?.let {
|
||||
objectTree(it, selected, select)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gridColumn(6, maxSize = GridMaxSize.XL) {
|
||||
css {
|
||||
+"order-1"
|
||||
+"order-xl-2"
|
||||
}
|
||||
//canvas
|
||||
child(ThreeCanvasComponent) {
|
||||
attrs {
|
||||
this.context = props.context
|
||||
this.obj = vision as? Solid
|
||||
this.selected = selected
|
||||
this.clickCallback = select
|
||||
this.canvasCallback = {
|
||||
canvas = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gridColumn(3, maxSize = GridMaxSize.XL) {
|
||||
css {
|
||||
+"order-3"
|
||||
}
|
||||
container {
|
||||
//settings
|
||||
canvas?.let {
|
||||
card("Canvas configuration") {
|
||||
canvasControls(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
container {
|
||||
//properties
|
||||
namecrumbs(selected, "World") { selected = it }
|
||||
selected.let { selected ->
|
||||
val selectedObject: Vision? = when {
|
||||
selected == null -> null
|
||||
selected.isEmpty() -> vision
|
||||
else -> (vision as? VisionGroup)?.get(selected)
|
||||
}
|
||||
if (selectedObject != null) {
|
||||
visionPropertyEditor(
|
||||
selectedObject,
|
||||
default = selectedObject.getAllProperties(),
|
||||
key = selected
|
||||
)
|
||||
}
|
||||
}
|
||||
threeControls(it, selected, onSelect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Three js demo for particle physics</title>
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
|
@ -48,7 +48,7 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(project(":ui:bootstrap"))
|
||||
implementation("io.ktor:ktor-client-js:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-serialization-js:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,19 +8,24 @@ import hep.dataforge.names.length
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.bootstrap.canvasControls
|
||||
import hep.dataforge.vision.bootstrap.card
|
||||
import hep.dataforge.vision.react.ThreeCanvasComponent
|
||||
import hep.dataforge.vision.react.configEditor
|
||||
import hep.dataforge.vision.react.objectTree
|
||||
import hep.dataforge.vision.solid.specifications.Camera
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.css.height
|
||||
import kotlinx.css.pct
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import react.*
|
||||
import react.RProps
|
||||
import react.child
|
||||
import react.dom.*
|
||||
import react.functionalComponent
|
||||
import react.useState
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import kotlin.math.PI
|
||||
@ -62,13 +67,18 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
||||
+"col-lg-3"
|
||||
+"px-0"
|
||||
+"overflow-auto"
|
||||
+"h-100"
|
||||
}
|
||||
//tree
|
||||
card("Object tree") {
|
||||
objectTree(root, selected, select)
|
||||
}
|
||||
}
|
||||
div("col-lg-6") {
|
||||
styledDiv {
|
||||
css {
|
||||
+"col-lg-6"
|
||||
height = 100.pct
|
||||
}
|
||||
//canvas
|
||||
child(ThreeCanvasComponent) {
|
||||
attrs {
|
||||
|
@ -3,12 +3,10 @@ package ru.mipt.npm.muon.monitor
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.js.Application
|
||||
import hep.dataforge.js.startApplication
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.features.json.JsonFeature
|
||||
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.serialization.json.Json
|
||||
import react.child
|
||||
import react.dom.div
|
||||
import react.dom.render
|
||||
@ -19,12 +17,10 @@ private class MMDemoApp : Application {
|
||||
|
||||
private val connection = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(Json { serializersModule = SolidManager.serializersModuleForSolids })
|
||||
serializer = KotlinxSerializer()
|
||||
}
|
||||
}
|
||||
|
||||
//TODO introduce react application
|
||||
|
||||
override fun start(state: Map<String, Any>) {
|
||||
|
||||
val context = Global.context("demo") {}
|
||||
|
@ -2,10 +2,10 @@ import hep.dataforge.context.Global
|
||||
import hep.dataforge.js.Application
|
||||
import hep.dataforge.js.startApplication
|
||||
import hep.dataforge.vision.bootstrap.visionPropertyEditor
|
||||
import hep.dataforge.vision.react.ThreeCanvasComponent
|
||||
import hep.dataforge.vision.react.objectTree
|
||||
import hep.dataforge.vision.solid.*
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
|
||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||
import kotlinx.browser.document
|
||||
import org.w3c.dom.HTMLElement
|
||||
|
@ -1,238 +1,116 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.length
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.react.ObjectTree
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.js.div
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.RBuilder
|
||||
import react.ReactElement
|
||||
import react.child
|
||||
import react.dom.*
|
||||
import styled.StyledDOMBuilder
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
//public inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
|
||||
// div("card w-100") {
|
||||
// div("card-body") {
|
||||
// h3(classes = "card-title") { +title }
|
||||
// block()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
public inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
|
||||
div("card w-100") {
|
||||
div("card-body") {
|
||||
h3(classes = "card-title") { +title }
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun RBuilder.card(title: String, classes: String? = null, crossinline block: RBuilder.() -> Unit) {
|
||||
div("card w-100 $classes") {
|
||||
div("card-body") {
|
||||
h3(classes = "card-title") {
|
||||
+title
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun TagConsumer<HTMLElement>.accordion(id: String, elements: List<Pair<String, DIV.() -> Unit>>) {
|
||||
div("container-fluid") {
|
||||
div("accordion") {
|
||||
this.id = id
|
||||
elements.forEachIndexed { index, (title, builder) ->
|
||||
val headerID = "${id}-${index}-heading"
|
||||
val collapseID = "${id}-${index}-collapse"
|
||||
div("card") {
|
||||
div("card-header") {
|
||||
this.id = headerID
|
||||
h5("mb-0") {
|
||||
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
|
||||
attributes["data-toggle"] = "collapse"
|
||||
attributes["data-target"] = "#$collapseID"
|
||||
attributes["aria-expanded"] = "false"
|
||||
attributes["aria-controls"] = collapseID
|
||||
+title
|
||||
}
|
||||
}
|
||||
}
|
||||
div("collapse") {
|
||||
this.id = collapseID
|
||||
attributes["aria-labelledby"] = headerID
|
||||
attributes["data-parent"] = "#$id"
|
||||
div("card-body", block = builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias AccordionBuilder = MutableList<Pair<String, DIV.() -> Unit>>
|
||||
|
||||
public fun AccordionBuilder.entry(title: String, builder: DIV.() -> Unit) {
|
||||
add(title to builder)
|
||||
}
|
||||
|
||||
public fun TagConsumer<HTMLElement>.accordion(id: String, builder: AccordionBuilder.() -> Unit) {
|
||||
val list = ArrayList<Pair<String, DIV.() -> Unit>>().apply(builder)
|
||||
accordion(id, list)
|
||||
}
|
||||
|
||||
public fun RBuilder.accordion(id: String, elements: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>>): ReactElement {
|
||||
return div("container-fluid") {
|
||||
div("accordion") {
|
||||
attrs {
|
||||
this.id = id
|
||||
}
|
||||
elements.forEachIndexed { index, (title, builder) ->
|
||||
val headerID = "${id}-${index}-heading"
|
||||
val collapseID = "${id}-${index}-collapse"
|
||||
div("card p-0 m-0") {
|
||||
div("card-header") {
|
||||
attrs {
|
||||
this.id = headerID
|
||||
}
|
||||
h5("mb-0") {
|
||||
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
|
||||
attrs {
|
||||
attributes["data-toggle"] = "collapse"
|
||||
attributes["data-target"] = "#$collapseID"
|
||||
attributes["aria-expanded"] = "false"
|
||||
attributes["aria-controls"] = collapseID
|
||||
}
|
||||
+title
|
||||
}
|
||||
}
|
||||
}
|
||||
div("collapse") {
|
||||
attrs {
|
||||
this.id = collapseID
|
||||
attributes["aria-labelledby"] = headerID
|
||||
attributes["data-parent"] = "#$id"
|
||||
}
|
||||
div("card-body", block = builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun RBuilder.namecrumbs(name: Name?, rootTitle: String, link: (Name) -> Unit) {
|
||||
div("container-fluid p-0") {
|
||||
nav {
|
||||
attrs {
|
||||
attributes["aria-label"] = "breadcrumb"
|
||||
}
|
||||
ol("breadcrumb") {
|
||||
li("breadcrumb-item") {
|
||||
button(classes = "btn btn-link p-0") {
|
||||
+rootTitle
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
link(Name.EMPTY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name != null) {
|
||||
val tokens = ArrayList<NameToken>(name.length)
|
||||
name.tokens.forEach { token ->
|
||||
tokens.add(token)
|
||||
val fullName = Name(tokens.toList())
|
||||
li("breadcrumb-item") {
|
||||
button(classes = "btn btn-link p-0") {
|
||||
+token.toString()
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
console.log("Selected = $fullName")
|
||||
link(fullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias RAccordionBuilder = MutableList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>
|
||||
|
||||
public fun RAccordionBuilder.entry(title: String, builder: RDOMBuilder<DIV>.() -> Unit) {
|
||||
add(title to builder)
|
||||
}
|
||||
|
||||
public fun RBuilder.accordion(id: String, builder: RAccordionBuilder.() -> Unit): ReactElement {
|
||||
val list = ArrayList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>().apply(builder)
|
||||
return accordion(id, list)
|
||||
}
|
||||
|
||||
public enum class ContainerSize(public val suffix: String) {
|
||||
DEFAULT(""),
|
||||
SM("-sm"),
|
||||
MD("-md"),
|
||||
LG("-lg"),
|
||||
XL("-xl"),
|
||||
FLUID("-fluid")
|
||||
}
|
||||
|
||||
public inline fun RBuilder.container(
|
||||
size: ContainerSize = ContainerSize.FLUID,
|
||||
block: StyledDOMBuilder<DIV>.() -> Unit
|
||||
): ReactElement = styledDiv{
|
||||
css{
|
||||
classes.add("container${size.suffix}")
|
||||
}
|
||||
block()
|
||||
}
|
||||
//public typealias SectionsBuilder = MutableList<Pair<String, DIV.() -> Unit>>
|
||||
//
|
||||
//public fun SectionsBuilder.entry(title: String, builder: DIV.() -> Unit) {
|
||||
// add(title to builder)
|
||||
//}
|
||||
|
||||
|
||||
public enum class GridMaxSize(public val suffix: String) {
|
||||
NONE(""),
|
||||
SM("-sm"),
|
||||
MD("-md"),
|
||||
LG("-lg"),
|
||||
XL("-xl")
|
||||
}
|
||||
//public fun TagConsumer<HTMLElement>.accordion(id: String, elements: List<Pair<String, DIV.() -> Unit>>) {
|
||||
// div("container-fluid") {
|
||||
// div("accordion") {
|
||||
// this.id = id
|
||||
// elements.forEachIndexed { index, (title, builder) ->
|
||||
// val headerID = "${id}-${index}-heading"
|
||||
// val collapseID = "${id}-${index}-collapse"
|
||||
// div("card") {
|
||||
// div("card-header") {
|
||||
// this.id = headerID
|
||||
// h5("mb-0") {
|
||||
// button(classes = "btn btn-link collapsed", type = ButtonType.button) {
|
||||
// attributes["data-toggle"] = "collapse"
|
||||
// attributes["data-target"] = "#$collapseID"
|
||||
// attributes["aria-expanded"] = "false"
|
||||
// attributes["aria-controls"] = collapseID
|
||||
// +title
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// div("collapse") {
|
||||
// this.id = collapseID
|
||||
// attributes["aria-labelledby"] = headerID
|
||||
// attributes["data-parent"] = "#$id"
|
||||
// div("card-body", block = builder)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
public inline fun RBuilder.gridColumn(
|
||||
weight: Int? = null,
|
||||
maxSize: GridMaxSize = GridMaxSize.NONE,
|
||||
block: StyledDOMBuilder<DIV>.() -> Unit
|
||||
): ReactElement = styledDiv {
|
||||
val weightSuffix = weight?.let { "-$it" } ?: ""
|
||||
css {
|
||||
classes.add("col${maxSize.suffix}$weightSuffix")
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
public inline fun RBuilder.gridRow(
|
||||
block: StyledDOMBuilder<DIV>.() -> Unit
|
||||
): ReactElement = styledDiv{
|
||||
css{
|
||||
classes.add("row")
|
||||
}
|
||||
block()
|
||||
}
|
||||
//public fun TagConsumer<HTMLElement>.accordion(id: String, builder: AccordionBuilder.() -> Unit) {
|
||||
// val list = ArrayList<Pair<String, DIV.() -> Unit>>().apply(builder)
|
||||
// accordion(id, list)
|
||||
//}
|
||||
|
||||
public fun Element.renderObjectTree(
|
||||
vision: Vision,
|
||||
clickCallback: (Name) -> Unit = {}
|
||||
): Unit = render(this) {
|
||||
card("Object tree") {
|
||||
child(ObjectTree) {
|
||||
attrs {
|
||||
this.name = Name.EMPTY
|
||||
this.obj = vision
|
||||
this.selected = null
|
||||
this.clickCallback = clickCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//public fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
|
||||
// clear()
|
||||
// append {
|
||||
// accordion("controls") {
|
||||
// entry("Settings") {
|
||||
// div("row") {
|
||||
// div("col-2") {
|
||||
// label("checkbox-inline") {
|
||||
// input(type = InputType.checkBox) {
|
||||
// checked = canvas.axes.visible
|
||||
// onChangeFunction = {
|
||||
// canvas.axes.visible = checked
|
||||
// }
|
||||
// }
|
||||
// +"Axes"
|
||||
// }
|
||||
// }
|
||||
// div("col-1") {
|
||||
// button {
|
||||
// +"Export"
|
||||
// onClickFunction = {
|
||||
// val json = (canvas.content as? SolidGroup)?.let { group ->
|
||||
// val visionManager = canvas.context.plugins.fetch(SolidManager).visionManager
|
||||
// visionManager.encodeToString(group)
|
||||
// }
|
||||
// if (json != null) {
|
||||
// saveData(it, "object.json", "text/json") {
|
||||
// json
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// entry("Layers") {
|
||||
// div("row") {
|
||||
// (0..11).forEach { layer ->
|
||||
// div("col-1") {
|
||||
// label { +layer.toString() }
|
||||
// input(type = InputType.checkBox) {
|
||||
// if (layer == 0) {
|
||||
// checked = true
|
||||
// }
|
||||
// onChangeFunction = {
|
||||
// if (checked) {
|
||||
// canvas.camera.layers.enable(layer)
|
||||
// } else {
|
||||
// canvas.camera.layers.disable(layer)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// block()
|
||||
// }
|
||||
//}
|
@ -1,24 +1,26 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.vision.react.flexColumn
|
||||
import hep.dataforge.vision.react.flexRow
|
||||
import hep.dataforge.vision.solid.SolidGroup
|
||||
import hep.dataforge.vision.solid.SolidManager
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import kotlinx.dom.clear
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.border
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.files.Blob
|
||||
import org.w3c.files.BlobPropertyBag
|
||||
import react.*
|
||||
import react.dom.button
|
||||
import react.dom.div
|
||||
import react.dom.h3
|
||||
import react.dom.input
|
||||
import react.dom.label
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
|
||||
event.stopPropagation();
|
||||
@ -30,8 +32,8 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl
|
||||
}
|
||||
|
||||
public fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement {
|
||||
return child(CanvasControls){
|
||||
attrs{
|
||||
return child(CanvasControls) {
|
||||
attrs {
|
||||
this.canvas = canvas
|
||||
}
|
||||
}
|
||||
@ -41,15 +43,18 @@ public external interface CanvasControlsProps : RProps {
|
||||
public var canvas: ThreeCanvas
|
||||
}
|
||||
|
||||
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent ("CanvasControls") { props ->
|
||||
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
||||
val visionManager = useMemo(
|
||||
{ props.canvas.context.plugins.fetch(SolidManager).visionManager },
|
||||
arrayOf(props.canvas)
|
||||
)
|
||||
accordion("controls") {
|
||||
entry("Settings") {
|
||||
div("row") {
|
||||
div("col-2") {
|
||||
flexColumn {
|
||||
h3 { +"Axes" }
|
||||
flexRow {
|
||||
css{
|
||||
border(1.px,BorderStyle.solid, Color.blue)
|
||||
padding(4.px)
|
||||
}
|
||||
label("checkbox-inline") {
|
||||
input(type = InputType.checkBox) {
|
||||
attrs {
|
||||
@ -62,7 +67,12 @@ public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functional
|
||||
+"Axes"
|
||||
}
|
||||
}
|
||||
div("col-1") {
|
||||
h3 { +"Export" }
|
||||
flexRow {
|
||||
css{
|
||||
border(1.px,BorderStyle.solid, Color.blue)
|
||||
padding(4.px)
|
||||
}
|
||||
button {
|
||||
+"Export"
|
||||
attrs {
|
||||
@ -79,12 +89,18 @@ public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functional
|
||||
}
|
||||
}
|
||||
}
|
||||
h3 { +"Layers" }
|
||||
flexRow {
|
||||
css {
|
||||
flexWrap = FlexWrap.wrap
|
||||
border(1.px,BorderStyle.solid, Color.blue)
|
||||
padding(4.px)
|
||||
}
|
||||
(0..31).forEach { layer ->
|
||||
styledDiv {
|
||||
css{
|
||||
padding(4.px)
|
||||
}
|
||||
entry("Layers") {
|
||||
div("row") {
|
||||
(0..11).forEach { layer ->
|
||||
div("col-1") {
|
||||
label { +layer.toString() }
|
||||
input(type = InputType.checkBox) {
|
||||
attrs {
|
||||
@ -104,67 +120,4 @@ public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functional
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
|
||||
clear()
|
||||
append {
|
||||
accordion("controls") {
|
||||
entry("Settings") {
|
||||
div("row") {
|
||||
div("col-2") {
|
||||
label("checkbox-inline") {
|
||||
input(type = InputType.checkBox) {
|
||||
checked = canvas.axes.visible
|
||||
onChangeFunction = {
|
||||
canvas.axes.visible = checked
|
||||
}
|
||||
}
|
||||
+"Axes"
|
||||
}
|
||||
}
|
||||
div("col-1") {
|
||||
button {
|
||||
+"Export"
|
||||
onClickFunction = {
|
||||
val json = (canvas.content as? SolidGroup)?.let { group ->
|
||||
val visionManager = canvas.context.plugins.fetch(SolidManager).visionManager
|
||||
visionManager.encodeToString(group)
|
||||
}
|
||||
if (json != null) {
|
||||
saveData(it, "object.json", "text/json") {
|
||||
json
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entry("Layers") {
|
||||
div("row") {
|
||||
(0..11).forEach { layer ->
|
||||
div("col-1") {
|
||||
label { +layer.toString() }
|
||||
input(type = InputType.checkBox) {
|
||||
if (layer == 0) {
|
||||
checked = true
|
||||
}
|
||||
onChangeFunction = {
|
||||
if (checked) {
|
||||
canvas.camera.layers.enable(layer)
|
||||
} else {
|
||||
canvas.camera.layers.disable(layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.length
|
||||
import kotlinx.html.ButtonType
|
||||
import kotlinx.html.DIV
|
||||
import kotlinx.html.id
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import react.RBuilder
|
||||
import react.ReactElement
|
||||
import react.dom.*
|
||||
import styled.StyledDOMBuilder
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
import styled.styledNav
|
||||
|
||||
|
||||
public inline fun RBuilder.card(title: String, crossinline block: StyledDOMBuilder<DIV>.() -> Unit): ReactElement =
|
||||
styledDiv {
|
||||
css {
|
||||
+"card"
|
||||
+"w-100"
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
+"card-body"
|
||||
}
|
||||
h3(classes = "card-title") {
|
||||
+title
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
public fun RBuilder.accordion(
|
||||
id: String,
|
||||
elements: List<Pair<String, StyledDOMBuilder<DIV>.() -> Unit>>,
|
||||
): ReactElement = styledDiv {
|
||||
css {
|
||||
+"accordion"
|
||||
//+"p-1"
|
||||
}
|
||||
attrs {
|
||||
this.id = id
|
||||
}
|
||||
elements.forEachIndexed { index, (title, builder) ->
|
||||
val headerID = "${id}-${index}-heading"
|
||||
val collapseID = "${id}-${index}-collapse"
|
||||
div("card p-0 m-0") {
|
||||
div("card-header") {
|
||||
attrs {
|
||||
this.id = headerID
|
||||
}
|
||||
h5("mb-0") {
|
||||
button(classes = "btn btn-link collapsed", type = ButtonType.button) {
|
||||
attrs {
|
||||
attributes["data-toggle"] = "collapse"
|
||||
attributes["data-target"] = "#$collapseID"
|
||||
attributes["aria-expanded"] = "false"
|
||||
attributes["aria-controls"] = collapseID
|
||||
}
|
||||
+title
|
||||
}
|
||||
}
|
||||
}
|
||||
div("collapse") {
|
||||
attrs {
|
||||
this.id = collapseID
|
||||
attributes["aria-labelledby"] = headerID
|
||||
attributes["data-parent"] = "#$id"
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
+"card-body"
|
||||
}
|
||||
builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public fun RBuilder.nameCrumbs(name: Name?, rootTitle: String, link: (Name) -> Unit): ReactElement = styledNav {
|
||||
css {
|
||||
+"p-0"
|
||||
}
|
||||
attrs {
|
||||
attributes["aria-label"] = "breadcrumb"
|
||||
}
|
||||
ol("breadcrumb") {
|
||||
li("breadcrumb-item") {
|
||||
button(classes = "btn btn-link p-0") {
|
||||
+rootTitle
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
link(Name.EMPTY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name != null) {
|
||||
val tokens = ArrayList<NameToken>(name.length)
|
||||
name.tokens.forEach { token ->
|
||||
tokens.add(token)
|
||||
val fullName = Name(tokens.toList())
|
||||
li("breadcrumb-item") {
|
||||
button(classes = "btn btn-link p-0") {
|
||||
+token.toString()
|
||||
attrs {
|
||||
onClickFunction = {
|
||||
console.log("Selected = $fullName")
|
||||
link(fullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias RSectionsBuilder = MutableList<Pair<String, StyledDOMBuilder<DIV>.() -> Unit>>
|
||||
|
||||
public fun RSectionsBuilder.entry(title: String, builder: StyledDOMBuilder<DIV>.() -> Unit) {
|
||||
add(title to builder)
|
||||
}
|
||||
|
||||
public fun RBuilder.accordion(id: String, builder: RSectionsBuilder.() -> Unit): ReactElement {
|
||||
val list = ArrayList<Pair<String, StyledDOMBuilder<DIV>.() -> Unit>>().apply(builder)
|
||||
return accordion(id, list)
|
||||
}
|
||||
|
||||
public enum class ContainerSize(public val suffix: String) {
|
||||
DEFAULT(""),
|
||||
SM("-sm"),
|
||||
MD("-md"),
|
||||
LG("-lg"),
|
||||
XL("-xl"),
|
||||
FLUID("-fluid")
|
||||
}
|
||||
|
||||
public inline fun RBuilder.container(
|
||||
size: ContainerSize = ContainerSize.FLUID,
|
||||
block: StyledDOMBuilder<DIV>.() -> Unit,
|
||||
): ReactElement = styledDiv {
|
||||
css {
|
||||
classes.add("container${size.suffix}")
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
|
||||
public enum class GridMaxSize(public val suffix: String) {
|
||||
NONE(""),
|
||||
SM("-sm"),
|
||||
MD("-md"),
|
||||
LG("-lg"),
|
||||
XL("-xl")
|
||||
}
|
||||
|
||||
public inline fun RBuilder.gridColumn(
|
||||
weight: Int? = null,
|
||||
maxSize: GridMaxSize = GridMaxSize.NONE,
|
||||
block: StyledDOMBuilder<DIV>.() -> Unit,
|
||||
): ReactElement = styledDiv {
|
||||
val weightSuffix = weight?.let { "-$it" } ?: ""
|
||||
css {
|
||||
classes.add("col${maxSize.suffix}$weightSuffix")
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
||||
public inline fun RBuilder.gridRow(
|
||||
block: StyledDOMBuilder<DIV>.() -> Unit,
|
||||
): ReactElement = styledDiv {
|
||||
css {
|
||||
classes.add("row")
|
||||
}
|
||||
block()
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.vision.react.flexColumn
|
||||
import kotlinx.css.Overflow
|
||||
import kotlinx.css.flexGrow
|
||||
import kotlinx.css.overflowY
|
||||
import kotlinx.html.DIV
|
||||
import kotlinx.html.classes
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import react.*
|
||||
import react.dom.button
|
||||
import react.dom.li
|
||||
import react.dom.ul
|
||||
import styled.StyledDOMBuilder
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
public external class TabProps : RProps {
|
||||
public var id: String
|
||||
public var title: String?
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val Tab: FunctionalComponent<TabProps> = functionalComponent { props ->
|
||||
props.children()
|
||||
}
|
||||
|
||||
public external class TabPaneProps : RProps {
|
||||
public var activeTab: String?
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val TabPane: FunctionalComponent<TabPaneProps> = functionalComponent("TabPane") { props ->
|
||||
var activeTab: String? by useState(props.activeTab)
|
||||
|
||||
val children: Array<out ReactElement?> = Children.map(props.children) {
|
||||
it.asElementOrNull()
|
||||
} ?: emptyArray()
|
||||
|
||||
val childrenProps = children.mapNotNull {
|
||||
it?.props?.unsafeCast<TabProps>()
|
||||
}
|
||||
|
||||
flexColumn {
|
||||
css {
|
||||
flexGrow = 1.0
|
||||
}
|
||||
ul("nav nav-tabs") {
|
||||
childrenProps.forEach { cp ->
|
||||
li("nav-item") {
|
||||
button(classes = "nav-link") {
|
||||
+(cp.title ?: cp.id)
|
||||
attrs {
|
||||
if (cp.id == activeTab) {
|
||||
classes += "active"
|
||||
}
|
||||
onClickFunction = {
|
||||
activeTab = cp.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
children.find { (it?.props?.unsafeCast<TabProps>())?.id == activeTab }?.let {
|
||||
child(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TabBuilder(internal val parentBuilder: RBuilder) {
|
||||
public fun tab(id: String, title: String? = null, builder: StyledDOMBuilder<DIV>.() -> Unit) {
|
||||
parentBuilder.child(Tab) {
|
||||
attrs {
|
||||
this.id = id
|
||||
this.title = title
|
||||
}
|
||||
styledDiv {
|
||||
css {
|
||||
overflowY = Overflow.auto
|
||||
}
|
||||
builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun RBuilder.tabPane(activeTab: String? = null, crossinline builder: TabBuilder.() -> Unit) {
|
||||
child(TabPane) {
|
||||
attrs {
|
||||
this.activeTab = activeTab
|
||||
}
|
||||
TabBuilder(this).builder()
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package hep.dataforge.vision.bootstrap
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vision.Vision
|
||||
import hep.dataforge.vision.VisionGroup
|
||||
import hep.dataforge.vision.react.objectTree
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import kotlinx.css.*
|
||||
import kotlinx.css.properties.border
|
||||
import react.*
|
||||
import react.dom.h2
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
public external interface ThreeControlsProps : RProps {
|
||||
public var canvas: ThreeCanvas
|
||||
public var selected: Name?
|
||||
public var onSelect: (Name) -> Unit
|
||||
}
|
||||
|
||||
@JsExport
|
||||
public val ThreeControls: FunctionalComponent<ThreeControlsProps> = functionalComponent { props ->
|
||||
val vision = props.canvas.content
|
||||
tabPane {
|
||||
tab("Canvas") {
|
||||
card("Canvas configuration") {
|
||||
canvasControls(props.canvas)
|
||||
}
|
||||
}
|
||||
tab("Tree") {
|
||||
css {
|
||||
border(1.px, BorderStyle.solid, Color.lightGray)
|
||||
padding(10.px)
|
||||
}
|
||||
h2 { +"Object tree" }
|
||||
styledDiv {
|
||||
css {
|
||||
overflowY = Overflow.auto
|
||||
flex(1.0, 0.0, FlexBasis.auto)
|
||||
}
|
||||
props.canvas.content?.let {
|
||||
objectTree(it, props.selected, props.onSelect)
|
||||
}
|
||||
}
|
||||
}
|
||||
tab("Properties") {
|
||||
props.selected.let { selected ->
|
||||
val selectedObject: Vision? = when {
|
||||
selected == null -> null
|
||||
selected.isEmpty() -> vision
|
||||
else -> (vision as? VisionGroup)?.get(selected)
|
||||
}
|
||||
if (selectedObject != null) {
|
||||
visionPropertyEditor(
|
||||
selectedObject,
|
||||
default = selectedObject.getAllProperties(),
|
||||
key = selected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.parentBuilder.run {
|
||||
props.children()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun RBuilder.threeControls(
|
||||
canvas: ThreeCanvas,
|
||||
selected: Name?,
|
||||
onSelect: (Name) -> Unit = {},
|
||||
builder: TabBuilder.() -> Unit = {}
|
||||
): ReactElement = child(ThreeControls) {
|
||||
attrs {
|
||||
this.canvas = canvas
|
||||
this.selected = selected
|
||||
this.onSelect = onSelect
|
||||
}
|
||||
TabBuilder(this).builder()
|
||||
}
|
@ -6,6 +6,7 @@ val reactVersion by extra("17.0.0")
|
||||
val kotlinWrappersVersion: String by rootProject.extra
|
||||
|
||||
dependencies{
|
||||
api(project(":visionforge-core"))
|
||||
api(project(":visionforge-solid"))
|
||||
api("org.jetbrains:kotlin-styled:5.2.0-$kotlinWrappersVersion")
|
||||
api("org.jetbrains:kotlin-react-dom:$reactVersion-$kotlinWrappersVersion")
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
package hep.dataforge.vision.solid.three
|
||||
package hep.dataforge.vision.react
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||
import hep.dataforge.vision.solid.three.output
|
||||
import kotlinx.css.*
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import react.*
|
||||
import react.dom.div
|
||||
import styled.css
|
||||
import styled.styledDiv
|
||||
|
||||
public external interface ThreeCanvasProps : RProps {
|
||||
public var context: Context
|
||||
@ -33,7 +38,8 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
|
||||
if (canvas == null) {
|
||||
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
||||
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
||||
val newCanvas = three.output(element, props.options ?: Canvas3DOptions.empty(), props.clickCallback)
|
||||
val newCanvas: ThreeCanvas =
|
||||
three.output(element, props.options ?: Canvas3DOptions.empty(), props.clickCallback)
|
||||
props.canvasCallback?.invoke(newCanvas)
|
||||
canvas = newCanvas
|
||||
}
|
||||
@ -51,43 +57,13 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
|
||||
canvas?.select(props.selected)
|
||||
}
|
||||
|
||||
div {
|
||||
styledDiv {
|
||||
css {
|
||||
minWidth = 500.px
|
||||
minHeight = 500.px
|
||||
display = Display.inherit
|
||||
flex(1.0, 1.0, FlexBasis.auto)
|
||||
}
|
||||
ref = elementRef
|
||||
}
|
||||
}
|
||||
|
||||
//public class ThreeCanvasComponent : RComponent<ThreeCanvasProps, ThreeCanvasState>() {
|
||||
//
|
||||
// private var canvas: ThreeCanvas? = null
|
||||
//
|
||||
// override fun componentDidMount() {
|
||||
// props.obj?.let { obj ->
|
||||
// if (canvas == null) {
|
||||
// val element = state.element as? HTMLElement ?: error("Canvas element not found")
|
||||
// val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
||||
// canvas = three.output(element, props.options ?: Canvas3DOptions.empty()).apply {
|
||||
// onClick = props.clickCallback
|
||||
// }
|
||||
// props.canvasCallback?.invoke(canvas)
|
||||
// }
|
||||
// canvas?.render(obj)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun componentDidUpdate(prevProps: ThreeCanvasProps, prevState: ThreeCanvasState, snapshot: Any) {
|
||||
// if (prevProps.obj != props.obj) {
|
||||
// componentDidMount()
|
||||
// }
|
||||
// if (prevProps.selected != props.selected) {
|
||||
// canvas?.select(props.selected)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun RBuilder.render() {
|
||||
// div {
|
||||
// ref {
|
||||
// state.element = findDOMNode(it)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -8,7 +8,7 @@ public object TreeStyles : StyleSheet("treeStyles", true) {
|
||||
/**
|
||||
* Remove default bullets
|
||||
*/
|
||||
public val tree by css {
|
||||
public val tree: RuleSet by css {
|
||||
paddingLeft = 8.px
|
||||
marginLeft = 0.px
|
||||
listStyleType = ListStyleType.none
|
||||
|
@ -28,8 +28,8 @@ kotlin {
|
||||
jsMain {
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
||||
api("org.jetbrains:kotlin-styled:5.2.0-$kotlinWrappersVersion")
|
||||
api("org.jetbrains:kotlin-extensions:1.0.1-$kotlinWrappersVersion")
|
||||
api("org.jetbrains:kotlin-css:1.0.0-$kotlinWrappersVersion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ public class Canvas3DOptions : Scheme() {
|
||||
public var camera: Camera by spec(Camera, Camera.empty())
|
||||
public var controls: Controls by spec(Controls, Controls.empty())
|
||||
|
||||
public var minSize: Int by int(300)
|
||||
public var minSize: Int by int(400)
|
||||
public var minWith: Number by number { minSize }
|
||||
public var minHeight: Number by number { minSize }
|
||||
|
||||
@ -18,3 +18,9 @@ public class Canvas3DOptions : Scheme() {
|
||||
|
||||
public companion object : SchemeSpec<Canvas3DOptions>(::Canvas3DOptions)
|
||||
}
|
||||
|
||||
public fun Canvas3DOptions.computeWidth(external: Number): Int =
|
||||
(external.toInt()).coerceIn(minWith.toInt()..maxWith.toInt())
|
||||
|
||||
public fun Canvas3DOptions.computeHeight(external: Number): Int =
|
||||
(external.toInt()).coerceIn(minHeight.toInt()..maxHeight.toInt())
|
@ -0,0 +1,5 @@
|
||||
package hep.dataforge.vision.solid.three
|
||||
|
||||
fun createDataControls() {
|
||||
val dat = kotlinext.js.require("dat.gui")
|
||||
}
|
@ -10,9 +10,7 @@ import hep.dataforge.names.toName
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vision.Colors
|
||||
import hep.dataforge.vision.solid.Solid
|
||||
import hep.dataforge.vision.solid.specifications.Camera
|
||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||
import hep.dataforge.vision.solid.specifications.Controls
|
||||
import hep.dataforge.vision.solid.specifications.*
|
||||
import hep.dataforge.vision.solid.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
||||
import hep.dataforge.vision.solid.three.ThreeMaterials.SELECTED_MATERIAL
|
||||
import info.laht.threekt.WebGLRenderer
|
||||
@ -28,9 +26,11 @@ import info.laht.threekt.materials.LineBasicMaterial
|
||||
import info.laht.threekt.math.Vector2
|
||||
import info.laht.threekt.objects.LineSegments
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import info.laht.threekt.renderers.WebGLRenderer
|
||||
import info.laht.threekt.scenes.Scene
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.dom.clear
|
||||
import org.w3c.dom.HTMLCanvasElement
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
@ -41,7 +41,6 @@ import kotlin.math.sin
|
||||
*
|
||||
*/
|
||||
public class ThreeCanvas(
|
||||
element: HTMLElement,
|
||||
public val three: ThreePlugin,
|
||||
public val options: Canvas3DOptions,
|
||||
public val onClick: ((Name?) -> Unit)? = null,
|
||||
@ -69,7 +68,24 @@ public class ThreeCanvas(
|
||||
|
||||
private var picked: Object3D? = null
|
||||
|
||||
init {
|
||||
/**
|
||||
* Attach canvas to given [HTMLElement]
|
||||
*/
|
||||
public fun attach(element: HTMLElement) {
|
||||
fun WebGLRenderer.resize() {
|
||||
val canvas = domElement as HTMLCanvasElement
|
||||
|
||||
val width = options.computeWidth(canvas.clientWidth)
|
||||
val height = options.computeHeight(canvas.clientHeight)
|
||||
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
setSize(width, height, false)
|
||||
camera.aspect = width.toDouble() / height
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
element.clear()
|
||||
|
||||
//Attach listener to track mouse changes
|
||||
@ -86,12 +102,18 @@ public class ThreeCanvas(
|
||||
onClick?.invoke(picked?.fullName())
|
||||
}, false)
|
||||
|
||||
camera.aspect = 1.0
|
||||
|
||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||
setClearColor(Colors.skyblue, 1)
|
||||
}
|
||||
|
||||
val canvas = renderer.domElement as HTMLCanvasElement
|
||||
|
||||
canvas.style.apply {
|
||||
width = "100%"
|
||||
height = "100%"
|
||||
display = "block"
|
||||
}
|
||||
|
||||
addControls(renderer.domElement, options.controls)
|
||||
|
||||
fun animate() {
|
||||
@ -106,19 +128,15 @@ public class ThreeCanvas(
|
||||
window.requestAnimationFrame {
|
||||
animate()
|
||||
}
|
||||
|
||||
renderer.render(scene, camera)
|
||||
}
|
||||
|
||||
element.appendChild(renderer.domElement)
|
||||
renderer.resize()
|
||||
|
||||
renderer.setSize(
|
||||
element.clientWidth.coerceIn(options.minWith.toInt()..options.maxWith.toInt()),
|
||||
element.clientHeight.coerceIn(options.minHeight.toInt()..options.maxHeight.toInt())
|
||||
)
|
||||
|
||||
window.onresize = {
|
||||
renderer.setSize(element.clientWidth, element.clientWidth)
|
||||
camera.updateProjectionMatrix()
|
||||
element.onresize = {
|
||||
renderer.resize()
|
||||
}
|
||||
|
||||
animate()
|
||||
@ -250,7 +268,7 @@ public fun ThreePlugin.output(
|
||||
element: HTMLElement,
|
||||
spec: Canvas3DOptions = Canvas3DOptions.empty(),
|
||||
onClick: ((Name?) -> Unit)? = null,
|
||||
): ThreeCanvas = ThreeCanvas(element, this, spec, onClick)
|
||||
): ThreeCanvas = ThreeCanvas(this, spec, onClick).apply { attach(element) }
|
||||
|
||||
public fun ThreePlugin.render(
|
||||
element: HTMLElement,
|
||||
|
@ -5,10 +5,7 @@ import hep.dataforge.meta.float
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.vision.solid.*
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.DirectGeometry
|
||||
import info.laht.threekt.core.Face3
|
||||
import info.laht.threekt.core.Geometry
|
||||
import info.laht.threekt.core.*
|
||||
import info.laht.threekt.external.controls.OrbitControls
|
||||
import info.laht.threekt.materials.Material
|
||||
import info.laht.threekt.math.Euler
|
||||
@ -77,3 +74,5 @@ internal fun Any.dispose() {
|
||||
is Texture -> dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public fun Layers.check(layer: Int): Boolean = (mask shr(layer) and 0x00000001) > 0
|
Loading…
Reference in New Issue
Block a user