Add compose-html
This commit is contained in:
parent
f0048a4d46
commit
ed71ba9ccb
@ -45,7 +45,7 @@ include(
|
|||||||
":ui:ring",
|
":ui:ring",
|
||||||
// ":ui:material",
|
// ":ui:material",
|
||||||
":ui:bootstrap",
|
":ui:bootstrap",
|
||||||
// ":ui:compose",
|
":ui:compose",
|
||||||
":visionforge-core",
|
":visionforge-core",
|
||||||
":visionforge-solid",
|
":visionforge-solid",
|
||||||
// ":visionforge-fx",
|
// ":visionforge-fx",
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import kotlinx.html.js.onClickFunction
|
|
||||||
import org.jetbrains.compose.web.css.AlignItems
|
import org.jetbrains.compose.web.css.AlignItems
|
||||||
import org.jetbrains.compose.web.css.alignItems
|
import org.jetbrains.compose.web.css.alignItems
|
||||||
|
import org.jetbrains.compose.web.dom.A
|
||||||
|
import org.jetbrains.compose.web.dom.Div
|
||||||
import org.jetbrains.compose.web.dom.Span
|
import org.jetbrains.compose.web.dom.Span
|
||||||
import org.w3c.dom.events.Event
|
import org.jetbrains.compose.web.dom.Text
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||||
import space.kscience.dataforge.meta.descriptors.get
|
import space.kscience.dataforge.meta.descriptors.get
|
||||||
@ -17,10 +18,6 @@ import space.kscience.dataforge.names.lastOrNull
|
|||||||
import space.kscience.dataforge.names.plus
|
import space.kscience.dataforge.names.plus
|
||||||
|
|
||||||
|
|
||||||
private val MetaViewerItem: FC<MetaViewerProps> = fc("MetaViewerItem") { props ->
|
|
||||||
metaViewerItem(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MetaViewerItem(root: Meta, name: Name, rootDescriptor: MetaDescriptor? = null) {
|
private fun MetaViewerItem(root: Meta, name: Name, rootDescriptor: MetaDescriptor? = null) {
|
||||||
var expanded: Boolean by remember { mutableStateOf(true) }
|
var expanded: Boolean by remember { mutableStateOf(true) }
|
||||||
@ -29,11 +26,7 @@ private fun MetaViewerItem(root: Meta, name: Name, rootDescriptor: MetaDescripto
|
|||||||
val actualValue = item?.value ?: descriptorItem?.defaultValue
|
val actualValue = item?.value ?: descriptorItem?.defaultValue
|
||||||
val actualMeta = item ?: descriptorItem?.defaultNode
|
val actualMeta = item ?: descriptorItem?.defaultNode
|
||||||
|
|
||||||
val token = name.lastOrNull()?.toString() ?: props.rootName ?: ""
|
val token = name.lastOrNull()?.toString() ?: ""
|
||||||
|
|
||||||
val expanderClick: (Event) -> Unit = {
|
|
||||||
expanded = !expanded
|
|
||||||
}
|
|
||||||
|
|
||||||
FlexRow(attrs = {
|
FlexRow(attrs = {
|
||||||
classes("metaItem")
|
classes("metaItem")
|
||||||
@ -42,42 +35,34 @@ private fun MetaViewerItem(root: Meta, name: Name, rootDescriptor: MetaDescripto
|
|||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
if (actualMeta?.isLeaf == false) {
|
if (actualMeta?.isLeaf == false) {
|
||||||
Span(attrs = {
|
Span({
|
||||||
|
classes(TreeStyles.treeCaret)
|
||||||
|
if (expanded) {
|
||||||
|
classes(TreeStyles.treeCaretDown)
|
||||||
|
}
|
||||||
|
onClick { expanded = !expanded }
|
||||||
})
|
})
|
||||||
styledSpan {
|
|
||||||
css {
|
|
||||||
+TreeStyles.treeCaret
|
|
||||||
if (expanded) {
|
|
||||||
+TreeStyles.treeCaredDown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attrs {
|
|
||||||
onClickFunction = expanderClick
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
styledSpan {
|
Span({
|
||||||
css {
|
classes(TreeStyles.treeLabel)
|
||||||
+TreeStyles.treeLabel
|
if (item == null) {
|
||||||
if (item == null) {
|
classes(TreeStyles.treeLabelInactive)
|
||||||
+TreeStyles.treeLabelInactive
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+token
|
}) {
|
||||||
|
Text(token)
|
||||||
}
|
}
|
||||||
styledDiv {
|
|
||||||
a {
|
Div {
|
||||||
+actualValue.toString()
|
A {
|
||||||
|
Text(actualValue.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
flexColumn {
|
FlexColumn({
|
||||||
css {
|
classes(TreeStyles.tree)
|
||||||
+TreeStyles.tree
|
}) {
|
||||||
}
|
|
||||||
val keys = buildSet {
|
val keys = buildSet {
|
||||||
descriptorItem?.children?.keys?.forEach {
|
descriptorItem?.children?.keys?.forEach {
|
||||||
add(NameToken(it))
|
add(NameToken(it))
|
||||||
@ -86,45 +71,17 @@ private fun MetaViewerItem(root: Meta, name: Name, rootDescriptor: MetaDescripto
|
|||||||
}
|
}
|
||||||
|
|
||||||
keys.filter { !it.body.startsWith("@") }.forEach { token ->
|
keys.filter { !it.body.startsWith("@") }.forEach { token ->
|
||||||
styledDiv {
|
Div({
|
||||||
css {
|
classes(TreeStyles.treeItem)
|
||||||
+TreeStyles.treeItem
|
}) {
|
||||||
}
|
MetaViewerItem(root, name + token, rootDescriptor)
|
||||||
child(MetaViewerItem) {
|
|
||||||
attrs {
|
|
||||||
this.key = props.name.toString()
|
|
||||||
this.root = props.root
|
|
||||||
this.name = props.name + token
|
|
||||||
this.descriptor = props.descriptor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//configEditor(props.root, props.name + token, props.descriptor, props.default)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsExport
|
@Composable
|
||||||
public val MetaViewer: FC<MetaViewerProps> = fc("MetaViewer") { props ->
|
public fun MetaViewer(meta: Meta, descriptor: MetaDescriptor? = null) {
|
||||||
child(MetaViewerItem) {
|
MetaViewerItem(meta, Name.EMPTY, descriptor)
|
||||||
attrs {
|
|
||||||
this.key = ""
|
|
||||||
this.root = props.root
|
|
||||||
this.name = Name.EMPTY
|
|
||||||
this.descriptor = props.descriptor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun RBuilder.metaViewer(meta: Meta, descriptor: MetaDescriptor? = null, key: Any? = null) {
|
|
||||||
child(MetaViewer) {
|
|
||||||
attrs {
|
|
||||||
this.key = key?.toString() ?: ""
|
|
||||||
this.root = meta
|
|
||||||
this.descriptor = descriptor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,189 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.jetbrains.compose.web.attributes.disabled
|
||||||
|
import org.jetbrains.compose.web.css.AlignItems
|
||||||
|
import org.jetbrains.compose.web.css.alignItems
|
||||||
|
import org.jetbrains.compose.web.css.px
|
||||||
|
import org.jetbrains.compose.web.css.width
|
||||||
|
import org.jetbrains.compose.web.dom.Button
|
||||||
|
import org.jetbrains.compose.web.dom.Div
|
||||||
|
import org.jetbrains.compose.web.dom.Span
|
||||||
|
import org.jetbrains.compose.web.dom.Text
|
||||||
|
import space.kscience.dataforge.meta.MutableMeta
|
||||||
|
import space.kscience.dataforge.meta.ObservableMutableMeta
|
||||||
|
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||||
|
import space.kscience.dataforge.meta.descriptors.ValueRequirement
|
||||||
|
import space.kscience.dataforge.meta.descriptors.get
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
|
import space.kscience.dataforge.meta.remove
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.NameToken
|
||||||
|
import space.kscience.dataforge.names.isEmpty
|
||||||
|
import space.kscience.dataforge.names.lastOrNull
|
||||||
|
import space.kscience.visionforge.hidden
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The display state of a property
|
||||||
|
*/
|
||||||
|
public sealed class EditorPropertyState {
|
||||||
|
public object Defined : EditorPropertyState()
|
||||||
|
public class Default(public val source: String = "unknown") : EditorPropertyState()
|
||||||
|
public object Undefined : EditorPropertyState()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param rootDescriptor Full path to the displayed node in [meta]. Could be empty
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun PropertyEditorItem(
|
||||||
|
/**
|
||||||
|
* Root config object - always non-null
|
||||||
|
*/
|
||||||
|
meta: MutableMeta,
|
||||||
|
getPropertyState: (Name) -> EditorPropertyState,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
updates: Flow<Name>,
|
||||||
|
name: Name,
|
||||||
|
rootDescriptor: MetaDescriptor?,
|
||||||
|
initialExpanded: Boolean? = null,
|
||||||
|
) {
|
||||||
|
var expanded: Boolean by remember { mutableStateOf(initialExpanded ?: true) }
|
||||||
|
val descriptor: MetaDescriptor? = remember(rootDescriptor, name) { rootDescriptor?.get(name) }
|
||||||
|
var property: MutableMeta by remember { mutableStateOf(meta.getOrCreate(name)) }
|
||||||
|
var editorPropertyState: EditorPropertyState by remember { mutableStateOf(getPropertyState(name)) }
|
||||||
|
|
||||||
|
|
||||||
|
val keys = remember(descriptor) {
|
||||||
|
buildSet {
|
||||||
|
descriptor?.children?.filterNot {
|
||||||
|
it.key.startsWith("@") || it.value.hidden
|
||||||
|
}?.forEach {
|
||||||
|
add(NameToken(it.key))
|
||||||
|
}
|
||||||
|
//ownProperty?.items?.keys?.filterNot { it.body.startsWith("@") }?.let { addAll(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val token = name.lastOrNull()?.toString() ?: "Properties"
|
||||||
|
|
||||||
|
fun update() {
|
||||||
|
property = meta.getOrCreate(name)
|
||||||
|
editorPropertyState = getPropertyState(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(meta) {
|
||||||
|
updates.collect { updatedName ->
|
||||||
|
if (updatedName == name) {
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlexRow({
|
||||||
|
style {
|
||||||
|
alignItems(AlignItems.Center)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
if (keys.isNotEmpty()) {
|
||||||
|
Span({
|
||||||
|
classes(TreeStyles.treeCaret)
|
||||||
|
if (expanded) {
|
||||||
|
classes(TreeStyles.treeCaretDown)
|
||||||
|
}
|
||||||
|
onClick { expanded = !expanded }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Span({
|
||||||
|
classes(TreeStyles.treeLabel)
|
||||||
|
if (editorPropertyState != EditorPropertyState.Defined) {
|
||||||
|
classes(TreeStyles.treeLabelInactive)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name.isEmpty() && descriptor?.valueRequirement != ValueRequirement.ABSENT) {
|
||||||
|
Div({
|
||||||
|
style {
|
||||||
|
width(160.px)
|
||||||
|
marginAll(1.px, 5.px)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
ValueChooser(descriptor, editorPropertyState, property.value) {
|
||||||
|
property.value = it
|
||||||
|
editorPropertyState = getPropertyState(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button({
|
||||||
|
classes(TreeStyles.propertyEditorButton)
|
||||||
|
if (editorPropertyState != EditorPropertyState.Defined) {
|
||||||
|
disabled()
|
||||||
|
} else {
|
||||||
|
onClick {
|
||||||
|
meta.remove(name)
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("\u00D7")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expanded) {
|
||||||
|
FlexColumn({
|
||||||
|
classes(TreeStyles.tree)
|
||||||
|
}) {
|
||||||
|
keys.forEach { token ->
|
||||||
|
Div({
|
||||||
|
classes(TreeStyles.treeItem)
|
||||||
|
}) {
|
||||||
|
PropertyEditorItem(meta, getPropertyState, scope, updates, name, descriptor, expanded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun PropertyEditor(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
properties: ObservableMutableMeta,
|
||||||
|
descriptor: MetaDescriptor? = null,
|
||||||
|
expanded: Boolean? = null,
|
||||||
|
) {
|
||||||
|
PropertyEditorItem(
|
||||||
|
meta = properties,
|
||||||
|
getPropertyState = { name ->
|
||||||
|
if (properties[name] != null) {
|
||||||
|
EditorPropertyState.Defined
|
||||||
|
} else if (descriptor?.get(name)?.defaultValue != null) {
|
||||||
|
EditorPropertyState.Default("descriptor")
|
||||||
|
} else {
|
||||||
|
EditorPropertyState.Undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scope = scope,
|
||||||
|
updates = callbackFlow {
|
||||||
|
properties.onChange(scope) { name ->
|
||||||
|
scope.launch {
|
||||||
|
send(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeOnClose {
|
||||||
|
properties.removeListener(scope)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name = Name.EMPTY,
|
||||||
|
rootDescriptor = descriptor,
|
||||||
|
initialExpanded = expanded,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import kotlinx.dom.clear
|
||||||
|
import org.jetbrains.compose.web.css.*
|
||||||
|
import org.jetbrains.compose.web.dom.Div
|
||||||
|
import space.kscience.dataforge.context.Context
|
||||||
|
import space.kscience.dataforge.context.request
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.visionforge.solid.Solid
|
||||||
|
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||||
|
import space.kscience.visionforge.solid.three.ThreeCanvas
|
||||||
|
import space.kscience.visionforge.solid.three.ThreePlugin
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun ThreeCanvas(
|
||||||
|
context: Context,
|
||||||
|
options: Canvas3DOptions?,
|
||||||
|
solid: Solid?,
|
||||||
|
selected: Name?,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val three: ThreePlugin by derivedStateOf { context.request(ThreePlugin) }
|
||||||
|
|
||||||
|
Div({
|
||||||
|
style {
|
||||||
|
maxWidth(100.vw)
|
||||||
|
maxHeight(100.vh)
|
||||||
|
width(100.percent)
|
||||||
|
height(100.percent)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
var canvas: ThreeCanvas? = null
|
||||||
|
DisposableEffect(options) {
|
||||||
|
canvas = ThreeCanvas(three, scopeElement, options ?: Canvas3DOptions())
|
||||||
|
onDispose {
|
||||||
|
scopeElement.clear()
|
||||||
|
canvas = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(solid) {
|
||||||
|
if (solid != null) {
|
||||||
|
canvas?.render(solid)
|
||||||
|
} else {
|
||||||
|
canvas?.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(selected) {
|
||||||
|
canvas?.select(selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
package space.kscience.visionforge.compose
|
|
||||||
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
public fun ThreeJs(){
|
|
||||||
Surface {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,10 @@
|
|||||||
package space.kscience.visionforge.compose
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
import kotlinx.css.*
|
import org.jetbrains.compose.web.ExperimentalComposeWebApi
|
||||||
import org.jetbrains.compose.web.css.*
|
import org.jetbrains.compose.web.css.*
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeWebApi::class)
|
||||||
public object TreeStyles : StyleSheet() {
|
public object TreeStyles : StyleSheet() {
|
||||||
/**
|
/**
|
||||||
* Remove default bullets
|
* Remove default bullets
|
||||||
@ -16,12 +18,12 @@ public object TreeStyles : StyleSheet() {
|
|||||||
/**
|
/**
|
||||||
* Style the caret/arrow
|
* Style the caret/arrow
|
||||||
*/
|
*/
|
||||||
public val treeCaret by style {
|
public val treeCaret: String by style {
|
||||||
cursor("pointer")
|
cursor("pointer")
|
||||||
userSelect = UserSelect.none
|
userSelect(UserSelect.none)
|
||||||
/* Create the caret/arrow with a unicode, and style it */
|
/* Create the caret/arrow with a unicode, and style it */
|
||||||
before {
|
before {
|
||||||
content = "\u25B6".quoted
|
content("\u25B6")
|
||||||
color(Color.black)
|
color(Color.black)
|
||||||
display(DisplayStyle.InlineBlock)
|
display(DisplayStyle.InlineBlock)
|
||||||
marginRight(6.px)
|
marginRight(6.px)
|
||||||
@ -31,9 +33,9 @@ public object TreeStyles : StyleSheet() {
|
|||||||
/**
|
/**
|
||||||
* Rotate the caret/arrow icon when clicked on (using JavaScript)
|
* Rotate the caret/arrow icon when clicked on (using JavaScript)
|
||||||
*/
|
*/
|
||||||
public val treeCaredDown by style {
|
public val treeCaretDown: String by style {
|
||||||
before {
|
before {
|
||||||
content = "\u25B6".quoted
|
content("\u25B6")
|
||||||
color(Color.black)
|
color(Color.black)
|
||||||
display(DisplayStyle.InlineBlock)
|
display(DisplayStyle.InlineBlock)
|
||||||
marginRight(6.px)
|
marginRight(6.px)
|
||||||
@ -45,7 +47,7 @@ public object TreeStyles : StyleSheet() {
|
|||||||
alignItems(AlignItems.Center)
|
alignItems(AlignItems.Center)
|
||||||
paddingLeft(10.px)
|
paddingLeft(10.px)
|
||||||
border {
|
border {
|
||||||
left{
|
left {
|
||||||
width(1.px)
|
width(1.px)
|
||||||
color(Color.lightgray)
|
color(Color.lightgray)
|
||||||
style = LineStyle.Dashed
|
style = LineStyle.Dashed
|
||||||
@ -53,19 +55,40 @@ public object TreeStyles : StyleSheet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public val treeLabel by style {
|
public val treeLabel: String by style {
|
||||||
border(style = LineStyle.None)
|
border(style = LineStyle.None)
|
||||||
padding(left = 4.pt, right = 4.pt, top = 0.pt, bottom = 0.pt)
|
paddingAll(left = 4.pt, right = 4.pt)
|
||||||
textAlign("left")
|
textAlign("left")
|
||||||
flex(1)
|
flex(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
public val treeLabelInactive: RuleSet by css {
|
public val treeLabelInactive: String by style {
|
||||||
color = Color.lightGray
|
color(Color.lightgray)
|
||||||
}
|
}
|
||||||
|
|
||||||
public val treeLabelSelected: RuleSet by css {
|
public val treeLabelSelected: String by style {
|
||||||
backgroundColor = Color.lightBlue
|
backgroundColor(Color.lightblue)
|
||||||
|
}
|
||||||
|
|
||||||
|
public val propertyEditorButton: String by style {
|
||||||
|
width(24.px)
|
||||||
|
alignSelf(AlignSelf.Stretch)
|
||||||
|
marginAll(1.px, 5.px)
|
||||||
|
backgroundColor(Color.white)
|
||||||
|
border{
|
||||||
|
style(LineStyle.Solid)
|
||||||
|
}
|
||||||
|
borderRadius(2.px)
|
||||||
|
textAlign("center")
|
||||||
|
textDecoration("none")
|
||||||
|
cursor("pointer")
|
||||||
|
disabled {
|
||||||
|
cursor("auto")
|
||||||
|
border{
|
||||||
|
style(LineStyle.Dashed)
|
||||||
|
}
|
||||||
|
color(Color.lightgray)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import org.jetbrains.compose.web.css.*
|
||||||
|
|
||||||
|
public enum class UserSelect {
|
||||||
|
inherit, initial, revert, revertLayer, unset,
|
||||||
|
|
||||||
|
none, auto, text, contain, all;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun StyleScope.userSelect(value: UserSelect) {
|
||||||
|
property("user-select", value.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun StyleScope.content(value: String) {
|
||||||
|
property("content", "'$value'")
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun StyleScope.paddingAll(
|
||||||
|
top: CSSNumeric = 0.pt,
|
||||||
|
right: CSSNumeric = top,
|
||||||
|
bottom: CSSNumeric = top,
|
||||||
|
left: CSSNumeric = right,
|
||||||
|
) {
|
||||||
|
padding(top, right, bottom, left)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun StyleScope.marginAll(
|
||||||
|
top: CSSNumeric = 0.pt,
|
||||||
|
right: CSSNumeric = top,
|
||||||
|
bottom: CSSNumeric = top,
|
||||||
|
left: CSSNumeric = right,
|
||||||
|
) {
|
||||||
|
margin(top, right, bottom, left)
|
||||||
|
}
|
@ -0,0 +1,268 @@
|
|||||||
|
@file:Suppress("UNUSED_PARAMETER")
|
||||||
|
|
||||||
|
package space.kscience.visionforge.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import org.jetbrains.compose.web.attributes.*
|
||||||
|
import org.jetbrains.compose.web.css.percent
|
||||||
|
import org.jetbrains.compose.web.css.px
|
||||||
|
import org.jetbrains.compose.web.css.width
|
||||||
|
import org.jetbrains.compose.web.dom.Input
|
||||||
|
import org.jetbrains.compose.web.dom.Option
|
||||||
|
import org.jetbrains.compose.web.dom.Select
|
||||||
|
import org.jetbrains.compose.web.dom.Text
|
||||||
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
import org.w3c.dom.HTMLOptionElement
|
||||||
|
import org.w3c.dom.asList
|
||||||
|
import space.kscience.dataforge.meta.*
|
||||||
|
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||||
|
import space.kscience.dataforge.meta.descriptors.ValueRequirement
|
||||||
|
import space.kscience.dataforge.meta.descriptors.allowedValues
|
||||||
|
import space.kscience.visionforge.Colors
|
||||||
|
import space.kscience.visionforge.widgetType
|
||||||
|
import three.math.Color
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun StringValueChooser(
|
||||||
|
descriptor: MetaDescriptor?,
|
||||||
|
state: EditorPropertyState,
|
||||||
|
value: Value?,
|
||||||
|
onValueChange: (Value?) -> Unit,
|
||||||
|
) {
|
||||||
|
var stringValue by remember { mutableStateOf(value?.string ?: "") }
|
||||||
|
Input(type = InputType.Text) {
|
||||||
|
style {
|
||||||
|
width(100.percent)
|
||||||
|
}
|
||||||
|
value(stringValue)
|
||||||
|
onKeyDown { event ->
|
||||||
|
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||||
|
stringValue = (event.target as HTMLInputElement).value
|
||||||
|
onValueChange(stringValue.asValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onChange {
|
||||||
|
stringValue = it.target.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun BooleanValueChooser(
|
||||||
|
descriptor: MetaDescriptor?,
|
||||||
|
state: EditorPropertyState,
|
||||||
|
value: Value?,
|
||||||
|
onValueChange: (Value?) -> Unit,
|
||||||
|
) {
|
||||||
|
Input(type = InputType.Checkbox) {
|
||||||
|
style {
|
||||||
|
width(100.percent)
|
||||||
|
}
|
||||||
|
//this.attributes["indeterminate"] = (props.item == null).toString()
|
||||||
|
checked(value?.boolean ?: false)
|
||||||
|
|
||||||
|
onChange {
|
||||||
|
val newValue = it.target.checked
|
||||||
|
onValueChange(newValue.asValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun NumberValueChooser(
|
||||||
|
descriptor: MetaDescriptor?,
|
||||||
|
state: EditorPropertyState,
|
||||||
|
value: Value?,
|
||||||
|
onValueChange: (Value?) -> Unit,
|
||||||
|
) {
|
||||||
|
var innerValue by remember { mutableStateOf(value?.string ?: "") }
|
||||||
|
Input(type = InputType.Number) {
|
||||||
|
style {
|
||||||
|
width(100.percent)
|
||||||
|
}
|
||||||
|
value(innerValue)
|
||||||
|
onKeyDown { event ->
|
||||||
|
if (event.type == "keydown" && event.asDynamic().key == "Enter") {
|
||||||
|
innerValue = (event.target as HTMLInputElement).value
|
||||||
|
val number = innerValue.toDoubleOrNull()
|
||||||
|
if (number == null) {
|
||||||
|
console.error("The input value $innerValue is not a number")
|
||||||
|
} else {
|
||||||
|
onValueChange(number.asValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onChange {
|
||||||
|
innerValue = it.target.value
|
||||||
|
}
|
||||||
|
descriptor?.attributes?.get("step").number?.let {
|
||||||
|
step(it)
|
||||||
|
}
|
||||||
|
descriptor?.attributes?.get("min").string?.let {
|
||||||
|
min(it)
|
||||||
|
}
|
||||||
|
descriptor?.attributes?.get("max").string?.let {
|
||||||
|
max(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun ComboValueChooser(
|
||||||
|
descriptor: MetaDescriptor?,
|
||||||
|
state: EditorPropertyState,
|
||||||
|
value: Value?,
|
||||||
|
onValueChange: (Value?) -> Unit,
|
||||||
|
) {
|
||||||
|
var selected by remember { mutableStateOf(value?.string ?: "") }
|
||||||
|
Select({
|
||||||
|
style {
|
||||||
|
width(100.percent)
|
||||||
|
}
|
||||||
|
onChange {
|
||||||
|
selected = it.target.value
|
||||||
|
onValueChange(selected.asValue())
|
||||||
|
}
|
||||||
|
}, multiple = false) {
|
||||||
|
descriptor?.allowedValues?.forEach {
|
||||||
|
Option(it.string, { if (it == value) selected() }) {
|
||||||
|
Text(it.string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun ColorValueChooser(
|
||||||
|
descriptor: MetaDescriptor?,
|
||||||
|
state: EditorPropertyState,
|
||||||
|
value: Value?,
|
||||||
|
onValueChange: (Value?) -> Unit,
|
||||||
|
) {
|
||||||
|
Input(type = InputType.Color) {
|
||||||
|
style {
|
||||||
|
width(100.percent)
|
||||||
|
marginAll(0.px)
|
||||||
|
}
|
||||||
|
value(
|
||||||
|
value?.let { value ->
|
||||||
|
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
|
||||||
|
else "#" + Color(value.string).getHexString()
|
||||||
|
} ?: "#000000"
|
||||||
|
)
|
||||||
|
onChange {
|
||||||
|
onValueChange(it.target.value.asValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun MultiSelectChooser(
|
||||||
|
descriptor: MetaDescriptor?,
|
||||||
|
state: EditorPropertyState,
|
||||||
|
value: Value?,
|
||||||
|
onValueChange: (Value?) -> Unit,
|
||||||
|
) {
|
||||||
|
Select({
|
||||||
|
onChange { event ->
|
||||||
|
val newSelected = event.target.selectedOptions.asList()
|
||||||
|
.map { (it as HTMLOptionElement).value.asValue() }
|
||||||
|
onValueChange(newSelected.asValue())
|
||||||
|
|
||||||
|
}
|
||||||
|
}, multiple = true) {
|
||||||
|
descriptor?.allowedValues?.forEach { optionValue ->
|
||||||
|
Option(optionValue.string, {
|
||||||
|
value?.list?.let { if (optionValue in it) selected() }
|
||||||
|
}) {
|
||||||
|
Text(optionValue.string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun RangeValueChooser(
|
||||||
|
descriptor: MetaDescriptor?,
|
||||||
|
state: EditorPropertyState,
|
||||||
|
value: Value?,
|
||||||
|
onValueChange: (Value?) -> Unit,
|
||||||
|
) {
|
||||||
|
var innerValue by remember { mutableStateOf(value?.double) }
|
||||||
|
var rangeDisabled: Boolean by remember { mutableStateOf(state != EditorPropertyState.Defined) }
|
||||||
|
|
||||||
|
|
||||||
|
FlexRow {
|
||||||
|
if (descriptor?.valueRequirement != ValueRequirement.REQUIRED) {
|
||||||
|
Input(type = InputType.Checkbox) {
|
||||||
|
if (!rangeDisabled) defaultChecked()
|
||||||
|
|
||||||
|
onChange {
|
||||||
|
val checkBoxValue = it.target.checked
|
||||||
|
rangeDisabled = !checkBoxValue
|
||||||
|
onValueChange(
|
||||||
|
if (!checkBoxValue) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
innerValue?.asValue()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Input(type = InputType.Range) {
|
||||||
|
style {
|
||||||
|
width(100.percent)
|
||||||
|
}
|
||||||
|
if (rangeDisabled) disabled()
|
||||||
|
value(innerValue?.toString() ?: "")
|
||||||
|
onChange {
|
||||||
|
val newValue = it.target.value
|
||||||
|
onValueChange(newValue.toDoubleOrNull()?.asValue())
|
||||||
|
innerValue = newValue.toDoubleOrNull()
|
||||||
|
}
|
||||||
|
descriptor?.attributes?.get("min").string?.let {
|
||||||
|
min(it)
|
||||||
|
}
|
||||||
|
descriptor?.attributes?.get("max").string?.let {
|
||||||
|
max(it)
|
||||||
|
}
|
||||||
|
descriptor?.attributes?.get("step").number?.let {
|
||||||
|
step(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun ValueChooser(
|
||||||
|
descriptor: MetaDescriptor?,
|
||||||
|
state: EditorPropertyState,
|
||||||
|
value: Value?,
|
||||||
|
onValueChange: (Value?) -> Unit,
|
||||||
|
) {
|
||||||
|
val rawInput by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val type = descriptor?.valueTypes?.firstOrNull()
|
||||||
|
|
||||||
|
when {
|
||||||
|
rawInput -> StringValueChooser(descriptor, state, value, onValueChange)
|
||||||
|
descriptor?.widgetType == "color" -> ColorValueChooser(descriptor, state, value, onValueChange)
|
||||||
|
descriptor?.widgetType == "multiSelect" -> MultiSelectChooser(descriptor, state, value, onValueChange)
|
||||||
|
descriptor?.widgetType == "range" -> RangeValueChooser(descriptor, state, value, onValueChange)
|
||||||
|
type == ValueType.BOOLEAN -> BooleanValueChooser(descriptor, state, value, onValueChange)
|
||||||
|
type == ValueType.NUMBER -> NumberValueChooser(descriptor, state, value, onValueChange)
|
||||||
|
descriptor?.allowedValues?.isNotEmpty() ?: false -> ComboValueChooser(descriptor, state, value, onValueChange)
|
||||||
|
//TODO handle lists
|
||||||
|
else -> StringValueChooser(descriptor, state, value, onValueChange)
|
||||||
|
}
|
||||||
|
}
|
@ -47,7 +47,7 @@ public interface Vision : Described {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun onMetaEvent(meta: Meta){
|
public fun onMetaEvent(meta: Meta){
|
||||||
|
//Do nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user