The available example are all limited to one file. Are there examples of larger projects?
One part of OPENRNDR programs that often grow long is
extend(ControlManager()) {...}
with it stylesheets and inputs. Is it possible to place that section in a different file? Would that complicate things as all the program variables would no longer be available to the GUI?
Are there any suggested techniques to keep longer OPENRNDR programs maintainable?
Absolutely an important question to ask, and the answer is something I feel we haven’t communicated before.
I will come back to this from time-to-time, as I think there is a lot to write about this.
In the case of the ControlManager, you can set up the UI in a separate file like this:
fun Program.setupUI() =
controlManager {
layout {
// setup the ui here
}
}
The mechanism I use here (fun Program.setupUI()) is called an extension function, they are extremely handy for adding functionality to existing classes.
Then your main program would look like this:
fun main() = application {
program {
extend(setupUI())
}
}
The first one is to create a state class that is shared between your program and the ui and use bind to keep values in sync.
class State {
var someValue: Double = 0.0
}
fun Program.setupUI(state: State): ControlManager = controlManager {
layout {
slider {
range = Range(0.0, 1.0)
bind(state::someValue)
}
}
}
fun main() {
application {
program {
val state = State()
extend(setupUI(state))
extend {
drawer.background(ColorRGBa.PINK.shade(state.someValue))
}
}
}
}
The second one is to create a functions class that is, again, shared between your program and the ui. The functions class is used to pass listener functions to the controls made in setupUI.
class Functions {
var someFunction = { e: Slider.ValueChangedEvent -> }
}
fun Program.setupUIFunctions(functions: Functions): ControlManager = controlManager {
layout {
slider {
range = Range(0.0, 1.0)
events.valueChanged.subscribe(functions.someFunction)
}
}
}
fun main() {
application {
program {
// -- here we keep state locally
var someValue = 0.0
val functions = Functions()
functions.someFunction = { e ->
// -- here someValue is captured in the function closure
someValue = e.newValue
}
extend(setupUIFunctions(functions))
extend {
drawer.background(ColorRGBa.PINK.shade(someValue))
}
}
}
}
It is possible to combine both state and functions methods by creating a setupUI that takes both a state and a functions argument.
Singletons will definitely work and there is nothing inherently wrong with using them. However, I personally find the Singletonian behavior to impose too many constraints on the unforeseeable future of my progams. I may end up in the situation in which I suddenly require two instances of my state class, at which point I’d have to change code (which takes time and is error-prone). Besides that, I am also not sure how Singletons interact with libraries like GSon.
We recently developed tools for orx-panel to simplify the creation of data-driven UIs. The tools are called WatchListDiv, WatchPropertyDiv and WatchObjectDiv and fit in nicely with the state class example I demonstrated before.
A demo for WatchListDiv including the loading and saving of state to json files: