Angband 3.0.9b.
[angband.git] / src / z-term.c
blobe3eb41292d09e40191cd85d2fd66ebf17c194610
1 /* File: z-term.c */
3 /*
4 * Copyright (c) 1997 Ben Harrison
6 * This software may be copied and distributed for educational, research,
7 * and not for profit purposes provided that this copyright and statement
8 * are included in all such copies.
9 */
11 /* Purpose: a generic, efficient, terminal window package -BEN- */
13 #include "z-term.h"
15 #include "z-virt.h"
19 * This file provides a generic, efficient, terminal window package,
20 * which can be used not only on standard terminal environments such
21 * as dumb terminals connected to a Unix box, but also in more modern
22 * "graphic" environments, such as the Macintosh or Unix/X11.
24 * Each "window" works like a standard "dumb terminal", that is, it
25 * can display a two dimensional array of grids containing colored
26 * textual symbols, plus an optional cursor, and it can be used to
27 * get keypress events from the user.
29 * In fact, this package can simply be used, if desired, to support
30 * programs which will look the same on a dumb terminal as they do
31 * on a graphic platform such as the Macintosh.
33 * This package was designed to help port the game "Angband" to a wide
34 * variety of different platforms. Angband, like many other games in
35 * the "rogue-like" heirarchy, requires, at the minimum, the ability
36 * to display "colored textual symbols" in a standard 80x24 "window",
37 * such as that provided by most dumb terminals, and many old personal
38 * computers, and to check for "keypresses" from the user. The major
39 * concerns were thus portability and efficiency, so Angband could be
40 * easily ported to many different systems, with minimal effort, and
41 * yet would run quickly on each of these systems, no matter what kind
42 * of underlying hardware/software support was being used.
44 * It is important to understand the differences between the older
45 * "dumb terminals" and the newer "graphic interface" machines, since
46 * this package was designed to work with both types of systems.
48 * New machines:
49 * waiting for a keypress is complex
50 * checking for a keypress is often cheap
51 * changing "colors" may be expensive
52 * the "color" of a "blank" is rarely important
53 * moving the "cursor" is relatively cheap
54 * use a "software" cursor (only moves when requested)
55 * drawing characters normally will not erase old ones
56 * drawing a character on the cursor often erases it
57 * may have fast routines for "clear a region"
58 * the bottom right corner is usually not special
60 * Old machines:
61 * waiting for a keypress is simple
62 * checking for a keypress is often expensive
63 * changing "colors" is usually cheap
64 * the "color" of a "blank" may be important
65 * moving the "cursor" may be expensive
66 * use a "hardware" cursor (moves during screen updates)
67 * drawing new symbols automatically erases old ones
68 * characters may only be drawn at the cursor location
69 * drawing a character on the cursor will move the cursor
70 * may have fast routines for "clear entire window"
71 * may have fast routines for "clear to end of line"
72 * the bottom right corner is often dangerous
75 * This package provides support for multiple windows, each of an
76 * arbitrary size (up to 255x255), each with its own set of flags,
77 * and its own hooks to handle several low-level procedures which
78 * differ from platform to platform. Then the main program simply
79 * creates one or more "term" structures, setting the various flags
80 * and hooks in a manner appropriate for the current platform, and
81 * then it can use the various "term" structures without worrying
82 * about the underlying platform.
85 * This package allows each "grid" in each window to hold an attr/char
86 * pair, with each ranging from 0 to 255, and makes very few assumptions
87 * about the meaning of any attr/char values. Normally, we assume that
88 * "attr 0" is "black", with the semantics that "black" text should be
89 * sent to "Term_wipe()" instead of "Term_text()", but this sematics is
90 * modified if either the "always_pict" or the "always_text" flags are
91 * set. We assume that "char 0" is "dangerous", since placing such a
92 * "char" in the middle of a string "terminates" the string, and usually
93 * we prevent its use.
95 * Finally, we use a special attr/char pair, defaulting to "attr 0" and
96 * "char 32", also known as "black space", when we "erase" or "clear"
97 * any window, but this pair can be redefined to any pair, including
98 * the standard "white space", or the bizarre "emptiness" ("attr 0"
99 * and "char 0"), as long as various obscure restrictions are met.
102 * This package provides several functions which allow a program to
103 * interact with the "term" structures. Most of the functions allow
104 * the program to "request" certain changes to the current "term",
105 * such as moving the cursor, drawing an attr/char pair, erasing a
106 * region of grids, hiding the cursor, etc. Then there is a special
107 * function which causes all of the "pending" requests to be performed
108 * in an efficient manner. There is another set of functions which
109 * allow the program to query the "requested state" of the current
110 * "term", such as asking for the cursor location, or what attr/char
111 * is at a given location, etc. There is another set of functions
112 * dealing with "keypress" events, which allows the program to ask if
113 * the user has pressed any keys, or to forget any keys the user pressed.
114 * There is a pair of functions to allow this package to memorize the
115 * contents of the current "term", and to restore these contents at
116 * a later time. There is a special function which allows the program
117 * to specify which "term" structure should be the "current" one. At
118 * the lowest level, there is a set of functions which allow a new
119 * "term" to be initialized or destroyed, and which allow this package,
120 * or a program, to access the special "hooks" defined for the current
121 * "term", and a set of functions which those "hooks" can use to inform
122 * this package of the results of certain occurances, for example, one
123 * such function allows this package to learn about user keypresses,
124 * detected by one of the special "hooks".
126 * We provide, among other things, the functions "Term_keypress()"
127 * to "react" to keypress events, and "Term_redraw()" to redraw the
128 * entire window, plus "Term_resize()" to note a new size.
131 * Note that the current "term" contains two "window images". One of
132 * these images represents the "requested" contents of the "term", and
133 * the other represents the "actual" contents of the "term", at the time
134 * of the last performance of pending requests. This package uses these
135 * two images to determine the "minimal" amount of work needed to make
136 * the "actual" contents of the "term" match the "requested" contents of
137 * the "term". This method is not perfect, but it often reduces the
138 * amount of work needed to perform the pending requests, which thus
139 * increases the speed of the program itself. This package promises
140 * that the requested changes will appear to occur either "all at once"
141 * or in a "top to bottom" order. In addition, a "cursor" is maintained,
142 * and this cursor is updated along with the actual window contents.
144 * Currently, the "Term_fresh()" routine attempts to perform the "minimum"
145 * number of physical updates, in terms of total "work" done by the hooks
146 * Term_wipe(), Term_text(), and Term_pict(), making use of the fact that
147 * adjacent characters of the same color can both be drawn together using
148 * the "Term_text()" hook, and that "black" text can often be sent to the
149 * "Term_wipe()" hook instead of the "Term_text()" hook, and if something
150 * is already displayed in a window, then it is not necessary to display
151 * it again. Unfortunately, this may induce slightly non-optimal results
152 * in some cases, in particular, those in which, say, a string of ten
153 * characters needs to be written, but the fifth character has already
154 * been displayed. Currently, this will cause the "Term_text()" routine
155 * to be called once for each half of the string, instead of once for the
156 * whole string, which, on some machines, may be non-optimal behavior.
158 * The new formalism includes a "displayed" screen image (old) which
159 * is actually seen by the user, a "requested" screen image (scr)
160 * which is being prepared for display, a "memorized" screen image
161 * (mem) which is used to save and restore screen images, and a
162 * "temporary" screen image (tmp) which is currently unused.
165 * Several "flags" are available in each "term" to allow the underlying
166 * visual system (which initializes the "term" structure) to "optimize"
167 * the performance of this package for the given system, or to request
168 * certain behavior which is helpful/required for the given system.
170 * The "soft_cursor" flag indicates the use of a "soft" cursor, which
171 * only moves when explicitly requested,and which is "erased" when
172 * any characters are drawn on top of it. This flag is used for all
173 * "graphic" systems which handle the cursor by "drawing" it.
175 * The "icky_corner" flag indicates that the bottom right "corner"
176 * of the windows are "icky", and "printing" anything there may
177 * induce "messy" behavior, such as "scrolling". This flag is used
178 * for most old "dumb terminal" systems.
181 * The "term" structure contains the following function "hooks":
183 * Term->init_hook = Init the term
184 * Term->nuke_hook = Nuke the term
185 * Term->user_hook = Perform user actions
186 * Term->xtra_hook = Perform extra actions
187 * Term->curs_hook = Draw (or Move) the cursor
188 * Term->bigcurs_hook = Draw (or Move) the big cursor (bigtile mode)
189 * Term->wipe_hook = Draw some blank spaces
190 * Term->text_hook = Draw some text in the window
191 * Term->pict_hook = Draw some attr/chars in the window
193 * The "Term->user_hook" hook provides a simple hook to an implementation
194 * defined function, with application defined semantics. It is available
195 * to the program via the "Term_user()" function.
197 * The "Term->xtra_hook" hook provides a variety of different functions,
198 * based on the first parameter (which should be taken from the various
199 * TERM_XTRA_* defines) and the second parameter (which may make sense
200 * only for some first parameters). It is available to the program via
201 * the "Term_xtra()" function, though some first parameters are only
202 * "legal" when called from inside this package.
204 * The "Term->curs_hook" hook provides this package with a simple way
205 * to "move" or "draw" the cursor to the grid "x,y", depending on the
206 * setting of the "soft_cursor" flag. Note that the cursor is never
207 * redrawn if "nothing" has happened to the screen (even temporarily).
208 * This hook is required.
210 * The "Term->wipe_hook" hook provides this package with a simple way
211 * to "erase", starting at "x,y", the next "n" grids. This hook assumes
212 * that the input is valid. This hook is required, unless the setting
213 * of the "always_pict" or "always_text" flags makes it optional.
215 * The "Term->text_hook" hook provides this package with a simple way
216 * to "draw", starting at "x,y", the "n" chars contained in "cp", using
217 * the attr "a". This hook assumes that the input is valid, and that
218 * "n" is between 1 and 256 inclusive, but it should NOT assume that
219 * the contents of "cp" are null-terminated. This hook is required,
220 * unless the setting of the "always_pict" flag makes it optional.
222 * The "Term->pict_hook" hook provides this package with a simple way
223 * to "draw", starting at "x,y", the "n" attr/char pairs contained in
224 * the arrays "ap" and "cp". This hook assumes that the input is valid,
225 * and that "n" is between 1 and 256 inclusive, but it should NOT assume
226 * that the contents of "cp" are null-terminated. This hook is optional,
227 * unless the setting of the "always_pict" or "higher_pict" flags make
228 * it required. Note that recently, this hook was changed from taking
229 * a byte "a" and a char "c" to taking a length "n", an array of bytes
230 * "ap" and an array of chars "cp". Old implementations of this hook
231 * should now iterate over all "n" attr/char pairs.
232 * The two new arrays "tap" and "tcp" can contain the attr/char pairs
233 * of the terrain below the values in "ap" and "cp". These values can
234 * be used to implement transparency when using graphics by drawing
235 * the terrain values as a background and the "ap", "cp" values in
236 * the foreground.
238 * The game "Angband" uses a set of files called "main-xxx.c", for
239 * various "xxx" suffixes. Most of these contain a function called
240 * "init_xxx()", that will prepare the underlying visual system for
241 * use with Angband, and then create one or more "term" structures,
242 * using flags and hooks appropriate to the given platform, so that
243 * the "main()" function can call one (or more) of the "init_xxx()"
244 * functions, as appropriate, to prepare the required "term" structs
245 * (one for each desired sub-window), and these "init_xxx()" functions
246 * are called from a centralized "main()" function in "main.c". Other
247 * "main-xxx.c" systems contain their own "main()" function which, in
248 * addition to doing everything needed to initialize the actual program,
249 * also does everything that the normal "init_xxx()" functions would do.
251 * The game "Angband" defines, in addition to "attr 0", all of the
252 * attr codes from 1 to 15, using definitions in "defines.h", and
253 * thus the "main-xxx.c" files used by Angband must handle these
254 * attr values correctly. Also, they must handle all other attr
255 * values, though they may do so in any way they wish, for example,
256 * by always taking every attr code mod 16. Many of the "main-xxx.c"
257 * files use "white space" ("attr 1" / "char 32") to "erase" or "clear"
258 * any window, for efficiency.
260 * The game "Angband" uses the "Term_user" hook to allow any of the
261 * "main-xxx.c" files to interact with the user, by calling this hook
262 * whenever the user presses the "!" key when the game is waiting for
263 * a new command. This could be used, for example, to provide "unix
264 * shell commands" to the Unix versions of the game.
266 * See "main-xxx.c" for a simple skeleton file which can be used to
267 * create a "visual system" for a new platform when porting Angband.
276 * The current "term"
278 term *Term = NULL;
283 /*** Local routines ***/
287 * Nuke a term_win (see below)
289 static errr term_win_nuke(term_win *s)
291 /* Free the window access arrays */
292 KILL(s->a);
293 KILL(s->c);
295 /* Free the window content arrays */
296 KILL(s->va);
297 KILL(s->vc);
299 /* Free the terrain access arrays */
300 KILL(s->ta);
301 KILL(s->tc);
303 /* Free the terrain content arrays */
304 KILL(s->vta);
305 KILL(s->vtc);
307 /* Success */
308 return (0);
313 * Initialize a "term_win" (using the given window size)
315 static errr term_win_init(term_win *s, int w, int h)
317 int y;
319 /* Make the window access arrays */
320 C_MAKE(s->a, h, byte*);
321 C_MAKE(s->c, h, char*);
323 /* Make the window content arrays */
324 C_MAKE(s->va, h * w, byte);
325 C_MAKE(s->vc, h * w, char);
327 /* Make the terrain access arrays */
328 C_MAKE(s->ta, h, byte*);
329 C_MAKE(s->tc, h, char*);
331 /* Make the terrain content arrays */
332 C_MAKE(s->vta, h * w, byte);
333 C_MAKE(s->vtc, h * w, char);
335 /* Prepare the window access arrays */
336 for (y = 0; y < h; y++)
338 s->a[y] = s->va + w * y;
339 s->c[y] = s->vc + w * y;
341 s->ta[y] = s->vta + w * y;
342 s->tc[y] = s->vtc + w * y;
345 /* Success */
346 return (0);
351 * Copy a "term_win" from another
353 static errr term_win_copy(term_win *s, term_win *f, int w, int h)
355 int x, y;
357 /* Copy contents */
358 for (y = 0; y < h; y++)
360 byte *f_aa = f->a[y];
361 char *f_cc = f->c[y];
363 byte *s_aa = s->a[y];
364 char *s_cc = s->c[y];
366 byte *f_taa = f->ta[y];
367 char *f_tcc = f->tc[y];
369 byte *s_taa = s->ta[y];
370 char *s_tcc = s->tc[y];
372 for (x = 0; x < w; x++)
374 *s_aa++ = *f_aa++;
375 *s_cc++ = *f_cc++;
377 *s_taa++ = *f_taa++;
378 *s_tcc++ = *f_tcc++;
382 /* Copy cursor */
383 s->cx = f->cx;
384 s->cy = f->cy;
385 s->cu = f->cu;
386 s->cv = f->cv;
388 /* Success */
389 return (0);
394 /*** External hooks ***/
398 * Execute the "Term->user_hook" hook, if available (see above).
400 errr Term_user(int n)
402 /* Verify the hook */
403 if (!Term->user_hook) return (-1);
405 /* Call the hook */
406 return ((*Term->user_hook)(n));
410 * Execute the "Term->xtra_hook" hook, if available (see above).
412 errr Term_xtra(int n, int v)
414 /* Verify the hook */
415 if (!Term->xtra_hook) return (-1);
417 /* Call the hook */
418 return ((*Term->xtra_hook)(n, v));
423 /*** Fake hooks ***/
427 * Hack -- fake hook for "Term_curs()" (see above)
429 static errr Term_curs_hack(int x, int y)
431 /* Compiler silliness */
432 if (x || y) return (-2);
434 /* Oops */
435 return (-1);
439 * Hack -- fake hook for "Term_wipe()" (see above)
441 static errr Term_wipe_hack(int x, int y, int n)
443 /* Compiler silliness */
444 if (x || y || n) return (-2);
446 /* Oops */
447 return (-1);
451 * Hack -- fake hook for "Term_text()" (see above)
453 static errr Term_text_hack(int x, int y, int n, byte a, const char *cp)
455 /* Compiler silliness */
456 if (x || y || n || a || cp) return (-2);
458 /* Oops */
459 return (-1);
464 * Hack -- fake hook for "Term_pict()" (see above)
466 static errr Term_pict_hack(int x, int y, int n, const byte *ap, const char *cp, const byte *tap, const char *tcp)
468 /* Compiler silliness */
469 if (x || y || n || ap || cp || tap || tcp) return (-2);
471 /* Oops */
472 return (-1);
476 /*** Efficient routines ***/
480 * Mentally draw an attr/char at a given location
482 * Assumes given location and values are valid.
484 void Term_queue_char(term *t, int x, int y, byte a, char c, byte ta, char tc)
486 byte *scr_aa = t->scr->a[y];
487 char *scr_cc = t->scr->c[y];
489 byte oa = scr_aa[x];
490 char oc = scr_cc[x];
492 byte *scr_taa = t->scr->ta[y];
493 char *scr_tcc = t->scr->tc[y];
495 byte ota = scr_taa[x];
496 char otc = scr_tcc[x];
498 /* Don't change is the terrain value is 0 */
499 if (!ta) ta = ota;
500 if (!tc) tc = otc;
502 /* Hack -- Ignore non-changes */
503 if ((oa == a) && (oc == c) && (ota == ta) && (otc == tc)) return;
505 /* Save the "literal" information */
506 scr_aa[x] = a;
507 scr_cc[x] = c;
509 scr_taa[x] = ta;
510 scr_tcc[x] = tc;
512 /* Check for new min/max row info */
513 if (y < t->y1) t->y1 = y;
514 if (y > t->y2) t->y2 = y;
516 /* Check for new min/max col info for this row */
517 if (x < t->x1[y]) t->x1[y] = x;
518 if (x > t->x2[y]) t->x2[y] = x;
523 * Mentally draw some attr/chars at a given location
525 * Assumes that (x,y) is a valid location, that the first "n" characters
526 * of the string "s" are all valid (non-zero), and that (x+n-1,y) is also
527 * a valid location, so the first "n" characters of "s" can all be added
528 * starting at (x,y) without causing any illegal operations.
530 void Term_queue_chars(int x, int y, int n, byte a, cptr s)
532 int x1 = -1, x2 = -1;
534 byte *scr_aa = Term->scr->a[y];
535 char *scr_cc = Term->scr->c[y];
537 byte *scr_taa = Term->scr->ta[y];
538 char *scr_tcc = Term->scr->tc[y];
540 /* Queue the attr/chars */
541 for ( ; n; x++, s++, n--)
543 byte oa = scr_aa[x];
544 char oc = scr_cc[x];
546 byte ota = scr_taa[x];
547 char otc = scr_tcc[x];
549 /* Hack -- Ignore non-changes */
550 if ((oa == a) && (oc == *s) && (ota == 0) && (otc == 0)) continue;
552 /* Save the "literal" information */
553 scr_aa[x] = a;
554 scr_cc[x] = *s;
556 scr_taa[x] = 0;
557 scr_tcc[x] = 0;
559 /* Note the "range" of window updates */
560 if (x1 < 0) x1 = x;
561 x2 = x;
564 /* Expand the "change area" as needed */
565 if (x1 >= 0)
567 /* Check for new min/max row info */
568 if (y < Term->y1) Term->y1 = y;
569 if (y > Term->y2) Term->y2 = y;
571 /* Check for new min/max col info in this row */
572 if (x1 < Term->x1[y]) Term->x1[y] = x1;
573 if (x2 > Term->x2[y]) Term->x2[y] = x2;
579 /*** Refresh routines ***/
583 * Flush a row of the current window (see "Term_fresh")
585 * Display text using "Term_pict()"
587 static void Term_fresh_row_pict(int y, int x1, int x2)
589 int x;
591 byte *old_aa = Term->old->a[y];
592 char *old_cc = Term->old->c[y];
594 byte *scr_aa = Term->scr->a[y];
595 char *scr_cc = Term->scr->c[y];
597 byte *old_taa = Term->old->ta[y];
598 char *old_tcc = Term->old->tc[y];
600 byte *scr_taa = Term->scr->ta[y];
601 char *scr_tcc = Term->scr->tc[y];
603 byte ota;
604 char otc;
606 byte nta;
607 char ntc;
609 /* Pending length */
610 int fn = 0;
612 /* Pending start */
613 int fx = 0;
615 byte oa;
616 char oc;
618 byte na;
619 char nc;
621 /* Scan "modified" columns */
622 for (x = x1; x <= x2; x++)
624 /* See what is currently here */
625 oa = old_aa[x];
626 oc = old_cc[x];
628 /* See what is desired there */
629 na = scr_aa[x];
630 nc = scr_cc[x];
632 ota = old_taa[x];
633 otc = old_tcc[x];
635 nta = scr_taa[x];
636 ntc = scr_tcc[x];
638 /* Handle unchanged grids */
639 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc))
641 /* Flush */
642 if (fn)
644 /* Draw pending attr/char pairs */
645 (void)((*Term->pict_hook)(fx, y, fn, &scr_aa[fx], &scr_cc[fx],
646 &scr_taa[fx], &scr_tcc[fx]));
648 /* Forget */
649 fn = 0;
652 /* Skip */
653 continue;
656 /* Save new contents */
657 old_aa[x] = na;
658 old_cc[x] = nc;
660 old_taa[x] = nta;
661 old_tcc[x] = ntc;
663 /* Restart and Advance */
664 if (fn++ == 0) fx = x;
667 /* Flush */
668 if (fn)
670 /* Draw pending attr/char pairs */
671 (void)((*Term->pict_hook)(fx, y, fn, &scr_aa[fx], &scr_cc[fx],
672 &scr_taa[fx], &scr_tcc[fx]));
679 * Flush a row of the current window (see "Term_fresh")
681 * Display text using "Term_text()" and "Term_wipe()",
682 * but use "Term_pict()" for high-bit attr/char pairs
684 static void Term_fresh_row_both(int y, int x1, int x2)
686 int x;
688 byte *old_aa = Term->old->a[y];
689 char *old_cc = Term->old->c[y];
690 byte *scr_aa = Term->scr->a[y];
691 char *scr_cc = Term->scr->c[y];
693 byte *old_taa = Term->old->ta[y];
694 char *old_tcc = Term->old->tc[y];
695 byte *scr_taa = Term->scr->ta[y];
696 char *scr_tcc = Term->scr->tc[y];
698 byte ota;
699 char otc;
700 byte nta;
701 char ntc;
703 /* The "always_text" flag */
704 int always_text = Term->always_text;
706 /* Pending length */
707 int fn = 0;
709 /* Pending start */
710 int fx = 0;
712 /* Pending attr */
713 byte fa = Term->attr_blank;
715 byte oa;
716 char oc;
718 byte na;
719 char nc;
721 /* Scan "modified" columns */
722 for (x = x1; x <= x2; x++)
724 /* See what is currently here */
725 oa = old_aa[x];
726 oc = old_cc[x];
728 /* See what is desired there */
729 na = scr_aa[x];
730 nc = scr_cc[x];
732 ota = old_taa[x];
733 otc = old_tcc[x];
735 nta = scr_taa[x];
736 ntc = scr_tcc[x];
738 /* Handle unchanged grids */
739 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc))
741 /* Flush */
742 if (fn)
744 /* Draw pending chars (normal) */
745 if (fa || always_text)
747 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
750 /* Draw pending chars (black) */
751 else
753 (void)((*Term->wipe_hook)(fx, y, fn));
756 /* Forget */
757 fn = 0;
760 /* Skip */
761 continue;
764 /* Save new contents */
765 old_aa[x] = na;
766 old_cc[x] = nc;
767 old_taa[x] = nta;
768 old_tcc[x] = ntc;
770 /* Handle high-bit attr/chars */
771 if ((na & 0x80) && (nc & 0x80))
773 /* 2nd byte of bigtile */
774 if ((na == 255) && (nc == (char) -1)) continue;
776 /* Flush */
777 if (fn)
779 /* Draw pending chars (normal) */
780 if (fa || always_text)
782 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
785 /* Draw pending chars (black) */
786 else
788 (void)((*Term->wipe_hook)(fx, y, fn));
791 /* Forget */
792 fn = 0;
795 /* Hack -- Draw the special attr/char pair */
796 (void)((*Term->pict_hook)(x, y, 1, &na, &nc, &nta, &ntc));
798 /* Skip */
799 continue;
802 /* Notice new color */
803 if (fa != na)
805 /* Flush */
806 if (fn)
808 /* Draw the pending chars */
809 if (fa || always_text)
811 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
814 /* Hack -- Erase "leading" spaces */
815 else
817 (void)((*Term->wipe_hook)(fx, y, fn));
820 /* Forget */
821 fn = 0;
824 /* Save the new color */
825 fa = na;
828 /* Restart and Advance */
829 if (fn++ == 0) fx = x;
832 /* Flush */
833 if (fn)
835 /* Draw pending chars (normal) */
836 if (fa || always_text)
838 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
841 /* Draw pending chars (black) */
842 else
844 (void)((*Term->wipe_hook)(fx, y, fn));
851 * Flush a row of the current window (see "Term_fresh")
853 * Display text using "Term_text()" and "Term_wipe()"
855 static void Term_fresh_row_text(int y, int x1, int x2)
857 int x;
859 byte *old_aa = Term->old->a[y];
860 char *old_cc = Term->old->c[y];
862 byte *scr_aa = Term->scr->a[y];
863 char *scr_cc = Term->scr->c[y];
865 /* The "always_text" flag */
866 int always_text = Term->always_text;
868 /* Pending length */
869 int fn = 0;
871 /* Pending start */
872 int fx = 0;
874 /* Pending attr */
875 byte fa = Term->attr_blank;
877 byte oa;
878 char oc;
880 byte na;
881 char nc;
884 /* Scan "modified" columns */
885 for (x = x1; x <= x2; x++)
887 /* See what is currently here */
888 oa = old_aa[x];
889 oc = old_cc[x];
891 /* See what is desired there */
892 na = scr_aa[x];
893 nc = scr_cc[x];
895 /* Handle unchanged grids */
896 if ((na == oa) && (nc == oc))
898 /* Flush */
899 if (fn)
901 /* Draw pending chars (normal) */
902 if (fa || always_text)
904 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
907 /* Draw pending chars (black) */
908 else
910 (void)((*Term->wipe_hook)(fx, y, fn));
913 /* Forget */
914 fn = 0;
917 /* Skip */
918 continue;
921 /* Save new contents */
922 old_aa[x] = na;
923 old_cc[x] = nc;
925 /* Notice new color */
926 if (fa != na)
928 /* Flush */
929 if (fn)
931 /* Draw the pending chars */
932 if (fa || always_text)
934 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
937 /* Hack -- Erase "leading" spaces */
938 else
940 (void)((*Term->wipe_hook)(fx, y, fn));
943 /* Forget */
944 fn = 0;
947 /* Save the new color */
948 fa = na;
951 /* Restart and Advance */
952 if (fn++ == 0) fx = x;
955 /* Flush */
956 if (fn)
958 /* Draw pending chars (normal) */
959 if (fa || always_text)
961 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
964 /* Draw pending chars (black) */
965 else
967 (void)((*Term->wipe_hook)(fx, y, fn));
977 * Actually perform all requested changes to the window
979 * If absolutely nothing has changed, not even temporarily, or if the
980 * current "Term" is not mapped, then this function will return 1 and
981 * do absolutely nothing.
983 * Note that when "soft_cursor" is true, we erase the cursor (if needed)
984 * whenever anything has changed, and redraw it (if needed) after all of
985 * the screen updates are complete. This will induce a small amount of
986 * "cursor flicker" but only when the screen has been updated. If the
987 * screen is updated and then restored, you may still get this flicker.
989 * When "soft_cursor" is not true, we make the cursor invisible before
990 * doing anything else if it is supposed to be invisible by the time we
991 * are done, and we make it visible after moving it to its final location
992 * after all of the screen updates are complete.
994 * Note that "Term_xtra(TERM_XTRA_CLEAR,0)" must erase the entire screen,
995 * including the cursor, if needed, and may place the cursor anywhere.
997 * Note that "Term_xtra(TERM_XTRA_FROSH,y)" will be always be called
998 * after any row "y" has been "flushed", unless the "Term->never_frosh"
999 * flag is set, and "Term_xtra(TERM_XTRA_FRESH,0)" will be called after
1000 * all of the rows have been "flushed".
1002 * Note the use of three different functions to handle the actual flush,
1003 * based on the settings of the "Term->always_pict" and "Term->higher_pict"
1004 * flags (see below).
1006 * The three helper functions (above) work by collecting similar adjacent
1007 * grids into stripes, and then sending each stripe to "Term->pict_hook",
1008 * "Term->text_hook", or "Term->wipe_hook", based on the settings of the
1009 * "Term->always_pict" and "Term->higher_pict" flags, which select which
1010 * of the helper functions to call to flush each row.
1012 * The helper functions currently "skip" any grids which already contain
1013 * the desired contents. This may or may not be the best method, especially
1014 * when the desired content fits nicely into the current stripe. For example,
1015 * it might be better to go ahead and queue them while allowed, but keep a
1016 * count of the "trailing skipables", then, when time to flush, or when a
1017 * "non skippable" is found, force a flush if there are too many skippables.
1019 * Perhaps an "initialization" stage, where the "text" (and "attr")
1020 * buffers are "filled" with information, converting "blanks" into
1021 * a convenient representation, and marking "skips" with "zero chars",
1022 * and then some "processing" is done to determine which chars to skip.
1024 * Currently, the helper functions are optimal for systems which prefer
1025 * to "print a char + move a char + print a char" to "print three chars",
1026 * and for applications that do a lot of "detailed" color printing.
1028 * In the two "queue" functions, total "non-changes" are "pre-skipped".
1029 * The helper functions must also handle situations in which the contents
1030 * of a grid are changed, but then changed back to the original value,
1031 * and situations in which two grids in the same row are changed, but
1032 * the grids between them are unchanged.
1034 * If the "Term->always_pict" flag is set, then "Term_fresh_row_pict()"
1035 * will be used instead of "Term_fresh_row_text()". This allows all the
1036 * modified grids to be collected into stripes of attr/char pairs, which
1037 * are then sent to the "Term->pict_hook" hook, which can draw these pairs
1038 * in whatever way it would like.
1040 * If the "Term->higher_pict" flag is set, then "Term_fresh_row_both()"
1041 * will be used instead of "Term_fresh_row_text()". This allows all the
1042 * "special" attr/char pairs (in which both the attr and char have the
1043 * high-bit set) to be sent (one pair at a time) to the "Term->pict_hook"
1044 * hook, which can draw these pairs in whatever way it would like.
1046 * Normally, the "Term_wipe()" function is used only to display "blanks"
1047 * that were induced by "Term_clear()" or "Term_erase()", and then only
1048 * if the "attr_blank" and "char_blank" fields have not been redefined
1049 * to use "white space" instead of the default "black space". Actually,
1050 * the "Term_wipe()" function is used to display all "black" text, such
1051 * as the default "spaces" created by "Term_clear()" and "Term_erase()".
1053 * Note that the "Term->always_text" flag will disable the use of the
1054 * "Term_wipe()" function hook entirely, and force all text, even text
1055 * drawn in the color "black", to be explicitly drawn. This is useful
1056 * for machines which implement "Term_wipe()" by just drawing spaces.
1058 * Note that the "Term->always_pict" flag will disable the use of the
1059 * "Term_wipe()" function entirely, and force everything, even text
1060 * drawn in the attr "black", to be explicitly drawn.
1062 * Note that if no "black" text is ever drawn, and if "attr_blank" is
1063 * not "zero", then the "Term_wipe" hook will never be used, even if
1064 * the "Term->always_text" flag is not set.
1066 * This function does nothing unless the "Term" is "mapped", which allows
1067 * certain systems to optimize the handling of "closed" windows.
1069 * On systems with a "soft" cursor, we must explicitly erase the cursor
1070 * before flushing the output, if needed, to prevent a "jumpy" refresh.
1071 * The actual method for this is horrible, but there is very little that
1072 * we can do to simplify it efficiently. XXX XXX XXX
1074 * On systems with a "hard" cursor, we will "hide" the cursor before
1075 * flushing the output, if needed, to avoid a "flickery" refresh. It
1076 * would be nice to *always* hide the cursor during the refresh, but
1077 * this might be expensive (and/or ugly) on some machines.
1079 * The "Term->icky_corner" flag is used to avoid calling "Term_wipe()"
1080 * or "Term_pict()" or "Term_text()" on the bottom right corner of the
1081 * window, which might induce "scrolling" or other nasty stuff on old
1082 * dumb terminals. This flag is handled very efficiently. We assume
1083 * that the "Term_curs()" call will prevent placing the cursor in the
1084 * corner, if needed, though I doubt such placement is ever a problem.
1085 * Currently, the use of "Term->icky_corner" and "Term->soft_cursor"
1086 * together may result in undefined behavior.
1088 errr Term_fresh(void)
1090 int x, y;
1092 int w = Term->wid;
1093 int h = Term->hgt;
1095 int y1 = Term->y1;
1096 int y2 = Term->y2;
1098 term_win *old = Term->old;
1099 term_win *scr = Term->scr;
1102 /* Do nothing unless "mapped" */
1103 if (!Term->mapped_flag) return (1);
1106 /* Trivial Refresh */
1107 if ((y1 > y2) &&
1108 (scr->cu == old->cu) &&
1109 (scr->cv == old->cv) &&
1110 (scr->cx == old->cx) &&
1111 (scr->cy == old->cy) &&
1112 !(Term->total_erase))
1114 /* Nothing */
1115 return (1);
1119 /* Paranoia -- use "fake" hooks to prevent core dumps */
1120 if (!Term->curs_hook) Term->curs_hook = Term_curs_hack;
1121 if (!Term->bigcurs_hook) Term->bigcurs_hook = Term->curs_hook;
1122 if (!Term->wipe_hook) Term->wipe_hook = Term_wipe_hack;
1123 if (!Term->text_hook) Term->text_hook = Term_text_hack;
1124 if (!Term->pict_hook) Term->pict_hook = Term_pict_hack;
1127 /* Handle "total erase" */
1128 if (Term->total_erase)
1130 byte na = Term->attr_blank;
1131 char nc = Term->char_blank;
1133 /* Physically erase the entire window */
1134 Term_xtra(TERM_XTRA_CLEAR, 0);
1136 /* Hack -- clear all "cursor" data */
1137 old->cv = old->cu = FALSE;
1138 old->cx = old->cy = 0;
1140 /* Wipe each row */
1141 for (y = 0; y < h; y++)
1143 byte *aa = old->a[y];
1144 char *cc = old->c[y];
1145 byte *taa = old->ta[y];
1146 char *tcc = old->tc[y];
1148 /* Wipe each column */
1149 for (x = 0; x < w; x++)
1151 /* Wipe each grid */
1152 *aa++ = na;
1153 *cc++ = nc;
1155 *taa++ = na;
1156 *tcc++ = nc;
1160 /* Redraw every row */
1161 Term->y1 = y1 = 0;
1162 Term->y2 = y2 = h - 1;
1164 /* Redraw every column */
1165 for (y = 0; y < h; y++)
1167 Term->x1[y] = 0;
1168 Term->x2[y] = w - 1;
1171 /* Forget "total erase" */
1172 Term->total_erase = FALSE;
1176 /* Cursor update -- Erase old Cursor */
1177 if (Term->soft_cursor)
1179 /* Cursor was visible */
1180 if (!old->cu && old->cv)
1182 int tx = old->cx;
1183 int ty = old->cy;
1185 byte *old_aa = old->a[ty];
1186 char *old_cc = old->c[ty];
1188 byte oa = old_aa[tx];
1189 char oc = old_cc[tx];
1191 byte *old_taa = old->ta[ty];
1192 char *old_tcc = old->tc[ty];
1194 byte ota = old_taa[tx];
1195 char otc = old_tcc[tx];
1197 /* Hack -- use "Term_pict()" always */
1198 if (Term->always_pict)
1200 (void)((*Term->pict_hook)(tx, ty, 1, &oa, &oc, &ota, &otc));
1203 /* Hack -- use "Term_pict()" sometimes */
1204 else if (Term->higher_pict && (oa & 0x80) && (oc & 0x80))
1206 (void)((*Term->pict_hook)(tx, ty, 1, &oa, &oc, &ota, &otc));
1209 /* Hack -- restore the actual character */
1210 else if (oa || Term->always_text)
1212 (void)((*Term->text_hook)(tx, ty, 1, oa, &oc));
1215 /* Hack -- erase the grid */
1216 else
1218 (void)((*Term->wipe_hook)(tx, ty, 1));
1223 /* Cursor Update -- Erase old Cursor */
1224 else
1226 /* Cursor will be invisible */
1227 if (scr->cu || !scr->cv)
1229 /* Make the cursor invisible */
1230 Term_xtra(TERM_XTRA_SHAPE, 0);
1235 /* Something to update */
1236 if (y1 <= y2)
1238 /* Handle "icky corner" */
1239 if (Term->icky_corner)
1241 /* Avoid the corner */
1242 if (y2 >= h - 1)
1244 /* Avoid the corner */
1245 if (Term->x2[h - 1] > w - 2)
1247 /* Avoid the corner */
1248 Term->x2[h - 1] = w - 2;
1254 /* Scan the "modified" rows */
1255 for (y = y1; y <= y2; ++y)
1257 int x1 = Term->x1[y];
1258 int x2 = Term->x2[y];
1260 /* Flush each "modified" row */
1261 if (x1 <= x2)
1263 /* Always use "Term_pict()" */
1264 if (Term->always_pict)
1266 /* Flush the row */
1267 Term_fresh_row_pict(y, x1, x2);
1270 /* Sometimes use "Term_pict()" */
1271 else if (Term->higher_pict)
1273 /* Flush the row */
1274 Term_fresh_row_both(y, x1, x2);
1277 /* Never use "Term_pict()" */
1278 else
1280 /* Flush the row */
1281 Term_fresh_row_text(y, x1, x2);
1284 /* This row is all done */
1285 Term->x1[y] = w;
1286 Term->x2[y] = 0;
1288 /* Hack -- Flush that row (if allowed) */
1289 if (!Term->never_frosh) Term_xtra(TERM_XTRA_FROSH, y);
1293 /* No rows are invalid */
1294 Term->y1 = h;
1295 Term->y2 = 0;
1299 /* Cursor update -- Show new Cursor */
1300 if (Term->soft_cursor)
1302 /* Draw the cursor */
1303 if (!scr->cu && scr->cv)
1305 if ((scr->cx + 1 < w) && (old->a[scr->cy][scr->cx + 1] == 255))
1307 /* Double width cursor for the Bigtile mode */
1308 (void)((*Term->bigcurs_hook)(scr->cx, scr->cy));
1310 else
1312 /* Call the cursor display routine */
1313 (void)((*Term->curs_hook)(scr->cx, scr->cy));
1318 /* Cursor Update -- Show new Cursor */
1319 else
1321 /* The cursor is useless, hide it */
1322 if (scr->cu)
1324 /* Paranoia -- Put the cursor NEAR where it belongs */
1325 (void)((*Term->curs_hook)(w - 1, scr->cy));
1327 /* Make the cursor invisible */
1328 /* Term_xtra(TERM_XTRA_SHAPE, 0); */
1331 /* The cursor is invisible, hide it */
1332 else if (!scr->cv)
1334 /* Paranoia -- Put the cursor where it belongs */
1335 (void)((*Term->curs_hook)(scr->cx, scr->cy));
1337 /* Make the cursor invisible */
1338 /* Term_xtra(TERM_XTRA_SHAPE, 0); */
1341 /* The cursor is visible, display it correctly */
1342 else
1344 /* Put the cursor where it belongs */
1345 (void)((*Term->curs_hook)(scr->cx, scr->cy));
1347 /* Make the cursor visible */
1348 Term_xtra(TERM_XTRA_SHAPE, 1);
1353 /* Save the "cursor state" */
1354 old->cu = scr->cu;
1355 old->cv = scr->cv;
1356 old->cx = scr->cx;
1357 old->cy = scr->cy;
1360 /* Actually flush the output */
1361 Term_xtra(TERM_XTRA_FRESH, 0);
1364 /* Success */
1365 return (0);
1370 /*** Output routines ***/
1374 * Set the cursor visibility
1376 errr Term_set_cursor(bool v)
1378 /* Already done */
1379 if (Term->scr->cv == v) return (1);
1381 /* Change */
1382 Term->scr->cv = v;
1384 /* Success */
1385 return (0);
1390 * Place the cursor at a given location
1392 * Note -- "illegal" requests do not move the cursor.
1394 errr Term_gotoxy(int x, int y)
1396 int w = Term->wid;
1397 int h = Term->hgt;
1399 /* Verify */
1400 if ((x < 0) || (x >= w)) return (-1);
1401 if ((y < 0) || (y >= h)) return (-1);
1403 /* Remember the cursor */
1404 Term->scr->cx = x;
1405 Term->scr->cy = y;
1407 /* The cursor is not useless */
1408 Term->scr->cu = 0;
1410 /* Success */
1411 return (0);
1416 * At a given location, place an attr/char
1417 * Do not change the cursor position
1418 * No visual changes until "Term_fresh()".
1420 errr Term_draw(int x, int y, byte a, char c)
1422 int w = Term->wid;
1423 int h = Term->hgt;
1425 /* Verify location */
1426 if ((x < 0) || (x >= w)) return (-1);
1427 if ((y < 0) || (y >= h)) return (-1);
1429 /* Paranoia -- illegal char */
1430 if (!c) return (-2);
1432 /* Queue it for later */
1433 Term_queue_char(Term, x, y, a, c, 0, 0);
1435 /* Success */
1436 return (0);
1441 * Using the given attr, add the given char at the cursor.
1443 * We return "-2" if the character is "illegal". XXX XXX
1445 * We return "-1" if the cursor is currently unusable.
1447 * We queue the given attr/char for display at the current
1448 * cursor location, and advance the cursor to the right,
1449 * marking it as unuable and returning "1" if it leaves
1450 * the screen, and otherwise returning "0".
1452 * So when this function, or the following one, return a
1453 * positive value, future calls to either function will
1454 * return negative ones.
1456 errr Term_addch(byte a, char c)
1458 int w = Term->wid;
1460 /* Handle "unusable" cursor */
1461 if (Term->scr->cu) return (-1);
1463 /* Paranoia -- no illegal chars */
1464 if (!c) return (-2);
1466 /* Queue the given character for display */
1467 Term_queue_char(Term, Term->scr->cx, Term->scr->cy, a, c, 0, 0);
1469 /* Advance the cursor */
1470 Term->scr->cx++;
1472 /* Success */
1473 if (Term->scr->cx < w) return (0);
1475 /* Note "Useless" cursor */
1476 Term->scr->cu = 1;
1478 /* Note "Useless" cursor */
1479 return (1);
1484 * At the current location, using an attr, add a string
1486 * We also take a length "n", using negative values to imply
1487 * the largest possible value, and then we use the minimum of
1488 * this length and the "actual" length of the string as the
1489 * actual number of characters to attempt to display, never
1490 * displaying more characters than will actually fit, since
1491 * we do NOT attempt to "wrap" the cursor at the screen edge.
1493 * We return "-1" if the cursor is currently unusable.
1494 * We return "N" if we were "only" able to write "N" chars,
1495 * even if all of the given characters fit on the screen,
1496 * and mark the cursor as unusable for future attempts.
1498 * So when this function, or the preceding one, return a
1499 * positive value, future calls to either function will
1500 * return negative ones.
1502 errr Term_addstr(int n, byte a, cptr s)
1504 int k;
1506 int w = Term->wid;
1508 errr res = 0;
1510 /* Handle "unusable" cursor */
1511 if (Term->scr->cu) return (-1);
1513 /* Obtain maximal length */
1514 k = (n < 0) ? (w + 1) : n;
1516 /* Obtain the usable string length */
1517 for (n = 0; (n < k) && s[n]; n++) /* loop */;
1519 /* React to reaching the edge of the screen */
1520 if (Term->scr->cx + n >= w) res = n = w - Term->scr->cx;
1522 /* Queue the first "n" characters for display */
1523 Term_queue_chars(Term->scr->cx, Term->scr->cy, n, a, s);
1525 /* Advance the cursor */
1526 Term->scr->cx += n;
1528 /* Hack -- Notice "Useless" cursor */
1529 if (res) Term->scr->cu = 1;
1531 /* Success (usually) */
1532 return (res);
1537 * Move to a location and, using an attr, add a char
1539 errr Term_putch(int x, int y, byte a, char c)
1541 errr res;
1543 /* Move first */
1544 if ((res = Term_gotoxy(x, y)) != 0) return (res);
1546 /* Then add the char */
1547 if ((res = Term_addch(a, c)) != 0) return (res);
1549 /* Success */
1550 return (0);
1555 * Move to a location and, using an attr, add a string
1557 errr Term_putstr(int x, int y, int n, byte a, cptr s)
1559 errr res;
1561 /* Move first */
1562 if ((res = Term_gotoxy(x, y)) != 0) return (res);
1564 /* Then add the string */
1565 if ((res = Term_addstr(n, a, s)) != 0) return (res);
1567 /* Success */
1568 return (0);
1574 * Place cursor at (x,y), and clear the next "n" chars
1576 errr Term_erase(int x, int y, int n)
1578 int i;
1580 int w = Term->wid;
1581 /* int h = Term->hgt; */
1583 int x1 = -1;
1584 int x2 = -1;
1586 byte na = Term->attr_blank;
1587 char nc = Term->char_blank;
1589 byte *scr_aa;
1590 char *scr_cc;
1592 byte *scr_taa;
1593 char *scr_tcc;
1595 /* Place cursor */
1596 if (Term_gotoxy(x, y)) return (-1);
1598 /* Force legal size */
1599 if (x + n > w) n = w - x;
1601 /* Fast access */
1602 scr_aa = Term->scr->a[y];
1603 scr_cc = Term->scr->c[y];
1605 scr_taa = Term->scr->ta[y];
1606 scr_tcc = Term->scr->tc[y];
1608 if ((n > 0) && (scr_cc[x] == (char) -1) && (scr_aa[x] == 255))
1610 x--;
1611 n++;
1614 /* Scan every column */
1615 for (i = 0; i < n; i++, x++)
1617 byte oa = scr_aa[x];
1618 char oc = scr_cc[x];
1620 /* Hack -- Ignore "non-changes" */
1621 if ((oa == na) && (oc == nc)) continue;
1623 /* Save the "literal" information */
1624 scr_aa[x] = na;
1625 scr_cc[x] = nc;
1627 scr_taa[x] = 0;
1628 scr_tcc[x] = 0;
1630 /* Track minumum changed column */
1631 if (x1 < 0) x1 = x;
1633 /* Track maximum changed column */
1634 x2 = x;
1637 /* Expand the "change area" as needed */
1638 if (x1 >= 0)
1640 /* Check for new min/max row info */
1641 if (y < Term->y1) Term->y1 = y;
1642 if (y > Term->y2) Term->y2 = y;
1644 /* Check for new min/max col info in this row */
1645 if (x1 < Term->x1[y]) Term->x1[y] = x1;
1646 if (x2 > Term->x2[y]) Term->x2[y] = x2;
1649 /* Success */
1650 return (0);
1655 * Clear the entire window, and move to the top left corner
1657 * Note the use of the special "total_erase" code
1659 errr Term_clear(void)
1661 int x, y;
1663 int w = Term->wid;
1664 int h = Term->hgt;
1666 byte na = Term->attr_blank;
1667 char nc = Term->char_blank;
1669 /* Cursor usable */
1670 Term->scr->cu = 0;
1672 /* Cursor to the top left */
1673 Term->scr->cx = Term->scr->cy = 0;
1675 /* Wipe each row */
1676 for (y = 0; y < h; y++)
1678 byte *scr_aa = Term->scr->a[y];
1679 char *scr_cc = Term->scr->c[y];
1680 byte *scr_taa = Term->scr->ta[y];
1681 char *scr_tcc = Term->scr->tc[y];
1683 /* Wipe each column */
1684 for (x = 0; x < w; x++)
1686 scr_aa[x] = na;
1687 scr_cc[x] = nc;
1689 scr_taa[x] = 0;
1690 scr_tcc[x] = 0;
1693 /* This row has changed */
1694 Term->x1[y] = 0;
1695 Term->x2[y] = w - 1;
1698 /* Every row has changed */
1699 Term->y1 = 0;
1700 Term->y2 = h - 1;
1702 /* Force "total erase" */
1703 Term->total_erase = TRUE;
1705 /* Success */
1706 return (0);
1714 * Redraw (and refresh) the whole window.
1716 errr Term_redraw(void)
1718 /* Force "total erase" */
1719 Term->total_erase = TRUE;
1721 /* Hack -- Refresh */
1722 Term_fresh();
1724 /* Success */
1725 return (0);
1730 * Redraw part of a widow.
1732 errr Term_redraw_section(int x1, int y1, int x2, int y2)
1734 int i, j;
1736 char *c_ptr;
1738 /* Bounds checking */
1739 if (y2 >= Term->hgt) y2 = Term->hgt - 1;
1740 if (x2 >= Term->wid) x2 = Term->wid - 1;
1741 if (y1 < 0) y1 = 0;
1742 if (x1 < 0) x1 = 0;
1745 /* Set y limits */
1746 Term->y1 = y1;
1747 Term->y2 = y2;
1749 /* Set the x limits */
1750 for (i = Term->y1; i <= Term->y2; i++)
1752 if ((x1 > 0) && (Term->old->a[i][x1] == 255))
1753 x1--;
1755 Term->x1[i] = x1;
1756 Term->x2[i] = x2;
1758 c_ptr = Term->old->c[i];
1760 /* Clear the section so it is redrawn */
1761 for (j = x1; j <= x2; j++)
1763 /* Hack - set the old character to "none" */
1764 c_ptr[j] = 0;
1768 /* Hack -- Refresh */
1769 Term_fresh();
1771 /* Success */
1772 return (0);
1779 /*** Access routines ***/
1783 * Extract the cursor visibility
1785 errr Term_get_cursor(bool *v)
1787 /* Extract visibility */
1788 (*v) = Term->scr->cv;
1790 /* Success */
1791 return (0);
1796 * Extract the current window size
1798 errr Term_get_size(int *w, int *h)
1800 /* Access the cursor */
1801 (*w) = Term->wid;
1802 (*h) = Term->hgt;
1804 /* Success */
1805 return (0);
1810 * Extract the current cursor location
1812 errr Term_locate(int *x, int *y)
1814 /* Access the cursor */
1815 (*x) = Term->scr->cx;
1816 (*y) = Term->scr->cy;
1818 /* Warn about "useless" cursor */
1819 if (Term->scr->cu) return (1);
1821 /* Success */
1822 return (0);
1827 * At a given location, determine the "current" attr and char
1828 * Note that this refers to what will be on the window after the
1829 * next call to "Term_fresh()". It may or may not already be there.
1831 errr Term_what(int x, int y, byte *a, char *c)
1833 int w = Term->wid;
1834 int h = Term->hgt;
1836 /* Verify location */
1837 if ((x < 0) || (x >= w)) return (-1);
1838 if ((y < 0) || (y >= h)) return (-1);
1840 /* Direct access */
1841 (*a) = Term->scr->a[y][x];
1842 (*c) = Term->scr->c[y][x];
1844 /* Success */
1845 return (0);
1850 /*** Input routines ***/
1854 * Flush and forget the input
1856 errr Term_flush(void)
1858 /* Hack -- Flush all events */
1859 Term_xtra(TERM_XTRA_FLUSH, 0);
1861 /* Forget all keypresses */
1862 Term->key_head = Term->key_tail = 0;
1864 /* Success */
1865 return (0);
1870 * Add a keypress to the "queue"
1872 errr Term_keypress(int k)
1874 /* Hack -- Refuse to enqueue non-keys */
1875 if (!k) return (-1);
1877 /* Store the char, advance the queue */
1878 Term->key_queue[Term->key_head].key = k;
1879 Term->key_queue[Term->key_head].index = 0;
1880 Term->key_queue[Term->key_head].type = EVT_KBRD;
1881 Term->key_head++;
1883 /* Circular queue, handle wrap */
1884 if (Term->key_head == Term->key_size) Term->key_head = 0;
1886 /* Success (unless overflow) */
1887 if (Term->key_head != Term->key_tail) return (0);
1889 /* Problem */
1890 return (1);
1894 * Add a mouse event to the "queue"
1896 errr Term_mousepress(int x, int y, char button)
1898 /* Store the char, advance the queue */
1899 Term->key_queue[Term->key_head].key = '\xff';
1900 Term->key_queue[Term->key_head].mousex = x;
1901 Term->key_queue[Term->key_head].mousey = y;
1902 Term->key_queue[Term->key_head].index = button;
1903 Term->key_queue[Term->key_head].type = EVT_MOUSE;
1904 Term->key_head++;
1906 /* Circular queue, handle wrap */
1907 if (Term->key_head == Term->key_size) Term->key_head = 0;
1909 /* Success (unless overflow) */
1910 if (Term->key_head != Term->key_tail) return (0);
1912 #if 0
1913 /* Hack -- Forget the oldest key */
1914 if (++Term->key_tail == Term->key_size) Term->key_tail = 0;
1915 #endif
1917 /* Problem */
1918 return (1);
1923 * Add a keypress to the FRONT of the "queue"
1925 errr Term_key_push(int k)
1927 event_type ke;
1929 if (!k) return (-1);
1931 ke.type = EVT_KBRD;
1932 ke.index = 0;
1933 ke.key = k;
1935 return Term_event_push(&ke);
1938 errr Term_event_push(const event_type *ke)
1940 /* Hack -- Refuse to enqueue non-keys */
1941 if (!ke) return (-1);
1943 /* Hack -- Overflow may induce circular queue */
1944 if (Term->key_tail == 0) Term->key_tail = Term->key_size;
1946 /* Back up, Store the char */
1947 /* Store the char, advance the queue */
1948 Term->key_queue[--Term->key_tail] = *ke;
1950 /* Success (unless overflow) */
1951 if (Term->key_head != Term->key_tail) return (0);
1953 #if 0
1954 /* Hack -- Forget the oldest key */
1955 if (++Term->key_tail == Term->key_size) Term->key_tail = 0;
1956 #endif
1958 /* Problem */
1959 return (1);
1967 * Check for a pending keypress on the key queue.
1969 * Store the keypress, if any, in "ch", and return "0".
1970 * Otherwise store "zero" in "ch", and return "1".
1972 * Wait for a keypress if "wait" is true.
1974 * Remove the keypress if "take" is true.
1976 errr Term_inkey(event_type *ch, bool wait, bool take)
1978 /* Assume no key */
1979 ch->type = ch->key = 0;
1981 /* Hack -- get bored */
1982 if (!Term->never_bored)
1984 /* Process random events */
1985 Term_xtra(TERM_XTRA_BORED, 0);
1988 /* Wait */
1989 if (wait)
1991 /* Process pending events while necessary */
1992 while (Term->key_head == Term->key_tail)
1994 /* Process events (wait for one) */
1995 Term_xtra(TERM_XTRA_EVENT, TRUE);
1999 /* Do not Wait */
2000 else
2002 /* Process pending events if necessary */
2003 if (Term->key_head == Term->key_tail)
2005 /* Process events (do not wait) */
2006 Term_xtra(TERM_XTRA_EVENT, FALSE);
2010 /* No keys are ready */
2011 if (Term->key_head == Term->key_tail) return (1);
2013 /* Extract the next keypress */
2014 (*ch) = Term->key_queue[Term->key_tail];
2016 /* If requested, advance the queue, wrap around if necessary */
2017 if (take && (++Term->key_tail == Term->key_size)) Term->key_tail = 0;
2019 /* Success */
2020 return (0);
2025 /*** Extra routines ***/
2028 * Save the "requested" screen into the "memorized" screen
2030 * Every "Term_save()" should match exactly one "Term_load()"
2032 errr Term_save(void)
2034 int w = Term->wid;
2035 int h = Term->hgt;
2037 term_win *mem;
2039 /* Allocate window */
2040 MAKE(mem, term_win);
2042 /* Initialize window */
2043 term_win_init(mem, w, h);
2045 /* Grab */
2046 term_win_copy(mem, Term->scr, w, h);
2048 /* Front of the queue */
2049 mem->next = Term->mem;
2050 Term->mem = mem;
2052 /* Success */
2053 return (0);
2058 * Restore the "requested" contents (see above).
2060 * Every "Term_save()" should match exactly one "Term_load()"
2062 errr Term_load(void)
2064 int y;
2066 int w = Term->wid;
2067 int h = Term->hgt;
2069 term_win *tmp;
2071 /* Pop off window from the list */
2072 if (Term->mem)
2074 /* Save pointer to old mem */
2075 tmp = Term->mem;
2077 /* Forget it */
2078 Term->mem = Term->mem->next;
2080 /* Load */
2081 term_win_copy(Term->scr, tmp, w, h);
2083 /* Free the old window */
2084 (void)term_win_nuke(tmp);
2087 /* Assume change */
2088 for (y = 0; y < h; y++)
2090 /* Assume change */
2091 Term->x1[y] = 0;
2092 Term->x2[y] = w - 1;
2095 /* Assume change */
2096 Term->y1 = 0;
2097 Term->y2 = h - 1;
2099 /* Success */
2100 return (0);
2106 * React to a new physical window size.
2108 errr Term_resize(int w, int h)
2110 int i;
2112 int wid, hgt;
2114 byte *hold_x1;
2115 byte *hold_x2;
2117 term_win *hold_old;
2118 term_win *hold_scr;
2119 term_win *hold_mem;
2120 term_win *hold_tmp;
2122 event_type evt = { EVT_RESIZE, 0, 0, 0, 0 };
2125 /* Resizing is forbidden */
2126 if (Term->fixed_shape) return (-1);
2129 /* Ignore illegal changes */
2130 if ((w < 1) || (h < 1)) return (-1);
2133 /* Ignore non-changes */
2134 if ((Term->wid == w) && (Term->hgt == h)) return (1);
2137 /* Minimum dimensions */
2138 wid = MIN(Term->wid, w);
2139 hgt = MIN(Term->hgt, h);
2141 /* Save scanners */
2142 hold_x1 = Term->x1;
2143 hold_x2 = Term->x2;
2145 /* Save old window */
2146 hold_old = Term->old;
2148 /* Save old window */
2149 hold_scr = Term->scr;
2151 /* Save old window */
2152 hold_mem = Term->mem;
2154 /* Save old window */
2155 hold_tmp = Term->tmp;
2157 /* Create new scanners */
2158 C_MAKE(Term->x1, h, byte);
2159 C_MAKE(Term->x2, h, byte);
2161 /* Create new window */
2162 MAKE(Term->old, term_win);
2164 /* Initialize new window */
2165 term_win_init(Term->old, w, h);
2167 /* Save the contents */
2168 term_win_copy(Term->old, hold_old, wid, hgt);
2170 /* Create new window */
2171 MAKE(Term->scr, term_win);
2173 /* Initialize new window */
2174 term_win_init(Term->scr, w, h);
2176 /* Save the contents */
2177 term_win_copy(Term->scr, hold_scr, wid, hgt);
2179 /* If needed */
2180 if (hold_mem)
2182 /* Create new window */
2183 MAKE(Term->mem, term_win);
2185 /* Initialize new window */
2186 term_win_init(Term->mem, w, h);
2188 /* Save the contents */
2189 term_win_copy(Term->mem, hold_mem, wid, hgt);
2192 /* If needed */
2193 if (hold_tmp)
2195 /* Create new window */
2196 MAKE(Term->tmp, term_win);
2198 /* Initialize new window */
2199 term_win_init(Term->tmp, w, h);
2201 /* Save the contents */
2202 term_win_copy(Term->tmp, hold_tmp, wid, hgt);
2205 /* Free some arrays */
2206 FREE(hold_x1);
2207 FREE(hold_x2);
2209 /* Nuke */
2210 term_win_nuke(hold_old);
2212 /* Kill */
2213 FREE(hold_old);
2215 /* Illegal cursor */
2216 if (Term->old->cx >= w) Term->old->cu = 1;
2217 if (Term->old->cy >= h) Term->old->cu = 1;
2219 /* Nuke */
2220 term_win_nuke(hold_scr);
2222 /* Kill */
2223 FREE(hold_scr);
2225 /* Illegal cursor */
2226 if (Term->scr->cx >= w) Term->scr->cu = 1;
2227 if (Term->scr->cy >= h) Term->scr->cu = 1;
2229 /* If needed */
2230 if (hold_mem)
2232 /* Nuke */
2233 term_win_nuke(hold_mem);
2235 /* Kill */
2236 FREE(hold_mem);
2238 /* Illegal cursor */
2239 if (Term->mem->cx >= w) Term->mem->cu = 1;
2240 if (Term->mem->cy >= h) Term->mem->cu = 1;
2243 /* If needed */
2244 if (hold_tmp)
2246 /* Nuke */
2247 term_win_nuke(hold_tmp);
2249 /* Kill */
2250 FREE(hold_tmp);
2252 /* Illegal cursor */
2253 if (Term->tmp->cx >= w) Term->tmp->cu = 1;
2254 if (Term->tmp->cy >= h) Term->tmp->cu = 1;
2257 /* Save new size */
2258 Term->wid = w;
2259 Term->hgt = h;
2261 /* Force "total erase" */
2262 Term->total_erase = TRUE;
2264 /* Assume change */
2265 for (i = 0; i < h; i++)
2267 /* Assume change */
2268 Term->x1[i] = 0;
2269 Term->x2[i] = w - 1;
2272 /* Assume change */
2273 Term->y1 = 0;
2274 Term->y2 = h - 1;
2276 /* Push a resize event onto the stack */
2277 Term_event_push(&evt);
2279 /* Success */
2280 return (0);
2286 * Activate a new Term (and deactivate the current Term)
2288 * This function is extremely important, and also somewhat bizarre.
2289 * It is the only function that should "modify" the value of "Term".
2291 * To "create" a valid "term", one should do "term_init(t)", then
2292 * set the various flags and hooks, and then do "Term_activate(t)".
2294 errr Term_activate(term *t)
2296 /* Hack -- already done */
2297 if (Term == t) return (1);
2299 /* Deactivate the old Term */
2300 if (Term) Term_xtra(TERM_XTRA_LEVEL, 0);
2302 /* Hack -- Call the special "init" hook */
2303 if (t && !t->active_flag)
2305 /* Call the "init" hook */
2306 if (t->init_hook) (*t->init_hook)(t);
2308 /* Remember */
2309 t->active_flag = TRUE;
2311 /* Assume mapped */
2312 t->mapped_flag = TRUE;
2315 /* Remember the Term */
2316 Term = t;
2318 /* Activate the new Term */
2319 if (Term) Term_xtra(TERM_XTRA_LEVEL, 1);
2321 /* Success */
2322 return (0);
2328 * Nuke a term
2330 errr term_nuke(term *t)
2332 /* Hack -- Call the special "nuke" hook */
2333 if (t->active_flag)
2335 /* Call the "nuke" hook */
2336 if (t->nuke_hook) (*t->nuke_hook)(t);
2338 /* Remember */
2339 t->active_flag = FALSE;
2341 /* Assume not mapped */
2342 t->mapped_flag = FALSE;
2346 /* Nuke "displayed" */
2347 term_win_nuke(t->old);
2349 /* Kill "displayed" */
2350 KILL(t->old);
2352 /* Nuke "requested" */
2353 term_win_nuke(t->scr);
2355 /* Kill "requested" */
2356 KILL(t->scr);
2358 /* If needed */
2359 if (t->mem)
2361 /* Nuke "memorized" */
2362 term_win_nuke(t->mem);
2364 /* Kill "memorized" */
2365 KILL(t->mem);
2368 /* If needed */
2369 if (t->tmp)
2371 /* Nuke "temporary" */
2372 term_win_nuke(t->tmp);
2374 /* Kill "temporary" */
2375 KILL(t->tmp);
2378 /* Free some arrays */
2379 KILL(t->x1);
2380 KILL(t->x2);
2382 /* Free the input queue */
2383 KILL(t->key_queue);
2385 /* Success */
2386 return (0);
2391 * Initialize a term, using a window of the given size.
2392 * Also prepare the "input queue" for "k" keypresses
2393 * By default, the cursor starts out "invisible"
2394 * By default, we "erase" using "black spaces"
2396 errr term_init(term *t, int w, int h, int k)
2398 int y;
2401 /* Wipe it */
2402 (void)WIPE(t, term);
2405 /* Prepare the input queue */
2406 t->key_head = t->key_tail = 0;
2408 /* Determine the input queue size */
2409 t->key_size = k;
2411 /* Allocate the input queue */
2412 C_MAKE(t->key_queue, t->key_size, event_type);
2415 /* Save the size */
2416 t->wid = w;
2417 t->hgt = h;
2419 /* Allocate change arrays */
2420 C_MAKE(t->x1, h, byte);
2421 C_MAKE(t->x2, h, byte);
2424 /* Allocate "displayed" */
2425 MAKE(t->old, term_win);
2427 /* Initialize "displayed" */
2428 term_win_init(t->old, w, h);
2431 /* Allocate "requested" */
2432 MAKE(t->scr, term_win);
2434 /* Initialize "requested" */
2435 term_win_init(t->scr, w, h);
2438 /* Assume change */
2439 for (y = 0; y < h; y++)
2441 /* Assume change */
2442 t->x1[y] = 0;
2443 t->x2[y] = w - 1;
2446 /* Assume change */
2447 t->y1 = 0;
2448 t->y2 = h - 1;
2450 /* Force "total erase" */
2451 t->total_erase = TRUE;
2454 /* Default "blank" */
2455 t->attr_blank = 0;
2456 t->char_blank = ' ';
2459 /* Success */
2460 return (0);