Gui: add .toObject() .fromObject() (#126)

Split .loadParameters() .saveParameters() in two

This allows the user to read / write the gui state and
serialize it externally.
This commit is contained in:
Abe Pazos
2020-06-11 21:07:46 +02:00
committed by GitHub
parent 051db162e7
commit e6857acb8b
2 changed files with 128 additions and 45 deletions

View File

@@ -0,0 +1,72 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.color.mix
import org.openrndr.extensions.SingleScreenshot
import org.openrndr.extra.gui.GUI
import org.openrndr.extra.parameters.*
/**
* Shows how to store and retrieve in-memory gui presets.
* Keyboard controls:
* [Left Shift] + [0]..[9] => store current gui values to a preset
* [0]..[9] => recall a preset
*/
fun main() = application {
program {
// -- this block is for automation purposes only
if (System.getProperty("takeScreenshot") == "true") {
extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
val gui = GUI()
gui.compartmentsCollapsedByDefault = false
val presets = MutableList(10) {
gui.toObject()
}
val settings = @Description("Settings") object {
@IntParameter("a", 1, 10)
var a = 7
@IntParameter("b", 1, 10)
var b = 3
@ColorParameter("foreground")
var foreground = ColorRGBa.fromHex("654062")
@ColorParameter("background")
var background = ColorRGBa.fromHex("ff9c71")
}
gui.add(settings)
extend(gui)
extend {
drawer.clear(settings.background)
drawer.stroke = settings.background
drawer.fill = settings.foreground
// Draw a pattern based on modulo
for(i in 0 until 100) {
if(i % settings.a == 0 || i % settings.b == 0) {
val x = (i % 10) * 64.0
val y = (i / 10) * 48.0
drawer.rectangle(x, y, 64.0, 48.0)
}
}
}
keyboard.keyDown.listen {
when (it.name) {
in "0" .. "9" -> {
if(keyboard.pressedKeys.contains("left-shift")) {
// 1. Get the current gui state, store it in a list
presets[it.name.toInt()] = gui.toObject()
} else {
// 2. Set the gui state
gui.fromObject(presets[it.name.toInt()])
}
}
}
}
}
}

View File

@@ -2,11 +2,7 @@ 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.KEY_LEFT_SHIFT
import org.openrndr.KeyModifier
import org.openrndr.Program
import org.openrndr.*
import org.openrndr.color.ColorRGBa
import org.openrndr.dialogs.getDefaultPathForContext
import org.openrndr.dialogs.openFileDialog
@@ -22,8 +18,6 @@ import org.openrndr.panel.ControlManager
import org.openrndr.panel.controlManager
import org.openrndr.panel.elements.*
import org.openrndr.panel.style.*
import org.openrndr.panel.styleSheet
import java.io.File
import kotlin.math.roundToInt
import kotlin.reflect.KMutableProperty1
@@ -631,49 +625,62 @@ class GUI : Extension {
}
}
private class ParameterValue(var doubleValue: Double? = null,
var intValue: Int? = null,
var booleanValue: Boolean? = null,
var colorValue: ColorRGBa? = null,
var vector2Value: Vector2? = null,
var vector3Value: Vector3? = null,
var vector4Value: Vector4? = null,
var doubleListValue: MutableList<Double>? = null,
var textValue: String? = null,
var optionValue: String? = null
class ParameterValue(var doubleValue: Double? = null,
var intValue: Int? = null,
var booleanValue: Boolean? = null,
var colorValue: ColorRGBa? = null,
var vector2Value: Vector2? = null,
var vector3Value: Vector3? = null,
var vector4Value: Vector4? = null,
var doubleListValue: MutableList<Double>? = null,
var textValue: String? = null,
var optionValue: String? = null
)
fun saveParameters(file: File) {
/**
* Can be called by the user to obtain an object to be serialized
* externally. This allows the user to combine custom data with gui
* state and save it all to one file. Complements `.fromObject()`.
*/
fun toObject(): Map<String, Map<String, ParameterValue>> {
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) {
/* 3) setup serializers */
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)
ParameterType.XY -> ParameterValue(vector2Value = k.property.qget(lo.obj) as Vector2)
ParameterType.DoubleList -> ParameterValue(doubleListValue = k.property.qget(lo.obj) as MutableList<Double>)
ParameterType.Vector2 -> ParameterValue(vector2Value = k.property.qget(lo.obj) as Vector2)
ParameterType.Vector3 -> ParameterValue(vector3Value = k.property.qget(lo.obj) as Vector3)
ParameterType.Vector4 -> ParameterValue(vector4Value = k.property.qget(lo.obj) as Vector4)
ParameterType.Option -> ParameterValue(optionValue = (k.property.qget(lo.obj) as Enum<*>).name)
})
})
}
file.writeText(Gson().toJson(toSave))
return 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) {
/* 3) setup serializers */
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)
ParameterType.XY -> ParameterValue(vector2Value = k.property.qget(lo.obj) as Vector2)
ParameterType.DoubleList -> ParameterValue(doubleListValue = k.property.qget(lo.obj) as MutableList<Double>)
ParameterType.Vector2 -> ParameterValue(vector2Value = k.property.qget(lo.obj) as Vector2)
ParameterType.Vector3 -> ParameterValue(vector3Value = k.property.qget(lo.obj) as Vector3)
ParameterType.Vector4 -> ParameterValue(vector4Value = k.property.qget(lo.obj) as Vector4)
ParameterType.Option -> ParameterValue(optionValue = (k.property.qget(lo.obj) as Enum<*>).name)
})
})
}
}
fun loadParameters(file: File) {
fun saveParameters(file: File) {
file.writeText(Gson().toJson(toObject()))
}
/**
* Can be called by the user to update the gui using an object
* deserialized externally. Allows the user to load a larger json object,
* deserialize it, and use part of it to update the GUI.
* Complements `.toObject()`.
*/
fun fromObject(labeledValues: Map<String, Map<String, ParameterValue>>) {
fun <T> KMutableProperty1<out Any, Any?>?.qset(obj: Any, value: T) =
(this as KMutableProperty1<Any, T>).set(obj, value)
@@ -686,10 +693,6 @@ class GUI : Extension {
(this as KMutableProperty1<Any, Enum<*>>).set(obj, enumValue)
}
val json = file.readText()
val typeToken = object : TypeToken<Map<String, Map<String, ParameterValue>>>() {}
val labeledValues: Map<String, Map<String, ParameterValue>> = Gson().fromJson(json, typeToken.type)
labeledValues.forEach { (label, ps) ->
trackedObjects.keys.find { it.label == label }?.let { lo ->
val binding = trackedObjects[lo]!!
@@ -741,6 +744,14 @@ class GUI : Extension {
updateControls()
}
fun loadParameters(file: File) {
val json = file.readText()
val typeToken = object : TypeToken<Map<String, Map<String, ParameterValue>>>() {}
val labeledValues: Map<String, Map<String, ParameterValue>> = Gson().fromJson(json, typeToken.type)
fromObject(labeledValues)
}
private fun updateControl(labeledObject: LabeledObject, parameter: Parameter, control: Element) {
when (parameter.parameterType) {
/* 5) Update control from property value */