2008-04-24

Modal Gadgets Revisited

Took another look at the Woopsi code today and worked out a way to add modal gadgets into the system without too much effort. You can now make a gadget modal by calling its “goModal()” method, and you can make it stop being modal by calling “stopModal()”. Gadgets automatically cease to be modal when they are closed, hidden or shelved.

There is an important caveat here. In order to achieve this, the goModal() function actually implements its own run loop. A modal gadget steals control of the DS away from the Woopsi instance and assumes control itself. This means that your call to stopModal(), assuming you’re not just waiting for the gadget to close, needs to be included in code that is triggered by one of the modal gadget’s events. There’s no point in including it elsewhere because it’ll never get executed.

Since this functionality is implemented in the Gadget class, all Woopsi gadgets can now be modal. It doesn’t make much sense to make buttons modal, though. Also, you can stack up modal gadgets. If a modal window opens another modal window, when the latest one closes control will return to the previous window. When that closes, control will return to Woopsi. (It’s brilliant when desired functionality just arises out of nowhere - the nature of computer design gives us this stacking with no effort at all.) I’ve removed the ModalScreen class as it is now redundant.

The next alteration is rather complex. There’s a huge change in the way that Woopsi applications should be put together. Until today, the best way to put an app together was to have a main() function that looked like this:


int main() {
    woopsiApplication = new Woopsi();

    AmigaScreen* screen = new Screen("My Screen");
    woopsiApplication->addGadget(screen);

    woopsiApplication->draw();

    while (1) {
        woopsiApplication->run();
        woopsiWaitVBL();
    }

    delete woopsiApplication;

    return 0;
}

Following Jeff’s advice to tidy this up, this is no longer the approved way to do things. The suggested approach is now to:

  • Create a new class and inherit from Woopsi (this will represent your application)
  • Override the startup() function and add all of your GUI creation
  • Use a very simple main() function that just runs the application class

It’s actually simpler than it sounds. If we want to use the new approach with the above main() function, we would create a new class like this:


#ifndef _MYAPP_H_
#define _MYAPP_H_

#include "woopsi.h"
#include "amigascreen.h"

class MyApp : public Woopsi {
public:
    void inline startup() {
        AmigaScreen* screen = new Screen("My Screen");
        woopsiApplication->addGadget(screen);
    }
};

#endif

It’s a very simple class that inherits from Woopsi and adds a screen in its startup() method. In addition to this, we need to modify the main() function in main.cpp:


int main(int argc, char* argv[]) {

        // Create the application
    MyApp app;

        // Run the application
    app.main(argc, argv);

    return 0;
}

The main() function now only consists of three lines. We just create a new instance of our MyApp class and call its main() method. All of the other code, such as the while loop and the initial draw, is now handled by Woopsi. As a nice side-effect, drawing is now disabled until the entire GUI is set up. Gadgets will no longer get drawn gradually as they are being created.

This new main function obviously affects SDL users. Things have improved here, too. The SDL run loop has been merged into the Woopsi code along with the standard DS run loop (it is #ifndef’d out), which means it’s no longer necessary to have a large block of custom code in main.cpp when compiling for SDL. The main() function does need one minor adjustment, but this can be left in place even when compiling for the DS. The SDL version of the main() function above would look like this:


int main(int argc, char* argv[]) {

        // Create the application
    MyApp app;

        // Run the application
    app.main(argc, argv);

#ifdef USING_SDL

    // Shut down SDL systems
    SDL_Quit();

#endif

    return 0;
}

The SDL version of woopsifuncs.h has the USING_SDL define set, so this code is ignored unless you are compiling an SDL version of a Woopsi application.

The demo code uses this new structure should you need further examples of how to re-arrange your programs.

There are a couple of other changes. Buttons now change colour when they are clicked. I fired up WinUAE and noticed that Amiga buttons did this, and they looked a lot better for it.

I’ve also added a “CycleButton” class. The cycle gadget was the Amiga’s equivalent of a drop-down list. I don’t think drop-downs had been invented when the Amiga UI was created, so the programmers implemented a button that cycled through a set of options when clicked, changing its value.

Drop-downs are going to be difficult to implement for two main reasons - one is the limited space on the DS, and the other is the fact that child gadgets cannot be larger than their parents. Well, they can, but any extra area is clipped out and not drawn. Drop-down lists that were larger than the area of their containing window would, therefore, have to follow a similar approach to the context menu (ie. a gadget that exists in the Woopsi object as a peer of the screen objects) and it gets complicated. I’d probably re-use the context menu if I did implement drop-downs, which would work but would be something of a bodge. I think the hacks that added drop-downs to the Amiga followed the same approach.

So, cycle gadgets it is. Along with the (still in-progress list box) I think that covers all possibilities anyway.

One last change. I’ve made a few minor changes to the logo. Looks a little cleaner now.

Woopsi Logo

2007-09-29

Woopsi Logo

Had an idea for a Woopsi logo, so dug out Photoshop and put something together quickly. This is the new Woopsi logo (until I come up with something better):

Woopsi Logo

It’s got DS elements to it (the twin boxes) and Amiga elements (the cracktro-esque gradient on the text, and the Workbench-style pointer), plus it’s got the Simian Zombie Blue in there, too.

One bug I’ve found in Woopsi that you might encounter involves the SuperBitmap. For some reason, the DS doesn’t like the way I delete the internally allocated bitmap memory in the setBitmap() function. I’ve now changed the class so that this is all handled in a new constructor. Depending on which constructor is called, the SuperBitmap will either allocate internal memory or will use an external pointer. It can’t switch between the two states.

The reason I encountered the bug was that I decided to allow SuperBitmaps to be added to instances of the Screen class, which lets me have desktop backgrounds. Here’s a screenshot:

Woopsi Backdrop

I’ll update the source archives eventually.