diff --git a/orx-panel/build.gradle b/orx-panel/build.gradle index ece0f363..a2d6ffea 100644 --- a/orx-panel/build.gradle +++ b/orx-panel/build.gradle @@ -9,6 +9,7 @@ sourceSets { } dependencies { + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" demoImplementation("org.openrndr:openrndr-extensions:$openrndrVersion") demoImplementation("org.openrndr:openrndr-dialogs:$openrndrVersion") demoImplementation("com.google.code.gson:gson:$gsonVersion") diff --git a/orx-panel/src/demo/kotlin/DemoWatchObjectDiv01.kt b/orx-panel/src/demo/kotlin/DemoWatchObjectDiv01.kt new file mode 100644 index 00000000..07409b39 --- /dev/null +++ b/orx-panel/src/demo/kotlin/DemoWatchObjectDiv01.kt @@ -0,0 +1,75 @@ +import org.openrndr.application +import org.openrndr.extensions.SingleScreenshot +import org.openrndr.panel.controlManager +import org.openrndr.panel.elements.* +import org.openrndr.panel.style.* + + +fun main() = application { + configure { + width = 900 + height = 720 + } + // A very simple state + class State { + var x = 0 + var y = 0 + var z = 0 + } + program { + // -- this block is for automation purposes only + if (System.getProperty("takeScreenshot") == "true") { + extend(SingleScreenshot()) { + this.outputFile = System.getProperty("screenshotPath") + } + } + val programState = State() + val cm = controlManager { + layout { + styleSheet(has class_ "matrix") { + this.width = 100.percent + } + + styleSheet(has class_ "row") { + this.display = Display.FLEX + this.flexDirection = FlexDirection.Row + this.width = 100.percent + + child(has type "slider") { + this.width = 80.px + } + } + + slider { + label = "x" + precision = 0 + bind(programState::x) + } + + slider { + label = "y" + precision = 0 + bind(programState::y) + } + + watchObjectDiv("matrix", watchObject = object { + // for primitive types we have to use property references + val x = programState::x + val y = programState::y + }) { + for (y in 0 until watchObject.y.get()) { + div("row") { + for (x in 0 until watchObject.x.get()) { + button() { + label = "$x, $y" + } + } + } + } + } + } + } + extend(cm) + } +} + diff --git a/orx-panel/src/main/kotlin/org/openrndr/panel/elements/WatchObjectDiv.kt b/orx-panel/src/main/kotlin/org/openrndr/panel/elements/WatchObjectDiv.kt new file mode 100644 index 00000000..da913d87 --- /dev/null +++ b/orx-panel/src/main/kotlin/org/openrndr/panel/elements/WatchObjectDiv.kt @@ -0,0 +1,77 @@ +package org.openrndr.panel.elements + +import kotlinx.coroutines.Job +import kotlinx.coroutines.yield +import org.openrndr.draw.Drawer +import org.openrndr.launch +import org.openrndr.panel.elements.* +import org.openrndr.panel.hash.watchHash +import kotlin.reflect.KMutableProperty0 + +class WatchObjectDiv( + val watchObject: T, + private val builder: WatchObjectDiv.(T) -> Unit +) : Div(), + DisposableElement { + override var disposed: Boolean = false + private var objectStateHash = watchHash(watchObject) + private var watchJob: Job? = null + + + override fun dispose() { + super.dispose() + for (child in children) { + child.parent = null + (child as? DisposableElement)?.dispose() + } + children.clear() + } + + fun regenerate(force: Boolean = false) { + var regenerate = force + if (watchHash(watchObject) != objectStateHash) { + regenerate = true + } + + if (regenerate) { + for (child in children) { + child.parent = null + (child as? DisposableElement)?.dispose() + } + objectStateHash = watchHash(watchObject) + children.clear() + builder(watchObject) + + requestRedraw() + } + } + + fun checkJob() { + if (watchJob == null) { + watchJob = (root() as? Body)?.controlManager?.program?.launch { + while (!disposed) { + regenerate() + yield() + } + } + } + } + + override fun draw(drawer: Drawer) { + checkJob() + super.draw(drawer) + } +} + +fun Element.watchObjectDiv( + vararg classes: String, + watchObject: T, + builder: WatchObjectDiv.(T) -> Unit +) : WatchObjectDiv { + val wd = WatchObjectDiv(watchObject, builder) + wd.classes.addAll(classes.map { ElementClass(it) }) + this.append(wd) + wd.regenerate(true) + wd.checkJob() + return wd +} diff --git a/orx-panel/src/main/kotlin/org/openrndr/panel/hash/WatchHash.kt b/orx-panel/src/main/kotlin/org/openrndr/panel/hash/WatchHash.kt new file mode 100644 index 00000000..d127c4dd --- /dev/null +++ b/orx-panel/src/main/kotlin/org/openrndr/panel/hash/WatchHash.kt @@ -0,0 +1,19 @@ +package org.openrndr.panel.hash + +import kotlin.reflect.KProperty0 +import kotlin.reflect.full.declaredMemberProperties + +fun watchHash(toHash: Any): Int { + var hash = 0 + for (property in toHash::class.declaredMemberProperties) { + val v = (property.getter)(toHash) + if (v is KProperty0<*>) { + val pv = v.get() + hash = 31 * hash + (pv?.hashCode() ?: 0) + } else { + hash = 31 * hash + (v?.hashCode() ?: 0) + } + } + return hash +} +