UI tune-up

This commit is contained in:
Alexander Nozik 2020-08-09 14:41:24 +03:00
parent 62a6eafdeb
commit de2ef1dcc5
10 changed files with 199 additions and 162 deletions

View File

@ -19,7 +19,7 @@ allprojects {
} }
group = "hep.dataforge" group = "hep.dataforge"
version = "0.1.5-dev" version = "0.1.5-dev-2"
} }
val githubProject by extra("visionforge") val githubProject by extra("visionforge")

View File

@ -7,10 +7,7 @@ import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.bootstrap.* import hep.dataforge.vision.bootstrap.*
import hep.dataforge.vision.gdml.toVision import hep.dataforge.vision.gdml.toVision
import hep.dataforge.vision.react.component import hep.dataforge.vision.react.*
import hep.dataforge.vision.react.configEditor
import hep.dataforge.vision.react.flexColumn
import hep.dataforge.vision.react.state
import hep.dataforge.vision.solid.Solid import hep.dataforge.vision.solid.Solid
import hep.dataforge.vision.solid.SolidGroup import hep.dataforge.vision.solid.SolidGroup
import hep.dataforge.vision.solid.specifications.Camera import hep.dataforge.vision.solid.specifications.Camera
@ -98,7 +95,7 @@ val GDMLApp = component<GDMLAppProps> { props ->
} }
} }
//tree //tree
card("Object tree", "overflow-auto") { card("Object tree") {
visual?.let { visual?.let {
objectTree(it, selected, select) objectTree(it, selected, select)
} }

View File

@ -6,9 +6,9 @@ import hep.dataforge.names.NameToken
import hep.dataforge.names.isEmpty import hep.dataforge.names.isEmpty
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.bootstrap.card import hep.dataforge.vision.bootstrap.card
import hep.dataforge.vision.bootstrap.objectTree
import hep.dataforge.vision.react.component import hep.dataforge.vision.react.component
import hep.dataforge.vision.react.configEditor import hep.dataforge.vision.react.configEditor
import hep.dataforge.vision.react.objectTree
import hep.dataforge.vision.react.state import hep.dataforge.vision.react.state
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

View File

@ -2,8 +2,8 @@ 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.names.Name import hep.dataforge.names.Name
import hep.dataforge.vision.bootstrap.objectTree
import hep.dataforge.vision.bootstrap.visualPropertyEditor import hep.dataforge.vision.bootstrap.visualPropertyEditor
import hep.dataforge.vision.react.objectTree
import hep.dataforge.vision.solid.Point3D import hep.dataforge.vision.solid.Point3D
import hep.dataforge.vision.solid.SolidGroup import hep.dataforge.vision.solid.SolidGroup
import hep.dataforge.vision.solid.box import hep.dataforge.vision.solid.box

View File

@ -7,11 +7,8 @@ import hep.dataforge.names.NameToken
import kotlinx.html.classes import kotlinx.html.classes
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.RBuilder import react.*
import react.RComponent
import react.RProps
import react.dom.* import react.dom.*
import react.setState
interface MetaViewerProps : RProps { interface MetaViewerProps : RProps {
var name: NameToken var name: NameToken
@ -19,6 +16,12 @@ interface MetaViewerProps : RProps {
var descriptor: NodeDescriptor? var descriptor: NodeDescriptor?
} }
interface TreeState : RState {
var expanded: Boolean
}
@Deprecated("To be replaced by react functional component")
class MetaViewerComponent : RComponent<MetaViewerProps, TreeState>() { class MetaViewerComponent : RComponent<MetaViewerProps, TreeState>() {
override fun TreeState.init() { override fun TreeState.init() {

View File

@ -2,12 +2,16 @@ package hep.dataforge.vision.bootstrap
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.vision.Vision
import hep.dataforge.vision.react.ObjectTree
import kotlinx.html.* import kotlinx.html.*
import kotlinx.html.js.div import kotlinx.html.js.div
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import react.RBuilder import react.RBuilder
import react.ReactElement import react.ReactElement
import react.child
import react.dom.* import react.dom.*
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) { inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
@ -205,4 +209,20 @@ inline fun RBuilder.gridRow(
block: RDOMBuilder<DIV>.() -> Unit block: RDOMBuilder<DIV>.() -> Unit
): ReactElement { ): ReactElement {
return div(joinStyles(classes, "row"), block) return div(joinStyles(classes, "row"), block)
}
fun Element.renderObjectTree(
vision: Vision,
clickCallback: (Name) -> Unit = {}
) = render(this) {
card("Object tree") {
child(ObjectTree) {
attrs {
this.name = Name.EMPTY
this.obj = vision
this.selected = null
this.clickCallback = clickCallback
}
}
}
} }

View File

@ -6,13 +6,10 @@ import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.values.Value import hep.dataforge.values.Value
import kotlinx.css.*
import kotlinx.css.properties.TextDecoration
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.* import react.*
import react.dom.div
import react.dom.render import react.dom.render
import styled.* import styled.*
@ -86,7 +83,10 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
when (actualItem) { when (actualItem) {
is MetaItem.NodeItem -> { is MetaItem.NodeItem -> {
div { styledDiv {
css {
+TreeStyles.treeLeaf
}
styledSpan { styledSpan {
css { css {
+TreeStyles.treeCaret +TreeStyles.treeCaret
@ -145,15 +145,14 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
styledDiv { styledDiv {
css { css {
+TreeStyles.treeLeaf +TreeStyles.treeLeaf
justifyContent = JustifyContent.flexEnd // justifyContent = JustifyContent.flexEnd
} }
styledDiv { styledDiv {
css { css {
flexGrow = 1.0 +TreeStyles.treeLabel
} }
styledSpan { styledSpan {
css { css {
+TreeStyles.treeLabel
if (item == null) { if (item == null) {
+TreeStyles.treeLabelInactive +TreeStyles.treeLabelInactive
} }
@ -162,6 +161,9 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
} }
} }
styledDiv { styledDiv {
css {
+TreeStyles.resizeableInput
}
valueChooser( valueChooser(
props.name, props.name,
actualItem, actualItem,
@ -169,37 +171,20 @@ private fun RFBuilder.configEditorItem(props: ConfigEditorItemProps) {
valueChanged valueChanged
) )
} }
styledDiv { styledButton {
css { css {
flexShrink = 1.0 +TreeStyles.removeButton
} }
styledButton { +"\u00D7"
css { attrs {
backgroundColor = Color.white if (item == null) {
borderStyle = BorderStyle.solid disabled = true
borderRadius = 2.px } else {
padding(1.px, 5.px) onClickFunction = removeClick
marginLeft = 4.px
textAlign = TextAlign.center
textDecoration = TextDecoration.none
display = Display.inlineBlock
cursor = Cursor.pointer
disabled {
cursor = Cursor.auto
borderStyle = BorderStyle.dashed
color = Color.lightGray
}
}
+"\u00D7"
attrs {
if (item == null) {
disabled = true
} else {
onClickFunction = removeClick
}
} }
} }
} }
} }
} }
} }

View File

@ -1,4 +1,4 @@
package hep.dataforge.vision.bootstrap package hep.dataforge.vision.react
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.plus import hep.dataforge.names.plus
@ -6,14 +6,10 @@ import hep.dataforge.names.startsWith
import hep.dataforge.vision.Vision import hep.dataforge.vision.Vision
import hep.dataforge.vision.VisionGroup import hep.dataforge.vision.VisionGroup
import hep.dataforge.vision.isEmpty import hep.dataforge.vision.isEmpty
import hep.dataforge.vision.react.RFBuilder
import hep.dataforge.vision.react.component
import kotlinx.html.classes
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.* import react.*
import react.dom.* import styled.*
interface ObjectTreeProps : RProps { interface ObjectTreeProps : RProps {
var name: Name var name: Name
@ -22,24 +18,25 @@ interface ObjectTreeProps : RProps {
var clickCallback: (Name) -> Unit var clickCallback: (Name) -> Unit
} }
interface TreeState : RState {
var expanded: Boolean
}
private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit { private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
var expanded: Boolean by useState{ props.selected?.startsWith(props.name) ?: false } var expanded: Boolean by useState { props.selected?.startsWith(props.name) ?: false }
val onClick: (Event) -> Unit = { val onClick: (Event) -> Unit = {
expanded = !expanded expanded = !expanded
} }
fun RBuilder.treeLabel(text: String) { fun RBuilder.treeLabel(text: String) {
button(classes = "btn btn-link align-middle tree-label p-0") { styledButton {
css {
//classes = mutableListOf("btn", "btn-link", "align-middle", "text-truncate", "p-0")
+TreeStyles.treeLabel
+TreeStyles.linkButton
if (props.name == props.selected) {
+TreeStyles.treeLabelSelected
}
}
+text +text
attrs { attrs {
if (props.name == props.selected) {
classes += "tree-label-selected"
}
onClickFunction = { props.clickCallback(props.name) } onClickFunction = { props.clickCallback(props.name) }
} }
} }
@ -50,13 +47,19 @@ private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
//display as node if any child is visible //display as node if any child is visible
if (obj is VisionGroup) { if (obj is VisionGroup) {
div("d-block text-truncate") { styledDiv {
css {
+TreeStyles.treeLeaf
}
if (obj.children.any { !it.key.body.startsWith("@") }) { if (obj.children.any { !it.key.body.startsWith("@") }) {
span("tree-caret") { styledSpan {
attrs { css {
+TreeStyles.treeCaret
if (expanded) { if (expanded) {
classes += "tree-caret-down" +TreeStyles.treeCaredDown
} }
}
attrs {
onClickFunction = onClick onClickFunction = onClick
} }
} }
@ -64,12 +67,18 @@ private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
treeLabel(token) treeLabel(token)
} }
if (expanded) { if (expanded) {
ul("tree") { styledUl {
css {
+TreeStyles.tree
}
obj.children.entries obj.children.entries
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children .filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisionGroup)?.isEmpty ?: true } .sortedBy { (it.value as? VisionGroup)?.isEmpty ?: true } // ignore empty groups
.forEach { (childToken, child) -> .forEach { (childToken, child) ->
li("tree-item") { styledLi {
css {
+TreeStyles.treeItem
}
child(ObjectTree) { child(ObjectTree) {
attrs { attrs {
this.name = props.name + childToken this.name = props.name + childToken
@ -83,8 +92,10 @@ private fun RFBuilder.objectTree(props: ObjectTreeProps): Unit {
} }
} }
} else { } else {
div("d-block text-truncate") { styledDiv {
span("tree-leaf") {} css {
+TreeStyles.treeLeaf
}
treeLabel(token) treeLabel(token)
} }
} }
@ -94,22 +105,6 @@ val ObjectTree: FunctionalComponent<ObjectTreeProps> = component { props ->
objectTree(props) objectTree(props)
} }
fun Element.renderObjectTree(
vision: Vision,
clickCallback: (Name) -> Unit = {}
) = render(this) {
card("Object tree") {
child(ObjectTree) {
attrs {
this.name = Name.EMPTY
this.obj = vision
this.selected = null
this.clickCallback = clickCallback
}
}
}
}
fun RBuilder.objectTree( fun RBuilder.objectTree(
vision: Vision, vision: Vision,
selected: Name? = null, selected: Name? = null,

View File

@ -1,8 +1,7 @@
package hep.dataforge.vision.react package hep.dataforge.vision.react
import kotlinx.css.* import kotlinx.css.*
import kotlinx.css.properties.deg import kotlinx.css.properties.*
import kotlinx.css.properties.rotate
import styled.StyleSheet import styled.StyleSheet
object TreeStyles : StyleSheet("treeStyles", true) { object TreeStyles : StyleSheet("treeStyles", true) {
@ -30,22 +29,6 @@ object TreeStyles : StyleSheet("treeStyles", true) {
} }
} }
val treeItem by css {
alignItems = Align.center
paddingLeft = 10.px
borderLeftStyle = BorderStyle.dashed
borderLeftWidth = 1.px
borderLeftColor = Color.lightGray
}
val treeLeaf by css {
display = Display.flex
flexDirection = FlexDirection.row
userSelect = UserSelect.none
alignItems = Align.center
}
/** /**
* Rotate the caret/arrow icon when clicked on (using JavaScript) * Rotate the caret/arrow icon when clicked on (using JavaScript)
*/ */
@ -59,8 +42,27 @@ object TreeStyles : StyleSheet("treeStyles", true) {
} }
} }
val treeItem by css {
alignItems = Align.center
paddingLeft = 10.px
borderLeftStyle = BorderStyle.dashed
borderLeftWidth = 1.px
borderLeftColor = Color.lightGray
borderBottomStyle = BorderStyle.dashed
borderBottomWidth = 1.px
borderBottomColor = Color.lightGray
}
val treeLeaf by css {
display = Display.flex
flexDirection = FlexDirection.row
flexWrap = FlexWrap.nowrap
//alignItems = Align.center
}
val treeLabel by css { val treeLabel by css {
overflow = Overflow.hidden overflow = Overflow.hidden
flex(flexGrow = 1.0, flexShrink = 1.0)
} }
val treeLabelInactive by css { val treeLabelInactive by css {
@ -71,4 +73,49 @@ object TreeStyles : StyleSheet("treeStyles", true) {
backgroundColor = Color.lightBlue backgroundColor = Color.lightBlue
} }
val linkButton by css {
backgroundColor = Color.white
border = "none"
padding(left = 4.pt, right = 4.pt, top = 0.pt, bottom = 0.pt)
textAlign = TextAlign.left
fontFamily = "arial,sans-serif"
color = Color("#069")
cursor = Cursor.pointer
hover {
textDecoration(TextDecorationLine.underline)
}
}
val removeButton by css {
backgroundColor = Color.white
borderStyle = BorderStyle.solid
borderRadius = 2.px
padding(1.px, 5.px)
marginLeft = 4.px
textAlign = TextAlign.center
textDecoration = TextDecoration.none
display = Display.inlineBlock
flexShrink = 1.0
cursor = Cursor.pointer
disabled {
cursor = Cursor.auto
borderStyle = BorderStyle.dashed
color = Color.lightGray
}
}
val resizeableInput by css {
overflow = Overflow.hidden
maxWidth = 120.pt
flex(flexGrow = 2.0, flexShrink = 2.0, flexBasis = 60.pt)
input {
textAlign = TextAlign.right
width = 100.pct
}
select{
textAlign = TextAlign.right
width = 100.pct
}
}
} }

View File

@ -6,8 +6,6 @@ import hep.dataforge.names.Name
import hep.dataforge.values.* import hep.dataforge.values.*
import hep.dataforge.vision.Colors import hep.dataforge.vision.Colors
import hep.dataforge.vision.widgetType import hep.dataforge.vision.widgetType
import kotlinx.css.Align
import kotlinx.css.alignSelf
import kotlinx.html.InputType import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onKeyDownFunction import kotlinx.html.js.onKeyDownFunction
@ -17,11 +15,9 @@ import org.w3c.dom.HTMLSelectElement
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import react.* import react.*
import react.dom.defaultValue import react.dom.defaultValue
import react.dom.input
import react.dom.option import react.dom.option
import react.dom.select import styled.styledInput
import styled.css import styled.styledSelect
import styled.styledDiv
interface ValueChooserProps : RProps { interface ValueChooserProps : RProps {
var item: MetaItem<*>? var item: MetaItem<*>?
@ -75,7 +71,7 @@ class ValueChooserComponent(props: ValueChooserProps) : RComponent<ValueChooserP
} }
} }
private fun RBuilder.stringInput() = input(type = InputType.text) { private fun RBuilder.stringInput() = styledInput(type = InputType.text) {
attrs { attrs {
this.defaultValue = props.item?.string ?: "" this.defaultValue = props.item?.string ?: ""
onKeyDownFunction = keyDown onKeyDownFunction = keyDown
@ -84,67 +80,61 @@ class ValueChooserComponent(props: ValueChooserProps) : RComponent<ValueChooserP
} }
override fun RBuilder.render() { override fun RBuilder.render() {
styledDiv { val descriptor = props.descriptor
css { val type = descriptor?.type?.firstOrNull()
alignSelf = Align.center when {
state.rawInput == true -> stringInput()
descriptor?.widgetType == "color" -> styledInput(type = InputType.color) {
ref = element
attrs {
this.defaultValue = props.item?.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string
} ?: "#000000"
onChangeFunction = commit
}
} }
val descriptor = props.descriptor type == ValueType.BOOLEAN -> {
val type = descriptor?.type?.firstOrNull() styledInput(type = InputType.checkBox) {
when {
state.rawInput == true -> stringInput()
descriptor?.widgetType == "color" -> input(type = InputType.color) {
ref = element ref = element
attrs { attrs {
this.defaultValue = props.item?.value?.let { value -> defaultChecked = props.item?.boolean ?: false
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string
} ?: "#000000"
onChangeFunction = commit onChangeFunction = commit
} }
} }
type == ValueType.BOOLEAN -> {
input(type = InputType.checkBox) {
ref = element
attrs {
defaultChecked = props.item?.boolean ?: false
onChangeFunction = commit
}
}
}
type == ValueType.NUMBER -> input(type = InputType.number) {
ref = element
attrs {
descriptor.attributes["step"].string?.let {
step = it
}
descriptor.attributes["min"].string?.let {
min = it
}
descriptor.attributes["max"].string?.let {
max = it
}
defaultValue = props.item?.string ?: ""
onKeyDownFunction = keyDown
}
}
descriptor?.allowedValues?.isNotEmpty() ?: false -> select {
descriptor!!.allowedValues.forEach {
option {
+it.string
}
}
ref = element
attrs {
this.value = props.item?.string ?: ""
multiple = false
onChangeFunction = commit
}
}
else -> stringInput()
} }
type == ValueType.NUMBER -> styledInput(type = InputType.number) {
ref = element
attrs {
descriptor.attributes["step"].string?.let {
step = it
}
descriptor.attributes["min"].string?.let {
min = it
}
descriptor.attributes["max"].string?.let {
max = it
}
defaultValue = props.item?.string ?: ""
onKeyDownFunction = keyDown
}
}
descriptor?.allowedValues?.isNotEmpty() ?: false -> styledSelect {
descriptor!!.allowedValues.forEach {
option {
+it.string
}
}
ref = element
attrs {
this.value = props.item?.string ?: ""
multiple = false
onChangeFunction = commit
}
}
else -> stringInput()
} }
} }
} }
internal fun RBuilder.valueChooser( internal fun RBuilder.valueChooser(