Add randomization, saving and loading of parameters in orx-gui
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
dependencies {
|
||||
compile project(":orx-parameters")
|
||||
compile "org.openrndr.panel:openrndr-panel:$panelVersion"
|
||||
compile "org.openrndr:openrndr-dialogs:$openrndrVersion"
|
||||
compile "com.google.code.gson:gson:$gsonVersion"
|
||||
}
|
||||
@@ -1,19 +1,29 @@
|
||||
package org.openrndr.extra.gui
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.openrndr.Extension
|
||||
import org.openrndr.KEY_F11
|
||||
import org.openrndr.Program
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.dialogs.openFileDialog
|
||||
import org.openrndr.dialogs.saveFileDialog
|
||||
import org.openrndr.extra.parameters.*
|
||||
import org.openrndr.internal.Driver
|
||||
import org.openrndr.panel.ControlManager
|
||||
import org.openrndr.panel.controlManager
|
||||
import org.openrndr.panel.elements.*
|
||||
import org.openrndr.panel.style.*
|
||||
|
||||
import java.io.File
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
||||
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 TrackedObjectBinding(
|
||||
val parameters: List<Parameter>,
|
||||
val parameterControls: MutableMap<Parameter, Element> = mutableMapOf()
|
||||
)
|
||||
|
||||
private val persistentCompartmentStates = mutableMapOf<Long, MutableMap<String, CompartmentState>>()
|
||||
|
||||
@@ -55,11 +65,19 @@ class GUI : Extension {
|
||||
panel = program.controlManager {
|
||||
styleSheet(has class_ "container") {
|
||||
this.display = Display.FLEX
|
||||
this.flexDirection = FlexDirection.Row
|
||||
this.flexDirection = FlexDirection.Column
|
||||
this.width = 200.px
|
||||
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") {
|
||||
this.display = Display.NONE
|
||||
}
|
||||
@@ -100,26 +118,45 @@ class GUI : Extension {
|
||||
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 {
|
||||
div("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") {
|
||||
id = "sidebar"
|
||||
for ((labeledObject, parameters) in trackedParams) {
|
||||
for ((labeledObject, binding) in trackedObjects) {
|
||||
val (label, obj) = labeledObject
|
||||
|
||||
val header = h3 { label }
|
||||
val collapsible = div("compartment") {
|
||||
for (parameter in parameters) {
|
||||
addControl(labeledObject, parameter)
|
||||
for (parameter in binding.parameters) {
|
||||
val element = addControl(labeledObject, parameter)
|
||||
binding.parameterControls[parameter] = element
|
||||
}
|
||||
}
|
||||
val collapseClass = ElementClass("collapsed")
|
||||
@@ -150,10 +187,10 @@ class GUI : Extension {
|
||||
program.extend(panel)
|
||||
}
|
||||
|
||||
private fun Div.addControl(compartment: LabeledObject, parameter: Parameter) {
|
||||
private fun Div.addControl(compartment: LabeledObject, parameter: Parameter): Element {
|
||||
val obj = compartment.obj
|
||||
|
||||
when (parameter.parameterType) {
|
||||
return when (parameter.parameterType) {
|
||||
ParameterType.Int -> {
|
||||
slider {
|
||||
label = parameter.label
|
||||
@@ -226,10 +263,19 @@ class GUI : Extension {
|
||||
colorpickerButton {
|
||||
label = parameter.label
|
||||
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)
|
||||
}
|
||||
getPersistedOrDefault(compartment.label, parameter.property as KMutableProperty1<Any, ColorRGBa>, obj)?.let {
|
||||
getPersistedOrDefault(
|
||||
compartment.label,
|
||||
parameter.property as KMutableProperty1<Any, ColorRGBa>,
|
||||
obj
|
||||
)?.let {
|
||||
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
|
||||
* @param label to find an alternate for in case it already exist
|
||||
*/
|
||||
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 {
|
||||
"${it.groupValues[1]} / ${1 + it.groupValues[2].toInt()}"
|
||||
} ?: "$label / 2")
|
||||
@@ -268,7 +445,7 @@ class GUI : Extension {
|
||||
collapseStates.getOrPut(uniqueLabel) {
|
||||
CompartmentState()
|
||||
}
|
||||
trackedParams[LabeledObject(uniqueLabel, objectWithParameters)] = parameters
|
||||
trackedObjects[LabeledObject(uniqueLabel, objectWithParameters)] = TrackedObjectBinding(parameters)
|
||||
}
|
||||
return objectWithParameters
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user