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 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 `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
|
### UIs for parameter objects
|
||||||
|
|
||||||
A simple UI can be created by creating an annotated `object`.
|
A simple UI can be created by creating an annotated `object`.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.openrndr.extra.gui
|
package org.openrndr.extra.gui
|
||||||
|
|
||||||
import org.openrndr.Extension
|
import org.openrndr.Extension
|
||||||
|
import org.openrndr.KEY_F11
|
||||||
import org.openrndr.Program
|
import org.openrndr.Program
|
||||||
import org.openrndr.color.ColorRGBa
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.extra.parameters.*
|
import org.openrndr.extra.parameters.*
|
||||||
@@ -22,6 +23,14 @@ class GUI : Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun setup(program: Program) {
|
override fun setup(program: Program) {
|
||||||
|
|
||||||
|
program.keyboard.keyDown.listen {
|
||||||
|
if (it.key == KEY_F11) {
|
||||||
|
enabled = !enabled
|
||||||
|
panel.enabled = enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
panel = program.controlManager {
|
panel = program.controlManager {
|
||||||
styleSheet(has class_ "container") {
|
styleSheet(has class_ "container") {
|
||||||
this.display = Display.FLEX
|
this.display = Display.FLEX
|
||||||
@@ -80,39 +89,39 @@ class GUI : Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Div.addControl(obj: Any, parameter: Parameter) {
|
private fun Div.addControl(obj: Any, parameter: Parameter) {
|
||||||
|
|
||||||
when(parameter.parameterType) {
|
when(parameter.parameterType) {
|
||||||
ParameterType.Int -> {
|
ParameterType.Int -> {
|
||||||
slider {
|
slider {
|
||||||
label = parameter.label
|
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
|
precision = 0
|
||||||
value = (parameter.property as KMutableProperty1<Any, Int>).get(obj).toDouble()
|
value = (parameter.property as KMutableProperty1<Any, Int>).get(obj).toDouble()
|
||||||
events.valueChanged.subscribe {
|
events.valueChanged.subscribe {
|
||||||
(parameter.property as KMutableProperty1<Any, Int>).set(obj, value.toInt())
|
(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 -> {
|
ParameterType.Double -> {
|
||||||
slider {
|
slider {
|
||||||
label = parameter.label
|
label = parameter.label
|
||||||
range = Range(parameter.doubleRange!!.start, parameter.doubleRange!!.endInclusive.toDouble())
|
range = Range(parameter.doubleRange!!.start, parameter.doubleRange!!.endInclusive)
|
||||||
precision = parameter.precision!!
|
precision = parameter.precision!!
|
||||||
value = (parameter.property as KMutableProperty1<Any, Double>).get(obj)
|
value = (parameter.property as KMutableProperty1<Any, Double>).get(obj)
|
||||||
events.valueChanged.subscribe {
|
events.valueChanged.subscribe {
|
||||||
(parameter.property as KMutableProperty1<Any, Double>).set(obj, value)
|
(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 {
|
button {
|
||||||
label = parameter.label
|
label = parameter.label
|
||||||
events.clicked.subscribe {
|
events.clicked.subscribe {
|
||||||
parameter.property.call(obj)
|
/* the `obj` we pass in here is the receiver */
|
||||||
onChangeListener?.invoke(parameter.property.name, null)
|
parameter.function!!.call(obj)
|
||||||
|
onChangeListener?.invoke(parameter.function!!.name, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +135,7 @@ class GUI : Extension {
|
|||||||
events.valueChanged.subscribe {
|
events.valueChanged.subscribe {
|
||||||
value = it.newValue
|
value = it.newValue
|
||||||
(parameter.property as KMutableProperty1<Any, Boolean>).set(obj, value > 0.5)
|
(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 {
|
events.valueChanged.subscribe {
|
||||||
value = it.newValue
|
value = it.newValue
|
||||||
(parameter.property as KMutableProperty1<Any, String>).set(obj, value)
|
(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)
|
color = (parameter.property as KMutableProperty1<Any, ColorRGBa>).get(obj)
|
||||||
events.valueChanged.subscribe {
|
events.valueChanged.subscribe {
|
||||||
(parameter.property as KMutableProperty1<Any, ColorRGBa>).set(obj, it.color)
|
(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
|
* Add an object to the GUI
|
||||||
*/
|
*/
|
||||||
fun add(objectWithParameters: Any) {
|
fun add(objectWithParameters: Any) {
|
||||||
val parameters = objectWithParameters.listParameters()
|
val parameters = objectWithParameters.listParameters()
|
||||||
if (parameters.size > 0) {
|
if (parameters.isNotEmpty()) {
|
||||||
trackedParams[objectWithParameters] = parameters
|
trackedParams[objectWithParameters] = parameters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,17 @@
|
|||||||
A collection of annotations and tools that are used to turn Kotlin properties into introspectable parameters. Parameters
|
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.
|
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:
|
Currently orx-parameters supplies the following annotations:
|
||||||
|
|
||||||
- `DoubleParameter`
|
- `DoubleParameter` for `Double` properties
|
||||||
- `IntParameter`
|
- `IntParameter` for `Int` properties
|
||||||
- `BooleanParameter`
|
- `BooleanParameter` for `Boolean` properties
|
||||||
- `TextParameter`
|
- `TextParameter` for `String` properties
|
||||||
|
- `ColorParameter` for `ColorRGBa` properties
|
||||||
|
|
||||||
|
Additionally there is an `ActionParameter` that can be used to annotate functions without arguments.
|
||||||
|
|
||||||
## Annotation application
|
## Annotation application
|
||||||
|
|
||||||
@@ -24,6 +29,12 @@ val foo = object {
|
|||||||
|
|
||||||
@BooleanParameter("a boolean parameter", order = 2)
|
@BooleanParameter("a boolean parameter", order = 2)
|
||||||
var b = false
|
var b = false
|
||||||
|
|
||||||
|
@ActionParameter("a simple action", order = 3)
|
||||||
|
fun actionFunction() {
|
||||||
|
// --
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
````
|
````
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.openrndr.extra.parameters
|
package org.openrndr.extra.parameters
|
||||||
|
|
||||||
|
import kotlin.reflect.KCallable
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KMutableProperty1
|
import kotlin.reflect.KMutableProperty1
|
||||||
import kotlin.reflect.KVisibility
|
import kotlin.reflect.KVisibility
|
||||||
|
import kotlin.reflect.full.declaredMemberFunctions
|
||||||
import kotlin.reflect.full.declaredMemberProperties
|
import kotlin.reflect.full.declaredMemberProperties
|
||||||
import kotlin.reflect.full.findAnnotation
|
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)
|
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 label a short description of the parameter
|
||||||
* @property order hint for where to place the parameter in user interfaces
|
* @property order hint for where to place the parameter in user interfaces
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@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>) {
|
enum class ParameterType(val annotationClass: KClass<out Annotation>) {
|
||||||
Double(DoubleParameter::class),
|
Double(DoubleParameter::class),
|
||||||
Int(IntParameter::class),
|
Int(IntParameter::class),
|
||||||
Boolean(BooleanParameter::class),
|
Boolean(BooleanParameter::class),
|
||||||
Button(ButtonParameter::class),
|
Action(ActionParameter::class),
|
||||||
Text(TextParameter::class),
|
Text(TextParameter::class),
|
||||||
Color(ColorParameter::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
|
* Parameter summary class. This is used by [listParameters] as a way to report parameters in
|
||||||
* a unified form.
|
* 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]
|
* [BooleanParameter], [IntParameter], or [DoubleParameter]
|
||||||
|
* @property function a function that received the [ActionParameter]
|
||||||
* @property label a label that describes the property
|
* @property label a label that describes the property
|
||||||
* @property doubleRange a floating point based range in case [DoubleParameter] is used
|
* @property doubleRange a floating point based range in case [DoubleParameter] is used
|
||||||
* @property intRange an integer range in case [IntParameter] 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(
|
class Parameter(
|
||||||
val parameterType: ParameterType,
|
val parameterType: ParameterType,
|
||||||
val property: KMutableProperty1<out Any, Any?>,
|
val property: KMutableProperty1<out Any, Any?>?,
|
||||||
|
val function: KCallable<Unit>?,
|
||||||
val label: String,
|
val label: String,
|
||||||
val doubleRange: ClosedRange<Double>?,
|
val doubleRange: ClosedRange<Double>?,
|
||||||
val intRange: IntRange?,
|
val intRange: IntRange?,
|
||||||
@@ -119,7 +122,7 @@ class Parameter(
|
|||||||
* List all parameters, (public var properties with a parameter annotation)
|
* List all parameters, (public var properties with a parameter annotation)
|
||||||
*/
|
*/
|
||||||
fun Any.listParameters(): List<Parameter> {
|
fun Any.listParameters(): List<Parameter> {
|
||||||
return this::class.declaredMemberProperties.filter {
|
return (this::class.declaredMemberProperties.filter {
|
||||||
!it.isConst &&
|
!it.isConst &&
|
||||||
it.visibility == KVisibility.PUBLIC &&
|
it.visibility == KVisibility.PUBLIC &&
|
||||||
it.annotations.map { it.annotationClass }.intersect(ParameterType.parameterAnnotationClasses).isNotEmpty()
|
it.annotations.map { it.annotationClass }.intersect(ParameterType.parameterAnnotationClasses).isNotEmpty()
|
||||||
@@ -150,10 +153,6 @@ fun Any.listParameters(): List<Parameter> {
|
|||||||
intRange = it.low..it.high
|
intRange = it.low..it.high
|
||||||
order = it.order
|
order = it.order
|
||||||
}
|
}
|
||||||
is ButtonParameter -> {
|
|
||||||
label = it.label
|
|
||||||
order = it.order
|
|
||||||
}
|
|
||||||
is TextParameter -> {
|
is TextParameter -> {
|
||||||
label = it.label
|
label = it.label
|
||||||
order = it.order
|
order = it.order
|
||||||
@@ -164,9 +163,34 @@ fun Any.listParameters(): List<Parameter> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Parameter(type
|
Parameter(
|
||||||
?: error("no type"), it as KMutableProperty1<out Any, Any?>, label, doubleRange, intRange, precision, order)
|
parameterType = type ?: error("no type"),
|
||||||
}.sortedBy { it.order }
|
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
|
fun Any.title() = this::class.findAnnotation<Description>()?.title
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import org.amshove.kluent.`should be equal to`
|
import org.amshove.kluent.*
|
||||||
import org.amshove.kluent.shouldBeInRange
|
|
||||||
import org.openrndr.color.ColorRGBa
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.extra.parameters.*
|
import org.openrndr.extra.parameters.*
|
||||||
import org.spekframework.spek2.Spek
|
import org.spekframework.spek2.Spek
|
||||||
import org.spekframework.spek2.style.specification.describe
|
import org.spekframework.spek2.style.specification.describe
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
val a = object {
|
val a = object {
|
||||||
@DoubleParameter("a double scalar", 0.0, 1.0, order = 0)
|
@DoubleParameter("a double scalar", 0.0, 1.0, order = 0)
|
||||||
@@ -15,8 +15,10 @@ val a = object {
|
|||||||
@BooleanParameter("a boolean parameter", order = 2)
|
@BooleanParameter("a boolean parameter", order = 2)
|
||||||
var b = false
|
var b = false
|
||||||
|
|
||||||
@ButtonParameter("a button parameter", order = 3)
|
@ActionParameter("an action parameter", order = 3)
|
||||||
var f = {}
|
fun a() {
|
||||||
|
error("this is to test if this function can be called")
|
||||||
|
}
|
||||||
|
|
||||||
@TextParameter("a text parameter", order = 4)
|
@TextParameter("a text parameter", order = 4)
|
||||||
var t = "test"
|
var t = "test"
|
||||||
@@ -32,7 +34,7 @@ object TestAnnotations : Spek({
|
|||||||
val list = a.listParameters()
|
val list = a.listParameters()
|
||||||
list.size `should be equal to` 6
|
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].parameterType `should be equal to` ParameterType.Double
|
||||||
list[0].label `should be equal to` "a double scalar"
|
list[0].label `should be equal to` "a double scalar"
|
||||||
list[0].doubleRange?.let {
|
list[0].doubleRange?.let {
|
||||||
@@ -41,29 +43,41 @@ object TestAnnotations : Spek({
|
|||||||
}
|
}
|
||||||
list[0].precision `should be equal to` 3
|
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].parameterType `should be equal to` ParameterType.Int
|
||||||
list[1].label `should be equal to` "an integer scalar"
|
list[1].label `should be equal to` "an integer scalar"
|
||||||
list[1].intRange?.let {
|
list[1].intRange?.let {
|
||||||
it.start `should be equal to` 1
|
it.first `should be equal to` 1
|
||||||
it.endInclusive `should be equal to` 100
|
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].parameterType `should be equal to` ParameterType.Boolean
|
||||||
list[2].label `should be equal to` "a boolean parameter"
|
list[2].label `should be equal to` "a boolean parameter"
|
||||||
list[2].precision `should be equal to` null
|
list[2].precision `should be equal to` null
|
||||||
|
|
||||||
list[3].parameterType `should be equal to` ParameterType.Button
|
list[3].parameterType `should be equal to` ParameterType.Action
|
||||||
list[3].property.name `should be equal to` "f"
|
list[3].property `should be equal to` null
|
||||||
list[3].label `should be equal to` "a button parameter"
|
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].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[4].label `should be equal to` "a text parameter"
|
||||||
|
|
||||||
list[5].parameterType `should be equal to` ParameterType.Color
|
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"
|
list[5].label `should be equal to` "a color parameter"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user