2010-01-12

Unicode Complete

Woopsi is now 100% unicode. Except for anything in the “bonus” folder, which I’ll get around to at some point.

I’ve tidied up the WoopsiString, removing redundant methods and sorting out which should be public and which shouldn’t. To cater for iteration, rather than trying to redesign the string, I’ve added a new StringIterator class. The WoopsiString can produce instances of these (in the same way that Gadgets produce instances of the GraphicsPort class) that can iterate over the string. It has some cunning optimisations so that it will always choose the optimal way of moving to a new character index.

On top of that, I’ve moved the FileRequester and associated classes into the main codebase. Lakedaemon pointed out that, unless you use any libfat features, it isn’t included in the final ROM. A quick test revealed that this was indeed the case, so there’s really no reason not to enable libfat in the makefile by default and include the FileRequester in the main library.

I’ve fixed a few problems in the FileRequester. Its background was transparent in places, which was caused by one of the gadgets in its children not having a draw() method. The sorting was broken - I thought I’d fixed this - so although the items were sorted alphabetically, directories were intermingled with files. Lastly, it doesn’t try to draw before the rest of Woopsi has been drawn, a problem which caused some graphical corruption.

The Bmp2Font .NET utility for converting BMP files to Woopsi fonts now creates working Font subclasses. I’ve removed four fonts from Woopsi as they didn’t seem to be converted properly by Jeff’s original process - they either had glyphs with missing sections or the vertical positioning of some of the glyphs was off.

2009-12-09

Fonts, Styles and Bugfixes

Thanks to Jeff, Woopsi now has a smorgasbord of new fonts. They are all included in the main libwoopsi folder, so I’ve had to tweak the makefiles in all projects to accommodate the change.

The fonts include standards such as MS Sans, Garamond, Times New Roman and Courier. Some of them are available in more than one size.

Not all of the fonts Jeff supplied were usable, due to being too large, too small or having other problems. The PackedFonts system has size limits due to using u16s instead of u32s in a variety of places; the limits should be trivial to remove (or at least extend) but it’s probably a good thing that absurdly-sized fonts aren’t allowed. At least one of the fonts was too small to be legible. Others had strange vertical alignment problems. I still need to remove some of the troublesome fonts.

Including all of the fonts in the library has no impact on projects built with standard makefiles, but it vastly bloats the size of the demo project that’s built with VC++. I suppose the makefile must include all .cpp files regardless of whether or not they’re used. Not a problem for end-users, though.

Having some example fonts allowed me to change the way Woopsi was working with its original two fonts - the system font and tiny font. Instead of needing to manually instantiate a Font object and pass in the data comprising the fonts, they’re now implemented as classes. To create an instance of the tiny font, include “fonts/tinyfont.h” and call “new TinyFont()”. To create an instance of the system font, include “fonts/topaz.h” and call “new Topaz()”.

The new name for the system font hints at another change - I’ve replaced the default font. Topaz, apart from being ugly, is a fixed-width font. A considerable amount of screen real estate is taken up with the blank space between letters. Using Jeff’s PackedFont classes I’ve replaced Topaz with NewTopaz. More text can now fit on the DS’ screens, the UI looks more attractive, and as an added bonus, Woopsi looks more like my WinUAE setup.

Along with these changes, I’ve ripped out the global “systemFont” and “tinyFont” objects. These were created in the woopsifuncs.h file. However, tinyFont was only used by the Debug class (which now creates the font itself), whilst systemFont has been absorbed into a new “defaultGadgetStyle” object.

Previously, most of the gadgets in Woopsi could receive a pointer to a font as their final constructor argument. If no font was supplied they used the systemFont instead. They pulled all of their default colours from static members of the “DefaultStyle” class. This worked well enough, but it was an ugly solution.

I’ve re-worked this so that instead of receiving a pointer to a font in their constructors, gadgets can be passed a pointer to a GadgetStyle object instead. GadgetStyle objects contain definitions for all colours a gadget uses and the font. The gadget copies the properties of the object into its own internal GadgetStyle instance. If no object is supplied, they copy the information from the global defaultGadgetStyle object instead. This means that:

  • It is much easier to change the font and colours for all gadgets that need to be created;
  • It is much easier to change the font and colours for a subset of gadgets that need to be created;
  • Colour and font information is stored together.

All of this represents breaking changes.

In other font/style news, the PackedFontBase::isCharBlank() method returns the correct value. I’ve added a packedfonts example program which demonstrates all of the fonts in Woopsi. I’ve also added a defaultstyle example which illustrates how to change the properties of the defaultGadgetStyle and alter the appearance of Woopsi.

I’ve made many other fixes. The ListBox draws correctly when it is dragged; previously, dragging upwards caused graphical artifacts to appear. It does not attempt to redraw the item at index -1 in its list. The CycleButton redraws itself correctly if it is clicked but it contains no options. The Label and its subclasses recalculate their text positions and redraw when their fonts are changed, a does the MultiLineTextBox.

There are a few fixes to the TextBox. It does not attempt to reposition its cursor if it contains no text. Its cursor jumps to the right location if its font is a PackedFont.

I’ve stopped the Graphics::floodFill() method from getting stuck in an infinite loop (not sure when I introduced that bug), and removed some unused bitmaps from the demo.

Finally, I’ve added the FileListBox classes to SVN. These were supposed to have been released in version 0.42, but I apparently forgot to check them into SVN and so they were omitted. This means that the FileRequester is broken in version 0.42. I’m just glad I still had copies of the files.

2009-12-04

Separating UI and Data Management - Lister Improvements

In my last post I discussed the possibility of ripping out the data management from the CycleButton and ContextMenu classes and replacing it with the ListData class. I was slightly dubious about this because of the potential extra overhead involved, but then realised that the current system did not offer any way to remove items from the CycleButton. In fact, the only possible operation that could be performed on CycleButton data was adding to it.

Worse, the ContextMenu duplicated the functionality of the ListBox but uses a gadget for each item in its list. This isn’t the fastest way of maintaining a list of options (I threw that model out in the early stages of ListBox development), and the option gadgets were being passed around as the arguments to ContextMenu events. Why pass around the entire gadget when only the data it contains is in any way worthwhile?

Based on these observations I’ve made a number of changes. The CycleButton now includes a ListData object to manage its options and exposes all relevant methods via a set of facade functions. The options can be sorted, added to, removed from, and so on.

The ContextMenu includes a ListBox gadget that does practically everything the ContextMenu tried to do; the ContextMenu is now little more than a wrapper that can raise relevant events and resize/reposition itself as necessary. It doesn’t expose any more methods, but it is more efficient now that each option isn’t an entire gadget. It also includes a getPreferredDimension() method that produces the correct values, and the resizing routine uses that instead of duplicating the functionality.

The ListData class has had several improvements and bugfixes. Its swapItems() method no longer raises a data changed event, as it is only called by one function - swap(). That method raises the event instead.

I’ve removed the ListDataItem struct from the listdata.h file and turned it into a separate class in its own file. That allows me to subclass it where necessary to change its behavioury. ListData’s sort() method uses a Java-esque “compareTo()” method in the ListDataIten class in order to sort its data, meaning subclasses of ListDataItem can override the default sort behaviour. This has proven useful in the FileRequester, which is now several times faster. The new sorting system means it doesn’t need to create two intermediate sorted arrays of files and directories in order to keep them separately ordered when displaying them.

Whilst on the subject of the FileRequester, I’ve split that into two classes. The FileRequester is still there, but most of the functionality has been moved out of it and into a new FileListBox class. This works just like the ListBox, but it automatically populates itself with a list of files when pointed at a directory. The FileRequester includes an instance of this new class in order to display its list of files.

Working with the ListBox so much made several bugs obvious. It now draws correctly - previously, a single rogue pixel was visible below the options in the list. It raises a double-click event when double-clicked, and ignores double-clicks if they occur on separate items within the list. It also redraws every time data in the list changes, does not overwrite items at the top of the list with those that have wrapped-around from the bottom (oops, clipping bug introduced in the last release), and adjusts its scroll position appropriately when items are removed.

Since the ListBox inherits from the ScrollingPanel and ScrollableBase classes, I inevitably found improvements to make there too. ScrollableBase and ScrollingPanel include functionality to disable scrolling either horizontally, vertically, or both. The ListBox uses this to prevent horizontal scrolling.

Aside from this, I’ve made some general improvements. Gadgets no longer respond to double-clicks if the first click actually falls on a different gadget. A new Gadget::isDoubleClick() method helps with this, and I’ve removed the unused Gadget::_doubleClickTime member. The Woopsi class does not attempt to retrieve a pointer to the system font before the font has been initialised. The WoopsiString class includes a new copyToCharArray() method for getting a copy of its internal string data. The TextWriter’s methods have been moved into the Graphics set of classes, making the class redundant so I have deleted it. Lastly, I’ve removed all of the individual colour variables from the Gadget class (_back, _highlight, _shine, etc) and merged them into a new GadgetColours struct.

I hope to get a new release out in the next few days.

2009-11-26

Multiple Inheritance and the Dreaded Diamond

Woopsi refactoring continues apace. Today’s latest changes were an attempt to consolidate the ListBox, ContextMenu and CycleButton gadgets.

Each of these gadgets is a different kind of view onto a list of items. The ContextMenu is the most primitive - it shows all of the items in its list as a vertical list. The ListBox is basically the same thing, except it introduces scrolling into the mix. The CycleButton shows just one option at a time; the other options can be paged through by clicking the button.

At present, the ListBox has the most advanced data mechanism. It leaves all of the data functionality to the ListData class, which wraps around a WoopsiArray (ie. vector) and provides such handy functions as sorting and item selection/deselection (enforcing single-only or multiple selection rules). It raises events to indicate changes to the data or selections within the data.

Meanwhile, the CycleButton and ContextMenu classes have very primitive data manipulation functionality. They both include an instance of the WoopsiArray and work with it directly. Wouldn’t it be better if they could benefit from the wealth of functionality in the ListData class?

Since the ContextMenu and ListBox work in very similar ways - the ContextMenu is basically a customised ListBox without scrolling - it makes sense to create a base class providing the most basic list view functionality that they can both inherit from. I split up the ListBox into “BasicListBox” (no scrolling) and “ListBox” (inherits from BasicListBox and ScrollingPanel, which adds the scrolling functionality). Here the problems began.

The BasicListBox inherits from the fundamental Gadget class, from which all Woopsi UI components inherit. The ScrollingPanel also inherits from the Gadget class. When the ListBox attempts to inherit from them both, it ends up with two copies of the Gadget class floating around in its inheritance hierarchy. Any attempts at working with the features of the Gadget class are now ambiguous and the compiler throws errors all over the place. This is called a “diamond” inheritance pattern:

           Gadget
       /             \
      /               \
     /                 \
BasicListBox    ScrollingPanel
     \                 /
      \               /
       \            /
           ListBox

The C++ FAQ Lite has a whole page about the diamond inheritance problem. The solution is to declare the inheritance from Gadget as virtual, which eliminates the ambiguities by eliminating the second copy of Gadget. However, attempting to do this has horrible problems in the Woopsi codebase. C-style casting cannot be used (uh oh) and the class at the top of the diamond (in this case, Gadget) should not include data members that need to be initialised (it can, but it shouldn’t).

The upshot of all this is that it is impossible to create a gadget that inherits from two other gadgets in Woopsi. If you think about it, this does make sense. It doesn’t help my goal of rationalising the list display gadgets, though. I could include an instance of the ScrollingPanel inside the ListBox to achieve the same effect, but that would be less efficient than the current solution. I could alternatively make the ListBox the basic list and have the ContextMenu inherit from that, and just not use the scrolling feature (though it would be handy if there are too many items to fit on-screen at once), but again that’s less efficient than the current solution.

I kept some of the changes I made before I reverted everything else back. ListData::swapItems() no longer raises a data changed event, as the only place it is called is in the sort() method. That method now raises the event instead. The ListDataItem struct has been replaced with a ListDataItem class, and it includes a C#/Java-style compareTo() method. Subclassing the ListDataItem and overriding this method allows the ListData class’ sort() method to sort the list differently. The ListBox’s scrolling canvas now has the correct height and the extra 1px at the bottom of every ListBox has now gone.

Finally, there are a couple of fixes in other places. The Woopsi class’ constructor no longer tries to retrieve the system font before it has been initialised, and the ContextMenu does not cast away the const-ness of the ContextMenuItem when raising a selection event to its listeners.

2009-10-11

Woopsi Testing and Fixes

Now that I have time again, I’m back to working on Woopsi. Something I haven’t really done yet is any extensive testing of the gadgets. I’ve checked that the obvious functionality works as I develop the system, but not that all of the functionality works for each gadget. To that end, I’m writing tests for each gadget.

So far, the CycleButton and RadioButtonGroup gadgets both have test suites written. The test program checks that the gadgets can be shelved, unshelved, hidden, shown, enabled, disabled, moved, resized, redimensioned (simultaneous move and resize) and that they produce the correct preferred dimensions. The test system also checks that they fire the expected events when interacted with.

The test system has enabled me to identify and fix a number of bugs. The CycleButton now produces the correct values when getPreferredDimensions() is called. The same is true of the RadioButtonGroup. The latter also redraws correctly when resized. Another bug spotted was in the base Gadget class - its enable() and disable() methods weren’t redrawing.

The last bug fixed is in the Gadget::clipRectToHierarchy() method. In the last set of changes I made this function non-recursive, but did so in such a way that the function did not clip a gadget to its immediate parent’s dimensions.

One bug I’ve spotted but haven’t fixed yet involves the show/hide/shelve/unshelve systems. Imagine gadget A overlaps button B but is hidden. Clicking button B shows gadget A. Upon clicking and releasing the button, gadget A is redrawn in place. Button B is then redrawn to show its newly unclicked state, but button B is actually drawn over the top of gadget A. Somewhere along the line, button B’s rect cache isn’t being flushed when it should so it draws over space that no longer belongs to it.

2009-04-24

More Optimisations and Framebuffer Copying

Following on from yesterday’s post, I have renamed the two types of clipping rect to “foreground” and “background”. It should be possible to optimise it further, and have the cache selectively invalidate either set of regions depending on what exactly has invalidated the cache.

The Gadget::clipRectToHierarchy() function, which clips drawing regions to a gadget’s ancestors, is now non-recursive, which should give a minor speed boost.

A feature that Jeff asked for nearly 18 months ago was a ScrollRect() function in the graphics port that would shift a region of the framebuffer by n pixels. I’ve now got part of this implemented - the GraphicsPort has a “copy()” method that will copy a rectangular region of the framebuffer from one location to another. It uses all of the scrolling tricks I worked out when writing the ScrollingPanel class, such as an off-screen buffer for horizontal copies (wherein the destination overlaps the source), and copying top-to-bottom or bottom-to-top depending on the vertical difference between source and destination. I still need to add in clipping methods to enable this to be used as a scrolling function.

The existing copy() method has allowed me to trim the Screen::drag() method. That was previously recalculating its foreground visible rects (for some inexplicable reason, as they’ve been cached for ages now) and had implemented its own copying routine that worked directly with the framebuffer. I’m trying to eliminate that from Woopsi to make it more portable. Anyway, the upshot of all this is that screen dragging is now faster, and the code is tidier. Hurrah!

2009-04-22

Happy Belated Birthday, and More Event Stuff

It was the blog’s birthday on April 11th. I’d intended to put up a birthday post like I did last year, and maybe a celebratory Woopsi app, but exam work got in the way. Ah well.

Anyhoo, I made some more Woopsi changes nearly a month ago that I haven’t been able to post about yet, so here’s some info on those.

One of the lingering problems with the Gadget class has been the way it overloads the draw() method. There are two draw() methods, one without parameters, and one with a single parameter (a Gadget::Rect). The first method works out which parts of the gadget are visible, whilst the second actually draws those visible parts. Sounds good, but in practice it causes problems.

There is a bug in GCC (or a weird C++ behaviour; let’s assume it’s a bug in the compiler and not a design flaw in the language) that prevents this arrangement from working. If we create a subclass of gadget (let’s call it “SubGadget”), we want to override the draw(Rect) method so the gadget can define how to draw each visible region. However, we don’t want to override the draw() method, as this is identical for all gadgets. Unfortunately, we can’t override one without also overriding the other. The compiler complains. As a workaround for this I have been overriding draw() with a method that just calls the base method, but this is clunky.

As a proper fix, the draw() method is now called “redraw()”. This change will probably break your code if you have created your own gadgets.

There are a few more changes in the event system. Previously, whenever a selection was made in the context menu, the gadget that “owned” the menu raised an event indicating the selection. It was then the programmer’s responsibility to check the context menu gadget itself to determine which value had been chosen. This is no longer the case - the handleContextMenuSelectionEvent() method now receives a reference to a “ContextMenuEventArgs” object that contains a pointer to the menu item that was selected. Much simpler to work with.

In other changes, the WoopsiArray and LinkedList classes no longer contain the “begin()” methods. This was left over from when Woopsi switched from the STL vector class to the WoopsiArray, a change in which I’d purposely made the WoopsiArray a drop-in replacement for the vector. The “begin()” was just a stub method, though - the WoopsiArray does not implement the STL iterator code, and begin() simply returned a zero. The WoopsiArray uses indexes internally, not pointer arithmetic, to navigate its contents.

2009-04-04

Keyboard Event Refactoring

As part of the Great Event Refactor, I have changed the way that keyboard events work. Previously, keyboard events worked like this:

  • User clicks a key;
  • WoopsiKeyboard object raises an action event;
  • Handler receives event;
  • Handler checks event type and source;
  • Handler gets the last key pointer from the source (ie. the WoopsiKeyboard instance);
  • Handler does whatever it needs to do with the key data.

If a key was held down, the keyboard would eventually refire the action event to indicate that the key was repeating. However, there was no way to distinguish between initial presses and repeats, and no way to detect key releases.

Keyboard events now work like this:

  • User clicks a key;
  • WoopsiKeyboard object raises a keyboard pressed event;
  • Handler receives “KeyboardEventArgs” object containing a pointer to the source (the keyboard) and the key that was clicked;
  • Handler does whatever it needs to do with the key data.

Much tidier. There are separate events for keys being pressed, released and keys repeating. It’s now simple to tell exactly what happened and respond accordingly.

2009-02-05

Woopsi Progress and News

After some delay, here is the latest Woopsi news.

First of all, there’s a new program out that uses Woopsi called ‘fileNinja”:

http://sourceforge.net/projects/fileninja/

fileNinja was released late last year, but it’s taken me this long to blog about it. Part of the reason for that is my inability to test it - the DS’ crappy wireless isn’t compatible with my Airport Extreme so I can’t use fileNinja. Shame, because it’s something I’ve wanted for a while - a DS FTP client.

In other news, EigenmathDS has had a new release:

http://sourceforge.net/projects/eigenmatds/

It now has graphs and matrices, amongst other updates. There’s a blog here:

http://eigenmatds.sourceforge.net/

Lastly, Richard “Quirky” Quirk, author of several of my favourite GBA homebrew games (including the excellent Cyclone, which I should really copy to the DS) has decided to use Woopsi as the GUI for a new version of Bunjalloo, which appears to be the last remaining homebrew DS web browser. His blog can be found here:

http://quirkygba.blogspot.com/

Regarding Woopsi progress, Quirky has submitted about a dozen bugfixes for memory leaks, crashes, etc. Check the changelog for a full list. They include a number of SDL fixes, including fixing the file requester for SDL operation. I’ve added a few bugfixes myself, and a couple of new features as requested by leonelhs in the forum. It is now possible to retrieve children from gadgets by their index number (Gadget::getChild()) and it is possible to determine how many children a gadget has (Gadget::getChildCount()). There’s a context menu example in the “examples” folder, too.

2009-01-19

More FileRequester Fixes

Thanks to Quirky’s patch, the FileRequester class now works correctly with devKitARM r24. I’ve tested it on the real hardware (none of the emulators seem to have working FAT emulation at the moment, which is odd as I’m sure I’ve used it before) and the latest version of libfat is considerably faster than the old version. In the “filerequester” example, the FAT system initialises in about a second, compared with about five seconds for the previous version.

Whilst testing that, I noticed that the requester wasn’t highlighting options correctly any more. Traced this to a bug in the ListBox class - the selection routine wasn’t redrawing the gadget. This only became an issue since I fixed the bug that prevented single selection mode from being activated.