Rework and fix for orx-parameter/orx-gui ButtonParameter (now ActionParameter)
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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<Any, Int>).get(obj).toDouble()
|
||||
events.valueChanged.subscribe {
|
||||
(parameter.property as KMutableProperty1<Any, Int>).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<Any, Double>).get(obj)
|
||||
events.valueChanged.subscribe {
|
||||
(parameter.property as KMutableProperty1<Any, Double>).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<Any, Boolean>).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<Any, String>).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<Any, ColorRGBa>).get(obj)
|
||||
events.valueChanged.subscribe {
|
||||
(parameter.property as KMutableProperty1<Any, ColorRGBa>).set(obj, it.color)
|
||||
onChangeListener?.invoke(parameter.property.name, it.color)
|
||||
onChangeListener?.invoke(parameter.property!!.name, it.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val trackedParams = mutableMapOf<Any, List<Parameter>>()
|
||||
private val trackedParams = mutableMapOf<Any, List<Parameter>>()
|
||||
|
||||
/**
|
||||
* Add an object to the GUI
|
||||
*/
|
||||
fun add(objectWithParameters: Any) {
|
||||
val parameters = objectWithParameters.listParameters()
|
||||
if (parameters.size > 0) {
|
||||
if (parameters.isNotEmpty()) {
|
||||
trackedParams[objectWithParameters] = parameters
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
// --
|
||||
}
|
||||
|
||||
}
|
||||
````
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user