/* ** 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 = { ">\" text=\"<>\">"; "<> \^<>=quit \^<< self.keyList[M_PREV]>>=previous menu
\^<>=up \^<< self.keyList[M_DOWN]>>=down \^<>=select"; "
"; } // 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\(<>\)\b"; for (i = 1; i <= len; i++) { if (i < 10) "\ "; "<>.\ <<(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); "<>\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 { ">\" text=\"<>\">"; "
>\"> "; 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) ">\">>"; else ">"; // Make each selection a plain (i.e. unhilighted) HREF ">\">"; (self.myContents[i]).title; "
"; } "
"; "
"; 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 ""; } removeStatusLine = { ""; } ; // 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\(<>\)\b"; for (i = 1; i <= len; i++) { if (i < 10) "\ "; "<>.\ <<(self.myContents[i]).title>>\n"; } "\bSelect a topic number, or press ’<< topMenu.keyList[M_QUIT]>>‘ to quit, ’<>‘ 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); "<>\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 { ">\" text=\"<>\">"; "
>\"> "; for (i = 1; i <= len; i++) { if (selection != i) ">\">>"; else ">"; (self.myContents[i]).title; "
"; } "
"; "
"; 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\(<>\)\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++) { "<> [<>/<>]\b"; // If we're at the end, let the player know by printing the // goodbye message if (i == len) "<>\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.lastDisplayed>>/<>]\b"; if (self.lastDisplayed == len) "<>\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 ">\" text=\"<>\">"; "
>\"> "; // 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) { "
"; "
"; 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); ">\" text=\"<>\">"; "
>\"> "; // 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; } "<> [<>/<>]
"; if (i == len) say(self.goodbye); i++; } "
"; "
"; 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 display(topMenu) = { local key, loc; clearscreen(); "
\(<>\)
\b"; // Simply dump out our contents, then print the goodbye message. "<>\b<>"; key = lower(inputkey()); loc = find(topMenu.keyList, key); if (loc && (loc % 2 == 0)) loc--; clearscreen(); return (loc != M_QUIT); } displayHTML(topMenu) = { local ret; // 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; } ;