|
|
| Questions about GUI design |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
axilmar said: It's a great idea. You can create very complex guis very easily, even with one liners. Not really. You have to manage your invisible widget sizes then, and adjust them to make sure they are creating the proper alignment. And being able to cram everything into one line is not a feature and hurts readability. Quote: Imagine having a grid of 10000x10 items, for example: if you have one cell object per cell, then you'll have 100,000 objects. You only need one object per actually occupied cell. Without using a cell parameter you can't leave spaces in the grid. There's nothing wrong with a grid holding a cell. And you can differentiate between a regular grid class and the flexible grid class. No reason they have to be the same. struct GRIDCELL {int x,y,w,h;WidgetBase* widget;} At 5X4X10000 that's only 200,000 bytes. That's 200k of memory. Not worried about it. Quote: It's always computed automatically from the layouts. That's the same way my layouts will work. Add a widget to a layout and the layout positions and sizes it. Move the layout and the widget is updated. Quote: For example, the chat window of DFUW is made entirely out of layouts: I don't think I understand how you want to implement layouts. Example code? Pseudo code? 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
|
Edgar Reynaldo said: Not really. You have to manage your invisible widget sizes then, and adjust them to make sure they are creating the proper alignment. Nope. The size and position of these widgets is automatically calculated. Quote: And being able to cram everything into one line is not a feature and hurts readability. Nope. For example, you can write Row(center(textbox()), left(stack(button(),bitmap()))) Which is extremely readable. Quote: At 5X4X10000 that's only 200,000 bytes. That's 200k of memory. Not worried about it. Nope. In my example, the grid had 10000 rows by 10 columns. That's 100000 grid cell instances. If you allocate them with a single array, then they take 2 mb. Nice way to increase the cache misses in the 1st level cache and dramatically reduce the performance of your game. For smaller devices, 2 mb ram is significant. If you do not allocate these entries as a single array, then you risk memory fragmentation. You may alleviate this by using a memory pool though, but then you have the risk of memory management. The pointer to the widget base is also a raw pointer, not a shared pointer. If you make it a shared pointer as you should, then we are talking about 4 more bytes per grid cell. Quote: That's the same way my layouts will work. Add a widget to a layout and the layout positions and sizes it. Move the layout and the widget is updated. Of course, we would not talk about automating positioning and sizing with layouts if that wasn't automatic. Quote: I don't think I understand how you want to implement layouts. Example code? Pseudo code? Did you watch the video? I am asking you because I can explain it to you using concrete examples. |
|
Thomas Fjellstrom
Member #476
June 2000
|
Qt's Designer had these spacer "widgets" you could place to manage some spacing. But they really weren't widgets, I think they were actually just layout children, rather than full fledged widgets, and played with the properties of that layout child cell. -- |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
axilmar said: If you allocate them with a single array, then they take 2 mb. Nice way to increase the cache misses in the 1st level cache and dramatically reduce the performance of your game. The entire 2mb won't be used at the same time. The L1 cache can more than hold the visible elements in a grid. Quote: For smaller devices, 2 mb ram is significant. When will you be using a grid with 100,000 elements on a smaller device? Or even at all? Quote: Nope. For example, you can write Row(center(textbox()), left(stack(button(),bitmap()))) Which is extremely readable. Not with your example of ten elements in a row. That would become an unreadable nightmare quickly unless you broke each element onto its own line. Does your Row use tuples? I'm only asking b/c it seems like it would have to. Here is what I would have : grid.setElement(row , 1 , textbox , HALIGN_CENTER); grid.setElement(row , 2 , iconbutton , HALIGN_LEFT); Very readable, and no extra layout objects are necessary. What I don't see is how you achieve your nested layouts. Do all your widgets and layouts derive from a common base class? Quote: The pointer to the widget base is also a raw pointer, not a shared pointer. If you make it a shared pointer as you should It is not necessary to use shared pointers for widgets in my gui. When you add an element to a gui you tell it whether to destroy the widget when done with it (ie. when removed from the gui). I use a simple map<WidgetBase* , bool> to track which widgets need to be destroyed. And how would a shared_ptr work with a statically allocated widget? Quote: Did you watch the video? I am asking you because I can explain it to you using concrete examples. I watched part of it, about the first half. I didn't see anything extraordinary in terms of layout really. What specifically did you want me to see? The second half didn't have much in the way of gui. 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
|
Edgar Reynaldo said: The entire 2mb won't be used at the same time. The L1 cache can more than hold the visible elements in a grid. The problem is not that the L1 cache cannot hold all the visible elements in the grid, the problem is that it will push other things out of the cache. Quote: When will you be using a grid with 100,000 elements on a smaller device? Or even at all? Lots of games have requirements for big lists. It's better to be proactive than reactive. Quote: Very readable, and no extra layout objects are necessary. No one is forcing you to write everything in one line. You could always write it like this (real life example): SFSpacer row = SFSpacer.createHBox(); row.addChild(SFSpacer.createCenter(new SFBitmap("IconFeatBitmap"))); row.addChild(SFSpacer.createCenter(_createSelectedFeatCurrentPointsText())); row.addChild(SFSpacer.createCenter(_createSelectedFeatTotalProgressText())); row.addChild(SFSpacer.createFixed(16, 1)); row.addChild(SFSpacer.createCenter(new SFBitmap("IconProwessBitmap"))); row.addChild(SFSpacer.createCenter(_createSelectedFeatCurrentProwessText())); row.addChild(SFSpacer.createCenter(_createSelectedFeatTotalProwessText())); row.addChild(SFSpacer.createFlexible()); return row;
Quote: What I don't see is how you achieve your nested layouts. Do all your widgets and layouts derive from a common base class? It's simple: each widget has a pointer to a layout instance. The layout instance takes care of managing the widget's size, if it depends on its children, and the position and size of children. Quote: It is not necessary to use shared pointers for widgets in my gui. Wrong approach. When you need to hold widget references from different places, your approach will come to make your life difficult. And don't tell me that you don't need to hold widget references from different places, because you do. You often have to update part of a complex widget that lives in another container than the current one, so instead of querying each time the container you hold a reference to the child. Quote: And how would a shared_ptr work with a statically allocated widget? Use an empty deleter. Quote: I didn't see anything extraordinary in terms of layout really. It depends on what you mean by extraordinary. The whole gui is created with few primitives, using aggregation. That's 'extraordinary' in terms of programming elegance. |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
axilmar said: The problem is not that the L1 cache cannot hold all the visible elements in the grid, the problem is that it will push other things out of the cache. Lots of games have requirements for big lists. It's better to be proactive than reactive. Sure you're not being premature in your quest to optimize? Quote: It's simple: each widget has a pointer to a layout instance. The layout instance takes care of managing the widget's size, if it depends on its children, and the position and size of children. This is the same as in my gui. The only difference is that I allow a layout to act as a panel and widget. axilmar said: Wrong approach. When you need to hold widget references from different places, your approach will come to make your life difficult. And don't tell me that you don't need to hold widget references from different places, because you do. You often have to update part of a complex widget that lives in another container than the current one, so instead of querying each time the container you hold a reference to the child. Edgar Reynaldo said: And how would a shared_ptr work with a statically allocated widget? Use an empty deleter. I haven't come across the situation where any widget needs to know about another widget that it isn't directly responsible for to begin with. You don't need a shared pointer to reference a widget. If something depends on a certain widget not being destroyed then don't destroy it until you're done using it. I understand shared_ptr can automate that, but I honestly haven't come across any situations where it would make my life substantially easier. Also, how would an empty deleter work? Are shared_ptr's type compatible across different deleters? Ie, how could you use a shared pointer to a stack object with shared pointers that have been set up to delete their object when done? Quote: It depends on what you mean by extraordinary. The whole gui is created with few primitives, using aggregation. That's 'extraordinary' in terms of programming elegance. Well it sounds good enough. I would like to see their code though. Is it open source? 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 |
|
bamccaig
Member #7,536
July 2006
|
Edgar Reynaldo said: Also, how would an empty deleter work? Are shared_ptr's type compatible across different deleters? Ie, how could you use a shared pointer to a stack object with shared pointers that have been set up to delete their object when done? A deleter is just a function or functor that is supposed to destroy the object passed in when invoked. An empty deleter would just be an empty function so that when invoked it does nothing: 1void null_deleter(void * const) {}
2
3// ...
4
5std::shared_ptr<int> q;
6
7{
8 int x;
9
10 // Note: generally this is a bad idea, but you can do it if you're careful.
11 std::shared_ptr<int> p(&x, null_deleter);
12
13 *p = 5;
14
15 q = p;
16}
17
18// Obviously if any copies of the shared pointer outlive the stack variable bad
19// things will happen if you dereference them, same with any pointer...
20std::cout << *q << std::endl;
I believe that Boost defines an empty deleter, but I'm not sure that the C++11x standard includes one... -- 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
|
Yes, but is a shared pointer with an empty deleter compatible with a shared pointer with a non empty deleter? Ie can they be used together? Can you use a shared pointer to a stack object in a list of shared pointers to heap objects? 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 |
|
bamccaig
Member #7,536
July 2006
|
Of course. The deleter is a run-time thing. Each instance of a shared pointer has its own deleter. -- 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
|
If you have to specify the deleter, how is that different from me specifying whether or not to delete my widgets when done? I understand a shared pointer tracks the number of references to an object, but in my case only one class is responsible for destroying widgets and that is the layout. When you remove a widget from a layout it uses its stored bool value to determine whether to destroy it or not. Maybe there are better ways, but this is what I have right now. 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 |
|
bamccaig
Member #7,536
July 2006
|
In your library you might not benefit much. A single place to destroy widgets is pretty easy to get right, or fix bugs at. One advantage of smart pointers would be that the decision to keep or destroy is automatic, and can change more easily at any time. For example, a remove() method could remove a child widget and return a smart pointer. If that is the last reference and the user doesn't hold onto it the widget will be automatically destroyed. If the user holds onto it or references exist elsewhere the widget will remain alive. All automatically. The programmer doesn't even have to think about it. -- 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
|
Edgar Reynaldo said: Sure you're not being premature in your quest to optimize? I am not. If you don't design it properly from the get go, then you'll create a mess afterwards. Things like that optimization cannot be done properly if not done from the beginning. Quote: This is the same as in my gui. The only difference is that I allow a layout to act as a panel and widget. So not the same at all. Quote: I haven't come across the situation where any widget needs to know about another widget that it isn't directly responsible for to begin with. Believe me, the need arises more often than you can imagine. Especially if you have a deep tree of widgets, due to the existence of layout widgets, you will need to keep pointers to the interesting widgets in your main window. Quote: Also, how would an empty deleter work? Are shared_ptr's type compatible across different deleters? Ie, how could you use a shared pointer to a stack object with shared pointers that have been set up to delete their object when done? The deleter is part of the struct that is shared between pointers. Quote: Well it sounds good enough. I would like to see their code though. Is it open source? Nope. bamccaig said: I believe that Boost defines an empty deleter, but I'm not sure that the C++11x standard includes one... You don't need one, an empty lambda function will do. Edgar Reynaldo said: If you have to specify the deleter, how is that different from me specifying whether or not to delete my widgets when done? When using shared pointers, and assuming you don't have reference cycles, you don't care about when to set the widget for destruction. In your case, you do. Your design is nothing more than deleting an object with a delay, which doesn't solve any reference problems. |
|
bamccaig
Member #7,536
July 2006
|
axilmar said: You don't need one, an empty lambda function will do. Touche. I haven't been using C++11x so I'm not used to having the new features. -- 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 |
|
jmasterx
Member #11,410
October 2009
|
Just for your reference, the code name was C++0x but the official name is C++11 and not C++11x Agui GUI API -> https://github.com/jmasterx/Agui |
|
Thomas Fjellstrom
Member #476
June 2000
|
The code name changed to C++1x at some point I think -- |
|
bamccaig
Member #7,536
July 2006
|
jmasterx said: Just for your reference, the code name was C++0x but the official name is C++11 and not C++11x
Good catch. -- 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 |
|
Dizzy Egg
Member #10,824
March 2009
|
Forget about it, it was Jasper that informed Tomasu, he deserves no cookie
---------------------------------------------------- |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
axilmar said: Believe me, the need arises more often than you can imagine. Especially if you have a deep tree of widgets, due to the existence of layout widgets, you will need to keep pointers to the interesting widgets in your main window. Even if you do need a reference to a widget, leave the deletion of the widget to its owner. If you delete your widget before you're done with it, that's your own problem. Quote: In your case, you do. Your design is nothing more than deleting an object with a delay, which doesn't solve any reference problems. But my design also doesn't have any reference problems. When the layout with the widgets is on it is destroyed so are the widgets. It's simple to avoid problems, don't destroy your layouts until you're done with them. I will admit though, I'm having kind of a design quandary. The lines between layout and widget handler are kind of blurred right now. Right now layouts do one thing, and that is position widgets. My widget handler takes care of drawing and input and event handling. The problem is sometimes it would be advantageous to have layouts draw their children but then they have to implement z order, dirty rectangles and / or buffering the gui image. I just haven't come up with a nice way to keep everything separated out into a nice modular design. What I have works, but its a blurred line as to who should be responsible for drawing. 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 |
|
Pho75_
Member #12,377
November 2010
|
Edgar Reynaldo said: Even if you do need a reference to a widget, leave the deletion of the widget to its owner. If you delete your widget before you're done with it, that's your own problem.
That's an unnecessary limitation to impose on developers. eg: you could be dynamically building a listbox. Devs can be referencing widgets in all kinds of ways (eg: as hash keys), |
|
axilmar
Member #1,204
April 2001
|
Edgar Reynaldo said: Even if you do need a reference to a widget, leave the deletion of the widget to its owner. If you delete your widget before you're done with it, that's your own problem. Most of the time there isn't a clear owner of a widget. This is especially true when using layout widgets: the layout widgets are never the actual managers of their children widgets in terms of functionality. Quote: But my design also doesn't have any reference problems. When the layout with the widgets is on it is destroyed so are the widgets. It's simple to avoid problems, don't destroy your layouts until you're done with them. You simply transfer the lifetime management problem of the child widget to the parent layout widget. In other words, with your design, the question 'when shall I delete this widget?' becomes 'when shall I delete this widget's parent?'. Quote: I just haven't come up with a nice way to keep everything separated out into a nice modular design. What I have works, but its a blurred line as to who should be responsible for drawing. That's not the only problem of your library, but it's nice to admit at least one flaw of it. Pho75_ said: Devs can be referencing widgets in all kinds of ways (eg: as hash keys), and want them to live beyond the life of their container widget. Absolutely. It comes up very often. |
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
Well really what I want is more of a widget factory, where the allocated widgets are managed by the system and don't get destroyed until it shuts down or you ask it to destroy a widget prematurely b/c you're done with it. That's the way I have it now, the system owns all windows, and all windows own all images and fonts and none get destroyed until you say you're done with them or you delete the system. That's exactly one point of memory to manage. Ie. when do I delete the system. 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 |
|
Pho75_
Member #12,377
November 2010
|
I guess we'd need a memory-pool (hierarchical?) with reference counting to accomplish this. Then we can re-use it for shared objects (icons, text, etc.) Depends how much hand-holding vs performance we want to trade. I really like Enlightenment E18 and their Eo object model. |
|
axilmar
Member #1,204
April 2001
|
Edgar Reynaldo said: Well really what I want is more of a widget factory, where the allocated widgets are managed by the system and don't get destroyed until it shuts down or you ask it to destroy a widget prematurely b/c you're done with it. That's the way I have it now, the system owns all windows, and all windows own all images and fonts and none get destroyed until you say you're done with them or you delete the system. That's exactly one point of memory to manage. Ie. when do I delete the system. Well, if you use shared pointers then there is NO point of memory to manage. And no point of management beats one point of management every time. Pho75_ said: All you get is an object id to interact with. And the object id becomes the pointer then. Look guys, there is no need to reinvent the wheel. There is a reason that reference counting was invented. I guarantee you that any other choice you make will not be cleverer and more desirable than what there is already out there. Let's not forget that thousands of people everyday have to deal with memory & resource management in C++ and if your methods were truly better then they would have been widely adopted. |
|
Elias
Member #358
May 2000
|
axilmar said: if your methods were truly better then they would have been widely adopted
Haha -- |
|
bamccaig
Member #7,536
July 2006
|
Hmmm, actually I think something like jQuery would be a really neat GUI API. Basically you query the GUI for a particular element or set of elements (using CSS-style selectors) and interact with the results in standard ways with a shared API. If your query doesn't match any elements then all interactions with that query result are basically a NO-OP. So no errors/exceptions if things don't exist. If they do exist then things just work. Doing something similar for a native GUI would be quite powerful, I think. I know that it makes jQuery super powerful. Of course, if you need an element (i.e., widget) to exist then you can check the results of the query, and even ask for a direct handle if you need it for some reason, but you typically don't because the jQuery API is more predictable and safer. That might be a fun project to tackle... -- 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 |
|
|
|