8. The GWindows Widget Set

8.1 Abstract Widgets

8.1.1 GPopupWin


As discussed in 6.4, if you want the appearance of a layout while the game is in progress, the easiest way to do this is via a window or windows which begin with a size of zero, and which are enlarged to become visible when needed (or vice versa). Because this is such a common trick, GWindows provides an abstract widget encapsulating this ability.

class GPopupWin


activate and deactivate switch the window between its states. You can test which state is current by checking the split property, which will be equal to the asplit when the window is active, and the dsplit when it is inactive. Before the window is activated or deactivated, its initial size is stored in split.

The split_key is the window pair you wish to resize. Recalling the discussion from chapter 3, we know that younger windows size themselves within their window pairs. For such a window, popping it up or down only affects the window itself. When you resize the window, the other window in its pair will be resized to fill the vacated space. The split_key in this case is the parent window pair of the popup window. If you do not specify a split_key, GWindows will assume it to be the parent window pair.

Resizing an elder window is more complicated. Because an elder window's size is implicit, you must resize it by resizing the younger window in its pair; (well, Glk allows you to resize the window itself, but GWindows is unable to preserve such a structure, so don't do it.) For example:

  TextGrid -> popup;
  GPopupWin -> mainwin
    class TextBuffer,
    with split 100,
      asplit 100,
      split_dir winmethod_Below,
      dsplit 90;

When the game starts, the popup window is invisible, because its younger brother is taking up the entire window pair. When you deactivate mainwin, it reduces its size to 90% of the screen, causing the popup window to become visible in the vacated space.

What if you want to pop up an entire window pair? Remember from chapter 3 that some elder windows "size" window pairs further up the tree. You can think of these windows as the "foundation" on which the window pair is built. If you were to pop one of these foundation windows up or down, it would resize the entire pair. For example:

WindowPair top;
  TextBuffer -> mainwin;
  WindowPair -> popuparea;
    WindowPair -> ->;
      GraphWin -> -> -> pic_win
        class GPopupWin,
        with asplit 50,
          split_dir winmethod_Right,
          split_key top;
      GraphWin -> -> -> pic2
        with split_dir winmethod_Below,
          split 75;
    TextGrid -> ->
      with split 10,
       split_dir winmethod_Left;

The window popuparea is initially invisible, because, according to the rules of chapter 3, it gets its size from pic_win, which has a default size of zero. When you activate pic_win, the entirety of popuparea becomes visible.

One small point, though, is that GWindows can't determine what window is sized by the elder window. So you have to specify the split_key in this case as the parent of the window you want to "pop up".

GPopupWin is a powerful tool for making the interface change as you see fit. Some of its most useful applications have their own widgets, the abstract GTransient, and the concrete GPopupMenu.

8.1.2 GTransient


Sometimes, you'll want a popup window to become visible, then hide itself again after a fixed amount of time has elapsed. GTransient defines a popup window which installs a timer to re-hide itself.

GTransient windows behave identically to GPopupWin windows, except that its activate method is called with a parameter. MyTransient.activate(5); will show MyTransientWindow for five turns, then hide it again.

The most common use for a transient window, displaying a quote box, is handled by the derived class GQuoteWin.

8.1.3 GCombiner


GCombiner is a powerful widget, which automatically chains together the methods of other inherited classes.

Class GCombineRedraw

class GCombiner,
with redraw [; self.thread(redraw); ];

This is a class which combines redraw events. You could use it to create a window which redraws itself according to two different rules:

object MyWin
  class GCombineRedraw Class1 Class2;

MyWin will now redraw by first calling the redraw method inherited from Class1, then from Class2.

One tricky point with GCombiner is that if you create your own GCombiner derived classes, you also have to add code like this:


Array GCOMBINER_LIST table GCombineRedraw;

before including GCombiner. GCombiner has to know which classes are also GCombiners so that it doesn't try to run itself more than once. Also, the GCombiner always has to be the first class in the inheritance list.

The most common methods to combine via GCombiner are activate, deactivate, redraw, and update. gcombine.h defines the classes GCombinerAD, GCombinerUR, and GCombinerADUR for combining these methods.

GCombiner is a very powerful class, and therefore fairly dangerous. You should try to avoid using it unless you are absolutely sure what you are doing.

8.2 Concrete Widgets

Many of the concrete windows have means of forcing themselves to appropriate sizes. It's generally best, therefore, to define these as younger windows.

8.2.1 GStatusWin


GStatusWin is a widget for implementing the standard statusline. Simply include a GStatusWin in your window tree, and a normal-looking statusline will appear.

The statusline generated by GStatusWin is visually similar to that used by z-code inform rather than Glulx; the window is displayed in reversed colors. Also, GStatusWin exhibits better behavior when the window is very small.

8.2.2 GQuoteWin


Though the library quote box is still available in GWindows, it is comparatively inflexible (and frankly, quite ugly). A much more useful quote box is available in the GQuoteWin class.

You can insert a GQuoteWin anywhere in your tree, but it should always size itself, and its split_dir should be horizontal (that is, wintype_Above or wintype_Below).

A GQuoteWin is activated by calling quotewindow.quote(text); where text is an table array (a table array is an array whose first element is the number of entries in it) of printables. The window will automatically choose the apropriate size for itself, and pop up, disappearing one turn later.

If you like, you can use a GQuoteWin to replace the library's quote window. The box command will use the GQuoteWin instead of the default library mechanism.

To do this, you must add the line:

Replace Box__Routine;

before including either gquote.h or parser.h, and you must define the variable Quote_GWindow to your own GQuoteWin object.

8.2.3 GMenu


GMenu is the standard menu-drawing window. It provides many hooks for deriving custom menu views, which are described in the comments to gmenu.h. GMenu windows support mouse and keyboard input, and can draw multiple pages to accommodate menus longer than a single window.

To activate a GMenu, call menu.activate(x), where x is the menu data object. Here's an example of a menu data object which provides a menu of game commands:

class cmd_word with select [; cmd_override=self; ];

object mdo;
  cmd_word -> "RESTART";
  cmd_word -> "RESTORE";
  cmd_word -> "SAVE";
  cmd_word -> "QUIT";

If you have a GMenu called 'menu', menu.activate(mdo); will provide a menu with options to restart, restore, save, and quit. The select property of the selected option is called when the user clicks or presses return. You can disable the menu by calling menu.deactivate();

If the menu data object provides an update function, it will be called once per turn, so that the menu can alter itself (for example, a menu of all objects currently in scope would need to be redrawn once per turn). It should return true if the menu needs to be redrawn.

GMenu provides many ways to customize its behavior. Here are the most common ways:

Additionally, a menu item which has the absent attribute set will behave differently from normal menu items: if the player is navigating the menu via the cursor keys, the cursor will skip over such elements. This can be used to place a separator within a menu. (However, placing such an item at the beginning or end of a menu may have undesired effects.) Clicking directly on the item with the mouse will still send a select message, however.

Note: In previous versions, the current_menu property of a GMenu could be used to determine the current menu data object. This practice is deprecated. To find the current menu data object, use the method active_menu(). current_menu will still hold the menu data object for simple menus, but may not be correct for all subclasses (Most notably, WrapMenu).

8.2.4 GPopupMenu


A GPopupMenu is the combination of a GPopupWin and a GMenu. When it is activated, it resizes itself and displays a menu.

8.2.5 GColumnMenu


While GMenu shows a single column of menu entries, GColumnMenu allows multiple columns. The menu data object is identical.

Columns in a GColumnMenu have a default width of 15 characters, but you can change this by specifying a columnwidth.

8.2.6 GAutoMenu


GAutoMenu is a column menu which automatically adjusts the width of its columns to fit the longest entry in the menu. You will generally prefer this to GColumnMenu unless you want to force a very specific appearance of the menu.

8.2.7 GImageWin


GImageWin is an easy way to define a window which displays an image. In addition to setting its image property, as we have seen in 5.3, you can change the image at a later time by calling imagewin.setImage(x); where x is the number of a Blorb image. The image will be automatically rescaled to fill the window. If the attribute aspected is set, the image will be scaled to fit the window as closely as possible without altering the aspect ratio of the image and centered.

GImageWin has a further property, col which is the background color of the window (white by default). This is relevant in three cases:

In the former case, the window will be cleared to its background color (Compatability note: In version before .9b, redraw was suppressed if the image was unset. The results were predictable but not defined). In the last case, the window will be cleared to the background color before the image is displayed. This prevents the appearance of "ghost" images in the unused space around an image. Since this can form a visible border around two sides of an image, you should try to set the background color of the window to match the background color of the image. If possible, you should consider using transparency effects where available to ensure that the unused space blends satisfactorily with the image background.

8.2.8 GImageMap


An image map is an image which is divided into rectangular regions which can be clicked.

GImageMap is, in essence, the graphical version of GMenu. The format of the image map data object is compatible with that of a GMenu's menu data object, so you can use the same data to generate either an image map or a menu (and it is a good idea to allow users who can't cope with graphics to view the map as a menu, if it is essential to the game).

You activate a GImageMap just as you would a GMenu. The only difference in the data is that each option must provide information about its location within the image:

object mdo;
  cmd_word -> "RESTART" with xpos 50, ypos 10, width 10, height 10;
  cmd_word -> "RESTORE" with xpos 60, ypos 10, width 10, height 10;
  cmd_word -> "SAVE" with xpos 50, ypos 30, width 10, height 10;
  cmd_word -> "QUIT" with xpos 55, ypos 40, width 10, height 10;

Each option defines a rectangular region, beginning at (xpos,ypos) of size width x height. These will be scaled appropriately if the image is resized.

Like GMenu, if the map data object provides an update method, it will be called once per turn. It is not necessary to return true from this function, since the map's appearance will not be changed by changing the clickable regions within it.

8.2.9 GDrawImageMap


GDrawImageMap is a version of GImageMap which allows the map items to draw themselves.

Each item may additionally provide an image property. If this is set, the image will be drawn over the background of the window in the position specified. If the widget is set aspected, the map items will be scaled proportionally.

8.2.10 GImageGrid


Though GImageMap is powerful, it can be a little slow, due to the processing involved in finding which item was clicked. If the map has a very regular layout, you might prefer to use GImageGrid instead.

When you declare a GImageGrid, you use the rows and cols properties to specify the number of rows and columns in the grid. The window is divided evenly into rectangular cells. When a cell is clicked, the corresponding entry is selected.

Entries are numbered across each row, so if the user clicks on the second element of the second row, and each row has 5 columns, the 7th entry is selected (7=5+2).

Image Grids are also pageable. If the grid data object has more entries than will fit on the grid, you can specify where the grid is to start via the start property of the Image Grid window. So, setting start to 5 means that the first four entries are ignored.

8.2.11 GDrawImageGrid


A GDrawImageGrid is the image grid counterpart of GDrawImageMap. This could easily be used to create, say, a graphical inventory:

WindowPair invarea;
  GDrawImageGrid -> invwin
    with rows 4, cols 4;
  GImage -> invscroll
    with image BUTTONS_PIC,
      click_event [ x y;
        if (y>(self.height/2)) invwin.start++;
        else invwin.start--;
      split 25,
      split_dir winmethod_Left,
    has on;



Then, make all takeable objects inherit from a class:

class inv_obj
  with select [; StreamWord(self);],
  image GENERIC_PIC;

Clicking on a cell in the inventory window will insert the object's name in the current command line. If the object provides an image of its own, that image will be drawn in its cell. Otherwise, the "generic" picture will be used.

We define the other window, invscroll, to allow the user to scroll the window.

A real implementation would want to redefine invscroll.click_event so that it doesn't allow invwin.start to take an unreasonable value, and give the player object an update method which returns true if the player's inventory has changed this turn.

8.2.11 WrapMenu (New in GWindows .9B)


WrapMenu is an enhanced menuing widget. It may be used just like any other GMenu, however, in a WrapMenu, if an entry takes up more than a single line, it will be folded over onto the next one, rather than being truncated.

If a menu item takes up more than one line, subsequent lines will be indented. Furthermore, when the player cycles through options using the cursor keys, the selection marker will skip over the subsequent lines, moving only between the first lines of each entry. Clicking on any line of the entry will, however, trigger the option.

If you only wish to use a single WrapMenu, you may treat it identically to a GMenu. However, if more than one WrapMenu is to be active at the same time, it must specify a wrapper proxy:

ww_contents my_proxy;
WrapMenu wmen with wrapper my_proxy;

WrapMenus which do not specify a wrapper proxy will use the system default proxy. At most one menu at a time may use this proxy. A wrapper proxy is simply an object used to hold virtual menu objects while the menu is being constructed. Wrapper proxies inherit from the class ww_contents. Other than creating them for WrapMenus you may declare, you need never deal with the proxy yourself.

WrapMenu defines two constants to modify its behavior:

WrapMenu can be combined with GPopupWin without the use of a GCombiner; simply inherit from both classes.

8.2.12 GTileWin (New in GWindows .9B)


GTileWin is a widget which tiles an image, as usually occurs with background images on webpages. It behaves like a GImageWin, except that rather than stretching an image to fit the window, the image is simply repeated in a grid pattern as many times as will fit in the window.

8.2.13 GVTileWin, GHTileWin (New in GWindows .9B)


GVTileWin and GHTileWin are subclasses of GTileWin. GVTileWin tiles images vertically, while GHTileWin tiles them horizontally. Unless the widget is set aspected, the image is stretched along the untiled dimension. Which widget will suit a particular need will depend on the nature of the image to be tiled. For example, consider an image consisting of four sqares, two black, two white, in a checkerboard configuration. GTileWin would produce a checkerboard pattern. GVTileWin would produce two columns of alternating black and white rows. GHTileWin would produce two rows of alternating black and white columns.