Rework and fix for orx-parameter/orx-gui ButtonParameter (now ActionParameter)

This commit is contained in:
Edwin Jakobs
2020-02-04 12:24:18 +01:00
parent 9ca639a14a
commit 2738b2f287
5 changed files with 106 additions and 46 deletions

View File

@@ -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() {
// --
}
}
````

View File

@@ -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<out Annotation>) {
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<out Annotation>) {
}
}
/**
* 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<out Annotation>) {
*/
class Parameter(
val parameterType: ParameterType,
val property: KMutableProperty1<out Any, Any?>,
val property: KMutableProperty1<out Any, Any?>?,
val function: KCallable<Unit>?,
val label: String,
val doubleRange: ClosedRange<Double>?,
val intRange: IntRange?,
@@ -119,7 +122,7 @@ class Parameter(
* List all parameters, (public var properties with a parameter annotation)
*/
fun Any.listParameters(): List<Parameter> {
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<Parameter> {
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> {
}
}
}
Parameter(type
?: error("no type"), it as KMutableProperty1<out Any, Any?>, label, doubleRange, intRange, precision, order)
}.sortedBy { it.order }
Parameter(
parameterType = type ?: error("no type"),
property = it as KMutableProperty1<out Any, Any?>,
function = null,
label = label,
doubleRange = doubleRange,
intRange = intRange,
precision = precision,
order = order
)
} + this::class.declaredMemberFunctions.filter {
it.findAnnotation<ActionParameter>() != null
}.map {
val annotation = it.findAnnotation<ActionParameter>()!!
val label = annotation.label
val order = annotation.order
val type = ParameterType.Action
Parameter(
type,
property = null,
function = it as KCallable<Unit>,
label = label,
doubleRange = null,
intRange = null,
precision = null,
order = order
)
}).sortedBy { it.order }
}
fun Any.title() = this::class.findAnnotation<Description>()?.title

View File

@@ -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"
}
}