6 Advanced Techniques

6.1 Command Streaming

In the two preceding sections, it was mentioned that you will very often want a mouse or menu selection to act as if the user had typed a command. You might want to have buttons on the UI for common verbs, such as "look", "take", etc. Now, it's not usually what you want to just have the button respond to a click_event by calling <<look>>;, because this doesn't count as a turn; the turn counter isn't moved ahead, timers and daemons aren't run, etc. So, if you want to force a specific command to be executed, GWindows provides a mechanism for manipulating the command line.

Let's consider the button window from our example UI. Suppose we want to change its behavior so that if a click occurs in the lower half of the window, it acts as if the player had typed 'take'.

GImageWin -> -> -> -> -> -> -> buttons
  with split 47,
  split_dir winmethod_Below,
  image BUTTON_PIC,
  click_event [ x y;
    if (y > (self.height/2)
    StreamWord("TAKE ");
  ],
  has on;

Now, when the player clicks, the word "TAKE" will appear in his input. StreamWord takes a printable as its argument, and inserts the text into the current command. You should take note that if the player has already typed "FOOBAR", after clicking, his input will read "FOOBARTAKE ". Though this may not be what you want, imagine the trouble in implementing a list of clickable nouns if StreamWord erased current player input. You should also notice that we put a space at the end of the string we wish to "stream". Though this isn't required, since 'take' is a verb, we can be pretty sure that the player is going to type another word after it, so it's convenient to have the space already there.

Another thing to note is that Glk (at least, the implementations I have tried; it appears to be required by the spec, but the author is unsure) will redraw the entire input line whenever StreamWord modifies it. So if the user clicks our button repeatedly...

>TAKE

TAKE TAKE

TAKE TAKE TAKE

And so on. The easiest way to avoid this problem is to use a separate input window. Then, the player will see the final

"TAKE TAKE TAKE " text, but not its intermediate incarnations. (Preventing the player from clicking out such a silly sentence as "TAKE TAKE TAKE " is a different matter altogether, and is much harder to solve in a way that won't anger the rare player who actually intends to take an object called the "take take". Good interface design is beyond the scope of this document, and would fill several books). If you want the typed command to also appear in the main window, GWindows can do this, via the GW_INPUT_ECHO configuration constant.

6.2 Command Override

Sometimes, you don't simply want to stick a word into the player's input, but replace it altogether. Suppose we wanted that button to execute the "look" command when the user clicked on the top half...

GImageWin -> -> -> -> -> -> -> buttons
  with split 47,
  split_dir winmethod_Below,
  image BUTTON_PIC,
  click_event [ x y;
    if (y > (self.height/2)
    StreamWord("TAKE ");
    else cmd_override="LOOK";
   ],
  has on;

If cmd_override is set to a printable within an event handler, the game will act as if the player had typed the given command and pressed enter. Anything the player has typed himself is simply thrown away.

Unlike StreamWord, command overrides are not automatically printed back to the screen. As this code stands, the user will see the output of the "look" command (a room description will appear in the main window), but the actual word "LOOK" will not appear. GWindows can print the command for you, and does if you use the GW_ECHO_OVERRIDE configuration constant. GWindows also does this if you're using a separate command window with input echoing enabled.

6.3 Multiple User Interfaces

So far, we have assumed that the game will have a single user interface which is used throughout the game. This may not be what you want, for several reasons. Several commercial games take the step of changing the interface design for critical sections of the game. For example, while in conversation, the user interface may switch to a "conversation mode", or while using some skill or piece of equipment, a specialized interface is shown. Perhaps more likely, you may wish to offer the user a choice of several interfaces. Our example so far is highly graphical. Users who cannot (or do not wish to) use graphical interfaces would be quite pleased if you allowed them to switch to a more traditional interface. If your interface is really exotic, you might want to provide three different versions: the "full" interface, a "reduced" version, which could, for example, offer menus and an input window, but not graphics, and a "traditional" version.

GWindows allows you to switch between interfaces at (more or less) any time by simply resetting the configuration variables, and restarting the system.

Let's say we want to let the player opt to play our game in a traditional motif. First, we design a traditional interface:

WindowPair text_root;
  TextBuffer -> text_mainwin;
  GStatusWin -> text_status;

Now, we add a verb to trigger the transformation...

[ tradmodesub;
  Active_UI=text_root;
  Main_GWindow=text_mainwin;
  RestartGWindows();
  "[Traditional mode selected]";
];

[ graphmodesub;
  Active_UI=root;
  Main_GWindow=mainwin;
  RestartGWindows();
  "[Graphical mode selected]";
];

verb meta 'mode' * 'traditional' -> tradmode
                 * 'graphical' -> graphmode;

We also want to make a slight change to our InitGWindows function:

[ InitGWindows;
  if (Active_UI==0)
  {
    Active_UI=root;
    Main_GWindow=mainwin;
  }
];

This way, when the player restores his game, the display comes up in the last mode he used (More than that -- InitGWindows is called every time GWindows initializes, so if you left the check out, the user interface would be immediately reset to the graphical version).

We can even go a step further, and decide that if the user doesn't have graphics available to him, we'll switch him over automagically.

[ InitGWindows;
  if (Active_UI==0)
  {
    Active_UI=root;
    Main_GWindow=mainwin;
  }
  if (Active_UI==root && ~~(GW_Abilities & GWIN_GWOK))
  {
    Active_UI=text_root;
    Main_GWindow=text_mainwin;
  }
];

Now, the user can switch modes by typing "mode traditional" or "mode graphical". GWindows will make sure that it only actually allows graphical mode if it's going to work.

6.4 Dynamic Layouts

What we've just described is a way to outright change the entire interface while the game is running. This is all well and good, but some times, you'd like to change the interface that is already there, without starting fresh.

Glk allows you to open a new window at any time. GWindows, however, does not. Your entire window layout must be defined ahead of time. The main reason for this is that GWindows has to know how to rebuild the interface after the game restarts or restores. Suppose you open a new window, then undo the move that caused the window to open. Should the window be there? Obviously not. Now, GWindows will handle one very simple case of this; when the quote box is used, the library splits a new quote window from the main window, which goes away again after the next move. However, the standard library contains a lot of special case code to make this work. If your case was any more complicated, you'd need to write all of that special case code yourself. So GWindows decides not to deal with the matter at all, by insisting that your windows are all created at the same time (Actually, it is possible to create new windows on the fly by adding new GWindow objects to the tree, but so much work is involved that this is hardly ever what you want.).

Nonetheless, GWindows does make it possible to make some alterations to the window structure while the game is running. You can't easily open or close windows, but you can resize them. The GWindows technique for defining a dynamic layout is this:

1. In your window layout, declare all the windows you will ever use. Windows that shouldn't be visible when the game starts should have a split size of 0. Glk specifies that windows with size 0 are invisible.

2. When the time comes to make the window visible, use glk_window_set_arrangement to resize the window, and change the GWindow's split.

Let's consider the "popup menu". As it stands, this menu doesn't really "pop up" at all; it always takes up a portion of the main window. First, we change the definition of the popup window to reflect its initial state:

TextGrid -> -> -> -> -> -> -> popupmenu
  with split 0,
  split_dir winmethod_Right;

Now, we'll write functions to make the window appear and disappear:

[ ShowPopup;
  glk_window_set_arrangement(parent(popupmenu).winid, winmethod_Right|winmethod_Proportional,
                            27, popupmenu.winid);
  popupmenu.split=27;
];

[ HidePopup;
  glk_window_set_arrangement(parent(popupmenu).winid, winmethod_Right|winmethod_Proportional,
                            0, popupmenu.winid);
  popupmenu.split=0;
];

Now, whenever ShowPopup or HidePopup are called, the window will be resized as appropriate. What do the arguments to glk_window_set_arrangement mean?

Now, the first argument in this case is the parent of the window you want to resize. But remember, some windows don't size themselves. If you wanted to pop up an entire Window Pair, you'd resize the parent of that window pair, but the "key window" would not be the window pair you were resizing, but rather the window which sizes that window pair.

Because this is a little complicated, GWindows provides a widget for it. GPopupWin is a window which switches between an "inactive" size (usually zero) and an "active" size (usually non-zero). Using a GPopupWin:

TextGrid -> -> -> -> -> -> -> popupmenu
  class GPopupWin,
  with asplit 27,
  split_dir winmethod_Right;

[ ShowPopup;
  popupmenu.activate();
];

[ HidePopup;
  popupmenu.deactivate();
];

6.4a The Validation Thing

There is one little pesky niggle with dynamic layouts, and this is the reason why it's better to use a GPopupWin if possible. Whenever the game restarts, restores, or undoes, GWindows goes through a complex procedure to attempt to salvage the window layout. Suppose we pop a window up, and save the game. Some time later, we fire the game up again, and restore. At the time we performed the restore, Glk had the window popped-down, but the game now thinks the window is popped up.

The last step of salvaging the user interface is called "Validation and Finalizing", and during this step, each window is asked to verify that it has the dimentions it wants. Every GWindow and WindowPair may provide a method, validate, which does this task. For a popup window, this is a simple matter of resizing the window to its active, or inactive size, as determined by the current settings. GPopupWin provides a validate method for doing just this.

Almost all the window-resizing behavior you're liable to need can be performed using a GPopupWin, but if you choose to do any window rearrangement yourself, you'll need to provide a validate method for ensuring the window has the right dimensions. If you can't be sure of how to rearrange the window, the validate method may simply return a non-zero value, to indicate that the window cannot be validated. Any layout containing such a window will not be salvaged; instead, whenever a restore, restart, or undo occurs, GWindows will simply discard the existing screen windows (and any scrollback with them), and start over.

6.4b Window Persistance

Most Glk implementations offer a scrollback facility, which allows players to review text which has scrolled off of the screen. How long the scrollback contents exist may depend on many things, but in general, scrollback will not persist after a window has been closed.

Consider a case where you close your normal game UI to open, for example, a chapter title page, then return to your old UI. In this case, the old Glk window corresponding to your main output has been closed and then opened anew. This is likely to irradicate any scrollback, which may be undesirable.

As of version .9B, GWindows has the facility to preserve one window from the previous screen layout when changing UIs. The class GPersistantWin represents a textbuffer window which should persist from UI to UI. If you switch between two UIs, both having a GPersistantWin, then the same Glk window will be used for both. There are, however, several serious constraints:

In light of these restrictions, you may find persistant windows too much trouble to use if you have a large number of different screen UIs. While the choice is yours, many users appreciate an uninterupted scrollback. Window persistance is not related to validation; the persistance mechanism is employed only if Validation is not going to happen.

6.5 Style Hints

Glk provides a mechanism for specifying the appearances of the various text styles. "Hints" are given prior to creating each window, which give various metrics for how a style should look. The interpreter is, of course, free to translate these metrics into something that "makes sense" for the particular display, or to disregard them altogether, in favor of the user's preferred text appearances.

Any GWindow can specify an array of style hints which will be used for that window. Each hint is encoded in the array as a triple, which names the style, the hint, and the value of the hint. A full list of hints is available in the Glk specification.

Suppose that we want the popup menu in our sample game to display its normal text in italics, and it wishes for the first user-defined style to appear in reverse colors:

TextGrid -> -> -> -> -> -> -> popupmenu
  class GPopupWin,
  with asplit 27,
  split_dir winmethod_Right,
  stylehints
   style_Normal stylehint_Oblique 1
   style_User1 stylehint_ReverseColor 1;

6.6 The GConsole

GWindows provides a debugging feature called the GConsole. Include "gconsole" between gwindefs and gwindows to install the console. When the GConsole is installed, a window at the bottom of the screen will display informative messages from GWindows whenever any action is taken:

[GWindows]: GConsole activated.
[GWindows]: Initializing the user interface.
[GWindows]: Opening pair window "(trad_ui)".
[GWindows]: Preparing to open window "(t_mainwin)".
[GWindows]: Opening new window from window #58. Split method is 34; window type 3; split size 75.
[GWindows]: Window "(t_mainwin)" open. Window ID is 59.
[GWindows]: Preparing to open window "(t_statuswin)".
[GWindows]: Setting stylehint 9 for style 0 to value 1 on window "(t_statuswin)".
[GWindows]: Opening new window from window #59. Split method is 18; window type 4; split size 1.
[GWindows]: Window "(t_statuswin)" open. Window ID is 61.
[GWindows]: Pair window "(trad_ui)" opened.
[GPopupWin]: Deactivating window (splash_quote).

These messages can be used to trace faults in the windowing system. Optionally, you can define GCONSOLE_PAUSE to some non-zero value to force the console to wait for keyboard input between each message -- though this can interfere with keystroke requests from other windows.

You can also print messages to the GConsole yourself. GConsole.write(x) will write any printable to the console. If you want to print something more complex, GConsole.penon(); redirects all printing to the GConsole until GConsole.penoff() is called. You can safely leave GConsole commands in your code even when not using the GConsole, but it is preferable to enclose GConsole commands in #ifdef USE_GCONSOLE; blocks.