2008-10-05

Cursor Drawing Done

That was easier than I thought. Here’s a demo; use the left and right d-pad buttons (when the keyboard is selected) to move the cursor left and right. Tap a key on the keyboard to insert text at the cursor position.

Woopsi Cursor Demo

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

Bugfixes and Refactoring; Xcode Goes Bananas

Loads of updates today. First of all, almost all of the classes are now javadoc commented. I’ve just got the skinned classes left to do, but as I might revist those I’m not documenting them for now. I need to document each class’ members, too. The autodocs generated by doxygen now look quite good, although there’s a lot of scope for more detail.

Next up, bugfixes. John spotted an overflow problem in the Font and MonoFont scanGlyph() function which was preventing font bitmaps with dimensions >=256 pixels from working properly. The MultiLineTextBox restores its visible/invisible property properly after being resized. The ContextMenu crash I found earlier seems to have been a bug in the PacMan demo rather than the ContextMenu itself, which I’ve also fixed.

On to the big changes now. There are two big API changes in the latest SVN code. I’ve refactored the active/inactive/focused/blurred system. The “active” flag has been replaced by a “hasFocus” flag, “setActiveGadget()” has become “setFocusedGadget()”, “_activeGadget” has become “_focusedGadget”, and the “setActive()” method has been absorbed into the “focus()” and “blur()” methods. There isn’t a huge number of differences, but it has made the whole thing much easier to follow.

The next big change is the visible/invisible system. This has bugged me for a while. If I call “gadget->setVisible(false)”, the gadget stays on the screen. I have to call “gadget->erase()” to make the gadget go away, but that doesn’t make “gadget->isVisible()” return false. What is going on?

The problem is that the “visible” flag and access methods don’t refer to gadget visibility; they refer to whether the gadget’s drawing methods are enabled or not. What I’ve done is give this block of functionality more sensible names. The “visible” flag has been replaced with a “drawingEnabled” flag. The “setVisible()” and “isVisible()” methods have been removed, and “enableDrawing()”, “disableDrawing()” and “isDrawingEnabled()” methods have taken their place. As an added bonus, “isDrawingEnabled()” only returns true if the gadget is neither deleted nor hidden, which makes working with it a little easier.

It’s now possible to determine if a gadget is hidden or not by calling its “isHidden()” method. The hidden status is stored in a new flag. There’s another new flag, too - I’ve moved the “visibleRegionCacheInvalid” bool into the flags struct.

Another thing that’s been bugging me is the spelling of “closable”. All occurrences of “closeable” in the flags, flag enum and getter methods are now spelt correctly.

Lastly, I’ve split the PacMan demo up into separate files for each class. Much easier to read now.

All of this has the potential to cause mayhem for people who are upgrading…

In other news, I decided to try and fix the ContextMenu crash bug last night by using the SDL version of Woopsi in Xcode. Synced the two versions in SVN, pulled down the latest copy in OSX, loaded up Xcode. Or tried to. Xcode crashed before it had finished loading. It refused to start up.

Had a look around on the internets, which recommended that I try repairing permissions in the Disk Utility. That didn’t work. The other suggestion was that it could be a corrupt setting in my user’s library folder. Tested this by moving everything out of the library to another folder. No luck. Started copying everything back, but due to the incompetence of the user stupidity of Finder I found that I’d copied some of the folders into other sub folders, and didn’t have any way of getting them back. Nuts.

Unfortunately, my Time Machine backup was a week out of date due to the incompetence of the user way Airport Extreme disks have to be mounted manually before Time Machine can back up to them. After a hell of a lot of fighting with Time Machine I got it to restore most of my settings. The bookmarks I’ve lost can’t have been all that important as I can’t remember what they were.

Xcode still didn’t work.

However, I tried to load it up this morning and it worked fine. Madness.

2008-04-07

Context Menu in Progress

The context menu is going quite well. You can see it working in the demo - hold down one of the shoulder buttons and tap the default bottom screen and a test menu will appear. Alternatively, bring down the PacMan screen and shoulder-click the PacMan window - you’ll get a context menu with two (two!) functioning options.

The context menu is implemented as a gadget within the Woopsi class. When the Woopsi singleton class is instantiated the menu gets created, added to the child list, and hidden. Each time it is shown, it gets moved from the hidden array back into the main array (which ensures it’s always at the top of the gadget stack), and every time it is closed it gets moved back to the hidden list.

Working with the context menu is very, very easy. Gadgets now have a list of name/value pairs that get automatically added to the context menu when a user shoulder-clicks on the gadget. If there are any items in the list, Woopsi automatically builds and displays the context menu. Clicking an item causes the gadget that opened the menu to raise a “EVENT_CONTEXT_MENU_SELECTION” event. Selecting an item, or clicking anywhere but within the menu, automatically closes it.

Building up a context menu works lilke this:


AmigaWindow* w = new AmigaWindow(blah);
w->addContextMenuItem("Item 1", 0);
w->addContextMenuItem("Item 2", 1);
w->addContextMenuItem("Item 3", 2);

This creates a menu definition for the AmigaWindow object with three items. The first parameter is the name of the menu item, the second is its value. When the user shoulder-clicks the window, the context menu will automatically open with the three items displayed. These values could, of course, be added in the constructor if you are subclassing (instead of added outside the class).

Interpreting menu selections can be handled in two ways. The easiest way is to give your gadget an event handler, and have that handle the event in the usual way:


void handleEvent(EventArgs e) {
    switch (e.type) {
        case EVENT_CONTEXT_MENU_SELECTION:
            switch (woopsiApplication->getContextMenuValue()) {
                case 0:
                    // Do something for the first option
                    break;
                case 1:
                    // Do something for the second option
                    break;
            }
            break;
    }
}

The function “woopsiApplication->getContextMenuValue()” will return the value of the last selected menu item.

The alternative way to handle menu selection (in case you are subclassing and don’t want to muck about with event handlers) is to override the “Gadget::handleContextMenuSelection()” function. The selected menu value is passed in as an argument, so you can switch() the value directly.

Doing all of this involved some changes. First of all, there’s the new “EVENT_CONTEXT_MENU_SELECTION” event. There’s also a new “EVENT_SHIFT_CLICK” event, which is raised when a gadget is shoulder-clicked (I’m avoiding “right click” as a term, and “shift click” suggesting a modifier key seemed like a non-hardware specific alternative). There’s a new “Gadget::shiftClick()” function that works in much the same way as “Gadget::click()“, and there’s a new “Gadget::raiseShiftClickEvent()” function. Woopsi’s “handleClick()” function now checks to see if it should close the context menu after it has passed the click event through the hierarchy.

There’s also a new value in the gadget’s _flags struct - “shiftClickChildren”. If this is set to false, shift clicks will not get sent to the gadget’s children and instead are processed by the gadget itself. This is useful in two circumstances. If you have a window, for example, and want the same context menu to show regardless of whether the user clicks the window or a button within a window, you should call “setShiftClickChildren(false)”. This will prevent the window from trying to get its children to handle the click instead of itself. The window’s context menu will therefore be shown regardless of whether the user clicks the window or one of the window’s children.

The second instance where it is useful is for compound gadgets, or gadgets made up of several other gadgets (ie. the ScrollingTextBox, which is composed of a MultiLineTextBox and a ScrollbarVertical, which itself consists of three other gadgets, one of which also consists of multiple gadgets). In this instance, only the main gadget should be capable of opening a context menu.

There are a few bugs remaining. The menu doesn’t seem to grow horizontally to accommodate all of its items correctly, nor does it try to keep within the bounds of the screen. It should be possible to suppress its focus-grabbing (if it does actually grab the focus; I’m not certain) by overriding its shiftClick() and click() method. Lastly, I managed to make it crash the real DS so I need to check for pointer problems.

Not a bad start, though.

Oh, a couple of bug fixes elsewhere in the code. The “Textbox::resize()” method remembers the value of the visible flag correctly (wouldn’t believe how much trouble that caused me), the gadget class’ destructor calls “destroy()” on its children instead of deleting them (dunno how that was still functioning) and “Gadget::show()” correctly invalidates its parent’s visible rect cache.