10. Modality and what you can click

10.1 The game event loop

It is not usually necessary for an inform programmer to consider how an inform program actually works. When one programs in inform, most of what is written is reactive code; one is programming responses to player input

"Underneath the hood", an inform program (at least, one which uses the standard library) goes through a continuous cycle of asking for player input (via KeyboardPrimitive), then responding to it (for example, by calling before and after routines).

The Glk interface works in a similar fashion; one or more events (line input, character input, mouse input, timer, etc.) are requested, glk_select() collects these events, then HandleGlkEvent() is invoked to respond to the event. In the standard library, KeyboardPrimitive() and KeyCharPrimitive() are where this occurs. GWindows uses the HandleGlkEvent entry point to invoke the _event hooks. KeyboardPrimitive is called by the library once per turn to gather player input. It is at this time that GWindows does most of its work. If the player enters a character into a window which is awaiting character input, the char_event method is invoked on that window; if the player clicks, the click_event method is invoked. KeyboardPrimitive contains a Glk event loop which calls glk_select then HandleGlkEvent over and over until line input is received. When the player finally types a line into the input window and presses enter, KeyboardPrimitive returns, and the processing of the player's input continues. Until KeyboardPrimitive is invoked again for the next turn, GWindows will not process any more input.

10.2 Writing your own event loop

Using the library's event loop is fine most of the time; it is the perfect place for GWindows to respond to, for example, the user clicking on a compass, because we can simply use command overrides to "pretend" that the user had actually typed in a command.

However, there may be times when you require some interaction from the player outside of the normal game loop. For example, when displaying a full-screen menu, or showing a splash screen, you really want to suspend the game until the player has pressed a button or made a selection. At these times, you may want to write an event loop of your own.

As described in the previous session, an event loop must have several key components:

One of the most important rules to follow when using your own event loop is to put things back the way you found them; if, during your event loop, you requested some event, make sure that the request is no longer pending when you exit your event loop -- many people have found that their game no longer responded to player input because an event loop they created to pause the game had left a request for character input pending in the input window (Since Glk specifies that you may not request character and line input simultaneously, the library's would be unable to get player input, because the glk_request_line_event would be blocked by the pending character request.).

It is generally preferable to use a built-in event loop than to write your own, precisely for this reason. If your exit-condition can be made to correspond to a keypress, then simply calling KeyCharPrimitive will suffice:

while(KeyCharPrimitive()~='q');

Is an event loop which will execute untill the player presses `q`. The following section describes an even more flexible event-loop.

10.3 The modality thing

Normally, GWindows will respond to input in any window that has requested it, just as any active control in a normal application will respond at any time. Consider, however, a window which serves like a "dialog box" in a normal application: the user cannot access any of the application's normal controls until the dialog box closes.

We call such dialogs "modal", because they define a state (or mode) where the user can interact only with that dialog. While overuse of modal dialogs can defeat usability, when used properly, they can be a powerful tool.

In GWindows, we expand the notion of modality to cover event loops which are not part of the standard library: these put the player in a mode where they must deal with some UI element, rather than performing "normal" game interaction.

The GWindows modality package (gmodal.h) allows you to enter a modal context, which has its own event loop. To trigger this event loop, create a modal context by calling GoModal(). GWindows will respond to input as it normally would, but attempts by the player to enter commands into the input window will be ignored.

Unlike the simple example above, the exit condition is left to the author. GoModal does not return until the program signals that the exit condition has been met. To do this, call EndModal(). EndModal is quite unlike most other functions in that it does not return a value; any code following a call to EndModal() inside a function body will not execute (just as statements appearing after a return, print_ret, or quit will not execute. However, the inform compiler will not warn you, as it does for these statements). As an example, suppose we wish to wait until the player clicks at position (0,0) in a certain window:

GImageWin ->
with click_event [x y; if (x==0 && y==0 && GW_Modal) EndModal(); ];

...

print "Now click on the top-left of the image window to continue.^";
GoModal();
print "Thanks!";


While the modal event loop is running, the game will still respond to being resized, to sound events, and, if there are menus or clickable windows showing, they will still respond as normal, but the player will not be able to enter regular input until he has satisfied the request. It is important in the code above to check that GW_Modal is nonzero, as it is illegal to call EndModal outside of a modal context (doing so will trigger the error GW_ERR_NOT_MODAL). It should be easy to see how, for example, a modal menu cold be constructed. Once the menu was activated, call GoModal() to enter a modal context. The player could then choose menu options, and finally, selecting the "quit" option would execute EndModal(). The line immediately following GoModal would deactivate the menu.

You can also nest modal contexts; if you call GoModal() while already in a modal context, you will have to exit first the inner, then the outer modal context. Alternatively, calling EndModal(1) will close all open modal contexts immediately. How deeply modal contexts can be nested is dependent on the Glulx stack.

10.4 Using modal contexts to control player input

Within a modal context, you do not need to honor input to the entire UI. If you wish pop up a menu which does not take up the entire screen, but insist that the player deal with it before doing anything else, you can call GoModal with a parameter. If this parameter is a GWindow, only input to that GWindow will be honored. If it is a window pair, then only input to that subtree of the UI will be honored. When nesting modal contexts, only the innermost context's constraint is considered to be active. GoModal(Active_UI) is exactly equivalent to calling GoModal with no parameters.