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.
menu.hpp #
//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, "Text"}, {2, "Expression"}, {3, "Number Format"}, {4, "Clr Constraint"}, {5, "Show All"}, {6, "Hide"}, {7, "Area Calc"</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, "to the front"}, {12, "to the back"}, {13, "All Text"}, {14, "Fade I/O"}, {15, "Store Picture"</strong>};
static struct menu_tab Tab2 = {"File", 4, <strong class="error">Template:21, "New"}, {22, "Open"}, {23, "Save as"}, {24, "Key Help"</strong>};
static struct menu_tab Tab3 = {"View", 6, <strong class="error">Template:31, "Zoom Box"}, {32, "Pan"}, {33, "Scroll"}, {34, "Zoom In"}, {35, "Zoom Out"}, {36, "Zoom to Fit"</strong>};
static struct menu_tab Tab4 = {"Edit", 6, <strong class="error">Template:41, "Undo/Redo"}, {42, "Select All"}, {43, "Deselect All"}, {44, "Select Figure"}, {45, "Delete"}, {46, "Clear All"</strong>};
static struct menu_tab Tab5 = {"Draw", 8, <strong class="error">Template:51, "Point"}, {52, "Line Segment"}, {53, "Infinite Line"}, {54, "Ray"}, {55, "Vector"}, {56, "Circle"}, {57, "Arc"}, {58, "SemiCirc%28Diam%29"</strong>};
static struct menu_tab Tab6 = {"Draw Spec", 7, <strong class="error">Template:61, "Triangle"}, {62, "Isocs Triangle"}, {63, "Rectangle"}, {64, "Square"}, {65, "Polygon"}, {66, "Regular n-gon"}, {67, "Function f%28x%29"</strong>};
static struct menu_tab Tab7 = {"Construct", 8, <strong class="error">Template:71, "Perp Bisector"}, {72, "Perpendicular"}, {73, "Midpoint"}, {74, "Intersection"}, {75, "Angle Bisector"}, {76, "Parallel"}, {77, "Tangent"}, {78, "Attached Angle"</strong>};
static struct menu_tab Tab8 = {"Transform", 6, <strong class="error">Template:81, "Reflection"}, {82, "Translation"}, {83, "Trans%28Sel Vec%29"}, {84, "Rotation"}, {85, "Dilation"}, {86, "Symmetry"</strong>};
static struct menu_tab Tab9 = {"Animate", 8, <strong class="error">Template:91, "Add Animation"}, {92, "Replace Anima"}, {93, "Trace"}, {94, "Edit Animation"}, {95, "Go%28once%29"}, {96, "Go%28repeat%29"}, {97, "Add Table"}, {98, "Display Table"</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 #
- This function uses SaveVRAM_1(). So your previously saved VRAM data will be overwritten.
- This function uses CopySpriteMasked2bitR() which is different from CopySpriteMasked2bit() and CopySpriteNbitMasked()
- This function uses sys_memset() and PrintCXY(). They might not be in your libfxcg. You can download the newest version from here