2010-03-09

Woopsi 0.46 Beta Released

Yet another release. This one is special as it marks Woopsi’s transition from alpha to beta. Woopsi is now in beta, which means that the feature set is frozen and I’m going to concentrate on documentation and bug fixing.

Woopsi is the poster child for the dangers of feature creep. I started it back in September 2007 with the idea that I’d support screens, windows, buttons and a textbox, and get the whole thing written in a month. Two and a half years later, Woopsi has evolved into a fairly complete windowing system with scrollers, listboxes, UTF-8 encoded unicode strings, TrueType font support, a suite of clipped 2D drawing tools, etc. A full SVN repository checkout consumes over 500MB of disk space.

I’m finally happy with the feature set. There are, of course, more features I’d like to add, such as skinning and bitmap loading, but it’s reached the point that I’d prefer to stop adding to the code and document what I’ve got so far.

I’m also very interested in feedback about the library. If you’ve been tempted to use Woopsi but have been put off by the ever-changing API, now is a good time to get started with it. The more feedback I get, the more bugs I’ll be able to fix.

Once all of the documentation is done and I hit version 1.0, I’m considering talking to some of the homebrew websites to see if they’d be interested in running a Woopsi-based competition. Just an idea at the moment, and I’m not sure how practical it will be or if they will be interested. Still, worth a try.

This latest release includes the last set of new features. MultiLineTextBoxes implement the cursor fully. The cursor can be moved up, down, left or right with the d-pad. It moves to the cursor’s position when the textbox is tapped with the stylus. The textbox scrolls to follow the cursor when it is moved. The MultiLineTextBox has been significantly refactored. All of its alignment options finally work, and it even shows the keyboard when it is double-clicked (though this behaviour can be disabled via a new disableKeyboardPopup() method).

I’ve added a few base classes that define the interface for some of the gadgets, enabling composite gadgets that mimic their behaviour to present a more consistent set of methods.

There are a few other bugfixes and improvements; they are all listed in the changelog, as usual.

Download the new release from SourceForge:

2010-02-19

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.

2010-02-07

Simplifying the API

Using Woopsi’s keyboard as an input device has been needlessly complicated. To output keyboard input, a programmer would need to:

  • Create a TextBox as the output gadget;
  • Create a WoopsiKeyboard as the input gadget;
  • Create a class that inherits from KeyboardEventHandler that will receive input from the keyboard and direct the output to the textbox;
  • Add the new class as an event handler to the keyboard.

This pattern would need to be repeated every time the keyboard needed to be used.

Clearly this is a bad idea, so instead I’ve made the TextBox and MultiLineTextBox gadgets into KeyboardEventHandlers. Using a keyboard now requires these steps:

  • Create a TextBox as the output gadget;
  • Create a WoopsiKeyboard as the input gadget;
  • Add the textbox as an event handler to the keyboard.

Missing out the middleman event handler reduces the amount of code necessary to achieve keyboard input from two dozen lines and an extra class down to around three lines.

Whilst on a simplifying drive, I was looking at the click(), release(), doubleClick() and similar functions in every gadget class. These were complicated to work with. Overriding them is essential if one is subclassing Gadget in order to make a new gadget, but it is very easy to break the methods by overriding them incorrectly.

Consider the click() method. This method contains around two dozen lines of code, all of which is absolutely crucial for the gadget to function properly when clicked. Subclasses had to call the base class’ click() method as the overridden method’s first step or the gadget wouldn’t work. The code worked like this:

click() method
    check that this is a valid click
        check that gadget is enabled
            run custom behaviour
            return true
    return false

Forgetting any of these steps resulted in a non- or semi-functional click() method.

Most of the time the click() method is overridden it is to add some trivial extra functionality, so having to write out a dozen lines of boilerplate code just to add a redraw() call or something similar is absurd.

The Gadget class now has a handful of stub methods that can be overridden in subclasses to avoid this problem. Instead of overriding click() - which is still possible, if really bizarre behaviour is required - developers should now override the new onClick() method. This function is called when the gadget determines that the click really is valid, so all that needs to go into the onClick() method is any code that is relevant to that method.

For example, if a gadget should redraw when clicked, this is the code needed:

void onClick(s16 x, s16 y) {
    redraw();
}

Conversely, an old-style click() method to do the same looks like this:

void click(s16 x, s16 y) {
    if (Gadget::click(x, y)) {
        if (isEnabled()) {
            redraw();
            return true;
        }
    }
    return false;
}

The new-style approach is considerably terser and has no vital steps that can be accidentally missed.

The new stub methods are as follows:

  • onClick() - called when the gadget is clicked.
  • onDoubleClick() - called when the gadget is double-clicked.
  • onShiftClick() - called when the gadget is shift-clicked (ie. when the shoulder button is held down; this triggers the context menu).
  • onDragStart() - called when a dragging starts on a gadget.
  • onDrag() - called when a gadget is dragged.
  • onDragStop() - called when a dragging stops.
  • onRelease() - called when a clicked gadget is released within its boundaries.
  • onReleaseOutside() - called when a gadget is released outside its boundaries.

I have switched to this new approach throughout the Woopsi library and it has made a lot of the gadget classes shorter and easier to understand.

Related to this, Gadget::click() no longer includes a call to setDragging(). If a gadget should respond to stylus drags, it should call this method in its onClick() override. I must get around to renaming that to “startDragging()” or something similar…

In other news, ScrollingPanel no longer includes a raiseScrollEvent() method. I’ve moved this into the GadgetEventHandlerList class. The delete key on the keyboard deletes characters in front of the cursor in the TextBox and MultiLineTextBox gadgets. Lastly, I’ve removed the parameters from the GadgetEventHandlerList::raiseActionEvent() method.

2010-01-05

Unicode News

Unicode support in Woopsi is coming along nicely. Most of the gadgets now expect to receive WoopsiString objects instead of chars or char arrays when dealing with text. As the WoopsiString now works with UTF-8, that means most of the gadgets now also support UTF-8.

I’ve overloaded the WoopsiString’s “=” operator for WoopsiString objects, char arrays and u32s, which means that any parameter that should be a WoopsiString can accept char arrays or u32s instead. Converting from string literals to WoopsiStrings is therefore seamless and automatic.

The glyphs are now stored in a separate “GlyphFont” font. The default glyph font is stored in the defaultGadgetStyle global variable, in the same way that the default system font is stored. Converting this unearthed a couple of bugs in the Bmp2Font program - it doesn’t write out the resultant font constuctor properly (it misses out the transparent colour and uses the raw bitmap data instead of the bitmap wrapper object). I still need to fix those.

The most problematic changes were convincing buttons that displayed glyphs to centre their text properly (turns out that they were centring based on the wrong font) and getting the MultiLineTextBox to redraw at a reasonable speed. After some cunning hacking about with the WoopsiString and the MultiLineTextBox I’ve got it back up to something approaching its old speed.

Regarding speed, unicode definitely has a negative impact. Having to scan through an entire string to locate a codepoint is a horrible performance killer. WoopsiString::getCharAt() is definitely not a function you should use if you’re reading out sequential characters, as it looks for the codepoint from the start of the string each time it is called. It’s much more advisable to use getToken() to get a pointer to the first token, read out the codepoint, then increase the pointer by the size of each character as it is read. Before I went back to optimise things, the MultiLineTextBox was using getCharAt() when drawing and so was the PackedFontBase, meaning the string was being scanned through twice for each character being printed. Xcode’s “Shark” profiling utility noted that the MultiLineTextBox was using 31% of the total CPU time when printing a line of text solely because of all the UTF-8 parsing. Yikes.

There are still a lot of gadgets to convert over, and probably a hefty set of bugfixes and optimisations to make.