How to keep .kt files not too long

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?

3 Likes

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())
    }
}
1 Like

Thanks for that example :slight_smile:

How can I access methods and variables in the main program from the GUI?

This doesn’t work as myFunc is not part of Program, but a local function defined inside main():

fun main() = application {
    program {
        extend(setupUI(this))
    }
}

fun Program.setupUI(program: Program) =
    controlManager {
        layout {
            button {
                label = "Add curve"
                clicked { program.myFunc() } // <-- no no
            }        
        }
    }

There are basically two (mixable) approaches:

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.

1 Like

Thank you for those examples!

I’m testing the first approach (State) with a twist. In https://superkotlin.com/kotlin-mega-tutorial/#niceties I read “A Singleton is Better than Static”, so I’m using object instead of class.

It works. I don’t even need to keep an instance of State nor to pass it to setupUI().

extend(setupUI())
...
drawer.background(TPState.bgColor) // access a property
...
button {
  label = "Foo"
  clicked { TPState.rnd() } // call a method
}

Any reason not to do that?

The only tricky thing was to register a font in the UI. I changed it to this:

fun Program.setupUI() =
    controlManager {
        controlManager.fontManager.register("small", "file:/bla/fonts/ccb.ttf")

I posted the example to https://github.com/hamoid/openrndr-template/tree/master/src/main/kotlin/ (TemplateProgram*)

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:

A demo for WatchObjectDiv:

1 Like