User:Compyx: Difference between revisions

From vice-emu
Jump to navigation Jump to search
(Start collection observations on the SDL UI code on my personal Wiki paga)
Line 57: Line 57:
} ui_menu_entry_type_t;
} ui_menu_entry_type_t;
</syntaxhighlight>
</syntaxhighlight>
=== The callback function ===
Callbacks are prototyped in <tt>uimenu.h</tt>:
<syntaxhighlight lang="C">typedef const char *(*ui_callback_t)(int activated, ui_callback_data_t param);</syntaxhighlight>
These callbacks are triggered both by displaying an item (<tt>activated == 0</tt>) and by activating an item (<tt>activated == 1</tt>). The return value is used to either display something in front of the item, like <tt>sdl_menu_text_tick</tt> (a tick mark), nothing (<tt>NULL</tt>) or the special <tt>sdl_menu_text_exit_ui</tt> ("\1") which means the activation succeeded and the UI must be exited (for example when triggering soft/hard reset). The <i><tt>param</tt></i> argument is the <tt>data</tt> field of the menu item definition.
Some helper macros and functions exit for the callbacks in <tt>menu_common.{c,h}</tt>.
<syntaxhighlight lang="C">
#define UI_MENU_CALLBACK(name) \
    const char *name(int activated, ui_callback_data_t param)
#define UI_MENU_DEFINE_TOGGLE(resource)                              \
    static UI_MENU_CALLBACK(toggle_##resource##_callback)            \
    {                                                                \
        return sdl_ui_menu_toggle_helper(activated, #resource);      \
    }
#define UI_MENU_DEFINE_RADIO(resource)                                \
    static UI_MENU_CALLBACK(radio_##resource##_callback)              \
    {                                                                \
        return sdl_ui_menu_radio_helper(activated, param, #resource); \
    }
#define UI_MENU_DEFINE_STRING(resource)                                \
    static UI_MENU_CALLBACK(string_##resource##_callback)              \
    {                                                                  \
        return sdl_ui_menu_string_helper(activated, param, #resource); \
    }
#define UI_MENU_DEFINE_INT(resource)                                \
    static UI_MENU_CALLBACK(int_##resource##_callback)              \
    {                                                              \
        return sdl_ui_menu_int_helper(activated, param, #resource); \
    }
#define UI_MENU_DEFINE_FILE_STRING(resource)                                \
    static UI_MENU_CALLBACK(file_string_##resource##_callback)              \
    {                                                                      \
        return sdl_ui_menu_file_string_helper(activated, param, #resource); \
    }
#define UI_MENU_DEFINE_SLIDER(resource, min, max)                                  \
    static UI_MENU_CALLBACK(slider_##resource##_callback)                          \
    {                                                                              \
        return sdl_ui_menu_slider_helper(activated, param, #resource, min, max);  \
    }
</syntaxhighlight>
These are used with a resource name as their first argument and expand to the beginning of a function definition of a callback. For example:
<syntaxhighlight lang="C">
UI_MENU_DEFINE_RADIO(VICIIBorderMode)
static const ui_menu_entry_t vicii_border_menu[] = {
    { "Normal",
      MENU_ENTRY_RESOURCE_RADIO,
      radio_VICIIBorderMode_callback,
      (ui_callback_data_t)VICII_NORMAL_BORDERS },
    { "Full",
      MENU_ENTRY_RESOURCE_RADIO,
      radio_VICIIBorderMode_callback,
      (ui_callback_data_t)VICII_FULL_BORDERS },
    /* Debug and None snipped */
    SDL_MENU_LIST_END
};
</syntaxhighlight>
The macro at the top expands to:
<syntaxhighlight lang="C">
static const char *radio_VICIIBorderMode_callback(int activated, ui_callback_data_t param)
{
    return sdl_ui_menu_radio_helper(activated, param, "VICIIBorderMode"); 
}
</syntaxhighlight>
The helper function is defined (in <tt>menu_common.c</tt>as:
<syntaxhighlight lang="C">
const char *sdl_ui_menu_radio_helper(int activated, ui_callback_data_t param, const char *resource_name)
{
    if (activated) {
        if (resources_query_type(resource_name) == RES_INTEGER) {
            resources_set_int(resource_name, vice_ptr_to_int(param));
        } else {
            resources_set_string(resource_name, (char *)param);
        }
    } else {
        int v;
        const char *w;
        if (resources_query_type(resource_name) == RES_INTEGER) {
            if (resources_get_int(resource_name, &v) == 0) {
                if (v == vice_ptr_to_int(param)) {
                    return sdl_menu_text_tick;
                }
            }
        } else {
            if (resources_get_string(resource_name, &w) == 0) {
                if (!strcmp(w, (char *)param)) {
                    return sdl_menu_text_tick;
                }
            }
        }
    }
    return NULL;
}
</syntaxhighlight>
So if activated the resource is set to <tt>param</tt> and <tt>NULL</tt> is returned. If not activated (when rendering the item's text), <tt>sdl_menu_text_tick</tt> is returned if the <tt>param</tt> matches the current resource value, otherwise <tt>NULL</tt> is returned.
In the above example when clicking on "Full" the integer resource "VICIIBorderMode" is set to <tt>VICII_FULL_BORDERS</tt> (0). When rendering the menu the callback for "Full" will return <tt>sdl_menu_text_tick</tt> and a tick mark (green filled circle) is rendered in front of the item and the other menu items' callbacks return <tt>NULL</tt> since their <tt>param</tt> doesn't match the value of "VICIIBorderMode" which the UI code appears to then render as a red circle.

Revision as of 23:48, 16 June 2023

Personal notes, observations and rants

SDL UI code

Since I'm tasked with updating the SDL code to use the generic UI actions and hotkeys code and use those UI actions from the menu items, hotkeys and custom joystick mappings, and the code is poorly documented, I'll be writing down some observations on how the SDL UI code works here.

Menu items

Menu items are defined with the type ui_menu_entry_t (in uimenu.h):

typedef struct ui_menu_entry_s {
    char *string;
    ui_menu_entry_type_t type;
    ui_callback_t callback;
    ui_callback_data_t data;
    ui_menu_status_type_t status;
} ui_menu_entry_t;

The string is used to display the item text in the menus. It is used by sdl_ui_display_item() to render a menu item. The type determines what to do when the user activates the item, types include radio buttons, toggle buttons, submenus, and dialogs. When an item is activated the callback is called with data as its argument. The return value of the callback (a const char*) is used to update the item's state or to exit the UI (more on that later). The status field is used for radio buttons to display the selection's state (or something like that).

The various menu item types are defined as:

typedef enum {
    /* Text item (no operation): if data == 1 text colors are inverted */
    MENU_ENTRY_TEXT = 0,

    /* Resource toggle: no UI needed, callback is used */
    MENU_ENTRY_RESOURCE_TOGGLE,

    /* Resource radio: no UI needed, callback is used, data is the resource value */
    MENU_ENTRY_RESOURCE_RADIO,

    /* Resource int: needs UI, callback is used */
    MENU_ENTRY_RESOURCE_INT,

    /* Resource string: needs UI, callback is used */
    MENU_ENTRY_RESOURCE_STRING,

    /* Submenu: needs UI, data points to the submenu */
    MENU_ENTRY_SUBMENU,

    /* Dynamic submenu: needs UI, data points to the submenu, hotkeys disabled */
    MENU_ENTRY_DYNAMIC_SUBMENU,

    /* Custom dialog: needs UI */
    MENU_ENTRY_DIALOG,

    /* Other: no UI needed */
    MENU_ENTRY_OTHER,

    /* Other: no UI needed */
    MENU_ENTRY_OTHER_TOGGLE
} ui_menu_entry_type_t;

The callback function

Callbacks are prototyped in uimenu.h:

typedef const char *(*ui_callback_t)(int activated, ui_callback_data_t param);

These callbacks are triggered both by displaying an item (activated == 0) and by activating an item (activated == 1). The return value is used to either display something in front of the item, like sdl_menu_text_tick (a tick mark), nothing (NULL) or the special sdl_menu_text_exit_ui ("\1") which means the activation succeeded and the UI must be exited (for example when triggering soft/hard reset). The param argument is the data field of the menu item definition.

Some helper macros and functions exit for the callbacks in menu_common.{c,h}.

#define UI_MENU_CALLBACK(name) \
    const char *name(int activated, ui_callback_data_t param)

#define UI_MENU_DEFINE_TOGGLE(resource)                              \
    static UI_MENU_CALLBACK(toggle_##resource##_callback)            \
    {                                                                \
        return sdl_ui_menu_toggle_helper(activated, #resource);      \
    }

#define UI_MENU_DEFINE_RADIO(resource)                                \
    static UI_MENU_CALLBACK(radio_##resource##_callback)              \
    {                                                                 \
        return sdl_ui_menu_radio_helper(activated, param, #resource); \
    }

#define UI_MENU_DEFINE_STRING(resource)                                \
    static UI_MENU_CALLBACK(string_##resource##_callback)              \
    {                                                                  \
        return sdl_ui_menu_string_helper(activated, param, #resource); \
    }

#define UI_MENU_DEFINE_INT(resource)                                \
    static UI_MENU_CALLBACK(int_##resource##_callback)              \
    {                                                               \
        return sdl_ui_menu_int_helper(activated, param, #resource); \
    }

#define UI_MENU_DEFINE_FILE_STRING(resource)                                \
    static UI_MENU_CALLBACK(file_string_##resource##_callback)              \
    {                                                                       \
        return sdl_ui_menu_file_string_helper(activated, param, #resource); \
    }

#define UI_MENU_DEFINE_SLIDER(resource, min, max)                                  \
    static UI_MENU_CALLBACK(slider_##resource##_callback)                          \
    {                                                                              \
        return sdl_ui_menu_slider_helper(activated, param, #resource, min, max);   \
    }

These are used with a resource name as their first argument and expand to the beginning of a function definition of a callback. For example:

UI_MENU_DEFINE_RADIO(VICIIBorderMode)

static const ui_menu_entry_t vicii_border_menu[] = {
    { "Normal",
      MENU_ENTRY_RESOURCE_RADIO,
      radio_VICIIBorderMode_callback,
      (ui_callback_data_t)VICII_NORMAL_BORDERS },
    { "Full",
      MENU_ENTRY_RESOURCE_RADIO,
      radio_VICIIBorderMode_callback,
      (ui_callback_data_t)VICII_FULL_BORDERS },
    /* Debug and None snipped */
    SDL_MENU_LIST_END
};

The macro at the top expands to:

static const char *radio_VICIIBorderMode_callback(int activated, ui_callback_data_t param)
{
    return sdl_ui_menu_radio_helper(activated, param, "VICIIBorderMode");  
}

The helper function is defined (in menu_common.cas:

const char *sdl_ui_menu_radio_helper(int activated, ui_callback_data_t param, const char *resource_name)
{
    if (activated) {
        if (resources_query_type(resource_name) == RES_INTEGER) {
            resources_set_int(resource_name, vice_ptr_to_int(param));
        } else {
            resources_set_string(resource_name, (char *)param);
        }
    } else {
        int v;
        const char *w;
        if (resources_query_type(resource_name) == RES_INTEGER) {
            if (resources_get_int(resource_name, &v) == 0) {
                if (v == vice_ptr_to_int(param)) {
                    return sdl_menu_text_tick;
                }
            }
        } else {
            if (resources_get_string(resource_name, &w) == 0) {
                if (!strcmp(w, (char *)param)) {
                    return sdl_menu_text_tick;
                }
            }
        }
    }
    return NULL;
}

So if activated the resource is set to param and NULL is returned. If not activated (when rendering the item's text), sdl_menu_text_tick is returned if the param matches the current resource value, otherwise NULL is returned.

In the above example when clicking on "Full" the integer resource "VICIIBorderMode" is set to VICII_FULL_BORDERS (0). When rendering the menu the callback for "Full" will return sdl_menu_text_tick and a tick mark (green filled circle) is rendered in front of the item and the other menu items' callbacks return NULL since their param doesn't match the value of "VICIIBorderMode" which the UI code appears to then render as a red circle.