customBanner.t | documentation |
#charset "us-ascii" #include <tads.h> #include "advlite.h" /* * Custom Banner version 1.2 * by Eric Eve * * Version date: 13-Sep-06 * Adapted for adv3Lite: 28-Dec-24 * * This file implements a CustomBannerWindow class that vastly eases * the process of setting up banners and displaying material in them. * e.g. to set up a graphics banner to display pictures, starting with * pic1.jpg at startup, but not appearing at all on an interpreter that * can't display JPEGs you could define: * * pictureWindow: CustomBannerWindow * canDisplay = (systemInfo(SysInfoJpeg)) * bannerArgs = [nil, BannerAfter, statuslineBanner, BannerTypeText, * BannerAlignTop, 10, BannerSizeAbsolute, BannerStyleBorder] * currentContents = '<img src="pic1.jpg">' * ; * * Then to change the picture dislayed at a later point, call: * * pictureWindow.updateContents('<img src="pic2.jpg">'); * * And everything else, including getting everything right on RESTART, UNDO * and RESTORE should be taken care of. */ /* ------------------------------------------------------------------------ */ /* * A CustomBannerWindow, like a BannerWindow, corrsponds to an on-screen * banner. The purpose of CustomBannerWindow is to eliminate most of the * busy-work that a game author would otherwise have to take care of in * displaying and manipulating banners. * * As with BannerWinnow, merely creating a CustomBannerWindow does not * display the banner. However, any CustomBannerWindows in existence at * the start of the game will be added to the screen display, unless the * condition specified in their shouldDisplay() method prevents initialization. * * The one property that must be defined on each instance of a CustomBannerWindow * is bannerArgs, which takes the form: * * bannerArgs = [parent, where, other, windowType, align, * size, sizeUnits, styleFlags] * * where each list element has the same meaning at the corresponding argument * to BannerWindow.showBanner() * * This merely ensures that the CustomBannerWindow is added to the screen's * banner window layout. To have the CustomBannerWindow display some content * when first added to the screen layout, override its current contents property: * * currentContents = 'My initial contents' * * To change what's displayed in a CustomBannerWindow from game code, call its * updateContents() method, e.g.: * * pictureWindow.updateContents('<img src="pics/troll.jpg">'); * * To redisplay the current contents, call the showCurrentContents() method. * By default a call to updateContents() or showCurrentContents() clears the * window before displaying the new content. To have the additional content * added to the existing content, change the clearBeforeUpdate property to nil. * * You can control whether the game uses this banner at all by overriding * the canDisplay property. The main purpose of this property is to easily allow * a game to run on different types of interpreter. For example, if your banner is * meant to display pictures in the JPEG format, there's no point having it included * in the screen layout of an interpreter that can't display JPEGs, and attempts to * update its contents with a new picture should do nothing. In which case we could * define: * * canDisplay = (systemInfo(SysInfoJpeg)) * * Calls to CustomBannerWindow methods like updateContents() and clearWindow() * should be safe on an interpreter for which shouldDisplay returns nil, since * by default these will do nothing beyond updating the banner's currentContents * property. This makes it easier to write game code that is suitable for all * classes of interpreter * * To have a CustomBannerWindow resize to contents each time its contents are * displayed, set its autoSize property to true. * * If you do not want a CustomBannerWindow you have defined not to be dispayed * at game startup, set its isActive property to nil. Call the activate() * method to add it to the screen layout and the deactivate() method to remove * it, or any other CustomBannerWindow, from the screen display. * * Obviously, it is then the game author's responsibility to ensure that no * other banner window that's meant to persist after pictureWindow is deactivated * depends on pictureWindow for its existence; i.e. that we're not deactivating * the parent of some other banner window(s) we want to keep or subsequently * activate, or the sibling of any banner window that's subsequently going to * defined in relation to us. */ class CustomBannerWindow: BannerWindow /* * The list of any banner windows that must be set up before me, * either one of them is my parent, or because I'm going * to be placed before or after them with BannerBefore or BannerAfter. * * If bannerArgs has been set up with the list of showBanner arguments, * then we can derive this information automatically */ initBeforeMe() { /* * If our bannerArgs property contains a list of the right length, i.e. 8 * elements, then the first and third elements of the list (our parent, and * the sibling we're to be placed before or after) must be initialized before * we are. If either of these is nil, no harm is done, since initBannerWindow() * will simply skip the nil value. * * Moreover, if our sibling is in the list, we don't need our parent as well, * since either our sibling or one of its siblings will initialize our parent. */ local lst = []; if (propType(&bannerArgs) == TypeList && bannerArgs.length() == 8) lst = bannerArgs[3] ? [bannerArgs[3]] : [bannerArgs[1]]; initBeforeMe = lst; return lst; } /* * A condition to test whether this banner window should actually display. * Normally this would test for the interpreter class if this would * affect whether we wanted this banner to be created. For example, if * we were going to use this banner window to display a JPEG picture, we * might not this window to display at all if the interpreter we're running * on can't display JPEGS, so we might write: * * canDisplay = (systemInfo(SysInfoJpeg)) * * If your complete system of CustomBanners depends on the same condition * (e.g. you don't want any CustomBanners if the interpreter we're running * on can't display JPEGs, then it's probably easiest to modify CustomBanner * and override scanDisplay on the modified class. * * By default, we simply check that the interpreter we're running on * can display banners. */ canDisplay = (systemInfo(SysInfoBanners)) shouldDisplay = (canDisplay && isActive) /* * The standard use of initBannerWindow is first to ensure that any * banner windows whose existence we presuppose have themselves been * initialized, and then to set up our own window on screen. * This function should be used for initializing banner window *layout*, * not content. */ initBannerWindow() { /* * If we shouldn't display on this class of interpreter, don't * initialize us. */ if(!shouldDisplay) return nil; /* * If we've already been initialized, there's nothing left to do. */ if(inited_) return true; /* * Initialize all the bannner windows on whose existence our own * depends. If one of them can't be initialized, neither can we, * in which case return nil to show that our initialization failed. * If, however, the parent or sibling banner window we want initialized * before us is not a CustomBannerWindow, then its initBannerWindow() * won't have a return value, in which case we ignore the fact that * it returns nil */ foreach(local ban in initBeforeMe) if(ban && !ban.initBannerWindow() && ban.ofKind(CustomBannerWindow)) return nil; /* * Create my banner window on screen; if this fails return nil * to indicate that the window could not be created */ return (inited_ = initBannerLayout()); } /* * Initialize my onscreen layout, normally through a call to showBanner(), * whose return value this method should return, e.g.: * * initBannerLayout() * { * return showBanner(nil, BannerAfter, statuslineBanner, * BannerTypeText, BannerAlignTop, 1, BannerSizeAbsolute, * BannerStyleBorder); * } * * By default we simply call initBannerLayout() using our bannerArgs. */ initBannerLayout() { return showBanner(bannerArgs...); } /* * The list of args used to define our screen layout, as they would be passed * to showBanner. This is used both by initBannerLayout and initBeforeMe. * * The args should be listed in the form * * bannerArgs = [parent, where, other, windowType, align, size, sizeUnits, styleFlags] * * e.g. * bannerArgs = [nil, BannerAfter, statuslineBanner, * BannerTypeText, BannerAlignTop, 1, BannerSizeAbsolute, * BannerStyleBorder] * */ bannerArgs = nil /* * The current contents to be displayed in this window, which could be * a string of text, or the HTML string to display a picture. * * currentContents can be overridden to hold the initial contents * we want this banner to display, but it should not otherwise be * directly written to in game code. To display new contents in the * banner, use updateContents() instead. */ currentContents = '' /* * Is this banner currently active? Set to nil if you don't want to this * CustomBannerWindow to be active at startup; thereafter use the deactivate() * and activate() methods */ isActive = true /* * deactivate a currently active banner; this removes it from the screen * and prevents writing anything further to it. Be careful to respect the * dependency order of banner windows when activating and deactivating * * The argument is optional. If it is the constant true then the currentContents * will be set to an empty string (''). If it is a string, then the currentContents * will be set to that string (ready to be displayed when the banner is reactivated). */ deactivate([args]) { removeBanner(); isActive = nil; if(args.length > 0) { local arg = args[1]; switch(dataType(arg)) { case TypeTrue: currentContents = ''; break; case TypeSString: currentContents = arg; break; } } } /* * Activate a currently inactive banner; this restores it to the screen. * The argument is optional; if present and true then activate(true) * displays the current contents of the banner window after activating it. * If the first argument is a string then the string is displayed in the banner. */ activate([args]) { if(isActive) return; isActive = true; initBannerWindow(); if(args.length() > 0 && args[1] != nil) { if(dataType(args[1]) == TypeSString) updateContents(args...); else showCurrentContents(); } } removeBanner() { /* * If I'm removed I can't be inited_ any more, and I'll need to be regarded * as not inited_ in the event of being redisplayed in the future. */ inited_ = nil; inherited; } /* * Set this flag to true to clear the contents of the window before displaying * the new contents, e.g. to display a new picture that replaces the old one. */ clearBeforeUpdate = true /* * Set this to true to have this banner size to contents each time its * contents are displayed. Note that not all interpreters support the size to * contents so you should still set an appropriate initial size, and, where * appropriate, call setSize() with the isAdvisory flag set. */ autoSize = nil /* * Update the contents of this banner window. This is the method to * call to change what a banner displays. * * The second argument is optional. If present it overrides the * setting of clearBeforeUpdate: updateContents(cont, true) will * clear the banner before the update, whereas updateContents(cont, nil) * will not, whatever the value of clearBeforeUpdate. */ updateContents(cont, [args]) { /* * Update our current contents. Note that this takes place even if * shouldDisplay is nil, so that if, for example, we are updated on * a text-only interpreter on which this banner is not displayed, * and the game is saved there and subsequently restored on a full HTML * interpreter in which we are displayed, the HTML interpreter will know * what contents it needs to display in us. */ currentContents = cont; showCurrentContents(args...); } /* Show the current contents of this banner window */ showCurrentContents([args]) { local clr; if(args.length > 0) clr = (args[1] != nil); else clr = clearBeforeUpdate; if(clr) clearWindow(); writeToBanner(currentContents); if(autoSize) sizeToContents(); } /* This is called on each CustomBannerWindow after a Restore. */ restoreBannerDisplay() { /* * It's possible a game was saved in a text-mode terp and * restored in an HTML one. In which case we need to initialize * this banner before attempting to display anything */ if(shouldDisplay && handle_ == nil) { if(!initBannerWindow()) return; } /* redisplay my contents after a restore */ showCurrentContents(); } /* * Alternatively we might have been saved in a terp that does * use this banner and restored in one that doesn't, in which * case we should remove ourselves. This is called on each BannerWindow * after a restore, but before bannerTracker.restoreDisplayState(). */ restoreRemove() { if(!shouldDisplay) removeBanner(); } /* show my initial contents on startup */ initBannerDisplay() { showCurrentContents(); } /* * We provide overrides for all the various banner manipulation methods * that game code might call, in order to make it safe to call them even * our shouldDisplay method returns nil and we don't - or shouldn't - exist. * For each of these xxxYyy methods we provide an altXxxyyy method that is * called when shouldDisplay is nil (e.g. because we're using a window to * display graphics on an interpreter that doesn't have graphics capablities). * By default these altXxxYyy methods do nothing, which in many cases will * be fine, but if you do want something else to happen you can override * the appropriate altXxxYyy method accordingly (e.g. to show a message in * the main game window instead of this banner). This should make it easier * to structure the rest of your game code without needing to worry about * what happens on interpreters which don't display your banners. */ clearWindow() { if(shouldDisplay) inherited(); else altClearWindow(); } altClearWindow() { } /* write to me, but only if I should display */ writeToBanner(txt) { if(shouldDisplay) inherited(txt); else altWriteToBanner(txt); } /* * altWriteToBanner(txt) is called when our game code tries to display * something in this banner, but our shouldDisplay method has ruled out * displaying this banner. In this case we might want to write something * to the main display instead. By default we do nothing here, but * individual instances and/or subclasses can override this method as * required. */ altWriteToBanner(txt) { } /* * We don't provide alternative methods for the setSize and sizeToContents * methods, since there would almost certainly be nothing for them to do. * We simply do nothing if shouldDisplay is nil. */ setSize(size, sizeUnits, isAdvisory) { if(shouldDisplay) inherited(size, sizeUnits, isAdvisory); } sizeToContents() { /* size our system-level window to our contents */ if(shouldDisplay) bannerSizeToContents(handle_); } captureOutput(func) { if(shouldDisplay) inherited(func); else altCaptureOutput(func); } /* Simply execute the callback without changing the output stream */ altCaptureOutput(func) { (func)(); } setOutputStream() { if(shouldDisplay) /* set my stream as the default */ return outputManager.setOutputStream(outputStream_); else return altSetOutputStream(); } /* * Our caller, or rather our caller's caller, will expect us to return * the current output stream, which means we must be sure to do this * whatever else we do. */ altSetOutputStream() { return outputManager.curOutputStream; } flushBanner() { if(shouldDisplay) inherited(); else altFlushBanner(); } altFlushBanner() { } setTextColor(fg, bg) { if(shouldDisplay) inherited(fg, bg); else altSetTextColor(fg, bg); } altSetTextColor(fg, bg) { } setScreenColor(color) { if(shouldDisplay) inherited(color); else altSetScreenColor(color); } altSetScreenColor(color) { } cursorTo(row, col) { if(shouldDisplay) inherited(row, col); else altCursorTo(row, col); } /* * If this banner isn't displaying we can't do anything directly comparable * to setting the cursot to a particular column and row in it, but we might * want to do something else instead, like inserting so many blank lines in * the main window. */ altCursorTo(row, col) { } ; /* * Initialize or reinitialize what all CustomBanners display at startup or * after an UNDO */ customBannerInit: InitObject, PostUndoObject execBeforeMe = [bannerInit] execute() { /* first ensure that all banner windows that need to exist do exist */ forEachInstance(CustomBannerWindow, new function(win) { if(win.shouldDisplay && win.handle_ == nil) win.initBannerWindow(); } ); /* then show the current contents of every active banner */ forEachInstance(CustomBannerWindow, {win: win.showCurrentContents() } ); } ; /* * Reinitialize what all the CustomBanners display on restoring. This requires * a different procedure since we can't be sure that we're being restored on * the same class of interpreter as we were saved on. */ customBannerRestore: PostRestoreObject execBeforeMe = [bannerTracker] execute() { /* * If we save in one terp, restore in the second terp, save in the second * terp, then restore in the first terp, when different rules apply about * displaying banners in the two terps, then windows removed in the second * terp could still be marked as inited_ in the restore file that comes * back to the first terp. To get round this, on restoration we ensure * that each CustomBanner's inited_ property in fact corresponds to whether * it has an active handle_, otherwise the attempt to reinitialize missing * banners might fail. */ forEachInstance(CustomBannerWindow, {win: win.inited_ = (win.handle_ != nil) } ); forEachInstance(CustomBannerWindow, {win: win.restoreBannerDisplay() } ); } ; customBannerRestoreRemove: PostRestoreObject execAfterMe = [bannerTracker] execute() { forEachInstance(CustomBannerWindow, {win: win.restoreRemove() } ); } ; /* * If we display a menu then we need to remove any active banners from the * screen before the menu displays and restore them to the screen on exiting * from the menu */ modify MenuItem display() { /* * First we store a list of all the banners that are currently * displaying */ local vec = new Vector(10); forEachInstance(CustomBannerWindow, new function(ban) { if(ban.shouldDisplay) vec.append(ban); } ); /* deactive all active banners */ foreach(local ban in vec) ban.deactivate(); try { /* carry out the inherited menu display */ inherited(); } /* * Restore all the banners in our list of banners that were previously * displayed. To ensure that they are activated in the right order * we make what may be several passes through the list. On each pass * we activate only those banners that don't depend on any inactive * banners for their activation. Each time we activate a banner, we * remove it from the list. On the next pass through the list any * banners that depended on banners we have just activated may now themselves * be activated, so we can carry on until every banner has been activated * and removed from the list. */ finally { while(vec.length()) { local bannerRemoved = nil; foreach(local ban in vec) { if(ban.bannerArgs[1] != nil && ban.bannerArgs[1].handle_ == nil) continue; if(ban.bannerArgs[3] != nil && ban.bannerArgs[3].handle_ == nil) continue; ban.activate(true); vec.removeElement(ban); bannerRemoved = true; } /* * If we didn't remove any banners on this pass through, we're * potentially in an infinite loop, so we'd better break out * of it. */ if(!bannerRemoved) break; } } } ;
Adv3Lite Library Reference Manual
Generated on 26/02/2025 from adv3Lite version 2.2
Generated on 26/02/2025 from adv3Lite version 2.2