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!
vpenquerch
Member #233
April 2000

Do not plan to abandon yours and join the common cause.
Do take note of the comments, but do not react on them. Let them work their way into your mind, and come back to them in a week, without the anger your feel now. You can't properly assess all what's been said here, now. And if you abandon yours to join one that's starting up now, it will most probably die out after a week of starting frenzy.
You'll need a load of will to do that though. It's
so easy to get disenheartened by the bad comments,
or those seen as bad. Which is why you should cool
off, then come back to the archive.
But, do not abandon yours.

EDIT:
I've downloaded the source and had a look:

Some comments on the source. Well, part of it (the event.h file and
the first half of widget.h).
I do realize you've just begun, but anyway, here goes:

don't assume left/middle/right buttons: make the button the message
applies to an argument of the message, like you do for keys (eg, you don't
have EVENT_KEY_A_UP, etc).

EVENT_LEFT_DROP:
eh ? you're assuming semantics here. Leave that to another higher level
layer of your lib only.

I'd suggest adding a type for your timestamps, not just int. It can prove
quite helpful down the road.

Also, add a type for geometry (eg, Maybe a vec2, which is two coords,
and Area, which is a couple of vec2s). This avoids passing 4 ints or
int*s to loads of routines and back.

Don't put everything and the rest in the event struct. It's supposed to
be akin to a virtual base class, in the ideal vision. X-Window, for one,
casts the rest of the event struct to a message specific struct, which
is not so pretty, but which conveys nicely the fact that the base event
is not dependant on whatever actual events you have. You can then add
events as you like without modifying the event struct.

I'd remove the routines from the event struct, and move them into a new
class, say, EventModifier, or something similar: you'd have a list of
them, which could filter the events as they're generated. You'd have
things like "LeftHandedMouseEventFilter", etc.

Have an EventQueue rather than statics in event to deal with the list.
This will help you have several possible policies for dealing with
those.

> Widget *root() const;

That should be static, I assume it's a typo.

Same for Widgets as for events, don't have too many statics, prefer a
manager instead, this yields more power to your API, due to possible
overriding.

Sometimes a pain, but, when it makes sense, add two versions of your
accessors, eg:

Widget *widgetAtXY(int,int);
const Widget *widgetAtXY(int,int) const;

///removes and destroys all children widgets
void destroyChildren()
{
while (_last) delete _last;
}

SIGSEGV

Good luck, and do not abandon it if you think it
could be a good thing to do for you and for others
to have.

axilmar
Member #1,204
April 2001

Vincent, thanks for having a look at my code. Your comments are greatly appreciated.

Let me ask you a few questions and comment on your proposals.

Quote:

don't assume left/middle/right buttons: make the button the message
applies to an argument of the message, like you do for keys (eg, you don't
have EVENT_KEY_A_UP, etc).

EVENT_LEFT_DROP:
eh ? you're assuming semantics here. Leave that to another higher level
layer of your lib only.

In other words, replace

EVENT_LEFT_BUTTON_DOWN, EVENT_RIGHT_BUTTON_DOWN, EVENT_MIDDLE_BUTTON_DOWN, EVENT_LEFT_BUTTON_UP, EVENT_RIGHT_BUTTON_UP, EVENT_MIDDLE_BUTTON_UP

with

EVENT_BUTTON_DOWN, EVENT_BUTTON_UP

so as that the class itself can choose what to do e ?

Something like this:

bool buttonDown(const Event &event)
{
    switch (event.button()) {
        case LeftButton:
            <bla bla>...
        case RightButton:
            <bla bla>...
        case MiddleButton:
            <bla bla>...
    }
}

Right now, if the buttons are reversed, the rightButtonDown and rightButtonUp will be called if the left button is pressed.

What are the advantages of the implementation you are proposing ?

Quote:

I'd suggest adding a type for your timestamps, not just int. It can prove
quite helpful down the road.

I agree. How shall the type should be declared ? should it be global or part of the Event namespace ?

Quote:

Also, add a type for geometry (eg, Maybe a vec2, which is two coords,
and Area, which is a couple of vec2s). This avoids passing 4 ints or
int*s to loads of routines and back

Ok, the Rect class will be put back. I was hoping that noone will notice this!!! I did it to save the library from one more class, but anyway it was a not so clever thought from the start. A Rect class is useful anyway.

Quote:

Don't put everything and the rest in the event struct. It's supposed to
be akin to a virtual base class, in the ideal vision. X-Window, for one,
casts the rest of the event struct to a message specific struct, which
is not so pretty, but which conveys nicely the fact that the base event
is not dependant on whatever actual events you have. You can then add
events as you like without modifying the event struct

I wasn't planning for an event queue that custom events would go into. My definition of an event is either a mouse, a keyboard or a timer event. I.E. something coming from the hardware input devices only.

The way it is designed, it is impossible to do it, because the Event class is simply a place holder for the event data. If you check the Event internals, you will see that everything is in plain C; there is a reason for this: I don't want to mix event callbacks that might be called from interrupts with C++ (for example, I can't lock STL containers with LOCK_VARIABLE).

Another reason that I don't want Event to be a virtual base class is because if I did so, then the library would constantly allocate and deallocate Event-derived objects. Which means memory fragmentation. I don't think it is necessary to fragment the memory so much just to provide the programmer a feature to put his/her own events in the event queue.

In case you know from other libs (Qt for example) that custom events are useful in a multithreaded application, I have other solutions for thread communication (which would be a different library from a GUI anyway).

Quote:

Have an EventQueue rather than statics in event to deal with the list.
This will help you have several possible policies for dealing with
those.

I would like only one event queue. If there was an event queue class, the programmer would be misled that each time he/she wanted to make an event loop, he/she has to declare an EventQueue (assumingly with different contents). By not having an EventQueue class, it is implicitely understood that all events come from the same queue.

To tell you the truth, I don't really see an advantage in what you are proposing. Would you care to elaborate with a few examples ?

Quote:

> Widget *root() const;

That should be static, I assume it's a typo.

No, not at all. The static method is:

Widget *Widget::rootWidget();

which returns the current root (or desktop) widget.

The plain 'root()' method returns the Widget that is root of the widget tree that is called from, which might not be currently on the screen.

Quote:

Same for Widgets as for events, don't have too many statics, prefer a
manager instead, this yields more power to your API, due to possible
overriding.

I tried that, but it did not look good enough to me. At one iteration of my design, I thought "let's make nothing static". The result was that I had to declare lots of "manager" type classes that holded the state of a gui, without significant gain: after all, there is only one screen, one widget tree active at a time, one mouse, one event queue...and this is unlikely to change in the near future. (Even if two or more screens where present, that would mean a "list" of root widgets, but not more than one event queues, not more than one case of drag-n-drop, etc).

And then there is another problem : how to retrieve the current 'static' information from inside the widget, without the widget knowing the "manager" variable that holds the current state. I would have to have statics, it can't be avoided.

So, instead of:

1class GuiStateManager {
2public:
3 Widget *rootWidget();
4 
5 static GuiStateManager * currentGuiStateManager();
6};
7 
8class Button {
9public:
10 void popupMenu() {
11 currentGuiStateManager()->rootWidget()->add(popupMenu);
12 }
13};
14 
15main()
16{
17 GuiStateManager gui1;
18 EventQueue queue1;
19 Event event;
20 
21 while (app_loop) {
22 if (queue1.get(event)) event.dispatch();
23 }
24}

I go for :

1class Button {
2public:
3 void popupMenu() {
4 Widget::rootWidget()->add(popupMenu);
5 }
6};
7 
8main()
9{
10 Event event;
11 
12 while (app_loop) {
13 if (Event.get()) event.dispatch();
14 }
15}

Which is much simpler and does the same job.

My line of thought is "keep it simple", simple enough to satisfy 90% of the user requirements. There is fine balance between simplicity and bloatness. I don't want my library to cross that limit. In other words, I don't want to put features that would be useful only in 10% of the use cases!!! the other 90% will blame me for not having followed the easiest way to do the job...

vpenquerch
Member #233
April 2000

About events and mouse buttons: yes, that's what I meant.

> What are the advantages of the implementation you are proposing ?

Extensibility: when you decide to supprt a fourth button, you do
not have to add new event types; users who do not care which
button is pressed have their code still work; various things
like that.

> I agree. How shall the type should be declared ? should it be global
> or part of the Event namespace ?

Depends on whether you want it to be event specific or not. I'd
put it as a global one (or add an algui namespace on top of the whole
thing).

> My definition of an event is either a mouse, a keyboard or a timer event.
> .E. something coming from the hardware input devices only.

You don't cater for, say, tilt information from tablets. Even if you
don't plan to add more, it's often wise to leave you some space to do,
if you change your mind.
Another example: joystick: they would probably send other messages.
Allegro currently solves the problem by generating fake keyboard events
to replace joystick movement - not pretty.

> I can't lock STL containers with LOCK_VARIABLE

Interesting, I hadn't thought of that at all.
Could you keep a small core in C to do that, then have the C++ part that
checks for events do the conversion, since it would be out of interrupt
context ? That would solve the LOCK_VARIABLE problem while leaving you
able to use C++ fully.

> Another reason that I don't want Event to be a virtual base class

It was only an image - but the memory problem you mention would apply
too. A possible way to fix this is to have a given event log size. Say,
64 KB. You allocate this much, and use it as a rotating buffer (there
is a word for it, but I can't remember it right now: everytime you add
an event, you use the memory after the last event you created, and when
you get to the end of the buffer, you start again at the beginning of
the memory block, erasing what was here already. If events here have
not been used yet, you can either erase them (events too old are just
discarded) or block any new events from being registered until these
old events are read.
That's just one of the possibilitiesa nyway.

> In case you know from other libs (Qt for example)

Sorry, I don't. I'm just talking out of common sense and experience
with other programs :)

> I would like only one event queue.

My point was more with the ability to control how it behaves. It can
be a class and be a singleton, for isntance.

> If there was an event queue class, the programmer would be misled
> that each time he/she wanted to make an event loop, he/she has to
> declare an EventQueue

No, that is user error, and an easy one (assuming it is documented).
Do not deny yourself the ability to use something useful on the fears
(often without grounds) that someone could maybe misuse it. If you
fear that, state it in the docs. Add a FAQ. Add a comment in the header.
Better, try to catch the error at compile time, or, if not possible,
at creation time.

> To tell you the truth, I don't really see an advantage in what you
> are proposing. Would you care to elaborate with a few examples ?

Here's one:

I want my event queue to transform all occurences of right click and
left click occuring at the same time (or narrowly) into a middle click,
like was often done when PC mice had only two buttons and Sun stations'
ones had three (Ah, we had optical mice years ago, before MS decided
that yes, it was a good thing...)
Anyway.

I see your point in making the lib simple though. My comments would
aim to make it more powerful, but at a price of complexity. So feel free
to disregard.

Keep it up :)

axilmar
Member #1,204
April 2001

Thanks Vincent, this is the type of discussion I expect on these boards.

Quote:

Extensibility: when you decide to supprt a fourth button, you do
not have to add new event types; users who do not care which
button is pressed have their code still work; various things
like that.

Ok, proposition accepted.

Quote:

Depends on whether you want it to be event specific or not. I'd
put it as a global one (or add an algui namespace on top of the whole
thing).

In the end, maybe I put a namespace to the library. The time type would be a global type.

Quote:

You don't cater for, say, tilt information from tablets. Even if you
don't plan to add more, it's often wise to leave you some space to do,
if you change your mind.

Maybe in the end I'll change the event internals, if it is requested by many. Right now, I don't think that anyone will care.

Quote:

Another example: joystick: they would probably send other messages.
Allegro currently solves the problem by generating fake keyboard events
to replace joystick movement - not pretty

Joystick to move the mouse around ? I don't see a reason for this. There is no computer without a mouse these days. Again, this will be implemented only if requested by many users.

Quote:

It was only an image - but the memory problem you mention would apply
too. A possible way to fix this is to have a given event log size. Say,
64 KB. You allocate this much, and use it as a rotating buffer (there
is a word for it, but I can't remember it right now: everytime you add
an event, you use the memory after the last event you created, and when
you get to the end of the buffer, you start again at the beginning of
the memory block, erasing what was here already. If events here have
not been used yet, you can either erase them (events too old are just
discarded) or block any new events from being registered until these
old events are read.

Right now I am using a simple _EVENT struct to create a rotating buffer (struct _QUEUE). Very simple. If I add internal memory management on event allocation, I will make it very complex. I would like to avoid complexity.

Quote:

Sorry, I don't. I'm just talking out of common sense and experience
with other programs

If I was to make a high level generic GUI toolkit, then I would definitely incorporate your proposals. Right now, I am thinking along the lines "a simple GUI library". Since I am solo, I don't have the time and appetite to add more functionality. I hope you understand this.

Quote:

I want my event queue to transform all occurences of right click and
left click occuring at the same time (or narrowly) into a middle click,
like was often done when PC mice had only two buttons and Sun stations'
ones had three (Ah, we had optical mice years ago, before MS decided
that yes, it was a good thing...)

This can be easily done from inside the mouse_callback. Doesn't Allegro offer the capability to emulate a 3 button mouse anyway ?

Quote:

I see your point in making the lib simple though. My comments would
aim to make it more powerful, but at a price of complexity. So feel free
to disregard.

Thank you. Really. A GUI lib is quite a complex thing, and I am alone. I have given much thought into many topics, but I can't handle it all alone. That's why I aim for simplicity and sacrifice a few complex functionalities.

Do you have any proposition for the widget set ? which widgets would you like to see ?

vpenquerch
Member #233
April 2000

> Joystick to move the mouse around ? I don't see a reason for this.

No, this is just an example. The main point being,
if you ever need to add new messages, you want to
make this as easy and non intrusive as possible.

> This can be easily done from inside the
> mouse_callback. Doesn't Allegro offer the
> capability to emulate a 3 button mouse anyway ?

Yes, but, once again, it's an example to show that
you can do many things if you design an extensible
base - you don't have to have a large base, just
one that can be built upon.
Virtual functions help. And especially modifier
objects which can be attached to something.
You don't have to add plenty of things to cater
for all needs, just the ability to possibly add
things to cater for this or that.
Just putting my point forward, I do understand
that you're aiming at another level, not as
complex as what I'm describing.

axilmar
Member #1,204
April 2001

Yes, that's my purpose. If something comes up, I will modify the library, but for now, "keep it simple!!!"...as simple as it gets!!!

Do you have a list of widgets that you would like to see implemented ?

EDIT:

Here is my list:

static widgets(widgets that don't respond to user input):
--------------
Label (for text)
Image (for bitmaps)
Separator (either horizontal or vertical)
Frame/Panel (either raised, sunken or flat)

buttons:
-------
PushButton
ToggleButton
CheckBox
RadioButton
ButtonFrame
ToolPushButton
ToolToggleButton
ToolCheckButton
ToolRadioButton

scrolling:
---------
Slider (horizontal or vertical)
ScrollBar (horizontal or vertical)

Menus:
------
MenuBar
PopupMenu
MenuBarItem
ActionMenuItem
CheckMenuItem
RadioMenuItem
SubmenuItem

Text editing:
-------------
TextBox (single line)
TextEditor (multi line)
SpinBox
ComboBox

views:
------
ListView (a.k.a. list box)
TreeView
TableView
HtmlView
IconView (for file dialogs)

Windowing:
----------
WindowFrame (caption, min/max buttons etc)
Dialog (A window frame that knows how to do a modal loop)

Framework:
----------
ToolBar (a window frame that positions its children according to selected docking)
Workspace (central widget for the main window)
MainWindow (a window frame that knows how to dock children toolbars)

Common dialogs:
---------------
MessageBox
FileDialog
PaletteDialog
FontDialog

Please note that widget names are not definitive: if someone proposes something better, it will be accepted. I tried to keep the names as close to the common terminology as possible.

The programming style would be very simple: just put one widget inside the either from parent to child, connect signals and slots and make an event loop. Example:

1#include <algui.h>
2 
3 
4//called to create a new document
5void onNewDocument()
6{
7}
8 
9 
10//called to open a document
11void onOpenDocument()
12{
13 FileDialog dlg;
14 
15 dlg.setCaption("Open file");
16 dlg.addFileType("exe", "Executable");
17 dlg.addFileType("lib", "Library");
18 dlg.addFileType("ico", "Icons");
19 if (!dlg.exec()) return;
20 <bla bla open file>
21}
22 
23 
24//main
25int main()
26{
27 //install Allegro
28 <bla bla bla>
29 
30 //load configuration
31 <bla bla bla>
32 
33 //set video mode
34 <bla bla bla>
35 
36 //load resources
37 <bla bla bla>
38 
39 //create the main window
40 MainWindow *mainWindow = new MainWindow(SCREEN_W, SCREEN_H, "My Application");
41 
42 //create the menu bar
43 MenuBar *menuBar = new MenuBar(mainWindow);
44 MenuBarItem *fileItem = new MenuBarItem(menuBar, "&File");
45 MenuBarItem *editItem = new MenuBarItem(menuBar, "&Edit");
46 MenuBarItem *viewItem = new MenuBarItem(menuBar, "&View");
47 MenuBarItem *optionsItem = new MenuBarItem(menuBar, "&Options");
48 
49 //create the file menu
50 PopupMenu *fileMenu = new PopupMenu(fileItem);
51 ActionMenuItem *newItem = new ActionMenuItem(fileMenu, "&New", "Ctrl+N", new FunctionSlot<>(onNewDocument));
52 ActionMenuItem *openItem = new ActionMenuItem(fileMenu, "&Open...", "Ctrl+O", new FunctionSlot<>(onOpenDocument));
53 
54 <bla bla create the rest of the menu items>
55 
56 //create the toolbar
57 ToolBar *standardToolBar = new ToolBar(mainWindow, "ToolBar");
58 ToolButton *newButton = new ToolButton(standardToolBar, (BITMAP *)resources[DAT_NEW_BITMAP].dat, new FunctionSlot<>(onNewDocument));
59 
60 <bla bla create the rest of the toolbars and toolbar items>
61 
62 //main loop
63 Event event;
64 while (app_loop) {
65 if (event.get()) event.dispatch();
66 }
67 
68 return 0;
69}

vpenquerch
Member #233
April 2000

Not particularly.
Though you'd better make your GUI independant of
the actual widgets - widgets use the GUI, so the
core can be coded without knowing the widgets.
After, you can add widgets, or maybe another lib
which adds widgets. So you can have a plethora of
widgets to choose from, without bloat. Of course,
some widgets will require other widgets (eg, a
button will require a label, which will in turn
require a base widget).
Do the widgets you like first, I'd say :)

spellcaster
Member #1,493
September 2001
avatar

Regarding labels: They do get userinput. One of the main features of a label is to provide a shortcut key for the widget it's associated to. So a label would react on the keypress by focusing / selecting the widget it's associated to.

I'd also suggest to avoid a single root widget. If you're using c++ use a list (or any other container) as root. If you just have one root widget it doesn't hurt, and it allows you to have several widgets which don't have a common parent.

If you decide to stick with your single root widget solution, don't make it a global. Create a class and/or struct to contain your global information, so the complete state of the gui is inside a single object. This allows the user to use several instances of the gui.

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

axilmar
Member #1,204
April 2001

Quote:

Not particularly.
Though you'd better make your GUI independant of
the actual widgets

Do you mean to have two libraries ? one for the 'window system' and one for the 'widgets' ? I tried that also. I tried to make a widget set using MxWindows as the gui. It works, but the actual window system is a tiny fraction of the toolkit, so, for simplicity reasons, I thought they should be intergrated. Just "#include <algui.h>", link with "algui.lib.so.whatever" and be done with it.

Quote:

Regarding labels: They do get userinput. One of the main features of a label is to provide a shortcut key for the widget it's associated to. So a label would react on the keypress by focusing / selecting the widget it's associated to

Gotcha. This will be provided. Widget categorization above was for just showing what I have in my mind.

Quote:

I'd also suggest to avoid a single root widget. If you're using c++ use a list (or any other container) as root. If you just have one root widget it doesn't hurt, and it allows you to have several widgets which don't have a common parent.

If you decide to stick with your single root widget solution, don't make it a global. Create a class and/or struct to contain your global information, so the complete state of the gui is inside a single object. This allows the user to use several instances of the gui

AlGui does have one "root" widget, i.e. only one root widget is currently on the screen. But widget trees can happily live off the screen, until they are added to a widget that is on-screen or be made "root" with a call to 'setRoot()'.

Here is the code:

//returns the current "root" widget, i.e. the root widget that is on the screen
static Widget *Widget::rootWidget();

//returns the root widget of this tree
Widget *Widget::root() const;

//sets the current root widget
bool Widget::setRoot();

If the user wants to make a multiscreen application, he/she can call 'setRoot' to set a widget as the current root. He/she can construct multiple widget trees, each one with their own root (the local tree's root), and activate each widget tree by calling 'setRoot'.

EDIT:

To all: do you find the above widget list adequate ? any other widget suggestions ?

vpenquerch
Member #233
April 2000

I find the above widget list comprehensive.
It seems like you're going for a large set of
widgets, which encompass a wide array of functionalities (eg, an HTML renderer is not a
trivial thing to do). So it does not seem to fit
with your stated goal of "a simple GUI for simple
uses".
That said, I can't think of other non specialized
widgets for you to add to the list.

spellcaster
Member #1,493
September 2001
avatar

Quote:

AlGui does have one "root" widget, i.e. only one root widget is currently on the screen. But widget trees can happily live off the screen, until they are added to a widget that is on-screen or be made "root" with a call to 'setRoot()'.

Assume you hae a game.
Assume you want to use some gui components for the game.
Assume the GUI should run with a lower priority than the renderer (say with 20fps instead of 60fps)
Assume you want to have several gui components on screen which are not connected (say a map width startegic control buttons / list, an area with messages, and one area with unit control buttons / infos).

The gui should be displayed on top of the game running at 60 fps, but it should render itself / react to events only with 20fps.

If you have several top level widgets, no problem. But how do you handle this with a single top level widget?

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

axilmar
Member #1,204
April 2001

Quote:

(eg, an HTML renderer is not a
trivial thing to do). So it does not seem to fit
with your stated goal of "a simple GUI for simple
uses".

It will only display HTML 1.0 and only some basic tags. It is necessary if an application must show help files. I have already done some of the work in past projects.

Actually, it is not very difficult to make an HTML renderer. Basically, each HTML renderer widget is a giant "list box". Each row of the list box can contain text, graphics, widgets, etc. You can fill this special "list box" from the HTML stream, as the stream is retrieved from the disk/network. Each row's size will be modified according to the size of the elements of the received data on the fly.

Quote:

Assume you hae a game.
Assume you want to use some gui components for the game.
Assume the GUI should run with a lower priority than the renderer (say with 20fps instead of 60fps)
Assume you want to have several gui components on screen which are not connected (say a map width startegic control buttons / list, an area with messages, and one area with unit control buttons / infos).

The gui should be displayed on top of the game running at 60 fps, but it should render itself / react to events only with 20fps.

If you have several top level widgets, no problem. But how do you handle this with a single top level widget?

You make a root widget that is the container of all these unconnected widgets. This root widget will draw nothing, it will be positioned at the screen 0, 0 pixel and it will have the size of the screen; its children, the widgets you are interested in, will have a x, y position relative to the root equal their screen position, since their parent is at screen 0, 0.

In other words:

1class GameRootWidget : public Widget {
2public:
3protected:
4 //draw nothing
5 virtual void paint() {
6 }
7};
8 
9 
10int main()
11{
12 <bla bla initialize Allegro>
13 
14 //make the root widgets
15 GameRootWidget *wgt = new GameRootWidget();
16 wgt->setGeometry(0, 0, SCREEN_W, SCREEN_H);
17 
18 //make the widgets you are interested in
19 Button *btn1 = new Button(root, 0, 0, 32, 32);
20 Button *btn2 = new Button(root, 32, 0, 32, 32);
21}

vpenquerch
Member #233
April 2000

> Actually, it is not very difficult to make an
> HTML renderer.

It's hard to make a good one :)
If you're going for HTML 1.0, then it should be
OK, as you don't have tables. For the rest, you're
right that it's just mostly a collection of lines
with adjustable height, but there are lots of small
things, easy by themselves, but there are a lot of
them. Things like lists, blockquote spacing, etc.
You end up having a quite large "state" stack. At
least, I ended up that way.

axilmar
Member #1,204
April 2001

Quote:

It's hard to make a good one

It would be good to have one, though. For showing help files.

The stack can't be avoided.



Go to: