Graphics window handling

The bi-platform library offers a number of entry points to make window management a little easier. An entry point, as touched upon earlier, is a sort of optional routine that programmers can put in their source code. See, every so often library writers will get to a spot in the library code where they suspect game programmers might want to run customized instructions, or might not. So the library writer whips up a dummy routine that does nothing, and calls it at that spot. Now the game programmer can write a routine with the same name, and when the library gets to that point, it will run the code in the game programmer's routine, which does do something. Or the game programmer can leave it out, and when the library gets to that point, it will run the dummy routine and safely continue on its way.

So if you're not creating any special windows or sound channels or file references or anything like that, you don't need to worry about any of the entry points that follow. The library will take care of gg_mainwin and gg_statuswin for you, as well as any saved game files you might generate. But if you create your own Glk objects, such as a graphics window, it's your responsibility to tend to them. Here's how.

First, you need to create a routine for the entry point that's called after every restart, restore and undo. This is IdentifyGlkObject(). The reason you must have an IdentifyGlkObject() routine if you create your own graphics windows is that after a restart, restore or undo, the global variables that handle those windows may well be wrong, and things can get ugly if you don't reset them.

IdentifyGlkObject() is actually called three times, sending a different number to the local variable "phase" each time: first 0, then 1, then 2. In phase 0, your job is to set all your Glk-object-handling variables to 0. In phase 1, you reset all your windows, streams, and file references, and in phase 2, you reset any other objects you might have created. Phase 2 is also where you should update your windows to show the right pictures, your sound channels to play the right music, and so forth.

So, continuing the example from the previous section, let's say that we have a graphics window, gg_picwin, in which we show a picture of the room the player character is in. But unlike in the previous section, we won't draw images directly to the window with commands like glk_image_draw(gg_picwin, Kitchen_pic, 0, 0). Instead, we'll make a global variable called "current_pic", and a routine that looks like this:

   [ MyRedrawGraphicsWindows;
      glk_image_draw(gg_picwin, current_pic, 0, 0);
   ];

Now when we write the routine to move the player from the dining room south into the kitchen, it should look like this:

   s_to [;
      current_pic = Kitchen_pic;
      MyRedrawGraphicsWindows();
      PlayerTo(Kitchen);
   ],

And last, we put in our IdentifyGlkObject() entry point:

   [ IdentifyGlkObject phase type ref rock;
      if (phase == 0) { ! Zero out references to our objects.
         gg_picwin = 0;
         return;
      }

      if (phase == 1) { ! Reset our windows, streams and filerefs.
         switch (type) {
            0: ! it's a window
               switch (rock) {
                  GG_PICWIN_ROCK: gg_picwin = ref;
               }
            1: ! it's a stream
                  ! But we don't create any.
            2: ! it's a fileref
                  ! But we don't create any.
         }
         return;
      }

      if (phase == 2) { ! Update our objects.
         MyRedrawGraphicsWindows();
      }
   ];

What have we just done? First, we took the global variable for the graphics window we created earlier (which is now filled with junk, thanks to the reset) and set it to 0 -- that's phase 0. Then, the library found this construct floating around, didn't recognize it, and so passed it to us to determine what it was. We said, "If the 'rock value' for this object (which isn't lost during a reset) is the same as the rock value we set for our graphics window, then this must be our graphics window! Therefore, we will now point the variable for the graphics window back at this thing. That's phase 1. Then in phase 2 we did a redraw: since we now know where to send the picture that should be displayed at this point, we can display it.

The graphics window should now react properly to restores and undos and the like: if the player moves her character into the kitchen, then types "UNDO", not only will the character be moved back into the dining room, but the window will replace the picture of the kitchen with a picture of the dining room.

What about external events that affect the game? For instance, the player might resize the Glulxe window, or change the monitor's resolution -- how do we deal with that? Answer: the library has a loop that keeps track of such things, and this loop also provides an entry point, this one called HandleGlkEvent(). HandleGlkEvent() takes two arguments: "ev" is an array which contains the information as to what just happened (a window resize? a mouse click? the end of a sound effect?), and "context" is 0 if the thing happened during line input (as in regular commands or a YesOrNo() prompt -- whenever the program should wait for the player to press Enter before responding to input) or 1 if the event occurred during character input (as in menus and such, where the game is responding to every keystroke.) For the example we're dealing with here, all we need is a very short routine:

   [ HandleGlkEvent ev context;
      context = 0; ! suppress ignored warning
      switch (ev-->0) {
         evtype_Redraw, evtype_Arrange:
            MyRedrawGraphicsWindows();
      }
   ];

This code essentially boils down to "if something happens which means we have to redraw the graphics windows, go ahead and redraw them using the instructions we set down earier."

And that should be all you need to make your graphics windows robust enough to handle most any contigency that comes down the pike.


Next section: Graphics troubleshooting
Or return to the table of contents