First Task : Render something
The first thing that I did was play around a bit with the themes and widget code to get something to render on my own. A grid system is already implemented in the application, in the save-load game dialog.
So, in a hack-ish way I copied over the code from saveload-dialog.cpp over to launcher.cpp and modified a few things. I wasn’t aware of how to load in images without the ThemeEngine, so for then, I chose to hardcode the images, and load the .pngs through the theme .stx files. This was way easier to do as I just need to copy over the images to the theme folder, and add lines to *_gfx.stx like so:<bitmap filename = 'adl-hires1.png' width = '512' height = '512'/>
The above image is still following the widget organisation which I gave in the last post, just that temporarily the thumbnail was a PicButtonWidget (as this was just a copy-paste from the save-load grid).
The information about the games themselves is retrieved through ConfMan (Config Manager) class, which operates on the scummvm.ini configuration file. The relevant information which needs to be extracted for a game is:
- GameID: e.g. “hires1”
- EngineID: e.g. “adl”
- Description: e.g. “Hi-Res Adventure #1: Mystery House (AppleII/English)”
- Language: e.g. “en”
- Platform: e.g. “apple2”
- The thumbnails are always of the name format :
<engineid>-<gameid>.png - The description is stripped of the extra info in parenthesis.
- If no language or platform is recorded in the config, nothing is rendered in those places.
Second Task : Make custom grid widgets
Now I start adding the custom widgets to widget.cpp and .h files. I create a EntryContainerWidget, and a GridWidget. It will have a pointer to which LauncherEntry(s) it has to be able to represent, and upon setting the displayed LauncherEntry, and calling update() on it, it will set its thumbnail, title, language, and platform accordingly.
The idea here is that a EntryContainer will represent one unique GameID, and the user will be able to pick which variant of that game they would like to launch.
Thumbnail/icon loading is done by, and kept track of by, the GridWidget. Currently, all of the thumbnails for all installed games are loaded at launch time. And all games have an EntryContainerWidget created for them. Eventually only the visible entries need to be kept in memory, so I have created a visibleEntries() function, but right now it just returns the entire list.
Scrolling presents few problems, however. Trying to implement this has uncovered instances of erroneous behaviour in the GUI.
Fixing bugs : Transitive clipping, and surface clipping.
Upon implementing a handleMouseDown() for the GridWidget, I noticed 3 bugs :
- The images (GraphicsWidget) are not cropped properly only when they intersect the top border.
- The images seem to “stick around” while resizing.
- The clipping only applies to direct children, and not to grandchildren.
Note: The first two images were taken by temporarily setting the entry’s internal widgets as direct children of GridWidget.
For this, I was able to get help from my mentor somaen, and find and fix the faulting code, and resolve all 3 of the above.
Janitorial session : Ambiguous naming!
As was pointed out by sev, the name of “EntryContainerWidget” is confusing, as it conflicts with the LauncherEntry, and doesn’t imply that it belongs to a grid. And both seem to be closely working in the GUI. Hence, “entry” becomes ambiguous. So I took a break here to rename my class and variables, and organise the code for better readability.
The new names going forward are “GridItemWidget”, “GridWidget”, and since the LauncherEntry was originally from Launcher.cpp, it was returned, and instead “GridItemInfo” holds the relevant game info for a grid item.
Third Task : Virtualised list
The user can possibly have several hundreds of games, and if we were to load all of their thumbnails at once, the memory requirement would shoot up, and the startup will be really slow. This problem in GUI can be solved with a virtualised list. We have to display only a few elements at a time, so we create a “dummy” list, which our program tells to hold a part of the “real” list. This dummy list is then rendered to the screen. When the user scrolls the screen, this dummy list is updated to display a different part of the “real” list.
This will require us to a) dynamically load and unload thumbnails, and b) reuse GridItemWidgets when they go offscreen.
So for example, in this case we will render only 5 rows at a time, the 3 which are visible, and 1 offscreen on top as well as bottom, so that images can be swapped without the user noticing. In the above situation, if user scrolls down, the topmost (invisible, offscreen) row jumps to the bottom of the list, its GridItemInfo s are updated, to reflect the upcoming row of the “real” grid. Similar thing happens in mirrored fashion on scrolling up. I had already made a dummy visibleEntries() function before, so I have to modify it to return the proper list of visibleEntries.
After few optimisations and with minor bugs left, a grid implementing a Virtualised List is rendered, and the memory usage has been significantly reduced.
Great work!! I’m really, really excited for this, definitely the coolest upgrade to the GUI yet! Can’t wait to see it pop up in a daily build