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-22

Key Repeats and Memory Leaks

As mentioned in the last post, both the TextBox and the MultiLineTextBox respond to key repeats and moved the cursor appropriately. The implementations in both were exactly the same. They included a WoopsiTimer gadget that was started when a key was pressed and stopped when the key was released. By tracking the last key pressed, they could respond to the timer’s “action” event and move the cursor in the appropriate direction.

This approach worked perfectly, but it was somewhat cumbersome - any gadget that needed to respond to key repeats would have to implement its own copy of the solution outlined above. A better approach would be to bake it into the core of the system itself, which I have now done. The base Gadget class includes a keyRepeat() method that is called when a button on the DS is held for the required amount of time (25 frames for the initial repeat; 5 frames for subsequent repeats). All gadgets can raise key repeat events.

In other textbox news, if the string is longer than the box it is now possible to scroll the text using the d-pad. When the cursor hits either end of the box, the text will scroll one character to the left or right. The cursor will not scroll outside the boundaries of the textbox. Additionally, if the width of the text exceeds the size of the box, the horizontal alignment options are overruled and the box switches to left-aligned. This ensures that the scrolling works.

I changed the way that the gadget destructor worked back in April 2008. It was more intelligent and would recursively delete the children of a gadget that was itself being deleted. However, I’d noticed that Xcode would occasionally throw a segfault when deleting gadgets. A lot of testing later and I found the problem - the child deletion didn’t quite work properly.

The parent gadget was doing this:

for i = 0 to child count
    delete child[i]

Meanwhile, the child gadget was doing this:

remove self from parent

When the child removed itself from its parent, the child count reduced. Thus, children at the end of the list were not being deleted. This caused memory leaks and, in certain situations, segfaults.

I’ve changed the parent’s loop and fixed it.

2009-12-23

Woopsi 0.43 Released

Another release escapes from the Simian Zombie factory.

In addition to the changes discussed recently, which mainly concern new fonts, new .NET utilities for working with fonts, and many bugfixes, this release moves event handler management out of the Gadget class and into a new GadgetEventHandlerList class. This is now responsible for maintaining the list of event handlers added to a gadget and raising any events to those handlers. This is an under- the-hood change that won’t affect the majority of user code. If you have created custom gadgets, you may find that you now need to call _gadgetEventHandlers->raiseXXXEvent() instead of just raiseXXXEvent().

Download it from SourceForge as usual:

Additionally, I have zipped up some pre-built example ROMs. Download them below.

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-04-01

Refactoring Events and Re-implementing the Wheel

Woopsi progress, at last! I’m neglecting my exam revision to bring you more Woopsi changes.

First up, the ListData class (that forms the data container for the ListBox-derived classes) now raises events when its data or selected items are changed. That means the ListBox classes no longer need their own wrappers around functionality that the ListData class provides (adding items, removing items, etc) - the ListData class notifies the ListBox when anything changes, rather than the other way around.

There are a number of classes involved in providing this functionality. A new “EventArgs” template class is the base class for all events within Woopsi. A “ListDataEventArgs” class contains data pertaining to all ListData events. Lastly, a “ListDataEventHandler” class listens for, and handles, all ListData events.

Related to this, I have refactored the existing gadget event system. Note that these are breaking changes and will definitely require you to change your code to match. Gadgets can now have multiple event handlers. Handlers are added with the “Gadget::addGadgetEventHandler()” method, and removed with the “Gadget::removeGadgetEventHandler()” method. Events are raised to all registered event handlers. The “EventHandler” class is now called “GadgetEventHandler”, and the “EventArgs” struct has been replaced with a “GadgetEventArgs” class.

In addition to this, I have replaced the “handleEvent()” method from the old “EventHandler” class with a suite of event handler functions, such as “handleClickEvent()”, handleDragEvent()“, etc. I originally followed this pattern, as described back in September 2007, but swapped to the single method on Jeff’s advice. As Woopsi has grown, though, this approach has led to some fantastically bloated and unintuitive handleEvent() methods. Switching back to the old system makes the code considerably tidier. Note that the new methods return void, not bool, which was always rather redundant.

These changes make the event system more extensible, in that developers can implement their own EventArgs-based classes for their own set of events.

I still need to remove the “EventType” enum from the “GadgetEventArgs” class. I’ll get around to that at some point.

Most of these changes have been inspired by my recent work with Swing. Swing gets a lot of things wrong - such as being horribly slow and featuring the worst layout system I’ve ever tried to use - but it also gets a lot of things right. It’s pleasing to see that an awful lot of the design decisions made for Woopsi are mirrored in the design of Swing (Gadget vs. Component, ListData vs. ListModel, EventHandler vs. Listener, Decoration vs… um… Decoration, etc). Though they have been developed entirely separately, Swing and Woopsi have both managed to re-implement a more-or-less round wheel. In some areas the Swing wheel is slightly rounder than the Woopsi wheel, so I’m just shaving Woopsi down to match.

2008-10-04

Functioning Keyboard

The keyboard works!

I’ve added the missing glyphs to the sysfont. Came up with a new glyph for the Ctrl key - a caret (^) with “ctl” written below it. The caret is the standard UNIX symbol for the Ctrl key but I needed some way to distinguish if from the actual caret key, hence the dinky “ctl” text.

I’ve re-worked the layout a bit - it’s now a more standardised American layout rather than the British layout I was originally working towards. It seems that the pound sign (£) isn’t in the standard 7-bit ASCII set; it’s actually 163 in the extended ASCII set. That places it well into the region of glyphs that I’m not intending to support, so there was little point in trying to replicate the British layout if it had a key missing.

The keyboard now acts on click rather than release events. Reacting to release events is of no use if we want to support key repeats whilst the keys are held down. However, the modifier keys still react to release events on other keys. We don’t want the modifiers to reset themselves as soon as a key is pressed because we might need to read the state of the modifiers outside of the keyboard class. So, clicking a key now fires the EVENT_ACTION event from the keyboard, and releasing a key causes any modifiers currently in the “down” position to revert back to the “up” position.

As it makes more sense when you see it in action, here’s the demo that produced the screenshot above:

Keyboard Demo

The code for this is in a new “keyboard” example in the “examples” folder. Wiring the keyboard up to an output window is trivial; there’s barely any code in the example.

Other new things include WoopsiKeyboard::isShiftDown() (etc) functions for reading the modifier states, WoopsiKey::getValue() for getting the display value of a key, and addText(char) methods for both the TextBox and MultiLineTextBox classes.

Things left to do are:

  • Key repeats (still)
  • Some way of removing characters from text boxes, to enable the delete key to work

Once that’s done, I’ll be all set up to enhance the various text box classes with cursor support.

EDIT:

This demo highlights the vertical alignment problems still in the MultLineTextBox class. Must get that fixed.

2008-05-14

An Assortment of Woopsi Changes

It’s been quiet around here lately. The weather has been gorgeous and I’ve been enjoying the first sunny week for 18 months. Knowing the English weather this is probably all the summer we’ll get. It’s already getting colder and I’m told it’ll be raining again by Friday.

Anyway, I’ve made a few fixes and changes that I haven’t got around to documenting yet. Most of the fixes are things that have been raised in the forum. Jeff pointed out an overflow problem with the slider grip calculations, which is now fixed. He also noted that the grip didn’t move as expected when the gutter or scrollbar buttons were clicked; that’s fixed too. The Gadget::addGadget() method will now apply the focus properly if a gadget is added that thinks it already has focus, and I’ve made a number of improvements to the focus system.

The way Woopsi handles clicked gadgets has also been improved. Rather than having a chain of clicked gadgets throughout the hierarchy, the Woopsi class now has a single pointer to the clicked gadget itself. Much tidier. Getting this working meant re-factoring some of the window and screen gadgets, which now rely more on straightforward event handling and have a lot less kludgy code.

Lastly, one for John - the MultiLineTextBox class will now handle single-line text properly. There were a number of problems with it. Firstly, the Text::wrap() function could get into a circumstance where it would read past the string terminator. Secondly, the Text::stripTopLines() function wasn’t recalculating the wrapping locations, which meant that it thought it had more lines than it actually did, and it didn’t know where those lines started or finished. Lastly, the MultiLineTextBox::setText() function wasn’t resizing its canvas properly. Along the way I fixed two of the vertical alignment options, top and centre, but the bottom alignment is still broken. There’s still a lot of potential improvements that could be made to this class.

There are a few new features. There’s a new “EVENT_RELEASE_OUTSIDE” event which gets fired when a gadget is released with the stylus outside the boundaries of the gadget. This is in addition to the usual “EVENT_RELEASE”, which gets fired when the stylus is within the gadget boundaries. Usually we’re only interested in the standard release event (for standard buttons), but there are situations where it is important to handle any release, such as releasing the window titlebar (in order to drop it to its new location).

Related to this, the EventArgs struct has two new properties - “eventVX” and “eventVY”, which are mainly used by the “drag” event to report how far the stylus was dragged.

2008-04-23

Modal Windows and Documentation

Had a go at trying to make modal gadgets yesterday. Not going to happen. Not in a clean way, anyhow. There are just too many problems. If I moved a lot of the physical event handling into the Gadget class out of the Woopsi class, I’m still left with a lot of problems:

  • How can I increase the VBL count?
  • How can I manage the context menu?
  • How can I manage the deletion queue?

There are plenty of other questions along the same lines. It’s possible, but the solutions would be very ugly. For the most part, Woopsi’s code is quite tidy at the moment and I don’t want to start kludging it up.

Instead, I’ve implemented a “ModalScreen” class, which consists of about 2 lines of code. It’s just a Screen class that calls “erase()” in its draw function, making it transparent. Since it fills the entire screen, it blocks access to any gadgets in lower screens and thus makes its own children modal. No kludges, one (tiny) extra class. Neat!

There are now “shelved” and “unshelved” events, which get triggered when gadgets are shelved or unshelved. The hide() and show() methods now trigger the hidden/shown events properly.

Lastly, I’ve made some updates to the documentation. The calculator tutorial should now work, as should the “hello world” tutorial. I’ve tidied up a few other things. Whilst I’m on the docs topic, I’ve switched the Woopsi web link on the SourceForge site to this blog. It should get me a bit more traffic and reduce the negative impact of the out-of-date documentation.

Oh, one last thing. There’s a new version of DeSmuME out (0.8) for Windows, OSX and Linux. This version works with Leopard.

DeSmuME