/*
** menus.t allows you to add simple menus to your HTML TADS game.
** For an example of their use, see the Arrival or Common Ground
** source (in if-archive/games/tads/source). Feel free to use in
** your own TADS 2 games.
**
** Copyright (c) 1998, 1999 Stephen Granade. All rights reserved.
**
** Special thanks to Mike Roberts, whose help with this module was
** invaluable.
*/
#pragma C+
// The keys used to navigate the menus are held in the keyList array of the
// menuItem. Each navigation option is bound to two keys. The #defines below
// are for accessing the keys associated with the navigation options (quit,
// previous menu, up a selection, down a selection, select a menu item).
// Since each navigation option is bound to two keys, the #defines consist
// of odd numbers.
#define M_QUIT 1
#define M_PREV 3
#define M_UP 5
#define M_DOWN 7
#define M_SEL 9
// The menuItem is the topmost object in a menu tree.
class menuItem: object
title = "" // The name of the menu
myContents = [] // The submenuItems, topicItems, and longTopicItems
bgcolor = 'statusbg' // Background color of the menu
fgcolor = 'statustext' // Foreground color of the menu
indent = '10' // # of spaces to indent the menu's contents
allowHTML = true // Assume the game is running in HTML mode
// (i.e. "\H+" has been printed)
keyList = ['q' '' 'p' '[left]' 'u' '[up]' 'd' '[down]' 'ENTER' '\n'
'[right]' ' ']
// topBanner creates the HTML TADS banner for the menu. The banner contains
// the title of the menu on the left and the navigation keys on the right
topBanner = {
"<banner id=MenuTitle><body bgcolor=\"<<self.fgcolor>>\"
text=\"<<self.bgcolor>>\">";
"<<self.title>>
<tab align=\"right\">\^<<self.keyList[M_QUIT]>>=quit \^<<
self.keyList[M_PREV]>>=previous menu<br>
<tab align=\"right\">\^<<self.keyList[M_UP]>>=up \^<<
self.keyList[M_DOWN]>>=down \^<<self.keyList[M_SEL]>>=select";
"</banner>";
}
// Call menu.display when you're ready to show the menu.
display = {
local i, selection, len = length(self.myContents), key = '', loc;
// If we are running on an HTML TADS interpreter and the game uses
// HTML formatting, use HTML tags to make the menus prettier
if (systemInfo(__SYSINFO_SYSINFO) == true &&
systemInfo(__SYSINFO_HTML) == 1 && self.allowHTML) {
self.displayHTML;
return;
}
// For standard TADS, print the title, then the menu options
// (numbered), then ask the player to make a selection.
do {
"\b\(<<self.title>>\)\b";
for (i = 1; i <= len; i++) {
if (i < 10) "\ ";
"<<i>>.\ <<(self.myContents[i]).title>>\n";
}
"\bSelect a topic number, or press ’<<
self.keyList[M_QUIT]>>‘ to quit.\ ";
do { // The input loop
key = lower(inputkey()); // Get the key
loc = find(self.keyList, key); // Get the key's pos in keyList
selection = cvtnum(key); // Turn key into a #, if possible
if (loc && (loc % 2 == 0)) // If loc is even, make it odd
loc--;
} while (selection == 0 && loc != M_QUIT);
"<<key>>\n"; // Print the selection
// If selection is a number, then the player selected that menu
// option. Call that submenu or topic's display routine. If the
// routine returns nil, the player selected QUIT, so we should
// quit as well.
if (selection != 0) {
if (!((self.myContents[selection]).display(self)))
loc = M_QUIT;
}
} while (loc != M_QUIT && loc != M_PREV); // Loop until player quits
}
displayHTML = {
local i, selection = 1, len = length(self.myContents), key = '', loc,
events;
// Erase the status line and print the topmost menu banner
self.removeStatusLine;
self.topBanner;
// For HTML TADS, set up a separate menu item banner. In it,
// print the menu's contents. Next to the current selection, print
// a greater-than sign. Each of the menu items is an HREF, so the
// player can select it using the mouse
do {
"<banner border id=MenuBody><body bgcolor=\"<<self.bgcolor>>\"
text=\"<<self.fgcolor>>\">";
"<table><tr><td width=\"<<self.indent>>\"> </td><td>";
for (i = 1; i <= len; i++) {
// To get the alignment right, we have to print '>' on each
// and every line. However, we print it in the background
// color to make it invisible everywhere but in front of the
// current selection.
if (selection != i)
"<font color=\"<<self.bgcolor>>\">></font>";
else ">";
// Make each selection a plain (i.e. unhilighted) HREF
"<a plain href=\"<<i>>\">";
(self.myContents[i]).title;
"</a><br>";
}
"</td></tr></table>";
"</banner>";
do { // The input loop. For HTML TADS,
events = inputevent(); // we look at events rather than
// just keystrokes
if (events[1] == INPUT_EVENT_HREF) { // For HREFs, set the
selection = cvtnum(events[2]); // selection # to the
loc = M_SEL; // HREF's value
}
else {
key = lower(events[2]); // Otherwise, assume the event is
// a keystroke and get the key
loc = find(self.keyList, key); // Find key's pos in keyList
if (loc && (loc % 2 == 0)) // If loc is even, make it odd
loc--;
}
// First off, handle arrow keys
if (loc == M_UP && selection != 1)
selection--;
else if (loc == M_DOWN && selection != len)
selection++;
} while (loc == nil); // Loop until the user presses a valid key
if (loc >= M_SEL) { // The player made a selection, so show it
if (!((self.myContents[selection]).displayHTML(self)))
loc = M_QUIT;
}
} while (loc != M_QUIT && loc != M_PREV);
// Clean up after ourselves
"<banner remove id=MenuTitle><banner remove id=MenuBody>";
}
// submenuItem is exactly like menuItem, except that it refers to the top-
// level menu item to get the keyList
class submenuItem: object
title = ""
myContents = []
bgcolor = 'statusbg'
fgcolor = 'statustext'
indent = '10'
display(topMenu) = {
local i, selection, len = length(self.myContents), key = '', loc;
do {
"\b\(<<self.title>>\)\b";
for (i = 1; i <= len; i++) {
if (i < 10) "\ ";
"<<i>>.\ <<(self.myContents[i]).title>>\n";
}
"\bSelect a topic number, or press ’<<
topMenu.keyList[M_QUIT]>>‘ to quit,
’<<topMenu.keyList[M_PREV]>>‘ to go to the
previous menu.\ ";
do {
key = lower(inputkey());
loc = find(topMenu.keyList, key);
selection = cvtnum(key);
if (loc && (loc % 2 == 0))
loc--;
} while (selection == 0 && loc != M_QUIT && loc != M_PREV);
"<<key>>\n";
if (selection != 0) {
if (!((self.myContents[selection]).display(topMenu)))
return nil;
}
} while (loc != M_QUIT && loc != M_PREV);
return (loc == M_PREV);
}
displayHTML(topMenu) = {
local i, selection = 1, len = length(self.myContents), key = '', loc,
events;
while (true) {
do {
"<banner border id=MenuBody><body bgcolor=\"<<self.bgcolor>>\"
text=\"<<self.fgcolor>>\">";
"<table><tr><td width=\"<<self.indent>>\"> </td><td>";
for (i = 1; i <= len; i++) {
if (selection != i)
"<font color=\"<<self.bgcolor>>\">></font>";
else ">";
(self.myContents[i]).title;
"<br>";
}
"</td></tr></table>";
"</banner>";
do {
events = inputevent();
if (events[1] == INPUT_EVENT_HREF) {
selection = cvtnum(events[2]);
loc = M_SEL;
}
else {
key = lower(events[2]);
loc = find(topMenu.keyList, key);
if (loc && (loc % 2 == 0))
loc--;
}
if (loc == M_UP && selection != 1)
selection--;
else if (loc == M_DOWN && selection != len)
selection++;
} while (loc == nil);
} while (loc == M_UP || loc == M_DOWN);
if (loc >= M_SEL) {
if (!((self.myContents[selection]).displayHTML(topMenu)))
return nil;
}
else return (loc == M_PREV);
}
}
;
// topicItem displays a series of entries successively. It's intended to be
// used for displaying a group of hints. Unlike [sub]menuItem, myContents
// contains a list of strings to be displayed
class topicItem: object
title = "" // The name of this topic
myContents = [] // A list of strings to be displayed
bgcolor = 'statusbg' // The background color
fgcolor = 'statustext' // The foreground color
indent = '30' // How far to indent all of the strings
lastDisplayed = 1 // The last string displayed
chunkSize = 6 // How many strings to display at once, max
// (only valid under HTML TADS)
goodbye = '[The End]' // The ending phrase
display(topMenu) = {
local i, len = length(self.myContents), key = '', loc;
"\b\(<<self.title>>\)\b";
// Print all of the strings up to and including lastDisplayed. Also,
// append "[#/#]" after them to show which hint out of how many
// each is.
for (i = 1; i <= self.lastDisplayed; i++) {
"<<self.myContents[i]>> [<<i>>/<<len>>]\b";
// If we're at the end, let the player know by printing the
// goodbye message
if (i == len)
"<<self.goodbye>>\n";
}
while (true) {
key = lower(inputkey());
loc = find(topMenu.keyList, key);
if (loc && (loc % 2 == 0))
loc--;
if (loc == M_QUIT)
return nil;
if (loc == M_PREV || self.lastDisplayed == len)
return true;
self.lastDisplayed++;
"<<self.myContents[self.lastDisplayed]>> [<<
self.lastDisplayed>>/<<len>>]\b";
if (self.lastDisplayed == len)
"<<self.goodbye>>\n";
}
}
displayHTML(topMenu) = {
local i, selection = 1, len = length(self.myContents), key = '', loc,
firstTime = true, topTopic = 1, chunk = self.chunkSize, events;
while (true) {
// Set up the banner
"<banner border id=MenuBody><body bgcolor=\"<<self.bgcolor>>\"
text=\"<<self.fgcolor>>\">";
"<table><tr><td width=\"<<self.indent>>\"> </td><td>";
// The firstTime variable flags whether or not this is our first
// trip through this loop. If it's not, we need to adjust the
// # of displayed hints, among other things.
if (firstTime)
i = 1;
else {
// Only display a # of strings equal to or less than chunkSize
if (topTopic != 1 &&
self.lastDisplayed - topTopic < self.chunkSize)
i = topTopic;
else i = self.lastDisplayed - self.chunkSize + 1;
if (i < 1)
i = 1;
}
while (i <= self.lastDisplayed) {
// If we display a chunk's worth of strings and this is our
// first time through the loop we pause, then keep printing
// out the rest of the strings
if (i > chunk && firstTime) {
"</td></tr></table>";
"</banner>";
events = inputevent();
if (events[1] == INPUT_EVENT_HREF) { // There should be
selection = cvtnum(events[2]); // no HREF events,
loc = M_SEL; // but just in case...
}
else {
key = lower(events[2]);
loc = find(topMenu.keyList, key);
if (loc && (loc % 2 == 0))
loc--;
}
if (loc == M_QUIT || loc == M_PREV)
return (loc != M_QUIT);
"<banner border id=MenuBody><body
bgcolor=\"<<self.bgcolor>>\"
text=\"<<self.fgcolor>>\">";
"<table><tr><td width=\"<<self.indent>>\"> </td><td>";
// Increment the counter which keeps track of the string
// # at which the next chunk ends. Also bump up the
// current top topic string #.
chunk += self.chunkSize;
topTopic = i;
}
"<<self.myContents[i]>> [<<i>>/<<len>>]<BR>";
if (i == len)
say(self.goodbye);
i++;
}
"</td></tr></table>";
"</banner>";
key = lower(inputkey());
loc = find(topMenu.keyList, key);
if (loc == M_QUIT || loc == M_PREV ||
self.lastDisplayed == len) {
return (loc != M_QUIT);
}
self.lastDisplayed++;
firstTime = nil; // This is no longer our first time through
// the loop
}
}
;
// longTopicItems are used to print out big long gobs of text on a subject.
// Use it for printing long treatises on your design philosophy and the like.
class longTopicItem: object
title = ""
myContents = '' // This can be a string or a routine
goodbye = '[The End]' // The goodbye message
// longTopicItems are displayed the same in HTML TADS as they are
// in standard TADS, barring some banner fiddling.
ret = self.display(topMenu);
topMenu.topBanner;
return ret;
}
;