From 2738b2f2871ed99e1ff7283b8d6dc6c74c4b29ab Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Tue, 4 Feb 2020 12:24:18 +0100 Subject: [PATCH] Rework and fix for orx-parameter/orx-gui ButtonParameter (now ActionParameter) --- orx-gui/README.md | 2 + orx-gui/src/main/kotlin/Gui.kt | 35 +++++++----- orx-parameters/README.md | 19 +++++-- orx-parameters/src/main/kotlin/Annotations.kt | 54 +++++++++++++------ .../src/test/kotlin/TestAnnotations.kt | 42 ++++++++++----- 5 files changed, 106 insertions(+), 46 deletions(-) diff --git a/orx-gui/README.md b/orx-gui/README.md index f67016c6..83d7249a 100644 --- a/orx-gui/README.md +++ b/orx-gui/README.md @@ -15,6 +15,8 @@ Preparation: make sure `orx-gui` is in the `orxFeatures` of your project (if you The essence of `orx-gui` lies in the provided a `GUI` extension, which can be used in your program using the `extend {}` function. The `GUI` class has an `add()` function that allows any annotated object to be passed in. +The visibility of the side bar can be toggled by pressing the F11 key on your keyboard. + ### UIs for parameter objects A simple UI can be created by creating an annotated `object`. diff --git a/orx-gui/src/main/kotlin/Gui.kt b/orx-gui/src/main/kotlin/Gui.kt index 9fbd33aa..cbed63e3 100644 --- a/orx-gui/src/main/kotlin/Gui.kt +++ b/orx-gui/src/main/kotlin/Gui.kt @@ -1,6 +1,7 @@ package org.openrndr.extra.gui import org.openrndr.Extension +import org.openrndr.KEY_F11 import org.openrndr.Program import org.openrndr.color.ColorRGBa import org.openrndr.extra.parameters.* @@ -22,6 +23,14 @@ class GUI : Extension { } override fun setup(program: Program) { + + program.keyboard.keyDown.listen { + if (it.key == KEY_F11) { + enabled = !enabled + panel.enabled = enabled + } + } + panel = program.controlManager { styleSheet(has class_ "container") { this.display = Display.FLEX @@ -80,39 +89,39 @@ class GUI : Extension { } private fun Div.addControl(obj: Any, parameter: Parameter) { - when(parameter.parameterType) { ParameterType.Int -> { slider { label = parameter.label - range = Range(parameter.intRange!!.start.toDouble(), parameter.intRange!!.endInclusive.toDouble()) + range = Range(parameter.intRange!!.first.toDouble(), parameter.intRange!!.last.toDouble()) precision = 0 value = (parameter.property as KMutableProperty1).get(obj).toDouble() events.valueChanged.subscribe { (parameter.property as KMutableProperty1).set(obj, value.toInt()) - onChangeListener?.invoke(parameter.property.name, it.newValue) + onChangeListener?.invoke(parameter.property!!.name, it.newValue) } } } ParameterType.Double -> { slider { label = parameter.label - range = Range(parameter.doubleRange!!.start, parameter.doubleRange!!.endInclusive.toDouble()) + range = Range(parameter.doubleRange!!.start, parameter.doubleRange!!.endInclusive) precision = parameter.precision!! value = (parameter.property as KMutableProperty1).get(obj) events.valueChanged.subscribe { (parameter.property as KMutableProperty1).set(obj, value) - onChangeListener?.invoke(parameter.property.name, it.newValue) + onChangeListener?.invoke(parameter.property!!.name, it.newValue) } } } - ParameterType.Button -> { + ParameterType.Action -> { button { label = parameter.label events.clicked.subscribe { - parameter.property.call(obj) - onChangeListener?.invoke(parameter.property.name, null) + /* the `obj` we pass in here is the receiver */ + parameter.function!!.call(obj) + onChangeListener?.invoke(parameter.function!!.name, null) } } } @@ -126,7 +135,7 @@ class GUI : Extension { events.valueChanged.subscribe { value = it.newValue (parameter.property as KMutableProperty1).set(obj, value > 0.5) - onChangeListener?.invoke(parameter.property.name, it.newValue) + onChangeListener?.invoke(parameter.property!!.name, it.newValue) } } } @@ -138,7 +147,7 @@ class GUI : Extension { events.valueChanged.subscribe { value = it.newValue (parameter.property as KMutableProperty1).set(obj, value) - onChangeListener?.invoke(parameter.property.name, it.newValue) + onChangeListener?.invoke(parameter.property!!.name, it.newValue) } } } @@ -149,21 +158,21 @@ class GUI : Extension { color = (parameter.property as KMutableProperty1).get(obj) events.valueChanged.subscribe { (parameter.property as KMutableProperty1).set(obj, it.color) - onChangeListener?.invoke(parameter.property.name, it.color) + onChangeListener?.invoke(parameter.property!!.name, it.color) } } } } } - val trackedParams = mutableMapOf>() + private val trackedParams = mutableMapOf>() /** * Add an object to the GUI */ fun add(objectWithParameters: Any) { val parameters = objectWithParameters.listParameters() - if (parameters.size > 0) { + if (parameters.isNotEmpty()) { trackedParams[objectWithParameters] = parameters } } diff --git a/orx-parameters/README.md b/orx-parameters/README.md index 990b193c..b875359c 100644 --- a/orx-parameters/README.md +++ b/orx-parameters/README.md @@ -3,12 +3,17 @@ A collection of annotations and tools that are used to turn Kotlin properties into introspectable parameters. Parameters are highly suitable for automatically generating user interfaces, but note that this is _not_ what `orx-parameters` does. +For an example (and a highly usable implementation) of generating interfaces from the annotations you are encouraged to check out [`orx-gui`](../orx-gui/README.md). + Currently orx-parameters supplies the following annotations: - - `DoubleParameter` - - `IntParameter` - - `BooleanParameter` - - `TextParameter` + - `DoubleParameter` for `Double` properties + - `IntParameter` for `Int` properties + - `BooleanParameter` for `Boolean` properties + - `TextParameter` for `String` properties + - `ColorParameter` for `ColorRGBa` properties + +Additionally there is an `ActionParameter` that can be used to annotate functions without arguments. ## Annotation application @@ -24,6 +29,12 @@ val foo = object { @BooleanParameter("a boolean parameter", order = 2) var b = false + + @ActionParameter("a simple action", order = 3) + fun actionFunction() { + // -- + } + } ```` diff --git a/orx-parameters/src/main/kotlin/Annotations.kt b/orx-parameters/src/main/kotlin/Annotations.kt index 9cc845e6..6a7e07c0 100644 --- a/orx-parameters/src/main/kotlin/Annotations.kt +++ b/orx-parameters/src/main/kotlin/Annotations.kt @@ -1,8 +1,10 @@ package org.openrndr.extra.parameters +import kotlin.reflect.KCallable import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KVisibility +import kotlin.reflect.full.declaredMemberFunctions import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.findAnnotation @@ -69,19 +71,19 @@ annotation class TextParameter(val label: String, val order: Int = Integer.MAX_V annotation class ColorParameter(val label: String, val order: Int = Integer.MAX_VALUE) /** - * ButtonParameter annotation for button parameter + * ActionParameter annotation for functions without arguments * @property label a short description of the parameter * @property order hint for where to place the parameter in user interfaces */ -@Target(AnnotationTarget.PROPERTY) +@Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) -annotation class ButtonParameter(val label: String, val order: Int = Integer.MAX_VALUE) +annotation class ActionParameter(val label: String, val order: Int = Integer.MAX_VALUE) enum class ParameterType(val annotationClass: KClass) { Double(DoubleParameter::class), Int(IntParameter::class), Boolean(BooleanParameter::class), - Button(ButtonParameter::class), + Action(ActionParameter::class), Text(TextParameter::class), Color(ColorParameter::class) ; @@ -94,12 +96,12 @@ enum class ParameterType(val annotationClass: KClass) { } } - /** * Parameter summary class. This is used by [listParameters] as a way to report parameters in * a unified form. - * @property property the property that received any of the parameter annotations + * @property property a property that received any of the parameter annotations * [BooleanParameter], [IntParameter], or [DoubleParameter] + * @property function a function that received the [ActionParameter] * @property label a label that describes the property * @property doubleRange a floating point based range in case [DoubleParameter] is used * @property intRange an integer range in case [IntParameter] is used @@ -108,7 +110,8 @@ enum class ParameterType(val annotationClass: KClass) { */ class Parameter( val parameterType: ParameterType, - val property: KMutableProperty1, + val property: KMutableProperty1?, + val function: KCallable?, val label: String, val doubleRange: ClosedRange?, val intRange: IntRange?, @@ -119,7 +122,7 @@ class Parameter( * List all parameters, (public var properties with a parameter annotation) */ fun Any.listParameters(): List { - return this::class.declaredMemberProperties.filter { + return (this::class.declaredMemberProperties.filter { !it.isConst && it.visibility == KVisibility.PUBLIC && it.annotations.map { it.annotationClass }.intersect(ParameterType.parameterAnnotationClasses).isNotEmpty() @@ -150,10 +153,6 @@ fun Any.listParameters(): List { intRange = it.low..it.high order = it.order } - is ButtonParameter -> { - label = it.label - order = it.order - } is TextParameter -> { label = it.label order = it.order @@ -164,9 +163,34 @@ fun Any.listParameters(): List { } } } - Parameter(type - ?: error("no type"), it as KMutableProperty1, label, doubleRange, intRange, precision, order) - }.sortedBy { it.order } + Parameter( + parameterType = type ?: error("no type"), + property = it as KMutableProperty1, + function = null, + label = label, + doubleRange = doubleRange, + intRange = intRange, + precision = precision, + order = order + ) + } + this::class.declaredMemberFunctions.filter { + it.findAnnotation() != null + }.map { + val annotation = it.findAnnotation()!! + val label = annotation.label + val order = annotation.order + val type = ParameterType.Action + Parameter( + type, + property = null, + function = it as KCallable, + label = label, + doubleRange = null, + intRange = null, + precision = null, + order = order + ) + }).sortedBy { it.order } } fun Any.title() = this::class.findAnnotation()?.title diff --git a/orx-parameters/src/test/kotlin/TestAnnotations.kt b/orx-parameters/src/test/kotlin/TestAnnotations.kt index abe9eeb7..c3e1986f 100644 --- a/orx-parameters/src/test/kotlin/TestAnnotations.kt +++ b/orx-parameters/src/test/kotlin/TestAnnotations.kt @@ -1,9 +1,9 @@ -import org.amshove.kluent.`should be equal to` -import org.amshove.kluent.shouldBeInRange +import org.amshove.kluent.* import org.openrndr.color.ColorRGBa import org.openrndr.extra.parameters.* import org.spekframework.spek2.Spek import org.spekframework.spek2.style.specification.describe +import java.lang.IllegalArgumentException val a = object { @DoubleParameter("a double scalar", 0.0, 1.0, order = 0) @@ -15,8 +15,10 @@ val a = object { @BooleanParameter("a boolean parameter", order = 2) var b = false - @ButtonParameter("a button parameter", order = 3) - var f = {} + @ActionParameter("an action parameter", order = 3) + fun a() { + error("this is to test if this function can be called") + } @TextParameter("a text parameter", order = 4) var t = "test" @@ -32,7 +34,7 @@ object TestAnnotations : Spek({ val list = a.listParameters() list.size `should be equal to` 6 - list[0].property.name `should be equal to` "d" + list[0].property?.name `should be equal to` "d" list[0].parameterType `should be equal to` ParameterType.Double list[0].label `should be equal to` "a double scalar" list[0].doubleRange?.let { @@ -41,29 +43,41 @@ object TestAnnotations : Spek({ } list[0].precision `should be equal to` 3 - list[1].property.name `should be equal to` "i" + list[1].property?.name `should be equal to` "i" list[1].parameterType `should be equal to` ParameterType.Int list[1].label `should be equal to` "an integer scalar" list[1].intRange?.let { - it.start `should be equal to` 1 - it.endInclusive `should be equal to` 100 + it.first `should be equal to` 1 + it.last `should be equal to` 100 } - list[2].property.name `should be equal to` "b" + list[2].property?.name `should be equal to` "b" list[2].parameterType `should be equal to` ParameterType.Boolean list[2].label `should be equal to` "a boolean parameter" list[2].precision `should be equal to` null - list[3].parameterType `should be equal to` ParameterType.Button - list[3].property.name `should be equal to` "f" - list[3].label `should be equal to` "a button parameter" + list[3].parameterType `should be equal to` ParameterType.Action + list[3].property `should be equal to` null + list[3].label `should be equal to` "an action parameter" + + /* test if we can call the annotated function, this is performed by raising an expected exception in the + function in the annotated function. + */ + invoking { + try { + list[3].function?.call(a) + } catch(e: java.lang.reflect.InvocationTargetException) { + /* this unpacks the exception that is wrapped in the ITExc */ + throw(e.targetException) + } + } `should throw` IllegalStateException::class withMessage "this is to test if this function can be called" list[4].parameterType `should be equal to` ParameterType.Text - list[4].property.name `should be equal to` "t" + list[4].property?.name `should be equal to` "t" list[4].label `should be equal to` "a text parameter" list[5].parameterType `should be equal to` ParameterType.Color - list[5].property.name `should be equal to` "c" + list[5].property?.name `should be equal to` "c" list[5].label `should be equal to` "a color parameter" } }