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:

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.