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

Keyboards Working

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.

Here’s a demo of the new textbox/keyboard functionality:

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.

2010-02-13

Active TextBox and the Keyboard

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?

2010-02-13

Simplifying the API Part 5

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.

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-06-13

Cursors, Text Boxes and Other Changes

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.

2008-10-31

Cursors and Bitmaps

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.

2008-10-26

Fixes and Examples

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.

2008-10-25

Cursors and Textboxes

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.

Download a demo here:

CursorTest

Cursor Test

Lastly, I’ve synced the OSX version with the standard version.

2008-10-20

MultiLineTextBox Alignment Done!

So, the last set of functions still didn’t co-operate. New solution - use the existing (and working) pre-clip routines for top-aligned text and the situation wherein there are more lines of text than are visible (this situation is implicitly top-aligned). In all other cases, just draw the entire damn box and let the GraphicsPort clipping routines deal with it.