UI Callbacks¶
UI Callbacks (STL.UI_CALLBACKS) are the mechanism by which widget interactions are dispatched to handler code. Rather than a proliferation of bare on ui_control() blocks throughout the codebase, IVLS routes widget events through a registered callback table using uicb.Bind and uicb.Fire.
Why This System Exists¶
KSP's raw on ui_control() syntax requires one block per widget, scattered wherever widgets are defined. In a large instrument with hundreds of controls this becomes unmanageable. The uicb system centralizes dispatch and adds programmatic firing — you can trigger the same logic as a widget interaction from code, with explicit arguments.
Key Concepts¶
uicb.Bind and uicb.Fire¶
uicb.Bind associates a widget's UI ID with a named callback tag. When the widget fires, the callback is invoked:
uicb.Bind(widget_id, callback_tag)uicb.Fire triggers a callback by tag without any widget interaction — widgetless firing for programmatic events. It sets an internal stub slider to the given value, then invokes the callback:
uicb.Fire(index, callback_tag, value)The on ui_control() Pattern¶
All on ui_control() blocks must live inside cb Functions. Never place them outside a node or in cb Init:
cb Functions:
on ui_control(nav.Main)
plc.ui.section_id := plc.ui.section.POLYCORE
call uir.update()
end on
on ui_control(nav.Console)
plc.ui.section_id := plc.ui.section.CONSOLE
call uir.update()
end onFor navigation buttons and stateless toggles, this direct form is appropriate. For widget-to-parameter plumbing (knobs, sliders, menus mapped to a box par), prefer the Box binding system — set ctrl_id in the *-ui-binds.ksp file (see engine-box) rather than writing a manual handler.
Dynamic Control ID Lookup¶
When widgets are determined at runtime (arrays of UI IDs from Jinja-generated init code), use get_ui_id for lookup and search() for reverse lookup:
// Store during init
declare my_buttons[4]
<! for i in range(0, 4) !>
my_buttons[<:i:>] := get_ui_id(Main.Layer.<:i:>.Button)
<! endfor !>
// In callback, identify which slot was touched
on ui_control(some_button_widget)
declare slot := search(my_buttons, get_ui_id(some_button_widget))
if slot # -1
// handle slot
end if
end onThe multiplex system uses the same search() pattern to identify which layer's shared knob was moved.
Widgetless Firing¶
When code needs to simulate a user interaction — for example forcing a UI refresh after a browser action changes the selected key — use uicb.Fire:
// After the browser assigns a sound, fire the sel_key callback
// so UI state updates as if the user moved the key selector
uicb.Fire(1, phr_sel_key_changed, phr.global.sel_key)This causes par_cb.response to fire with the correct par and value — the same path as if the user had moved the widget.
UI.NULL¶
When a parameter has no linked widget (set only by code or by the browser), assign UI.NULL as the ctrl_id:
phr.global.speed.ctrl_id := UI.NULLUI.NULL is defined as get_ui_id(null_widget) by the STL. Using it explicitly documents intent and avoids accidental binding to widget ID zero.
When to Use Which Approach¶
| Use case | Approach |
|---|---|
| Knob / slider / menu mapped to a box par | Set par.ctrl_id in *-ui-binds.ksp |
| Navigation button updating section state | on ui_control() in cb Functions |
| Button triggering a function with no associated par | on ui_control() + direct function call |
| Multiple widgets mapping to one par (mode-dependent) | Multiplex override in *-ui-binds.ksp |
| Widgetless programmatic trigger | uicb.Fire(...) |
Connections to Other Parts of IVLS¶
UI Callbacks drive the ui-routines system — most on ui_control() handlers that change navigation state end with call uir.update(). The engine-box system handles most widget-to-parameter binding automatically once ctrl_id is set, reducing the need for manual on ui_control() blocks. The console has its own callback hook (console.ui_cb.response) separate from the general uicb system.
Patterns and Caveats¶
- All
on ui_control()blocks must be insidecb Functions. The IVLS compiler only processes them in that context. - Do not write
on ui_control()handlers for parameters that already havectrl_idset via the Engine Box. The box handles those automatically; a manual handler would duplicate the logic. - Jinja loops generate repetitive binding code without copy-paste errors. Use them for arrays of similar widgets (layer knobs, browser entry buttons, tag buttons).
Related¶
- engine-box — the box system handles most widget-to-parameter binding automatically
- ui-routines — routines run after callbacks update state to refresh the display