1 /* File: z-term.c */
2
3 /*
4 * Copyright (c) 1997 Ben Harrison
5 *
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 */
10
11 /* Purpose: a generic, efficient, terminal window package -BEN- */
12 #include "z-virt.h"
13 #include "z-term.h"
14
15 /*
16 * This file provides a generic, efficient, terminal window package,
17 * which can be used not only on standard terminal environments such
18 * as dumb terminals connected to a Unix box, but also in more modern
19 * "graphic" environments, such as the Macintosh or Unix/X11.
20 *
21 * Each "window" works like a standard "dumb terminal", that is, it
22 * can display a two dimensional array of grids containing colored
23 * textual symbols, plus an optional cursor, and it can be used to
24 * get keypress events from the user.
25 *
26 * In fact, this package can simply be used, if desired, to support
27 * programs which will look the same on a dumb terminal as they do
28 * on a graphic platform such as the Macintosh.
29 *
30 * This package was designed to help port the game "Angband" to a wide
31 * variety of different platforms. Angband, like many other games in
32 * the "rogue-like" heirarchy, requires, at the minimum, the ability
33 * to display "colored textual symbols" in a standard 80x24 "window",
34 * such as that provided by most dumb terminals, and many old personal
35 * computers, and to check for "keypresses" from the user. The major
36 * concerns were thus portability and efficiency, so Angband could be
37 * easily ported to many different systems, with minimal effort, and
38 * yet would run quickly on each of these systems, no matter what kind
39 * of underlying hardware/software support was being used.
40 *
41 * It is important to understand the differences between the older
42 * "dumb terminals" and the newer "graphic interface" machines, since
43 * this package was designed to work with both types of systems.
44 *
45 * New machines:
46 * waiting for a keypress is complex
47 * checking for a keypress is often cheap
48 * changing "colors" may be expensive
49 * the "color" of a "blank" is rarely important
50 * moving the "cursor" is relatively cheap
51 * use a "software" cursor (only moves when requested)
52 * drawing characters normally will not erase old ones
53 * drawing a character on the cursor often erases it
54 * may have fast routines for "clear a region"
55 * the bottom right corner is usually not special
56 *
57 * Old machines:
58 * waiting for a keypress is simple
59 * checking for a keypress is often expensive
60 * changing "colors" is usually cheap
61 * the "color" of a "blank" may be important
62 * moving the "cursor" may be expensive
63 * use a "hardware" cursor (moves during screen updates)
64 * drawing new symbols automatically erases old ones
65 * characters may only be drawn at the cursor location
66 * drawing a character on the cursor will move the cursor
67 * may have fast routines for "clear entire window"
68 * may have fast routines for "clear to end of line"
69 * the bottom right corner is often dangerous
70 *
71 *
72 * This package provides support for multiple windows, each of an
73 * arbitrary size (up to 255x255), each with its own set of flags,
74 * and its own hooks to handle several low-level procedures which
75 * differ from platform to platform. Then the main program simply
76 * creates one or more "term" structures, setting the various flags
77 * and hooks in a manner appropriate for the current platform, and
78 * then it can use the various "term" structures without worrying
79 * about the underlying platform.
80 *
81 *
82 * This package allows each "grid" in each window to hold an attr/char
83 * pair, with each ranging from 0 to 255, and makes very few assumptions
84 * about the meaning of any attr/char values. Normally, we assume that
85 * "attr 0" is "black", with the semantics that "black" text should be
86 * sent to "Term_wipe()" instead of "Term_text()", but this sematics is
87 * modified if either the "always_pict" or the "always_text" flags are
88 * set. We assume that "char 0" is "dangerous", since placing such a
89 * "char" in the middle of a string "terminates" the string, and usually
90 * we prevent its use.
91 *
92 * Finally, we use a special attr/char pair, defaulting to "attr 0" and
93 * "char 32", also known as "black space", when we "erase" or "clear"
94 * any window, but this pair can be redefined to any pair, including
95 * the standard "white space", or the bizarre "emptiness" ("attr 0"
96 * and "char 0"), as long as various obscure restrictions are met.
97 *
98 *
99 * This package provides several functions which allow a program to
100 * interact with the "term" structures. Most of the functions allow
101 * the program to "request" certain changes to the current "term",
102 * such as moving the cursor, drawing an attr/char pair, erasing a
103 * region of grids, hiding the cursor, etc. Then there is a special
104 * function which causes all of the "pending" requests to be performed
105 * in an efficient manner. There is another set of functions which
106 * allow the program to query the "requested state" of the current
107 * "term", such as asking for the cursor location, or what attr/char
108 * is at a given location, etc. There is another set of functions
109 * dealing with "keypress" events, which allows the program to ask if
110 * the user has pressed any keys, or to forget any keys the user pressed.
111 * There is a pair of functions to allow this package to memorize the
112 * contents of the current "term", and to restore these contents at
113 * a later time. There is a special function which allows the program
114 * to specify which "term" structure should be the "current" one. At
115 * the lowest level, there is a set of functions which allow a new
116 * "term" to be initialized or destroyed, and which allow this package,
117 * or a program, to access the special "hooks" defined for the current
118 * "term", and a set of functions which those "hooks" can use to inform
119 * this package of the results of certain occurances, for example, one
120 * such function allows this package to learn about user keypresses,
121 * detected by one of the special "hooks".
122 *
123 * We provide, among other things, the functions "Term_keypress()"
124 * to "react" to keypress events, and "Term_redraw()" to redraw the
125 * entire window, plus "Term_resize()" to note a new size.
126 *
127 *
128 * Note that the current "term" contains two "window images". One of
129 * these images represents the "requested" contents of the "term", and
130 * the other represents the "actual" contents of the "term", at the time
131 * of the last performance of pending requests. This package uses these
132 * two images to determine the "minimal" amount of work needed to make
133 * the "actual" contents of the "term" match the "requested" contents of
134 * the "term". This method is not perfect, but it often reduces the
135 * amount of work needed to perform the pending requests, which thus
136 * increases the speed of the program itself. This package promises
137 * that the requested changes will appear to occur either "all at once"
138 * or in a "top to bottom" order. In addition, a "cursor" is maintained,
139 * and this cursor is updated along with the actual window contents.
140 *
141 * Currently, the "Term_fresh()" routine attempts to perform the "minimum"
142 * number of physical updates, in terms of total "work" done by the hooks
143 * Term_wipe(), Term_text(), and Term_pict(), making use of the fact that
144 * adjacent characters of the same color can both be drawn together using
145 * the "Term_text()" hook, and that "black" text can often be sent to the
146 * "Term_wipe()" hook instead of the "Term_text()" hook, and if something
147 * is already displayed in a window, then it is not necessary to display
148 * it again. Unfortunately, this may induce slightly non-optimal results
149 * in some cases, in particular, those in which, say, a string of ten
150 * characters needs to be written, but the fifth character has already
151 * been displayed. Currently, this will cause the "Term_text()" routine
152 * to be called once for each half of the string, instead of once for the
153 * whole string, which, on some machines, may be non-optimal behavior.
154 *
155 * The new formalism includes a "displayed" screen image (old) which
156 * is actually seen by the user, a "requested" screen image (scr)
157 * which is being prepared for display, and a list of screen images
158 * which are accessed through the "next" pointers from scr.
159 *
160 *
161 * Several "flags" are available in each "term" to allow the underlying
162 * visual system (which initializes the "term" structure) to "optimize"
163 * the performance of this package for the given system, or to request
164 * certain behavior which is helpful/required for the given system.
165 *
166 * The "soft_cursor" flag indicates the use of a "soft" cursor, which
167 * only moves when explicitly requested,and which is "erased" when
168 * any characters are drawn on top of it. This flag is used for all
169 * "graphic" systems which handle the cursor by "drawing" it.
170 *
171 * The "icky_corner" flag indicates that the bottom right "corner"
172 * of the windows are "icky", and "printing" anything there may
173 * induce "messy" behavior, such as "scrolling". This flag is used
174 * for most old "dumb terminal" systems.
175 *
176 *
177 * The "term" structure contains the following function "hooks":
178 *
179 * Term->init_hook = Init the term
180 * Term->nuke_hook = Nuke the term
181 * Term->user_hook = Perform user actions
182 * Term->xtra_hook = Perform extra actions
183 * Term->curs_hook = Draw (or Move) the cursor
184 * Term->wipe_hook = Draw some blank spaces
185 * Term->text_hook = Draw some text in the window
186 * Term->pict_hook = Draw some attr/chars in the window
187 *
188 * The "Term->user_hook" hook provides a simple hook to an implementation
189 * defined function, with application defined semantics. It is available
190 * to the program via the "Term_user()" function.
191 *
192 * The "Term->xtra_hook" hook provides a variety of different functions,
193 * based on the first parameter (which should be taken from the various
194 * TERM_XTRA_* defines) and the second parameter (which may make sense
195 * only for some first parameters). It is available to the program via
196 * the "Term_xtra()" function, though some first parameters are only
197 * "legal" when called from inside this package.
198 *
199 * The "Term->curs_hook" hook provides this package with a simple way
200 * to "move" or "draw" the cursor to the grid "x,y", depending on the
201 * setting of the "soft_cursor" flag. Note that the cursor is never
202 * redrawn if "nothing" has happened to the screen (even temporarily).
203 * This hook is required.
204 *
205 * The "Term->wipe_hook" hook provides this package with a simple way
206 * to "erase", starting at "x,y", the next "n" grids. This hook assumes
207 * that the input is valid. This hook is required, unless the setting
208 * of the "always_pict" or "always_text" flags makes it optional.
209 *
210 * The "Term->text_hook" hook provides this package with a simple way
211 * to "draw", starting at "x,y", the "n" chars contained in "cp", using
212 * the attr "a". This hook assumes that the input is valid, and that
213 * "n" is between 1 and 256 inclusive, but it should NOT assume that
214 * the contents of "cp" are null-terminated. This hook is required,
215 * unless the setting of the "always_pict" flag makes it optional.
216 *
217 * The "Term->pict_hook" hook provides this package with a simple way
218 * to "draw", starting at "x,y", the "n" attr/char pairs contained in
219 * the arrays "ap" and "cp". This hook assumes that the input is valid,
220 * and that "n" is between 1 and 256 inclusive, but it should NOT assume
221 * that the contents of "cp" are null-terminated. This hook is optional,
222 * unless the setting of the "always_pict" or "higher_pict" flags make
223 * it required. Note that recently, this hook was changed from taking
224 * a byte "a" and a char "c" to taking a length "n", an array of bytes
225 * "ap" and an array of chars "cp". Old implementations of this hook
226 * should now iterate over all "n" attr/char pairs.
227 *
228 *
229 * The game "Angband" uses a set of files called "main-xxx.c", for
230 * various "xxx" suffixes. Most of these contain a function called
231 * "init_xxx()", that will prepare the underlying visual system for
232 * use with Angband, and then create one or more "term" structures,
233 * using flags and hooks appropriate to the given platform, so that
234 * the "main()" function can call one (or more) of the "init_xxx()"
235 * functions, as appropriate, to prepare the required "term" structs
236 * (one for each desired sub-window), and these "init_xxx()" functions
237 * are called from a centralized "main()" function in "main.c". Other
238 * "main-xxx.c" systems contain their own "main()" function which, in
239 * addition to doing everything needed to initialize the actual program,
240 * also does everything that the normal "init_xxx()" functions would do.
241 *
242 * The game "Angband" defines, in addition to "attr 0", all of the
243 * attr codes from 1 to 15, using definitions in "defines.h", and
244 * thus the "main-xxx.c" files used by Angband must handle these
245 * attr values correctly. Also, they must handle all other attr
246 * values, though they may do so in any way they wish, for example,
247 * by always taking every attr code mod 16. Many of the "main-xxx.c"
248 * files use "white space" ("attr 1" / "char 32") to "erase" or "clear"
249 * any window, for efficiency.
250 *
251 * The game "Angband" uses the "Term_user" hook to allow any of the
252 * "main-xxx.c" files to interact with the user, by calling this hook
253 * whenever the user presses the "!" key when the game is waiting for
254 * a new command. This could be used, for example, to provide "unix
255 * shell commands" to the Unix versions of the game.
256 *
257 * See "main-xxx.c" for a simple skeleton file which can be used to
258 * create a "visual system" for a new platform when porting Angband.
259 */
260
261
262
263
264
265
266 /*
267 * The current "term"
268 */
269 term *Term = NULL;
270
271
272 /*** Local routines ***/
273
274
275 /*
276 * Nuke a term_win (see below)
277 */
term_win_nuke(term_win * s)278 static errr term_win_nuke(term_win *s)
279 {
280 /* Free the window access arrays */
281 KILL(s->a);
282 KILL(s->c);
283
284 /* Free the window content arrays */
285 KILL(s->va);
286 KILL(s->vc);
287
288 /* Free the terrain access arrays */
289 KILL(s->ta);
290 KILL(s->tc);
291
292 /* Free the terrain content arrays */
293 KILL(s->vta);
294 KILL(s->vtc);
295
296 /* Success */
297 return (0);
298 }
299
300
301 /*
302 * Initialize a "term_win" (using the given window size)
303 */
term_win_init(term_win * s,int w,int h)304 static errr term_win_init(term_win *s, int w, int h)
305 {
306 int y;
307
308 /* Make the window access arrays */
309 C_MAKE(s->a, h, byte *);
310 C_MAKE(s->c, h, char *);
311
312 /* Make the window content arrays */
313 C_MAKE(s->va, h * w, byte);
314 C_MAKE(s->vc, h * w, char);
315
316 /* Make the terrain access arrays */
317 C_MAKE(s->ta, h, byte *);
318 C_MAKE(s->tc, h, char *);
319
320 /* Make the terrain content arrays */
321 C_MAKE(s->vta, h * w, byte);
322 C_MAKE(s->vtc, h * w, char);
323
324
325 /* Prepare the window access arrays */
326 for (y = 0; y < h; y++)
327 {
328 s->a[y] = s->va + w * y;
329 s->c[y] = s->vc + w * y;
330
331 s->ta[y] = s->vta + w * y;
332 s->tc[y] = s->vtc + w * y;
333 }
334
335 /* Hack - disable bigtile */
336 s->big_x1 = -1;
337 s->big_y1 = -1;
338 s->big_y2 = -1;
339
340 /* Success */
341 return (0);
342 }
343
344
345 /*
346 * Copy a "term_win" from another
347 * If bigtile is set, then copy bigtile region.
348 */
term_win_copy(term_win * s,term_win * f,int w,int h)349 static errr term_win_copy(term_win *s, term_win *f, int w, int h)
350 {
351 int x, y;
352
353 /* Copy contents */
354 for (y = 0; y < h; y++)
355 {
356 byte *f_aa = f->a[y];
357 char *f_cc = f->c[y];
358
359 byte *s_aa = s->a[y];
360 char *s_cc = s->c[y];
361
362 byte *f_taa = f->ta[y];
363 char *f_tcc = f->tc[y];
364
365 byte *s_taa = s->ta[y];
366 char *s_tcc = s->tc[y];
367
368 for (x = 0; x < w; x++)
369 {
370 *s_aa++ = *f_aa++;
371 *s_cc++ = *f_cc++;
372
373 *s_taa++ = *f_taa++;
374 *s_tcc++ = *f_tcc++;
375 }
376 }
377
378 /* Copy cursor */
379 s->cx = f->cx;
380 s->cy = f->cy;
381 s->cu = f->cu;
382 s->cv = f->cv;
383
384 /* Copy bigtile region */
385 s->big_x1 = f->big_x1;
386 s->big_y1 = f->big_y1;
387 s->big_y2 = f->big_y2;
388
389 /* Copy list pointer */
390 s->next = f->next;
391
392 /* Success */
393 return (0);
394 }
395
396
397 /*
398 * Resize a "term_win"
399 *
400 * wn, hn are the new width / height
401 * wo, ho are the old width / height
402 * win is the window to resize.
403 */
Term_resize_win(int wn,int hn,int wo,int ho,term_win * win)404 static errr Term_resize_win(int wn, int hn, int wo, int ho, term_win *win)
405 {
406 term_win tmp;
407
408 /* Create a new window of the correct size */
409 (void) term_win_init(&tmp, wn, hn);
410
411 /* Copy in the information from the old window */
412 (void) term_win_copy(&tmp, win, wo, ho);
413
414 /* Nuke the old window */
415 (void) term_win_nuke(win);
416
417 /* Illegal cursor? */
418 if (tmp.cx >= wn) tmp.cu = 1;
419 if (tmp.cy >= hn) tmp.cu = 1;
420
421 /* Save new window (Structure Copy) */
422 *win = tmp;
423
424 /* Success */
425 return (0);
426 }
427
428
429 /*** External hooks ***/
430
431
432 /*
433 * Execute the "Term->user_hook" hook, if available (see above).
434 */
Term_user(int n)435 errr Term_user(int n)
436 {
437 /* Verify the hook */
438 if (!Term->user_hook) return (-1);
439
440 /* Call the hook */
441 return ((*Term->user_hook) (n));
442 }
443
444 /*
445 * Execute the "Term->xtra_hook" hook, if available (see above).
446 */
Term_xtra(int n,int v)447 void Term_xtra(int n, int v)
448 {
449 /* Verify the hook */
450 if (!Term->xtra_hook) return;
451
452 /* Call the hook */
453 (void)(*Term->xtra_hook) (n, v);
454
455 /* Done */
456 return;
457 }
458
459
460
461 /*** Fake hooks ***/
462
463
464 /*
465 * Hack -- fake hook for "Term_curs()" (see above)
466 */
Term_curs_hack(int x,int y)467 static errr Term_curs_hack(int x, int y)
468 {
469 /* Compiler silliness */
470 if (x || y) return (-2);
471
472 /* Oops */
473 return (-1);
474 }
475
476 /*
477 * Hack -- fake hook for "Term_wipe()" (see above)
478 */
Term_wipe_hack(int x,int y,int n)479 static errr Term_wipe_hack(int x, int y, int n)
480 {
481 /* Compiler silliness */
482 if (x || y || n) return (-2);
483
484 /* Oops */
485 return (-1);
486 }
487
488 /*
489 * Hack -- fake hook for "Term_text()" (see above)
490 */
Term_text_hack(int x,int y,int n,byte a,const char * cp)491 static errr Term_text_hack(int x, int y, int n, byte a, const char *cp)
492 {
493 /* Compiler silliness */
494 if (x || y || n || a || cp) return (-2);
495
496 /* Oops */
497 return (-1);
498 }
499
500 /*
501 * Hack -- fake hook for "Term_pict()" (see above)
502 */
Term_pict_hack(int x,int y,int n,const byte * ap,const char * cp,const byte * tap,const char * tcp)503 static errr Term_pict_hack(int x, int y, int n, const byte *ap, const char *cp,
504 const byte *tap, const char *tcp)
505 {
506 /* Compiler silliness */
507 if (x || y || n || ap || cp || tap || tcp) return (-2);
508
509 /* Oops */
510 return (-1);
511 }
512
513
514
515 /*** Efficient routines ***/
516
517 /*
518 * Decrease the size of the bigtile region so
519 * that it is outside the row y.
520 */
Term_bigtile_expand(int x,int y)521 static void Term_bigtile_expand(int x, int y)
522 {
523 int sy;
524
525 int i;
526
527 /* Disable bigtile */
528 if (Term->scr->wipe_bigtile)
529 {
530 /* Are we in the bigtile region? */
531 if ((y >= Term->scr->big_y1) && (y <= Term->scr->big_y2) &&
532 (x >= Term->scr->big_x1))
533 {
534 /* Save start point */
535 sy = Term->scr->big_y1;
536
537 /* Move bigscreen region to below text area */
538 Term->scr->big_y1 = y + 1;
539
540 /* Wipe old bigtiled region */
541 for (i = sy; i < Term->scr->big_y1; i++)
542 {
543 Term_erase(Term->scr->big_x1, i, 255);
544 }
545
546 /* Hack - We need to redraw everything later */
547 Term->total_erase = TRUE;
548 }
549 }
550 }
551
552 /*
553 * Mentally draw an attr/char at a given location
554 *
555 * Assumes given location and values are valid.
556 */
Term_queue_char(int x,int y,byte a,char c,byte ta,char tc)557 void Term_queue_char(int x, int y, byte a, char c, byte ta, char tc)
558 {
559 term_win *scrn = Term->scr;
560
561 byte *scr_aa = &scrn->a[y][x];
562 char *scr_cc = &scrn->c[y][x];
563
564 byte *scr_taa = &scrn->ta[y][x];
565 char *scr_tcc = &scrn->tc[y][x];
566
567 /* Hack -- Ignore non-changes */
568 if ((*scr_aa == a) && (*scr_cc == c) &&
569 (*scr_taa == ta) && (*scr_tcc == tc)) return;
570
571 /* Save the "literal" information */
572 *scr_aa = a;
573 *scr_cc = c;
574
575 *scr_taa = ta;
576 *scr_tcc = tc;
577
578 /* Check for new min/max row info */
579 if (y < Term->y1) Term->y1 = y;
580 if (y > Term->y2) Term->y2 = y;
581
582 /* Check for new min/max col info for this row */
583 if (x < Term->x1[y]) Term->x1[y] = x;
584 if (x > Term->x2[y]) Term->x2[y] = x;
585 }
586
587
588 /*
589 * Mentally draw a string of attr/chars at a given location
590 *
591 * Assumes given location and values are valid.
592 *
593 * This function is designed to be fast, with no consistancy checking.
594 * It is used to update the map in the game.
595 */
Term_queue_line(int x,int y,int n,byte * a,char * c,byte * ta,char * tc)596 void Term_queue_line(int x, int y, int n, byte *a, char *c, byte *ta, char *tc)
597 {
598 term_win *scrn = Term->scr;
599
600 int x1 = -1;
601 int x2 = -1;
602
603 byte *scr_aa = &scrn->a[y][x];
604 char *scr_cc = &scrn->c[y][x];
605
606 byte *scr_taa = &scrn->ta[y][x];
607 char *scr_tcc = &scrn->tc[y][x];
608
609 /*
610 * Hack - don't worry about bigtile stuff here,
611 * since this routine isn't used by the menues.
612 */
613
614 while (n--)
615 {
616 /* Hack -- Ignore non-changes */
617 if ((*scr_aa == *a) && (*scr_cc == *c) &&
618 (*scr_taa == *ta) && (*scr_tcc == *tc))
619 {
620 x++;
621 a++;
622 c++;
623 ta++;
624 tc++;
625 scr_aa++;
626 scr_cc++;
627 scr_taa++;
628 scr_tcc++;
629 continue;
630 }
631
632 /* Save the "literal" information */
633 *scr_taa++ = *ta++;
634 *scr_tcc++ = *tc++;
635
636 /* Save the "literal" information */
637 *scr_aa++ = *a++;
638 *scr_cc++ = *c++;
639
640 /* Track minimum changed column */
641 if (x1 < 0) x1 = x;
642
643 /* Track maximum changed column */
644 x2 = x;
645
646 x++;
647 }
648
649 /* Expand the "change area" as needed */
650 if (x1 >= 0)
651 {
652 /* Check for new min/max row info */
653 if (y < Term->y1) Term->y1 = y;
654 if (y > Term->y2) Term->y2 = y;
655
656 /* Check for new min/max col info in this row */
657 if (x1 < Term->x1[y]) Term->x1[y] = x1;
658 if (x2 > Term->x2[y]) Term->x2[y] = x2;
659 }
660 }
661
662
663 /*** Refresh routines ***/
664
665
666 /*
667 * Flush a row of the current window (see "Term_fresh")
668 *
669 * Display text using "Term_pict()"
670 */
Term_fresh_row_pict(int y,int x1,int x2)671 static void Term_fresh_row_pict(int y, int x1, int x2)
672 {
673 int x;
674
675 byte *old_aa = Term->old->a[y];
676 char *old_cc = Term->old->c[y];
677
678 byte *scr_aa = Term->scr->a[y];
679 char *scr_cc = Term->scr->c[y];
680
681 byte *old_taa = Term->old->ta[y];
682 char *old_tcc = Term->old->tc[y];
683
684 byte *scr_taa = Term->scr->ta[y];
685 char *scr_tcc = Term->scr->tc[y];
686
687 byte ota;
688 char otc;
689
690 byte nta;
691 char ntc;
692
693 /* Pending length */
694 int fn = 0;
695
696 /* Pending start */
697 int fx = 0;
698
699 byte oa;
700 char oc;
701
702 byte na;
703 char nc;
704
705 /* Scan "modified" columns */
706 for (x = x1; x <= x2; x++)
707 {
708 /* See what is currently here */
709 oa = old_aa[x];
710 oc = old_cc[x];
711
712 /* See what is desired there */
713 na = scr_aa[x];
714 nc = scr_cc[x];
715
716 ota = old_taa[x];
717 otc = old_tcc[x];
718
719 nta = scr_taa[x];
720 ntc = scr_tcc[x];
721
722 /* Handle unchanged grids */
723 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc)
724 && !Term->total_erase)
725 {
726 /* Flush */
727 if (fn)
728 {
729 /* Draw pending attr/char pairs */
730 (void)((*Term->pict_hook) (fx, y, fn,
731 &scr_aa[fx], &scr_cc[fx],
732 &scr_taa[fx], &scr_tcc[fx]));
733
734 /* Forget */
735 fn = 0;
736 }
737
738 /* Skip */
739 continue;
740 }
741 /* Save new contents */
742 old_aa[x] = na;
743 old_cc[x] = nc;
744
745 old_taa[x] = nta;
746 old_tcc[x] = ntc;
747
748 /* Restart and Advance */
749 if (fn++ == 0) fx = x;
750 }
751
752 /* Flush */
753 if (fn)
754 {
755 /* Draw pending attr/char pairs */
756 (void)((*Term->pict_hook) (fx, y, fn,
757 &scr_aa[fx], &scr_cc[fx], &scr_taa[fx],
758 &scr_tcc[fx]));
759 }
760 }
761
762
763
764 /*
765 * Flush a row of the current window (see "Term_fresh")
766 *
767 * Display text using "Term_text()" and "Term_wipe()",
768 * but use "Term_pict()" for high-bit attr/char pairs
769 */
Term_fresh_row_both(int y,int x1,int x2)770 static void Term_fresh_row_both(int y, int x1, int x2)
771 {
772 int x;
773
774 byte *old_aa = Term->old->a[y];
775 char *old_cc = Term->old->c[y];
776
777 byte *scr_aa = Term->scr->a[y];
778 char *scr_cc = Term->scr->c[y];
779
780 byte *old_taa = Term->old->ta[y];
781 char *old_tcc = Term->old->tc[y];
782 byte *scr_taa = Term->scr->ta[y];
783 char *scr_tcc = Term->scr->tc[y];
784
785 byte ota;
786 char otc;
787 byte nta;
788 char ntc;
789
790 /* The "always_text" flag */
791 int always_text = Term->always_text;
792
793 /* Pending length */
794 int fn = 0;
795
796 /* Pending start */
797 int fx = 0;
798
799 /* Pending attr */
800 byte fa = Term->attr_blank;
801
802 byte oa;
803 char oc;
804
805 byte na;
806 char nc;
807
808 /* Scan "modified" columns */
809 for (x = x1; x <= x2; x++)
810 {
811 /* See what is currently here */
812 oa = old_aa[x];
813 oc = old_cc[x];
814
815 /* See what is desired there */
816 na = scr_aa[x];
817 nc = scr_cc[x];
818
819 ota = old_taa[x];
820 otc = old_tcc[x];
821
822 nta = scr_taa[x];
823 ntc = scr_tcc[x];
824
825 /* Handle unchanged grids */
826 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc)
827 && !Term->total_erase)
828 {
829 /* Flush */
830 if (fn)
831 {
832 /* Draw pending chars (normal) */
833 if (fa || always_text)
834 {
835 (void)((*Term->text_hook) (fx, y, fn, fa, &scr_cc[fx]));
836 }
837 /* Draw pending chars (black) */
838 else
839 {
840 (void)((*Term->wipe_hook) (fx, y, fn));
841 }
842 /* Forget */
843 fn = 0;
844 }
845
846 /* Skip */
847 continue;
848 }
849
850 /* Save new contents */
851 old_aa[x] = na;
852 old_cc[x] = nc;
853
854 old_taa[x] = nta;
855 old_tcc[x] = ntc;
856
857 /* Handle high-bit attr/chars */
858 if (na & 0x80)
859 {
860 /* Flush */
861 if (fn)
862 {
863 /* Draw pending chars (normal) */
864 if (fa || always_text)
865 {
866 (void)((*Term->text_hook) (fx, y, fn, fa, &scr_cc[fx]));
867 }
868 /* Draw pending chars (black) */
869 else
870 {
871 (void)((*Term->wipe_hook) (fx, y, fn));
872 }
873 /* Forget */
874 fn = 0;
875 }
876
877 /* Hack -- Draw the special attr/char pair */
878 (void)((*Term->pict_hook) (x, y, 1, &na, &nc, &nta, &ntc));
879
880 /* Skip */
881 continue;
882 }
883
884 /* Notice new color */
885 if (fa != na)
886 {
887 /* Flush */
888 if (fn)
889 {
890 /* Draw the pending chars */
891 if (fa || always_text)
892 {
893 (void)((*Term->text_hook) (fx, y, fn, fa, &scr_cc[fx]));
894 }
895 /* Hack -- Erase "leading" spaces */
896 else
897 {
898 (void)((*Term->wipe_hook) (fx, y, fn));
899 }
900 /* Forget */
901 fn = 0;
902 }
903
904 /* Save the new color */
905 fa = na;
906 }
907
908 /* Restart and Advance */
909 if (fn++ == 0) fx = x;
910 }
911
912 /* Flush */
913 if (fn)
914 {
915 /* Draw pending chars (normal) */
916 if (fa || always_text)
917 {
918 (void)((*Term->text_hook) (fx, y, fn, fa, &scr_cc[fx]));
919 }
920 /* Draw pending chars (black) */
921 else
922 {
923 (void)((*Term->wipe_hook) (fx, y, fn));
924 }
925 }
926 }
927
928
929 /*
930 * Flush a row of the current window (see "Term_fresh")
931 *
932 * Display text using "Term_text()" and "Term_wipe()"
933 */
Term_fresh_row_text(int y,int x1,int x2)934 static void Term_fresh_row_text(int y, int x1, int x2)
935 {
936 int x;
937
938 byte *old_aa = Term->old->a[y];
939 char *old_cc = Term->old->c[y];
940
941 byte *scr_aa = Term->scr->a[y];
942 char *scr_cc = Term->scr->c[y];
943
944 /* The "always_text" flag */
945 int always_text = Term->always_text;
946
947 /* Pending length */
948 int fn = 0;
949
950 /* Pending start */
951 int fx = 0;
952
953 /* Pending attr */
954 byte fa = Term->attr_blank;
955
956 byte oa;
957 char oc;
958
959 byte na;
960 char nc;
961
962
963 /* Scan "modified" columns */
964 for (x = x1; x <= x2; x++)
965 {
966 /* See what is currently here */
967 oa = old_aa[x];
968 oc = old_cc[x];
969
970 /* See what is desired there */
971 na = scr_aa[x];
972 nc = scr_cc[x];
973
974 /* Handle unchanged grids */
975 if ((na == oa) && (nc == oc) && !Term->total_erase)
976 {
977 /* Flush */
978 if (fn)
979 {
980 /* Draw pending chars (normal) */
981 if (fa || always_text)
982 {
983 (void)((*Term->text_hook) (fx, y, fn, fa, &scr_cc[fx]));
984 }
985
986 /* Draw pending chars (black) */
987 else
988 {
989 (void)((*Term->wipe_hook) (fx, y, fn));
990 }
991
992 /* Forget */
993 fn = 0;
994 }
995
996 /* Skip */
997 continue;
998 }
999
1000 /* Save new contents */
1001 old_aa[x] = na;
1002 old_cc[x] = nc;
1003
1004 /* Notice new color */
1005 if (fa != na)
1006 {
1007 /* Flush */
1008 if (fn)
1009 {
1010 /* Draw the pending chars */
1011 if (fa || always_text)
1012 {
1013 (void)((*Term->text_hook) (fx, y, fn, fa, &scr_cc[fx]));
1014 }
1015
1016 /* Hack -- Erase "leading" spaces */
1017 else
1018 {
1019 (void)((*Term->wipe_hook) (fx, y, fn));
1020 }
1021
1022 /* Forget */
1023 fn = 0;
1024 }
1025
1026 /* Save the new color */
1027 fa = na;
1028 }
1029
1030 /* Restart and Advance */
1031 if (fn++ == 0) fx = x;
1032 }
1033
1034 /* Flush */
1035 if (fn)
1036 {
1037 /* Draw pending chars (normal) */
1038 if (fa || always_text)
1039 {
1040 (void)((*Term->text_hook) (fx, y, fn, fa, &scr_cc[fx]));
1041 }
1042
1043 /* Draw pending chars (black) */
1044 else
1045 {
1046 (void)((*Term->wipe_hook) (fx, y, fn));
1047 }
1048 }
1049 }
1050
1051
1052 /*
1053 * Draw / Redraw the tiles in a term
1054 */
Term_fresh_section(void)1055 static void Term_fresh_section(void)
1056 {
1057 int y;
1058 int y1 = Term->y1;
1059 int y2 = Term->y2;
1060
1061 int w = Term->wid;
1062 int h = Term->hgt;
1063
1064 /* Something to update */
1065 if (y1 <= y2)
1066 {
1067 /* Handle "icky corner" */
1068 if (Term->icky_corner)
1069 {
1070 /* Avoid the corner */
1071 if (y2 >= h - 1)
1072 {
1073 /* Avoid the corner */
1074 if (Term->x2[h - 1] > w - 2)
1075 {
1076 /* Avoid the corner */
1077 Term->x2[h - 1] = w - 2;
1078 }
1079 }
1080 }
1081
1082
1083 /* Scan the "modified" rows */
1084 for (y = y1; y <= y2; ++y)
1085 {
1086 int x1 = Term->x1[y];
1087 int x2 = Term->x2[y];
1088
1089 /* Flush each "modified" row */
1090 if (x1 <= x2)
1091 {
1092 /* Always use "Term_pict()" */
1093 if (Term->always_pict)
1094 {
1095 /* Flush the row */
1096 Term_fresh_row_pict(y, x1, x2);
1097 }
1098
1099 /* Sometimes use "Term_pict()" */
1100 else if (Term->higher_pict)
1101 {
1102 /* Flush the row */
1103 Term_fresh_row_both(y, x1, x2);
1104 }
1105
1106 /* Never use "Term_pict()" */
1107 else
1108 {
1109 /* Flush the row */
1110 Term_fresh_row_text(y, x1, x2);
1111 }
1112
1113 /* This row is all done */
1114 Term->x1[y] = w;
1115 Term->x2[y] = 0;
1116
1117 /* Hack -- Flush that row (if allowed) */
1118 if (!Term->never_frosh) Term_xtra(TERM_XTRA_FROSH, y);
1119 }
1120 }
1121
1122 /* No rows are invalid */
1123 Term->y1 = h;
1124 Term->y2 = 0;
1125 }
1126 }
1127
1128 /*
1129 * Redraw the cursor
1130 */
Term_fresh_cursor(void)1131 static void Term_fresh_cursor(void)
1132 {
1133 term_win *old = Term->old;
1134 term_win *scr = Term->scr;
1135
1136 /* Cursor update -- Show new Cursor */
1137 if (Term->soft_cursor)
1138 {
1139 /* Draw the cursor */
1140 if (!scr->cu && scr->cv)
1141 {
1142 /* Call the cursor display routine */
1143 (void)((*Term->curs_hook) (scr->cx, scr->cy));
1144 }
1145 }
1146
1147 /* Cursor Update -- Show new Cursor */
1148 else
1149 {
1150 /* The cursor is useless, hide it */
1151 if (scr->cu)
1152 {
1153 /* Paranoia -- Put the cursor NEAR where it belongs */
1154 (void)((*Term->curs_hook) (Term->wid - 1, scr->cy));
1155
1156 /* Make the cursor invisible */
1157 /* Term_xtra(TERM_XTRA_SHAPE, 0); */
1158 }
1159
1160 /* The cursor is invisible, hide it */
1161 else if (!scr->cv)
1162 {
1163 /* Paranoia -- Put the cursor where it belongs */
1164 (void)((*Term->curs_hook) (scr->cx, scr->cy));
1165
1166 /* Make the cursor invisible */
1167 /* Term_xtra(TERM_XTRA_SHAPE, 0); */
1168 }
1169
1170 /* The cursor is visible, display it correctly */
1171 else
1172 {
1173 /* Put the cursor where it belongs */
1174 (void)((*Term->curs_hook) (scr->cx, scr->cy));
1175
1176 /* Make the cursor visible */
1177 Term_xtra(TERM_XTRA_SHAPE, 1);
1178 }
1179 }
1180
1181
1182 /* Save the "cursor state" */
1183 old->cu = scr->cu;
1184 old->cv = scr->cv;
1185 old->cx = scr->cx;
1186 old->cy = scr->cy;
1187 }
1188
1189 /*
1190 * Actually perform all requested changes to the window
1191 *
1192 * If absolutely nothing has changed, not even temporarily, or if the
1193 * current "Term" is not mapped, then this function will return 1 and
1194 * do absolutely nothing.
1195 *
1196 * Note that when "soft_cursor" is true, we erase the cursor (if needed)
1197 * whenever anything has changed, and redraw it (if needed) after all of
1198 * the screen updates are complete. This will induce a small amount of
1199 * "cursor flicker" but only when the screen has been updated. If the
1200 * screen is updated and then restored, you may still get this flicker.
1201 *
1202 * When "soft_cursor" is not true, we make the cursor invisible before
1203 * doing anything else if it is supposed to be invisible by the time we
1204 * are done, and we make it visible after moving it to its final location
1205 * after all of the screen updates are complete.
1206 *
1207 * Note that "Term_xtra(TERM_XTRA_FROSH,y)" will be always be called
1208 * after any row "y" has been "flushed", unless the "Term->never_frosh"
1209 * flag is set, and "Term_xtra(TERM_XTRA_FRESH,0)" will be called after
1210 * all of the rows have been "flushed".
1211 *
1212 * Note the use of three different functions to handle the actual flush,
1213 * based on the settings of the "Term->always_pict" and "Term->higher_pict"
1214 * flags (see below).
1215 *
1216 * The three helper functions (above) work by collecting similar adjacent
1217 * grids into stripes, and then sending each stripe to "Term->pict_hook",
1218 * "Term->text_hook", or "Term->wipe_hook", based on the settings of the
1219 * "Term->always_pict" and "Term->higher_pict" flags, which select which
1220 * of the helper functions to call to flush each row.
1221 *
1222 * The helper functions currently "skip" any grids which already contain
1223 * the desired contents. This may or may not be the best method, especially
1224 * when the desired content fits nicely into the current stripe. For example,
1225 * it might be better to go ahead and queue them while allowed, but keep a
1226 * count of the "trailing skipables", then, when time to flush, or when a
1227 * "non skippable" is found, force a flush if there are too many skippables.
1228 *
1229 * Perhaps an "initialization" stage, where the "text" (and "attr")
1230 * buffers are "filled" with information, converting "blanks" into
1231 * a convenient representation, and marking "skips" with "zero chars",
1232 * and then some "processing" is done to determine which chars to skip.
1233 *
1234 * Currently, the helper functions are optimal for systems which prefer
1235 * to "print a char + move a char + print a char" to "print three chars",
1236 * and for applications that do a lot of "detailed" color printing.
1237 *
1238 * In the two "queue" functions, total "non-changes" are "pre-skipped".
1239 * The helper functions must also handle situations in which the contents
1240 * of a grid are changed, but then changed back to the original value,
1241 * and situations in which two grids in the same row are changed, but
1242 * the grids between them are unchanged.
1243 *
1244 * If the "Term->always_pict" flag is set, then "Term_fresh_row_pict()"
1245 * will be used instead of "Term_fresh_row_text()". This allows all the
1246 * modified grids to be collected into stripes of attr/char pairs, which
1247 * are then sent to the "Term->pict_hook" hook, which can draw these pairs
1248 * in whatever way it would like.
1249 *
1250 * If the "Term->higher_pict" flag is set, then "Term_fresh_row_both()"
1251 * will be used instead of "Term_fresh_row_text()". This allows all the
1252 * "special" attr/char pairs (in which both the attr and char have the
1253 * high-bit set) to be sent (one pair at a time) to the "Term->pict_hook"
1254 * hook, which can draw these pairs in whatever way it would like.
1255 *
1256 * Normally, the "Term_wipe()" function is used only to display "blanks"
1257 * that were induced by "Term_clear()" or "()", and then only
1258 * if the "attr_blank" and "char_blank" fields have not been redefined
1259 * to use "white space" instead of the default "black space". Actually,
1260 * the "Term_wipe()" function is used to display all "black" text, such
1261 * as the default "spaces" created by "Term_clear()" and "Term_erase()".
1262 *
1263 * Note that the "Term->always_text" flag will disable the use of the
1264 * "Term_wipe()" function hook entirely, and force all text, even text
1265 * drawn in the color "black", to be explicitly drawn. This is useful
1266 * for machines which implement "Term_wipe()" by just drawing spaces.
1267 *
1268 * Note that the "Term->always_pict" flag will disable the use of the
1269 * "Term_wipe()" function entirely, and force everything, even text
1270 * drawn in the attr "black", to be explicitly drawn.
1271 *
1272 * Note that if no "black" text is ever drawn, and if "attr_blank" is
1273 * not "zero", then the "Term_wipe" hook will never be used, even if
1274 * the "Term->always_text" flag is not set.
1275 *
1276 * This function does nothing unless the "Term" is "mapped", which allows
1277 * certain systems to optimize the handling of "closed" windows.
1278 *
1279 * On systems with a "soft" cursor, we must explicitly erase the cursor
1280 * before flushing the output, if needed, to prevent a "jumpy" refresh.
1281 * The actual method for this is horrible, but there is very little that
1282 * we can do to simplify it efficiently. XXX XXX XXX
1283 *
1284 * On systems with a "hard" cursor, we will "hide" the cursor before
1285 * flushing the output, if needed, to avoid a "flickery" refresh. It
1286 * would be nice to *always* hide the cursor during the refresh, but
1287 * this might be expensive (and/or ugly) on some machines.
1288 *
1289 * The "Term->icky_corner" flag is used to avoid calling "Term_wipe()"
1290 * or "Term_pict()" or "Term_text()" on the bottom right corner of the
1291 * window, which might induce "scrolling" or other nasty stuff on old
1292 * dumb terminals. This flag is handled very efficiently. We assume
1293 * that the "Term_curs()" call will prevent placing the cursor in the
1294 * corner, if needed, though I doubt such placement is ever a problem.
1295 * Currently, the use of "Term->icky_corner" and "Term->soft_cursor"
1296 * together may result in undefined behavior.
1297 */
Term_fresh(void)1298 void Term_fresh(void)
1299 {
1300 int y;
1301
1302 int w = Term->wid;
1303 int h = Term->hgt;
1304
1305 int y1 = Term->y1;
1306 int y2 = Term->y2;
1307
1308 term_win *old = Term->old;
1309 term_win *scr = Term->scr;
1310
1311
1312 /* Do nothing unless "mapped" */
1313 if (!Term->mapped_flag) return;
1314
1315
1316 /* Trivial Refresh */
1317 if ((y1 > y2) &&
1318 (scr->cu == old->cu) &&
1319 (scr->cv == old->cv) &&
1320 (scr->cx == old->cx) && (scr->cy == old->cy) && !(Term->total_erase))
1321 {
1322 /* Nothing */
1323 return;
1324 }
1325
1326 /* Paranoia -- use "fake" hooks to prevent core dumps */
1327 if (!Term->curs_hook) Term->curs_hook = Term_curs_hack;
1328 if (!Term->wipe_hook) Term->wipe_hook = Term_wipe_hack;
1329 if (!Term->text_hook) Term->text_hook = Term_text_hack;
1330 if (!Term->pict_hook) Term->pict_hook = Term_pict_hack;
1331
1332 /* Handle "total erase" */
1333 if (Term->total_erase)
1334 {
1335 /* Hack -- clear all "cursor" data */
1336 old->cv = old->cu = old->cx = old->cy = 0;
1337
1338 /* Redraw every row */
1339 Term->y1 = y1 = 0;
1340 Term->y2 = y2 = h - 1;
1341
1342 /* Redraw every column */
1343 for (y = 0; y < h; y++)
1344 {
1345 Term->x1[y] = 0;
1346 Term->x2[y] = w - 1;
1347 }
1348 }
1349
1350
1351 /* Cursor update -- Erase old Cursor */
1352 if (Term->soft_cursor)
1353 {
1354 /* Cursor was visible */
1355 if (!old->cu && old->cv)
1356 {
1357 int tx = old->cx;
1358 int ty = old->cy;
1359
1360 byte *old_aa = old->a[ty];
1361 char *old_cc = old->c[ty];
1362
1363 byte oa = old_aa[tx];
1364 char oc = old_cc[tx];
1365
1366 byte *old_taa = old->ta[ty];
1367 char *old_tcc = old->tc[ty];
1368
1369 byte ota = old_taa[tx];
1370 char otc = old_tcc[tx];
1371
1372 /* Hack -- use "Term_pict()" always */
1373 if (Term->always_pict)
1374 {
1375 (void)((*Term->pict_hook) (tx, ty, 1, &oa, &oc, &ota, &otc));
1376 }
1377
1378 /* Hack -- use "Term_pict()" sometimes */
1379 else if (Term->higher_pict && (oa & 0x80))
1380 {
1381 (void)((*Term->pict_hook) (tx, ty, 1, &oa, &oc, &ota, &otc));
1382 }
1383
1384 /* Hack -- restore the actual character */
1385 else if (oa || Term->always_text)
1386 {
1387 (void)((*Term->text_hook) (tx, ty, 1, oa, &oc));
1388 }
1389
1390 /* Hack -- erase the grid */
1391 else
1392 {
1393 (void)((*Term->wipe_hook) (tx, ty, 1));
1394 }
1395 }
1396 }
1397
1398 /* Cursor Update -- Erase old Cursor */
1399 else
1400 {
1401 /* Cursor will be invisible */
1402 if (scr->cu || !scr->cv)
1403 {
1404 /* Make the cursor invisible */
1405 Term_xtra(TERM_XTRA_SHAPE, 0);
1406 }
1407 }
1408
1409 /* Redraw stuff as required */
1410 Term_fresh_section();
1411
1412 /* Redraw cursor */
1413 Term_fresh_cursor();
1414
1415 /* Actually flush the output */
1416 Term_xtra(TERM_XTRA_FRESH, 0);
1417
1418 /* Forget "total erase" */
1419 Term->total_erase = FALSE;
1420
1421 /* Success */
1422 return;
1423 }
1424
1425
1426
1427 /*** Output routines ***/
1428
1429
1430 /*
1431 * Set the cursor visibility
1432 */
Term_set_cursor(int v)1433 errr Term_set_cursor(int v)
1434 {
1435 /* Already done */
1436 if (Term->scr->cv == v) return (1);
1437
1438 /* Change */
1439 Term->scr->cv = v;
1440
1441 /* Success */
1442 return (0);
1443 }
1444
1445
1446 /*
1447 * Place the cursor at a given location
1448 *
1449 * Note -- "illegal" requests do not move the cursor.
1450 */
Term_gotoxy(int x,int y)1451 void Term_gotoxy(int x, int y)
1452 {
1453 int w = Term->wid;
1454 int h = Term->hgt;
1455
1456 /* Verify */
1457 if ((x < 0) || (x >= w)) return;
1458 if ((y < 0) || (y >= h)) return;
1459
1460 /* Remember the cursor */
1461 Term->scr->cx = x;
1462 Term->scr->cy = y;
1463
1464 /* The cursor is not useless */
1465 Term->scr->cu = 0;
1466
1467 /* Success */
1468 return;
1469 }
1470
1471
1472 /*
1473 * At a given location, place an attr/char
1474 * Do not change the cursor position
1475 * No visual changes until "Term_fresh()".
1476 */
Term_draw(int x,int y,byte a,char c)1477 void Term_draw(int x, int y, byte a, char c)
1478 {
1479 int w = Term->wid;
1480 int h = Term->hgt;
1481
1482 /* Verify location */
1483 if ((x < 0) || (x >= w)) return;
1484 if ((y < 0) || (y >= h)) return;
1485
1486 /* Paranoia -- illegal char */
1487 if (!c) return;
1488
1489 /* Notice bigtile region changes */
1490 Term_bigtile_expand(x, y);
1491
1492 /* Queue it for later */
1493 Term_queue_char(x, y, a, c, 0, 0);
1494
1495 /* Success */
1496 return;
1497 }
1498
1499
1500 /*
1501 * Using the given attr, add the given char at the cursor.
1502 *
1503 * We queue the given attr/char for display at the current
1504 * cursor location, and advance the cursor to the right,
1505 * marking it as unuable and returning "1" if it leaves
1506 * the screen, and otherwise returning "0".
1507 *
1508 * So when this function, or the following one, return a
1509 * positive value, future calls to either function will
1510 * return negative ones.
1511 */
Term_addch(byte a,char c)1512 void Term_addch(byte a, char c)
1513 {
1514 int w = Term->wid;
1515
1516 /* Handle "unusable" cursor */
1517 if (Term->scr->cu) return;
1518
1519 /* Paranoia -- no illegal chars */
1520 if (!c) return;
1521
1522 /* Notice bigtile region changes */
1523 Term_bigtile_expand(Term->scr->cx, Term->scr->cy);
1524
1525 /* Queue the given character for display */
1526 Term_queue_char(Term->scr->cx, Term->scr->cy, a, c, 0, 0);
1527
1528 /* Advance the cursor */
1529 Term->scr->cx++;
1530
1531 /* Success */
1532 if (Term->scr->cx < w) return;
1533
1534 /* Note "Useless" cursor */
1535 Term->scr->cu = 1;
1536
1537 /* Note "Useless" cursor */
1538 return;
1539 }
1540
1541
1542 /*
1543 * Move to a location and, using an attr, add a char
1544 */
Term_putch(int x,int y,byte a,char c)1545 void Term_putch(int x, int y, byte a, char c)
1546 {
1547 /* Move first */
1548 Term_gotoxy(x, y);
1549
1550 /* Then add the char */
1551 Term_addch(a, c);
1552 }
1553
1554
1555 /*
1556 * Place cursor at (x,y), and clear the next "n" chars
1557 */
Term_erase(int x,int y,int n)1558 void Term_erase(int x, int y, int n)
1559 {
1560 int i;
1561
1562 int w = Term->wid;
1563 int h = Term->hgt;
1564
1565 int x1 = -1;
1566 int x2 = -1;
1567
1568 int na = Term->attr_blank;
1569 int nc = Term->char_blank;
1570
1571 byte *scr_aa;
1572 char *scr_cc;
1573
1574 byte *scr_taa;
1575 char *scr_tcc;
1576
1577 /* Place cursor */
1578 Term_gotoxy(x, y);
1579
1580 /* Paranoia */
1581 if (n < 0) n = 0;
1582 if ((x >= w) || (y >= h)) return;
1583
1584 /* Force legal size */
1585 if (x + n > w) n = w - x;
1586
1587 /* Update bigtile info */
1588 Term_bigtile_expand(x, y);
1589
1590 /* Fast access */
1591 scr_aa = Term->scr->a[y];
1592 scr_cc = Term->scr->c[y];
1593
1594 scr_taa = Term->scr->ta[y];
1595 scr_tcc = Term->scr->tc[y];
1596
1597 /* Scan every column */
1598 for (i = 0; i < n; i++, x++)
1599 {
1600 int oa = scr_aa[x];
1601 int oc = scr_cc[x];
1602
1603 /* Hack -- Ignore "non-changes" */
1604 if ((oa == na) && (oc == nc)) continue;
1605
1606 /* Save the "literal" information */
1607 scr_aa[x] = na;
1608 scr_cc[x] = nc;
1609
1610 scr_taa[x] = 0;
1611 scr_tcc[x] = 0;
1612
1613 /* Track minimum changed column */
1614 if (x1 < 0) x1 = x;
1615
1616 /* Track maximum changed column */
1617 x2 = x;
1618 }
1619
1620 /* Expand the "change area" as needed */
1621 if (x1 >= 0)
1622 {
1623 /* Check for new min/max row info */
1624 if (y < Term->y1) Term->y1 = y;
1625 if (y > Term->y2) Term->y2 = y;
1626
1627 /* Check for new min/max col info in this row */
1628 if (x1 < Term->x1[y]) Term->x1[y] = x1;
1629 if (x2 > Term->x2[y]) Term->x2[y] = x2;
1630 }
1631 }
1632
1633
1634 /*
1635 * Clear the entire window, and move to the top left corner
1636 *
1637 * Note the use of the special "total_erase" code
1638 */
Term_clear(void)1639 void Term_clear(void)
1640 {
1641 int x, y;
1642
1643 int w = Term->wid;
1644 int h = Term->hgt;
1645
1646 byte na = Term->attr_blank;
1647 char nc = Term->char_blank;
1648
1649 /* Cursor usable */
1650 Term->scr->cu = 0;
1651
1652 /* Cursor to the top left */
1653 Term->scr->cx = Term->scr->cy = 0;
1654
1655 /* Wipe each row */
1656 for (y = 0; y < h; y++)
1657 {
1658 byte *scr_aa = Term->scr->a[y];
1659 char *scr_cc = Term->scr->c[y];
1660
1661 byte *scr_taa = Term->scr->ta[y];
1662 char *scr_tcc = Term->scr->tc[y];
1663
1664 /* Wipe each column */
1665 for (x = 0; x < w; x++)
1666 {
1667 scr_aa[x] = na;
1668 scr_cc[x] = nc;
1669
1670 scr_taa[x] = 0;
1671 scr_tcc[x] = 0;
1672 }
1673
1674 /* This row has changed */
1675 Term->x1[y] = 0;
1676 Term->x2[y] = w - 1;
1677 }
1678
1679 /* Every row has changed */
1680 Term->y1 = 0;
1681 Term->y2 = h - 1;
1682
1683 /* Force "total erase" */
1684 Term->total_erase = TRUE;
1685 }
1686
1687
1688
1689 /*
1690 * Redraw (and refresh) the whole window.
1691 */
Term_redraw(void)1692 void Term_redraw(void)
1693 {
1694 /* Force "total erase" */
1695 Term->total_erase = TRUE;
1696
1697 /* Hack -- Refresh */
1698 Term_fresh();
1699 }
1700
1701
1702 /*
1703 * Redraw part of a window.
1704 */
Term_redraw_section(int x1,int y1,int x2,int y2)1705 errr Term_redraw_section(int x1, int y1, int x2, int y2)
1706 {
1707 int i;
1708
1709 bool redraw_cursor = FALSE;
1710
1711 term_win *old = Term->old;
1712
1713 /* Hack - make bigtile work */
1714 if (Term->scr->big_x1 != -1)
1715 {
1716 x1 = (x1 - 1) / 2;
1717 x2 = x2 * 2;
1718 }
1719
1720 /* Bounds checking */
1721 if (y2 >= Term->hgt) y2 = Term->hgt - 1;
1722 if (x2 >= Term->wid) x2 = Term->wid - 1;
1723 if (y1 < 0) y1 = 0;
1724 if (x1 < 0) x1 = 0;
1725
1726 /* Set y limits */
1727 if (Term->y1 > y1) Term->y1 = y1;
1728 if (Term->y2 < y2) Term->y2 = y2;
1729
1730 /* Set the x limits */
1731 for (i = y1; i <= y2; i++)
1732 {
1733 if (Term->x1[i] > x1) Term->x1[i] = x1;
1734 if (Term->x2[i] < x2) Term->x2[i] = x2;
1735 }
1736
1737 if ((old->cx >= Term->x1[old->cy]) && (old->cx <= Term->x2[old->cy]) &&
1738 (old->cy >= Term->y1) && (old->cy <= Term->y2))
1739 {
1740 /* Hack -- clear all "cursor" data */
1741 old->cv = 0;
1742 old->cu = 0;
1743 old->cx = 0;
1744 old->cy = 0;
1745
1746 redraw_cursor = TRUE;
1747 }
1748
1749 /* Refresh */
1750 Term->total_erase = TRUE;
1751 Term_fresh_section();
1752 Term->total_erase = FALSE;
1753
1754 if (redraw_cursor) Term_fresh_cursor();
1755
1756 /* Actually flush the output */
1757 Term_xtra(TERM_XTRA_FRESH, 0);
1758
1759 /* Success */
1760 return (0);
1761 }
1762
1763
1764
1765 /*** Access routines ***/
1766
1767
1768 /*
1769 * Extract the cursor visibility
1770 */
Term_get_cursor(int * v)1771 errr Term_get_cursor(int *v)
1772 {
1773 /* Extract visibility */
1774 (*v) = Term->scr->cv;
1775
1776 /* Success */
1777 return (0);
1778 }
1779
1780
1781 /*
1782 * Extract the current window size
1783 */
Term_get_size(int * w,int * h)1784 void Term_get_size(int *w, int *h)
1785 {
1786 /* Access the cursor */
1787 (*w) = Term->wid;
1788 (*h) = Term->hgt;
1789 }
1790
1791
1792 /*
1793 * Extract the current cursor location
1794 */
Term_locate(int * x,int * y)1795 errr Term_locate(int *x, int *y)
1796 {
1797 /* Access the cursor */
1798 (*x) = Term->scr->cx;
1799 (*y) = Term->scr->cy;
1800
1801 /* Warn about "useless" cursor */
1802 if (Term->scr->cu) return (1);
1803
1804 /* Success */
1805 return (0);
1806 }
1807
1808
1809 /*
1810 * At a given location, determine the "current" attr and char
1811 * Note that this refers to what will be on the window after the
1812 * next call to "Term_fresh()". It may or may not already be there.
1813 */
Term_what(int x,int y,byte * a,char * c)1814 errr Term_what(int x, int y, byte *a, char *c)
1815 {
1816 int w = Term->wid;
1817 int h = Term->hgt;
1818
1819 /* Verify location */
1820 if ((x < 0) || (x >= w)) return (-1);
1821 if ((y < 0) || (y >= h)) return (-1);
1822
1823 /* Direct access */
1824 (*a) = Term->scr->a[y][x];
1825 (*c) = Term->scr->c[y][x];
1826
1827 /* Success */
1828 return (0);
1829 }
1830
1831
1832
1833 /*** Input routines ***/
1834
1835
1836 /*
1837 * Flush and forget the input
1838 */
Term_flush(void)1839 void Term_flush(void)
1840 {
1841 /* Hack -- Flush all events */
1842 Term_xtra(TERM_XTRA_FLUSH, 0);
1843
1844 /* Forget all keypresses */
1845 Term->key_head = Term->key_tail = 0;
1846 }
1847
1848
1849
1850 /*
1851 * Add a keypress to the "queue"
1852 */
Term_keypress(int k)1853 errr Term_keypress(int k)
1854 {
1855 /* Hack -- Refuse to enqueue non-keys */
1856 if (!k) return (-1);
1857
1858 /* Store the char, advance the queue */
1859 Term->key_queue[Term->key_head++] = k;
1860
1861 /* Circular queue, handle wrap */
1862 if (Term->key_head == Term->key_size) Term->key_head = 0;
1863
1864 /* Success (unless overflow) */
1865 if (Term->key_head != Term->key_tail) return (0);
1866
1867 #if 0
1868 /* Hack -- Forget the oldest key */
1869 if (++Term->key_tail == Term->key_size) Term->key_tail = 0;
1870 #endif
1871
1872 /* Problem */
1873 return (1);
1874 }
1875
1876
1877 /*
1878 * Add a keypress to the FRONT of the "queue"
1879 */
Term_key_push(int k)1880 errr Term_key_push(int k)
1881 {
1882 /* Hack -- Refuse to enqueue non-keys */
1883 if (!k) return (-1);
1884
1885 /* Hack -- Overflow may induce circular queue */
1886 if (Term->key_tail == 0) Term->key_tail = Term->key_size;
1887
1888 /* Back up, Store the char */
1889 Term->key_queue[--Term->key_tail] = k;
1890
1891 /* Success (unless overflow) */
1892 if (Term->key_head != Term->key_tail) return (0);
1893
1894 #if 0
1895 /* Hack -- Forget the oldest key */
1896 if (++Term->key_tail == Term->key_size) Term->key_tail = 0;
1897 #endif
1898
1899 /* Problem */
1900 return (1);
1901 }
1902
1903
1904
1905
1906
1907 /*
1908 * Check for a pending keypress on the key queue.
1909 *
1910 * Store the keypress, if any, in "ch", and return "0".
1911 * Otherwise store "zero" in "ch", and return "1".
1912 *
1913 * Wait for a keypress if "wait" is true.
1914 *
1915 * Remove the keypress if "take" is true.
1916 */
Term_inkey(char * ch,bool wait,bool take)1917 errr Term_inkey(char *ch, bool wait, bool take)
1918 {
1919 /* Assume no key */
1920 (*ch) = '\0';
1921
1922 /* Hack -- get bored */
1923 if (!Term->never_bored)
1924 {
1925 /* Process random events */
1926 Term_xtra(TERM_XTRA_BORED, 0);
1927 }
1928
1929 /* Wait */
1930 if (wait)
1931 {
1932 /* Process pending events while necessary */
1933 while (Term->key_head == Term->key_tail)
1934 {
1935 /* Process events (wait for one) */
1936 Term_xtra(TERM_XTRA_EVENT, TRUE);
1937 }
1938 }
1939
1940 /* Do not Wait */
1941 else
1942 {
1943 /* Process pending events if necessary */
1944 if (Term->key_head == Term->key_tail)
1945 {
1946 /* Process events (do not wait) */
1947 Term_xtra(TERM_XTRA_EVENT, FALSE);
1948 }
1949 }
1950
1951 /* No keys are ready */
1952 if (Term->key_head == Term->key_tail) return (1);
1953
1954 /* Extract the next keypress */
1955 (*ch) = Term->key_queue[Term->key_tail];
1956
1957 /* If requested, advance the queue, wrap around if necessary */
1958 if (take && (++Term->key_tail == Term->key_size)) Term->key_tail = 0;
1959
1960 /* Success */
1961 return (0);
1962 }
1963
1964
1965
1966 /*** Extra routines ***/
1967
1968
1969 /*
1970 * Save the "requested" screen into "memorized" screen list
1971 *
1972 * Every "Term_save()" should match exactly one "Term_load()"
1973 */
Term_save(void)1974 void Term_save(void)
1975 {
1976 int w = Term->wid;
1977 int h = Term->hgt;
1978
1979 term_win *tmp;
1980
1981 /* Allocate window */
1982 MAKE(tmp, term_win);
1983
1984 /* Initialize window */
1985 (void)term_win_init(tmp, w, h);
1986
1987 /* Grab */
1988 (void)term_win_copy(tmp, Term->scr, w, h);
1989
1990 /* Add the front of the list */
1991 tmp->next = Term->scr;
1992 Term->scr = tmp;
1993
1994 /* Wipe bigtile regions as required */
1995 Term->scr->wipe_bigtile = TRUE;
1996 }
1997
1998
1999 /*
2000 * Restore the "requested" contents (see above).
2001 *
2002 * Every "Term_save()" should match exactly one "Term_load()"
2003 */
Term_load(void)2004 void Term_load(void)
2005 {
2006 int y;
2007
2008 int w = Term->wid;
2009 int h = Term->hgt;
2010
2011 term_win *tmp;
2012
2013 /* Pop off window from the list */
2014 if (Term->scr->next)
2015 {
2016 /* Hack - is bigtile not on? */
2017 if ((Term->scr->big_x1 == -1) && (Term->scr->next->big_x1 != -1))
2018 {
2019 /* Erase term so boundaries are cleared properly. */
2020 Term_clear();
2021 Term_redraw();
2022 }
2023
2024 /* Save pointer to old window */
2025 tmp = Term->scr;
2026
2027 /* Point to new window */
2028 Term->scr = Term->scr->next;
2029
2030 /* Free the old window */
2031 (void)term_win_nuke(tmp);
2032
2033 /* Kill */
2034 KILL(tmp);
2035 }
2036
2037 /* Assume change */
2038 for (y = 0; y < h; y++)
2039 {
2040 /* Assume change */
2041 Term->x1[y] = 0;
2042 Term->x2[y] = w - 1;
2043 }
2044
2045 /* Assume change */
2046 Term->y1 = 0;
2047 Term->y2 = h - 1;
2048
2049 /* Hack - is bigtile on? */
2050 if (Term->scr->big_x1 != -1)
2051 {
2052 /* Redraw term */
2053 Term_redraw();
2054 }
2055 }
2056
2057
2058 /*
2059 * React to a new physical window size.
2060 */
Term_resize(int w,int h)2061 errr Term_resize(int w, int h)
2062 {
2063 int i;
2064
2065 int wid, hgt;
2066
2067 byte *hold_x1;
2068 byte *hold_x2;
2069
2070 term_win *scr;
2071
2072 /* Resizing is forbidden */
2073 if (Term->fixed_shape) return (-1);
2074
2075 /* Ignore illegal changes */
2076 if ((w < 1) || (h < 1)) return (-1);
2077
2078
2079 /* Ignore non-changes */
2080 if ((Term->wid == w) && (Term->hgt == h)) return (1);
2081
2082
2083 /* Minimum dimensions */
2084 wid = MIN(Term->wid, w);
2085 hgt = MIN(Term->hgt, h);
2086
2087 /* Resize old window */
2088 (void) Term_resize_win(w, h, wid, hgt, Term->old);
2089
2090 /* Resize current window and the previous list */
2091 for (scr = Term->scr; scr; scr = scr->next)
2092 {
2093 Term_resize_win(w, h, wid, hgt, scr);
2094 }
2095
2096 /* Save scanners */
2097 hold_x1 = Term->x1;
2098 hold_x2 = Term->x2;
2099
2100 /* Create new scanners */
2101 C_MAKE(Term->x1, h, byte);
2102 C_MAKE(Term->x2, h, byte);
2103
2104 /* Free some arrays */
2105 KILL(hold_x1);
2106 KILL(hold_x2);
2107
2108 /* Save new size */
2109 Term->wid = w;
2110 Term->hgt = h;
2111
2112 /* Force "total erase" */
2113 Term->total_erase = TRUE;
2114
2115 /* Assume change */
2116 for (i = 0; i < h; i++)
2117 {
2118 /* Assume change */
2119 Term->x1[i] = 0;
2120 Term->x2[i] = w - 1;
2121 }
2122
2123 /* Assume change */
2124 Term->y1 = 0;
2125 Term->y2 = h - 1;
2126
2127 /* Execute the "resize_hook" hook, if available */
2128 if (Term->resize_hook)
2129 {
2130 Term->resize_hook();
2131 }
2132
2133 /* Success */
2134 return (0);
2135 }
2136
2137
2138
2139 /*
2140 * Activate a new Term (and deactivate the current Term)
2141 *
2142 * This function is extremely important, and also somewhat bizarre.
2143 * It is the only function that should "modify" the value of "Term".
2144 *
2145 * To "create" a valid "term", one should do "term_init(t)", then
2146 * set the various flags and hooks, and then do "Term_activate(t)".
2147 */
Term_activate(term * t)2148 void Term_activate(term *t)
2149 {
2150 /* Hack -- already done */
2151 if (Term == t) return;
2152
2153 /* Deactivate the old Term */
2154 if (Term) Term_xtra(TERM_XTRA_LEVEL, 0);
2155
2156 /* Hack -- Call the special "init" hook */
2157 if (t && !t->active_flag)
2158 {
2159 /* Call the "init" hook */
2160 if (t->init_hook) (*t->init_hook) (t);
2161
2162 /* Remember */
2163 t->active_flag = TRUE;
2164
2165 /* Assume mapped */
2166 t->mapped_flag = TRUE;
2167 }
2168
2169 /* Remember the Term */
2170 Term = t;
2171
2172 /* Activate the new Term */
2173 if (Term) Term_xtra(TERM_XTRA_LEVEL, 1);
2174 }
2175
2176
2177
2178 /*
2179 * Nuke a term
2180 */
term_nuke(term * t)2181 errr term_nuke(term *t)
2182 {
2183 term_win *tmp, *tmp2;
2184
2185 /* Hack -- Call the special "nuke" hook */
2186 if (t->active_flag)
2187 {
2188 /* Call the "nuke" hook */
2189 if (t->nuke_hook) (*t->nuke_hook) (t);
2190
2191 /* Remember */
2192 t->active_flag = FALSE;
2193
2194 /* Assume not mapped */
2195 t->mapped_flag = FALSE;
2196 }
2197
2198
2199 /* Nuke "displayed" */
2200 (void)term_win_nuke(t->old);
2201
2202 /* Kill "displayed" */
2203 KILL(t->old);
2204
2205 for (tmp = t->scr; tmp; tmp = tmp2)
2206 {
2207 tmp2 = tmp->next;
2208
2209 /* Nuke "requested" */
2210 (void) term_win_nuke(tmp);
2211
2212 /* Kill "requested" */
2213 KILL(tmp);
2214 }
2215
2216 /* Free some arrays */
2217 KILL(t->x1);
2218 KILL(t->x2);
2219
2220 /* Free the input queue */
2221 KILL(t->key_queue);
2222
2223 /* Success */
2224 return (0);
2225 }
2226
2227
2228 /*
2229 * Initialize a term, using a window of the given size.
2230 * Also prepare the "input queue" for "k" keypresses
2231 * By default, the cursor starts out "invisible"
2232 * By default, we "erase" using "black spaces"
2233 */
term_init(term * t,int w,int h,int k)2234 errr term_init(term *t, int w, int h, int k)
2235 {
2236 int y;
2237
2238 /* Wipe it */
2239 (void)WIPE(t, term);
2240
2241
2242 /* Prepare the input queue */
2243 t->key_head = t->key_tail = 0;
2244
2245 /* Determine the input queue size */
2246 t->key_size = k;
2247
2248 /* Allocate the input queue */
2249 C_MAKE(t->key_queue, t->key_size, char);
2250
2251
2252 /* Save the size */
2253 t->wid = w;
2254 t->hgt = h;
2255
2256 /* Allocate change arrays */
2257 C_MAKE(t->x1, h, byte);
2258 C_MAKE(t->x2, h, byte);
2259
2260
2261 /* Allocate "displayed" */
2262 MAKE(t->old, term_win);
2263
2264 /* Initialize "displayed" */
2265 (void)term_win_init(t->old, w, h);
2266
2267
2268 /* Allocate "requested" */
2269 MAKE(t->scr, term_win);
2270
2271 /* Initialize "requested" */
2272 (void)term_win_init(t->scr, w, h);
2273
2274
2275 /* Assume change */
2276 for (y = 0; y < h; y++)
2277 {
2278 /* Assume change */
2279 t->x1[y] = 0;
2280 t->x2[y] = w - 1;
2281 }
2282
2283 /* Assume change */
2284 t->y1 = 0;
2285 t->y2 = h - 1;
2286
2287 /* Force "total erase" */
2288 t->total_erase = TRUE;
2289
2290
2291 /* Default "blank" */
2292 t->attr_blank = 0;
2293 t->char_blank = ' ';
2294
2295
2296 /* Success */
2297 return (0);
2298 }
2299
2300
2301 /*
2302 * Make a region of a term 'bigtiled'
2303 */
Term_bigregion(int x1,int y1,int y2)2304 errr Term_bigregion(int x1, int y1, int y2)
2305 {
2306 /* Save region */
2307 Term->scr->big_x1 = x1;
2308 Term->scr->big_y1 = y1;
2309 Term->scr->big_y2 = y2;
2310
2311 /* Success */
2312 return (0);
2313 }
2314