Apollo PS4 Menu System Architecture: How `menu_option_t` and Callbacks Power the UI

Apollo PS4 implements a data-driven UI architecture where the menu_option_t structure defines menu items and callback functions handle state changes, enabling developers to extend menus without modifying rendering code.

Apollo PS4 is an open-source save game manager for the PlayStation 4 that utilizes a lightweight, data-driven menu system to handle all on-screen interfaces. The architectural design centers on the menu_option_t structure defined in include/settings.h and a system of callback functions that bridge user input to application state. This separation of concerns allows new settings or features to be added by simply extending a static array and implementing a small callback, without touching the rendering pipeline.

Core Data Structure – menu_option_t

The foundation of Apollo's menu architecture is the menu_option_t structure, which encapsulates everything needed to display and manage a single menu item.

Structure Definition

Located in include/settings.h, the structure ties together display text, option values, data types, and function pointers:

/* include/settings.h */
typedef struct
{
    const char * name;               // Text shown in the menu
    const char * * options;          // NULL for non-list types, otherwise array of strings
    enum app_option_type type;       // BOOL, LIST, INC, CALL, NONE
    uint8_t spacer;                  // Extra vertical space before this item
    uint8_t * value;                 // Pointer to stored setting (NULL for CALL)
    void(*callback)(int);            // Function executed on value change/activation
} menu_option_t;

Source: [include/settings.h lines 19-27](https://github.com/bucanero/apollo-ps4/blob/main/include/settings.h#L19-L27)

Option Types

The type field determines how the input handler and renderer treat the option:

  • APP_OPTION_BOOL: On/off toggle using texture indices opt_on_png_index and opt_off_png_index
  • APP_OPTION_LIST: Cycles through a predefined string array (options field)
  • APP_OPTION_INC: Numeric increment/decrement control (e.g., "- 5 +")
  • APP_OPTION_CALL: Executes a function without storing a value, used for actions like "Clear Cache"
  • APP_OPTION_NONE: Reserved placeholder type

Defining Menu Options

All visible menu options reside in source/settings.c as the global menu_options[] array. Each entry maps UI elements to their underlying storage in the apollo_config structure and their respective callbacks.

The Global Options Array

/* source/settings.c */
menu_option_t menu_options[] = {
    { .name = _i18n("Background Music"),
      .options = NULL,
      .type = APP_OPTION_BOOL,
      .value = &apollo_config.music,
      .callback = music_callback },

    { .name = _i18n("Menu Animations"),
      .options = NULL,
      .type = APP_OPTION_BOOL,
      .value = &apollo_config.doAni,
      .callback = ani_callback },

    { .name = _i18n("Sort Saves"),
      .options = sort_opt,
      .type = APP_OPTION_LIST,
      .value = &apollo_config.doSort,
      .callback = sort_callback },

    /* Additional options... */

    { .name = NULL }   // Sentinel terminates the array
};

Source: [source/settings.c lines 33-59](https://github.com/bucanero/apollo-ps4/blob/main/source/settings.c#L33-L59)

Extending the Menu

Adding a new option requires only three steps without modifying rendering code:

/* 1. Define value list if using LIST type */
static const char * theme_opt[] = { "Light", "Dark", NULL };

/* 2. Append to menu_options[] */
{ .name = _i18n("UI Theme"),
  .options = theme_opt,
  .type = APP_OPTION_LIST,
  .value = &apollo_config.theme,
  .callback = theme_callback },

/* 3. Implement callback */
void theme_callback(int sel) {
    apollo_config.theme = sel;
    apply_theme(sel);  // Immediate application
}

Input Handling and Callback Dispatch

The input loop in source/menu_main.c drives the menu interaction. The doOptionsMenu() function polls the PS4 controller via orbisPad* helpers and manages selection movement, value adjustment, and callback invocation.

When the user presses LEFT or RIGHT, the handler updates the underlying value and immediately triggers the callback:

/* source/menu_main.c */
else if (orbisPadGetButtonHold(ORBIS_PAD_BUTTON_LEFT)) {
    if (menu_options[menu_sel].type == APP_OPTION_LIST) {
        if (*menu_options[menu_sel].value > 0)
            (*menu_options[menu_sel].value)--;
        else
            *menu_options[menu_sel].value = menu_options_maxsel[menu_sel] - 1;
    } else if (menu_options[menu_sel].type == APP_OPTION_INC) {
        (*menu_options[menu_sel].value)--;
    }
    if (menu_options[menu_sel].type != APP_OPTION_CALL)
        menu_options[menu_sel].callback(*menu_options[menu_sel].value);
}

Source: [source/menu_main.c lines 94-106](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_main.c#L94-L106)

Activation Handling

The CROSS button handles boolean toggles and call-type actions:

/* source/menu_main.c */
else if (orbisPadGetButtonPressed(ORBIS_PAD_BUTTON_CROSS)) {
    if (menu_options[menu_sel].type == APP_OPTION_BOOL)
        menu_options[menu_sel].callback(*menu_options[menu_sel].value);
    else if (menu_options[menu_sel].type == APP_OPTION_CALL)
        menu_options[menu_sel].callback(0);
}

Source: [source/menu_main.c lines 123-130](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_main.c#L123-L130)

Callback Implementation Examples

Callbacks bridge UI changes to application state. The music toggle demonstrates the pattern:

/* source/settings.c */
void music_callback(int sel) {
    apollo_config.music = !sel;
}

Source: [source/settings.c lines 6-9](https://github.com/bucanero/apollo-ps4/blob/main/source/settings.c#L6-L9)

A more complex example handles debug logging initialization:

/* source/settings.c */
void log_callback(int sel) {
    apollo_config.dbglog = !sel;
    if (!apollo_config.dbglog) {
        dbglogger_stop();
        show_message(_("Debug Logging Disabled"));
    } else {
        dbglogger_init_mode(FILE_LOGGER, APOLLO_PATH "apollo.log", 0);
        show_message("%s\n\n%s", _("Debug Logging Enabled!"), APOLLO_PATH "apollo.log");
    }
}

Source: [source/settings.c lines 89-101](https://github.com/bucanero/apollo-ps4/blob/main/source/settings.c#L89-L101)

Rendering Pipeline

The source/menu_options.c file contains the generic rendering routine _draw_OptionsMenu. This function iterates over menu_options[] and draws the appropriate visual elements based on the type field, completely decoupling presentation from data definition.

The Drawing Loop

/* source/menu_options.c */
for (int ind = 0, y_off = 200; menu_options[ind].name; ind++, y_off += APP_LINE_OFFSET) {
    /* Label drawing... */
    
    switch (menu_options[ind].type) {
        case APP_OPTION_BOOL:
            c = (*menu_options[ind].value == 1) ? opt_on_png_index : opt_off_png_index;
            DrawTexture(&menu_textures[c], OPTION_ITEM_OFF - 29, y_off, 0,
                        menu_textures[c].width, menu_textures[c].height,
                        0xFFFFFF00 | alpha);
            break;
        case APP_OPTION_LIST:
            SetFontAlign(FONT_ALIGN_CENTER);
            DrawFormatString(OPTION_ITEM_OFF - 18, y_off,
                "< %s >", menu_options[ind].options[*menu_options[ind].value]);
            SetFontAlign(FONT_ALIGN_LEFT);
            break;
        case APP_OPTION_INC:
            SetFontAlign(FONT_ALIGN_CENTER);
            DrawFormatString(OPTION_ITEM_OFF - 18, y_off,
                "- %d +", *menu_options[ind].value);
            SetFontAlign(FONT_ALIGN_LEFT);
            break;
        /* APP_OPTION_CALL draws generic button icon */
    }
    
    /* Selection highlight */
    if (menu_sel == ind) {
        DrawTexture(&menu_textures[mark_line_png_index], 0, y_off, 0,
                    SCREEN_WIDTH, menu_textures[mark_line_png_index].height * 2,
                    0xFFFFFF00 | alpha);
        DrawTextureCenteredX(&menu_textures[mark_arrow_png_index],
                    MENU_ICON_OFF + MENU_TITLE_OFF, y_off, 0,
                    (2 * APP_LINE_OFFSET) / 3, APP_LINE_OFFSET + 2,
                    0xFFFFFF00 | alpha);
    }
}

Source: [source/menu_options.c lines 16-56](https://github.com/bucanero/apollo-ps4/blob/main/source/menu_options.c#L16-L56)

The rendering code remains completely agnostic of the semantic meaning of each option. It only interrogates the type, value, and options fields, enabling the data-driven architecture.

Key Files in the Menu Subsystem

Understanding the file organization helps navigate the codebase when extending functionality:

File Role Key Symbols
include/settings.h Structure definitions and enums menu_option_t, enum app_option_type, app_config_t
source/settings.c Menu definition array and callbacks menu_options[], music_callback(), log_callback()
source/menu_main.c Input processing and screen flow doOptionsMenu(), move_selection_fwd(), SetMenu()
source/menu_options.c Generic rendering implementation _draw_OptionsMenu(), texture indices
include/menu.h Rendering helper prototypes DrawTexture(), SetFontSize(), DrawFormatString()

Summary

Apollo PS4's menu architecture demonstrates effective separation of concerns through a data-driven design:

  • menu_option_t serves as the universal descriptor for menu items, encapsulating display text, data type, storage pointer, and callback function
  • Callback functions bridge UI interactions to application state, executing immediately when values change or actions trigger
  • Input handling in source/menu_main.c operates generically on the menu_options[] array, requiring no modification when adding new options
  • Rendering in source/menu_options.c draws appropriate widgets based solely on the type field, maintaining visual consistency across all menus

This architecture enables rapid feature addition—developers extend menu_options[] with a new struct entry and implement a callback, while the existing input and rendering infrastructure handles the rest automatically.

Frequently Asked Questions

What is the menu_option_t structure in Apollo PS4?

The menu_option_t structure is the core data structure defined in include/settings.h that describes every interactive element in Apollo's settings menus. It contains fields for the display name, option type (boolean, list, increment, or call), a pointer to the stored value, an optional list of string choices, and a callback function pointer that executes when the value changes.

How do callbacks function in the Apollo PS4 menu system?

Callbacks in Apollo PS4 are function pointers stored in the callback field of menu_option_t that bridge user interface interactions to application logic. When a user changes a setting value using the directional buttons or activates a call-type option with the CROSS button, the input handler in source/menu_main.c immediately invokes the associated callback with the new value as an argument, allowing real-time updates to the apollo_config state.

Where is the menu rendering code located in Apollo PS4?

The generic menu rendering implementation resides in source/menu_options.c, specifically within the _draw_OptionsMenu() function. This file handles drawing all visual elements—including boolean toggle textures, list selection arrows, increment controls, and selection highlights—based solely on the metadata in the menu_options[] array. The rendering code remains completely decoupled from specific menu logic, using only the type, value, and options fields to determine visual presentation.

How do I add a new option to the Apollo PS4 settings menu?

To add a new option, declare any necessary string arrays for list types, append a new menu_option_t entry to the menu_options[] array in source/settings.c with appropriate .name, .type, .value, and .callback fields, then implement the callback function to handle value changes. The existing input handling in source/menu_main.c and rendering in source/menu_options.c will automatically recognize and display the new option without requiring modifications.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →