diff --git a/orx-rabbit-control/README.MD b/orx-rabbit-control/README.MD
index 6002e42f..a0bdb972 100644
--- a/orx-rabbit-control/README.MD
+++ b/orx-rabbit-control/README.MD
@@ -6,6 +6,10 @@
+For examples, check out the [demo](./src/demo/kotlin) folder.
+
+
+
### Using the web client
Go to [rabbitcontrol.github.io/client](https://rabbitcontrol.github.io/client/) and enter your IP-address and port.
diff --git a/orx-rabbit-control/build.gradle b/orx-rabbit-control/build.gradle
index 2080ff96..af139d09 100644
--- a/orx-rabbit-control/build.gradle
+++ b/orx-rabbit-control/build.gradle
@@ -1,9 +1,27 @@
+repositories {
+ maven { url 'https://jitpack.io' }
+}
+
+sourceSets {
+ demo {
+ java {
+ srcDirs = ["src/demo/kotlin"]
+ compileClasspath += main.getCompileClasspath()
+ runtimeClasspath += main.getRuntimeClasspath()
+ }
+ }
+}
+
dependencies {
api project(":orx-parameters")
+ api project(":orx-compositor")
+ api project(":orx-image-fit")
- 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"
+ implementation "com.google.zxing:core:3.3.0"
+ implementation "com.google.zxing:javase:3.3.0"
+
+ demoRuntimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion")
+ demoRuntimeOnly("org.openrndr:openrndr-gl3-natives-$openrndrOS:$openrndrVersion")
+ demoImplementation(sourceSets.getByName("main").output)
}
diff --git a/orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt b/orx-rabbit-control/src/demo/kotlin/DemoRabbitControl.kt
similarity index 93%
rename from orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt
rename to orx-rabbit-control/src/demo/kotlin/DemoRabbitControl.kt
index 797a1236..db808803 100644
--- a/orx-rabbit-control/src/test/kotlin/RabbitControlServerTest.kt
+++ b/orx-rabbit-control/src/demo/kotlin/DemoRabbitControl.kt
@@ -9,13 +9,13 @@ import org.openrndr.math.Vector4
fun main() = application {
configure {
- width = 400
- height = 400
+ width = 800
+ height = 800
}
program {
val rabbit = RabbitControlServer()
- val font= loadFont("orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf", 20.0)
+ val font= loadFont("orx-rabbit-control/src/demo/resources/fonts/Roboto-Regular.ttf", 20.0)
val settings = object {
@TextParameter("A string")
var s: String = "Hello"
diff --git a/orx-rabbit-control/src/demo/kotlin/DemoRabbitControlManualOverlay.kt b/orx-rabbit-control/src/demo/kotlin/DemoRabbitControlManualOverlay.kt
new file mode 100644
index 00000000..784d6940
--- /dev/null
+++ b/orx-rabbit-control/src/demo/kotlin/DemoRabbitControlManualOverlay.kt
@@ -0,0 +1,45 @@
+import org.openrndr.KEY_HOME
+import org.openrndr.application
+import org.openrndr.color.ColorRGBa
+import org.openrndr.extra.parameters.*
+
+
+fun main() = application {
+ configure {
+ width = 800
+ height = 800
+ }
+
+ program {
+ val rabbit = RabbitControlServer(showQRUntilClientConnects = false)
+
+ val settings = object {
+ @BooleanParameter("White on black")
+ var whiteOnBlack: Boolean = true
+ }
+
+ rabbit.add(settings)
+ extend(rabbit)
+
+ /**
+ * Example: only show the QR code when the [KEY_HOME] button is pressed
+ */
+ keyboard.keyDown.listen {
+ when (it.key) {
+ KEY_HOME -> rabbit.showQRCode = true
+ }
+ }
+
+ keyboard.keyUp.listen {
+ when (it.key) {
+ KEY_HOME -> rabbit.showQRCode = false
+ }
+ }
+
+ extend {
+ drawer.background(if (settings.whiteOnBlack) ColorRGBa.BLACK else ColorRGBa.WHITE)
+ drawer.fill = if (settings.whiteOnBlack) ColorRGBa.WHITE else ColorRGBa.BLACK
+ drawer.circle(drawer.bounds.center, 250.0)
+ }
+ }
+}
diff --git a/orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf b/orx-rabbit-control/src/demo/resources/fonts/Roboto-Regular.ttf
similarity index 100%
rename from orx-rabbit-control/src/test/resources/fonts/Roboto-Regular.ttf
rename to orx-rabbit-control/src/demo/resources/fonts/Roboto-Regular.ttf
diff --git a/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt b/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt
index f41130be..293cb829 100644
--- a/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt
+++ b/orx-rabbit-control/src/main/kotlin/RabbitControlServer.kt
@@ -1,30 +1,78 @@
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.client.j2se.MatrixToImageWriter
+import com.google.zxing.qrcode.QRCodeWriter
import org.openrndr.Extension
import org.openrndr.Program
import org.openrndr.color.ColorRGBa
+import org.openrndr.draw.ColorBufferProxy
+import org.openrndr.draw.Drawer
+import org.openrndr.draw.isolated
+import org.openrndr.extra.compositor.*
+import org.openrndr.extra.fx.blend.Darken
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.extras.imageFit.FitMethod
+import org.openrndr.extras.imageFit.imageFit
+import org.openrndr.internal.colorBufferLoader
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.Vector4
+import org.openrndr.math.mix
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 java.io.File
+import java.net.InetSocketAddress
+import java.net.Socket
+import java.nio.file.FileSystems
+import java.nio.file.Path
import kotlin.reflect.KMutableProperty1
-class RabbitControlServer : Extension {
+class RabbitControlServer(private val showQRUntilClientConnects: Boolean = true, port: Int = 10000) : Extension {
private val rabbitServer = RCPServer()
private val transporter = WebsocketServerTransporterNetty()
private var parameterMap = mutableMapOf>()
+ private var qrCodeImageProxy: ColorBufferProxy? = null
+ private var qrImagePath: Path? = null
+ private var qrOverlayComposition: Composite? = null
+
+
+ /**
+ * Animate the opacity to make it look smooooth
+ */
+ private var currentOpacity = 0.0
+
+ private var targetOpacity: Double = 0.0
+ get() = if (shouldShowQR) 0.8 else 0.0
+
+ private var shouldShowQR = false
+ get() = (rabbitServer.connectionCount == 0 && showQRUntilClientConnects) || showQRCode
+
+
+ /**
+ * Used to manually show and hide the QR code and override the default
+ * behaviour of (only) showing the code when no clients are connected
+ */
+ var showQRCode = false
+
init {
rabbitServer.addTransporter(transporter)
- transporter.bind(10000)
+ transporter.bind(port)
+
+ // FIXME please help me find a better way to get the local address
+ val socket = Socket()
+ socket.connect(InetSocketAddress("google.com", 80))
+ val ip = socket.localAddress.toString().replace("/", "")
+
+ val clientUrlWithHash = "https://rabbitcontrol.github.io/client/#$ip:$port"
+ qrCodeImageProxy = getQRCodeImageProxy(barcodeText = clientUrlWithHash)
+ println("RabbitControl Web Client: $clientUrlWithHash")
/**
* Update the object when it has been updated in RabbitControl
@@ -70,7 +118,38 @@ class RabbitControlServer : Extension {
}
- fun add(objectWithParameters: Any, label: String? = objectWithParameters.title()) {
+ override fun setup(program: Program) {
+ /**
+ * Creating the Composite for the overlay needs to happen in setup(),
+ * as we need access to [Program.drawer]
+ */
+ qrOverlayComposition = compose {
+ layer {
+ draw {
+ program.drawer.isolated {
+ fill = ColorRGBa.WHITE.opacify(currentOpacity)
+ stroke = null
+ rectangle(0.0,0.0, width.toDouble(), height.toDouble())
+ }
+ }
+
+ layer {
+ blend(Darken()) {
+ clip = true
+ }
+
+ draw {
+ qrCodeImageProxy!!.colorBuffer?.let {
+ program.drawer.imageFit(it, program.width / 4.0,program.height / 4.0, program.width * .5, program.height * .5, 0.0,0.0, FitMethod.Contain)
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ fun add(objectWithParameters: Any) {
val parameters = objectWithParameters.listParameters()
parameters.forEach {
@@ -142,6 +221,26 @@ class RabbitControlServer : Extension {
override fun shutdown(program: Program) {
transporter.dispose()
+ // Delete the temporary file
+ File(qrImagePath!!.toUri()).delete()
+ }
+
+ // FIXME is it possible to avoid the file entirely?
+ private fun getQRCodeImageProxy(barcodeText: String): ColorBufferProxy {
+ val qrCodeWriter = QRCodeWriter()
+ val bitMatrix = qrCodeWriter.encode(barcodeText, BarcodeFormat.QR_CODE, 500, 500)
+ qrImagePath = FileSystems.getDefault().getPath("./qr.JPG")
+ MatrixToImageWriter.writeToPath(bitMatrix, "JPG", qrImagePath)
+ return colorBufferLoader.loadFromUrl(qrImagePath!!.toUri().toURL().toString())
+ }
+
+ override fun afterDraw(drawer: Drawer, program: Program) {
+ currentOpacity = mix(targetOpacity, currentOpacity, 0.8)
+
+ // Don't draw if it isn't necessary
+ if (currentOpacity > 0.0) {
+ qrOverlayComposition?.draw(drawer)
+ }
}
}