|
|
| Questions about GUI design |
|
Pho75_
Member #12,377
November 2010
|
axilmar said: And the object id becomes the pointer then.
That would be misuse. You add the reference to the object that the id points to. axilmar said: Look guys, there is no need to reinvent the wheel.
Assuming the language chosen is C++ I wasn't disagreeing about reference-counting. |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
One of the reasons I didn't use any shared pointers was that they were only available in Boost and C++11. I hate boost, and I haven't had time to upgrade my compiler to a version that supports c++11 yet. Should I support MinGW 4.7 or skip directly to MinGW 4.8? It looks like MSVC 2013 still doesn't support all of c++11 (http://msdn.microsoft.com/en-us/library/hh567368.aspx), but gcc 4.8.1 is close (http://gcc.gnu.org/gcc-4.8/cxx0x_status.html). I guess I better go with mingw 4.8.1. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
|
axilmar
Member #1,204
April 2001
|
Pho75_ said: That would be misuse. You add the reference to the object that the id points to So the id points to an object which contains pointers to the objects you want? I don't see how this indirection makes anything easier to use. It's just pointers again. Edgar Reynaldo said: One of the reasons I didn't use any shared pointers was that they were only available in Boost and C++11. You could have rolled your own. I've been using my own smart pointers for many years, but now I am gonna throw them out and use the standard ones. I have mingw 4.8.1 and it works superbly so far. |
|
bamccaig
Member #7,536
July 2006
|
axilmar said: So the id points to an object which contains pointers to the objects you want? I don't see how this indirection makes anything easier to use. It's just pointers again. The "id" can be a "symbolic reference". Just a string that the API knows how to associate with an actual widget. If it can't find the thing then the symbolic pointer API will just throw an exception or return an error code or do nothing or whatever is appropriate when you attempt to interact. That way the underlying widget can be destroyed at any time without any segfaults. Alternatively, what usually ends up being done is something like findWidget(id), which can return nothing/null/nil if a child widget with that id doesn't exist. I'm in favor of smart pointers, but I think that an API that doesn't rely on pointers to widgets would be ideal. Wrap the widget pointers in an abstraction layer so that the user never needs to directly interact with widget pointers. // Completely out of context, contrived example: gui.q("#main_menu button#start_game").click([&game, &gui](E) { gui.q("#main_menu").hide(); // ... game = game.factory()->create(); game.play(); });
-- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
axilmar
Member #1,204
April 2001
|
bamccaig said: If it can't find the thing then the symbolic pointer API will just throw an exception or return an error code or do nothing or whatever is appropriate when you attempt to interact. Exactly like a pointer then. For example, this: gui.q("#main_menu button#start_game").click([&game, &gui](E)... Is exactly the same as this: main_menu button->start_game->click([&game, &gui](E)... when it comes to resource management: in both cases, if the target object is not there, an exception can be thrown. Quote: but I think that an API that doesn't rely on pointers to widgets would be ideal. Wrap the widget pointers in an abstraction layer so that the user never needs to directly interact with widget pointers. The abstraction you describe doesn't offer anything for resource management. It offers gui scripting though. |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
axilmar said: I have mingw 4.8.1 and it works superbly so far. Have you compiled all the dependencies for allegro though? Last time Felix The Ghost and I tried to compile allegro with all dependencies we couldn't get some of them to build with 4.8.1. I suppose it is time to try again. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
|
Matthew Leverton
Supreme Loser
January 1999
|
axilmar said: Is exactly the same as this: Sort of, at least in that particular example. jQuery uses an expressive query language that always returns an array of matched objects. e.g., Something like the following would set the background color of every button to red. jQuery("button").css("background-color", "red"); If there are no buttons, the code does not throw an exception because it's not an error. Nothing happens. |
|
pkrcel
Member #14,001
February 2012
|
Edgar Reynaldo said: Have you compiled all the dependencies for allegro though? This is OT but: I did with GCC 4.8.2 (mingw-builds project) both 64 and 32 bit flavors. Beware of PhysFS thou, either you use the 2.0.3 or the 2.1 branch....you have to modify Cmakelists to disable -Werror (otherwise you'll be bothered by a couple "sorta innocuos" warnings 'as errors'). It is unlikely that Google shares your distaste for capitalism. - Derezo |
|
Pho75_
Member #12,377
November 2010
|
axilmar said:
Is exactly the same as this:
No, not quite. You can catch BAD pointers (corrupt, dangling), not just null pointers. |
|
axilmar
Member #1,204
April 2001
|
Edgar Reynaldo said: Have you compiled all the dependencies for allegro though? Last time Felix The Ghost and I tried to compile allegro with all dependencies we couldn't get some of them to build with 4.8.1. I suppose it is time to try again. No, I am using the precompiled 4.7.0 binaries from allegro.cc. They work so far with mingw32 4.8.1. Perhaps I should not use that version and use a previous one? Matthew Leverton said: Sort of, at least in that particular example. jQuery uses an expressive query language that always returns an array of matched objects You have to go full scripting though, not just replace pointers with ids. Still, this doesn't help you in anything, you simply lose type safety without any benefits. Pho75_ said: No, not quite. You can catch BAD pointers (corrupt, dangling), not just null pointers. that's the benefit of the id. No runtime segfaults from bad pointers. No, you can't catch bad pointers. Suppose you have a pointer with id 5, and the pointer is corrupt/dangling. When you call the function getPointer(5), it will return the erroneous contents of the pointer. On the other hand, if you have an id value that is corrupted, it might return the value of another pointer. In either case, you don't solve any problem with this approach. |
|
Pho75_
Member #12,377
November 2010
|
axilmar said: On the other hand, if you have an id value that is corrupted, it might return the value of another pointer. In either case, you don't solve any problem with this approach.
The id is not a simple index lookup. Of course this comes at a performance price. I was simply admiring E18. |
|
axilmar
Member #1,204
April 2001
|
Pho75_ said: The id is not a simple index lookup. No matter how it is encoded, there are two cases: 1) the id itself may be corrupt. In either case, a corrupted pointer will be used. |
|
bamccaig
Member #7,536
July 2006
|
axilmar said: You have to go full scripting though, not just replace pointers with ids. Still, this doesn't help you in anything, you simply lose type safety without any benefits. Scripting is a very simple concept: code as data. That is basically all that scripting is. You do not need that for this to be useful. Imagine this: Gui gui("gui.xml"); GuiQuery q = qui.q("<syntax to be defined>"); q.value("This was a triumph."); q is an object of type GuiQuery (for short, but it's really a query and a result). No matter what you pass into Gui::q you will always get a GuiQuery back. The GuiQuery contains zero, one, or many widgets that match your query. The GuiQuery contains a large set of standard interface methods to interact with widgets. Event binding and triggering, styling and layout, data storage, child management, etc. Basically you can do everything you would do to a widget through the GuiQuery. When you call a method on the GuiQuery the appropriate action is taken on each widget that matched the query. If no widgets matched the query then no action is taken. No exception is thrown. Nothing happens. This allows your code to be very safe. You can add and remove widgets on the fly. It all just works. You can even do lazy binding and apply event handlers automatically to widgets that match some query when the event actually fires. That way the widget doesn't even have to exist when you bind the event handler. These are some of the things that jQuery accomplishes, and it works very well in practice. It allows you to very efficiently express ideas across your entire UI, and it fails gracefully. I really think it is something that should be translated back to desktop (or, as the case may be, game) GUIs. axilmar said: No, you can't catch bad pointers. Suppose you have a pointer with id 5, and the pointer is corrupt/dangling. When you call the function getPointer(5), it will return the erroneous contents of the pointer. On the other hand, if you have an id value that is corrupted, it might return the value of another pointer. In either case, you don't solve any problem with this approach. You're confusing ideas. There are data variables that store virtual memory addresses. These are pointers. These are low-level and unforgiving. Do the wrong thing and your program is terminated, or corrupted, or whatever. I believe that what Pho75_ was trying to say is that in a way a "symbolic reference" like an "id" (of whatever type) is like a pointer in that it refers to something else, but unlike a native process memory address it can be easily validated at run-time to guarantee that it is good or bad. You can verify that the "id" exists. If it doesn't exist you can choose what to do in response to using it. 1GuiPointer p(gui.find("foo"));
2
3if(p->exists())
4{
5 gui.messageBox("Whooo! `foo` exists!");
6}
7else
8{
9 try
10 {
11 p->value("This was not a triumph at all.");
12 }
13 catch(GuiNullPointerException &)
14 {
15 gui.messageBox("Oh well, recovered!");
16 }
17}
Obviously if your API allows the user to manage raw memory-address pointers themselves then the API can't guarantee that those are valid. However, the API really doesn't need to let the user manage those directly. That can be abstracted away from the user. And even if you do consider it useful to expose that unsafe part of the API, you can push a safe part of the API to be used for best practices. And it becomes the user's choice to accept responsibility (just as using smart pointers would be, except with smart pointers the user can still shoot themselves in the foot). You can instead expose something high level that the API can guarantee is always safe. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
Thomas Fjellstrom
Member #476
June 2000
|
Guys, snap to it. The time you've spent bickering could have been used to actually work on your GUIs. Especially axilmar's long posts. It's pretty pointless to be involved in this thread/project if you aren't actually going to contribute to the development, especially if you already admitted that you won't. Get to work already! -- |
|
axilmar
Member #1,204
April 2001
|
bamccaig said: Basically you can do everything you would do to a widget through the GuiQuery. When you call a method on the GuiQuery the appropriate action is taken on each widget that matched the query. If no widgets matched the query then no action is taken. No exception is thrown. Nothing happens. This allows your code to be very safe. You can add and remove widgets on the fly. It all just works. You can even do lazy binding and apply event handlers automatically to widgets that match some query when the event actually fires. That way the widget doesn't even have to exist when you bind the event handler. These are some of the things that jQuery accomplishes, and it works very well in practice. It allows you to very efficiently express ideas across your entire UI, and it fails gracefully. I really think it is something that should be translated back to desktop (or, as the case may be, game) GUIs. I simply disagree with these ideas. I want my errors to be visible as soon as I compile my application. If the widget is not there, I want the compiler to tell me that. If my widget is used erroneously, I want my compiler to tell me that. The time and money saved by static compile time checking simply makes these kinds of ideas not worthwhile to me. Quote: You can verify that the "id" exists. You can verify that your id exists as long as your variable that holds the id value is not corrupted, or the hash table that holds the id information is not corrupted. Quote: However, the API really doesn't need to let the user manage those directly. That's not how you present it to me. You wrote the following piece of code: gui.q("#main_menu button#start_game").click() That's a pointer access right there: the function qui.q() accesses a pointer in order to return access to an object. And since we are talking about untyped interfaces, the object that gui.q() returns will dispatch the click() call to an internal object via a pointer again. Quote: You can instead expose something high level that the API can guarantee is always safe. You can't do that in an unsafe language. Thomas Fjellstrom said: Guys, snap to it. The time you've spent bickering could have been used to actually work on your GUIs. Especially axilmar's long posts. I DO work on my gui, as well as posting. |
|
bamccaig
Member #7,536
July 2006
|
axilmar said: I simply disagree with these ideas.
We're all shocked, I'm sure. axilmar said: I want my errors to be visible as soon as I compile my application. If the widget is not there, I want the compiler to tell me that. If my widget is used erroneously, I want my compiler to tell me that. Who's to say that it's an "error"? That is entirely up to you to define. Maybe it's an error, and maybe it isn't. In my case it wouldn't be. It isn't like saying, "do y with X." It's like saying, "if any Xs exist then do y with each." If not matching a widget is an actual programming error then you can account for that in the API so that the user can choose to make that an error. It will have to be a run-time error. A well-designed API would allow automated testing though to catch mistakes easily. No global state at all, and all external stimulus (i.e., the user) flows into the system as mockable objects. axilmar said: The time and money saved by static compile time checking simply makes these kinds of ideas not worthwhile to me.
Not all errors can be checked at compile-time. That is something that you just have to accept. The computer cannot read your mind. Arguably, static typing costs more in maintenance than it makes up for in type-safety. axilmar said: You can verify that your id exists as long as your variable that holds the id value is not corrupted, or the hash table that holds the id information is not corrupted.
If you have memory corruption then your problems are bigger than GUI design semantics. axilmar said: That's not how you present it to me. You wrote the following piece of code: gui.q("#main_menu button#start_game").click() That's a pointer access right there: the function qui.q() accesses a pointer in order to return access to an object. And since we are talking about untyped interfaces, the object that gui.q() returns will dispatch the click() call to an internal object via a pointer again. A pointer that the user never has direct access to. So if at any time the API destroys that object and invalidates the pointer it will know the object no longer exists when you further attempt to access it. Or, as the case may be, it could use smart pointers to keep the object alive until the last reference goes away, while still allowing it to be removed from the GUI hierarchy, etc. The point is that the user never gets a GuiWidget * or std::shared_ptr<GuiWidget>. axilmar said: You can't do that in an unsafe language. Yes, you can. "Unsafe" just means that the language gives you low enough access to shoot yourself in the foot. Your API doesn't have to expose that. Of course, bugs are a different story altogether. You will always have those. You can't factor those into your design though because like memory corruption they are not by design. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
Well I have been cleaning up WidgetHandler, allowing the user to set the root layout for the container. Now I am working on my Grid layout. It has the option to fill the cell or align to the cell only as well as to set a horizontal or vertical gap and horizontal or vertical cell padding. How do you guys prefer drawing layout containers? If at all. But consider how they would be drawn - do you use virtual callback functions to draw them based on your preferences? So you can override the drawing in a sub class and then use that as your layout container? Or so you can draw widget backgrounds and nine patches in a derived widget? That's one way I could allow the user to customize my gui. So they can specify their own drawing routines or leave it and use the default ones or use both draw the base class first and then decorate it. After Grid layout is Flow layout. Every layout has a halign and valign property, unlike Java cough&. You also couldn't leave empty grid markers, which sucked. Later on I will write GridView and Table and Pin layout. edit - I mean cmon, the JDK must have literally hundreds to thousands of contributors, surely they could have come up with some better default layouts? My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
|
axilmar
Member #1,204
April 2001
|
bamccaig said: A well-designed API would allow automated testing though to catch mistakes easily. No global state at all, and all external stimulus (i.e., the user) flows into the system as mockable objects. Nobody does that tests consistently and continuously, especially for small projects. Quote: Not all errors can be checked at compile-time. That is something that you just have to accept. The computer cannot read your mind. Arguably, static typing costs more in maintenance than it makes up for in type-safety. Nope. Static typing costs a lot less. Quote: If you have memory corruption then your problems are bigger than GUI design semantics. We are human and we might create programs with memory corruption, at least in these unsafe languages (C and C++). So we must take that into account. Quote: A pointer that the user never has direct access to. It's still access via a pointer. Quote: So if at any time the API destroys that object and invalidates the pointer it will know the object no longer exists when you further attempt to access it. No, it's wrong. Objects should never be destroyed and pointers invalidated. In that way, the chance of creating dangling pointers is much greater than when using garbage collection (reference counting is a form of garbage collection). Quote: The point is that the user never gets a std::shared_ptr<GuiWidget>. Which is a stupid thing to NOT do, since it's the safest and fastest way to develop code. Quote: Yes, you can. No, you can't. When we say that something is safe, it must be 100% safe no matter what we do to make it unsafe. Something cannot be called unsafe when there is a ton of assumptions of things that shouldn't be done. Quote: We're all shocked, I'm sure. It's not my fault if you're wrong. |
|
bamccaig
Member #7,536
July 2006
|
axilmar said: We are human and we might create programs with memory corruption, at least in these unsafe languages (C and C++). So we must take that into account. You cannot account for memory corruption. Unless that memory corruption is by design. You don't seem to be capable of understanding our conversation. I'm not at all arguing against smart pointers. They are a very good thing, and I use them myself whenever I write C++. That's entirely beside the point. I'd prefer an API that didn't even require me to manipulate pointers directly. I want a higher-level abstraction that lets me express what I want without having to manage all of the language semantics. C++ is plenty capable of doing this, and while there needs to be ugly and unsafe code, the ugly and unsafe code can all be centralized and written once and hidden away. It's essentially the same concept as a smart pointer, but at a higher-level. There will always be bugs, but over time those bugs can get found and fixed and it will stabilize. If you wrap all of the ugly and unsafe code up into a safe API then you can guarantee a safe library. The alternative is worse: leaving the user to manage the unsafe code themselves, in each and every program, so the number of bugs will be exponentially higher and never cease. axilmar said: It's still access via a pointer. Pointers are not the enemy. Pointers are not dangerous. Invalid pointers are dangerous. Correct code does not have invalid pointers. Pointers are an inevitability in any program, whether you know about them or not. However, pointers can be wrapped up into safe black boxes to separate your program from the error-prone work of managing pointers. That's essentially what interpreted languages do. Smart pointers are still pointers. They're only as correct as the user is setting them up. axilmar said: Objects should never be destroyed and pointers invalidated.
What you describe is a memory leak. axilmar said: In that way, the chance of creating dangling pointers is much greater than when using garbage collection (reference counting is a form of garbage collection). And now you're agreeing with me again. axilmar said: It's not my fault if you're wrong.
I could say the same thing to you. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
|
axilmar
Member #1,204
April 2001
|
I respect your opinion, but there is no need to debate static vs dynamic typing. There is plenty of evidence that static typing is better than dynamic typing anyway. Although the way C/C++ are implemented, some dynamic things that could be done with static typing cannot be done in C/C++, leading you to believe that dynamic typing is better than static typing. Therefore I am not gonna debate the need for a gui scripting language. If you want that, go ahead and implement it. |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
Edgar Reynaldo said: How do you guys prefer drawing layout containers? If at all. But consider how they would be drawn - do you use virtual callback functions to draw them based on your preferences? So you can override the drawing in a sub class and then use that as your layout container? Or so you can draw widget backgrounds and nine patches in a derived widget? That's one way I could allow the user to customize my gui. So they can specify their own drawing routines or leave it and use the default ones or use both draw the base class first and then decorate it. What do you guys think? Are virtual callback functions the best way to decorate and or draw widgets through inheritance? What about function pointers? Passing functions to use? Or make widgets fully re-implement the draw function? If I use virtual call backs that is one easy way to change the way an object is drawn, by a quick subclassing of that widget. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
|
jmasterx
Member #11,410
October 2009
|
I was thinking it would be interesting to use the Visitor Pattern for drawing: Visitor::paintVisit(Widget* w, Graphics& g) { w->onPaint(g,this); } Widget::onPaint(Graphics& g, Visitor* v) { v->onPaint(g,this); } Visitor::onPaint(Button* b) { //paint that button! } Or something to that extent. This would allow the user to custom skin any widget and any subclass without needing to subclass. Agui GUI API -> https://github.com/jmasterx/Agui |
|
|
|