Menu

Synopsis #

This is a routine that displays a menu system similar to Geometry plugin’s one.

Definition #

Inputs #

int menu(struct Tmenu* mymenu, unsigned char selected_page, unsigned char selected_tab)

  • struct Tmenu* mymenu - the structure, which contains the menu system (pages, tabs and items)
  • unsigned char selected_page - the function draws this page
  • unsigned char selected_tab - the function draws this tab of the selected page

Output #

The function returns the ID of the selected menu item as integer, or -1 if [EXIT] was pressed

Data structure #

struct menu_item{
    int id;
    char *text;
};

struct menu_tab{
    char *label;
    unsigned char item_count;
    struct menu_item items[];
};

struct menu_page{
    char *label;
    int key;
    unsigned char tab_count;
    struct menu_tab *tabs[];
};

struct Tmenu{
    unsigned char page_count;
    struct menu_page *pages[];
};

Configuration #

The following constants can manipulate the style and the sizes of the menu. The meanings of these constants are visible on s1.png in the downloadable package

#define MENU_X 6
#define MENU_Y 24
#define MENU_HEIGHT 191
#define MENU_WIDTH 326
#define MENU_HEADER_HEIGHT 27

#define MENU_TAB_INDENT 30
#define MENU_TAB_WIDTH 140
#define MENU_TAB_SPACE 4

#define MENU_COLOR COLOR_LIME

For example you can display more than 2 tabs on the same page, if you set a low value to MENU_TAB_WIDTH or a longer than 2 character long text in the upper left corner by changing MENU_TAB_INDENT. If its needed, you can transform this constants to function arguments with the same name. If a label is longer than it’s space then the rest of the string is not displayed.

Source #

Full source code with example and documentation is downloadable from cemetech achieves.

//Created by Balping
//http://www.cemetech.net

#include <fxcg/display.h>

#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))

void CopySpriteMasked2bitR(const unsigned char* data, int x, int y, int width, int height, short unsigned int* palette, unsigned char mask){
    short unsigned int* VRAM = (short unsigned int*) GetVRAMAddress();
    VRAM += (LCD_WIDTH_PX*y + x);
    int offset = 0;
    unsigned char buf;
    unsigned char this;
    int availbits = 0;
    for(int j=y; j<y+height; j++) {
        for(int i=x; i<x+width;  i++) {
            if (!availbits) {
                buf = data[offset++];
                availbits = 8;
            }
            this = ((buf&0xC0)>>6);
            if (this != mask){
                *VRAM = palette[(int)this];
            }
            VRAM++;
            buf<<=2;
            availbits-=2;
        }
        VRAM += (LCD_WIDTH_PX-width);
    }
}

int menu(struct Tmenu *mymenu, unsigned char selected_page, unsigned char selected_tab){
    const color_t tab_palette[4] = {MENU_COLOR, COLOR_BLACK, COLOR_WHITE, MENU_COLOR};
    const unsigned char left_corner[18] = {1, 90, 129, 107, 241, 111, 252, 111, 255, 91, 255, 219, 255, 214, 255, 213, 191, 213};
    const unsigned char right_corner[18] = {169, 84, 63, 165, 79, 254, 83, 255, 229, 255, 249, 95, 255, 85, 255, 213, 111, 245};
    unsigned char go=1, page, tab, i, starti, selected_item, mehet, item_count;
    unsigned short tabplus=0;
    int x, y, lwidth, key, retval=0;
    unsigned char cheight = (MENU_HEIGHT-MENU_HEADER_HEIGHT-18)/24;
    unsigned char cwidth = (MENU_WIDTH-16)/18;
    char buff[cwidth+1];
    buff[1] = ':'; buff[cwidth] = 0;

    SaveVRAM_1();

    do{
        fillArea(MENU_X, MENU_Y, 1, MENU_HEIGHT, COLOR_BLACK);
        fillArea(MENU_X+1, MENU_Y+MENU_HEADER_HEIGHT+1, 1, MENU_HEIGHT-MENU_HEADER_HEIGHT-3, COLOR_WHITE);
        fillArea(MENU_X+1, MENU_Y, MENU_TAB_INDENT+4, 1, COLOR_BLACK);
        fillArea(MENU_X, MENU_Y+MENU_HEIGHT-2, MENU_WIDTH, 2, COLOR_BLACK);
        fillArea(MENU_X+MENU_WIDTH-2, MENU_Y+MENU_HEADER_HEIGHT+1, 2, MENU_HEIGHT-MENU_HEADER_HEIGHT-3, COLOR_BLACK);
        fillArea(MENU_X, MENU_Y+MENU_HEADER_HEIGHT, MENU_WIDTH, 1, COLOR_BLACK);
        fillArea(MENU_X+1, MENU_Y+MENU_HEADER_HEIGHT+1, MENU_WIDTH-3, 1, COLOR_WHITE);

        fillArea(MENU_X+2, MENU_Y+MENU_HEADER_HEIGHT+2, MENU_WIDTH-4, 4, MENU_COLOR);
        fillArea(MENU_X+2, MENU_Y+MENU_HEIGHT-6, MENU_WIDTH-4, 4, MENU_COLOR);
        fillArea(MENU_X+2, MENU_Y+MENU_HEADER_HEIGHT+6, 4, MENU_HEIGHT-MENU_HEADER_HEIGHT-12, MENU_COLOR);
        fillArea(MENU_WIDTH-MENU_X+6, MENU_Y+MENU_HEADER_HEIGHT+6, 4, MENU_HEIGHT-MENU_HEADER_HEIGHT-12, MENU_COLOR);

        fillArea(MENU_X+6, MENU_Y+MENU_HEADER_HEIGHT+6, 2, MENU_HEIGHT-MENU_HEADER_HEIGHT-13, COLOR_BLACK);
        fillArea(MENU_WIDTH-MENU_X+4, MENU_Y+MENU_HEADER_HEIGHT+6, 1, MENU_HEIGHT-MENU_HEADER_HEIGHT-13, COLOR_BLACK);
        fillArea(MENU_WIDTH-MENU_X+5, MENU_Y+MENU_HEADER_HEIGHT+6, 1, MENU_HEIGHT-MENU_HEADER_HEIGHT-12, COLOR_WHITE);
        fillArea(MENU_X+8, MENU_Y+MENU_HEADER_HEIGHT+6, MENU_WIDTH-16, 2, COLOR_BLACK);
        fillArea(MENU_X+8, MENU_Y+MENU_HEIGHT-8, MENU_WIDTH-16, 1, COLOR_BLACK);
        fillArea(MENU_X+6, MENU_Y+MENU_HEIGHT-7, MENU_WIDTH-13, 1, COLOR_WHITE);

        tabplus=0;
        for(tabplus=0; tabplus<mymenu->pages[selected_page]->tab_count*(MENU_TAB_WIDTH+MENU_TAB_SPACE); tabplus+=MENU_TAB_WIDTH+MENU_TAB_SPACE){
            CopySpriteMasked2bitR(left_corner, tabplus+MENU_X+MENU_TAB_INDENT, MENU_Y+1, 9, 8, tab_palette, 5*!tabplus);
            CopySpriteMasked2bitR(right_corner, tabplus+MENU_X+MENU_TAB_INDENT+MENU_TAB_WIDTH-9, MENU_Y+1, 9, 8, tab_palette, 0);

            fillArea(tabplus+MENU_X+MENU_TAB_INDENT, MENU_Y+9, 1, MENU_HEADER_HEIGHT-9, COLOR_BLACK);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+1, MENU_Y+9, 1, MENU_HEADER_HEIGHT-9, COLOR_WHITE);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+2, MENU_Y+9, 4, MENU_HEADER_HEIGHT-9, MENU_COLOR);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+6, MENU_Y+9, 2, MENU_HEADER_HEIGHT-9, COLOR_BLACK);

            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+MENU_TAB_WIDTH-8, MENU_Y+9, 1, MENU_HEADER_HEIGHT-9, COLOR_BLACK);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+MENU_TAB_WIDTH-7, MENU_Y+9, 1, MENU_HEADER_HEIGHT-9, COLOR_WHITE);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+MENU_TAB_WIDTH-6, MENU_Y+9, 4, MENU_HEADER_HEIGHT-9, MENU_COLOR);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+MENU_TAB_WIDTH-2, MENU_Y+9, 2, MENU_HEADER_HEIGHT-9, COLOR_BLACK);

            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+9, MENU_Y+6, MENU_TAB_WIDTH-18, 2, COLOR_BLACK);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+9, MENU_Y+2, MENU_TAB_WIDTH-18, 4, MENU_COLOR);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+9, MENU_Y+1, MENU_TAB_WIDTH-18, 1, COLOR_WHITE);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+9, MENU_Y+8, MENU_TAB_WIDTH-18, 1, COLOR_WHITE);
            fillArea(tabplus+MENU_X+MENU_TAB_INDENT+5, MENU_Y, MENU_TAB_WIDTH-9, 1, COLOR_BLACK);
        }

        tabplus=selected_tab*(MENU_TAB_WIDTH+MENU_TAB_SPACE);
        fillArea(tabplus+MENU_X+MENU_TAB_INDENT+8, MENU_Y+MENU_HEADER_HEIGHT, MENU_TAB_WIDTH-16, 8, COLOR_WHITE);
        fillArea(tabplus+MENU_X+MENU_TAB_INDENT+6, MENU_Y+MENU_HEADER_HEIGHT+1, 2, 5, COLOR_BLACK);
        plot(tabplus+MENU_X+MENU_TAB_INDENT+1, MENU_Y+MENU_HEADER_HEIGHT, COLOR_WHITE);
        fillArea(tabplus+MENU_X+MENU_TAB_INDENT+2, MENU_Y+MENU_HEADER_HEIGHT, 4, 2, MENU_COLOR);
        fillArea(tabplus+MENU_X+MENU_TAB_INDENT+MENU_TAB_WIDTH-8, MENU_Y+MENU_HEADER_HEIGHT+1, 1, 5, COLOR_BLACK);
        fillArea(tabplus+MENU_X+MENU_TAB_INDENT+MENU_TAB_WIDTH-7, MENU_Y+MENU_HEADER_HEIGHT, 1, 6, COLOR_WHITE);
        fillArea(tabplus+MENU_X+MENU_TAB_INDENT+MENU_TAB_WIDTH-6, MENU_Y+MENU_HEADER_HEIGHT, 4, 2, MENU_COLOR);


        y=(MENU_HEADER_HEIGHT-27)/2 + 10;
        for(tab=0; tab<mymenu->pages[selected_page]->tab_count; tab++){
            x=MENU_X+MENU_TAB_INDENT+tab*(MENU_TAB_WIDTH+MENU_TAB_SPACE)+11;
            fillArea(x-3, MENU_Y+9, MENU_TAB_WIDTH-16, MENU_HEADER_HEIGHT-9, COLOR_WHITE);
            PrintMini(&x, &y, mymenu->pages[selected_page]->tabs[tab]->label, 0x02, x+MENU_TAB_WIDTH-24, 0,0,COLOR_BLACK, COLOR_WHITE, 1,0);
        }

        x=MENU_X+1; lwidth=x;
        y = MENU_HEADER_HEIGHT/2-6;
        PrintMini(&x, &y, mymenu->pages[selected_page]->label, 0, x+MENU_TAB_INDENT-1, 0,0,COLOR_BLACK, MENU_COLOR, 1,0);
        lwidth = x-lwidth;
        x = max(MENU_X + (MENU_TAB_INDENT-1-lwidth)/2 + 1, MENU_X+1);
        fillArea(MENU_X+1, MENU_Y+1, MENU_TAB_INDENT-1, MENU_HEADER_HEIGHT-1, MENU_COLOR);
        PrintMini(&x, &y, mymenu->pages[selected_page]->label, 0, x+MENU_TAB_INDENT-1, 0,0,COLOR_BLACK, MENU_COLOR, 1,0);
    
        fillArea(MENU_X+8, MENU_Y+MENU_HEADER_HEIGHT+8, MENU_WIDTH-16, MENU_HEIGHT-MENU_HEADER_HEIGHT-16, COLOR_WHITE);


        starti=0; selected_item=0;
        mehet=1;
        item_count = mymenu->pages[selected_page]->tabs[selected_tab]->item_count;
        do{ 
            for(i=starti; i<starti+cheight && i<item_count && i<9; i++){
                buff[0] = '1' + i;
                sys_memset(buff+2, ' ', cwidth-2);
                memcpy(buff+2, mymenu->pages[selected_page]->tabs[selected_tab]->items[i].text,min(strlen(mymenu->pages[selected_page]->tabs[selected_tab]->items[i].text),cwidth-2));
        
                PrintCXY(MENU_X+8, MENU_Y+MENU_HEADER_HEIGHT-15+(i-starti)*24, buff, i==selected_item, -1, COLOR_BLACK, COLOR_WHITE, 1, 0);
                fillArea(MENU_X+8+cwidth*18, MENU_Y+MENU_HEADER_HEIGHT+9+(i-starti)*24, MENU_WIDTH-16-cwidth*18, 24, i==selected_item ? COLOR_BLACK : COLOR_WHITE);
            }

            if(starti>0){
                PrintCXY(MENU_X+MENU_WIDTH-26, MENU_Y+MENU_HEADER_HEIGHT-15, "\xE6\x92", TEXT_MODE_NORMAL, -1, starti==selected_item?COLOR_LIME:COLOR_FUCHSIA, starti==selected_item?COLOR_BLACK:COLOR_WHITE, 1, 0);
            }
            if(item_count>starti+cheight){
                PrintCXY(MENU_X+MENU_WIDTH-26, MENU_Y+MENU_HEADER_HEIGHT-15+(cheight-1)*24, "\xE6\x93", TEXT_MODE_NORMAL, -1, starti+cheight-1==selected_item?COLOR_LIME:COLOR_FUCHSIA, starti+cheight-1==selected_item?COLOR_BLACK:COLOR_WHITE, 1, 0);
            }

            GetKey(&key);
            switch(key){
                case KEY_CTRL_UP:
                    selected_item = (selected_item+item_count-1)%item_count;
                    break;
                case KEY_CTRL_DOWN:
                    selected_item = (selected_item+1)%item_count;
                    break;
                case KEY_CHAR_1:
                case KEY_CHAR_2:
                case KEY_CHAR_3:
                case KEY_CHAR_4:
                case KEY_CHAR_5:
                case KEY_CHAR_6:
                case KEY_CHAR_7:
                case KEY_CHAR_8:
                case KEY_CHAR_9:
                    selected_item = min(key-KEY_CHAR_1, item_count-1);
                    break;
                default:
                    mehet=0;
                    break;
            }

            if(starti > selected_item){starti=selected_item;}
            if(starti+cheight <= selected_item){starti=selected_item-cheight+1;}

        } while(mehet);

        switch(key){
            case KEY_CTRL_RIGHT:
                if(mymenu->pages[selected_page]->tab_count-1 == selected_tab){
                    selected_page = (selected_page+1)%mymenu->page_count;
                    selected_tab = 0;
                }else{
                    selected_tab++;
                }
                break;
            case KEY_CTRL_LEFT:
                if(selected_tab == 0){
                    selected_page = (selected_page+mymenu->page_count-1)%mymenu->page_count;
                    selected_tab = mymenu->pages[selected_page]->tab_count-1;
                }else{
                    selected_tab--;
                }
                break;
            case KEY_CTRL_EXIT:
                go=0;
                retval=-1;
                break;
            case KEY_CTRL_EXE:
                go=0;
                retval = mymenu->pages[selected_page]->tabs[selected_tab]->items[selected_item].id;
            default:
                for(page=0; page<mymenu->page_count; page++){
                    if(mymenu->pages[page]->key == key){
                        selected_page = page;
                        selected_tab = 0;
                    }
                }
                break;
        }


        LoadVRAM_1();

    } while(go);


    return retval;
}

Example #

Example initialization of a menu object (this is the copy of the Geometry plugin’s menu system):

//                     v-----The tab names are not important                  v----ID of the menu item, it's returned on select                 v----Menu text
static struct menu_tab Tab0 = {"Option", 7, <strong class="error">Template:1, &quot;Text&quot;}, {2, &quot;Expression&quot;}, {3, &quot;Number Format&quot;}, {4, &quot;Clr Constraint&quot;}, {5, &quot;Show All&quot;}, {6, &quot;Hide&quot;}, {7, &quot;Area Calc&quot;</strong>};
//      The label of the tag----^        ^----Numbers of the items on this tab. Cannot be grater than 9
static struct menu_tab Tab1 = {"Properties", 5, <strong class="error">Template:11, &quot;to the front&quot;}, {12, &quot;to the back&quot;}, {13, &quot;All Text&quot;}, {14, &quot;Fade I/O&quot;}, {15, &quot;Store Picture&quot;</strong>};
static struct menu_tab Tab2 = {"File", 4, <strong class="error">Template:21, &quot;New&quot;}, {22, &quot;Open&quot;}, {23, &quot;Save as&quot;}, {24, &quot;Key Help&quot;</strong>};
static struct menu_tab Tab3 = {"View", 6, <strong class="error">Template:31, &quot;Zoom Box&quot;}, {32, &quot;Pan&quot;}, {33, &quot;Scroll&quot;}, {34, &quot;Zoom In&quot;}, {35, &quot;Zoom Out&quot;}, {36, &quot;Zoom to Fit&quot;</strong>};
static struct menu_tab Tab4 = {"Edit", 6, <strong class="error">Template:41, &quot;Undo/Redo&quot;}, {42, &quot;Select All&quot;}, {43, &quot;Deselect All&quot;}, {44, &quot;Select Figure&quot;}, {45, &quot;Delete&quot;}, {46, &quot;Clear All&quot;</strong>};
static struct menu_tab Tab5 = {"Draw", 8, <strong class="error">Template:51, &quot;Point&quot;}, {52, &quot;Line Segment&quot;}, {53, &quot;Infinite Line&quot;}, {54, &quot;Ray&quot;}, {55, &quot;Vector&quot;}, {56, &quot;Circle&quot;}, {57, &quot;Arc&quot;}, {58, &quot;SemiCirc%28Diam%29&quot;</strong>};
static struct menu_tab Tab6 = {"Draw Spec", 7, <strong class="error">Template:61, &quot;Triangle&quot;}, {62, &quot;Isocs Triangle&quot;}, {63, &quot;Rectangle&quot;}, {64, &quot;Square&quot;}, {65, &quot;Polygon&quot;}, {66, &quot;Regular n-gon&quot;}, {67, &quot;Function f%28x%29&quot;</strong>};
static struct menu_tab Tab7 = {"Construct", 8, <strong class="error">Template:71, &quot;Perp Bisector&quot;}, {72, &quot;Perpendicular&quot;}, {73, &quot;Midpoint&quot;}, {74, &quot;Intersection&quot;}, {75, &quot;Angle Bisector&quot;}, {76, &quot;Parallel&quot;}, {77, &quot;Tangent&quot;}, {78, &quot;Attached Angle&quot;</strong>};
static struct menu_tab Tab8 = {"Transform", 6, <strong class="error">Template:81, &quot;Reflection&quot;}, {82, &quot;Translation&quot;}, {83, &quot;Trans%28Sel Vec%29&quot;}, {84, &quot;Rotation&quot;}, {85, &quot;Dilation&quot;}, {86, &quot;Symmetry&quot;</strong>};
static struct menu_tab Tab9 = {"Animate", 8, <strong class="error">Template:91, &quot;Add Animation&quot;}, {92, &quot;Replace Anima&quot;}, {93, &quot;Trace&quot;}, {94, &quot;Edit Animation&quot;}, {95, &quot;Go%28once%29&quot;}, {96, &quot;Go%28repeat%29&quot;}, {97, &quot;Add Table&quot;}, {98, &quot;Display Table&quot;</strong>};

//                      v-----The page names are not important    v-------pointer to the previously defined tab variable
static struct menu_page Page0 = {"OPT", KEY_CTRL_OPTN, 2, {&Tab0, &Tab1}};
//Displayed in the upper left corner^   ^----the program automatically jumps here if this key is pressed
static struct menu_page Page1 = {"F1", KEY_CTRL_F1, 2, {&Tab2, &Tab3}};
//                 Number of tabs on this page------^
static struct menu_page Page2 = {"F2", KEY_CTRL_F2, 1, {&Tab4}};
static struct menu_page Page3 = {"F3", KEY_CTRL_F3, 2, {&Tab5, &Tab6}};
static struct menu_page Page4 = {"F4", KEY_CTRL_F4, 1, {&Tab7}};
static struct menu_page Page5 = {"F5", KEY_CTRL_F5, 1, {&Tab8}};
static struct menu_page Page6 = {"F6", KEY_CTRL_F6, 1, {&Tab9}};

//      Number of the pages-----v   v-------pointer to the previously defined page variable
static struct Tmenu geometry = {7, {&Page0,&Page1,&Page2,&Page3,&Page4,&Page5,&Page6}};
//   ^-----every struct must be static

//How to call the function:
int choice = menu(&geometry, 3, 1);

Controls #

  • Press keys UP and DOWN to select items
  • Press keys LEFT and RIGHT to jump to the next or the previous tab
  • Press EXE to select an item
  • Press EXIT to close the menu without selecting an item
  • Press your pre-defined keys to jump to page

Notices #