diff --git a/orx-gui/src/demo/kotlin/DemoPresets01.kt b/orx-gui/src/demo/kotlin/DemoPresets01.kt new file mode 100644 index 00000000..49566d4c --- /dev/null +++ b/orx-gui/src/demo/kotlin/DemoPresets01.kt @@ -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()]) + } + } + } + } + } +} \ 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 afc2afd9..dbc86daa 100644 --- a/orx-gui/src/main/kotlin/Gui.kt +++ b/orx-gui/src/main/kotlin/Gui.kt @@ -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? = 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? = 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> { 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) { - /* 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) - 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) + 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>) { fun KMutableProperty1?.qset(obj: Any, value: T) = (this as KMutableProperty1).set(obj, value) @@ -686,10 +693,6 @@ class GUI : Extension { (this as KMutableProperty1>).set(obj, enumValue) } - val json = file.readText() - val typeToken = object : TypeToken>>() {} - val labeledValues: Map> = 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>>() {} + val labeledValues: Map> = 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 */