Woopsi 0.99.2 Released

Woopsi 0.99.2 is now out. Grab it from SourceForge, as usual:


The font system has had some minor changes made. I finally got around to ripping out the Font and MonoFont classes, as the PackedFont classes perform the same job with far more efficiency. They’ve been moved to the “extras” directory in the SVN repository in case anyone should ever need a reference for creating new font classes.

All of the monochrome fonts that ship as part of the library are now PackedFont1 classes instead of PackedFont16. This highlighted a bug in the bmp2font .NET program that is included in Woopsi’s “tools” directory - it was allocating relatively huge bitmaps for the font data instead of the tiny bitmaps that were really necessary. I’ve deleted the “CourierMono” font as it was an exact duplicate of the “Courier” font. The GlyphFont is a PackedFont16-based class instead of a Font-based class.

This changes have reduced Woopsi’s footprint quite significantly:

  • 1.5MB from the size of the Woopsi library;
  • 2MB from the sourcecode;
  • 400K from the packedfont example ROM;
  • 60K from the helloworld example ROM.

A big saving for very little work.

Other changes include replacing the x, y, width and height members of the Gadget class with an instance of the Rect class (itself converted from a struct). A lot of the clipping work can now done be within the rect instance rather than throughout the codebase.

The FileListBox and FileRequester classes include a getPath() method that allows a developer to determine which directory the file requester is pointing at.

Other miscellaneous changes are detailed in the changelog.


GraphicsPort Refactored

There have been quite a few changes and improvements since the last blog post. The biggest one is another redesign of the GraphicsPort class. Back in November last year I stripped the drawing code out of the bitmap class and separated it into a new hierarchy. The GraphicsUnclipped class contained drawing methods that were not clipped. The Graphics class was a subclass of GraphicsUnclipped and clipped to the confines of a bitmap. The GraphicsPort also subclasses GraphicsUnclipped and clipped to the visible regions of a Gadget.

This seemed like a reasonable design, but it did have its downsides. As the Graphics and GraphicsPort classes were peers within the inheritance hierarchy, rather than one subclassing the other, there was inevitably some repeated code. More disturbing was the spaghetti-like integration between the classes, in which GraphicsPort would call a method in GraphicsUnclipped which would in turn call a method in GraphicsPort.

In addition to the inheritance problems, the GraphicsPort had its own internal problems. Its drawing methods used a variety of different co-ordinate systems. Some used the co-ordinates of the entire UI as their reference point, whilst some used the co-ordinates of the gadget, and others used the co-ordinates of the GraphicsPort itself.

The GraphicsPort is designed to work in two ways - it can be constructed and used within a gadget’s draw methods, or it can be created outside of the gadget and used to draw over the gadget. This meant that it needed to retain either a single clipping rectangle, used when drawing from within a draw method, or a list of clipping rectangles, used when drawing from outside of the gadget’s code.

All of these problems led to a horrible mess, which I have now put right. I’ve even introduced some new features in the process.

First of all, I’ve merged the Graphics and GraphicsUnclipped classes into a single class: “Graphics”. It contains all of the drawing methods available to the entire Woopsi system. The class can be given a clipping rectangle in which it can draw, which means that all of the drawing methods now clip. Any potential speed loss is negligible, since the drawing methods were already clipping to the confines of the bitmap being drawn to. I’ve just made that clipping area user-definable.

The GraphicsPort class no longer inherits from either of the other Graphics classes. Instead, it includes an instance of the Graphics class and presents a facade over the top. The GraphicsPort no longer has a convoluted set of responsibilities; it now:

  • Maintains a list of clipping rects for the gadget it relates to;
  • Receives drawing instructions;
  • Converts the co-ordinates from “GraphicsPort space” to framebuffer space;
  • Uses its Graphics object to draw to all clipping rects in its list.

The GraphicsPort does not maintain a single clipping rect in addition to a separate list of clipping rects. It now adds that single clipping rect to its list, erasing any previous data in its list. In this way, it achieves the same functionality without the extra complexity of two separate data storage mechanisms.

I mentioned GraphicsPort space and framebuffer space in the above descrition. I’ve put some work into trying to formalise the different co-ordinate systems that Woopsi uses. Descriptions of these will be included in the documentation whenever I get around to finishing it.

The GraphicsPort class is now entirely separate from the Gadget class. Previously the GraphicsPort included a pointer to the gadget that it was drawing to. This is no longer necessary, which should result in a (negligible) speed increase, since the GraphicsPort no longer needs to query the gadget’s Woopsi space co-ordinates using its recursive getX() and getY() methods.

I’ve removed the OutlineType enum from the Graphics class. This was not relevant to all gadgets so should not have been in the base class. This resulted in the addition of a new CalendarDayButton class and some changes to the WoopsiKey class so that they could remain “stuck down” when selected, which is represented by their outlines changing from bevelled out of the screen to bevelled into the screen.

I removed the padding variables from a few classes a week or so ago. I’ve now done the same to the MultiLineTextBox and replaced it with larger border sizes. This change, coupled with the rationalisation of the GraphicsPort’s co-ordinates, finally enabled me to identify the bug that caused the textbox to have graphical glitches when using a padding of greater than 12. That’s the first bug I’ve closed in the SourceForge tracker in months. The textbox’s vertical top alignment option works correctly, too.

Lastly, I’ve improved the cursor-following code in the TextBox again. When deleting characters from a string that is wider than the textbox, it is no longer possible to create a large gap between the end of the string and the right edge of the textbox.


A Bugfix Bonanza

If you have been following the comments for the previous few posts, or the forum, you’ll have seen work progressing at a frantic pace. Most of the thanks for this goes to Lakedaemon, who has been zapping bugs throughout Woopsi’s text rendering system. Working so quickly and not regularly blogging about it means that I’ve probably got a lot of changes to list here. Let’s see if I can collate them into some sort of order…

The WoopsiString class has seen a lot of changes. I’ve added indexOf(), lastIndexOf() and subString() methods. The first will find the first index of a particular character in the string and return its index. The second does the same thing, but in reverse - it finds the last index of a character. The last will return a pointer to a WoopsiString containing a subsection of the original string.

WoopsiStrings are no longer null-terminated. They already included a member variable that stores the string length, so appending a terminator was a waste of a byte.

The Text class’ wrap() function has had some long-standing bugs fixes. The strangest was probably an invalid comparison between a line index and a char index that was supposed to prevent the function from wrapping the entire text, but actually just screwed up the function most of the time, and made it slower the rest of the time. Not sure how that stayed in there for so long.

The next most significant bugfix was to the GraphicsPort’s clipScroll() method. This is used primarily in the ScrollingPanel gadget. It performs two functions. If it is possible to copy any of the existing visible region to the new location, the function copies it. This prevents redrawing, which is slow, if the required visual data is already available. Secondly, it remembers any rectangular regions that require redrawing and passes the list back to its caller. The ScrollingPanel uses the list to redraw any non-copied regions of itself.

The clipScroll() method worked perfectly if scrolling in just one plane, but as soon as horizontal and vertical movement was attempted simultaneously, it fell apart. Rectangular regions were missed out or copied to the wrong locations, particularly in the corners.

The function was originally tackling the problem backwards. It calculated the size of the source and destination rectangles then clipped those rectangles. However, this introduces two problems:

  • The rectangles could become different sizes;
  • The rectangles could acquire different offsets from their original locations.

Neither problem is too difficult to solve, but it does mean that if either problem arises, the function will not be able to copy the regions and must therefore fall back on the much slower redraw option.

The “correct” approach to the problem is to clip before calculating the sizes of the source and destination rectangles. That will ensure that neither of the two highlighted issues arises, which means that the copy can be used in preference to redrawing much more frequently.

I’ve finally got around to removing the glyphs from the Topaz and NewTopaz fonts. As the glyphs are now an entirely separate font, keeping that data in the bitmaps was a waste of space. Neither font requires colour now that the glyphs are gone, to I’ve converted Topaz from a Font to a MonoFont, and converted NewTopaz from a PackedFont16 to a PackedFont1. This has made both fonts considerably smaller.

Related to this, PackedFontBase::isCharBlank() returns the correct value (false) if the requested character is outside the range of the available bitmap data.

Dragging a background screen below the level of the topmost screen no longer crashes the system. The Screen class was attempting to copy a region with a negative height, which the DMA hardware really doesn’t like.

The FileRequester has had yet more fixes since the last post. When running in SDL mode, it now shows a dummy list of files and directories. I had intended to get it to show a real list of directories, but I ran into The Windows Problem - every major operating system but Windows has POSIX-compliant file system access. Unfortunately, of those major operating systems Windows is the most prevalent. I really couldn’t be bothered to write the POSIX code if most Woopsi developers are using Windows, and I couldn’t be bothered to write Windows code when I only run the SDL version in OSX. The compromise, which will work on all platforms, POSIX or not, was just to create the dummy list.

Finally, both the MultiLineTextBox and the standard TextBox respond to d-pad repeats. Holding down left or right on the d-pad when either of these two gadgets has focus and a visible cursor will cause the cursor to move repeatedly until the d-pad is released.


Woopsi 0.38 Released!

It’s been about 4 months since the last release, so it’s about time. Changes include the usual mix of bugfixes, many of which were patched by Quirky. Nothing new to report here.

New features include the complete overhaul of the event system, explained over a few posts a while ago; some optimisation, particularly of the clipping system; and the scroll() and copy() methods in the GraphicsPort.

New features not announced yet include the ability to specify whether a GraphicsPort object draws to the foreground or background rects, but this is of very little use for developers unless they’re creating a particularly crazy custom gadget. Secondly, I’ve taken the dimming hack from the DimmedScreen, tidied it up into a real feature, and added it to the GraphicsPort. This essentially makes the DimmedScreen useless, so I’ll remove it in the next release.

Download it from the SourceForge page.


Woopsi Optimisations and Fixes

I’ve made a number of fixes to Woopsi today. First of all, sliders and scrollbars. The slider grip now automatically resizes itself, meaning there’s no need to call resizeGrip() when setting the max/min/value properties of a slider/scrollbar. The resizeGrip() method is now private. Still on the topic of scrollbars, the grip in the ScrollingTextbox’s scrollbar now adjusts to the correct position when the textbox is first initialised. It was previously placed at the top of the scrollbar regardless of the position of the text.

Next, some fixes related to the event refactoring. The Alert, Requester and WoopsiKeyboard gadgets now handle events from their decorations correctly. Previously, the XOR dragging rect wasn’t being drawn correctly when they were first clicked.

Lastly, I’ve made some improvements to the visible region caching system. There are two types of rectangles used in the system. The first, which was already being cached, represents the entire visible surface of a gadget, ignoring any children it might have. The closest Java analogy would be Swing’s “glass pane”. Drawing to these rects draws over the top of the entire gadget, even over the top of any children the gadget might have. The second type of rects represent the visible portions of a gadget once its children have been drawn; they can be thought of as background rects, behind everything else in the gadget. Woopsi only draws the background rects; everything else is left to children to draw (and so on, recursively, until the screen is filled).

Whilst looking at the Gadget::redraw() method, I noticed that the background rects were being recalculated every time redraw() was called. I’ve now changed this so that both foreground and background rects are cached, which should provide a small speed boost. Note that, as I type this, I’ve realised that calling the rects “foreground” and “background” is a far better convention than their current names, so I’ll probably rename them soon.

Still on the subject of caching, I’ve moved the caching code and the rect splitting code into a new class, “RectCache”. The Gadget class is overloaded with functionality at the moment, so I want to extract some of it out to make it easier to work with. As a byproduct, I’ve made the “removeOverlappedRects()” method non-recursive, which should make that a little faster, too.

A major reason for trying to move this code into a separate class is to try and either optimise it (by making the rect splitting smarter) or replace it with the BSP tree code I came up with - oops - a year ago. I’ve fixed a raft of bugs in that code, but I’m still struggling to work out how exactly I can integrate it into Woopsi, or if I even need to. On a system with a little more CPU oomph and no DMA hardware (GP2x or Wiz, for example), it makes sense to replace the current system with the BSP code. It’s tidier, more efficient, and simpler. On the DS, however, I keep encountering optimisations that I’ve made in Woopsi that can’t be replicated using the BSP method and that offer significant speed gains. I might put up a longer post specifically on this topic later.