Overview
Let's start with an example:
Broadly, this script proceeds as follows:
- Tells the IDE to display an interactive user interface and its current configuration (the slider returned by the
render
function) - Waits for messages from the IDE informing it how the UI has changed (eg: when the user moves the slider to a new position)
- Reacts to that change by updating the
ui.State
instance and, potentially, re-invokingrender
.
The steps above repeat in a loop until the script is terminated.
Let's dig into each of these steps, starting with the ui.run
function.
ui.run
The
run
function has a single required argument - a function (renderer
) that returns the current user interface. In the example above, this would be therender
function that returns a singleSlider
as the UI.All other positional and keyword arguments are bound to the
renderer
function (equivalent tofunctools.partial
). These are useful for passing in expensive resources that you don't want to re-create every time the user interfaces changes (theCanvas
instance in our example).
The
renderer
function is invoked at the beginning to render the initial UI. Therun
function then enters an infinite loop receiving and processing UI events (like slider drags, button clicks, etc).The rendered UI is completely dynamic, and some events (like the slider drag in our example) can trigger a re-rendering by re-invoking the
renderer
function. You can also perform other updates here that are a function of the UI state (like the canvas drawing in our example).
ui.State
You might have noticed the ui.State
instance in the example earlier:
It provides a few key functionalities:
It acts as a simple namespace for grouping all mutable state for the user interface.
state = ui.State( learning_rate=0.03, warmup=True ) # Simple access to the attributes assert type(state.warmup) == bool # Mutable state.learning_rate = 0.2The special
bind
accessor provides a way for widgets (likeui.Slider
) to "bind" their value to a particular state attribute.ui.Slider( # While state.radius is just an int or a float, state.bind.radius # is a special "Binding" instance that allows for both reading and # updating the value of state.radius value=state.bind.radius, range=(10, 100) )The
value
argument forui.Slider
can accept either a numeric type (int
orfloat
), or aBinding
instance that acts as an indirection to both fetching and mutating a numeric value. This essentially allows the IDE to auto-updatestate.radius
whenever the slider is changed.Mutating any attributes of a
ui.State
instance (including via binding auto-updates) automatically triggers a UI re-rendering. In our slider example, this is how therender
function is invoked whenever the user drags the radius slider.