From df65502cbead9eee25c8bac1140ebd4db070f8fe Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Sun, 2 Feb 2020 01:04:09 +0100 Subject: [PATCH] Add orx-gui Includes changes to orx-parameters to make it work --- build.gradle | 1 + orx-gui/build.gradle | 4 + orx-gui/src/main/kotlin/Gui.kt | 143 ++++++++++++++++++ orx-parameters/src/main/kotlin/Annotations.kt | 64 +++++++- .../src/test/kotlin/TestAnnotations.kt | 16 +- settings.gradle | 1 + 6 files changed, 216 insertions(+), 13 deletions(-) create mode 100644 orx-gui/build.gradle create mode 100644 orx-gui/src/main/kotlin/Gui.kt diff --git a/build.gradle b/build.gradle index 41c4e2ac..6443d322 100644 --- a/build.gradle +++ b/build.gradle @@ -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" diff --git a/orx-gui/build.gradle b/orx-gui/build.gradle new file mode 100644 index 00000000..90b58bba --- /dev/null +++ b/orx-gui/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(":orx-parameters") + compile "org.openrndr.panel:openrndr-panel:$panelVersion" +} \ No newline at end of file diff --git a/orx-gui/src/main/kotlin/Gui.kt b/orx-gui/src/main/kotlin/Gui.kt new file mode 100644 index 00000000..8e02b811 --- /dev/null +++ b/orx-gui/src/main/kotlin/Gui.kt @@ -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).get(obj).toDouble() + events.valueChanged.subscribe { + (parameter.property as KMutableProperty1).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).get(obj) + events.valueChanged.subscribe { + (parameter.property as KMutableProperty1).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).get(obj)) 1.0 else 0.0 + events.valueChanged.subscribe { + value = it.newValue + (parameter.property as KMutableProperty1).set(obj, value > 0.5) + onChangeListener?.invoke(parameter.property.name, it.newValue) + } + } + } + } + } + + val trackedParams = mutableMapOf>() + + /** + * Add an object to the GUI + */ + fun add(objectWithParameters: Any) { + val parameters = objectWithParameters.listParameters() + if (parameters.size > 0) { + trackedParams[objectWithParameters] = parameters + } + } +} diff --git a/orx-parameters/src/main/kotlin/Annotations.kt b/orx-parameters/src/main/kotlin/Annotations.kt index 5f6d8601..818c8bec 100644 --- a/orx-parameters/src/main/kotlin/Annotations.kt +++ b/orx-parameters/src/main/kotlin/Annotations.kt @@ -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) { + 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, val label: String, val doubleRange: ClosedRange?, @@ -69,34 +102,49 @@ fun Any.listParameters(): List { it.visibility == KVisibility.PUBLIC && (it.findAnnotation() != null || it.findAnnotation() != null || - it.findAnnotation() != null) + it.findAnnotation() != null || + it.findAnnotation() != null) }.map { - val annotations = listOf(it.findAnnotation(), it.findAnnotation(), it.findAnnotation()).filterNotNull() + val annotations = listOf(it.findAnnotation(), it.findAnnotation(), it.findAnnotation(), it.findAnnotation()).filterNotNull() var intRange: IntRange? = null var doubleRange: ClosedRange? = 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, label, doubleRange, intRange, precision, order) + Parameter(type?:error("no type"), it as KMutableProperty1, label, doubleRange, intRange, precision, order) }.sortedBy { it.order } -} \ No newline at end of file +} + +fun Any.title() = this::class.findAnnotation()?.let { + it.title +} + +fun Any.description() = this::class.findAnnotation()?.let { + it.description +} diff --git a/orx-parameters/src/test/kotlin/TestAnnotations.kt b/orx-parameters/src/test/kotlin/TestAnnotations.kt index f30c69f2..70046c09 100644 --- a/orx-parameters/src/test/kotlin/TestAnnotations.kt +++ b/orx-parameters/src/test/kotlin/TestAnnotations.kt @@ -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" } } }) diff --git a/settings.gradle b/settings.gradle index 5ee09606..76c0375b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ include 'orx-camera', 'orx-integral-image', 'orx-interval-tree', 'orx-jumpflood', + 'orx-gui', 'orx-kdtree', 'orx-mesh-generators', 'orx-midi',