Allegro.cc - Online Community

Allegro.cc Forums » Game Design & Concepts » A question for Steve Terry, Miran, Spellcaster and other GUI people!

This thread is locked; no one can reply to it. rss feed Print
A question for Steve Terry, Miran, Spellcaster and other GUI people!
Richard Phipps
Member #1,632
November 2001
avatar

Hi there,

Since you three have some experience of modifying the Allegro GUI I was wondering how you would go about designing a new GUI system? One that's more flexible and skinnable..

Thought I'd better get some ideas before I jump in at the deep end!

:)

amarillion
Member #940
January 2001
avatar

Just download their GUI's and look at the source code. I haven't tried out all tree of them but I know that MASKinG comes with docs explaining the interface in great detail. Copy & paste!

Richard Phipps
Member #1,632
November 2001
avatar

I was thinking more of listening to their ideas after they have done their libs. Most of us realise the mistakes we've made in design after we've done something!

But, you do have a good point there amarillion.. :)

amarillion
Member #940
January 2001
avatar

Try to do write a program using Java SWING. Although it is highly critisized for being very slow, I think it has a very nice, neat structure. For example, the way events are handled or the way data is separated from widgets.

Richard Phipps
Member #1,632
November 2001
avatar

spellcaster
Member #1,493
September 2001
avatar

Ok, let's startr simple: You should use an OO approach. I'll use C++, you can of course use C aswell (but using C++ in the examples will make the examples more readable).

The first think you should realize is that every widget (UI Element) should be eable to draw itself, and to deal with any events itself.

This leads to the point, that sombody or something needs to tell the widgets that an event has happened. Let's call this thing the EventManager.

What the EventManger does is quite simple. Every time he is called (Let's assume there is an EventMessagner::update() method, or something like this) it checks where the mouse is. It then tries to find the widget at this position (if any).
Usually, you'll check if teh mouse has moved at all first, then if the mouse is still within the widget it has been in before.
Once the widget the mouse is in is found, you generate an according event (mouseMoved, mouseOut, mouseEnter, mouseDrag, etc).
If the mouse button status has changed, you signal this is well (mouseButtonPressed, mouseButtonReleased).

These methods might create repaint events which are stored in the EventManagers queue. So, after doing it's normal stuff, it will work on this list as well. And calling the update method of the widgets in question.

In order to make painting easy, every widget should only get a sub bitmap of the bitmap everything is drawn on. So, you can always assume that (0,0) is the upper left corner of the widget whichs paint() method is called.

Depending on your intention, you might want to use something similar to a graphics context, to store the bitmap to work on, the current font, foreground and background colors, etc.

Another key point is that your widgets should be containers. This means they should be able to contain other widgets.

So, if you have a textbox, a button and a pop-up menu you should be able to code a combo box simply by combining these elements.
A spinner would be a textfield an two buttons (up/down).

Once you have a simple widget, and the EventManager writing the widget itself is pretty simple.

In theory, your widget class could look like this:

1class Widget {
2 vector<Widget*> childs;
3 Widget* parent;
4 
5 int state;
6 Rect bounds;
7 
8protected:
9 virtual void handleMouseEvent(MouseEvent e);
10 virtual void handleKeyboardEvent(KeyboardEvent e);
11 virtual void handleJoystickEvent(JoystickEvent e);
12 
13 virtual void repaintChilds(GraphicsContext *g);
14 virtual void repaintSelf(GraphicsContext *g);
15 
16public:
17 Widget();
18 virtual ~Widget();
19 virtual void repaint(GraphicsContext *g);
20 virtual int getState();
21 virtual void setState(int state);
22 
23 virtual void setLocation(Point *p);
24 virtual void setLocation(int x, int y);
25 virtual void setBounds(Rectangle *rect);
26 virtual void setBounds(int x, int y, int w, int h);
27 virtual void setSize(int w, int h);
28 virtual void setSize(Dimension *size);
29 
30 virtual void setVisible(bool visible);
31 virtual bool isVisible();
32 
33 virtual int add(Widget* child);
34 virtual Widget *getChildAt(int index);
35 virtual int getChildCount();
36 virtual Widget *getWidgetAtLocation(int x, int y);
37 
38};

The getWidgetAtLocation() checks if one of the child widgets is at the given position. If so, it calls the getWidgetAtLocation() of the corresponding child widget. If there's no child at this position, it returns the this pointer.

So, your EventManager class can call the getWidgetAtLocation() method to get the widget under the mouse (Some complex widgets, say a combobox, might not call the child class, but simply always return this).

And yes, this is of course simplified. :)
Hope it helps anyway...

--
There are no stupid questions, but there are a lot of inquisitive idiots.

Steve Terry
Member #1,989
March 2002
avatar

Well I did write some docs for my lib and went over a few of the subsystems in depth. I can safely say I've made many mistakes while making my GUI, but there is really no right or wrong way, just a matter of preference. For instance at one point I had a texture field for all of my skins so you could have one texture used for multiple things, later out of preference I changed it so that each object has a different texture name, this cut down the number of config lines in the config files, but in some instances caused skins to bloat a bit because two textures may look identical, but require different names. If you really want to know how to make the allegro GUI more flexible download my source and look at some of the things I did, also looking at the docs and the "making procedures" section should explain some of the subsystems I had to create to handle stuff. The most important subsystem I had to create so far was the memory manager, without that I would be extremely limited in how flexible I could make the GUI, with it however there is almost no limit. In fact all of the subsystems I use for my GUI are completely portable to any other GUI and are not tied into NAS GUI at all. Of course you are free to extend any part of my GUI as you wish. I'm looking for someone who is willing to extend it to allow for multiple overlapping dialogs and menus. After that all I have to do is finish off the few remaining procs and it'll be nearly complete.
Here's a simple example of how my GUI works:

1//Draws a bitmap onto a dialog box
2int nas_bitmap_proc(int msg, DIALOG *d, int c){
3 BITMAP *wnd; //window bitmap
4 BITMAP *b = (BITMAP *)d->dp; //bitmap image
5 int x, y;
6 //Returns relative coordinates since we are not drawing to the screen, we have to get the relative coordinates of the procedure in relation to the main dialog window
7 get_rel_coords(d, &x, &y);
8 if (msg == MSG_DRAW){
9 wnd = get_window(); // Get the window bitmap
10 // Smart updating, only the bitmap portion of the screen will be updated
11 invalidate_rect(d -> x, d -> y, d -> w, d -> h);
12 // Draw our bitmap using any one of the four drawing modes
13 switch(d -> d1){
14 case NAS_BITMAP_BLIT:
15 blit(b, wnd, 0, 0, x, y, d -> w, d -> h);
16 break;
17 case NAS_BITMAP_MASKED_BLIT:
18 masked_blit(b, wnd, 0, 0, x, y, d -> w, d ->h);
19 break;
20 case NAS_BITMAP_STRETCH_BLIT:
21 stretch_blit(b, wnd, 0, 0, b -> w, b -> h, x, y, d -> w, d -> h);
22 break;
23 case NAS_BITMAP_MASKED_STRETCH_BLIT:
24 masked_stretch_blit(b, wnd, 0, 0, b -> w, b -> h, x, y, d -> w, d -> h);
25 break;
26 }
27 }
28 return D_O_K;
29}

Simple no? We don't even have to worry about handling the mouse as hiding it and such because that is handled by the dirty rectangle system.
Here's one using the memory manager:

1// Displays a progress bar
2int nas_progress_proc(int msg, DIALOG *d, int c){
3 BITMAP *wnd;
4 int *state;
5 int ret = D_O_K;
6 int x1, y1, col;
7 int (*proc)(int d2value);
8 int newpos;
9 int texty = ((d -> h - progressbar.tpadding - progressbar.epadding) >> 1) - (nasfont_text_height(AL_font, NASFONT_NORMAL) >> 1) + progressbar.tpadding;
10 int width = d -> w * (d -> d2/(float)d -> d1);
11 int x, y, w, h, cl, cr, ct, cb;
12 if (width > d -> w)
13 width = d -> w;
14 if (width < 0)
15 width = 0;
16 get_rel_coords(d, &x1, &y1);
17 // Retrieve our state variable from memory (returns NULL at first since it doesn't exist)
18 state = get_mem(d, MEM_STATES);
19 switch(msg){
20 case MSG_START:
21 // Two states:
22 // State[0] - set to mouse state
23 // State[1] - set to last clicked position
24 // Create our state memory segment to hold two integers.
25 state = (int *)create_mem(d, MEM_STATES, (int *)calloc(MEM_STATES, sizeof(int) * 2), MEM_ALLOCATED);
26 break;
27 case MSG_DRAW:
28 // Get our window again
29 wnd = get_window();
30 // Update the DRS system
31 invalidate_rect(d -> x, d -> y, d -> w, d -> h);
32 // Draw the progress box
33 drawskinnedrect(wnd, &progressbar.box[0], x1, y1, x1 + d -> w, y1 + d -> h);
34 drawskinnedrect(wnd, &progressbar.box[1], x1, y1, x1 + width, y1 + d -> h);
35 if(d -> dp){
36 if(d -> flags & D_DISABLED)
37 col = 2;
38 else
39 col = 0;
40 if(wnd -> clip){
41 cl = wnd -> cl;
42 cr = wnd -> cr;
43 ct = wnd -> ct;
44 cb = wnd -> cb;
45 }
46 else{
47 cl = ct = 0;
48 cr = wnd -> w;
49 cb = wnd -> h;
50 }
51 x = x1 + progressbar.lpadding;
52 y = y1 + progressbar.tpadding;
53 w = MIN(x1 + d -> w - progressbar.rpadding, x1 + width - progressbar.spacing) - 1;
54 h = y1 + d -> h - progressbar.epadding - 1;
55 set_clip(wnd, x, y, w, h);
56 nasfont_textout_shadow(wnd, AL_font, d -> dp, x1 + (d -> w >> 1), y1 + texty, progressbar.textcol[col], NASFONT_CENTERED);
57 set_clip(wnd, cl, ct, cr, cb);
58
59 if(d -> flags & D_DISABLED)
60 col = 2;
61 else
62 col = 1;
63 if(wnd -> clip){
64 cl = wnd -> cl;
65 cr = wnd -> cr;
66 ct = wnd -> ct;
67 cb = wnd -> cb;
68 }
69 else{
70 cl = ct = 0;
71 cr = wnd -> w;
72 cb = wnd -> h;
73 }
74 x = x1 + width - progressbar.spacing;
75 y = y1 + progressbar.tpadding;
76 w = x1 + d -> w - progressbar.rpadding - 1;
77 h = y1 + d -> h - progressbar.epadding - 1;
78 set_clip(wnd, x, y, w, h);
79 nasfont_textout_shadow(wnd, AL_font, d -> dp, x1 + (d -> w >> 1), y1 + texty, progressbar.textcol[col], NASFONT_CENTERED);
80 set_clip(wnd, cl, ct, cr, cb);
81 }
82 break;
83 case MSG_LPRESS:
84 if(!(state[0] & BUTTON_FOCUS))
85 state[0] ^= BUTTON_FOCUS;
86 break;
87 case MSG_LRELEASE:
88 if(state[0] & BUTTON_FOCUS)
89 state[0] ^= BUTTON_FOCUS;
90 break;
91 case MSG_IDLE:
92 if(!(mouse_b & 1) && (state[0] & BUTTON_FOCUS) && !(mouse_x >= d -> x && mouse_x < d -> x + d -> w && mouse_y >= d -> y && mouse_y < d -> y + d -> h))
93 state[0] ^= BUTTON_FOCUS;
94 if(d -> dp2 && (state[0] & BUTTON_FOCUS)){
95 newpos = d -> d1*((mouse_x - d -> x)/(float)d -> w);
96 if(state[1] != newpos && newpos >= 0 && newpos <= d -> x + d -> w){
97 proc = d -> dp2;
98 ret |= (*proc)(newpos);
99 nas_object_message(d, MSG_DRAW, 0);
100 state[1] = d -> d1*((mouse_x - d -> x)/(float)d -> w);
101 }
102
103 }
104 if(d -> dp3 && !(d -> flags & D_DISABLED)){
105 proc = d -> dp3;
106 ret |= (*proc)(d -> d2);
107 }
108 break;
109 case MSG_END:
110 // Destroy the memory segment
111 destroy_mem(d, MEM_STATES);
112 break;
113 }
114 return ret;
115}

This code is entirely non-blocking as well since it uses no while loops. This is because the memory manager allows me to store a little bit of extra information per procedure like the states variable. This allows me to retirive the last mouse clicked position and some other useful information like a status flag telling me that it has been clicked, or the mouse was held down outside of the procedure, etc. Because of this system I can store any type of data and retrieve it per procedure as needed.. pretty nifty right;D

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

Richard Phipps
Member #1,632
November 2001
avatar

So pretty much the same way as Allegro handles it then?

Is this really how all modern GUI's handle things?

EDIT: Saw Steve's post after this reply. Thanks Steve that looks very useful. I'm still not clear on why you need a memory manager though? Can you explain it to me a bit more? (sorry I'm a newbie on GUI stuff!)

Cheers!
Rich

X-G
Member #856
December 2000
avatar

Qt and Swing both use a signals/slots mechanism, AFAIK - someone explained this in another thread.

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

23yrold3yrold
Member #1,134
March 2001
avatar

I wrote a GUI (remember that ugly horror? ;D). If I were going to write an Allegro GUI, I'd probably ape the crap out of Win32 since it's all I know. Are any of the Allegro GUI's at a good useable state? I keep forgetting; there's too many :P

--
Software Development == Church Development
Step 1. Build it.
Step 2. Pray.

Steve Terry
Member #1,989
March 2002
avatar

The memory manager is not really needed as you could store teh extra information in any one of the free dp* fields, this however would clutter up the GUI since you'd have to create structures or something to hold multiple types of data in one pointer. This manager allows flexibility without using any dp* fields. In fact I could rewrite the DIALOG structure to not have any dp fields at all and just call helper functions to set callbacks, etc. because those procs could retirieve that information from the memory banks. It just makes the API a lot prettier ;D
It's also a very simple manager, three basic commands, create, get, destroy...

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

spellcaster
Member #1,493
September 2001
avatar

Signal / Slot is also calles Listener and/or Subscriber.
It works like this: You're telling the Widget what events you're interested in, and pass an instance of yourself. Whenever the event occurs, you will be called.

Edit:

Quote:

So pretty much the same way as Allegro handles it then?

Um no.
Allegro has no concept of containers. There's no z-order as well. Allegro does not abstract the graphics context. And allegro is blocking.

--
There are no stupid questions, but there are a lot of inquisitive idiots.

Steve Terry
Member #1,989
March 2002
avatar

My GUI lacks z-ordering but as soon as I impliment multiple dialogs that will be there. So far I've managed to abstract the graphics context and made non-blocking procedures, not so sure about containers or how I could do that...

[edit]
Richard.. if it makes more sense I need the memory manager to "remember" stuff outside of the procedure. If I made it a local variable then the procedure would "forget" what it was set to each time it returned. This way I can have separate unique variables for each procedure. It also comes in handy for the dialogs because get_window uses the memory manager to get the window bitmap from the main dialog procedure. Sorta like shared memory, all the procedures under that dialog have access to the main dialog's nas_dialog_proc window.
[/edit]

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

Peter Wang
Member #23
April 2000

Attaching on to spellcaster's post:

Note the distinction between Events and Signals. Events are from the event manager to the widgets (e.g. "repaint yourself", "the user pushed the left mouse button"). Signals are from the widget to the application (e.g. "hey, I got clicked, time for you to do something useful").

This is the terminology used by GTK. Other toolkits [should] have the same separation, if under different names. Otherwise, you end up with a mess like the Allegro GUI.

Andrei Ellman
Member #3,434
April 2003

I've noticed several people (including myself) are working on their own GUIs instead of using a pre-existing GUI. Is this because they feel more comfortable using a GUI they completely understand rather than an off-the-shelf GUI, or do they simply find Allegro's GUI to be a bit limited, or do they want to have complete control of it and customise it any way they want? I chose to write my own GUI in my own games for a mixture of these three reasons.

AE.

--
Don't let the illegitimates turn you into carbon.

IronBob
Member #3,248
February 2003
avatar

not only is allegro's gui limiting. but its down-right ugly.

Steve Terry
Member #1,989
March 2002
avatar

Which reminds me, an Allegro theme for my GUI would be neat for those who want some nastalgia. Hmm completely black and white buttons and dialogs... fun fun;D

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

X-G
Member #856
December 2000
avatar

You should call it GEM though, because that's what Allegro is supposed to look like, afaik. :P

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

Richard Phipps
Member #1,632
November 2001
avatar

Well, while working on your own GUI is a lot of work I think it's something that I may have to do at some point. However I really should look at the GUI Libs people have done using Allegro and see if I can adapt them.. (Those that are open source of course).

So that's why I was interested to seeing the different design approaches GUI's use. :)

EDIT: GEM (or Graphical Enviroment Manager) was part of the GUI system used on the Atari ST. Pretty soon it looked horribly dated, but that black and white style looks quite retro and attractive recently! :D

miran
Member #2,407
June 2002

I would just like to add that I based my GUI on a library called DeGUI which was basically a C++ wrapper for the Allegro GUI (that was in the times of Allegro 3.12). The author of DeGUI abandoned it and I picked up on it improving it and adding extra functionality (support for skins etc.) While it's still incomplete and a bit on the buggy side, it does support more or less everything spellcaster said up there except for that z sorting thingy which I don't even know what it is. Or if it is what I think it might be then I have that too to some extent.

Anyway, while working on the lib I discovered it's NOT a good idea to make a GUI based on the Allegro GUI code. You will be much better off writing it completely from scratch using spellcaster's code as a starting point. If you decide to make your own GUI I suggest you study a good C++ book very carefully before you start. I was almost a complete newbie in C++ a year ago and that shows in my code. Since then I learned a lot and a while ago I was even tempted to rewrite my lib from scratch too but then I realized it would be too much work for too little benefit and I just quit. I am planning to make major improvements to the lib in the summer though (summer starts in july here)...

--
sig used to be here

Richard Phipps
Member #1,632
November 2001
avatar

Hmmm.. The problem here is that I don't know C++ (I did try to learn). I know C++ would be a better choice for doing a GUI, but for some reason I just don't like it.

I have done some tinkering with the Allegro GUI routines for the interface in Stylepaint. But I find some aspects of it very restricitng.

Oh, I thought that LexGui was based around simply adding skin support to the Allegro GUI. Or have I done Lenny an injustice? ???

miran
Member #2,407
June 2002

Yeah, LexGUI is just an extension of the default Allegro GUI (as far as I can see all the widgets are "derived" from the default ones and they only "overload" the MSG_DRAW message)...

--
sig used to be here

gillius
Member #119
April 2000

I've used a lot of Java Swing and only a very brief peppering of Win32 code, but I do know that I love Java Swing. Looking at spellcaster's code that's a really good way to do it, so listen to spellcaster ;). Signal / slots is awesome, although I've never heard that term except on this fourm. I thought the Java Swing followed MVC (Model-View-Controller). But its event system is subscription based and I love it. I like those concepts so much that I followed them very greatly when I designed my GNE library.

I think GUIs is the the best or very close to the best example case for object-oriented programming and one of the best cases for mutli-threaded programming. GUIs are very suited for a lot of really cool programming concepts that I like ;).

Gillius
Gillius's Programming -- https://gillius.org/

Richard Phipps
Member #1,632
November 2001
avatar

Lenny, apparantly I've got to listen to you.. ;)

Using plain C how would you do a GUI system? Or would you simply modify allegro's? (That question is for everyone else too..)

;D

miran
Member #2,407
June 2002

As I already said, I don't suggest just modifying the Allegro GUI. You'll get results sooner that way but in the long run you'll regret it. If you do want to go down that road then why don't you modify LexGUI instead? Spellcaster obviously stopped working on it a long time ago...

--
sig used to be here



Go to: