Add randomization, saving and loading of parameters in orx-gui

This commit is contained in:
Edwin Jakobs
2020-02-06 23:38:48 +01:00
parent 5111bd6fb8
commit 8dd4f92366
2 changed files with 197 additions and 18 deletions

View File

@@ -1,4 +1,6 @@
dependencies { dependencies {
compile project(":orx-parameters") compile project(":orx-parameters")
compile "org.openrndr.panel:openrndr-panel:$panelVersion" compile "org.openrndr.panel:openrndr-panel:$panelVersion"
compile "org.openrndr:openrndr-dialogs:$openrndrVersion"
compile "com.google.code.gson:gson:$gsonVersion"
} }

View File

@@ -1,19 +1,29 @@
package org.openrndr.extra.gui package org.openrndr.extra.gui
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.openrndr.Extension import org.openrndr.Extension
import org.openrndr.KEY_F11 import org.openrndr.KEY_F11
import org.openrndr.Program import org.openrndr.Program
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.dialogs.openFileDialog
import org.openrndr.dialogs.saveFileDialog
import org.openrndr.extra.parameters.* import org.openrndr.extra.parameters.*
import org.openrndr.internal.Driver import org.openrndr.internal.Driver
import org.openrndr.panel.ControlManager import org.openrndr.panel.ControlManager
import org.openrndr.panel.controlManager import org.openrndr.panel.controlManager
import org.openrndr.panel.elements.* import org.openrndr.panel.elements.*
import org.openrndr.panel.style.* import org.openrndr.panel.style.*
import java.io.File
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
private data class LabeledObject(val label: String, val obj: Any) private data class LabeledObject(val label: String, val obj: Any)
private class CompartmentState(var collapsed: Boolean = false, val parameterValues: MutableMap<String, Any> = mutableMapOf()) private class CompartmentState(var collapsed: Boolean = false, val parameterValues: MutableMap<String, Any> = mutableMapOf())
private class TrackedObjectBinding(
val parameters: List<Parameter>,
val parameterControls: MutableMap<Parameter, Element> = mutableMapOf()
)
private val persistentCompartmentStates = mutableMapOf<Long, MutableMap<String, CompartmentState>>() private val persistentCompartmentStates = mutableMapOf<Long, MutableMap<String, CompartmentState>>()
@@ -55,11 +65,19 @@ class GUI : Extension {
panel = program.controlManager { panel = program.controlManager {
styleSheet(has class_ "container") { styleSheet(has class_ "container") {
this.display = Display.FLEX this.display = Display.FLEX
this.flexDirection = FlexDirection.Row this.flexDirection = FlexDirection.Column
this.width = 200.px this.width = 200.px
this.height = 100.percent this.height = 100.percent
} }
styleSheet(has class_ "toolbar") {
this.height = 30.px
this.width = 100.percent
this.display = Display.FLEX
this.flexDirection = FlexDirection.Row
this.background = Color.RGBa(ColorRGBa.GRAY.copy(a = 0.99))
}
styleSheet(has class_ "collapsed") { styleSheet(has class_ "collapsed") {
this.display = Display.NONE this.display = Display.NONE
} }
@@ -100,26 +118,45 @@ class GUI : Extension {
this.width = 175.px this.width = 175.px
} }
styleSheet(has class_ "filter") {
this.width = 185.px
this.paddingBottom = 20.px
this.paddingTop = 10.px
this.marginRight = 2.px
this.paddingRight = 5.px
}
layout { layout {
div("container") { div("container") {
id = "container" id = "container"
div("toolbar") {
button {
label = "Randomize"
clicked {
randomize()
}
}
button {
label = "Load"
clicked {
openFileDialog(supportedExtensions = listOf("json")) {
loadParameters(it)
}
}
}
button {
label = "Save"
clicked {
saveFileDialog(supportedExtensions = listOf("json")) {
saveParameters(it)
}
}
}
}
div("sidebar") { div("sidebar") {
id = "sidebar" id = "sidebar"
for ((labeledObject, parameters) in trackedParams) { for ((labeledObject, binding) in trackedObjects) {
val (label, obj) = labeledObject val (label, obj) = labeledObject
val header = h3 { label } val header = h3 { label }
val collapsible = div("compartment") { val collapsible = div("compartment") {
for (parameter in parameters) { for (parameter in binding.parameters) {
addControl(labeledObject, parameter) val element = addControl(labeledObject, parameter)
binding.parameterControls[parameter] = element
} }
} }
val collapseClass = ElementClass("collapsed") val collapseClass = ElementClass("collapsed")
@@ -150,10 +187,10 @@ class GUI : Extension {
program.extend(panel) program.extend(panel)
} }
private fun Div.addControl(compartment: LabeledObject, parameter: Parameter) { private fun Div.addControl(compartment: LabeledObject, parameter: Parameter): Element {
val obj = compartment.obj val obj = compartment.obj
when (parameter.parameterType) { return when (parameter.parameterType) {
ParameterType.Int -> { ParameterType.Int -> {
slider { slider {
label = parameter.label label = parameter.label
@@ -226,10 +263,19 @@ class GUI : Extension {
colorpickerButton { colorpickerButton {
label = parameter.label label = parameter.label
events.valueChanged.subscribe { events.valueChanged.subscribe {
setAndPersist(compartment.label, parameter.property as KMutableProperty1<Any, ColorRGBa>, obj, it.color) setAndPersist(
compartment.label,
parameter.property as KMutableProperty1<Any, ColorRGBa>,
obj,
it.color
)
onChangeListener?.invoke(parameter.property!!.name, it.color) onChangeListener?.invoke(parameter.property!!.name, it.color)
} }
getPersistedOrDefault(compartment.label, parameter.property as KMutableProperty1<Any, ColorRGBa>, obj)?.let { getPersistedOrDefault(
compartment.label,
parameter.property as KMutableProperty1<Any, ColorRGBa>,
obj
)?.let {
color = it color = it
} }
} }
@@ -237,14 +283,145 @@ class GUI : Extension {
} }
} }
private val trackedParams = mutableMapOf<LabeledObject, List<Parameter>>() private val trackedObjects = mutableMapOf<LabeledObject, TrackedObjectBinding>()
private fun updateControls() {
for ((labeledObject, binding) in trackedObjects) {
for ((parameter, control) in binding.parameterControls) {
updateControl(labeledObject, parameter, control)
}
}
}
private class ParameterValue(var doubleValue: Double? = null,
var intValue: Int? = null,
var booleanValue: Boolean? = null,
var colorValue: ColorRGBa? = null,
var textValue: String? = null)
private fun saveParameters(file: File) {
fun <T> KMutableProperty1<out Any, Any?>?.qget(obj: Any): T {
return (this as KMutableProperty1<Any, T>).get(obj)
}
val toSave =
trackedObjects.entries.associate { (lo, b) ->
Pair(lo.label, b.parameterControls.keys.associate { k ->
Pair(k.property?.name ?: k.function?.name ?: error("no name"), when (k.parameterType) {
ParameterType.Double -> ParameterValue(doubleValue = k.property.qget(lo.obj) as Double)
ParameterType.Int -> ParameterValue(intValue = k.property.qget(lo.obj) as Int)
ParameterType.Action -> ParameterValue()
ParameterType.Color -> ParameterValue(colorValue = k.property.qget(lo.obj) as ColorRGBa)
ParameterType.Text -> ParameterValue(textValue = k.property.qget(lo.obj) as String)
ParameterType.Boolean -> ParameterValue(booleanValue = k.property.qget(lo.obj as Boolean))
})
})
}
file.writeText(Gson().toJson(toSave))
}
private fun loadParameters(file: File) {
fun <T> KMutableProperty1<out Any, Any?>?.qset(obj: Any, value:T) {
return (this as KMutableProperty1<Any, T>).set(obj, value)
}
val json = file.readText()
val tt = object : TypeToken<Map<String, Map<String, ParameterValue>>>() {}
val labeledValues: Map<String, Map<String, ParameterValue>> = Gson().fromJson(json, tt.type)
labeledValues.forEach { label, ps ->
trackedObjects.keys.find { it.label == label }?.let { lo ->
val binding = trackedObjects[lo]!!
ps.forEach { (parameterName, parameterValue) ->
binding.parameters.find { it.property?.name == parameterName}?.let { parameter ->
when (parameter.parameterType) {
ParameterType.Double -> parameterValue.doubleValue?.let {
parameter.property.qset(lo.obj, it)
}
ParameterType.Int -> parameterValue.intValue?.let {
parameter.property.qset(lo.obj, it)
}
ParameterType.Text -> parameterValue.textValue?.let {
parameter.property.qset(lo.obj, it)
}
ParameterType.Color -> parameterValue.colorValue?.let {
parameter.property.qset(lo.obj, it)
}
ParameterType.Boolean -> parameterValue.booleanValue?.let {
parameter.property.qset(lo.obj, it)
}
ParameterType.Action -> {
// intentionally do nothing
}
}
}
}
}
}
updateControls()
}
private fun updateControl(labeledObject: LabeledObject, parameter: Parameter, control: Element) {
when (parameter.parameterType) {
ParameterType.Double -> {
(control as Slider).value = (parameter.property as KMutableProperty1<Any, Double>).get(labeledObject.obj)
}
ParameterType.Int -> {
(control as Slider).value = (parameter.property as KMutableProperty1<Any, Int>).get(labeledObject.obj).toDouble()
}
ParameterType.Text -> {
(control as Textfield).value = (parameter.property as KMutableProperty1<Any, String>).get(labeledObject.obj)
}
ParameterType.Color -> {
(control as ColorpickerButton).color = (parameter.property as KMutableProperty1<Any, ColorRGBa>).get(labeledObject.obj)
}
ParameterType.Boolean -> {
(control as Slider).value = if ((parameter.property as KMutableProperty1<Any, Boolean>).get(labeledObject.obj)) 1.0 else 0.0
}
ParameterType.Action -> {
// intentionally do nothing
}
}
}
fun randomize() {
for ((labeledObject, binding) in trackedObjects) {
// -- only randomize visible parameters
for (parameter in binding.parameterControls.keys) {
when (parameter.parameterType) {
ParameterType.Double -> {
val min = parameter.doubleRange!!.start
val max = parameter.doubleRange!!.endInclusive
(parameter.property as KMutableProperty1<Any, Double>).set(labeledObject.obj, Math.random() * (max - min) + min)
}
ParameterType.Int -> {
val min = parameter.intRange!!.first
val max = parameter.intRange!!.last
(parameter.property as KMutableProperty1<Any, Int>).set(labeledObject.obj, (Math.random() * (max - min) + min).toInt())
}
ParameterType.Boolean -> {
(parameter.property as KMutableProperty1<Any, Boolean>).set(labeledObject.obj, (Math.random() < 0.5))
}
ParameterType.Color -> {
val c = ColorRGBa(Math.random(), Math.random(), Math.random())
(parameter.property as KMutableProperty1<Any, ColorRGBa>).set(labeledObject.obj, c)
}
else -> {
// do nothing
}
}
}
}
updateControls()
}
/** /**
* Recursively find a unique label * Recursively find a unique label
* @param label to find an alternate for in case it already exist * @param label to find an alternate for in case it already exist
*/ */
private fun resolveUniqueLabel(label: String): String { private fun resolveUniqueLabel(label: String): String {
return trackedParams.keys.find { it.label == label }?.let { lo -> return trackedObjects.keys.find { it.label == label }?.let { lo ->
resolveUniqueLabel(Regex("(.*) / ([0-9]+)").matchEntire(lo.label)?.let { resolveUniqueLabel(Regex("(.*) / ([0-9]+)").matchEntire(lo.label)?.let {
"${it.groupValues[1]} / ${1 + it.groupValues[2].toInt()}" "${it.groupValues[1]} / ${1 + it.groupValues[2].toInt()}"
} ?: "$label / 2") } ?: "$label / 2")
@@ -268,7 +445,7 @@ class GUI : Extension {
collapseStates.getOrPut(uniqueLabel) { collapseStates.getOrPut(uniqueLabel) {
CompartmentState() CompartmentState()
} }
trackedParams[LabeledObject(uniqueLabel, objectWithParameters)] = parameters trackedObjects[LabeledObject(uniqueLabel, objectWithParameters)] = TrackedObjectBinding(parameters)
} }
return objectWithParameters return objectWithParameters
} }