Banner Windows

The “tadsio” function set provides a group of functions that allow the program to perform sophisticated manipulation of the display layout.  This group of functions, called the Banner Window API, lets the program divide the screen into separate “window” areas that can show independent information.

 

The banner window API provides much of the same functionality that was available in TADS 2’s full HTML interpreters using the <BANNER> tag.  However, the banner window API goes beyond the <BANNER> tag from TADS 2 in several important ways:

 

 

Note that the banner API replaces the <BANNER> tag.  The <BANNER> tag is not supported in TADS 3; interpreters will ignore it.

Screen Layout Overview

Banners work by splitting one window into two.  In other words, we take an existing window and draw an imaginary line either across the width of the window, or down the height of the window; the existing window keeps the space on one side of the line, and the new banner window gets the space on the other side of the line.  The existing window shrinks in the process, since some of its space is taken over for the new banner window.

 

The original window that we split in two is called the “parent” window.  The parent can be the main game window (the original window that the interpreter automatically creates at start-up, and which contains the main input/output transcript of the game), or it can be another banner window.  We sometimes refer to the new banner window as a “child” of the parent window.

 

When the program creates a banner window, it specifies an “alignment” and a size for the new window.  The alignment determines how we draw the line that splits the parent window, and which side of the line contains the banner window and which contains the shrunken parent window.  There are four types of alignment: Top, Bottom, Left, and Right.  For Top and Bottom alignment, we split the window with a line across the width of the window; for Top alignment, the new banner window gets the space above the line, and the for Bottom alignment, the new banner takes the space below the line.  Left and Right alignment split the window with a vertical line; a Left banner takes the portion to the left of the line, and a Right banner takes the portion to the right of the line.  The size determines where the dividing line goes: the dividing line is placed such that the banner’s height (for a Top or Bottom banner) or width (for a Left or Right banner) matches the size parameter.

 

To determine where each banner goes, the interpreter does the following.  We start with the main window, giving it the entire available space (this might be the entire screen on a character-mode terminal, or the entire application window on a graphical system).  We then visit each child of the main window.  For each child, we look at the child’s alignment and size to determine where to draw the imaginary dividing line; then we give the banner window the appropriate portion, and shrink the main game window to take its portion of the space.  Then, we visit all of the child’s children, and do the same thing, dividing and shrinking the child.  We repeat the process on the child’s children’s children, and so on until we run out of descendants.

 

Let’s look at an example.  Initially, we start out with just the main game window, which takes the entire available space:

 

 

 

Main

 

Now, let’s add a banner.  Text games traditionally display a status line at the top of the screen, with one or two lines of text; so, let’s create a banner for the status line.  For now, we’ll make it one “text unit” high, which is roughly a line of text.  So, we’re creating a banner whose parent is the main window, with Top alignment and a size of 1.

 

Status

 

Main

 

Now let’s suppose that we want to add a more complex display, with an area below the status line that displays a picture of the current location.  We want another top-aligned child of the main window, because we want it to run the width of the main window and be above the main window.  We’ll make its size about ten lines for now.

 

Status

Picture

 

Main

 

This raises a question: why is the Picture window below the Status window?  And what if we actually wanted it above the Status window?  The answer is in the ordering of the windows: the Status window comes first in the main window’s child list, so it gets split off first, which means it gets the top of the entire display area.  The Picture window is split off second, which means that it has to take what’s left of the main window after the Status window has already been carved out.  If we wanted the Picture window to be above the Status window, we’d simply have to specify that it comes first.  Note that this doesn’t mean we have to create it first; when we create a new banner, we can specify where the new banner goes among the other children of its parent, so if we had wanted the Picture window to be on top, we could simply have specified that it goes first in the main window’s child list.

 

Now we want to add another window: we want to add a window alongside the picture window, showing a compass graphic in the new window.  We don’t want to create another “band” across the entire width of the main window; rather, we want to share some of the space the Picture window already has.  To do this, we can create a child of the Picture window, and align it to the right.

 

Status

Picture

C

 

Main

 

The Compass (“C”) window splits the area that the Picture window had, taking the right portion because it’s a right-aligned banner.

Borders

A banner can optionally display a “border” to separate it from its parent.  The border essentially makes the imaginary dividing line between the banner and its parent visible on the screen.  Borders are controlled via the “style” flag when creating a banner.

 

A banner’s border is always exactly one line: it’s always the dividing line between the banner and its parent.  In the example above with the Picture and Status banners, if the Picture banner had a border, it would appear between the Picture and Main windows, because the Picture window was split from the Main window.  To draw a line between the Status and Picture windows, you would have to give the Status banner a border.

 

A banner’s children have no effect on the banner’s border.  If a banner is drawn with a border, the border will appear exactly the same way whether the banner has children or not; so, to figure out where a border would be drawn, consider what the window would look like without any children of its own.  In the example above with the Picture and Compass windows, if the Picture window had a border, this border would be drawn across the full width of the window, under both the Picture and Compass (“C”) windows.  So, there would be a border between the Compass window and the Main window whether or not the Compass window itself had a border.  (Furthermore, if the Compass window did have a border, it would be drawn as a vertical line between the Compass and Picture windows, because the Picture window is the Compass window’s parent.)

 

Some systems do not support borders at all; in fact, most character-mode platforms do not, because there’s usually no good way to draw lines in character mode, and even if there were, it would take up too much space.  You can tell whether or not the current system supports border drawing by checking the style flag returned from bannerGetInfo(); on systems that don’t support borders, you might want to use some other means to visually separate a banner from its parent, such as using different background (“screen”) colors in each window.

More Details on Layout

TADS interpreters display everything on what we call the “screen,” by which we mean the physical display area devoted to the interpreter.  The screen isn’t necessarily the same as the physical display device (the user’s monitor, or the terminal, for example), although on some systems it is.  The most typical examples are:

 

 

When a TADS interpreter first loads a new game program, it devotes the entire “screen” to one window, which we call the “main game window.”  This main window acts a lot like a banner window, but it has some differences, so we use the special designation “main game window” to refer to this window.

 

At any time while the game program is running, it can create new “banner windows.”  A banner window is a rectangular area of the screen that can be separately controlled: each banner has its own contents, background color, text color, and so on.

 

All of the banner windows that are shown at any given time share space on the screen with one another and with the main game window.  Two windows can never overlap, and all of the windows taken together must fill the entire screen; in other words, the windows act as non-overlapping “tiles” that completely fill the available screen space.  These are important constraints that determine precisely how the screen looks with a given arrangement of windows, and make it relatively simple to specify the screen layout; the disadvantage of these constraints is that they don’t allow the game program to create any arbitrary display, but the compensating advantage is that the constraints are simple to apply across a wide range of platforms, so the program can count on consistent results without having to include special-case code for different machines and display types.

 

Each banner window has three main parameters that control its layout on the screen.  First, each banner has a “parent”: this is either the main game window or another banner window, and provides the base area from which the new banner’s area is taken.  Second, each banner has an alignment type, which can be Top, Bottom, Left, or Right.  The alignment specifies which portion of the parent window’s space is taken for the banner.  A top-aligned banner takes the top portion of the parent’s space, and runs the full width of the parent window; a bottom-aligned banner also runs the entire width of the parent, but takes the bottom portion of its space.  Left and right banners run the entire height of the parent window, and take the left and right side, respectively, of the parent’s space.  Third, each banner has a size, which gives the size of the “free” dimension, as determined by the alignment: a top or bottom banner’s size specifies its height, and the left or right banner’s size specifies its width.

 

Each parent window can have one or more children.  When a window has more than one child, the children are arranged into an ordered list: one banner is the first child, another is the second, and so on.  The order is important because each child is carved out of its parent’s remaining space; we start with the first child, taking its space away from the parent’s space, then move to the second child, taking its space away from what remains of the parent’s space, and so on.  When the game program creates a banner, it can specify the position of the new banner among the parent’s children.

 

The screen layout is dynamic: each time a new banner window is created, or a banner window is removed, or a banner is resized, or the screen size changes (which can happen, for example, when the user resizes the “xterm” window displaying the interpreter), TADS must recalculate the screen layout.  Every time any of these events occurs, TADS completely recalculates the screen layout; this means that the layout is always predictable, because it’s always calculated the same way, regardless of what caused a change.

 

To calculate the screen layout, the interpreter first gives the main game window the entire screen area.  The interpreter then visits each child of the main game window, in the order that the program specified when creating the child windows.  For each child window, we do the following:

 

  1. We look at the current child window’s alignment and size settings, and figure out where to draw the imaginary line dividing the parent window.  If the child’s size setting is greater than the available space in the parent window, we limit the child’s size to that of the parent window; in other words, the child gets all of the parent’s space (but no more), and the parent will get nothing.
  2. We assign the space on one side of the dividing line (according to the alignment) to the current child.
  3. We assign the space on the other side of the line to the parent, shrinking the parent to its new size.
  4. For each child of the current child window, we recursively apply this same procedure to subdivide the current child’s space among its own children.

 

There are a couple of interesting features of this layout algorithm worth pointing out.

 

First, when a banner is deleted, all of its children become immediately invisible.  This is because a banner gets its space exclusively from its parent; once a banner is deleted, its children no longer have any way to obtain any space on the display.  It won’t even help to resize the children: without a parent, a child simply has no way to obtain any display space.  The children of a deleted banner remain valid as abstract objects, so you can still call banner API functions that reference their banner handles, but they can never again have a display presence.

 

Second, a banner’s final size is not necessarily the same as its size parameter setting.  Suppose we have a top-aligned banner (call it “A”), a child of the main game window with a size of 50%.  Suppose also that this window has a child of its own (call this “B”): a bottom-aligned banner which also has a size of 50%.  The layout algorithm will start by giving banner A the top half of the entire screen; but when we lay out its child B, we take away half of that height to give to B.  In the end, A only gets 25% of the entire screen.  (This probably won’t come up a lot in practice, since it would be unusual to create a child with the same orientation as its parent.)

 

Third, a child window can never cause its parent to expand.  If a child’s size is greater than the available space in its parent, the child simply gets the entire available space in the parent – but no more – and the parent is shrunken down to zero size.

 

Fourth, although a child causes its parent to shrink as the child carves out space from the parent, this effect never flows “up the tree”: a child can never affect its grandparent or any siblings of its parent.  This is an important but subtle point, because it makes the layout rules simple and predictable; there is no “negotiation” for space, and no possibility of irresolvable conflicts, as can sometimes occur with constraint-based layout systems.

Banner Types

When the game creates a banner window, it can specify the type of window to use.  The following types are defined:

 

 

Note that text grids are mostly provided for character-mode platforms, but they’re also supported on the full HTML interpreters for compatibility.  In most cases, the kinds of effects for which text grids are most useful can be presented more elegantly on the full HTML interpreters by other means; for example, a “menu” window that uses cursor positioning to move around a selection pointer in response to keyboard inputs can often be better presented in HTML interpreters using a list of hyperlinks.  Whenever you're about to use a text grid, you should at least consider whether there might be another alternative that’s a better fit for GUI platforms.

Sizing Banners

The banner API provides three different ways of setting the size of a banner.  Which sizing method is best for a particular banner depends on how the banner is used.

 

Note that when we talk about the “size” of a banner, we’re talking about the free dimension of the banner, because the other dimension is always bound by the layout rules and thus can’t be controlled independently.  For a top-aligned or bottom-aligned banner, the size is the height; for a left- or right-aligned banner, the size is the width.

 

Percentage sizing:  The first method is to size a banner in proportion to the available parent area, by specifying the size as a percentage when creating the banner (with bannerCreate()) or changing its size (with bannerSetSize()).  The size of the banner will always be the given percentage of the available parent space; see the Screen Layout Overview for details of how this is determined.  When you set a percentage size, the banner specifically remembers the percentage value, not the pixel size that the percentage translates into at any given time; whenever the layout changes (because the user resizes the application window, for example, or because another banner is created), the banner automatically adjusts to the new layout using the same percentage.

 

Absolute sizing:  The second method of sizing a banner is to state the size in terms of the “natural units” of the banner window.  The natural units depend on the type of banner:

 

 

Contents-based sizing:  The third method is to size the banner so that its current contents exactly fit.  In many cases, it’s desirable for a banner to be exactly big enough to show some specific contents; this is the case when a banner is used to display something like a status line or an interactive menu.  Even absolute sizing is inherently imprecise for regular text windows, because the natural sizing units are character cells that might not represent the actual mix of characters and fonts displayed.  To accommodate the need for exact content-based sizing, the banner API provides a function, bannerSizeToContents(), that sets the size of a banner window so that the actual current contents exactly fit.

 

Unfortunately, contents-based sizing is not available on all platforms, because some platforms are not capable of supporting it.  It is always safe to call bannerSizeToContents(), but on platforms where contents-based sizing isn’t supported, the function will simply do nothing.  Because of this, programs must use the following procedure when contents-based sizing is desired:

 

  1. Calculate an estimate of the required size, in the window’s natural units.  For text windows, this is usually just a matter of determining how many lines of text will be displayed; it’s not possible to take into account any line wrapping that might occur, so if line wrapping is probable, it might be desirable to pad the estimate accordingly.
  2. Call bannerSetSize() to set the banner size to the estimated size.  Pass true for the isAdvisory flag, to indicate that an exact contents-based size will be set later.  Platforms that support contents-based sizing will ignore this call entirely, because of the isAdvisory flag, so there will be no unnecessary redrawing from an extra resize.
  3. Display the contents.
  4. Call bannerSizeToContents() to set the exact contents-based size, if possible.  For platforms where this is not implemented, it will have no effect, so the estimated size will remain in effect; where contents-based sizing is available, the estimated size will be discarded and the exact size set instead.

 

By using this procedure, a program can ensure that it will look reasonably good on all platforms, and that it will look exactly as desired on those platforms that do support size-to-contents (as most platforms do).

Banner Functions

The banner API is part of the “tads-io” function set, which provides programmatic control over the TADS user interface.  The banner functions all have names of the form bannerXxx.

 

bannerClear(handle) – clears the banner’s display.  Removes all text from the banner, and moves the output position back to the upper left corner of the banner’s window.

 

bannerCreate(parent, where, other, windowType, align, size, sizeUnits, style) – creates a new banner window with the given parameters.

 

parent is the handle of an existing banner that is to serve as the parent of the new banner, or nil if the new banner is to be a child of the main game window.  The new banner’s space is obtained by splitting the parent window.

 

where and other together indicate where the banner goes in the parent’s list of children, which determines how the new banner is laid out relative to the other children of the same parent (see “Screen Layout Overview”).  where can be one of the following values:

 

 

Note that the child list order specified via where and other is not permanent; it merely determines where the new banner goes in the current child list of the given parent.  For example, specifying BannerFirst does not mean that the banner will remain the first child forever; it merely puts it at the start of the current list.  If banner A is created with BannerFirst specified, and later banner B is created with BannerFirst, banner A will become the second child after B.

 

If BannerBefore or BannerAfter is specified, and other is not a valid banner handle or is not a child of the given parent, then the system ignores where and other and inserts the banner as the last child of the parent, as though BannerLast had been specified.

 

The windowType parameter indicates the type of banner to create; this is one of the following (see “Banner Types” for more information):

 

 

The align setting indicates how the banner’s space is carved out of its parent’s space.  This can have one of the following values:

 

 

The size parameter gives the initial size of the banner, the meaning of which depends on sizeUnits:

 

 

style is a combination of flag values specifying the desired behavior for the banner.  Some of the style flags directly indicate particular aspects of the on-screen appearance of the banner; other styles are advisory, giving the interpreter some hints about how you’re planning to use the banner, so that the interpreter can select appearance or behavior variations that are appropriate to the current platform.  Not all interpreters support all styles, so you have to think of the style flags as hints to the interpreter about the desired appearance, rather than a specification of the actual appearance.  After you create the banner, you can use bannerGetInfo() to retrieve the actual style flags, which will give you some indication of how the interpreter treated your request.

 

The style flags are:

 

 

This function returns a handle to the new banner, or nil if an error occurs creating the banner.  The banner handle can be used to operate on the banner in other bannerXxx() functions.

 

bannerDelete(handle) – delete the given banner.  This removes the banner from the display, and recalculates the layout for all of the other banners remaining on the screen.  After this function is called, the banner handle becomes invalid and must not be used for anything else.

 

Note that any children of the banner being deleted will immediately become invisible.  They will remain valid, so you can continue to pass their handles to banner functions, but they will not have any display presence.  A banner always obtains its display space by splitting its parent, so once the parent is gone, a child has no way of obtaining any screen space of its own and thus becomes invisible.

 

bannerFlush(handle) – flushes the text output buffer for the given banner, immediately updating the display with any pending text.

 

bannerGetInfo(banner) – retrieves information on the banner.  This function returns a list of values, as follows:

 

 

bannerGoTo(handle, row, col) – move the output position in the given text grid banner to the given row and column.  Rows and columns are numbered from 1 at the upper left corner.  This function can be used only in text grid windows; in other types of windows, it has no effect.

 

bannerSay(handle, …) – writes one or more text items to the banner.  This function treats the parameters following handle the same way that tadsSay() does.

 

bannerSetScreenColor(handle, color) – set the background color in the banner.  This immediately changes the entire window’s background to the given color (in other words, this doesn’t merely affect subsequent text, but also affects everything already displayed in the banner).  The color values are the same as for bannerSetTextColor(), except that ColorTransparent is not meaningful here.  This function can't be used in ordinary text windows (BannerTypeText); use the HTML <BODY BGCOLOR> tag instead.

 

bannerSetSize(handle, size, sizeUnits, isAdvisory) – set the size of the banner.  The size and sizeUnits parameters have the same meanings they do in bannerCreate().  If isAdvisory is true, it indicates that the size setting is only an estimate, and that a call to bannerSizeToContents() will be made later; in this case, the interpreter might simply ignore this estimated size setting entirely, to avoid unnecessary redrawing.  Platforms that do not support contents-based sizing will always set the estimated size, even when isAdvisory is true.  If isAdvisory is nil, the platform will set the banner size as requested; set isAdvisory to nil when you will not follow up with a call to bannerSizeToContents().

 

bannerSetTextColor(handle, fg, bg) – set the text color in the given banner to the given foreground and background colors.  The new color settings are used for text subsequently displayed; any text already displayed is not affected.  This can't be used in ordinary text windows (BannerTypeText); use the HTML <FONT COLOR> tag instead. 

 

The foreground and background colors can have the following values:

 

 

In addition, the special value ColorTransparent can be used for the background color.  This indicates that the text should be drawn with a transparent background, and thus should simply be drawn against the banner’s current background color.

 

bannerSizeToContents(handle) – resizes the given banner based on the current contents of the banner.  For a top-aligned or bottom-aligned banner, this sets the banner’s height so that the banner is just tall enough to show all of the contents as currently laid out.  For a left-aligned or right-aligned banner, this sets the banner’s width so that the banner is just wide enough to hold the banner’s single widest indivisible element (such as a single word or a picture).  This routine can be used to set the banner’s size based on the actual size of the contents; it’s impossible to know the exact size of a banner’s contents until you actually display the contents, because the sizes of fonts and other display elements vary from one machine to another, and can even change on the same machine in response to user preference settings and other factors.

 

Note that this routine might not be implemented on all platforms; on platforms where it’s not implemented, this routine will simply do nothing at all.  Importantly, it’s legal to call the routine on all platforms (there will be no error or other adverse effect where the routine isn’t implemented), but programs cannot rely on this routine being available.  To ensure a reasonable size is always set regardless of platform, callers should always use bannerSetSize() to set an approximate size, assign true for the isAdvisory flag, and then also call bannerSizeToContents() to set the exact content-based size, if appropriate.