5 Basic Techniques
5.1 Defining a Layout.
In GWindows, you define a layout by creating an object tree which reflects the Glk window hierarchy. GWindows opens windows in more-or-less the order they are specified in the source, so the elder child of a window pair is the first object inside that pair. Let's consider the example from chapter 3:
(19) root main window / \ 18 Status (17) game area with trim (TextGrid) implicit itself 1 line above / \ 16 top trim (15) "real" game area (GraphWin) implicit itself 4% above / \ 14 Spell area (13) Client Area with trim (GraphWin) implicit itself 11% below / \ (11) Client area without left trim 12 left trim implicit (GraphWin) itself 2% / \ left (9) "real" Client area 10 right trim implicit (GraphWin) itself 3% right / \ (8) Controls (3) Main window area inventory implicit / \ / \ 7 buttons (6) Inventory area 1 main window 2 popup menu (GraphWin) (TextBuffer) (TextGrid) itself implicit implicit itself 47% (root 27% below automatically right gets the whole screen) / \ 4 inventory 5 arrows (GraphWin) (GraphWin) implicit itself (controls) 15% 38% right left
The chart above has annotated the window tree, specifying the type of each window. The GWindows syntax for this encapsulates all the same information, in the inform equivalent of its syntax:
WindowPair root;
WindowPair -> gamearea_t;
WindowPair -> -> gamearea;
WindowPair -> -> -> clientarea_t;
WindowPair -> -> -> -> clientarea_r;
WindowPair -> -> -> -> -> clientarea;
WindowPair -> -> -> -> -> -> mainarea;
TextBuffer -> -> -> -> -> -> -> mainwin;
TextGrid -> -> -> -> -> -> -> popupmenu
with split 27,
split_dir winmethod_Right;
WindowPair -> -> -> -> -> -> controls;
WindowPair -> -> -> -> -> -> -> inventoryarea;
GraphWin -> -> -> -> -> -> -> -> inventorywin
with split 38,
split_dir winmethod_Left;
GraphWin -> -> -> -> -> -> -> -> arrows
with split 15,
split_dir winmethod_Right;
GraphWin -> -> -> -> -> -> -> buttons
with split 47,
split_dir winmethod_Below;
GraphWin -> -> -> -> -> right_trim
with split 3,
split_dir winmethod_Right;
GraphWin -> -> -> -> left_trim
with split 2,
split_dir winmethod_left;
GraphWin -> -> -> spellarea
with split 11,
split_dir winmethod_Below;
GraphWin -> -> top_trim
with split 4,
split_dir winmethod_Above;
TextGrid -> status
with split 1,
split_dir winmethod_Above
has abssplit;
Even though this is pretty long, it should be straightforward.
5.2 Using a Window Layout
Now that you've got a window layout defined, you have to tell
GWindows to use it. To do this, you need to set the
Active_UI
variable. Since this is the only layout we'll
be using, you can do this in InitGWindows
. See chapter 6
for instructions on how to use more than one layout.
We also have to set the main window, so that the inform library knows where to put its own output.
[ InitGWindows;
Active_UI=root;
Main_GWindow=mainwin;
];
That should do it. Now, when the game starts, your interface will be shown.
A GWindow may also include a method called init
. If present, this
is called after the UI has been built, but before any automatic redrawing takes
place. You should probably not print anything important to the window here,
but any setup you need to do for the window can occur here.
5.3 Redrawing windows
If you try to run this game, you'll find the screen suitably partitioned. If your interpreter displays visible window borders, you will see lines separating the various windows (** Note: Whether or not visible boundaries are drawn around each window is a user-configurable option on most Glk implementations. Depending on your layout, you may or may not desire these borders. A text-based interface might look better with the boundaries, while a graphical one, like the one we have here, will look much worse. At this time, Glk has no way for the game author to control the display of borders. You might want to inform players that visible borders should be enabled or disabled, though you can usually rely on the common sense of the player to pick the mode that he thinks looks best. Of course, some interpreters do not provide the option at all, so these users will be stuck with whichever version the Glk author chose. Hopefully, the Glk specification will someday be expanded to allow the author to recommend one border setting or the other.)
However, you may have noticed that the vast majority of these windows are empty. This is because you haven't put anything in them yet. You could draw the windows yourself, by putting statements directly into your game, but there will be all manner of trouble when, say, the player resizes the screen, or restores the game.
All GWindow objects provide a hook for drawing the window. The redraw method of a window is called:
general
attribute set.Let's look at the Spell Window as an example. Suppose the image for
the spell window is available to the game (as a Blorb resource) with
the name SPELL_PIC
. We tell the redraw method for the
spell window to display this picture:
GraphWin -> -> -> spellarea
with split 11,
split_dir winmethod_Below,
redraw [; gwin_image_draw(self.winid,SPELL_PIC,0,0,self.width, self.height); ];
gwin_image_draw
is a wrapper which only tries to draw the
image if the interpreter is capable of doing the deed. It behaves
exactly like glk_image_draw_scaled
, so this redraw method
will scale the image to fill the entire window, whatever its
size.
Note that if the window has a different aspect ratio than the image, this
can lead to distortions. In some cases, this will be acceptable to you, in others
it will not. gwin_image_draw_aspect
does the same thing, but will
preserve the proportions of the original image, centering the image in the space
provided. Note, however, that if you are also going to receive mouse events in this
window, details in the image may not be exactly where you expect them to be;
when the player clicks on the image, he will be clicking on the smaller, centered
image. For this reason, it is recommended that you avoid having the user click
on areas more specific than "within the boundaries of this image". Also,
since aspect preservation may leave empty space in the window, be careful to
always draw the background when changing the image, or you may leave behind "ghosts"
of previous images in the unused space.
Rather than gwin_image_draw_aspect
and gwin_image_draw
,
you can also use gwin_image_draw_auto
. This function first checks
the corresponding GraphWin. If the aspected
attribute is set, GWindows
will preserve the aspect ratio when scaling the image, if not, it will draw the
image to the exact size specified. GWindows widgets such as GDrawImageGrid use
gwin_image_draw_auto
so that you can control whether or not the images retain
their original aspect ratio
Because this is such a common use for a graphics window, the widget
GImageWin
exists for this purpose. Instead of the code
above, we could do:
GImageWin -> -> -> spellarea
with split 11,
split_dir winmethod_Below,
image SPELL_PIC;
Which would do the same thing. GImageWin
is documented in chapter 8.
You should never call the redraw method by yourself. If you want to
force the spell window to be redrawn, call
GW_ForceRedraw(spellarea);
Window Pairs provide a method checkredraw
, which
redraws the tree from that point down, as
needed. Active_UI.checkredraw()
is called once per turn,
to keep the interface fresh.
You don't usually want to force a window to be redrawn, however,
even with GW_ForceRedraw
; it's usually better to give the
window in question the general
attribute, and wait for it
to be redrawn at the end of the turn. This way, the window will only
be redrawn once, even if you make several changes to it.
You should, however, call GW_ForceRedraw
if a change
to a window occurs outside of a normal turn. For example, pressing one
of the buttons on the left of our sample interface might change the
contents of the menu window, without causing the normal turn sequence
to occur. If you didn't call GW_ForceRedraw
, the change
would not become visible until the player had given his next command
in the main window.
5.4 Updating Windows
In addition to redraw, GWindows can provide an
update
method which is called once per turn to keep their
contents fresh.
update
is similar to redraw
, but it
serves a different purpose. When redraw
is called, you
can assume the window's contents are trashed, and you must rebuild
them from scratch. update
is called every turn. Most
windows won't need an update method, but one that will is the status
line.
TextGrid -> status
with split 1,
split_dir winmethod_Above,
update [;
glk_window_clear(self.winid);
glk_window_move_cursor(0,0);
print (name) location;
glk_window_move_cursor(20,0);
print score;
],
has abssplit;
Now, the status window will be updated every turn to show
the player's score and location. A much better status line is
available in the widget GStatusWin
. This widget emulates
the library's status line.
GstatusWin -> status;
Since GStatusWin
has a default split size of 1
line, and a default split direction of above, you don't even need to
specify the size. Of course, if you want a customized status line,
you're on your own. The update method of a status window can be
written in much the same way as you'd write a custom
DrawStatusLine
function in the standard library.
Again, you shouldn't call update
yourself. To force an
update earlier than usual, you can call DrawStatusLine(),
which GWindows uses to call the update methods on all windows.
5.5 Getting mouse input from windows
The main form of input, typing a command, is already handled
by the library. If you want to take mouse input from a window,
GWindows provides the click_event(x,y)
hook.
Let's consider the button window. For right now, we'll keep it
pretty boring. Let's say that whenever the user clicks anywhere in the
button window, we call a function called
ButtonPressed
.
GImageWin -> -> -> -> -> -> -> buttons
with split 47,
split_dir winmethod_Below,
image BUTTON_PIC,
click_event [ x y; ButtonPressed(); ],
has on;
The attribute on
is used to specify that this
window should receive clicks. GWindows will automatically request
clicks from a window that has a click_event
, and which is
set on
. If you turn this attribute off, however, you have
to manually cancel its mouse request (and likewise, if you turn it on
again later, you have to request a mouse event); GWindows only makes
the request for you when it's setting up the screen.
click_event
is called with the X and Y co-ordinate of
the click. For a graphics window, this is the co-ordinate within the
window, in pixels. For text grids, this is the co-ordinate of the
particular character that was clicked.
For graphics windows, you have to remember that unless the window size is absolute, the click co-ordinates will refer to the window as the player sees it, so if his screen is twice as big as the image, his co-ordinates will be doubled. You can scale the click back into your co-ordinate system by the following formula:
scaled_x = ( x * your_width) / window.width;
scaled_y = ( y * your_height) / window.height;
Let's do it for the button window:
GImageWin -> -> -> -> -> -> -> buttons
with split 47,
split_dir winmethod_Below,
image BUTTON_PIC,
click_event [ x y;
x=(x*BUTTON_WIDTH)/self.width;
y=(y*BUTTON_HEIGHT)/self.height;
ButtonPressed();
],
has on;
Where BUTTON_WIDTH
and
BUTTON_HEIGHT
are given by you, and are the dimensions of
BUTTON_PIC
. You can also use
glk_image_get_info
to find out the size of the image at
run-time.
Remember that clicking on a window doesn't count as a turn. If the button actually does something in the game world (for example, if you click on the menu option "Quit Game"), you'll probably want to use a command override to cause a turn to progress. This is a good thing, because it generally means that all the game-world actions performed by clicks also have a corresponding textual command. Users who can't or don't want to use the mouse will have the same functions available to them. Command overrides are covered in the next chapter.
5.6 Getting character input from windows
The same basic method applies if you want to read single characters from a window. Let's say that you want to make the popup menu respond to the key 'q' by quitting the game.
TextGrid -> -> -> -> -> ->
-> popupmenu
with split 27,
split_dir winmethod_Right,
char_event [ x;
if (x=='q') cmd_override="QUIT";
],
has on;
char_event(x)
is called with the key pressed
whenever a character is typed into the window.
It works more or less like
click_event
.
This example uses a command override, which will be discussed in the next chapter.