From 8dd4f9236680135c418ec620e8a2780b9cfae144 Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Thu, 6 Feb 2020 23:38:48 +0100 Subject: [PATCH] Add randomization, saving and loading of parameters in orx-gui --- orx-gui/build.gradle | 2 + orx-gui/src/main/kotlin/Gui.kt | 213 ++++++++++++++++++++++++++++++--- 2 files changed, 197 insertions(+), 18 deletions(-) diff --git a/orx-gui/build.gradle b/orx-gui/build.gradle index 90b58bba..063fc58f 100644 --- a/orx-gui/build.gradle +++ b/orx-gui/build.gradle @@ -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" } \ No newline at end of file diff --git a/orx-gui/src/main/kotlin/Gui.kt b/orx-gui/src/main/kotlin/Gui.kt index e28161a8..f39a49c1 100644 --- a/orx-gui/src/main/kotlin/Gui.kt +++ b/orx-gui/src/main/kotlin/Gui.kt @@ -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 = mutableMapOf()) +private class TrackedObjectBinding( + val parameters: List, + val parameterControls: MutableMap = mutableMapOf() +) private val persistentCompartmentStates = mutableMapOf>() @@ -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, obj, it.color) + setAndPersist( + compartment.label, + parameter.property as KMutableProperty1, + obj, + it.color + ) onChangeListener?.invoke(parameter.property!!.name, it.color) } - getPersistedOrDefault(compartment.label, parameter.property as KMutableProperty1, obj)?.let { + getPersistedOrDefault( + compartment.label, + parameter.property as KMutableProperty1, + obj + )?.let { color = it } } @@ -237,14 +283,145 @@ class GUI : Extension { } } - private val trackedParams = mutableMapOf>() + private val trackedObjects = mutableMapOf() + + 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 KMutableProperty1?.qget(obj: Any): T { + return (this as KMutableProperty1).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 KMutableProperty1?.qset(obj: Any, value:T) { + return (this as KMutableProperty1).set(obj, value) + } + val json = file.readText() + val tt = object : TypeToken>>() {} + val labeledValues: Map> = 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).get(labeledObject.obj) + } + ParameterType.Int -> { + (control as Slider).value = (parameter.property as KMutableProperty1).get(labeledObject.obj).toDouble() + } + ParameterType.Text -> { + (control as Textfield).value = (parameter.property as KMutableProperty1).get(labeledObject.obj) + } + ParameterType.Color -> { + (control as ColorpickerButton).color = (parameter.property as KMutableProperty1).get(labeledObject.obj) + } + ParameterType.Boolean -> { + (control as Slider).value = if ((parameter.property as KMutableProperty1).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).set(labeledObject.obj, Math.random() * (max - min) + min) + } + ParameterType.Int -> { + val min = parameter.intRange!!.first + val max = parameter.intRange!!.last + (parameter.property as KMutableProperty1).set(labeledObject.obj, (Math.random() * (max - min) + min).toInt()) + } + ParameterType.Boolean -> { + (parameter.property as KMutableProperty1).set(labeledObject.obj, (Math.random() < 0.5)) + } + ParameterType.Color -> { + val c = ColorRGBa(Math.random(), Math.random(), Math.random()) + (parameter.property as KMutableProperty1).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 }