static int _mouse_move(GRAPHICS_CONTROLLER *gc, unsigned x, unsigned y, GC_NAV_CMDS *cmds)
{
    PG_DISPLAY_SET *s          = gc->igs;
    BD_IG_PAGE     *page       = NULL;
    unsigned        page_id    = bd_psr_read(gc->regs, PSR_MENU_PAGE_ID);
    unsigned        cur_btn_id = bd_psr_read(gc->regs, PSR_SELECTED_BUTTON_ID);
    unsigned        new_btn_id = 0xffff;
    unsigned        ii;

    gc->valid_mouse_position = 0;

    page = _find_page(&s->ics->interactive_composition, page_id);
    if (!page) {
        ERROR("_mouse_move(): unknown page #%d (have %d pages)\n",
              page_id, s->ics->interactive_composition.num_pages);
        return -1;
    }

    for (ii = 0; ii < page->num_bogs; ii++) {
        BD_IG_BOG    *bog      = &page->bog[ii];
        unsigned      valid_id = gc->enabled_button[ii];
        BD_IG_BUTTON *button   = _find_button_bog(bog, valid_id);

        if (!button)
            continue;

        if (x < button->x_pos || y < button->y_pos)
            continue;

        /* Check for SELECTED state object (button that can be selected) */
        BD_PG_OBJECT *object = _find_object_for_button(s, button, BTN_SELECTED);
        if (!object)
            continue;

        if (x >= button->x_pos + object->width || y >= button->y_pos + object->height)
            continue;

        /* mouse is over button */

        /* is button already selected? */
        if (button->id == cur_btn_id) {
            gc->valid_mouse_position = 1;
            return 0;
        }

        new_btn_id = button->id;
        break;
    }

    if (new_btn_id != 0xffff) {
        bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, new_btn_id);

        _render_page(gc, -1, cmds);

        gc->valid_mouse_position = 1;
    }

     return gc->valid_mouse_position;
}
static void _set_button_page(GRAPHICS_CONTROLLER *gc, uint32_t param)
{
    unsigned page_flag   = param & 0x80000000;
    unsigned effect_flag = param & 0x40000000;
    unsigned button_flag = param & 0x20000000;
    unsigned page_id     = (param >> 16) & 0xff;
    unsigned button_id   = param & 0xffff;
    unsigned bog_idx     = 0;

    PG_DISPLAY_SET *s      = gc->igs;
    BD_IG_PAGE     *page   = NULL;
    BD_IG_BUTTON   *button = NULL;

    GC_TRACE("_set_button_page(0x%08x): page flag %d, id %d, effects %d   button flag %d, id %d\n",
          param, !!page_flag, page_id, !!effect_flag, !!button_flag, button_id);

    /* 10.4.3.4 (D) */

    if (!page_flag && !button_flag) {
        return;
    }

    if (page_flag) {

        /* current page --> command is ignored */
        if (page_id == bd_psr_read(gc->regs, PSR_MENU_PAGE_ID)) {
            GC_TRACE("  page is current\n");
            return;
        }

        page = _find_page(&s->ics->interactive_composition, page_id);

        /* invalid page --> command is ignored */
        if (!page) {
            GC_TRACE("  page is invalid\n");
            return;
        }

        /* page changes */

        _select_page(gc, page_id);

    } else {
        /* page does not change */
        page_id = bd_psr_read(gc->regs, PSR_MENU_PAGE_ID);
        page    = _find_page(&s->ics->interactive_composition, page_id);

        if (!page) {
            GC_ERROR("_set_button_page(): PSR_MENU_PAGE_ID refers to unknown page %d\n", page_id);
            return;
        }
    }

    if (button_flag) {
        /* find correct button and overlap group */
        button = _find_button_page(page, button_id, &bog_idx);

        if (!page_flag) {
            if (!button) {
                /* page not given, invalid button --> ignore command */
                GC_TRACE("  button is invalid\n");
                return;
            }
            if (button_id == bd_psr_read(gc->regs, PSR_SELECTED_BUTTON_ID)) {
                /* page not given, current button --> ignore command */
                GC_TRACE("  button is current\n");
                return;
            }
        }
    }

    if (button) {
        gc->bog_data[bog_idx].enabled_button = button_id;
        _select_button(gc, button_id);
    }

    _render_page(gc, 0xffff, NULL); /* auto action not triggered yet */
}
static int _user_input(GRAPHICS_CONTROLLER *gc, uint32_t key, GC_NAV_CMDS *cmds)
{
    PG_DISPLAY_SET *s          = gc->igs;
    BD_IG_PAGE     *page       = NULL;
    unsigned        page_id    = bd_psr_read(gc->regs, PSR_MENU_PAGE_ID);
    unsigned        cur_btn_id = bd_psr_read(gc->regs, PSR_SELECTED_BUTTON_ID);
    unsigned        new_btn_id = cur_btn_id;
    unsigned        ii;
    int             activated_btn_id = -1;

    if (s->ics->interactive_composition.ui_model == IG_UI_MODEL_POPUP && !gc->popup_visible) {
        GC_TRACE("_user_input(): popup menu not visible\n");
        return -1;
    }
    if (!gc->ig_drawn) {
        GC_ERROR("_user_input(): menu not visible\n");
        return -1;
    }

    GC_TRACE("_user_input(%d)\n", key);

    page = _find_page(&s->ics->interactive_composition, page_id);
    if (!page) {
        GC_ERROR("_user_input(): unknown page id %d (have %d pages)\n",
              page_id, s->ics->interactive_composition.num_pages);
        return -1;
    }

    if (key == BD_VK_MOUSE_ACTIVATE) {
        if (!gc->valid_mouse_position) {
            GC_TRACE("_user_input(): BD_VK_MOUSE_ACTIVATE outside of valid buttons\n");
            return -1;
        }
        key = BD_VK_ENTER;
    }

    for (ii = 0; ii < page->num_bogs; ii++) {
        BD_IG_BOG *bog      = &page->bog[ii];
        unsigned   valid_id = gc->bog_data[ii].enabled_button;
        BD_IG_BUTTON *button = _find_button_bog(bog, valid_id);
        if (!button) {
            continue;
        }

        /* numeric select */
        if (VK_IS_NUMERIC(key)) {
            if (button->numeric_select_value == VK_TO_NUMBER(key)) {
                new_btn_id = button->id;
            }
        }

        /* cursor keys */
        else if (VK_IS_CURSOR(key) || key == BD_VK_ENTER) {
            if (button->id == cur_btn_id) {
                switch(key) {
                    case BD_VK_UP:
                        new_btn_id = button->upper_button_id_ref;
                        break;
                    case BD_VK_DOWN:
                        new_btn_id = button->lower_button_id_ref;
                        break;
                    case BD_VK_LEFT:
                        new_btn_id = button->left_button_id_ref;
                        break;
                    case BD_VK_RIGHT:
                        new_btn_id = button->right_button_id_ref;
                        break;
                    case BD_VK_ENTER:
                        activated_btn_id = cur_btn_id;

                        cmds->num_nav_cmds = button->num_nav_cmds;
                        cmds->nav_cmds     = button->nav_cmds;
                        cmds->sound_id_ref = button->activated_sound_id_ref;
                        break;
                    default:;
                }
            }

            if (new_btn_id != cur_btn_id) {
                BD_IG_BUTTON *new_button = _find_button_page(page, new_btn_id, NULL);
                if (new_button) {
                    cmds->sound_id_ref = new_button->selected_sound_id_ref;
                }
            }
        }
    }

    /* render page ? */
    if (new_btn_id != cur_btn_id || activated_btn_id >= 0) {

        _select_button(gc, new_btn_id);

        _render_page(gc, activated_btn_id, cmds);

        /* found one*/
        return 1;
    }

    return 0;
}
int gc_run(GRAPHICS_CONTROLLER *gc, gc_ctrl_e ctrl, uint32_t param, GC_NAV_CMDS *cmds)
{
    int result = -1;

    if (cmds) {
        cmds->num_nav_cmds = 0;
        cmds->nav_cmds     = NULL;
        cmds->sound_id_ref = -1;
        cmds->status       = GC_STATUS_NONE;
    }

    if (!gc) {
        GC_TRACE("gc_run(): no graphics controller\n");
        return result;
    }

    bd_mutex_lock(&gc->mutex);

    /* always accept reset */
    switch (ctrl) {
        case GC_CTRL_RESET:
            _gc_reset(gc);

            bd_mutex_unlock(&gc->mutex);
            return 0;

        default:;
    }

    /* other operations require complete display set */
    if (!gc->igs || !gc->igs->ics || !gc->igs->complete) {
        GC_TRACE("gc_run(): no interactive composition\n");
        bd_mutex_unlock(&gc->mutex);
        return result;
    }

    switch (ctrl) {

        case GC_CTRL_SET_BUTTON_PAGE:
            _set_button_page(gc, param);
            break;

        case GC_CTRL_VK_KEY:
            if (param != BD_VK_POPUP) {
                result = _user_input(gc, param, cmds);
                break;
            }
            param = !gc->popup_visible;
            /* fall thru (BD_VK_POPUP) */

        case GC_CTRL_POPUP:
            if (gc->igs->ics->interactive_composition.ui_model != IG_UI_MODEL_POPUP) {
                /* not pop-up menu */
                break;
            }

            gc->popup_visible = !!param;

            if (gc->popup_visible) {
                _select_page(gc, 0);
            }

            /* fall thru */

        case GC_CTRL_NOP:
            result = _render_page(gc, 0xffff, cmds);
            break;

        case GC_CTRL_INIT_MENU:
            _select_page(gc, 0);
            _render_page(gc, 0xffff, cmds);
            break;

        case GC_CTRL_IG_END:
            _update_selected_button(gc);
            _render_page(gc, 0xffff, cmds);
            break;

        case GC_CTRL_ENABLE_BUTTON:
            _enable_button(gc, param, 1);
            break;

        case GC_CTRL_DISABLE_BUTTON:
            _enable_button(gc, param, 0);
            break;

        case GC_CTRL_MOUSE_MOVE:
            result = _mouse_move(gc, param >> 16, param & 0xffff, cmds);
            break;

        case GC_CTRL_RESET:
            /* already handled */
            break;
    }

    if (cmds) {
        if (gc->igs->ics->interactive_composition.ui_model == IG_UI_MODEL_POPUP) {
            cmds->status |= GC_STATUS_POPUP;
        }
        if (gc->ig_drawn) {
            cmds->status |= GC_STATUS_MENU_OPEN;
        }
    }

    bd_mutex_unlock(&gc->mutex);

    return result;
}
static void _set_button_page(GRAPHICS_CONTROLLER *gc, uint32_t param, GC_NAV_CMDS *cmds)
{
    unsigned page_flag   = param & 0x80000000;
    unsigned effect_flag = param & 0x40000000;
    unsigned button_flag = param & 0x20000000;
    unsigned page_id     = (param >> 16) & 0xff;
    unsigned button_id   = param & 0xffff;
    unsigned bog_idx     = 0;

    PG_DISPLAY_SET *s      = gc->igs;
    BD_IG_PAGE     *page   = NULL;
    BD_IG_BUTTON   *button = NULL;

    TRACE("_set_button_page(0x%08x): page flag %d, id %d, effects %d   button flag %d, id %d",
          param, !!page_flag, page_id, !!effect_flag, !!button_flag, button_id);

    /* 10.4.3.4 (D) */

    if (!page_flag && !button_flag) {
        return;
    }

    if (page_flag) {

        /* current page --> command is ignored */
        if (page_id == bd_psr_read(gc->regs, PSR_MENU_PAGE_ID)) {
            TRACE("  page is current\n");
            return;
        }

        page = _find_page(&s->ics->interactive_composition, page_id);

        /* invalid page --> command is ignored */
        if (!page) {
            TRACE("  page is invalid\n");
            return;
        }

        /* page changes */

        bd_psr_write(gc->regs, PSR_MENU_PAGE_ID, page_id);

        _reset_enabled_button(gc);
        _gc_clear_osd(gc, 1);


    } else {
        /* page does not change */
        page_id = bd_psr_read(gc->regs, PSR_MENU_PAGE_ID);
        page    = _find_page(&s->ics->interactive_composition, page_id);

        if (!page) {
            ERROR("_set_button_page(): PSR_MENU_PAGE_ID refers to unknown page %d\n", page_id);
            return;
        }
    }

    if (button_flag) {
        /* find correct button and overlap group */
        button = _find_button_page(page, button_id, &bog_idx);

        if (!page_flag) {
            if (!button) {
                /* page not given, invalid button --> ignore command */
                TRACE("  button is invalid\n");
                return;
            }
            if (button_id == bd_psr_read(gc->regs, PSR_SELECTED_BUTTON_ID)) {
                /* page not given, current button --> ignore command */
                TRACE("  button is current\n");
                return;
            }
        }
    }

    if (!button) {
        button_id = 0xffff; // run 5.9.7.4 and 5.9.8.3
    } else {
        gc->enabled_button[bog_idx] = button_id;
    }

    bd_psr_write(gc->regs, PSR_SELECTED_BUTTON_ID, button_id);

    gc->ig_drawn = 0;

    _render_page(gc, 0xffff, cmds);
}