Reducing the kludge in menu.c
Moderator: InsideQC Admins
17 posts
• Page 1 of 2 • 1, 2
Reducing the kludge in menu.c
menu.c has a hell of a lot of redundancy.
And every menu level has:
1. An entrance function (Set menu state, do misc things like scan save games folder, etc.)
2. A drawing function
3. A key navigation function
I want menu.c to be short, sweet and flexible.
After playing with the code and playing with the code some more and conducting some mental experiments, I ended up coming to the conclusion that even a somewhat complicated menu should only have an entrance function.
Plus merely drawing it should automatically give it gui capability. FTEQW sort of does this, and the FTEQW implementation is clean and simple (left click = enter, right click = ESC, hover = select).
But I need the ability to draw a button in some cases and after mentally factoring out the rather large amount of code redundancy in menu.c (plus noticing in ezQuake the support for mcharset.png) realized it can be REALLY streamlined and allow the quick modification.
What the Quake menu really is
The Quake menu really in some ways is not a heck of a lot different than, say, a form in .NET or vb6. It is just a set of input controls onscreen with a tabindex and optionally an image.
.NET / vb6 analogy examples:
1. Quake logo on left is just some static image (image control?)
2. Single Player - Multiplayer - Options - Help - Quake (buttons)
3. Volume slider ... no explanation required
4. The "name field in Multiplayer -> Setup (textbox?)
5. The on and off fields (check boxes)
6. etc.
Drawing them should also set up a hotspot structure for mouse tracking. If the menu is up, check the x,y versus x1, y1 to x2, y2 to check for a hotspot ("the control surface").
When you draw, say, the "Single Player" button ... the draw function does know the size of the graphic.
But again, I'm arguing that the menu shouldn't even have draw functions, per se. Nor navigation functions.
Just make a "control array" with different control types. To draw "gfx\mainmenu.lmp" set the control type to some "imagebox constant". Fill in the data field with graphic name.
To draw text, set the control type to "label", store the text in a data field. Etc.
Instead of the menu having a draw function, it cycles through the control array and does the type of drawing for each type of "control". The controls are expected to have a linear "tabindex" relationship. For input, navigation keys just move the cursor (spinning Q or blinking triangle) to the appropriate "control" .
Much easier said than done, I will have quite a bit to flesh out because I have some ambitious menu goals and functionality.
Notice I could still throw "ScanSaves ();" in the entrance function or whatever else needs to be done on entering a specific menu. The final argument in the function Navigation_Add is a function reference that if not NULL lets a "control" have a function that gives an extra evaluation (like don't go into the Save game menu if !sv.active, for instance) that has the option to cancel an operation, modify it or whatever you want.
This philosophy should lead to a very streamlined menu.c where the entrance function provides all the drawing instructions and all the info for the keyboard navigation plus hotspots for a gui to track and act on mouse actions.
And every menu level has:
1. An entrance function (Set menu state, do misc things like scan save games folder, etc.)
2. A drawing function
3. A key navigation function
I want menu.c to be short, sweet and flexible.
After playing with the code and playing with the code some more and conducting some mental experiments, I ended up coming to the conclusion that even a somewhat complicated menu should only have an entrance function.
Plus merely drawing it should automatically give it gui capability. FTEQW sort of does this, and the FTEQW implementation is clean and simple (left click = enter, right click = ESC, hover = select).
But I need the ability to draw a button in some cases and after mentally factoring out the rather large amount of code redundancy in menu.c (plus noticing in ezQuake the support for mcharset.png) realized it can be REALLY streamlined and allow the quick modification.
What the Quake menu really is
The Quake menu really in some ways is not a heck of a lot different than, say, a form in .NET or vb6. It is just a set of input controls onscreen with a tabindex and optionally an image.
.NET / vb6 analogy examples:
1. Quake logo on left is just some static image (image control?)
2. Single Player - Multiplayer - Options - Help - Quake (buttons)
3. Volume slider ... no explanation required
4. The "name field in Multiplayer -> Setup (textbox?)
5. The on and off fields (check boxes)
6. etc.
Drawing them should also set up a hotspot structure for mouse tracking. If the menu is up, check the x,y versus x1, y1 to x2, y2 to check for a hotspot ("the control surface").
When you draw, say, the "Single Player" button ... the draw function does know the size of the graphic.
But again, I'm arguing that the menu shouldn't even have draw functions, per se. Nor navigation functions.
Just make a "control array" with different control types. To draw "gfx\mainmenu.lmp" set the control type to some "imagebox constant". Fill in the data field with graphic name.
To draw text, set the control type to "label", store the text in a data field. Etc.
Instead of the menu having a draw function, it cycles through the control array and does the type of drawing for each type of "control". The controls are expected to have a linear "tabindex" relationship. For input, navigation keys just move the cursor (spinning Q or blinking triangle) to the appropriate "control" .
Much easier said than done, I will have quite a bit to flesh out because I have some ambitious menu goals and functionality.
- Code: Select all
void MenuEnter_Main (void)
{
int cursor_y = 32;
Menu_SetState (menu_main); HotSpot_Init (menu_cursorrow [menu_state]); // Clear the GUI thing and set cursor to use
ImageStatic_Add ( 16, 4, "gfx/qplaque.lmp"); // Autosize
ImageStatic_Add (CENTER, 4, "gfx/ttl_main.lmp"); // Autosize
Navigation_Add ( 72, cursor_y, "Single Player", "Start a single player game", BIG, 54, menu_session, MenuFunc_Session_Think);
Navigation_Add ( 72, cursor_y+=20, "Server Browser", "Play multiplayer online", BIG, 54, menu_serverbrowser, NULL);
Navigation_Add ( 72, cursor_y+=20, "Options", "Set up preferences", BIG, 54, menu_options, NULL);
Navigation_Add ( 72, cursor_y+=20, "Demos", "Watch a recorded game", BIG, 54, menu_demos, NULL);
Navigation_Add ( 72, cursor_y+=20, "Help", "Help contents", BIG, 54, menu_help, NULL);
Navigation_Add ( 72, cursor_y+=20, "Quit", "Exit game", BIG, 54, menu_quit, NULL);
// At this point we have significantly described everything for both drawing and input and mouse navigation
}
Notice I could still throw "ScanSaves ();" in the entrance function or whatever else needs to be done on entering a specific menu. The final argument in the function Navigation_Add is a function reference that if not NULL lets a "control" have a function that gives an extra evaluation (like don't go into the Save game menu if !sv.active, for instance) that has the option to cancel an operation, modify it or whatever you want.
This philosophy should lead to a very streamlined menu.c where the entrance function provides all the drawing instructions and all the info for the keyboard navigation plus hotspots for a gui to track and act on mouse actions.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Same realization some years ago (have a look at my menu_common.cpp), but it's uncanny when you think about it - that's really all that a menu actually is. Build a controls library around some drawing and input functions, add some event handlers and you're there. (Plus you'll be able to build new menus - and add new items to existing menus - really quickly, easily and safely).
Unfortunately a lot of the Quake menus require certain special-case handling, so things don't always come out quite so clean as one would like, but it definitely is an improvement on the old way.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
We knew the words, we knew the score, we knew what we were fighting for
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
I have been working on my own menu system too
I was going to post a topic about it a while back as well. I racked my mind and came up with this system:
1.Hold your current option and current menu in a struct.
2. Pass on your values in the struct to a couple of integers that can be used globally to increment your current option.
3. Have 1 key function that deals with Keyboard input
So far it has reduced code bloat majorly, and has made coding menus quite a bit easier. Theres a few other things that could be worked on to make menus easier as well.
Good thinking Baker
I was going to post a topic about it a while back as well. I racked my mind and came up with this system:
1.Hold your current option and current menu in a struct.
2. Pass on your values in the struct to a couple of integers that can be used globally to increment your current option.
3. Have 1 key function that deals with Keyboard input
So far it has reduced code bloat majorly, and has made coding menus quite a bit easier. Theres a few other things that could be worked on to make menus easier as well.
Good thinking Baker
-

Mexicouger - Posts: 514
- Joined: Sat May 01, 2010 10:12 pm
fte's widgit library is in m_items.c, but read menu.h for an overview of it.
Basically its just a new menu type, but pretty much every menu uses that instead, which is why the mouse works in them.
Once you have multiple widgit types, its fairly easy to add a new one.
It gets painful when you try implementing server browsers though!
Basically its just a new menu type, but pretty much every menu uses that instead, which is why the mouse works in them.
Once you have multiple widgit types, its fairly easy to add a new one.
It gets painful when you try implementing server browsers though!
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
I deliberately didn't add mouse control because Quake's menus are really really tiny and everything is scrunched so tightly together that it would be a bit of a pain for the player. At least with keyboard you've got precision. There's also the mouse behaviour in windowed modes to consider - Quake releases the mouse to the OS when you bring up the menus or drop the console, and I didn't want to lose this behaviour as I think it's good form. Hard to reconcile that with having mouse control in menus.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
We knew the words, we knew the score, we knew what we were fighting for
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
mh wrote:There's also the mouse behaviour in windowed modes to consider - Quake releases the mouse to the OS when you bring up the menus or drop the console, and I didn't want to lose this behaviour as I think it's good form. Hard to reconcile that with having mouse control in menus.
Surely you let the user click on menu items when the mouse is in the window, and let Windows handle the mouse click if outside the window? That's how other apps work. I mean, let the OS mouse exist as it does now, but start listening for click events in your quake window and decide whether to act on each one as it comes in.
Caveat: i've never done this, don't know how hard it is.
- metlslime
- Posts: 316
- Joined: Tue Feb 05, 2008 11:03 pm
metlslime wrote:mh wrote:There's also the mouse behaviour in windowed modes to consider - Quake releases the mouse to the OS when you bring up the menus or drop the console, and I didn't want to lose this behaviour as I think it's good form. Hard to reconcile that with having mouse control in menus.
Surely you let the user click on menu items when the mouse is in the window, and let Windows handle the mouse click if outside the window? That's how other apps work. I mean, let the OS mouse exist as it does now, but start listening for click events in your quake window and decide whether to act on each one as it comes in.
Caveat: i've never done this, don't know how hard it is.
Yeah, that would work OK. Still gotta deal with everything being scrunched up tightly together though (this is where an intermediate sized font would really help).
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
We knew the words, we knew the score, we knew what we were fighting for
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
metlslime wrote:That's how other apps work. I mean, let the OS mouse exist as it does now, but start listening for click events in your quake window and decide whether to act on each one as it comes in.
Caveat: i've never done this, don't know how hard it is.
ProQuake has done this for 3 years in the namemaker menu. Quake releases the mouse to Windows if the menu is up and if in the namemaker, Key_Event passes mouse X, Y to the menu (adjusting it for canvas size). I use a similar method to allow binding of the mouse keys in customized controls with windowed mode with the mouse released.
mh wrote:Still gotta deal with everything being scrunched up tightly together though (this is where an intermediate sized font would really help).
Set the canvas size to 320 x 240
And don't forget I mentioned this in the first post ---
http://www.ezepov.com/qw/gfx/mcharset.png --- draw teh menu items on the fly! No more making .lmp files for everything.
At some point I might check with Moon[Drunk], with the right menu charset, you could make the title plaques on the fly too.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Baker wrote:At some point I might check with Moon[Drunk], with the right menu charset, you could make the title plaques on the fly too.
Yeah, not having a "big conchars" (12x12) into original iD's pack is a shame. While I think Moon[Drunk] work awesome, it's not close enough to the original menus and plaques.
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC
(LordHavoc)
-

frag.machine - Posts: 2090
- Joined: Sat Nov 25, 2006 1:49 pm
I don't really like enlarging the 2D pics for the simple reason that they distort. 2x size is OK, but any intermediate size just looks ugly. Quake's pics were never really designed with that in mind.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
We knew the words, we knew the score, we knew what we were fighting for
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
just for reference, the smallest regular size for windows etc fonts is 12 pixels high.
If you're running at 640*480 with a conwidh/height of 320*240, the mouse is probably going to be usable enough. If you're using a 1:1 ratio then yes, its a little tricky to select stuff. But it goes without saying that adding a mouse cursor doesn't mean you have to remove keyboard support. And there's no reason to prevent the menu from using absolute cursor positions so that it doesn't care whether the pointer is emulated or provided by the windowing system. If its running windowed, you're actually better off releasing the mouse cursor as soon as they hit the menus, because the user is acoustomed to their windows mouse sensitivity, along with its mouse acceleration.
An aside: HalfLife does mouse support within the clientside dll. Yes, really. GetCursorPos/SetCursorPos are called from the gamecode, with the engine telling the gamecode where the center of the window is. hideous. But its no barrier to mouse-driven menus. Just directly use the cursor pos and stop changing it. Job done.
the menucharset by Moon[Drunk] is 20*20, which matches the main menu graphics. Its primary use is of course changing what's said on those menus. Its not really intended for individual options. There's only 64 chars max, and its too tall for lots of options on the screen at once (the idea for it is somewhat derived from hexen2, tbh, which has a similar image as standard).
If you're running at 640*480 with a conwidh/height of 320*240, the mouse is probably going to be usable enough. If you're using a 1:1 ratio then yes, its a little tricky to select stuff. But it goes without saying that adding a mouse cursor doesn't mean you have to remove keyboard support. And there's no reason to prevent the menu from using absolute cursor positions so that it doesn't care whether the pointer is emulated or provided by the windowing system. If its running windowed, you're actually better off releasing the mouse cursor as soon as they hit the menus, because the user is acoustomed to their windows mouse sensitivity, along with its mouse acceleration.
An aside: HalfLife does mouse support within the clientside dll. Yes, really. GetCursorPos/SetCursorPos are called from the gamecode, with the engine telling the gamecode where the center of the window is. hideous. But its no barrier to mouse-driven menus. Just directly use the cursor pos and stop changing it. Job done.
the menucharset by Moon[Drunk] is 20*20, which matches the main menu graphics. Its primary use is of course changing what's said on those menus. Its not really intended for individual options. There's only 64 chars max, and its too tall for lots of options on the screen at once (the idea for it is somewhat derived from hexen2, tbh, which has a similar image as standard).
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
Imperfect, but I figured I'd dump some code here on how to use mcharset with minimal modifications. Pretty much requires a JoeQuake or FuhQuake based engine, with the core of this code borrowed from ezQuake, although I decreased the complexity and all the twists and turns a bit.
Requires an mcharset.lmp and [mcharset.tga or mcharcset.png] in gfx folder.
Requires an mcharset.lmp and [mcharset.tga or mcharcset.png] in gfx folder.
- Code: Select all
/*
Copyright (C) 2011 The EZQuake peoples mostly
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// mcharset.c
//
// Finds the coordinates in the bigfont texture of a given character.
//
#include "quakedef.h"
#include "gl_local.h" // Baker: needed
void Draw_GetBigfontSourceCoords(char c, int char_width, int char_height, int *sx, int *sy)
{
if (c >= 'A' && c <= 'Z')
{
(*sx) = ((c - 'A') % 8) * char_width;
(*sy) = ((c - 'A') / 8) * char_height;
}
else if (c >= 'a' && c <= 'z')
{
// Skip A-Z, hence + 26.
(*sx) = ((c - 'a' + 26) % 8) * char_width;
(*sy) = ((c - 'a' + 26) / 8) * char_height;
}
else if (c >= '0' && c <= '9')
{
// Skip A-Z and a-z.
(*sx) = ((c - '0' + 26 * 2) % 8 ) * char_width;
(*sy) = ((c - '0' + 26 * 2) / 8) * char_height;
}
else if (c == ':')
{
// Skip A-Z, a-z and 0-9.
(*sx) = ((c - '0' + 26 * 2 + 10) % 8) * char_width;
(*sy) = ((c - '0' + 26 * 2 + 10) / 8) * char_height;
}
else if (c == '/')
{
// Skip A-Z, a-z, 0-9 and :
(*sx) = ((c - '0' + 26 * 2 + 11) % 8) * char_width;
(*sy) = ((c - '0' + 26 * 2 + 11) / 8) * char_height;
}
else
{
(*sx) = -1;
(*sy) = -1;
}
}
void Draw_BigString (int x, int y, char *str)
{
float frow, fcol;
int num;
if (y <= -8)
return; // totally off screen
if (!*str)
return;
// 1. Bind the right texture
// 2. Calculate the coordinates correctly
{
mpic_t *big_charset = Draw_CachePic("gfx/mcharset.lmp");
int char_width = (big_charset->width / 8);
int char_height = (big_charset->height / 8);
while (*str) // stop rendering when out of characters
{
if ((num = *str++) != 32) // skip spaces
{
int sx, sy;
char c = (char)(num & 0xFF);
Draw_GetBigfontSourceCoords (c, char_width, char_height, &sx, &sy); // Get frow and fcol
if (sx >= 0) // Negative values mean no char
Draw_SubPic (x, y, big_charset, sx, sy, char_width, char_height);
}
x += char_width;
}
}
}
void SCR_DrawTest (void)
{
Draw_BigString (120, 100, "Test");
}
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
silverjoel wrote:Have you looked at the Quake 3 menu system?
I haven't, at least not the code. As far as menu systems go, I think Quake's menu system is rather effective, with one such an example of expanding it (ezQuake) taking it, in my opinion, a bit farther than it needed to go.
I'm kind of looking for a happy medium. But with one eye on making the code easy to maintain and modify.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
17 posts
• Page 1 of 2 • 1, 2
Who is online
Users browsing this forum: No registered users and 1 guest