Add orx-gui
Includes changes to orx-parameters to make it work
This commit is contained in:
@@ -38,6 +38,7 @@ plugins {
|
||||
|
||||
project.ext {
|
||||
openrndrVersion = "0.3.38"
|
||||
panelVersion = "0.3.20"
|
||||
kotlinVersion = "1.3.61"
|
||||
spekVersion = "2.0.9"
|
||||
libfreenectVersion = "0.5.7-1.5.2"
|
||||
|
||||
4
orx-gui/build.gradle
Normal file
4
orx-gui/build.gradle
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
compile project(":orx-parameters")
|
||||
compile "org.openrndr.panel:openrndr-panel:$panelVersion"
|
||||
}
|
||||
143
orx-gui/src/main/kotlin/Gui.kt
Normal file
143
orx-gui/src/main/kotlin/Gui.kt
Normal file
@@ -0,0 +1,143 @@
|
||||
package org.openrndr.extra.gui
|
||||
|
||||
import org.openrndr.Extension
|
||||
import org.openrndr.Program
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.extra.parameters.*
|
||||
import org.openrndr.panel.ControlManager
|
||||
import org.openrndr.panel.controlManager
|
||||
import org.openrndr.panel.elements.*
|
||||
import org.openrndr.panel.style.*
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
||||
@Suppress("unused", "UNCHECKED_CAST")
|
||||
class GUI : Extension {
|
||||
private var onChangeListener: ((name: String, value: Any?) -> Unit)? = null
|
||||
override var enabled = true
|
||||
|
||||
private lateinit var panel: ControlManager
|
||||
|
||||
fun onChange(listener: (name: String, value: Any?) -> Unit) {
|
||||
onChangeListener = listener
|
||||
}
|
||||
|
||||
override fun setup(program: Program) {
|
||||
panel = program.controlManager {
|
||||
styleSheet(has class_ "container") {
|
||||
this.display = Display.FLEX
|
||||
this.flexDirection = FlexDirection.Row
|
||||
this.width = 200.px
|
||||
this.height = 100.percent
|
||||
}
|
||||
|
||||
styleSheet(has class_ "sidebar") {
|
||||
this.width = 200.px
|
||||
this.paddingBottom = 20.px
|
||||
this.paddingTop = 10.px
|
||||
this.paddingLeft = 10.px
|
||||
this.paddingRight = 10.px
|
||||
this.marginRight = 2.px
|
||||
this.height = 100.percent
|
||||
this.background = Color.RGBa(ColorRGBa.GRAY.copy(a = 0.2))
|
||||
}
|
||||
|
||||
styleSheet(has type "dropdown-button") {
|
||||
this.width = 175.px
|
||||
}
|
||||
|
||||
styleSheet(has class_ "filter") {
|
||||
this.width = 185.px
|
||||
this.paddingBottom = 20.px
|
||||
this.paddingTop = 10.px
|
||||
this.marginRight = 2.px
|
||||
this.paddingRight = 5.px
|
||||
}
|
||||
|
||||
layout {
|
||||
div("container") {
|
||||
id = "container"
|
||||
div("sidebar") {
|
||||
id = "sidebar"
|
||||
for ((obj, parameters) in trackedParams) {
|
||||
h3 { obj.title() ?: "untitled" }
|
||||
|
||||
for (parameter in parameters) {
|
||||
addSlider(obj, parameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panel.enabled = enabled
|
||||
|
||||
program.extend(panel)
|
||||
}
|
||||
|
||||
private fun Div.addSlider(obj: Any, parameter: Parameter) {
|
||||
|
||||
when(val type = parameter.parameterType) {
|
||||
ParameterType.Int -> {
|
||||
slider {
|
||||
label = parameter.label
|
||||
range = Range(parameter.intRange!!.start.toDouble(), parameter.intRange!!.endInclusive.toDouble())
|
||||
precision = parameter.precision!!
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
ParameterType.Double -> {
|
||||
slider {
|
||||
label = parameter.label
|
||||
range = Range(parameter.doubleRange!!.start, parameter.doubleRange!!.endInclusive.toDouble())
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParameterType.Button -> {
|
||||
button {
|
||||
label = parameter.label
|
||||
events.clicked.subscribe {
|
||||
parameter.property.call()
|
||||
onChangeListener?.invoke(parameter.property.name, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParameterType.Boolean -> {
|
||||
slider {
|
||||
label = parameter.label
|
||||
range = Range(0.0, 1.0)
|
||||
precision = 0
|
||||
value = if ((parameter.property as KMutableProperty1<Any, Boolean>).get(obj)) 1.0 else 0.0
|
||||
events.valueChanged.subscribe {
|
||||
value = it.newValue
|
||||
(parameter.property as KMutableProperty1<Any, Boolean>).set(obj, value > 0.5)
|
||||
onChangeListener?.invoke(parameter.property.name, it.newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val trackedParams = mutableMapOf<Any, List<Parameter>>()
|
||||
|
||||
/**
|
||||
* Add an object to the GUI
|
||||
*/
|
||||
fun add(objectWithParameters: Any) {
|
||||
val parameters = objectWithParameters.listParameters()
|
||||
if (parameters.size > 0) {
|
||||
trackedParams[objectWithParameters] = parameters
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.openrndr.extra.parameters
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
@@ -7,7 +8,7 @@ import kotlin.reflect.full.findAnnotation
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Description(val description: String)
|
||||
annotation class Description(val title: String, val description: String = "")
|
||||
|
||||
/**
|
||||
* DoubleParameter annotation for a double precision Filter parameter
|
||||
@@ -41,6 +42,37 @@ annotation class IntParameter(val label: String, val low: Int, val high: Int, va
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class BooleanParameter(val label: String, val order: Int = Integer.MAX_VALUE)
|
||||
|
||||
|
||||
/**
|
||||
* ButtonParameter annotation for an integer Filter parameter
|
||||
* @property label a short description of the parameter
|
||||
* @property order hint for where to place the parameter in user interfaces
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ButtonParameter(val label: String, val order: Int = Integer.MAX_VALUE)
|
||||
|
||||
enum class ParameterType(val annotation: KClass<out Annotation>) {
|
||||
Double(DoubleParameter::class),
|
||||
Int(IntParameter::class),
|
||||
Boolean(BooleanParameter::class),
|
||||
Button(ButtonParameter::class)
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun forParameterAnnotationClass(annotation: Annotation) : ParameterType {
|
||||
return when(annotation) {
|
||||
is DoubleParameter -> Double
|
||||
is IntParameter -> Int
|
||||
is BooleanParameter -> Boolean
|
||||
is ButtonParameter -> Button
|
||||
else -> error("no type for $annotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parameter summary class. This is used by [listParameters] as a way to report parameters in
|
||||
* a unified form.
|
||||
@@ -53,6 +85,7 @@ annotation class BooleanParameter(val label: String, val order: Int = Integer.MA
|
||||
* @property order a hint for where in the ui this parameter is placed, lower value means higher priority
|
||||
*/
|
||||
class Parameter(
|
||||
val parameterType: ParameterType,
|
||||
val property: KMutableProperty1<out Any, Any?>,
|
||||
val label: String,
|
||||
val doubleRange: ClosedRange<Double>?,
|
||||
@@ -69,34 +102,49 @@ fun Any.listParameters(): List<Parameter> {
|
||||
it.visibility == KVisibility.PUBLIC &&
|
||||
(it.findAnnotation<BooleanParameter>() != null ||
|
||||
it.findAnnotation<IntParameter>() != null ||
|
||||
it.findAnnotation<DoubleParameter>() != null)
|
||||
it.findAnnotation<DoubleParameter>() != null ||
|
||||
it.findAnnotation<ButtonParameter>() != null)
|
||||
}.map {
|
||||
val annotations = listOf(it.findAnnotation<BooleanParameter>(), it.findAnnotation<IntParameter>(), it.findAnnotation<DoubleParameter>()).filterNotNull()
|
||||
val annotations = listOf(it.findAnnotation<BooleanParameter>(), it.findAnnotation<IntParameter>(), it.findAnnotation<DoubleParameter>(), it.findAnnotation<ButtonParameter>()).filterNotNull()
|
||||
var intRange: IntRange? = null
|
||||
var doubleRange: ClosedRange<Double>? = null
|
||||
var order: Int = Integer.MAX_VALUE
|
||||
var label: String = ""
|
||||
var precision: Int? = null
|
||||
var type : ParameterType? = null
|
||||
|
||||
annotations.forEach {
|
||||
when(it) {
|
||||
type = ParameterType.forParameterAnnotationClass(it)
|
||||
when (it) {
|
||||
is BooleanParameter -> {
|
||||
label = it.label
|
||||
order = it.order
|
||||
}
|
||||
is DoubleParameter -> {
|
||||
label = it.label
|
||||
doubleRange = it.low .. it.high
|
||||
doubleRange = it.low..it.high
|
||||
precision = it.precision
|
||||
order = it.order
|
||||
}
|
||||
is IntParameter -> {
|
||||
label = it.label
|
||||
intRange = it.low .. it.high
|
||||
intRange = it.low..it.high
|
||||
order = it.order
|
||||
}
|
||||
is ButtonParameter -> {
|
||||
label = it.label
|
||||
order = it.order
|
||||
}
|
||||
}
|
||||
}
|
||||
Parameter(it as KMutableProperty1<out Any, Any?>, label, doubleRange, intRange, precision, order)
|
||||
Parameter(type?:error("no type"), it as KMutableProperty1<out Any, Any?>, label, doubleRange, intRange, precision, order)
|
||||
}.sortedBy { it.order }
|
||||
}
|
||||
|
||||
fun Any.title() = this::class.findAnnotation<Description>()?.let {
|
||||
it.title
|
||||
}
|
||||
|
||||
fun Any.description() = this::class.findAnnotation<Description>()?.let {
|
||||
it.description
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import org.amshove.kluent.`should be equal to`
|
||||
import org.amshove.kluent.shouldBeInRange
|
||||
import org.openrndr.extra.parameters.BooleanParameter
|
||||
import org.openrndr.extra.parameters.DoubleParameter
|
||||
import org.openrndr.extra.parameters.IntParameter
|
||||
import org.openrndr.extra.parameters.listParameters
|
||||
import org.openrndr.extra.parameters.*
|
||||
import org.spekframework.spek2.Spek
|
||||
import org.spekframework.spek2.style.specification.describe
|
||||
|
||||
@@ -16,14 +13,18 @@ val a = object {
|
||||
|
||||
@BooleanParameter("a boolean parameter", order = 2)
|
||||
var b = false
|
||||
|
||||
@ButtonParameter("a button parameter", order = 3)
|
||||
var f = {}
|
||||
}
|
||||
|
||||
object TestAnnotations : Spek({
|
||||
describe("an annotated object") {
|
||||
it("has listable parameters") {
|
||||
val list = a.listParameters()
|
||||
list.size `should be equal to` 3
|
||||
list.size `should be equal to` 4
|
||||
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 {
|
||||
it.start shouldBeInRange 0.0 .. 0.0001
|
||||
@@ -32,6 +33,7 @@ object TestAnnotations : Spek({
|
||||
list[0].precision `should be equal to` 3
|
||||
|
||||
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
|
||||
@@ -39,8 +41,12 @@ object TestAnnotations : Spek({
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ include 'orx-camera',
|
||||
'orx-integral-image',
|
||||
'orx-interval-tree',
|
||||
'orx-jumpflood',
|
||||
'orx-gui',
|
||||
'orx-kdtree',
|
||||
'orx-mesh-generators',
|
||||
'orx-midi',
|
||||
|
||||
Reference in New Issue
Block a user