summaryrefslogtreecommitdiff
path: root/src/z-term.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/z-term.cc')
-rw-r--r--src/z-term.cc1911
1 files changed, 1911 insertions, 0 deletions
diff --git a/src/z-term.cc b/src/z-term.cc
new file mode 100644
index 00000000..cc7ee4d6
--- /dev/null
+++ b/src/z-term.cc
@@ -0,0 +1,1911 @@
+/*
+ * Copyright (c) 1997 Ben Harrison
+ *
+ * This software may be copied and distributed for educational, research,
+ * and not for profit purposes provided that this copyright and statement
+ * are included in all such copies.
+ */
+
+/* Purpose: a generic, efficient, terminal window package -BEN- */
+
+#include "z-term.hpp"
+
+#include "key_queue.hpp"
+#include "frontend.hpp"
+#include "tome/unique_handle.hpp"
+
+#include <cassert>
+#include <memory>
+#include <optional>
+#include <vector>
+
+
+/*
+ * This file provides a generic, efficient, terminal window package,
+ * which can be used not only on standard terminal environments such
+ * as dumb terminals connected to a Unix box, but also in more modern
+ * "graphic" environments, such as the Macintosh or Unix/X11.
+ *
+ * Each "window" works like a standard "dumb terminal", that is, it
+ * can display a two dimensional array of grids containing colored
+ * textual symbols, plus an optional cursor, and it can be used to
+ * get keypress events from the user.
+ *
+ * In fact, this package can simply be used, if desired, to support
+ * programs which will look the same on a dumb terminal as they do
+ * on a graphic platform such as the Macintosh.
+ *
+ * This package was designed to help port the game "Angband" to a wide
+ * variety of different platforms. Angband, like many other games in
+ * the "rogue-like" heirarchy, requires, at the minimum, the ability
+ * to display "colored textual symbols" in a standard 80x24 "window",
+ * such as that provided by most dumb terminals, and many old personal
+ * computers, and to check for "keypresses" from the user. The major
+ * concerns were thus portability and efficiency, so Angband could be
+ * easily ported to many different systems, with minimal effort, and
+ * yet would run quickly on each of these systems, no matter what kind
+ * of underlying hardware/software support was being used.
+ *
+ * It is important to understand the differences between the older
+ * "dumb terminals" and the newer "graphic interface" machines, since
+ * this package was designed to work with both types of systems.
+ *
+ * New machines:
+ * waiting for a keypress is complex
+ * checking for a keypress is often cheap
+ * changing "colors" may be expensive
+ * the "color" of a "blank" is rarely important
+ * moving the "cursor" is relatively cheap
+ * use a "software" cursor (only moves when requested)
+ * drawing characters normally will not erase old ones
+ * drawing a character on the cursor often erases it
+ * may have fast routines for "clear a region"
+ * the bottom right corner is usually not special
+ *
+ * Old machines:
+ * waiting for a keypress is simple
+ * checking for a keypress is often expensive
+ * changing "colors" is usually cheap
+ * the "color" of a "blank" may be important
+ * moving the "cursor" may be expensive
+ * use a "hardware" cursor (moves during screen updates)
+ * drawing new symbols automatically erases old ones
+ * characters may only be drawn at the cursor location
+ * drawing a character on the cursor will move the cursor
+ * may have fast routines for "clear entire window"
+ * may have fast routines for "clear to end of line"
+ * the bottom right corner is often dangerous
+ *
+ *
+ * This package provides support for multiple windows, each of an
+ * arbitrary size (up to 255x255), each with its own set of flags,
+ * and its own hooks to handle several low-level procedures which
+ * differ from platform to platform. Then the main program simply
+ * creates one or more "term" structures, setting the various flags
+ * and hooks in a manner appropriate for the current platform, and
+ * then it can use the various "term" structures without worrying
+ * about the underlying platform.
+ *
+ *
+ * This package allows each "grid" in each window to hold an attr/char
+ * pair, with each ranging from 0 to 255, and makes very few assumptions
+ * about the meaning of any attr/char values. We assume that "attr 0" is
+ * "black", with the semantics that "black" text should be
+ * sent to "Term_wipe()" instead of "Term_text()".
+ *
+ * Finally, we use a special attr/char pair, defaulting to "attr 0" and
+ * "char 32", also known as "black space", when we "erase" or "clear"
+ * any window, but this pair can be redefined to any pair, including
+ * the standard "white space", or the bizarre "emptiness" ("attr 0"
+ * and "char 0"), as long as various obscure restrictions are met.
+ *
+ *
+ * This package provides several functions which allow a program to
+ * interact with the "term" structures. Most of the functions allow
+ * the program to "request" certain changes to the current "term",
+ * such as moving the cursor, drawing an attr/char pair, erasing a
+ * region of grids, hiding the cursor, etc. Then there is a special
+ * function which causes all of the "pending" requests to be performed
+ * in an efficient manner. There is another set of functions which
+ * allow the program to query the "requested state" of the current
+ * "term", such as asking for the cursor location, or what attr/char
+ * is at a given location, etc. There is another set of functions
+ * dealing with "keypress" events, which allows the program to ask if
+ * the user has pressed any keys, or to forget any keys the user pressed.
+ * There is a pair of functions to allow this package to memorize the
+ * contents of the current "term", and to restore these contents at
+ * a later time. There is a special function which allows the program
+ * to specify which "term" structure should be the "current" one. At
+ * the lowest level, there is a set of functions which allow a new
+ * "term" to be initialized or destroyed, and which allow this package,
+ * or a program, to access the special "hooks" defined for the current
+ * "term", and a set of functions which those "hooks" can use to inform
+ * this package of the results of certain occurances, for example, one
+ * such function allows this package to learn about user keypresses,
+ * detected by one of the special "hooks".
+ *
+ * We provide, among other things, the functions "Term_keypress()"
+ * to "react" to keypress events, and "Term_redraw()" to redraw the
+ * entire window, plus "Term_resize()" to note a new size.
+ *
+ *
+ * Note that the current "term" contains two "window images". One of
+ * these images represents the "requested" contents of the "term", and
+ * the other represents the "actual" contents of the "term", at the time
+ * of the last performance of pending requests. This package uses these
+ * two images to determine the "minimal" amount of work needed to make
+ * the "actual" contents of the "term" match the "requested" contents of
+ * the "term". This method is not perfect, but it often reduces the
+ * amount of work needed to perform the pending requests, which thus
+ * increases the speed of the program itself. This package promises
+ * that the requested changes will appear to occur either "all at once"
+ * or in a "top to bottom" order. In addition, a "cursor" is maintained,
+ * and this cursor is updated along with the actual window contents.
+ *
+ * Currently, the "Term_fresh()" routine attempts to perform the "minimum"
+ * number of physical updates, in terms of total "work" done by the hooks
+ * Term_wipe(), Term_text(), and Term_pict(), making use of the fact that
+ * adjacent characters of the same color can both be drawn together using
+ * the "Term_text()" hook, and that "black" text can often be sent to the
+ * "Term_wipe()" hook instead of the "Term_text()" hook, and if something
+ * is already displayed in a window, then it is not necessary to display
+ * it again. Unfortunately, this may induce slightly non-optimal results
+ * in some cases, in particular, those in which, say, a string of ten
+ * characters needs to be written, but the fifth character has already
+ * been displayed. Currently, this will cause the "Term_text()" routine
+ * to be called once for each half of the string, instead of once for the
+ * whole string, which, on some machines, may be non-optimal behavior.
+ *
+ * The new formalism includes a "displayed" screen image (old) which
+ * is actually seen by the user, a "requested" screen image (scr)
+ * which is being prepared for display, a "memorized" screen image
+ * (mem) which is used to save and restore screen images.
+ *
+ *
+ * Several "flags" are available in each "term" to allow the underlying
+ * visual system (which initializes the "term" structure) to "optimize"
+ * the performance of this package for the given system, or to request
+ * certain behavior which is helpful/required for the given system.
+ *
+ * The "soft_cursor" flag indicates the use of a "soft" cursor, which
+ * only moves when explicitly requested,and which is "erased" when
+ * any characters are drawn on top of it. This flag is used for all
+ * "graphic" systems which handle the cursor by "drawing" it.
+ *
+ * The "icky_corner" flag indicates that the bottom right "corner"
+ * of the windows are "icky", and "printing" anything there may
+ * induce "messy" behavior, such as "scrolling". This flag is used
+ * for most old "dumb terminal" systems.
+ *
+ *
+ * The "term" structure contains the following function "hooks":
+ *
+ * Term->init_hook = Init the term
+ * Term->nuke_hook = Nuke the term
+ * Term->xtra_hook = Perform extra actions
+ * Term->curs_hook = Draw (or Move) the cursor
+ * Term->text_hook = Draw some text in the window
+ *
+ * The "Term->xtra_hook" hook provides a variety of different functions,
+ * based on the first parameter (which should be taken from the various
+ * TERM_XTRA_* defines) and the second parameter (which may make sense
+ * only for some first parameters). It is available to the program via
+ * the "Term_xtra()" function, though some first parameters are only
+ * "legal" when called from inside this package.
+ *
+ * The "Term->curs_hook" hook provides this package with a simple way
+ * to "move" or "draw" the cursor to the grid "x,y", depending on the
+ * setting of the "soft_cursor" flag. Note that the cursor is never
+ * redrawn if "nothing" has happened to the screen (even temporarily).
+ * This hook is required.
+ *
+ * The "Term->text_hook" hook provides this package with a simple way
+ * to "draw", starting at "x,y", the "n" chars contained in "cp", using
+ * the attr "a". This hook assumes that the input is valid, and that
+ * "n" is between 1 and 256 inclusive, but it should NOT assume that
+ * the contents of "cp" are null-terminated.
+ *
+ * The game "Angband" uses a set of files called "main-xxx.c", for
+ * various "xxx" suffixes. Most of these contain a function called
+ * "init_xxx()", that will prepare the underlying visual system for
+ * use with Angband, and then create one or more "term" structures,
+ * using flags and hooks appropriate to the given platform, so that
+ * the "main()" function can call one (or more) of the "init_xxx()"
+ * functions, as appropriate, to prepare the required "term" structs
+ * (one for each desired sub-window), and these "init_xxx()" functions
+ * are called from a centralized "main()" function in "main.c". Other
+ * "main-xxx.c" systems contain their own "main()" function which, in
+ * addition to doing everything needed to initialize the actual program,
+ * also does everything that the normal "init_xxx()" functions would do.
+ *
+ * The game "Angband" defines, in addition to "attr 0", all of the
+ * attr codes from 1 to 15, using definitions in "defines.h", and
+ * thus the "main-xxx.c" files used by Angband must handle these
+ * attr values correctly. Also, they must handle all other attr
+ * values, though they may do so in any way they wish, for example,
+ * by always taking every attr code mod 16. Many of the "main-xxx.c"
+ * files use "white space" ("attr 1" / "char 32") to "erase" or "clear"
+ * any window, for efficiency.
+ *
+ * See "main-xxx.c" for a simple skeleton file which can be used to
+ * create a "visual system" for a new platform when porting Angband.
+ */
+
+
+
+/*
+ * A term_win is a "window" for a Term
+ *
+ * - Cursor Useless/Visible codes
+ * - Cursor Location (see "Useless")
+ *
+ * - Array[h] -- Access to the attribute array
+ * - Array[h] -- Access to the character array
+ *
+ * - Array[h*w] -- Attribute array
+ * - Array[h*w] -- Character array
+ *
+ * Note that the attr/char pair at (x,y) is a[y][x]/c[y][x]
+ * and that the row of attr/chars at (0,y) is a[y]/c[y]
+ */
+
+struct term_win final
+{
+private:
+ std::vector<byte> va;
+ std::vector<char> vc;
+
+public:
+
+ bool cu = true;
+ bool cv = false;
+ byte cx = 0;
+ byte cy = 0;
+
+ std::vector<byte *> a;
+ std::vector<char *> c;
+
+ /**
+ * Ctor
+ */
+ explicit term_win(int w, int h)
+ : va(h * w)
+ , vc(h * w)
+ , a(h)
+ , c(h)
+ {
+ // The idea here is that our va and vc vectors are just
+ // a single contiguous block of memory and that each "row"
+ // of a and c point into the correct location in that single
+ // block of memory.
+ for (int y = 0; y < h; y++)
+ {
+ a[y] = va.data() + w * y;
+ c[y] = vc.data() + w * y;
+ }
+ }
+
+ /*
+ * Copy contents of a "term_win" up to the given dimensions.
+ */
+ void copy_from(term_win const *f, int w, int h)
+ {
+ /* Copy contents */
+ for (int y = 0; y < h; y++)
+ {
+ byte *f_aa = f->a[y];
+ char *f_cc = f->c[y];
+
+ byte *s_aa = a[y];
+ char *s_cc = c[y];
+
+ for (int x = 0; x < w; x++)
+ {
+ *s_aa++ = *f_aa++;
+ *s_cc++ = *f_cc++;
+ }
+ }
+
+ /* Copy cursor */
+ cx = f->cx;
+ cy = f->cy;
+ cu = f->cu;
+ cv = f->cv;
+ }
+
+ /**
+ * Copy contents of a "term_win" up to the given dimensions.
+ */
+ void copy_from(std::unique_ptr<term_win> const &p, int w, int h)
+ {
+ copy_from(p.get(), w, h);
+ }
+
+ /**
+ * Force cursor to be visible.
+ */
+ void set_cursor_visible()
+ {
+ cu = false;
+ cv = true;
+ }
+
+ /**
+ * Dtor
+ */
+ ~term_win() = default;
+
+};
+
+
+
+static errr push_result_to_errr(key_queue::push_result_t r)
+{
+ using pr = key_queue::push_result_t;
+
+ switch (r)
+ {
+ case pr::OK:
+ return 0;
+ case pr::OVERFLOW:
+ return 1;
+ }
+
+ return -1;
+}
+
+
+
+/*
+ * An actual "term" structure
+ *
+ * - Extra "data" info (used by implementation)
+ *
+ *
+ * - Flag "active_flag"
+ * This "term" is "active"
+ *
+ * - Flag "mapped_flag"
+ * This "term" is "mapped"
+ *
+ * - Flag "total_erase"
+ * This "term" should be fully erased
+ *
+ * - Flag "icky_corner"
+ * This "term" has an "icky" corner grid
+ *
+ * - Flag "soft_cursor"
+ * This "term" uses a "software" cursor
+ *
+ *
+ *
+ *
+ * - Ignore this pointer
+ *
+ * - Keypress Queue -- various data
+ *
+ * - Keypress Queue -- pending keys
+ *
+ *
+ * - Window Width (max 255)
+ * - Window Height (max 255)
+ *
+ * - Minimum modified row
+ * - Maximum modified row
+ *
+ * - Minimum modified column (per row)
+ * - Maximum modified column (per row)
+ *
+ *
+ * - Displayed screen image
+ * - Requested screen image
+ *
+ * - Temporary screen image
+ * - Memorized screen image
+ *
+ *
+ * - Hook for init-ing the term
+ * - Hook for nuke-ing the term
+ *
+ * - Hook for extra actions
+ *
+ * - Hook for placing the cursor
+ *
+ * - Hook for drawing a string of chars using an attr
+ *
+ * - Hook for drawing a sequence of special attr/char pairs
+ */
+
+struct term
+{
+ bool active_flag = false;
+ bool mapped_flag = false;
+ bool total_erase = true;
+
+ key_queue m_key_queue;
+
+ byte wid;
+ byte hgt;
+
+ byte y1;
+ byte y2;
+
+ std::vector<byte> x1;
+ std::vector<byte> x2;
+
+ std::unique_ptr<term_win> old;
+ std::unique_ptr<term_win> scr;
+ std::unique_ptr<term_win> mem;
+
+ std::shared_ptr<Frontend> m_frontend;
+ std::function<void ()> m_resize_hook;
+
+ bool icky_corner;
+ bool soft_cursor;
+
+ /**
+ * Ctor
+ */
+ term(int w, int h, int k, std::shared_ptr<Frontend> user_interface)
+ : m_key_queue(k)
+ , wid(w)
+ , hgt(h)
+ , x1(h)
+ , x2(h)
+ , m_frontend(std::move(user_interface))
+ , icky_corner(m_frontend->icky_corner())
+ , soft_cursor(m_frontend->soft_cursor())
+ {
+ /* Allocate "displayed" */
+ old = std::make_unique<term_win>(w, h);
+
+ /* Allocate "requested" */
+ scr = std::make_unique<term_win>(w, h);
+
+ /* Assume change */
+ for (int y = 0; y < h; y++)
+ {
+ /* Assume change */
+ x1[y] = 0;
+ x2[y] = w - 1;
+ }
+
+ /* Assume change */
+ y1 = 0;
+ y2 = h - 1;
+ }
+
+ /**
+ * Dtor
+ */
+ ~term()
+ {
+ /* Hack -- Call the special "nuke" hook */
+ if (active_flag)
+ {
+ /* Call the "nuke" hook */
+ m_frontend->nuke();
+
+ /* Remember */
+ active_flag = false;
+
+ /* Assume not mapped */
+ mapped_flag = false;
+ }
+ }
+
+ void text(int x, int y, int n, byte a, const char *s)
+ {
+ m_frontend->draw_text(x, y, n, a, s);
+ }
+
+ void curs(int x, int y)
+ {
+ m_frontend->draw_cursor(x, y);
+ }
+
+ void fresh()
+ {
+ m_frontend->flush_output();
+ }
+
+};
+
+
+
+/*
+ * The current "term"
+ */
+term *Term = nullptr;
+
+
+/*
+ * Mentally draw an attr/char at a given location
+ *
+ * Assumes given location and values are valid.
+ */
+void Term_queue_char(int x, int y, byte a, char c)
+{
+ auto const &scrn = Term->scr;
+
+ byte *scr_aa = &scrn->a[y][x];
+ char *scr_cc = &scrn->c[y][x];
+
+ /* Hack -- Ignore non-changes */
+ if ((*scr_aa == a) && (*scr_cc == c)) return;
+
+ /* Save the "literal" information */
+ *scr_aa = a;
+ *scr_cc = c;
+
+ /* Check for new min/max row info */
+ if (y < Term->y1) Term->y1 = y;
+ if (y > Term->y2) Term->y2 = y;
+
+ /* Check for new min/max col info for this row */
+ if (x < Term->x1[y]) Term->x1[y] = x;
+ if (x > Term->x2[y]) Term->x2[y] = x;
+}
+
+
+
+/*
+ * Mentally draw some attr/chars at a given location
+ *
+ * Assumes that (x,y) is a valid location, that the first "n" characters
+ * of the string "s" are all valid (non-zero), and that (x+n-1,y) is also
+ * a valid location, so the first "n" characters of "s" can all be added
+ * starting at (x,y) without causing any illegal operations.
+ */
+static void Term_queue_chars(int x, int y, int n, byte a, const char *s)
+{
+ int x1 = -1, x2 = -1;
+
+ byte *scr_aa = Term->scr->a[y];
+ char *scr_cc = Term->scr->c[y];
+
+ /* Queue the attr/chars */
+ for ( ; n; x++, s++, n--)
+ {
+ int oa = scr_aa[x];
+ int oc = scr_cc[x];
+
+ /* Hack -- Ignore non-changes */
+ if ((oa == a) && (oc == *s)) continue;
+
+ /* Save the "literal" information */
+ scr_aa[x] = a;
+ scr_cc[x] = *s;
+
+ /* Note the "range" of window updates */
+ if (x1 < 0) x1 = x;
+ x2 = x;
+ }
+
+ /* Expand the "change area" as needed */
+ if (x1 >= 0)
+ {
+ /* Check for new min/max row info */
+ if (y < Term->y1) Term->y1 = y;
+ if (y > Term->y2) Term->y2 = y;
+
+ /* Check for new min/max col info in this row */
+ if (x1 < Term->x1[y]) Term->x1[y] = x1;
+ if (x2 > Term->x2[y]) Term->x2[y] = x2;
+ }
+}
+
+
+/*
+ * Blank attribute/character
+ */
+static const byte ATTR_BLANK = TERM_WHITE;
+static const char CHAR_BLANK = ' ';
+
+/*
+ * Flush a row of the current window (see "Term_fresh")
+ *
+ * Display text using "Term_text()" and "Term_wipe()"
+ */
+static void Term_fresh_row_text(int y, int x1, int x2)
+{
+ int x;
+
+ byte *old_aa = Term->old->a[y];
+ char *old_cc = Term->old->c[y];
+
+ byte *scr_aa = Term->scr->a[y];
+ char *scr_cc = Term->scr->c[y];
+
+ /* Pending length */
+ int fn = 0;
+
+ /* Pending start */
+ int fx = 0;
+
+ /* Pending attr */
+ byte fa = ATTR_BLANK;
+
+ byte oa;
+ char oc;
+
+ byte na;
+ char nc;
+
+ /* Scan "modified" columns */
+ for (x = x1; x <= x2; x++)
+ {
+ /* See what is currently here */
+ oa = old_aa[x];
+ oc = old_cc[x];
+
+ /* See what is desired there */
+ na = scr_aa[x];
+ nc = scr_cc[x];
+
+ /* Handle unchanged grids */
+ if ((na == oa) && (nc == oc))
+ {
+ /* Flush */
+ if (fn)
+ {
+ Term->text(fx, y, fn, fa, &scr_cc[fx]);
+
+ /* Forget */
+ fn = 0;
+ }
+
+ /* Skip */
+ continue;
+ }
+
+ /* Save new contents */
+ old_aa[x] = na;
+ old_cc[x] = nc;
+
+ /* Notice new color */
+ if (fa != na)
+ {
+ /* Flush */
+ if (fn)
+ {
+ /* Draw the pending chars */
+ Term->text(fx, y, fn, fa, &scr_cc[fx]);
+
+ /* Forget */
+ fn = 0;
+ }
+
+ /* Save the new color */
+ fa = na;
+ }
+
+ /* Restart and Advance */
+ if (fn++ == 0) fx = x;
+ }
+
+ /* Flush */
+ if (fn)
+ {
+ Term->text(fx, y, fn, fa, &scr_cc[fx]);
+ }
+}
+
+
+
+
+
+/*
+ * Actually perform all requested changes to the window
+ *
+ * If absolutely nothing has changed, not even temporarily, or if the
+ * current "Term" is not mapped, then this function will return 1 and
+ * do absolutely nothing.
+ *
+ * Note that when "soft_cursor" is true, we erase the cursor (if needed)
+ * whenever anything has changed, and redraw it (if needed) after all of
+ * the screen updates are complete. This will induce a small amount of
+ * "cursor flicker" but only when the screen has been updated. If the
+ * screen is updated and then restored, you may still get this flicker.
+ *
+ * When "soft_cursor" is not true, we make the cursor invisible before
+ * doing anything else if it is supposed to be invisible by the time we
+ * are done, and we make it visible after moving it to its final location
+ * after all of the screen updates are complete.
+ *
+ * Note that "Term_xtra(TERM_XTRA_CLEAR,0)" must erase the entire screen,
+ * including the cursor, if needed, and may place the cursor anywhere.
+ *
+ * Note that "Term_xtra(TERM_XTRA_FRESH,0)" will be called after
+ * all of the rows have been "flushed".
+ *
+ * The helper functions currently "skip" any grids which already contain
+ * the desired contents. This may or may not be the best method, especially
+ * when the desired content fits nicely into the current stripe. For example,
+ * it might be better to go ahead and queue them while allowed, but keep a
+ * count of the "trailing skipables", then, when time to flush, or when a
+ * "non skippable" is found, force a flush if there are too many skippables.
+ *
+ * Perhaps an "initialization" stage, where the "text" (and "attr")
+ * buffers are "filled" with information, converting "blanks" into
+ * a convenient representation, and marking "skips" with "zero chars",
+ * and then some "processing" is done to determine which chars to skip.
+ *
+ * Currently, the helper functions are optimal for systems which prefer
+ * to "print a char + move a char + print a char" to "print three chars",
+ * and for applications that do a lot of "detailed" color printing.
+ *
+ * In the two "queue" functions, total "non-changes" are "pre-skipped".
+ * The helper functions must also handle situations in which the contents
+ * of a grid are changed, but then changed back to the original value,
+ * and situations in which two grids in the same row are changed, but
+ * the grids between them are unchanged.
+ *
+ * Normally, the "Term_wipe()" function is used only to display "blanks"
+ * that were induced by "Term_clear()" or "Term_erase()", and then only
+ * if the "attr_blank" and "char_blank" fields have not been redefined
+ * to use "white space" instead of the default "black space". Actually,
+ * the "Term_wipe()" function is used to display all "black" text, such
+ * as the default "spaces" created by "Term_clear()" and "Term_erase()".
+ *
+ * Note that if no "black" text is ever drawn, and if "attr_blank" is
+ * not "zero", then the "Term_wipe" hook will never be used.
+ *
+ * This function does nothing unless the "Term" is "mapped", which allows
+ * certain systems to optimize the handling of "closed" windows.
+ *
+ * On systems with a "soft" cursor, we must explicitly erase the cursor
+ * before flushing the output, if needed, to prevent a "jumpy" refresh.
+ * The actual method for this is horrible, but there is very little that
+ * we can do to simplify it efficiently. XXX XXX XXX
+ *
+ * On systems with a "hard" cursor, we will "hide" the cursor before
+ * flushing the output, if needed, to avoid a "flickery" refresh. It
+ * would be nice to *always* hide the cursor during the refresh, but
+ * this might be expensive (and/or ugly) on some machines.
+ *
+ * The "Term->icky_corner" flag is used to avoid calling "Term_wipe()"
+ * or "Term_pict()" or "Term_text()" on the bottom right corner of the
+ * window, which might induce "scrolling" or other nasty stuff on old
+ * dumb terminals. This flag is handled very efficiently. We assume
+ * that the "Term_curs()" call will prevent placing the cursor in the
+ * corner, if needed, though I doubt such placement is ever a problem.
+ * Currently, the use of "Term->icky_corner" and "Term->soft_cursor"
+ * together may result in undefined behavior.
+ */
+void Term_fresh()
+{
+ int x, y;
+
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ int y1 = Term->y1;
+ int y2 = Term->y2;
+
+ auto const &old = Term->old;
+ auto const &scr = Term->scr;
+
+
+ /* Do nothing unless "mapped" */
+ if (!Term->mapped_flag)
+ {
+ return;
+ }
+
+
+ /* Trivial Refresh */
+ if ((y1 > y2) &&
+ (scr->cu == old->cu) &&
+ (scr->cv == old->cv) &&
+ (scr->cx == old->cx) &&
+ (scr->cy == old->cy) &&
+ !(Term->total_erase))
+ {
+ /* Nothing */
+ return;
+ }
+
+ /* Handle "total erase" */
+ if (Term->total_erase)
+ {
+ byte na = ATTR_BLANK;
+ char nc = CHAR_BLANK;
+
+ /* Physically erase the entire window */
+ Term->m_frontend->clear();
+
+ /* Hack -- clear all "cursor" data */
+ old->cv = false;
+ old->cu = false;
+ old->cx = 0;
+ old->cy = 0;
+
+ /* Wipe each row */
+ for (y = 0; y < h; y++)
+ {
+ byte *aa = old->a[y];
+ char *cc = old->c[y];
+
+ /* Wipe each column */
+ for (x = 0; x < w; x++)
+ {
+ /* Wipe each grid */
+ *aa++ = na;
+ *cc++ = nc;
+ }
+ }
+
+ /* Redraw every row */
+ Term->y1 = y1 = 0;
+ Term->y2 = y2 = h - 1;
+
+ /* Redraw every column */
+ for (y = 0; y < h; y++)
+ {
+ Term->x1[y] = 0;
+ Term->x2[y] = w - 1;
+ }
+
+ /* Forget "total erase" */
+ Term->total_erase = false;
+ }
+
+
+ /* Cursor update -- Erase old Cursor */
+ if (Term->soft_cursor)
+ {
+ /* Cursor was visible */
+ if (!old->cu && old->cv)
+ {
+ int tx = old->cx;
+ int ty = old->cy;
+
+ byte *old_aa = old->a[ty];
+ char *old_cc = old->c[ty];
+
+ byte oa = old_aa[tx];
+ char oc = old_cc[tx];
+
+ /* Hack -- restore the actual character */
+ Term->text(tx, ty, 1, oa, &oc);
+ }
+ }
+
+ /* Something to update */
+ if (y1 <= y2)
+ {
+ /* Handle "icky corner" */
+ if (Term->icky_corner)
+ {
+ /* Avoid the corner */
+ if (y2 >= h - 1)
+ {
+ /* Avoid the corner */
+ if (Term->x2[h - 1] > w - 2)
+ {
+ /* Avoid the corner */
+ Term->x2[h - 1] = w - 2;
+ }
+ }
+ }
+
+
+ /* Scan the "modified" rows */
+ for (y = y1; y <= y2; ++y)
+ {
+ int x1 = Term->x1[y];
+ int x2 = Term->x2[y];
+
+ /* Flush each "modified" row */
+ if (x1 <= x2)
+ {
+ /* Flush the row */
+ Term_fresh_row_text(y, x1, x2);
+
+ /* This row is all done */
+ Term->x1[y] = w;
+ Term->x2[y] = 0;
+ }
+ }
+
+ /* No rows are invalid */
+ Term->y1 = h;
+ Term->y2 = 0;
+ }
+
+
+ /* Cursor update -- Show new Cursor */
+ if (Term->soft_cursor)
+ {
+ /* Draw the cursor */
+ if (!scr->cu && scr->cv)
+ {
+ /* Call the cursor display routine */
+ Term->curs(scr->cx, scr->cy);
+ }
+ }
+
+ /* Cursor Update -- Show new Cursor */
+ else
+ {
+ /* The cursor is useless, hide it */
+ if (scr->cu)
+ {
+ /* Paranoia -- Put the cursor NEAR where it belongs */
+ Term->curs(w - 1, scr->cy);
+
+ /* Make the cursor invisible */
+ /* Term_xtra(TERM_XTRA_SHAPE, 0); */
+ }
+
+ /* The cursor is invisible, hide it */
+ else if (!scr->cv)
+ {
+ /* Paranoia -- Put the cursor where it belongs */
+ Term->curs(scr->cx, scr->cy);
+
+ /* Make the cursor invisible */
+ /* Term_xtra(TERM_XTRA_SHAPE, 0); */
+ }
+
+ /* The cursor is visible, display it correctly */
+ else
+ {
+ /* Put the cursor where it belongs */
+ Term->curs(scr->cx, scr->cy);
+ }
+ }
+
+
+ /* Save the "cursor state" */
+ old->cu = scr->cu;
+ old->cv = scr->cv;
+ old->cx = scr->cx;
+ old->cy = scr->cy;
+
+
+ /* Actually flush the output */
+ Term->fresh();
+}
+
+
+
+/*** Output routines ***/
+
+
+/*
+ * Place the cursor at a given location
+ *
+ * Note -- "illegal" requests do not move the cursor.
+ */
+errr Term_gotoxy(int x, int y)
+{
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ /* Verify */
+ if ((x < 0) || (x >= w)) return ( -1);
+ if ((y < 0) || (y >= h)) return ( -1);
+
+ /* Remember the cursor */
+ Term->scr->cx = x;
+ Term->scr->cy = y;
+
+ /* The cursor is not useless */
+ Term->scr->cu = false;
+
+ /* Success */
+ return (0);
+}
+
+
+/*
+ * At a given location, place an attr/char
+ * Do not change the cursor position
+ * No visual changes until "Term_fresh()".
+ */
+void Term_draw(int x, int y, byte a, char c)
+{
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ /* Verify location */
+ if ((x < 0) || (x >= w)) return;
+ if ((y < 0) || (y >= h)) return;
+
+ /* Paranoia -- illegal char */
+ if (!c) return;
+
+ /* Queue it for later */
+ Term_queue_char(x, y, a, c);
+}
+
+
+/*
+ * Using the given attr, add the given char at the cursor.
+ *
+ * We return "-2" if the character is "illegal". XXX XXX
+ *
+ * We return "-1" if the cursor is currently unusable.
+ *
+ * We queue the given attr/char for display at the current
+ * cursor location, and advance the cursor to the right,
+ * marking it as unuable and returning "1" if it leaves
+ * the screen, and otherwise returning "0".
+ *
+ * So when this function, or the following one, return a
+ * positive value, future calls to either function will
+ * return negative ones.
+ */
+void Term_addch(byte a, char c)
+{
+ int w = Term->wid;
+
+ /* Handle "unusable" cursor */
+ if (Term->scr->cu)
+ {
+ return;
+ }
+
+ /* Paranoia -- no illegal chars */
+ if (!c)
+ {
+ return;
+ }
+
+ /* Queue the given character for display */
+ Term_queue_char(Term->scr->cx, Term->scr->cy, a, c);
+
+ /* Advance the cursor */
+ Term->scr->cx++;
+
+ /* Success */
+ if (Term->scr->cx < w)
+ {
+ return;
+ }
+
+ /* Note "Useless" cursor */
+ Term->scr->cu = true;
+}
+
+
+/*
+ * At the current location, using an attr, add a string
+ *
+ * We also take a length "n", using negative values to imply
+ * the largest possible value, and then we use the minimum of
+ * this length and the "actual" length of the string as the
+ * actual number of characters to attempt to display, never
+ * displaying more characters than will actually fit, since
+ * we do NOT attempt to "wrap" the cursor at the screen edge.
+ *
+ * We return "-1" if the cursor is currently unusable.
+ * We return "N" if we were "only" able to write "N" chars,
+ * even if all of the given characters fit on the screen,
+ * and mark the cursor as unusable for future attempts.
+ *
+ * So when this function, or the preceding one, return a
+ * positive value, future calls to either function will
+ * return negative ones.
+ */
+errr Term_addstr(int n, byte a, const char *s)
+{
+ int k;
+
+ int w = Term->wid;
+
+ errr res = 0;
+
+ /* Handle "unusable" cursor */
+ if (Term->scr->cu) return ( -1);
+
+ /* Obtain maximal length */
+ k = (n < 0) ? (w + 1) : n;
+
+ /* Obtain the usable string length */
+ for (n = 0; (n < k) && s[n]; n++) /* loop */;
+
+ /* React to reaching the edge of the screen */
+ if (Term->scr->cx + n >= w) res = n = w - Term->scr->cx;
+
+ /* Queue the first "n" characters for display */
+ Term_queue_chars(Term->scr->cx, Term->scr->cy, n, a, s);
+
+ /* Advance the cursor */
+ Term->scr->cx += n;
+
+ /* Hack -- Notice "Useless" cursor */
+ if (res)
+ {
+ Term->scr->cu = true;
+ }
+
+ /* Success (usually) */
+ return (res);
+}
+
+
+/*
+ * Move to a location and, using an attr, add a char
+ */
+void Term_putch(int x, int y, byte a, char c)
+{
+ /* Move first */
+ if (Term_gotoxy(x, y)) return;
+
+ /* Add the char */
+ Term_addch(a, c);
+}
+
+
+/*
+ * Move to a location and, using an attr, add a string
+ */
+void Term_putstr(int x, int y, int n, byte a, const char *s)
+{
+ /* Move first */
+ if (Term_gotoxy(x, y)) return;
+
+ /* Add the string */
+ Term_addstr(n, a, s);
+}
+
+
+
+/*
+ * Place cursor at (x,y), and clear the next "n" chars
+ */
+void Term_erase(int x, int y, int n)
+{
+ int i;
+
+ int w = Term->wid;
+ /* int h = Term->hgt; */
+
+ int x1 = -1;
+ int x2 = -1;
+
+ int na = ATTR_BLANK;
+ int nc = CHAR_BLANK;
+
+ byte *scr_aa;
+ char *scr_cc;
+
+ /* Place cursor */
+ if (Term_gotoxy(x, y))
+ {
+ return;
+ }
+
+ /* Force legal size */
+ if (x + n > w) n = w - x;
+
+ /* Fast access */
+ scr_aa = Term->scr->a[y];
+ scr_cc = Term->scr->c[y];
+
+ if (n > 0 && (byte)scr_cc[x] == 255 && scr_aa[x] == 255)
+ {
+ x--;
+ n++;
+ }
+
+ /* Scan every column */
+ for (i = 0; i < n; i++, x++)
+ {
+ int oa = scr_aa[x];
+ int oc = scr_cc[x];
+
+ /* Hack -- Ignore "non-changes" */
+ if ((oa == na) && (oc == nc)) continue;
+
+ /* Save the "literal" information */
+ scr_aa[x] = na;
+ scr_cc[x] = nc;
+
+ /* Track minimum changed column */
+ if (x1 < 0) x1 = x;
+
+ /* Track maximum changed column */
+ x2 = x;
+ }
+
+ /* Expand the "change area" as needed */
+ if (x1 >= 0)
+ {
+ /* Check for new min/max row info */
+ if (y < Term->y1) Term->y1 = y;
+ if (y > Term->y2) Term->y2 = y;
+
+ /* Check for new min/max col info in this row */
+ if (x1 < Term->x1[y]) Term->x1[y] = x1;
+ if (x2 > Term->x2[y]) Term->x2[y] = x2;
+ }
+}
+
+
+/*
+ * Clear the entire window, and move to the top left corner
+ *
+ * Note the use of the special "total_erase" code
+ */
+void Term_clear()
+{
+ int x, y;
+
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ byte na = ATTR_BLANK;
+ char nc = CHAR_BLANK;
+
+ /* Cursor usable */
+ Term->scr->cu = false;
+
+ /* Cursor to the top left */
+ Term->scr->cx = Term->scr->cy = 0;
+
+ /* Wipe each row */
+ for (y = 0; y < h; y++)
+ {
+ byte *scr_aa = Term->scr->a[y];
+ char *scr_cc = Term->scr->c[y];
+
+ /* Wipe each column */
+ for (x = 0; x < w; x++)
+ {
+ scr_aa[x] = na;
+ scr_cc[x] = nc;
+ }
+
+ /* This row has changed */
+ Term->x1[y] = 0;
+ Term->x2[y] = w - 1;
+ }
+
+ /* Every row has changed */
+ Term->y1 = 0;
+ Term->y2 = h - 1;
+
+ /* Force "total erase" */
+ Term->total_erase = true;
+}
+
+
+/*
+ * Redraw (and refresh) the whole window.
+ */
+void Term_redraw()
+{
+ /* Force "total erase" */
+ Term->total_erase = true;
+
+ /* Hack -- Refresh */
+ Term_fresh();
+}
+
+
+/*
+ * Redraw part of a window.
+ */
+void Term_redraw_section(int x1, int y1, int x2, int y2)
+{
+ int i, j;
+
+ char *c_ptr;
+
+ /* Bounds checking */
+ if (y2 >= Term->hgt) y2 = Term->hgt - 1;
+ if (x2 >= Term->wid) x2 = Term->wid - 1;
+ if (y1 < 0) y1 = 0;
+ if (x1 < 0) x1 = 0;
+
+ /* Set y limits */
+ Term->y1 = y1;
+ Term->y2 = y2;
+
+ /* Set the x limits */
+ for (i = Term->y1; i <= Term->y2; i++)
+ {
+ Term->x1[i] = x1;
+ Term->x2[i] = x2;
+
+ c_ptr = Term->old->c[i];
+
+ /* Clear the section so it is redrawn */
+ for (j = x1; j <= x2; j++)
+ {
+ /* Hack - set the old character to "none" */
+ c_ptr[j] = 0;
+ }
+ }
+
+ /* Hack -- Refresh */
+ Term_fresh();
+}
+
+void Term_bell()
+{
+ Term->m_frontend->noise();
+}
+
+/*** Access routines ***/
+
+namespace { // anonymous
+
+/**
+ * Policy handler for setting/resetting cursor visibility.
+ */
+struct CursorVisibilyPolicy {
+public:
+ struct handle_type {
+ term *t = nullptr;
+ bool cv = false;
+ };
+
+ static handle_type from(term *t)
+ {
+ return handle_type {
+ .t = t,
+ .cv = t->scr->cv
+ };
+ }
+
+ static handle_type get_null()
+ {
+ return handle_type();
+ }
+
+ static bool is_null(handle_type const &v)
+ {
+ return v.t == nullptr;
+ }
+
+ static void close(handle_type const &v)
+ {
+ Term->scr->cv = v.cv;
+ }
+};
+
+/**
+ * Policy handler for setting/restoring cursor flags.
+ */
+struct CursorFlagsPolicy {
+
+public:
+ struct handle_type {
+ term *t = nullptr;
+ bool cu = false;
+ bool cv = false;
+ };
+
+private:
+ handle_type handle;
+
+public:
+ static handle_type from(term *t)
+ {
+ return handle_type {
+ .t = t,
+ .cu = t->scr->cu,
+ .cv = t->scr->cv
+ };
+ }
+
+ static handle_type get_null()
+ {
+ return handle_type();
+ }
+
+ static bool is_null(handle_type const &h)
+ {
+ return h.t == nullptr;
+ }
+
+ static void close(handle_type &h)
+ {
+ h.t->scr->cu = h.cu;
+ h.t->scr->cv = h.cv;
+ }
+
+};
+
+/**
+ * Policy handler for setting/resetting the active terminal.
+ */
+struct ActiveTerminalPolicy {
+public:
+ using handle_type = term *;
+
+ static handle_type get_null()
+ {
+ return nullptr;
+ }
+
+ static bool is_null(handle_type t)
+ {
+ return t == nullptr;
+ }
+
+ static void close(handle_type t)
+ {
+ Term_activate(t);
+ }
+};
+
+} // namespace (anonymous)
+
+
+void Term_with_saved_cursor_flags(std::function<void ()> callback)
+{
+ unique_handle<CursorFlagsPolicy> resetter(CursorFlagsPolicy::from(Term));
+ callback();
+}
+
+void Term_with_saved_cursor_visbility(std::function<void ()> callback)
+{
+ unique_handle<CursorVisibilyPolicy> resetter(CursorVisibilyPolicy::from(Term));
+ callback();
+}
+
+void Term_with_active(term *t, std::function<void ()> callback)
+{
+ unique_handle<ActiveTerminalPolicy> resetter(Term);
+ Term_activate(t);
+ callback();
+}
+
+/*
+ * Extract the current window size
+ */
+void Term_get_size(int *w, int *h)
+{
+ if (w)
+ {
+ (*w) = Term->wid;
+ }
+
+ if (h)
+ {
+ (*h) = Term->hgt;
+ }
+}
+
+
+/*
+ * Extract the current cursor location
+ */
+void Term_locate(int *x, int *y)
+{
+ /* Access the cursor */
+ (*x) = Term->scr->cx;
+ (*y) = Term->scr->cy;
+}
+
+
+/*
+ * At a given location, determine the "current" attr and char
+ * Note that this refers to what will be on the window after the
+ * next call to "Term_fresh()". It may or may not already be there.
+ */
+void Term_what(int x, int y, byte *a, char *c)
+{
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ /* Verify location */
+ assert(x >= 0);
+ assert(x < w);
+ assert(y >= 0);
+ assert(y < h);
+
+ /* Direct access */
+ (*a) = Term->scr->a[y][x];
+ (*c) = Term->scr->c[y][x];
+}
+
+
+void Term_hide_cursor()
+{
+ Term->scr->cv = 0;
+}
+
+
+void Term_show_cursor()
+{
+ Term->scr->set_cursor_visible();
+}
+
+
+
+/*** Input routines ***/
+
+
+/*
+ * Flush and forget the input
+ */
+void Term_flush()
+{
+ /* Hack -- Flush all events */
+ Term->m_frontend->flush_events();
+
+ /* Forget all keypresses */
+ Term->m_key_queue.clear();
+}
+
+
+
+/*
+ * Add a keypress to the "queue"
+ */
+void Term_keypress(int k)
+{
+ /* Ignore non-keys */
+ if (!k) return;
+
+ /* Push */
+ Term->m_key_queue.push_back(k);
+}
+
+
+/*
+ * Add a keypress to the FRONT of the "queue"
+ */
+errr Term_key_push(int k)
+{
+ /* Hack -- Refuse to enqueue non-keys */
+ if (!k) return ( -1);
+
+ /* Push */
+ return push_result_to_errr(Term->m_key_queue.push_front(k));
+}
+
+
+/*
+ * Check for a pending keypress on the key queue.
+ *
+ * Store the keypress, if any, in "ch", and return "0".
+ * Otherwise store "zero" in "ch", and return "1".
+ *
+ * Wait for a keypress if "wait" is true.
+ *
+ * Remove the keypress if "take" is true.
+ */
+errr Term_inkey(char *ch, bool wait, bool take)
+{
+ auto &key_queue = Term->m_key_queue;
+
+ /* Assume no key */
+ (*ch) = '\0';
+
+ /* Process queued UI events */
+ Term->m_frontend->process_queued_events();
+
+ /* Wait */
+ if (wait)
+ {
+ /* Process pending events while necessary */
+ while (key_queue.empty())
+ {
+ /* Process events (wait for one) */
+ Term->m_frontend->process_event(true);
+ }
+ }
+
+ /* Do not Wait */
+ else
+ {
+ /* Process pending events if necessary */
+ if (key_queue.empty())
+ {
+ /* Process events (do not wait) */
+ Term->m_frontend->process_event(false);
+ }
+ }
+
+ /* No keys are ready */
+ if (key_queue.empty())
+ {
+ return (1);
+ }
+
+ /* Extract the next keypress */
+ if (take)
+ {
+ *ch = key_queue.pop_front();
+ }
+ else
+ {
+ *ch = key_queue.front();
+ }
+
+ /* Success */
+ return (0);
+}
+
+
+/*** Extra routines ***/
+
+
+/*
+ * Save the "requested" screen into the "memorized" screen
+ *
+ * Every "Term_save()" should match exactly one "Term_load()"
+ */
+void Term_save()
+{
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ /* Create */
+ if (!Term->mem)
+ {
+ Term->mem = std::make_unique<term_win>(w, h);
+ }
+
+ /* Grab */
+ Term->mem->copy_from(Term->scr, w, h);
+}
+
+/*
+ * Same as before but can save more than once
+ */
+term_win* Term_save_to()
+{
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ /* Copy */
+ auto save = new term_win(w, h);
+ save->copy_from(Term->scr, w, h);
+
+ /* Success */
+ return (save);
+}
+
+/*
+ * Restore the "requested" contents (see above).
+ *
+ * Every "Term_save()" should match exactly one "Term_load()"
+ */
+void Term_load()
+{
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ /* Create empty contents if nothing was actually saved previously */
+ if (!Term->mem)
+ {
+ Term->mem = std::make_unique<term_win>(w, h);
+ }
+
+ /* Load */
+ Term->scr->copy_from(Term->mem, w, h);
+
+ /* Assume change */
+ for (int y = 0; y < h; y++)
+ {
+ /* Assume change */
+ Term->x1[y] = 0;
+ Term->x2[y] = w - 1;
+ }
+
+ /* Assume change */
+ Term->y1 = 0;
+ Term->y2 = h - 1;
+}
+
+/*
+ * Same as previous but allow to save more than one
+ */
+void Term_load_from(term_win *save)
+{
+ int y;
+
+ int w = Term->wid;
+ int h = Term->hgt;
+
+ /* Create */
+ if (!save)
+ {
+ return;
+ }
+
+ /* Load */
+ Term->scr->copy_from(save, w, h);
+
+ /* Assume change */
+ for (y = 0; y < h; y++)
+ {
+ /* Assume change */
+ Term->x1[y] = 0;
+ Term->x2[y] = w - 1;
+ }
+
+ /* Assume change */
+ Term->y1 = 0;
+ Term->y2 = h - 1;
+
+ /* Free is requested */
+ delete save;
+}
+
+/*
+ * React to a new physical window size.
+ */
+void Term_resize(int w, int h)
+{
+ int i;
+
+ int wid, hgt;
+
+ /* Ignore illegal changes */
+ if ((w < 1) || (h < 1))
+ {
+ return;
+ }
+
+ /* Ignore non-changes */
+ if ((Term->wid == w) && (Term->hgt == h))
+ {
+ return;
+ }
+
+ /* Minimum dimensions */
+ wid = std::min<int>(Term->wid, w);
+ hgt = std::min<int>(Term->hgt, h);
+
+ /* Create new window */
+ {
+ auto hold_old = std::move(Term->old);
+ Term->old = std::make_unique<term_win>(w, h);
+ Term->old->copy_from(hold_old, wid, hgt);
+
+ /* Illegal cursor? */
+ if (Term->old->cx >= w) Term->old->cu = true;
+ if (Term->old->cy >= h) Term->old->cu = true;
+ }
+
+ /* Create new window */
+ {
+ auto hold_scr = std::move(Term->scr);
+ Term->scr = std::make_unique<term_win>(w, h);
+ Term->scr->copy_from(hold_scr, wid, hgt);
+
+ /* Illegal cursor? */
+ if (Term->scr->cx >= w) Term->scr->cu = true;
+ if (Term->scr->cy >= h) Term->scr->cu = true;
+ }
+
+ /* Create new window */
+ if (Term->mem)
+ {
+ auto hold_mem = std::move(Term->mem);
+ Term->mem = std::make_unique<term_win>(w, h);
+ Term->mem->copy_from(hold_mem, wid, hgt);
+
+ /* Illegal cursor? */
+ if (Term->mem->cx >= w) Term->mem->cu = true;
+ if (Term->mem->cy >= h) Term->mem->cu = true;
+ }
+
+ /* Resize scanners */
+ Term->x1.resize(h);
+ Term->x2.resize(h);
+ for (i = 0; i < h; i++)
+ {
+ Term->x1[i] = 0;
+ Term->x2[i] = w - 1;
+ }
+
+ /* Save new size */
+ Term->wid = w;
+ Term->hgt = h;
+
+ /* Force "total erase" */
+ Term->total_erase = true;
+
+ /* Assume change */
+ Term->y1 = 0;
+ Term->y2 = h - 1;
+
+ /* Execute the "resize_hook" hook, if available */
+ if (Term->m_resize_hook)
+ {
+ Term->m_resize_hook();
+ }
+}
+
+
+
+/*
+ * Activate a new Term (and deactivate the current Term)
+ *
+ * This function is extremely important, and also somewhat bizarre.
+ * It is the only function that should "modify" the value of "Term".
+ *
+ * To "create" a valid "term", one should do "term_init(t)", then
+ * set the various flags and hooks, and then do "Term_activate(t)".
+ */
+void Term_activate(term *t)
+{
+ /* No change? */
+ if (Term == t)
+ {
+ return;
+ }
+
+ /* Hack -- Call the special "init" hook */
+ if (t && !t->active_flag)
+ {
+ t->m_frontend->init();
+
+ /* Remember */
+ t->active_flag = true;
+
+ /* Assume mapped */
+ t->mapped_flag = true;
+ }
+
+ /* Remember the Term */
+ Term = t;
+}
+
+
+void Term_xtra_react()
+{
+ Term->m_frontend->react();
+}
+
+/**
+ * Set the current terminal "mapped" flag.
+ */
+void Term_mapped()
+{
+ Term->mapped_flag = true;
+}
+
+
+/**
+ * Unset the current terminal "mapped" flag.
+ */
+void Term_unmapped()
+{
+ Term->mapped_flag = false;
+}
+
+
+void Term_rename_main_win(std::string_view name)
+{
+ Term->m_frontend->rename_main_window(name);
+}
+
+
+/*
+ * Initialize a term, using a window of the given size.
+ * Also prepare the "input queue" for "k" keypresses
+ * By default, the cursor starts out "invisible"
+ * By default, we "erase" using "black spaces"
+ */
+term *term_init(int w, int h, int k, std::shared_ptr<Frontend> user_interface)
+{
+ return new term(w, h, k, user_interface);
+}
+
+/*
+ * Nuke a term
+ */
+void term_nuke(term *t)
+{
+ delete t;
+}
+
+/**
+ * Set the function to call when terminal is resized.
+ */
+void term_set_resize_hook(term *t, std::function<void ()> f)
+{
+ t->m_resize_hook = f;
+}