Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Questions about GUI design

This thread is locked; no one can reply to it. rss feed Print
Questions about GUI design
Thomas Fjellstrom
Member #476
June 2000
avatar

bamccaig said:

This is all I was referring to. If the abstraction layer needs to change in order to accommodate the other backends then it's not really an abstraction layer.

You have to start somewhere. Most people aren't going to sit down and look at all of the possible backends and design the ONE TRUE API that fits all of them. Most start with one or two, and will refactor as needed.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

LennyLen
Member #5,313
December 2004
avatar

bamccaig said:

It's a redundant layer (i.e., it doesn't serve a purpose yet, and will need to change before it could).

By that definition, all unfinished code is redundant. Edgar's abstraction layer is simply incomplete.

axilmar
Member #1,204
April 2001

bamccaig said:

so that pretty much makes the current abstraction layer not really an abstraction at all, but a redundant layer.

This is all I was referring to. If the abstraction layer needs to change in order to accommodate the other backends then it's not really an abstraction layer. It's a redundant layer (i.e., it doesn't serve a purpose yet, and will need to change before it could). Not trying to insult your library which I know nothing about (I admire your aspirations and efforts).

Exactly.

You have to start somewhere. Most people aren't going to sit down and look at all of the possible backends and design the ONE TRUE API that fits all of them. Most start with one or two, and will refactor as needed.

By that definition, all unfinished code is redundant. Edgar's abstraction layer is simply incomplete.

Refactoring is one thing, throwing away the code and starting over is another.

Why don't you people go review Edgar's code? you will understand what I and bamccaig is saying. Edgar has also incorporated Allegro event sources to his library. We are talking about design 1:1 to Allegro.

Come on people, let's quickly finish this round of discussion. Edgar's code may contain some good pieces, we may reuse them.

Why don't we open a design document for our GUI?

I've started one:

https://docs.google.com/document/d/1_epQPxQpJjUosAgUfKW3kGW9x5HBjuAlvmbmD7L3CYQ/edit?usp=sharing

jmasterx
Member #11,410
October 2009

You may also want to abstract away cursors. For example, Agui sets the cursor when mouse enters textbox. Possibly also transformations. Timers can be useful too but may be harder to abstract.

Transformations are useful if say, the native buffer size is 640x480 and the user goes fullscreen with that bitmap. You want the mouse to still correctly detect widgets.

Supporting UTF-8 is nice too.

For features, it could have all 3 messaging styles like QT:
-Messages
-Listeners
-Signal Slot

How you handle skinning is important too.

It would be nice if:
In the simple case, users can just supply bitmaps for each Widget (or one big one like GWEN allows). But in the more advanced case, the user should be able to override how a Widget both: Paints its background and its component.

When making Agui I realized the importance of having both.

This is where the idea of margins came into play.

So for skinning, all rendering could be done possibly using the Visitor design pattern http://en.wikipedia.org/wiki/Visitor_pattern or by creating a class with virtual methods: buttonPaintBackground(Graphics& g) ... etc.

This way the user can skin without subclassing all the widgets (One of the pitfalls of Agui).

Another interesting problem you need to think about is:

How do events bubble up. This is sort of another pitfall in Agui.

The event in Agui goes straight to the designated widget and all attached listeners.

But what happens if you have a TextBox with a scrollbar inside. The textbox needs to scroll itself when the scrollbar is scrolled. The only somewhat ugly way to do this in Agui is for the textbox to internally attach a listener to its scrollbar.(The even better way is to put it in a scroll panel).

There are a few better ways to handle this. Some GUIs send the event to the parent first, then if the parent is not interested, it is sent to the widget itself. Other guis will use the chain pattern:
http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern

You will need to decide what is best and how listeners should consume events, etc.

Where this went most horribly wrong in Agui is my ScrollPanel widget. It attaches all kinds of listeners to its children to detect when to show / hide the scrollbars.

axilmar
Member #1,204
April 2001

Added mouse cursors to the list of things to abstract.

For the rest you mention, I updated the document.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

axilmar said:

Why don't you people go review Edgar's code? you will understand what I and bamccaig is saying. Edgar has also incorporated Allegro event sources to his library. We are talking about design 1:1 to Allegro.

What are you talking about? Event sources copied 1:1? Here is my EagleEventSource header :

#SelectExpand
1 2class EagleEventSource { 3 4private : 5 std::vector<EagleEventListener*> listeners; 6 7 bool OnList(EagleEventListener* l); 8 9public : 10 EagleEventSource() : listeners() {} 11 virtual ~EagleEventSource(); 12 13 void EmitEvent(EagleEvent e); 14 15 void SubscribeListener(EagleEventListener* l); 16 void UnsubscribeListener(EagleEventListener* l); 17 bool HasListeners() {return !listeners.empty();} 18 19 virtual void ReadEvents()=0;/// TODO : Has this been implemented in any subclasses? What was it for again? Polling? 20};

That is certainly not a 1:1 copy of anything.

And if you're talking about using ALLEGRO_EVENT_QUEUE's inside the Allegro5EventHandler, why not? It's an a5 driver, it should be using a5 functionality. I used the same names and values for events to make it easier to port from a5 to my library. All they have to do is rename their events to EAGLE_ instead of ALLEGRO_. And the id values are the same so I can just perform a simple assignment of the variable value instead of having to map to different values. :/ That is pretty much the only thing that is the same about my library and allegro 5. If I didn't use ALLEGRO_EVENT_QUEUE's internally I would have had to implement a separate thread for every input source instead of one thread per event handler. And I would have had to implement some version of locking and sleeping using pthreads on top of it then.

I attached both Events.hpp and Allegro5EventHandler.hpp. Look for yourself and see.

axilmar
Member #1,204
April 2001

That is certainly not a 1:1 copy of anything.

The idea of having an event source is a copy of Allegro's relevant idea. I didn't say you copied the implementation.

Let's stop with this nonsense and collaborate on a GUI. Did you read the design document I started? you can edit it if you wish.

We can bring anything we need from EagleGUI into the new GUI, if it's good and available.

jmasterx
Member #11,410
October 2009

The document looks good. However, I have an alternate suggestion for skinning. You seem to imply that the Widget class has a virtual setSkin. This means the user needs to subclass the Skin class for each widget they want to custom render since it is per-Widget. Instead, it might be better if you have setSkin in your Gui class of sorts. This way calling gui.render() will actually do:

for each widget
widget.paintBackground(skin,g);
widget.paintComponent(skin,g);
...
Widget::paintBackground(Skin s, Graphics g)
{
   s.paintBackground(this,g);
}

This sort of thing is possible with the visitor pattern and double dispatch.

A user should be able to subclass Skin and custom paint any and all widgets without having to subclass the skin class for each widget.

Your way may be able to do this and I may have misunderstood. I apologize if I did. But one of the caveats of Agui is having to subclass all Widgets just to paint them in a custom way. It would be cool to have something more like Java Swing.

axilmar
Member #1,204
April 2001

jmasterx said:

This means the user needs to subclass the Skin class

No, the Skin class would not need to be subclassed. It's a resource manager. Each widget will know how to get resources from it and apply it to its painting algorithm.

Pho75_
Member #12,377
November 2010

Design doc looks like a great start.

Requested Additions:

-drag and drop.
could we also support DnD across different ALLEGRO_DISPLAY's?

-a default event loop to give control to GUI

-input injection methods for all input devices.
for people who want to manage input themselves

-gesture support
phase #1: as supported by platform

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

axilmar
Member #1,204
April 2001

I have added drag and drop and gestures.

Edgar, are you in?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

You put your left foot in, you put your right foot out, you do the hokey pokey and you shake your self about. That's what it's all about.

Just so you know, my library already meets about half to two thirds of those elements listed on your design doc...

IDK... Can we co-develop? Ie. contribute to both code bases at once maybe at times? I don't really want to abandon EAGLE... :(

jmasterx
Member #11,410
October 2009

axilmar said:

No, the Skin class would not need to be subclassed. It's a resource manager. Each widget will know how to get resources from it and apply it to its painting algorithm.

With this technique, how do you deal with ninepatch backgrounds? Ninepatch is very important for drawing backgrounds. How will you differentiate from a normal resource and a ninepatch resource?

Other silly little things that come to mind: What if a user only wants a 2 state button, and does not want to provide a focus texture, a highlight texture, and a disabled texture. Will this do something like:

If has resource loaded for state, use it, else, use default resource.

I also wanted to note a rare case: In my card game, I have a Widget that renders a bitmap that has some opaque and some transparent areas on top of what is normally rendered and then Widgets are rendered on top of that:
{"name":"608312","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/d\/ed789bc69f8058a6d5941a8c020a6b82.png","w":858,"h":939,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/d\/ed789bc69f8058a6d5941a8c020a6b82"}608312

Ofcourse the user could just override paint methods to achieve this, but, it is a case where subclassing a widget is needed to paint.

Your method is simpler and easier for 95% of cases, but introduces subclassing in rare cases. That's not a problem, but I wanted to bring that sort of weird corner case to your attention.

That brings another feature to mind:

It would be good to have a visibility clipping mechanism. For example, being able to put a ListBox in a ScrollPanel and have the ListBox only render items visible to the user. In Agui, since scrollable widgets directly take in scroll bars in their constructor this is easy to do, but it would be better to have it generalized.

bamccaig
Member #7,536
July 2006
avatar

In terms of buttons, you can have multiple implementations. Default buttons that are entirely based on simple attributes like colors and dimensions and the like. That's all you really need for traditional GUI-style buttons that aren't themselves images. You can have alternative implementations for more customizable rendering. Basically, a good library handles as many reasonable cases for you so that you don't have to do a ton of redundant work for nothing.

jmasterx
Member #11,410
October 2009

bamccaig said:

In terms of buttons, you can have multiple implementations. Default buttons that are entirely based on simple attributes like colors and dimensions and the like. That's all you really need for traditional GUI-style buttons that aren't themselves images. You can have alternative implementations for more customizable rendering. Basically, a good library handles as many reasonable cases for you so that you don't have to do a ton of redundant work for nothing.

Yeah, I agree. I was overkill-trying to separate logic and presentation :D

Oh just thought of another feature I like: resizeToContents in the base widget :) (or an interface IResizable if you prefer coding to interfaces).

axilmar
Member #1,204
April 2001

Can we co-develop? Ie. contribute to both code bases at once maybe at times? I don't really want to abandon EAGLE...

You obviously are not looking for a joined project. I don't want to work on Eagle, it's not up to my coding and design standards, so obviously we are off and I am not going to ask you again about collaboration.

jmasterx said:

With this technique, how do you deal with ninepatch backgrounds? Ninepatch is very important for drawing backgrounds. How will you differentiate from a normal resource and a ninepatch resource?

The widgets that need ninepatches can query the Skin instance about the bitmap or bitmaps they want and any metrics that come along with them. The Skin class itself can have a configuration data type which represents a ninepatch.

Quote:

If has resource loaded for state, use it, else, use default resource.

Yeah, absolutely. When you request a resource from Skin, you also pass a default value. If the resource doesn't exist, you get the default value back.

Quote:

I wanted to bring that sort of weird corner case to your attention.

I do not understand the case. Could you elaborate? what is the actual problem?

Quote:

It would be good to have a visibility clipping mechanism. For example, being able to put a ListBox in a ScrollPanel and have the ListBox only render items visible to the user.

Yes, absolutely, and this is a top priority. In many games, for example in the one I work now, there is a huge problem of resources for big lists and you have to make miracles in order to cut resources down, due to the framework not supporting clipping properly.

jmasterx said:

Oh just thought of another feature I like: resizeToContents in the base widget

That would be taken care of in the packing phase of the layout.

jmasterx
Member #11,410
October 2009

The ace of Spades container is weird because you essentially have:
Paint the inner children
Paint the ace
Paint the outer children

Actually, now that I remember how I did it:

I have a class Frame in Agui which allows me to put Widgets in the frame's content panel, and in the frame itself.

So the X, cancel and Okay are Widgets of the frame while the inner ones, scrollbar and options are widgets of the content panel.

In this strange scenario, I had to subclass my Frame so that I can do this painthing:
Paint the content panel
Paint the ace image
Paint the Frame's children

Which, now that I think about it, is a scenario where subclassing is pretty much unavoidable. :P

I was wondering, since each Widget has a setSkin method, who calls setSkin? Is it the Gui class that has the current skin and gives it to the widget when it is added?

What happens in the case where the skin of the entire gui were to be changed at runtime. Is this possible in a singe line of code?

I'm thinking of Java's Swing where the look and feel of the entire gui can be dynamically changed very easily.

axilmar
Member #1,204
April 2001

jmasterx said:

Which, now that I think about it, is a scenario where subclassing is pretty much unavoidable.

You don't need subclassing. You can do it with multiple widget containers stacked on top of each other.

Quote:

who calls setSkin?

The programmer.

Quote:

Is this possible in a singe line of code?

Yes:

myGUI->setSkin(Skin("myskin.txt"));

jmasterx
Member #11,410
October 2009

That's cool! :)

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

axilmar said:

You obviously are not looking for a joined project. I don't want to work on Eagle, it's not up to my coding and design standards, so obviously we are off and I am not going to ask you again about collaboration.

It's a two way street axilmar. If you expect me to donate any or all of my library or time to the community gui library, it might be nice to have something in return ie... freedom to use code from the new library in Eagle as well. As far as not being up to standards, I did say that a lot of the issues on your list were on my todo list, things that I had planned to do anyway. The fact that you're not even willing to discuss my methods suggests you are not a team player. Why would I want to work on a team with you? What do I get out of it?

And what kind of license do you want this library to have?

axilmar
Member #1,204
April 2001

The fact that you're not even willing to discuss my methods suggests you are not a team player.

I am willing to discuss them. Which one would you like to discuss?

Quote:

And what kind of license do you want this library to have?

An open source one, of course.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

axilmar said:

I am willing to discuss them. Which one would you like to discuss?

Well for one, the gui needs to communicate within itself. Which method do you prefer among these or another? 1) WidgetMessages passed from child to parent 2) Listeners 3) Signals and Slots 4) other.

Now explain why and how you plan to handle this in the proposed gui.

axilmar
Member #1,204
April 2001

Well for one, the gui needs to communicate within itself. Which method do you prefer among these or another? 1) WidgetMessages passed from child to parent 2) Listeners 3) Signals and Slots 4) other.

I prefer the event handler pattern:

  • a base class named 'EventHandler' contains a method 'virtual void handleEvent(const EventPtr &event)' that allows it to handle incoming events.

  • a class named 'EventDispatcher' that is derived from class 'EventHandler' allows external classes to attach event handling functions to an object.

  • a class named 'DisplayObjectContainer' that is derived from class 'EventDispatcher' propagates GUI events from parent to children.

I.e.:

#SelectExpand
1class EventHandler { 2public: 3 virtual void handleEvent(const EventPtr &event); 4}; 5 6class EventDispatcher : public EventHandler { 7public: 8 virtual void handleEvent(const EventPtr &event) { 9 dispatchEvent(event); 10 } 11 12 void dispatchEvent(const EventPtr &event); 13 EventListenerId addEventListener(EventType eventType, const std::function<void(const EventPtr &)>); 14}; 15 void removeEventListener(const EventListenerId &id); 16 17class DisplayObject : public EventDispatcher { 18}; 19 20class DisplayObjectContainer : public DisplayObject { 21public: 22 virtual void handleEvent(const EventPtr &event) { 23 DisplayObject::handleEvent(event); 24 if (event->isAccepted()) return; 25 switch (event->getType()) { 26 case MouseEvent::CLICK: 27 //dispatch event to children 28 break; 29 } 30 } 31};

The above design has all the advantages of widget messages, listeners and signals and slots, and more:

  1. it allows messages to be passed from one widget to another or from an external object to a widget.

  2. it provides a listener interface.

  3. the listener interface works as a signal and slot mechanism, since external objects can attach listeners to objects.

  4. if the event is a GUI event, then it allows the creation of modal widgets without a modal loop since widgets lower in the hierarchy can block events of widgets higher in the hierarchy without the addition of special panels.

  5. it allows for propagating down and bubbling up of events: a display object container can choose not to propagate an event to its children or process the event after the children have processed the event.

EDIT: fixed inheritance of EventDispatcher from EventHandler.

pkrcel
Member #14,001
February 2012

I guess class EventDispatcher in your code snipped should inherit from EventHander?

It is unlikely that Google shares your distaste for capitalism. - Derezo
If one had the eternity of time, one would do things later. - Johan Halmén



Go to: