2008-04-23

Modal Windows and Documentation

Had a go at trying to make modal gadgets yesterday. Not going to happen. Not in a clean way, anyhow. There are just too many problems. If I moved a lot of the physical event handling into the Gadget class out of the Woopsi class, I’m still left with a lot of problems:

  • How can I increase the VBL count?
  • How can I manage the context menu?
  • How can I manage the deletion queue?

There are plenty of other questions along the same lines. It’s possible, but the solutions would be very ugly. For the most part, Woopsi’s code is quite tidy at the moment and I don’t want to start kludging it up.

Instead, I’ve implemented a “ModalScreen” class, which consists of about 2 lines of code. It’s just a Screen class that calls “erase()” in its draw function, making it transparent. Since it fills the entire screen, it blocks access to any gadgets in lower screens and thus makes its own children modal. No kludges, one (tiny) extra class. Neat!

There are now “shelved” and “unshelved” events, which get triggered when gadgets are shelved or unshelved. The hide() and show() methods now trigger the hidden/shown events properly.

Lastly, I’ve made some updates to the documentation. The calculator tutorial should now work, as should the “hello world” tutorial. I’ve tidied up a few other things. Whilst I’m on the docs topic, I’ve switched the Woopsi web link on the SourceForge site to this blog. It should get me a bit more traffic and reduce the negative impact of the out-of-date documentation.

Oh, one last thing. There’s a new version of DeSmuME out (0.8) for Windows, OSX and Linux. This version works with Leopard.

DeSmuME

Comments

Jeff on 2008-04-23 at 23:50 said:

I’m not quite sure about “moving event handling into the Gadget class out of the Woopsi class” , given that a Woopsi isa Gadget. You could move the method signatures without any drama at all, they just get implemented as noops in Gadget and meaningful in Woopsi/Screen/Window - I don’t think you need to think about a modal Button, for example.

Now, if its more the issue of “but each Gadget needs to manipulate ‘the one deletion queue’” then it can do so by talking to the Woopsi::singleton()

Anyway, I haven’t thought about it all that hard, so there are probably subtleties I’m missing. What I would like to see, however, is a tiny bit more framework-ownership of the ‘run-loop’ - the stuff I have in woopsi_application.h

http://ds-hpcalc.svn.sourceforge.net/viewvc/ds-hpcalc/common/woopsi_application.h?revision=12&view=markup

All it does is formalise that ‘the application object knows how to manage the runloop’, not demand how its implemented. Whilst ‘process one event’ is currently only a two-liner, if its replicated everythere you can’t come along later and implement a four-liner that does modality, etc.

ie, I think that if run() took an argument that was the top-most Gadget in a hierarchy, it could prevent activation of any Gadget that was not in that tree - and thats modal dialogs in a nutshell. Yes, the current ProcessOneVBL() would need to take an input Gadget but you can add that on later with a default argument of NULL and all existing code still works.

ant on 2008-04-24 at 07:50 said:

OK, after a bit more thought I’ve got real modal gadgets working wiith just a couple of changes to the Woopsi class. If you want gadgets to go modal, you can just call “gadget->goModal()”. The gadget steals the runloop until “gadget->stopModal()” is called or the gadget is hidden/closed/shelved. This works for all gadgets.

How do you see the woopsi_application.h folding into the main codebase? As a separate file, or as methods within the Woopsi class? I can definitely see the advantages, but I’m not sure how you envisage it working within the framework. Do you want it as a separate subclass?

ant on 2008-04-24 at 08:11 said:

Try the latest SVN code. I’ve lower-camel-cased everything, and added an “abortRunLoop()” method (in case you need control to go somewhere else) but it’s more or less exactly what you had in your woopsi_application.h. Let me know if I’ve missed anything.

ant on 2008-04-24 at 08:56 said:

I’ve moved the draw() command into startup(), which makes things a little tidier.

ant on 2008-04-24 at 08:56 said:

Incidentally, I notice that HPCalc is now at version 1.0 - all done?

Jeff on 2008-04-24 at 09:00 said:

Definitely methods in the base Woopsi class.

Essentially my apps are all structured:

class Woopsi … class WoopsiApplication : Woopsi … class MyApplication : WoopsiApplication …

ie, I’m using it as a shim to get things into the basic application object that I think should be there, without having to replicate it into all my apps.

It gives a bit more formality to the notion that the Woopsi class is the “application controller object” rather than being off to the side as some “not quite model, not quite view, not quite controller”

ant on 2008-04-24 at 09:03 said:

Scratch that thing about draw() and startup(), I’ll add your main() function instead. Much nicer, I see where you’re going with it!

No, hang on - it does need to be part of the startup function. Simple solution - overridden startup() and shutdown() methods need to call Woopsi::startup() and Woopsi::shutdown() before they exit. That way, the base class can do anything it needs to do without anyone else worrying about how it all works.

Jeff on 2008-04-24 at 09:11 said:

Note, there is still an implied main which currently can’t be boilerplate if you use a custom WoopsiApplication subclass. Buts its insanely trivial.

You can see from http://ds-hpcalc.svn.sourceforge.net/viewvc/ds-hpcalc/HP11C/hp11cmain.cpp?revision=1&view=markup that I did it once, then never needed to make changes.


    3 int main(int argc, char *argv[])
    4 {
    5   // Create woopsi application
    6   HP11C theApp;
    7   theApp.Startup();       // start it up
    8   theApp.draw();      // ensure physical screen is up to date
    9   theApp.RunLoop();   // let the event loop run
   10   theApp.Shutdown();  // and we are done
   11
   12   return 0;
   13 }

I’m sure template purists might even go so far as to creating a template that could be instantiated to hide the custom type. And it could all be embedded as a static method of WoopsiApplication as well. ie,


class Woopsi {
public:
    static int main(int argc, char *argv[])
    {
    // Create woopsi application

    theApp.Startup();       // start it up
    theApp.draw();      // ensure physical screen is up to date
    theApp.RunLoop();   // let the event loop run
    theApp.Shutdown();  // and we are done
   }

and then main.cpp degenerates to


int main(int argc, char *argv[])
{
    WoopsiApplication app;
    return app.main(argc,argv);
}

which again gives the framework control over the startup/shutdown/eventloop sequence that it requires. It eliminates the need for application code to do the leg work of remembering to call woopsiInitGfx() -- or whatever it is, I forget ;-)

This is a big deal for me because I’m trying to get a generic Wifi app working and you need to insert the wifi sequences at certain points in the app startup sequence. Once its nailed down, I would move it into a standard subclass WifiApplication : WoopsiApplication and then forget about it, knowing that it just works…

Jeff on 2008-04-24 at 09:36 said:

… and of course, I posted before reading (had to dash out to deliver kids somewhere). The main() method in the sample was complete nonsense, being static. It should have just been:


class WoopsiApplication ...
{
  inline int main(int argc, char *argv[])
  {
    Startup();    // start it up
    draw();        // ensure physical screen is up to date
    RunLoop(); // let the event loop run
    Shutdown(); // and we are done
  }
}

(that looks a little better…)

ant on 2008-04-24 at 09:39 said:

That’s precisely it. Woopsi::startup() just enables drawing and calls the draw() function. If this is done last in MyApp::startup(), drawing is deferred until everything is done. If it is called first, drawing is enabled and you’ll see gadgets being added and drawn as the system is created.

The draw() method can’t go into main(), at least for the demo, because I want the alert window to go modal after drawing is enabled, but before the main loop starts up. With the current setup, I can add all of the gadgets in Demo::startup(), call Woopsi::startup() to draw everything, then call alert->goModal().

Woopsi::shutdown() is currently empty, so there’s no problem with calling that last.

EDIT: Unless, of course, you create your interface in the constructor, in which case MyApp::startup() can call Woopsi::startup() before it does its own thing. For the demo, this would be calling alert->goModal().

Jeff on 2008-04-24 at 09:53 said:

One less source file I need to worry about. Thanks for incorporating that stuff.

ant on 2008-04-24 at 10:17 said:

woopsiLidClosed() returns the contents of Pad.Held.Lid, which is memset to 0 when initWoopsiGfxMode() is called and not populated again until a VBL. So, all that call really does is set the _lidClosed bool to false.

The other way to handle the startup/shutdown business is to add an extra function:


createGUI()
startup()
shutdown()

That way, createGUI() get overridden to do the UI setup. startup() gets called to enable drawing and then any other app setup that needs to be done (modal alert box, for example). shutdown() does the usual shutdown routine.

Scratch that, I’ll do a main loop like this:


    virtual inline int main(int argc, char* argv[]) {
        startup();
        enableDrawing();
        draw();
        preLoop();
        runLoop();
        postLoop();
        shutdown();

        return 0;
    }

That gives me more control and, as the new functions will be inlined noops, there shouldn’t be any real overhead. It also means that the base methods are all empty, so don’t need to be called anywhere in the subclass.

Jeff on 2008-04-24 at 10:34 said:

HPCalc is at version 1.0 because its function complete, as far as I’m concerned. However, I’m still tinkering a little with the build system, and incorporating the latest Woopsi changes in to make sure it builds if people insist on trying to.

MyApp::startup() needs to call Woopsi::startup() before it does other stuff. MyApp::shutdown() cleans up then calls Woopsi::shutdown()

But it is nicer if the Woopsi::draw() is deferred till after the entire startup - I have this feeling that the current hpcalc flashes a gray screen at the moment prior to doing its fade out/in.

Jeff on 2008-04-24 at 11:08 said:

The thing is, if you call Woopsi::startup() at the end of MyApp::startup(), you end up calling irqInit() which nukes any dswifi that you might have done up till that point.

No, I look now and I see that that happens inside initWoopsiGfxMode() which I thought used to be in my WoopsiApplication::startup() but I see its at the tail end of the constructor. Its probably not tragic any more, I guess, so long as the base class doesn’t put anything too state-centric into Woopsi::startup() - ie, it doesn’t set any state variables that MyApp::startup() might need in place.

Again, I’m thinking about how I slide WifiWoopsi in between the Woopsi base class and my Organiser App (and my AddressBookSync app and …).

In effect, I have


Organizer::startup()
{
   ... set organiser specific stuff ...
   WifiWoopsi::startup()
}

WIfiWoopsi::startup()
{
  ... wifi setup stuff ...
  Woopsi::startup()
}

It seems far more likely that I’ll have something in WifiWoopsi::startup() that has to run before Organiser::startup() can run safely. And I can’t reverse the order in WifiWoopsi because Woopsi::startup() will draw too soon.

I have to say, I think having the draw() inside startup() isn’t a great idea.

Jeff on 2008-04-24 at 11:12 said:

Actually, I just went and took another look and I wonder if there’s a subtle problem to do with startup.

The first thing that Woopsi::Woopsi() does is call woopsiLidClosed()

Thats before it has called initWoopsiGfxMode() which means its before its called irqInit(), etc. Will the arm7 actually be ready to respond that early in the process?

(Working this close to the metal gets tricky sometimes)

Jeff on 2008-04-24 at 11:53 said:

No, woopsiLidClosed() is going to be reading an uninitialised value from Pad because initWoopsiGfxMode() has not been called yet


Woopsi::Woopsi(FontBase* font) : Gadget(0, 0, SCREEN_WIDTH, TOP_SCREEN_Y_OFFSET + SCREEN_HEIGHT, GADGET_BORDERLESS, font) {

        _lidClosed = woopsiLidClosed();  // OOPS, TOO SOON

 [snip]

        initWoopsiGfxMode();             // OOPS, TOO LATE
}

As to the proposed main(), it looks fine - as you say, inlined stubs should hurt nothing - I’m not sure what the difference between postLoop() and shutdown() would be though I’m sure there could be something…

ant on 2008-04-24 at 12:09 said:

Got me, but it’s already fixed!