Skip to content

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 on

For 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 on

The 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.NULL

UI.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 inside cb Functions. The IVLS compiler only processes them in that context.
  • Do not write on ui_control() handlers for parameters that already have ctrl_id set 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).
  • engine-box — the box system handles most widget-to-parameter binding automatically
  • ui-routines — routines run after callbacks update state to refresh the display