[glk, I7] the whole enchilada

Posted below is the complete code for the picture-displaying module that I’ve been working on, bolted onto a one-room game. Briefly, it works like this: the game displays a thumbnail image in the main text window. When the player clicks on the thumbnail, the game opens up a graphics window and displays the full-size picture. When the player clicks on the picture OR presses any key, the graphics window disappears and play resumes in the main text window.

It mostly works, except for the following problems:

  1. I can’t get the game to accept a ‘press any key’ event.
  2. I can’t get the status line to revert to normal behavior after the picture goes away.
  3. The game won’t print a command line after the picture goes away (minor - I can fake this)

I’m so close to getting this working, but I’ve kind of hit a wall. I’m posting the entire code in the hopes someone with a better knowledge of glk might have some insight. I would be incredibly grateful.

Note: the Windows Inform IDE doesn’t support graphics in the main text window, apparently, so to actually see this in action you have to fire up the blorb and run it in Glulx interpreter. I’ve attached the blorb file at the bottom of this post, along with the two graphics files in case anyone wants to try compiling this at home.

[spoiler][code]Include Basic Screen Effects by Emily Short.
Include Glulx Entry Points by Emily Short.
Include Glulx Text Effects by Emily Short.

[The overall concept is this: At various points throughout the game, small “thumbnail” graphics will appear in the main window. These are hyperlinked. If the player clicks a thumbnail:
1) The game opens up a graphical window that covers the main window entirely (it splits the main window, but takes up 100% of the window space) and displays a larger illustration.
2) The game also displays a caption for the picture in the status line.
3) If the player clicks on the larger illustration, OR presses any key, the graphical window closes and the player is returned to normal gameplay.

Here are the problems so far:

1. I cannot get the game to accept a character event while the graphical window is open. The mouse-click works fine, but game does not respond to keyboard input at all until after the graphical window is closed.

2. If the player presses the ENTER key while the graphical window is open, the status line reverts to its normal behavior (displaying the room name), while the graphical window stays up.

2.5 Related to item 2, after the graphics window closes, the status line won't rever to its normal behavior UNTIL the player hits the Enter key. 

3. When the player clicks on the illustration, the window closes and gameplay resumes as it should, BUT the game won't print a command prompt (">") on its own, and the player's input is in the wrong font. I can fake this if I have to, but it'd be nice to know why this is happening.]

Rule for constructing the status line:
center “[the player’s surroundings]” at row 1;
rule succeeds.

Test Chamber is a room. “[Figure - Red Button]Not much to do here, [if Figure - Red Button has been displayed]now that you’ve pushed[otherwise]other than push[end if] that big, shiny red button in the corner.” The big shiny red button is scenery in the Test Chamber. Instead of doing something with the button, say “Just click it with your mouse.”

Figure - Red Button is the file “red_button.png”.
Figure - Gorilla Shark is the file “gorilla_shark.png”.

Section - the table of illustrations

[In the full game, this table will match up all the full illustrations with their thumbnails and caption text.]

Table of Illustrations
link-number illustration-name thumb-name display-status caption
102 Figure - Gorilla Shark Figure - Red Button false “Totally Awesome!!”

To decide if (F - a figure-name) has been displayed:
if the display-status corresponding to a thumb-name of F in the Table of Illustrations is false, no;
otherwise yes.

Section - displaying thumbnails

[This part all works pretty much the way it’s supposed to, assuming the interpreter supports graphics in the main window.]

To say (F - a figure-name):
if F has been displayed:
do nothing;
otherwise:
let N be the link-number corresponding to a thumb-name of F in the Table of Illustrations;
display F with link N.

To display (F - a figure-name) with link (N - a number):
(- DrawThumb({F},{N}); -).

Include (-
[ DrawThumb F N;
glk_request_hyperlink_event(gg_mainwin);
glk_set_hyperlink(N);
glk_image_draw(gg_mainwin, ResourceIDsOfFigures–>F, imagealign_MarginLeft, 0);
glk_set_hyperlink(0);
];
-).

Section - handling input

[The handling for the hyperlink event works more or less as it should. The code that puts the figure caption in the the status line works, but causes the main window to malfunction once the graphical window is closed.]

Glulx input handling rule for a hyperlink-event:
let N be the character code returned;
let C be the caption corresponding to a link-number of N in the Table of Illustrations;
let F be the illustration-name corresponding to a link-number of N in the Table of Illustrations;
reveal the full illustration of F;
clear only the status line;
center “[C]” at row 1;
now the display-status corresponding to a link-number of N in the Table of Illustrations is true.

Glulx input handling rule for a mouse-event: [this rule does what it should]
return to play.

Glulx input handling rule for a char-event when the illustration window is open: [this rule never fires while the graphical window is open]
return to play.

To decide if the illustration window is open:
(- (gg_illustration_window) -).

[This is the routine that should run when the player clicks on the big illustration or hits any key. It gets called in the former case but never in the latter. It should do the following things:
1. close the illustration window;
3. reconstruct the standard status line;
2. clear the main window screen;
4. process a LOOK command, so that the player can see the room description.

Note, steps 1 and 3 don't have to happen in any particular order, as long as they work.

This is working more-or-less properly, except that a) the game doesn’t print a command prompt on its own, and b) the status line goes blank and won’t revert to its normal behavior until the player hits ENTER. If I manually reset the status line (by carrying out the constructing a status line activity or telling Inform to center “[location]” at row 1), it works, but then the game won’t return functionality to the main window at all, and the ‘try looking’ and ‘say’ commands don’t do anything.]

To return to play:
close the illustration window;
redraw the status line; [this blanks out the status line but doesn’t print the location until the player]
clear the screen; [hits enter.]
try looking;
say “>[input style][run paragraph on]”. [this line prints a new command prompt and sets the font to input style, because]
[the game won’t do it on its own for some reason.]

To redraw the status line:
(- DrawStatusLine(); -).

Section - opening the full illustration window

Include (-
Constant GG_ILLUSTRATION_ROCK 210;
Global gg_illustration_window = 0;
-) after “Global Variables” in “Output.i6t”.

[This part mostly does what it should. It’s supposed to:
1. open a graphical window that takes up 100% of the main window;
2. fill the window with black;
3. draw the appropriate image in the window;
4. wait for a mouse-click;
5. OR any keyboard input

After a mouse-click or a keypress, the game should run the "return to play" procedure, above.]

To reveal the full illustration of (F - a figure-name):
(-
if (gg_illustration_window == 0) {
gg_illustration_window = glk_window_open(gg_mainwin, (winmethod_Above+winmethod_Proportional), 100, wintype_Graphics, GG_ILLUSTRATION_ROCK );
}
if (gg_illustration_window) { ! testing to see if the window exists
BlackBackground();
DisplayPicture(ResourceIDsOfFigures–>{F});
glk_request_mouse_event(gg_illustration_window); ! wait for a mouse-click event
glk_request_char_event(gg_mainwin); ! OR press any key
}
-).

[It’s step 5 that doesn’t work. The glk_request_char_event line does nothing, as far as I can tell; the interpreter does allow keyboard input (if you can set the window percentage to something less than 100%, you can see the keyboard input being printed in the main window), but the event handling rules never fire.]

Section - blacking out the background

[This is copied nearly verbatim from Emily Short’s Simple Graphical Windows extension. It works just fine.]

Include (-
[ BlackBackground color result graph_width graph_height;
if (gg_illustration_window) {
result = glk_window_get_size(gg_illustration_window, gg_arguments, gg_arguments+WORDSIZE);
graph_width = gg_arguments–>0;
graph_height = gg_arguments–>1;
glk_window_fill_rect(gg_illustration_window, 0, 0, 0, graph_width, graph_height);
}
];
-).

Section - scaling and displaying the picture

[This is also swiped from SGW. It also works exactly as it should.]

Include (-
[ DisplayPicture cur_pic result graph_width graph_height img_width img_height h_total w_total h_offset w_offset;
if (gg_illustration_window) {

	result = glk_window_get_size(gg_illustration_window, gg_arguments, gg_arguments+WORDSIZE);
		graph_width  = gg_arguments-->0;
		graph_height = gg_arguments-->1;

	result = glk_image_get_info( cur_pic, gg_arguments,  gg_arguments+WORDSIZE);
		img_width  = gg_arguments-->0;
		img_height = gg_arguments-->1;

	w_total = img_width;
	h_total = img_height;

	if (graph_height - h_total < 0) {	! if the image won't fit, find the scaling factor
		w_total = (graph_height * w_total)/h_total;
		h_total = graph_height;
	}

	if (graph_width - w_total < 0) {
		h_total = (graph_width * h_total)/w_total;
		w_total = graph_width;
	}

	w_offset = (graph_width - w_total)/2; if (w_offset < 0) w_offset = 0;
	h_offset = (graph_height - h_total)/2; if (h_offset < 0) h_offset = 0;

	glk_image_draw_scaled(gg_illustration_window, cur_pic, w_offset, h_offset, w_total, h_total); 
}

];
-).

Section - removing the illustration

[Finally, this part just closes the graphics window. As far as I can tell, it’s doing its job.]

To close the illustration window:
(-
if (gg_illustration_window) {
glk_window_close(gg_illustration_window, 0);
}
-).
[/code][/spoiler]
gorilla_shark.png
red_button.png
story.gblorb (667 KB)

Progress . . . sort of.

I’ve discovered that if I add a glk_cancel_line_event command to the routine that reveals the full illustration (see below), then the glk_request_char_event works! Now the main illustration disappears as soon as you hit any key.

HOWEVER, now, after the illustration disappears, and a room description is printed, the game won’t accept any keyboard input whatsoever. The interpreter might as well be frozen.

I have to admit I’m totally flailing here. I discovered this “fix” based on a vague and intuitive interpretation of the glk documentation – guesswork, really – but I don’t really understand what the glk_cancel_line_event command does, so I don’t really understand what’s happening here or why it’s happening. Does anyone have any insight into this?

To reveal the full illustration of (F - a figure-name):
	(- 
		if (gg_illustration_window == 0) {
			gg_illustration_window = glk_window_open(gg_mainwin, (winmethod_Above+winmethod_Proportional), 100, wintype_Graphics, GG_ILLUSTRATION_ROCK );
		}
		if (gg_illustration_window) { ! testing to see if the window exists
			BlackBackground();
			DisplayPicture(ResourceIDsOfFigures-->{F});
			glk_cancel_line_event(gg_mainwin, NULL);		! this line allows the request_char_event line to work, for some reason
			glk_request_mouse_event(gg_illustration_window);	! wait for a mouse-click event
			glk_request_char_event(gg_mainwin);				! OR press any key
		}
	-).

story_v2.gblorb (667 KB)

Your problems are probably caused by having both kinds of keyboard input active at the same time. The normal parser is waiting for line input, and then you request character input. I’m guessing the line input takes precedence, so key presses appear to do nothing, until you press enter, at which time it tries to parse the ‘command’, resulting in the status line changing. But the graphics window doesn’t get closed because it’s waiting for a character input event, which never comes.

The right thing to do is to stop waiting for line input events when you start waiting for character input, which you just discovered. But when you close the window you’ll need to start listening for line input events again so that the parser can continue.

I was thinking that might be the case, but I’m a bit stymied as to how to make a glk_request_line_event command work. The specs give the format as

glk_request_line_event(winid_t win, char *buf, glui32 maxlen, glui32 initlen);

I know what win should be, and I can guess what maxlen and initlen should be, but what do I put for buf? Is there an “address” for the buffer used by the main window? Does it even matter?

I’m not able to look myself now, but the easiest thing would be to just copy the glk_request_line_event() call from the templates. Or search the build.inf for glk_request_line_event - there will probably only be one or two, and it should be easy enough to tell which one is the parser and which one is something else (final question, yes/no questions etc. Though they may even all use the same buffer… I can’t remember.)

I searched the build file and found this:

glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0);

Added it to the close window routine like so:

[code]To close the illustration window:
(- ClosePicture(); -).

Include (-
[ ClosePicture a_buffer;
if (gg_illustration_window) {
glk_window_close(gg_illustration_window, 0);
glk_cancel_char_event(gg_mainwin);
glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0);
}
];
-).[/code]

It returns keyboard input to the main window and lets you type a command, but when you hit Enter, the interpreter crashes. “Glulxe fatal error: Memory write to read-only address (4).”
story_v3.gblorb (667 KB)

Diligent interpreters will choke with a fatal error if you do this.

(Life would be easier if the interpreter bundled with I7 were diligent.)

(I will look more at this question later today, I promise.)

You aren’t initializing a_buffer anywhere, so it’s defaulting to zero instead of the actual address of the buffer. Perhaps you could save the buffer address into a register by altering VM_ReadKeyboard in Glulx.i6t, then read from that when you turn line input back on?

Technically it’s defaulting to zero plus whatever the constant WORDSIZE is set to, right? I don’t know what that is, although I guess it’d be easy enough to find out.

I’d have figured there was a way to get the buffer “address” of a window with a glk command, but I can’t find a command in the specs that does that. But then I’m not really certain that the phrase “get the buffer address of a window” has any meaning whatsoever in glk.

It doesn’t.

In this case it isn’t the address of the window buffer, but of the buffer that the parser wants the player’s input in. It’s passed to the keyboard primitive functions, and I don’t think it’s stored anywhere.

Okay, I have a clearer idea what’s going on…

First bug fix:

To close the illustration window:
	(- 
		if (gg_illustration_window) { 
			glk_window_close(gg_illustration_window, 0);
			gg_illustration_window = 0;
			} 
	-).

Gotta reset that global to zero, or all your “if (gg_illustration_window)” tests will be wrong.

I then added a cancel_line_event call:

To reveal the full illustration of (F - a figure-name):
	(- 
		if (gg_illustration_window == 0) {
			gg_illustration_window = glk_window_open(gg_mainwin, (winmethod_Above+winmethod_Proportional), 100, wintype_Graphics, GG_ILLUSTRATION_ROCK );
		}
		if (gg_illustration_window) { ! testing to see if the window exists
			BlackBackground();
			DisplayPicture(ResourceIDsOfFigures-->{F});
			glk_cancel_line_event(gg_mainwin, gg_event);
			glk_request_mouse_event(gg_illustration_window);	! wait for a mouse-click event
			glk_request_char_event(gg_mainwin);				! OR press any key
		}
	-).

When the hyperlink event comes in, the parser is waiting for line input. You have to cancel that before requesting character input (or printing any text).

After the reveal call, the parser still thinks it’s waiting for line input, but it won’t get any. This is a mean trick to play on the poor parser, but it’ll behave the way you want.

Now a mouse event comes in. You now have to reverse the changes that you made:

To return to play:
	cancel character input in the main window;
	close the illustration window;
	redraw the status line;					[this blanks out the status line but doesn't print the location until the player]
	clear the screen;						[hits enter.]
	try looking;
	say ">[run paragraph on]";
	re-request line input in the main window;

To re-request line input in --/the main window:
	(- glk_request_line_event(gg_mainwin, buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0); -).

Again, you must cancel the character request before you print anything. It’s safest to do this first. Go through your look action, print a new prompt, and (lastly) re-request line input.

(The mysterious a_buffer is a local variable inside the parser loop. You don’t have access to it. I’m passing buffer, the global array here, because we happen to know that the parser loop always reads input into there.) (Unless it’s doing disambiguation input, in which case you’ve broken the disambig question… oh well.)

It’s not necessary to set input style; the interpreter always does input in input style. However, you do have to print a new prompt, because the parser loop never notices that anything has happened at all! The way this is set up, the entire window change happens “while waiting for line input”. (From the parser’s point of view.) The parser isn’t going to print a new prompt because it’s already printed a prompt for this input.

With these changes, the demo works in Gargoyle.

In Quixe, well, you run into a couple of problems. First – you’ve discovered a bug! Woo. Quixe doesn’t correctly hyperlink images. I’ll have to fix that.

Second, the zero-size main window refuses to pick up character input because it’s trying to MORE for you. All keystrokes go to making it page down, but it pages down zero lines each time… not so helpful. (Quixe’s policy is to MORE-page all windows before accepting more input, on the grounds that the player will probably want to pound space until they’ve seen all the text. This is a good policy when windows aren’t squashed. Might have to revisit it for this case.)

You may get better results by requesting character input in the status window. You know that’s going to be visible. I haven’t tested that change though.

Next I want to try rewriting this whole thing for Unified Glulx Input, which I designed to be awesome and handle all your request/cancel calls for you. However, it’s late and I have stuff to do tomorrow. So not right now.

Andrew, you are magnificent. What do you want for Christmas?

Any thoughts on how I can get the status line to reset itself properly? Any attempt to invoke a center at row X phrase, or the constructing the status line activity, outside of the normal turn sequence seems to throw the interpreter off the rails.

Wait, I got it! You have to call VM_Mainwindow () after messing with the status line; that seems to get everything back to normal. Unless I’m missing some horrible side effect or something.

As I think someone said above, don’t invoke “constructing the status line activity” outside a DrawStatusLine() call.

Ideally, use DrawStatusLine() as the universal entry point for – er – redrawing the status line. That’s what it’s for. :slight_smile:

DrawStatusLine() clears the status line and draws a fresh one, but it doesn’t write any text into the status line by itself – unless you rewrite the routine, which I’d like to not have to do.

This appears to work, however:

[code]To return to play:
close the illustration window;
clear only the status line;
center “[location]” at row 1;
return focus to the main window;
clear only the main screen;
try looking;
say “>[run paragraph on]”;
re-request line input in the main window.

To return focus to the main window:
(- VM_MainWindow(); -).[/code]

This is what I mean:

Rule for constructing the status line:
	center "[the player's surroundings]" at row 1;
	rule succeeds.

Rule for constructing the status line when the illustration window is open:
	let N be the current illustration link-number;
	let C be the caption corresponding to a link-number of N in the Table of Illustrations;
	center C at row 1;
	rule succeeds.


The current illustration link-number is a number that varies.

Glulx input handling rule for a hyperlink-event:
	let N be the character code returned;
	now the current illustration link-number is N;
	let F be the illustration-name corresponding to a link-number of N in the Table of Illustrations;
	reveal the full illustration of F;
	redraw the status line;
	now the display-status corresponding to a link-number of N in the Table of Illustrations is true.

To return to play:
	cancel character input in the main window;
	close the illustration window;
	redraw the status line;
	try looking;
	say ">[run paragraph on]";
	re-request line input in the main window;

You had a comment in there “why doesn’t this print the location until the player hits enter…” It’s because your use of “center C at row 1;” leaves output directed to the status window rather than the story window. This call, too, is meant to be used only from the status line activity.

In this new version, the code is tidier – all status-line code is in status-line-constructing rules. The functions that set and clear the screen are symmetrical, both calling “redraw the status line”. And you don’t wind up with output directed somewhere unexpected.

You need an additional global variable, but it turns out you want that anyhow. When you have a graphics window open, you have to be prepared to redraw it in case the window size changes.

…which would look like:

Glulx input handling rule for a redraw-event when the illustration window is open:
	let F be the illustration-name corresponding to a link-number of current illustration link-number in the Table of Illustrations;
	redraw the full illustration of F;

To redraw the full illustration of (F - a figure-name):
	(- 
		BlackBackground();
		DisplayPicture(ResourceIDsOfFigures-->{F});
	-).