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.

I’ve implemented the suggestion in the previous post (thanks to Jeff for the vote of confidence). The keyboard now appears if a textbox is clicked. The bottom screen jumps up to the top display and the keyboard screen takes its place. When the keyboard is closed, the original screen drops back down into place in the bottom display.

The keyboard can be closed by either clicking on the return key (which also fires an action event) or by clicking a new “OK” button at the bottom of the keyboard. I’ve changed the keyboard so that it no longer inherits from the AmigaWindow class, giving it much more flexibility. The automatically-appearing keyboard is an instance of a new “WoopsiKeyboardScreen” class hosted by the Woopsi gadget.

Getting the textbox to listen to d-pad presses to move the cursor was very simple. The WoopsiKeyboardScreen and keyboard no longer attempt to steal focus, which means that the textbox that opened the keyboard will keep focus even though the keyboard is open and being interacted with. This allowed me to change the cursor so that it is only drawn when the textbox has focus, solving the dilemma of indicating which textbox is being edited with the keyboard.

I noticed that pulling down the top screen did not correctly erase that screen from the top display. It transpires that this is an issue with the GraphicsPort class - for various reasons it isn’t possible to have a gadget that exists on both displays, which the Woopsi gadget attempts to do. To fix this I’ve added two decoration screens to the Woopsi gadget that sit behind all other screens. They ensure that exposed areas of the Woopsi gadget are cleared correctly.

Double-click the textbox (move the debug screen out of the way to see it) to bring up the keyboard. You will be able to type into it, delete characters from it, and
close the keyboard by tapping return or OK.

One issue that still remains in Woopsi is the question of how the active textbox should be represented visually. One possibility that I tried was to make the cursor visible only when the textbox had focus. However, when a textbox is being used for input, the active textbox does not have focus; the keyboard does instead.

I’ve got another possible solution for this. When a textbox is clicked:

Flip the bottom screen to the top display.

Automatically open a new keyboard screen on the bottom display.

Automatically wire up the keyboard to the textbox so that the textbox receives input from the keyboard.

Automatically wire up d-pad events so that the textbox receives cursor left/right/up/down key presses from the keyboard.

Add a “close” button the keyboard so that it can be closed.

When the keyboard’s close button is clicked:

Close the keyboard screen.

Flip the top screen back down to the bottom display.

Thoughts? One objection is that clicking the textbox accidentally will cause the entire display to shuffle itself around. In that case, perhaps the textbox should have an “edit” icon somewhere? Or need to be double-clicked before it could be edited? Or should I not worry about this at all, and leave it up to the developer to implement their own solution?

More tidying up and throwing away. A while ago I altered the TextBox so that it would scroll to follow the cursor. The code worked but it was ugly, and the scrolling effect was ugly too. The text was always aligned to the left of the textbox, so if the cursor scrolled to the right partial characters would appear on the right-hand side of the box. What a user would expect to happen is see the characters on the right aligned nicely whilst the characters on the left were partially visible. I ripped out the existing code and replaced it with a much improved system.

I’ve removed the “_padding” variable from the Label, Requester, FileRequester and Alert classes. This variable was used to force the gadgets’ children to leave a border between themselves and the real gadget border. I’ve replaced it with the newly improved border code. Related to this, a number of gadgets have improved and simplified drawing routines.

Finally, I’ve added a new set of XOR drawing routines. These accept a colour parameter to XOR against, allowing for more interesting XOR effects.

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:

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.

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.

Some minor Woopsi news. The ListBox removes itself from its ListData object’s list of event handlers when it gets deleted. This is slightly redundant at the moment, as the ListData object is internal within the ListBox and will therefore be deleted once its owner is deleted. However, it’s possible that I’ll allow the ListData class to be defined outside of the ListBox eventually, so this change does make sense in that scenario.

The DimmedScreen is now an “official” gadget. Since I’ve moved all of the dimming code into the GraphicsPort, the DimmedScreen only contains about a dozen lines of code, but creating the dimmed effect requires some knowledge of exactly how Woopsi’s drawing/erasing systems work. Due to this (plus the fact that it’s a neat trick, and isn’t hacky any more) it made sense to promote the DimmedScreen from bonus gadget to official gadget.

Lastly, I’m starting to finish off the cursor support in the text boxes. When the TextBox or MultiLineTextBox gadgets have focus, pressing left or right on the d-pad causes the cursor to move. There are a couple more changes to be made to the MultiLineTextBox:

Pressing up or down on the d-pad needs to move the cursor up or down a row;

Tapping on the text box with the stylus needs to cause the cursor to jump to the stylus location.

Both of these are rendered slightly more complex due to the MultiLineTextBox’s support for different alignment styles.

I’ve also got a few alignment bugs to fix in the MultiLineTextBox, too. Once these are done, I’m not sure there’s anything left to do. I’ve got all of the gadgets created that I wanted to, the event system is tidier and working (note to self - do I need to refactor the radio button group’s event system?), and I can’t think of any other outstanding bugs. I should probably have made a to-do list before I got distracted by my exam revision (provisional average grade is just over 80%, which should put me well into the “distinction” category - hurrah!).

The next steps will be documenting and testing Woopsi, as well as writing plenty of example programs.

Two changes today. I’ve optimised the MultiLineTextBox’s cursor drawing routines. It’s not as fast as it could be - the fastest way to draw it would probably be to inject it into the main drawText() routine - but as that class changes so much I think it’s best to keep it readable at this stage.

Secondly, I’ve replaced the Bitmap struct with a Bitmap class. The struct wasn’t being used anywhere. The class includes all of the drawing functionality previously
only available in the SuperBitmap, so it is now possible to create and manipulate bitmaps entirely in memory without an associated gadget. This should, I think, answer one of Jeff’s earlier queries. I’ll get around to stripping the code from the SuperBitmap and replacing it with the Bitmap class later. Not sure if the SuperBitmap is going to inherit from the Bitmap or just contain an instance of it yet.

Following on from the last post, the Date class now works correctly (as far as I’ve tested). I’ve tidied it up a bit too, and moved most of the code out of the header file and into the cpp file. I’ve also fixed rounding errors in the MultiLineTextBox’s vertical alignment option (centred) - it moves around as you’d expect now. Lastly, the WoopsiString class doesn’t crash if you try to insert text into an empty string. This was causing the demo released the other day to lock up if you deleted all of the text and then pressed a key.

In other news, there are now examples illustrating the use of the Date class and ProgressBar gadget. I’m hoping to get examples done for all of the gadgets, where possible, because they double as unit tests as well as providing guidance for users.

I think it’s about time for another release…

EDIT: Oh, and the cursor in the MultiLineTextBox is hidden by default, now that I’ve got the preliminary work done.

The MultiLineTextBox now has cursor support. At present there’s no simple way to move the cursor up or down between rows, and it lacks the stylus support built into the standard TextBox. I’ll get around to that next. However, the cursor can easily be made to jump to different locations in the text, so supporting d-pad movement is simple. There’s still a lot of optimisation I could do, but I think I’ll leave that for later.