Skip to content

UI Callbacks


Engine box parameters handle continuous controls beautifully -- knobs, sliders, value edits. But not every widget maps to a stored parameter. Buttons that switch pages, navigation tabs, toggle controls that trigger one-shot actions -- these need custom behavior that runs when the widget is interacted with, not a persistent value that gets saved and restored.

The UI callback system lets you bind any widget to a named handler function. When the user interacts with the widget, the framework dispatches the event to your handler. No engine box involved, no serialization, just direct custom behavior.

Binding a Widget


UI callbacks are registered by adding names to the STL.UI_CALLBACKS extension point, then binding widgets to them with uicb.Bind():

node MyNavigation:
    cb Init:
        define STL.UI_CALLBACKS += switch_page

        uicb.Bind(Main.PageButton, switch_page)

uicb.Bind() associates a widget with a named callback. When the user interacts with Main.PageButton, the framework will call uicb.switch_page().

You can bind multiple widgets to the same callback:

uicb.Bind(Main.PageButton_A, switch_page)
uicb.Bind(Main.PageButton_B, switch_page)
uicb.Bind(Main.PageButton_C, switch_page)

Each binding gets a unique index (starting from 0 in the order they were bound), which your handler can use to distinguish between them.

Implementing the Handler


Your handler function goes inside cb Functions and takes two arguments:

    cb Functions:
        function uicb.switch_page(ui_id, n)
            select n
                case 0
                    { Page A button was clicked }
                    current_page := 0
                case 1
                    { Page B button was clicked }
                    current_page := 1
                case 2
                    { Page C button was clicked }
                    current_page := 2
            end select

            call uir.update()
        end function

ui_id is the Kontakt UI ID of the widget that triggered the event. n is the binding index -- the position of this widget in the order you called uicb.Bind() for this callback. This lets you use one handler for a group of related buttons.

Why cb Functions?


You might wonder why UI callback handlers live in cb Functions rather than in a dedicated on ui_control block. In IVLS, all on ui_control events are routed through a centralized dispatcher in the cb UICBS callback. The framework handles the event interception and delegation. Your handler functions are called by the dispatcher, so they need to be regular functions declared in cb Functions.

This is also why engine boxes use Data.UICBS() inside cb UICBS -- it's the same dispatching mechanism. The difference is that engine boxes handle the routing automatically, while uicb.Bind() gives you manual control.

Firing Callbacks Programmatically


Sometimes you need to trigger a callback from code rather than from a user interaction. uicb.Fire() does this:

uicb.Fire(0, switch_page, 0)

The arguments are:

  • index -- the binding index passed as n to the handler
  • callback name -- the registered callback to invoke
  • value -- an integer value (accessible via the stub widget if needed)

This is useful for initializing state, simulating user actions during preset loads, or triggering navigation from non-UI code.

When to Use uicb vs. Engine Box Parameters


The distinction is straightforward:

  • Engine box parameters are for values that need to be saved, restored, and displayed -- volume, attack, mode selectors, enable toggles. They persist across preset loads and have built-in UI sync and display formatting.

  • UI callbacks are for actions -- switching pages, opening menus, triggering one-shot operations. They don't store a value; they run code when a widget is clicked.

If you're reaching for a button or a navigation tab, use uicb.Bind(). If you're reaching for a knob, slider, or any control whose value matters after the user closes and reopens the project, use an engine box parameter.

Some controls fall in between. A toggle button that enables/disables a feature should probably be an engine box parameter (because you want that state saved). A button that randomizes all parameters is an action -- use uicb.


With engine boxes, UI routines, and UI callbacks, you have a complete system for managing instrument parameters and user interaction. Parameters are stored and synced automatically, display logic is centralized in routines, and custom widget behavior is dispatched through callbacks.