|
|
|
| GUI Clinic Home |
||
| How do I Put Menus into My Program? | The MENU Structure | Running a Menu | ||
Previous: Building and Using Dialogs How do I Put Menus into My Program? So far, we've covered all of Allegro's standard dialog controls, save one: the menu control. Conceptually, menus are a lot like the standard controls: fill out a DIALOG structure to represent the menu and manipulate its data fields to modify its behavior. Easy. However, there is a fair amount of power hidden behind that statement, more so than with most of the GUI objects. That's why this control has its own section. The menu control is based on two parts: the dialog procedure and the MENU structure. As for the first part, Allegro's documentation again contains an entry for this object (you may be getting the idea that it would be a Good Thing to read the documentation, and it definitely is; Shawn, et al put a lot of work into it): d_menu_proc So what about the second part of the menu control, the MENU structure? Read on.
The second part of the menu control is an array of MENU structures, much the same way a dialog is made up of an array of DIALOG structures. A d_menu_proc object's dp field will point to this array. The array is terminated by an entry with a NULL text pointer. Here's how this structure is, well, structured: typedef struct MENU Let's discuss this structure, shall we? Most of the following is paraphrased from the documentation. char *text int (*proc)() int menu_callback(); The return value will be given back to the dialog manager if the menu was part of a dialog; if the menu was processed by calling do_menu(), it is ignored. struct MENU *child int flags void *dp
If you can visualize a menu and initialize an array of structures, you can create an Allegro menu object. It's one of the easier things you can do with the GUI (it's certainly much easier than designing menuing systems by hand for one of the big-time GUI systems). You can run an Allegro menu in two ways: by using do_dialog() and activating a menu, or by calling do_menu() directly. We'll cover both ways here. Let's build an example program. To simplify things, our program will consist of only menuing code, with only what is necessary to support it. That should keep it small. We'll build a program that uses menu selections to change the color of some on-screen text, sort of like the program we built in the section on dialogs. Designing the Menu The declaration for such a menuing system follows: MENU file_menu[] =
{
{ "&Disabled", NULL, NULL, D_DISABLED, NULL },
{ "", NULL, NULL, 0, NULL },
{ "E&xit\tCtrl-X", quitter, NULL, 0, NULL },
{ NULL, NULL, NULL, 0, NULL }
};
MENU child[] =
{
{ "&Dark", dark_red, NULL, 0, NULL },
{ "&Bright", bright_red, NULL, 0, NULL },
{ NULL, NULL, NULL, 0, NULL },
};
MENU text_menu[] =
{
{ "&Red", NULL, child, 0, NULL },
{ "&Green", text_green, NULL, 0, NULL },
{ "&Blue", text_blue, NULL, 0, NULL },
{ NULL, NULL, NULL, 0, NULL }
};
MENU main_menu[] =
{
{ "&File", NULL, file_menu, 0, NULL },
{ "&Text", NULL, text_menu, 0, NULL },
{ NULL, NULL, NULL, 0, NULL }
};
Note the relationship between the menus: The top-level menu is main_menu. It has two children, file_menu and text_menu. The "Red" item in text_menu has its own child. It could go deeper, but it shows how things work. Powering the Menu void change_color(int r, int g, int b)
{
RGB col;
col.r = r;
col.g = g;
col.b = b;
vsync();
set_color(254, &col);
}
int quitter()
{
return D_CLOSE;
}
int dark_red()
{
change_color(32, 0, 0);
return D_O_K;
}
int bright_red()
{
change_color(63, 32, 32);
return D_O_K;
}
int text_green()
{
change_color(0, 63, 0);
return D_O_K;
}
int text_blue()
{
change_color(0, 0, 63);
return D_O_K;
}
int text_purple()
{
change_color(48, 0, 48);
return D_O_K;
}
int text_orange()
{
change_color(63, 32, 0);
return D_O_K;
}
int text_black()
{
change_color(0, 0, 0);
return D_O_K;
}
So basically, when the user clicks on a menu item, the GUI calls the appropriate menu callback, which in turn calls change_color(), which changes the color of the text in the main dialog (which we haven't gotten to yet). Easy. As a side note, you may have noticed that one source of ugliness with GUI code is that the menu items all have their own little functions that clutter up the source. Oh, well... Smell something fishy? I do. There are three extra callbacks in the above list! Where'd they come from? You didn't think we'd finish without demonstrating popup menus, did you? Popping Up So exactly what does do_menu() do? As usual, the documentation can clue us in: int do_menu(MENU *menu, int x, int y) Note that you can either check the return value from this function and take action based on the user's menu choice, or simply use menu callbacks (just like you would with menus inside dialogs). Or, you can use some sort of clever combination of both, for some sort of hybrid multi-callback checking system. I'll leave that up to you. For our example, we'll stick to using callbacks. Here is our popup menu definition, as well as its "driver" function: MENU popup_menu[] =
{
{ "&Purple", text_purple, NULL, 0, NULL },
{ "&Orange", text_orange, NULL, 0, NULL },
{ "&Black", text_black, NULL, 0, NULL },
{ NULL, NULL, NULL, 0, NULL }
};
/* popup menu function */
int run_popup()
{
do_menu(popup_menu, mouse_x, mouse_y);
return D_O_K;
}
Nothing new here, really, except for the call to do_menu(). It just displays our popup menu at the same position as the mouse. Driving Miss Menu #define ctrl(x) (x - 'a' + 1)
DIALOG main_dialog[] =
{
/* (dialog proc) (x) (y) (w) (h)
(fg) (bg) (key)
(flags)(d1) (d2) (dp) */
{ d_clear_proc, 0, 0, 640, 480,
0, 0, 0,
0, 0, 0, NULL },
{ d_menu_proc, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0, main_menu },
{ d_ctext_proc, 320, 240, 300, 20,
254, 0, 0,
0, 0, 0, "Some text" },
{ d_keyboard_proc, 0, 0, 0, 0,
0, 0, ctrl('x'),
0, 0, 0, quitter },
{ d_keyboard_proc, 0, 0, 0, 0,
0, 0, ctrl('m'),
0, 0, 0, run_popup },
{ NULL, 0, 0, 0, 0,
0, 0, 0,
0, 0, 0, NULL }
};
int main()
{
allegro_init();
install_keyboard();
install_mouse();
install_timer();
set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0);
set_palette(desktop_palette);
/* initialize the text color */
change_color(0, 0, 0);
/* run the dialog */
do_dialog(main_dialog, -1);
return 0;
}
As with the dialog example, downloading the source code will make things easier to read, since the dialog is declared one line per object. Notice that there are a couple of d_keyboard_proc objects in the main dialog. They handle the keypresses for our program. If the user presses Ctrl-M, the popup menu is displayed. If the user presses Ctrl-X, the GUI calls the quitter() function, which is also called when the user clicks File | Exit. You see, since the d_keyboard_proc objects use the exact same kind of callbacks as the d_menu_proc objects, they can share functions. This makes it very easy to provide keyboard shortcuts for menu items. If you'd like the source code to this program, you can download menu.c. By now, you should have enough knowledge to go out and create your own GUI-based programs. However, you'll quickly learn that the GUI controls don't always do exactly what you want them to do. And even when they do, you'll probably find yourself dissatisfied with the rather dated look of the Atari ST GUI. Fear not, brave coder! The true power of Allegro's GUI comes from the fact that you can modify it without hacking into its internals. The GUI subsystem has some object-oriented-esque(!) features, which allow you to enhance it by deriving new controls from pre-existing ones, and in a fairly rapid manner. We'll talk about that and more in the next few sections. By the time we're through, you'll even be able to create your own GUI objects from scratch! Stay tuned! But first, let's learn about some simple ways to change the GUI system's behavior... Next: Modifying GUI Behavior |