8. The GWindows Widget Set
8.1 Abstract Widgets
8.1.1 GPopupWin
gpopup.h
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
with
activate,
deactivate,
split,
asplit,
dsplit,
split_key;
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:
WindowPair;
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
gtrans.h
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
gcombine.h
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:
Constant HAVE_GCOMBINER_LIST 1;
Array GCOMBINER_LIST table GCombineRedraw;
before including
GCombiner
. GCombiner
has to know which
classes are also GCombiner
s 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
gstatus.h
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
gquote.h
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.h
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:
- sel_marker
: This property contains the character which is printed beside the current menu selection.
- prev_word
: This printable is printed on the first line if the window can be paged upward
- next_word
: This printable is printed on the last line if the menu can be paged downward
- unpageable
: If this attribute is set, the menu will not display <previous> and <next> indicators or allow paging, even if the menu is longer than the available space. 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
gpopmen.h
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
gcolmen.h
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
gautomen.h
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
gimage.h
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:
aspected
8.2.8 GImageMap
gimap.h
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
gdrmap.h
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
gigrid.h
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
gdrgrid.h
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--;
GW_ForceRedraw(invwin);
],
split 25,
split_dir winmethod_Left,
has on;
...
invwin.activate(player);
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)
wmenu.h
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;
WrapMenu
s 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 WrapMenu
s you may declare, you need
never deal with the proxy yourself.
WrapMenu
defines two constants to modify its behavior:
GW_MAX_MENU_SIZE
(Default: 256) - This is the maximum number
of lines which can appear in any WrapMenu. That is, if each option takes up a
single line, the largest legal menu can have 256 options. If each option takes
two lines, the menu can have at most 128 options. If, as is highly unlikely
(actually, impossible, since GMenu will truncate the name at 128 characters
no matter what, unless GW_BUFFER_SIZE has been modified),
a you have an option which is 256 lines long, it must be the only thing in
the menu. Of course, you cannot be absolutely certain how many lines a
given option will take up on the user's screen, but it is unlikely that you
will run out of room as long as this exceeds twice the size of the
largest possible menu.
GW_MAX_WRAPS
(Default: 256) - This is the maximum
number of lines to be given to any single menu item. Longer items will be truncated.
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)
gtile.h
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)
gtile.h
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.