diff --git a/orx-rabbit-control/README.MD b/orx-rabbit-control/README.MD
new file mode 100644
index 00000000..6002e42f
--- /dev/null
+++ b/orx-rabbit-control/README.MD
@@ -0,0 +1,13 @@
+# orx-rabbit-control
+
+`orx-rabbit-control` uses `orx-parameters` annotations to generate a control interface, just like `orx-gui`. It can be used as an alternative to `orx-gui` when you want to control your OPENRNDR program from a mobile device or a different computer.
+
+
+
+
+
+### Using the web client
+
+Go to [rabbitcontrol.github.io/client](https://rabbitcontrol.github.io/client/) and enter your IP-address and port.
+
+_Note: there are currently issues with the insecure websocket connection that RabbitControl uses and some browsers (like mobile Safari), we're looking for solutions..._
diff --git a/orx-rabbit-control/build.gradle b/orx-rabbit-control/build.gradle
new file mode 100644
index 00000000..2080ff96
--- /dev/null
+++ b/orx-rabbit-control/build.gradle
@@ -0,0 +1,9 @@
+dependencies {
+ api project(":orx-parameters")
+
+ implementation "org.openrndr:openrndr-core:$openrndrVersion"
+ implementation "org.openrndr:openrndr-gl3:$openrndrVersion"
+ implementation "org.lwjgl:lwjgl-opengl:3.2.3"
+ implementation "org.openrndr:openrndr-gl3-natives-macos:$openrndrVersion"
+ implementation "io.github.rabbitcontrol:rcp:0.3.0"
+}
diff --git a/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt b/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt
new file mode 100644
index 00000000..f41130be
--- /dev/null
+++ b/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt
@@ -0,0 +1,150 @@
+import org.openrndr.Extension
+import org.openrndr.Program
+import org.openrndr.color.ColorRGBa
+import org.openrndr.extra.parameters.Parameter
+import org.openrndr.extra.parameters.ParameterType
+import org.openrndr.extra.parameters.listParameters
+import org.openrndr.extra.parameters.title
+import org.openrndr.math.Vector2
+import org.openrndr.math.Vector3
+import org.openrndr.math.Vector4
+import org.rabbitcontrol.rcp.RCPServer
+import org.rabbitcontrol.rcp.model.interfaces.IParameter
+import org.rabbitcontrol.rcp.model.parameter.*
+import org.rabbitcontrol.rcp.transport.websocket.server.WebsocketServerTransporterNetty
+import java.awt.Color
+import kotlin.reflect.KMutableProperty1
+
+
+class RabbitControlServer : Extension {
+ private val rabbitServer = RCPServer()
+ private val transporter = WebsocketServerTransporterNetty()
+
+ private var parameterMap = mutableMapOf>()
+
+ init {
+ rabbitServer.addTransporter(transporter)
+ transporter.bind(10000)
+
+ /**
+ * Update the object when it has been updated in RabbitControl
+ */
+ rabbitServer.setUpdateListener {
+ val (obj, orxParameter) = parameterMap[it]!!
+ when(it) {
+ is Int32Parameter -> {
+ val v = it.value
+ orxParameter.property.qset(obj, v)
+ }
+ is Float64Parameter -> {
+ val v = it.value
+ orxParameter.property.qset(obj, v)
+ }
+ is BooleanParameter -> {
+ val v = it.value
+ orxParameter.property.qset(obj, v)
+ }
+ is StringParameter -> {
+ val v = it.value
+ orxParameter.property.qset(obj, v)
+ }
+ is RGBAParameter -> {
+ val c = it.value
+ val cc = ColorRGBa(c.red.toDouble() / 255.0, c.green.toDouble() / 255.0, c.blue.toDouble() / 255.0, c.alpha.toDouble() / 255.0)
+ orxParameter.property.qset(obj, cc)
+ }
+ is Vector2Float32Parameter -> {
+ val v = it.value
+ orxParameter.property.qset(obj, Vector2(v.x.toDouble(), v.y.toDouble()))
+ }
+ is Vector3Float32Parameter -> {
+ val v = it.value
+ orxParameter.property.qset(obj, Vector3(v.x.toDouble(), v.y.toDouble(), v.z.toDouble()))
+ }
+ is Vector4Float32Parameter -> {
+ val v = it.value
+ orxParameter.property.qset(obj, Vector4(v.x.toDouble(), v.y.toDouble(), v.z.toDouble(), v.t.toDouble()))
+ }
+ }
+ }
+ }
+
+
+ fun add(objectWithParameters: Any, label: String? = objectWithParameters.title()) {
+ val parameters = objectWithParameters.listParameters()
+
+ parameters.forEach {
+ val rabbitParam = when (it.parameterType) {
+ ParameterType.Int -> {
+ val param = rabbitServer.createInt32Parameter(it.label)
+ param.value = (it.property as KMutableProperty1).get(objectWithParameters)
+ param
+ }
+ ParameterType.Double -> {
+ val param = rabbitServer.createFloat64Parameter(it.label)
+ param.value = (it.property as KMutableProperty1).get(objectWithParameters)
+ param
+ }
+ ParameterType.Action -> {
+ val param = rabbitServer.createBangParameter(it.label)
+ param.setFunction {
+ it.function!!.call(objectWithParameters)
+ }
+ param
+ }
+ ParameterType.Boolean -> {
+ val param = rabbitServer.createBooleanParameter(it.label)
+ param.value = (it.property as KMutableProperty1).get(objectWithParameters)
+ param
+ }
+ ParameterType.Text -> {
+ val param =rabbitServer.createStringParameter(it.label)
+ param.value = (it.property as KMutableProperty1).get(objectWithParameters)
+ param
+ }
+ ParameterType.Color -> {
+ val param = rabbitServer.createRGBAParameter(it.label)
+ val c = (it.property as KMutableProperty1).get(objectWithParameters)
+ param.value = Color(c.r.toFloat(), c.g.toFloat(), c.b.toFloat(), c.a.toFloat())
+ param
+ }
+ ParameterType.Vector2 -> {
+ val param = rabbitServer.createVector2Float32Parameter(it.label)
+ val v2 = (it.property as KMutableProperty1).get(objectWithParameters)
+ param.value = org.rabbitcontrol.rcp.model.types.Vector2(v2.x.toFloat(), v2.y.toFloat())
+ param
+ }
+ ParameterType.Vector3 -> {
+ val param = rabbitServer.createVector3Float32Parameter(it.label)
+ val v3 = (it.property as KMutableProperty1).get(objectWithParameters)
+ param.value = org.rabbitcontrol.rcp.model.types.Vector3(v3.x.toFloat(), v3.y.toFloat(), v3.z.toFloat())
+ param
+ }
+ ParameterType.Vector4 -> {
+ val param = rabbitServer.createVector4Float32Parameter(it.label)
+ val v4 = (it.property as KMutableProperty1).get(objectWithParameters)
+ param.value = org.rabbitcontrol.rcp.model.types.Vector4(v4.x.toFloat(), v4.y.toFloat(), v4.z.toFloat(), v4.w.toFloat())
+ param
+ }
+
+ else -> rabbitServer.createBangParameter(it.label)
+ }
+
+ // We need to store a mapping from Rabbit parameter to target object + orx parameter
+ // so we can update the object later
+ parameterMap[rabbitParam] = Pair(objectWithParameters, it)
+ }
+
+ rabbitServer.update()
+ }
+
+ override var enabled = true
+
+ override fun shutdown(program: Program) {
+ transporter.dispose()
+ }
+}
+
+fun KMutableProperty1?.qset(obj: Any, value: T) {
+ return (this as KMutableProperty1).set(obj, value)
+}
diff --git a/orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt b/orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt
new file mode 100644
index 00000000..797a1236
--- /dev/null
+++ b/orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt
@@ -0,0 +1,62 @@
+import org.openrndr.application
+import org.openrndr.color.ColorRGBa
+import org.openrndr.draw.loadFont
+import org.openrndr.extra.parameters.*
+import org.openrndr.math.Vector2
+import org.openrndr.math.Vector3
+import org.openrndr.math.Vector4
+
+
+fun main() = application {
+ configure {
+ width = 400
+ height = 400
+ }
+
+ program {
+ val rabbit = RabbitControlServer()
+ val font= loadFont("orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf", 20.0)
+ val settings = object {
+ @TextParameter("A string")
+ var s: String = "Hello"
+
+ @DoubleParameter("A double", 0.0, 10.0)
+ var d: Double = 10.0
+
+ @BooleanParameter("A bool")
+ var b: Boolean = true
+
+ @ColorParameter("A fill color")
+ var fill = ColorRGBa.PINK
+
+ @ColorParameter("A stroke color")
+ var stroke = ColorRGBa.WHITE
+
+ @Vector2Parameter("A vector2")
+ var v2 = Vector2(200.0,200.0)
+
+ @Vector3Parameter("A vector3")
+ var v3 = Vector3(200.0, 200.0, 200.0)
+
+ @Vector4Parameter("A vector4")
+ var v4 = Vector4(200.0, 200.0, 200.0, 200.0)
+
+ @ActionParameter("Action test")
+ fun clicked() {
+ println("Clicked from RabbitControl")
+ }
+ }
+
+ rabbit.add(settings)
+
+ extend(rabbit)
+ extend {
+ drawer.background(if (settings.b) ColorRGBa.BLUE else ColorRGBa.BLACK)
+ drawer.fontMap = font
+ drawer.fill = settings.fill
+ drawer.stroke = settings.stroke
+ drawer.circle(settings.v2, settings.d)
+ drawer.text(settings.s, 10.0, 20.0)
+ }
+ }
+}
diff --git a/orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf b/orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf
new file mode 100644
index 00000000..8c082c8d
Binary files /dev/null and b/orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf differ
diff --git a/settings.gradle b/settings.gradle
index 0235c1ee..b12d25dd 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -25,6 +25,7 @@ include 'orx-camera',
'orx-palette',
'orx-panel',
'orx-poisson-fill',
+ 'orx-rabbit-control',
'orx-runway',
'orx-shader-phrases',
'orx-shade-styles',