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.context.Context
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.isEmpty
|
|
||||||
import hep.dataforge.vision.Vision
|
import hep.dataforge.vision.Vision
|
||||||
import hep.dataforge.vision.VisionGroup
|
import hep.dataforge.vision.bootstrap.card
|
||||||
import hep.dataforge.vision.bootstrap.*
|
import hep.dataforge.vision.bootstrap.nameCrumbs
|
||||||
|
import hep.dataforge.vision.bootstrap.threeControls
|
||||||
import hep.dataforge.vision.gdml.toVision
|
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.Solid
|
||||||
import hep.dataforge.vision.solid.SolidGroup
|
|
||||||
import hep.dataforge.vision.solid.SolidManager
|
import hep.dataforge.vision.solid.SolidManager
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
|
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import kotlinx.css.FlexBasis
|
import kotlinx.css.*
|
||||||
import kotlinx.css.Overflow
|
import kotlinx.css.properties.border
|
||||||
import kotlinx.css.flex
|
|
||||||
import kotlinx.css.overflow
|
|
||||||
import kscience.gdml.GDML
|
import kscience.gdml.GDML
|
||||||
import kscience.gdml.decodeFromString
|
import kscience.gdml.decodeFromString
|
||||||
import org.w3c.files.FileReader
|
import org.w3c.files.FileReader
|
||||||
import org.w3c.files.get
|
import org.w3c.files.get
|
||||||
import react.*
|
import react.RProps
|
||||||
|
import react.child
|
||||||
import react.dom.h1
|
import react.dom.h1
|
||||||
|
import react.functionalComponent
|
||||||
|
import react.useState
|
||||||
import styled.css
|
import styled.css
|
||||||
|
import styled.styledDiv
|
||||||
|
|
||||||
external interface GDMLAppProps : RProps {
|
external interface GDMLAppProps : RProps {
|
||||||
var context: Context
|
var context: Context
|
||||||
@ -46,7 +48,7 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
|||||||
var canvas: ThreeCanvas? by useState { null }
|
var canvas: ThreeCanvas? by useState { null }
|
||||||
var vision: Vision? by useState { props.rootObject }
|
var vision: Vision? by useState { props.rootObject }
|
||||||
|
|
||||||
val select: (Name?) -> Unit = {
|
val onSelect: (Name?) -> Unit = {
|
||||||
selected = it
|
selected = it
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,20 +69,56 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
|||||||
vision = parsedVision
|
vision = parsedVision
|
||||||
}
|
}
|
||||||
|
|
||||||
gridColumn {
|
flexColumn {
|
||||||
css {
|
css {
|
||||||
flex(1.0, 1.0, FlexBasis.auto)
|
height = 100.pct
|
||||||
|
width = 100.pct
|
||||||
}
|
}
|
||||||
h1 { +"GDML/JSON loader demo" }
|
styledDiv {
|
||||||
gridRow {
|
|
||||||
css {
|
css {
|
||||||
+"p-1"
|
+"page-header"
|
||||||
overflow = Overflow.auto
|
+"justify-content-center"
|
||||||
}
|
}
|
||||||
gridColumn(3, maxSize = GridMaxSize.XL) {
|
h1 { +"GDML/JSON loader demo" }
|
||||||
|
}
|
||||||
|
flexRow {
|
||||||
|
css {
|
||||||
|
flex(1.0, 1.0, FlexBasis.auto)
|
||||||
|
flexWrap = FlexWrap.wrap
|
||||||
|
}
|
||||||
|
|
||||||
|
flexColumn {
|
||||||
css {
|
css {
|
||||||
+"order-2"
|
flex(1.0, 1.0, FlexBasis.auto)
|
||||||
+"order-xl-1"
|
margin(10.px)
|
||||||
|
}
|
||||||
|
nameCrumbs(selected, "World", onSelect)
|
||||||
|
|
||||||
|
//canvas
|
||||||
|
styledDiv {
|
||||||
|
css {
|
||||||
|
flex(1.0, 1.0, FlexBasis.auto)
|
||||||
|
}
|
||||||
|
child(ThreeCanvasComponent) {
|
||||||
|
attrs {
|
||||||
|
this.context = props.context
|
||||||
|
this.obj = vision as? Solid
|
||||||
|
this.selected = selected
|
||||||
|
this.clickCallback = onSelect
|
||||||
|
this.canvasCallback = {
|
||||||
|
canvas = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flexColumn {
|
||||||
|
css {
|
||||||
|
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") {
|
card("Load data") {
|
||||||
fileDrop("(drag file here)") { files ->
|
fileDrop("(drag file here)") { files ->
|
||||||
@ -97,61 +135,8 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//tree
|
canvas?.let {
|
||||||
card("Object tree") {
|
threeControls(it, selected, onSelect)
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<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>
|
<title>Three js demo for particle physics</title>
|
||||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="css/main.css">
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
@ -48,7 +48,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":ui:bootstrap"))
|
implementation(project(":ui:bootstrap"))
|
||||||
implementation("io.ktor:ktor-client-js:$ktorVersion")
|
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.Vision
|
||||||
import hep.dataforge.vision.bootstrap.canvasControls
|
import hep.dataforge.vision.bootstrap.canvasControls
|
||||||
import hep.dataforge.vision.bootstrap.card
|
import hep.dataforge.vision.bootstrap.card
|
||||||
|
import hep.dataforge.vision.react.ThreeCanvasComponent
|
||||||
import hep.dataforge.vision.react.configEditor
|
import hep.dataforge.vision.react.configEditor
|
||||||
import hep.dataforge.vision.react.objectTree
|
import hep.dataforge.vision.react.objectTree
|
||||||
import hep.dataforge.vision.solid.specifications.Camera
|
import hep.dataforge.vision.solid.specifications.Camera
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.css.height
|
||||||
|
import kotlinx.css.pct
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import react.*
|
import react.RProps
|
||||||
|
import react.child
|
||||||
import react.dom.*
|
import react.dom.*
|
||||||
|
import react.functionalComponent
|
||||||
|
import react.useState
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
@ -62,13 +67,18 @@ val MMApp = functionalComponent<MMAppProps>("Muon monitor") { props ->
|
|||||||
+"col-lg-3"
|
+"col-lg-3"
|
||||||
+"px-0"
|
+"px-0"
|
||||||
+"overflow-auto"
|
+"overflow-auto"
|
||||||
|
+"h-100"
|
||||||
}
|
}
|
||||||
//tree
|
//tree
|
||||||
card("Object tree") {
|
card("Object tree") {
|
||||||
objectTree(root, selected, select)
|
objectTree(root, selected, select)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("col-lg-6") {
|
styledDiv {
|
||||||
|
css {
|
||||||
|
+"col-lg-6"
|
||||||
|
height = 100.pct
|
||||||
|
}
|
||||||
//canvas
|
//canvas
|
||||||
child(ThreeCanvasComponent) {
|
child(ThreeCanvasComponent) {
|
||||||
attrs {
|
attrs {
|
||||||
|
@ -3,12 +3,10 @@ package ru.mipt.npm.muon.monitor
|
|||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
import hep.dataforge.js.Application
|
import hep.dataforge.js.Application
|
||||||
import hep.dataforge.js.startApplication
|
import hep.dataforge.js.startApplication
|
||||||
import hep.dataforge.vision.solid.SolidManager
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.json.JsonFeature
|
import io.ktor.client.features.json.JsonFeature
|
||||||
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import react.child
|
import react.child
|
||||||
import react.dom.div
|
import react.dom.div
|
||||||
import react.dom.render
|
import react.dom.render
|
||||||
@ -19,12 +17,10 @@ private class MMDemoApp : Application {
|
|||||||
|
|
||||||
private val connection = HttpClient {
|
private val connection = HttpClient {
|
||||||
install(JsonFeature) {
|
install(JsonFeature) {
|
||||||
serializer = KotlinxSerializer(Json { serializersModule = SolidManager.serializersModuleForSolids })
|
serializer = KotlinxSerializer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO introduce react application
|
|
||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
override fun start(state: Map<String, Any>) {
|
||||||
|
|
||||||
val context = Global.context("demo") {}
|
val context = Global.context("demo") {}
|
||||||
|
@ -2,10 +2,10 @@ import hep.dataforge.context.Global
|
|||||||
import hep.dataforge.js.Application
|
import hep.dataforge.js.Application
|
||||||
import hep.dataforge.js.startApplication
|
import hep.dataforge.js.startApplication
|
||||||
import hep.dataforge.vision.bootstrap.visionPropertyEditor
|
import hep.dataforge.vision.bootstrap.visionPropertyEditor
|
||||||
|
import hep.dataforge.vision.react.ThreeCanvasComponent
|
||||||
import hep.dataforge.vision.react.objectTree
|
import hep.dataforge.vision.react.objectTree
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvasComponent
|
|
||||||
import hep.dataforge.vision.solid.three.ThreePlugin
|
import hep.dataforge.vision.solid.three.ThreePlugin
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
|
@ -1,238 +1,116 @@
|
|||||||
package hep.dataforge.vision.bootstrap
|
package hep.dataforge.vision.bootstrap
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
//public inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
|
||||||
import hep.dataforge.names.NameToken
|
// div("card w-100") {
|
||||||
import hep.dataforge.names.length
|
// div("card-body") {
|
||||||
import hep.dataforge.vision.Vision
|
// h3(classes = "card-title") { +title }
|
||||||
import hep.dataforge.vision.react.ObjectTree
|
// block()
|
||||||
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) {
|
//public typealias SectionsBuilder = MutableList<Pair<String, DIV.() -> Unit>>
|
||||||
div("card w-100") {
|
//
|
||||||
div("card-body") {
|
//public fun SectionsBuilder.entry(title: String, builder: DIV.() -> Unit) {
|
||||||
h3(classes = "card-title") { +title }
|
// add(title to builder)
|
||||||
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 enum class GridMaxSize(public val suffix: String) {
|
//public fun TagConsumer<HTMLElement>.accordion(id: String, elements: List<Pair<String, DIV.() -> Unit>>) {
|
||||||
NONE(""),
|
// div("container-fluid") {
|
||||||
SM("-sm"),
|
// div("accordion") {
|
||||||
MD("-md"),
|
// this.id = id
|
||||||
LG("-lg"),
|
// elements.forEachIndexed { index, (title, builder) ->
|
||||||
XL("-xl")
|
// 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(
|
//public fun TagConsumer<HTMLElement>.accordion(id: String, builder: AccordionBuilder.() -> Unit) {
|
||||||
block: StyledDOMBuilder<DIV>.() -> Unit
|
// val list = ArrayList<Pair<String, DIV.() -> Unit>>().apply(builder)
|
||||||
): ReactElement = styledDiv{
|
// accordion(id, list)
|
||||||
css{
|
//}
|
||||||
classes.add("row")
|
|
||||||
}
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Element.renderObjectTree(
|
//public fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
|
||||||
vision: Vision,
|
// clear()
|
||||||
clickCallback: (Name) -> Unit = {}
|
// append {
|
||||||
): Unit = render(this) {
|
// accordion("controls") {
|
||||||
card("Object tree") {
|
// entry("Settings") {
|
||||||
child(ObjectTree) {
|
// div("row") {
|
||||||
attrs {
|
// div("col-2") {
|
||||||
this.name = Name.EMPTY
|
// label("checkbox-inline") {
|
||||||
this.obj = vision
|
// input(type = InputType.checkBox) {
|
||||||
this.selected = null
|
// checked = canvas.axes.visible
|
||||||
this.clickCallback = clickCallback
|
// 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
|
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.SolidGroup
|
||||||
import hep.dataforge.vision.solid.SolidManager
|
import hep.dataforge.vision.solid.SolidManager
|
||||||
import hep.dataforge.vision.solid.three.ThreeCanvas
|
import hep.dataforge.vision.solid.three.ThreeCanvas
|
||||||
import kotlinx.dom.clear
|
import kotlinx.css.*
|
||||||
import kotlinx.html.*
|
import kotlinx.css.properties.border
|
||||||
import kotlinx.html.dom.append
|
import kotlinx.html.InputType
|
||||||
import kotlinx.html.js.onChangeFunction
|
import kotlinx.html.js.onChangeFunction
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import org.w3c.dom.Element
|
|
||||||
import org.w3c.dom.HTMLElement
|
|
||||||
import org.w3c.dom.HTMLInputElement
|
import org.w3c.dom.HTMLInputElement
|
||||||
import org.w3c.dom.events.Event
|
import org.w3c.dom.events.Event
|
||||||
import org.w3c.files.Blob
|
import org.w3c.files.Blob
|
||||||
import org.w3c.files.BlobPropertyBag
|
import org.w3c.files.BlobPropertyBag
|
||||||
import react.*
|
import react.*
|
||||||
import react.dom.button
|
import react.dom.button
|
||||||
import react.dom.div
|
import react.dom.h3
|
||||||
import react.dom.input
|
import react.dom.input
|
||||||
import react.dom.label
|
import react.dom.label
|
||||||
|
import styled.css
|
||||||
|
import styled.styledDiv
|
||||||
|
|
||||||
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
|
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -30,8 +32,8 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement {
|
public fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement {
|
||||||
return child(CanvasControls){
|
return child(CanvasControls) {
|
||||||
attrs{
|
attrs {
|
||||||
this.canvas = canvas
|
this.canvas = canvas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,62 +43,75 @@ public external interface CanvasControlsProps : RProps {
|
|||||||
public var canvas: ThreeCanvas
|
public var canvas: ThreeCanvas
|
||||||
}
|
}
|
||||||
|
|
||||||
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent ("CanvasControls") { props ->
|
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent("CanvasControls") { props ->
|
||||||
val visionManager = useMemo(
|
val visionManager = useMemo(
|
||||||
{ props.canvas.context.plugins.fetch(SolidManager).visionManager },
|
{ props.canvas.context.plugins.fetch(SolidManager).visionManager },
|
||||||
arrayOf(props.canvas)
|
arrayOf(props.canvas)
|
||||||
)
|
)
|
||||||
accordion("controls") {
|
flexColumn {
|
||||||
entry("Settings") {
|
h3 { +"Axes" }
|
||||||
div("row") {
|
flexRow {
|
||||||
div("col-2") {
|
css{
|
||||||
label("checkbox-inline") {
|
border(1.px,BorderStyle.solid, Color.blue)
|
||||||
input(type = InputType.checkBox) {
|
padding(4.px)
|
||||||
attrs {
|
}
|
||||||
defaultChecked = props.canvas.axes.visible
|
label("checkbox-inline") {
|
||||||
onChangeFunction = {
|
input(type = InputType.checkBox) {
|
||||||
props.canvas.axes.visible = (it.target as HTMLInputElement).checked
|
attrs {
|
||||||
}
|
defaultChecked = props.canvas.axes.visible
|
||||||
}
|
onChangeFunction = {
|
||||||
|
props.canvas.axes.visible = (it.target as HTMLInputElement).checked
|
||||||
}
|
}
|
||||||
+"Axes"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div("col-1") {
|
+"Axes"
|
||||||
button {
|
}
|
||||||
+"Export"
|
}
|
||||||
|
h3 { +"Export" }
|
||||||
|
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) {
|
||||||
|
saveData(it, "object.json", "text/json") {
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
label { +layer.toString() }
|
||||||
|
input(type = InputType.checkBox) {
|
||||||
attrs {
|
attrs {
|
||||||
onClickFunction = {
|
if (layer == 0) {
|
||||||
val json = (props.canvas.content as? SolidGroup)?.let { group ->
|
defaultChecked = true
|
||||||
visionManager.encodeToString(group)
|
|
||||||
}
|
|
||||||
if (json != null) {
|
|
||||||
saveData(it, "object.json", "text/json") {
|
|
||||||
json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
onChangeFunction = {
|
||||||
}
|
if ((it.target as HTMLInputElement).checked) {
|
||||||
}
|
props.canvas.camera.layers.enable(layer)
|
||||||
}
|
} else {
|
||||||
}
|
props.canvas.camera.layers.disable(layer)
|
||||||
entry("Layers") {
|
|
||||||
div("row") {
|
|
||||||
(0..11).forEach { layer ->
|
|
||||||
div("col-1") {
|
|
||||||
label { +layer.toString() }
|
|
||||||
input(type = InputType.checkBox) {
|
|
||||||
attrs {
|
|
||||||
if (layer == 0) {
|
|
||||||
defaultChecked = true
|
|
||||||
}
|
|
||||||
onChangeFunction = {
|
|
||||||
if ((it.target as HTMLInputElement).checked) {
|
|
||||||
props.canvas.camera.layers.enable(layer)
|
|
||||||
} else {
|
|
||||||
props.canvas.camera.layers.disable(layer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,65 +121,3 @@ 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
|
val kotlinWrappersVersion: String by rootProject.extra
|
||||||
|
|
||||||
dependencies{
|
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")
|
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.context.Context
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
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.Element
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import react.*
|
import react.*
|
||||||
import react.dom.div
|
import styled.css
|
||||||
|
import styled.styledDiv
|
||||||
|
|
||||||
public external interface ThreeCanvasProps : RProps {
|
public external interface ThreeCanvasProps : RProps {
|
||||||
public var context: Context
|
public var context: Context
|
||||||
@ -33,7 +38,8 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
|
|||||||
if (canvas == null) {
|
if (canvas == null) {
|
||||||
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
|
||||||
val three: ThreePlugin = props.context.plugins.fetch(ThreePlugin)
|
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)
|
props.canvasCallback?.invoke(newCanvas)
|
||||||
canvas = newCanvas
|
canvas = newCanvas
|
||||||
}
|
}
|
||||||
@ -51,43 +57,13 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
|
|||||||
canvas?.select(props.selected)
|
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
|
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
|
* Remove default bullets
|
||||||
*/
|
*/
|
||||||
public val tree by css {
|
public val tree: RuleSet by css {
|
||||||
paddingLeft = 8.px
|
paddingLeft = 8.px
|
||||||
marginLeft = 0.px
|
marginLeft = 0.px
|
||||||
listStyleType = ListStyleType.none
|
listStyleType = ListStyleType.none
|
||||||
|
@ -28,8 +28,8 @@ kotlin {
|
|||||||
jsMain {
|
jsMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
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-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 camera: Camera by spec(Camera, Camera.empty())
|
||||||
public var controls: Controls by spec(Controls, Controls.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 minWith: Number by number { minSize }
|
||||||
public var minHeight: 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 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.output.Renderer
|
||||||
import hep.dataforge.vision.Colors
|
import hep.dataforge.vision.Colors
|
||||||
import hep.dataforge.vision.solid.Solid
|
import hep.dataforge.vision.solid.Solid
|
||||||
import hep.dataforge.vision.solid.specifications.Camera
|
import hep.dataforge.vision.solid.specifications.*
|
||||||
import hep.dataforge.vision.solid.specifications.Canvas3DOptions
|
|
||||||
import hep.dataforge.vision.solid.specifications.Controls
|
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
import hep.dataforge.vision.solid.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
||||||
import hep.dataforge.vision.solid.three.ThreeMaterials.SELECTED_MATERIAL
|
import hep.dataforge.vision.solid.three.ThreeMaterials.SELECTED_MATERIAL
|
||||||
import info.laht.threekt.WebGLRenderer
|
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.math.Vector2
|
||||||
import info.laht.threekt.objects.LineSegments
|
import info.laht.threekt.objects.LineSegments
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
|
import info.laht.threekt.renderers.WebGLRenderer
|
||||||
import info.laht.threekt.scenes.Scene
|
import info.laht.threekt.scenes.Scene
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import kotlinx.dom.clear
|
import kotlinx.dom.clear
|
||||||
|
import org.w3c.dom.HTMLCanvasElement
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
import org.w3c.dom.events.MouseEvent
|
import org.w3c.dom.events.MouseEvent
|
||||||
@ -41,7 +41,6 @@ import kotlin.math.sin
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class ThreeCanvas(
|
public class ThreeCanvas(
|
||||||
element: HTMLElement,
|
|
||||||
public val three: ThreePlugin,
|
public val three: ThreePlugin,
|
||||||
public val options: Canvas3DOptions,
|
public val options: Canvas3DOptions,
|
||||||
public val onClick: ((Name?) -> Unit)? = null,
|
public val onClick: ((Name?) -> Unit)? = null,
|
||||||
@ -69,7 +68,24 @@ public class ThreeCanvas(
|
|||||||
|
|
||||||
private var picked: Object3D? = null
|
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()
|
element.clear()
|
||||||
|
|
||||||
//Attach listener to track mouse changes
|
//Attach listener to track mouse changes
|
||||||
@ -86,12 +102,18 @@ public class ThreeCanvas(
|
|||||||
onClick?.invoke(picked?.fullName())
|
onClick?.invoke(picked?.fullName())
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
camera.aspect = 1.0
|
|
||||||
|
|
||||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||||
setClearColor(Colors.skyblue, 1)
|
setClearColor(Colors.skyblue, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val canvas = renderer.domElement as HTMLCanvasElement
|
||||||
|
|
||||||
|
canvas.style.apply {
|
||||||
|
width = "100%"
|
||||||
|
height = "100%"
|
||||||
|
display = "block"
|
||||||
|
}
|
||||||
|
|
||||||
addControls(renderer.domElement, options.controls)
|
addControls(renderer.domElement, options.controls)
|
||||||
|
|
||||||
fun animate() {
|
fun animate() {
|
||||||
@ -106,19 +128,15 @@ public class ThreeCanvas(
|
|||||||
window.requestAnimationFrame {
|
window.requestAnimationFrame {
|
||||||
animate()
|
animate()
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.render(scene, camera)
|
renderer.render(scene, camera)
|
||||||
}
|
}
|
||||||
|
|
||||||
element.appendChild(renderer.domElement)
|
element.appendChild(renderer.domElement)
|
||||||
|
renderer.resize()
|
||||||
|
|
||||||
renderer.setSize(
|
element.onresize = {
|
||||||
element.clientWidth.coerceIn(options.minWith.toInt()..options.maxWith.toInt()),
|
renderer.resize()
|
||||||
element.clientHeight.coerceIn(options.minHeight.toInt()..options.maxHeight.toInt())
|
|
||||||
)
|
|
||||||
|
|
||||||
window.onresize = {
|
|
||||||
renderer.setSize(element.clientWidth, element.clientWidth)
|
|
||||||
camera.updateProjectionMatrix()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
animate()
|
animate()
|
||||||
@ -250,7 +268,7 @@ public fun ThreePlugin.output(
|
|||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
spec: Canvas3DOptions = Canvas3DOptions.empty(),
|
spec: Canvas3DOptions = Canvas3DOptions.empty(),
|
||||||
onClick: ((Name?) -> Unit)? = null,
|
onClick: ((Name?) -> Unit)? = null,
|
||||||
): ThreeCanvas = ThreeCanvas(element, this, spec, onClick)
|
): ThreeCanvas = ThreeCanvas(this, spec, onClick).apply { attach(element) }
|
||||||
|
|
||||||
public fun ThreePlugin.render(
|
public fun ThreePlugin.render(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
|
@ -5,10 +5,7 @@ import hep.dataforge.meta.float
|
|||||||
import hep.dataforge.meta.get
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.node
|
import hep.dataforge.meta.node
|
||||||
import hep.dataforge.vision.solid.*
|
import hep.dataforge.vision.solid.*
|
||||||
import info.laht.threekt.core.BufferGeometry
|
import info.laht.threekt.core.*
|
||||||
import info.laht.threekt.core.DirectGeometry
|
|
||||||
import info.laht.threekt.core.Face3
|
|
||||||
import info.laht.threekt.core.Geometry
|
|
||||||
import info.laht.threekt.external.controls.OrbitControls
|
import info.laht.threekt.external.controls.OrbitControls
|
||||||
import info.laht.threekt.materials.Material
|
import info.laht.threekt.materials.Material
|
||||||
import info.laht.threekt.math.Euler
|
import info.laht.threekt.math.Euler
|
||||||
@ -77,3 +74,5 @@ internal fun Any.dispose() {
|
|||||||
is Texture -> 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