6. Additional Menu Items (via Janet)

In order to promote additional functionality, such as loading/saving state data, a customizable menu will be created. This functionality will allow additional items to appear in their own submenu, which can be selected from the main menu.

This aux menu is populated with a Janet array. This array is stored as a reference in the C struct, and must be set via a janet function monolith/norns-menu-aux-set.

<<norns_aux_menu_data>>=
JanetArray *aux_items;
<<norns_aux_menu_init>>=
menu->aux_items = NULL;


<<norns_janet_entries>>=
{
"monolith/norns-menu-aux-set",
j_norns_menu_aux_set,
"Sets the array."
},
<<norns_janet>>=
static Janet j_norns_menu_aux_set(int32_t argc, Janet *argv)
{
    const char *str;
    monolith_d *m;
    monolith_dict *dict;
    monolith_page *pg;
    int rc;
    JanetArray *a;
    norns_main_menu *menu;

    janet_fixarity(argc, 2);
    m = monolith_data_get();
    dict = monolith_dict_get(m);

    str = (const char *)janet_unwrap_string(argv[0]);
    a = janet_unwrap_array(argv[1]);

    rc = monolith_dict_lookup(dict, &pg, str, strlen(str));

    if (!rc) {
        fprintf(stderr, "could not find page '%s'\n", str);
        return janet_wrap_nil();
    }

    menu = monolith_page_data_get(pg);

    norns_menu_aux_set(menu, a);

    return janet_wrap_nil();
}
<<norns_funcdefs>>=
static void norns_menu_aux_set(norns_main_menu *m,
                               JanetArray *a);

TODO: implement.

<<norns_functions>>=
static void norns_menu_aux_set(norns_main_menu *m,
                               JanetArray *a)
{
    m->aux_items = a;
}

There is a risk that the garbage collector in Janet could free this prematurely, but the flexibility of using a Janet array makes it worth trying.

The Janet array is expected to be an array of tuples. Each tuple contains the menu item name as a string, as well as a zero-parameter function that gets called when it is selected.

Because this menu is dynamically populated using Janet structures, drawing and selection behavior happen differently. When the aux menu is selected, a special flag is turned on to channel this new behavior. Returning to the main menu causes this flag to be switched off.

<<aux_menu_return>>=
mm->aux_menu = 0;
<<norns_aux_menu_data>>=
int aux_menu;
<<norns_aux_menu_init>>=
menu->aux_menu = 0;

Selection. Even though the aux menu plays by its own rules, it is still fundamentally using the norns_menu interface. When selected, the aux menu page will call on norns_menu_reinit similar to how the main and pages menu work. It will also flip on the aux_menu flag.

<<norns_funcdefs>>=
void norns_aux_menu_select(norns_main_menu *menu);

It is only when the aux menu is selected that the number of items is populated from the array. This keeps things simple and fast.

<<norns_functions>>=
<<aux_menu_callbacks>>
void norns_aux_menu_select(norns_main_menu *menu)
{
    norns_menu_reinit(&menu->menu,
                      "Aux",
                      NULL,
                      1,
                      menu);
    menu->aux_menu = 1;
<<set_aux_menu_callbacks>>

    if (menu->aux_items != NULL) {
        menu->menu.nitems += menu->aux_items->count;
    }
}

When norns_menu_reinit is called, it sets a callback for for what to do when a knob is called. This particular callback needs to be replaced because this is where the redrawing occurs.

<<set_aux_menu_callbacks>>=
{
    norns_poll_d *poll;
    poll = monolith_norns_poll(menu->m);
    norns_poll_cb_knob(poll, 1, aux_menu_knob, menu);
    norns_poll_cb_key(poll, 1, aux_menu_key, menu);
}
<<aux_menu_callbacks>>=
void monolith_norns_draw(monolith_d *m);
static void aux_menu_knob(void *ud, int pos)
{
    norns_main_menu *mm;
    norns_menu *menu;

    mm = ud;
    menu = &mm->menu;

    norns_menu_step(menu, pos);
    norns_menu_janet_draw(menu, mm->aux_items);

    monolith_norns_draw(monolith_data_get());
}

The button also needs to be changed to due to the behavior required.

<<norns_static_funcdefs>>=
static void aux_menu_key(void *ud, int state);
<<norns_functions>>=
static void aux_menu_key(void *ud, int state)
{
    norns_main_menu *mm;
    norns_menu *menu;
    int selected;

    if (state == 0) return;

    mm = ud;
    menu = &mm->menu;

    selected = menu->selected;

    if (selected == 1) {
        /* void (*f)(norns_menu *, int); */
        /* selected--; */
        /* f = menu->items[selected].select; */
        /* if (f != NULL) { */
        /*     f(menu, selected); */
        /* } */
        return_to_main(menu, selected);
    } else if (selected > 1 && mm->aux_items != NULL) {
        JanetArray *tuple;
        JanetFunction *fun;
        JanetArray *a;
        a = mm->aux_items;
        tuple = janet_unwrap_array(a->data[selected - 2]);
        fun = janet_unwrap_function(tuple->data[1]);
        janet_call(fun, 0, NULL);
    }
}

Drawing. Menu items are read from a Janet array instead of the norns_menu_item struct. Item names are extracted from the array as a C string, and then drawn onto the screen. The first menu item is hardcoded to go back to the main menu screen.

Regrettably, it would seem that the current drawing function does lend itself well for introducing a "Janet mode" for aux functions. Attempting to do so now would involving a lot of duplicate code and headache.

Currently, a menu is drawn out using a self-contained function called norns_menu_draw. To draw things the new way, function called norns_menu_janet_draw will be used. As arguments, it will take in the Janet Array, in addition to the norns menu type.

The norns_menu_janet_draw function will be built up of similar components as norns_menu_draw for things like the header, but replacing item drawing with the janet array instead of the internal array.

<<norns_funcdefs>>=
void norns_menu_janet_draw(norns_menu *menu, JanetArray *a);

For now, just call norns_menu_draw as a placeholder.

<<norns_functions>>=
void norns_menu_janet_draw(norns_menu *menu, JanetArray *a)
{
    norns_videobuf_clear(menu->buf);
    norns_menu_header(menu);
<<draw_aux_menu_items>>
}

Selecting. Selecting an item is a matter of finding correct item in the array, extracting the function, and calling that function. If the selection is the first menu item, it will return to the main menu screen.

<<draw_aux_menu_items>>=
{
    int i;
    int item;
    int x, y;
    int selected;
    unsigned char bg;
    unsigned char fg;
    int nrows;

    selected = menu->selected;

    nrows = menu->nitems - menu->offset;

    if (nrows > 6) nrows = 6;
    if (nrows < 0) nrows = 0;

    for (i = 0; i < nrows; i++) {
        item = menu->offset + i;
        if ((item + 1) == selected) {
            fg = 0x00;
            bg = 0xff;
            for (y = 0; y < 10; y++) {
                for (x = 0; x < 128; x++) {
                    norns_videobuf_write(menu->buf,
                                         x, (14 + 9*i) + y,
                                         0xff);
                }
            }
        } else {
            fg = 0xff;
            bg = 0x00;
        }

        if (item == 0) {
            norns_draw_string(menu->buf,
                            0, 15 + 9*0,
                            fg, bg,
                            "_ Main Menu");
        } else {
            JanetArray *tuple;
            const char *str;
            tuple = janet_unwrap_array(a->data[item - 1]);
            str = (const char *)janet_unwrap_string(tuple->data[0]);
            norns_draw_string(menu->buf,
                            0, 15 + 9*i,
                            fg, bg,
                            str);
        }
    }
}



prev | home | next