2008-04-26

Arghitecture, More Modal Gadgets and SDL Updates

Following some fast-paced discussion regarding the last set of changes, I’ve made some more alterations.

The Woopsi class now uses the new modal system to run its own loop. This is only of interest if you’re following the old design pattern of writing your own main loop. You should replace a call to woopsiApplication->run() or woopsiApplication->play() with a call to woopsiApplication->goModal().

I realised that having a custom loop within the Woopsi class is a bit daft when that class already has built-in functionality to run a loop courtesy of the Gadget class. This change has let me take out the custom loop code.

Gadgets are now only modal whilst the Woopsi instance is itself modal. The upshot of this is that calling woopsiApplication->stopModal(), in order to quit Woopsi, will now force any other modal gadgets to cede control back to the main loop so that it can exit.

I’ve added a few bugfixes. All attempts to dereference woopsiApplication are now guarded within NULL checks, which should prevent any NULL pointer-related crashes.

The SDL code has seen some improvements, too. I’ve merged the SDL quit code into the Woopsi::shutdown() method. This method should be called last in any overrides, unless you don’t want to quit SDL, in which case it’s your own responsibility to shut down SDL.

Quitting an SDL Woopsi application now stops Woopsi being modal, which means (aha!) all gadgets stop being modal and the application exits correctly.

The SDL event pump is now part of Woopsi::processOneVBL(), which means that as long as VBLs are getting processed, Woopsi will listen for quit events.

Lastly, I’ve merged the DS and SDL versions of the woopsifuncs files. This means that the SDL and DS “woopsi” folders are now identical. In fact, the only difference between a DS Woopsi project and an SDL Woopsi project is the need to include the supplied “sdl/nds.h” file in the SDL project. This includes a “USING_SDL” define again, since switching to a check for “_SDLH” didn’t work for me.

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

2008-04-20

Showing and Hiding Gadgets

The existing show/hide methods within Woopsi are designed to allow hidden gadgets to be moved out of the gadget hierarchy entirely. When a gadget is hidden, it gets moved into a separate array within its parent where it is totally ignored until it is shown again. This means that hidden gadgets do not cause any performance loss at all; in fact, other than their memory requirements, hidden gadgets use the same resources as non-existent gadgets.

This can be problematic, though, as John discovered. Hidden gadgets that get re-shown are pushed onto the end of the gadget array, thus losing their original z-index. There’s no point in remembering the original z-index as the gadget order may have changed.

To work around this, I’ve renamed the current hide/show system to “shelve” and “unshelve”. If you want to remove a gadget from the system but don’t want to recreate it later, you can shelve() it. If you want to bring it back, you can unshelve() it. I have added new show() and hide() functions that just make a gadget invisible and non-interactive. However, unlike shelved gadgets Woopsi will attempt (fruitlessly) to interact with hidden gadgets.

There are a couple of other changes. There’s a new Gadget::isEnabled() method that will check that a gadget is enabled by recursing through its ancestors. I’ve replaced the majority of _flags.enabled checks (in Gadget::click(), for example) with calls to this function instead.

The closeChild() and shelveChild() methods weren’t reassigning focus correctly if the child being closed was the only child. This should now be sorted.

It is possible that the changes will introduce bugs, so if you spot anything strange happening when hiding gadgets let me know.

2008-04-01

ScrollingTextBox and a Context Menu

One gadget on the go, one proposed. The ScrollingTextBox class is the first one. It’s a combination of a MultiLineTextBox and ScrollbarVertical. All of the event wiring up is handled for you, and it presents the same API as the MultiLineTextBox. The two are completely interchangeable. There are a few things I need to do to it before it’s finished, including a resize function, adding in wrapper functions to mimic the scrollbar API and raising events, but it’s coming along. It’s in the SVN repository and is used in the Debug class.

Note that the MultiLineTextBox also supports horizontal scrolling, should you need it. I just haven’t added a horizontal scrollbar to the ScrollingTextBox class, as I’m guessing that vertical scrolling will be considerably more in demand.

Next is the proposed ContextMenu class. A right-mouse menu has been on the feature list since I started Woopsi, and I’ve been planning out how it will work. The ContextMenu will exist as a singleton within the Woopsi instance (putting it there ensures that it’s fairly easy to ensure that it always appears at the top of the gadget stack). Users can open it by clicking one of the shoulder buttons and tapping a gadget. If that gadget wants to use the context menu, it will:

  • Call the ContextMenu::clear() method
  • Add as many ContextMenuItems into the menu as it needs to
  • Set the ContextMenu’s event handler to be the current gadget
  • Show the menu

The menu will automatically hide itself if it loses focus (ie. user clicks elsewhere) or an item is selected. In order to make this work, gadgets need to be able to interrogate the status of the DS’ buttons. I don’t think there’s a supported way to do that yet, so I’ll need to add it in. That should also allow developers to check for multiple keypresses/chording.