2 * @brief Purpose: a generic, efficient, terminal window package -BEN-
3 * Copyright (c) 1997 Ben Harrison
5 * This software may be copied and distributed for educational, research,
6 * and not for profit purposes provided that this copyright and statement
7 * are included in all such copies.
10 #include "term/z-term.h"
11 #include "game-option/map-screen-options.h"
12 #include "game-option/runtime-arguments.h"
13 #include "game-option/special-options.h"
14 #include "term/gameterm.h"
15 #include "term/term-color-types.h"
16 #include "term/z-virt.h"
18 /* Special flags in the attr data */
19 #define AF_BIGTILE2 0xf0
25 * 属性に全角文字の1バイト目、2バイト目も記憶。
28 #define AF_KANJI1 0x10
29 #define AF_KANJI2 0x20
30 #define AF_KANJIC 0x0f
33 /* The current "term" */
34 term_type *Term = nullptr;
36 /*** Local routines ***/
39 * Initialize a "term_win" (using the given window size)
41 term_win::term_win(TERM_LEN w, TERM_LEN h)
42 : a(h, std::vector<TERM_COLOR>(w))
43 , c(h, std::vector<char>(w))
44 , ta(h, std::vector<TERM_COLOR>(w))
45 , tc(h, std::vector<char>(w))
49 std::unique_ptr<term_win> term_win::create(TERM_LEN w, TERM_LEN h)
51 // privateコンストラクタを呼び出すための補助クラス
52 struct impl : term_win {
53 impl(TERM_LEN w, TERM_LEN h)
58 return std::make_unique<impl>(w, h);
61 std::unique_ptr<term_win> term_win::clone() const
63 return std::make_unique<term_win>(*this);
66 void term_win::resize(TERM_LEN w, TERM_LEN h)
68 /* Ignore non-changes */
69 if (this->a.size() == static_cast<size_t>(h) && this->a[0].size() == static_cast<size_t>(w))
72 this->a.resize(h, std::vector<TERM_COLOR>(w));
73 this->c.resize(h, std::vector<char>(w));
74 this->ta.resize(h, std::vector<TERM_COLOR>(w));
75 this->tc.resize(h, std::vector<char>(w));
77 for (TERM_LEN y = 0; y < h; y++) {
80 this->ta[y].resize(w);
81 this->tc[y].resize(w);
91 /*** External hooks ***/
94 * Execute the "Term->user_hook" hook, if available (see above).
103 return ((*Term->user_hook)(n));
107 * Execute the "Term->xtra_hook" hook, if available (see above).
109 errr term_xtra(int n, int v)
111 /* Verify the hook */
112 if (!Term->xtra_hook)
116 return ((*Term->xtra_hook)(n, v));
122 * Fake hook for "term_curs()" (see above)
124 static errr term_curs_hack(TERM_LEN x, TERM_LEN y)
134 * Fake hook for "term_bigcurs()" (see above)
136 static errr term_bigcurs_hack(TERM_LEN x, TERM_LEN y)
138 return (*Term->curs_hook)(x, y);
142 * Fake hook for "term_wipe()" (see above)
144 static errr term_wipe_hack(TERM_LEN x, TERM_LEN y, int n)
155 * Fake hook for "term_text()" (see above)
157 static errr term_text_hack(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
170 * Fake hook for "term_pict()" (see above)
172 static errr term_pict_hack(TERM_LEN x, TERM_LEN y, int n, const TERM_COLOR *ap, concptr cp, const TERM_COLOR *tap, concptr tcp)
186 /*** Efficient routines ***/
189 * Mentally draw an attr/char at a given location
190 * Assumes given location and values are valid.
192 void term_queue_char(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c, TERM_COLOR ta, char tc)
194 const auto &scrn = Term->scr;
196 TERM_COLOR *scr_aa = &scrn->a[y][x];
197 char *scr_cc = &scrn->c[y][x];
199 TERM_COLOR *scr_taa = &scrn->ta[y][x];
200 char *scr_tcc = &scrn->tc[y][x];
202 /* Ignore non-changes */
203 if ((*scr_aa == a) && (*scr_cc == c) && (*scr_taa == ta) && (*scr_tcc == tc))
206 /* Save the "literal" information */
213 /* Check for new min/max row info */
219 /* Check for new min/max col info for this row */
226 if (((scrn->a[y][x] & AF_BIGTILE2) == AF_BIGTILE2) || (scrn->a[y][x] & AF_KANJI2))
228 if ((scrn->a[y][x] & AF_BIGTILE2) == AF_BIGTILE2)
230 if ((x - 1) < Term->x1[y])
235 * Bigtile version of term_queue_char().
236 * If use_bigtile is FALSE, simply call term_queue_char().
237 * Otherwise, mentally draw a pair of attr/char at a given location.
238 * Assumes given location and values are valid.
240 void term_queue_bigchar(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c, TERM_COLOR ta, char tc)
244 * A table which relates each ascii character to a multibyte
247 * 「■」は二倍幅豆腐の内部コードに使用。
249 static char ascii_to_zenkaku[] = " !”#$%&’()*+,-./"
260 /* If non bigtile mode, call orginal function */
262 term_queue_char(x, y, a, c, ta, tc);
266 /* A tile becomes a Bigtile */
267 if ((a & AF_TILE1) && (c & 0x80)) {
268 /* Mark it as a Bigtile */
273 /* Ignore non-tile background */
274 if (!((ta & AF_TILE1) && (tc & 0x80))) {
282 * Use a multibyte character instead of a dirty pair of ASCII
285 else if (' ' <= c) /* isprint(c) */
287 c2 = ascii_to_zenkaku[2 * (c - ' ') + 1];
288 c = ascii_to_zenkaku[2 * (c - ' ')];
290 /* Mark it as a Kanji */
297 /* Dirty pair of ASCII characters */
302 /* Display pair of attr/char */
303 term_queue_char(x, y, a, c, ta, tc);
304 term_queue_char(x + 1, y, a2, c2, 0, 0);
308 * Mentally draw a string of attr/chars at a given location
309 * Assumes given location and values are valid.
310 * This function is designed to be fast, with no consistancy checking.
311 * It is used to update the map in the game.
313 void term_queue_line(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR *a, char *c, TERM_COLOR *ta, char *tc)
315 const auto &scrn = Term->scr;
320 TERM_COLOR *scr_aa = &scrn->a[y][x];
321 char *scr_cc = &scrn->c[y][x];
323 TERM_COLOR *scr_taa = &scrn->ta[y][x];
324 char *scr_tcc = &scrn->tc[y][x];
327 /* Ignore non-changes */
328 if ((*scr_aa == *a) && (*scr_cc == *c) && (*scr_taa == *ta) && (*scr_tcc == *tc)) {
341 /* Save the "literal" information */
345 /* Save the "literal" information */
349 /* Track minimum changed column */
353 /* Track maximum changed column */
359 /* Expand the "change area" as needed */
361 /* Check for new min/max row info */
367 /* Check for new min/max col info in this row */
368 if (x1 < Term->x1[y])
370 if (x2 > Term->x2[y])
376 * Mentally draw some attr/chars at a given location
378 * Assumes that (x,y) is a valid location, that the first "n" characters
379 * of the string "s" are all valid (non-zero), and that (x+n-1,y) is also
380 * a valid location, so the first "n" characters of "s" can all be added
381 * starting at (x,y) without causing any illegal operations.
383 static void term_queue_chars(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr s)
385 TERM_LEN x1 = -1, x2 = -1;
387 auto &scr_aa = Term->scr->a[y];
389 auto &scr_cc = Term->scr->c[y];
391 auto &scr_taa = Term->scr->ta[y];
392 auto &scr_tcc = Term->scr->tc[y];
394 auto &scr_cc = Term->scr->c[y];
396 auto &scr_taa = Term->scr->ta[y];
397 auto &scr_tcc = Term->scr->tc[y];
402 if (n == 0 || *s == 0)
405 * 全角文字の右半分から文字を表示する場合、
409 if ((scr_aa[x] & AF_KANJI2) && (scr_aa[x] & AF_BIGTILE2) != AF_BIGTILE2) {
411 scr_aa[x - 1] &= AF_KANJIC;
415 /* Queue the attr/chars */
416 for (; n; x++, s++, n--) {
418 /* 特殊文字としてMSBが立っている可能性がある */
419 /* その場合attrのMSBも立っているのでこれで識別する */
421 if (!(a & AF_TILE1) && iskanji(*s)) {
425 byte na1 = (a | AF_KANJI1);
426 byte na2 = (a | AF_KANJI2);
428 if ((--n == 0) || !nc2)
431 if (scr_aa[x++] == na1 && scr_aa[x] == na2 && scr_cc[x - 1] == nc1 && scr_cc[x] == nc2 && (scr_taa[x - 1] == 0) && (scr_taa[x] == 0)
432 && (scr_tcc[x - 1] == 0) && (scr_tcc[x] == 0))
445 TERM_COLOR oa = scr_aa[x];
448 TERM_COLOR ota = scr_taa[x];
449 char otc = scr_tcc[x];
451 /* Ignore non-changes */
452 if ((oa == a) && (oc == *s) && (ota == 0) && (otc == 0))
455 /* Save the "literal" information */
462 /* Note the "range" of window updates */
473 * 全角文字の左半分で表示を終了する場合、
475 * (条件追加:タイルの1文字目でない事を確かめるように。)
479 term_get_size(&w, &h);
480 if (x != w && !(scr_aa[x] & AF_TILE1) && (scr_aa[x] & AF_KANJI2)) {
482 scr_aa[x] &= AF_KANJIC;
489 /* Expand the "change area" as needed */
491 /* Check for new min/max row info */
497 /* Check for new min/max col info in this row */
498 if (x1 < Term->x1[y])
500 if (x2 > Term->x2[y])
505 /*** Refresh routines ***/
508 * Flush a row of the current window (see "term_fresh")
509 * Display text using "term_pict()"
511 static void term_fresh_row_pict(TERM_LEN y, TERM_LEN x1, TERM_LEN x2)
513 auto &old_aa = Term->old->a[y];
514 auto &old_cc = Term->old->c[y];
516 const auto &scr_aa = Term->scr->a[y];
517 const auto &scr_cc = Term->scr->c[y];
519 auto &old_taa = Term->old->ta[y];
520 auto &old_tcc = Term->old->tc[y];
522 const auto &scr_taa = Term->scr->ta[y];
523 const auto &scr_tcc = Term->scr->tc[y];
547 /* Scan "modified" columns */
548 for (TERM_LEN x = x1; x <= x2; x++) {
549 /* See what is currently here */
553 /* See what is desired there */
566 /* 特殊文字としてMSBが立っている可能性がある */
567 /* その場合attrのMSBも立っているのでこれで識別する */
569 kanji = (iskanji(nc) && !(na & AF_TILE1));
578 /* Handle unchanged grids */
580 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc)
582 || (scr_aa[x + 1] == old_aa[x + 1] && scr_cc[x + 1] == old_cc[x + 1] && scr_taa[x + 1] == old_taa[x + 1] && scr_tcc[x + 1] == old_tcc[x + 1])))
584 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc))
589 /* Draw pending attr/char pairs */
590 (void)((*Term->pict_hook)(fx, y, fn, &scr_aa[fx], &scr_cc[fx], &scr_taa[fx], &scr_tcc[fx]));
607 /* Save new contents */
614 /* Restart and Advance */
621 /* Draw pending attr/char pairs */
622 (void)((*Term->pict_hook)(fx, y, fn, &scr_aa[fx], &scr_cc[fx], &scr_taa[fx], &scr_tcc[fx]));
627 * Flush a row of the current window (see "term_fresh")
629 * Display text using "term_text()" and "term_wipe()",
630 * but use "term_pict()" for high-bit attr/char pairs
632 static void term_fresh_row_both(TERM_LEN y, int x1, int x2)
634 auto &old_aa = Term->old->a[y];
635 auto &old_cc = Term->old->c[y];
637 const auto &scr_aa = Term->scr->a[y];
638 const auto &scr_cc = Term->scr->c[y];
640 auto &old_taa = Term->old->ta[y];
641 auto &old_tcc = Term->old->tc[y];
642 const auto &scr_taa = Term->scr->ta[y];
643 const auto &scr_tcc = Term->scr->tc[y];
650 /* The "always_text" flag */
651 int always_text = Term->always_text;
660 byte fa = Term->attr_blank;
672 /* Scan "modified" columns */
673 for (TERM_LEN x = x1; x <= x2; x++) {
674 /* See what is currently here */
678 /* See what is desired there */
691 /* 特殊文字としてMSBが立っている可能性がある */
692 /* その場合attrのMSBも立っているのでこれで識別する */
694 /* kanji = (iskanji(nc)); */
695 kanji = (iskanji(nc) && !(na & AF_TILE1));
704 /* Handle unchanged grids */
706 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc)
708 || (scr_aa[x + 1] == old_aa[x + 1] && scr_cc[x + 1] == old_cc[x + 1] && scr_taa[x + 1] == old_taa[x + 1] && scr_tcc[x + 1] == old_tcc[x + 1])))
710 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc))
715 /* Draw pending chars (normal) */
716 if (fa || always_text) {
717 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
720 /* Draw pending chars (black) */
722 (void)((*Term->wipe_hook)(fx, y, fn));
741 /* Save new contents */
748 /* 2nd byte of bigtile */
749 if ((na & AF_BIGTILE2) == AF_BIGTILE2)
752 /* Handle high-bit attr/chars */
753 if ((na & AF_TILE1) && (nc & 0x80)) {
756 /* Draw pending chars (normal) */
757 if (fa || always_text) {
758 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
761 /* Draw pending chars (black) */
763 (void)((*Term->wipe_hook)(fx, y, fn));
770 /* Draw the special attr/char pair */
771 (void)((*Term->pict_hook)(x, y, 1, &na, &nc, &nta, &ntc));
777 /* Notice new color */
779 if (fa != (na & AF_KANJIC))
787 /* Draw the pending chars */
788 if (fa || always_text) {
789 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
792 /* Erase "leading" spaces */
794 (void)((*Term->wipe_hook)(fx, y, fn));
801 /* Save the new color */
803 fa = (na & AF_KANJIC);
809 /* Restart and Advance */
816 /* Draw pending chars (normal) */
817 if (fa || always_text) {
818 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
821 /* Draw pending chars (black) */
823 (void)((*Term->wipe_hook)(fx, y, fn));
829 * Flush a row of the current window (see "term_fresh")
831 * Display text using "term_text()" and "term_wipe()"
833 static void term_fresh_row_text(TERM_LEN y, TERM_LEN x1, TERM_LEN x2)
835 auto &old_aa = Term->old->a[y];
836 auto &old_cc = Term->old->c[y];
838 const auto &scr_aa = Term->scr->a[y];
839 const auto &scr_cc = Term->scr->c[y];
841 /* The "always_text" flag */
842 int always_text = Term->always_text;
851 byte fa = Term->attr_blank;
863 for (TERM_LEN x = 0; x < x1; x++)
864 if (!(old_aa[x] & AF_TILE1) && iskanji(old_cc[x])) {
872 /* Scan "modified" columns */
873 for (TERM_LEN x = x1; x <= x2; x++) {
874 /* See what is currently here */
878 /* See what is desired there */
891 /* 特殊文字としてMSBが立っている可能性がある */
892 /* その場合attrのMSBも立っているのでこれで識別する */
894 kanji = (iskanji(nc) && !(na & AF_TILE1));
896 /* Handle unchanged grids */
898 if ((na == oa) && (nc == oc) && (!kanji || (scr_aa[x + 1] == old_aa[x + 1] && scr_cc[x + 1] == old_cc[x + 1])))
900 if ((na == oa) && (nc == oc))
906 /* Draw pending chars (normal) */
907 if (fa || always_text) {
908 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
911 /* Draw pending chars (black) */
913 (void)((*Term->wipe_hook)(fx, y, fn));
932 /* Save new contents */
936 /* Notice new color */
938 if (fa != (na & AF_KANJIC))
946 /* Draw the pending chars */
947 if (fa || always_text) {
948 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
951 /* Erase "leading" spaces */
953 (void)((*Term->wipe_hook)(fx, y, fn));
960 /* Save the new color */
962 fa = (na & AF_KANJIC);
968 /* Restart and Advance */
975 /* Draw pending chars (normal) */
976 if (fa || always_text) {
977 (void)((*Term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
980 /* Draw pending chars (black) */
982 (void)((*Term->wipe_hook)(fx, y, fn));
988 * @brief Actually perform all requested changes to the window
990 errr term_fresh(void)
998 const auto &old = Term->old;
999 const auto &scr = Term->scr;
1001 /* Before initialize (Advice from Mr.shimitei)*/
1005 if (Term->never_fresh)
1008 /* Do nothing unless "mapped" */
1009 if (!Term->mapped_flag)
1012 /* Trivial Refresh */
1013 if ((y1 > y2) && (scr->cu == old->cu) && (scr->cv == old->cv) && (scr->cx == old->cx) && (scr->cy == old->cy) && !(Term->total_erase)) {
1018 /* Handle "total erase" */
1019 if (Term->total_erase) {
1020 byte na = Term->attr_blank;
1021 char nc = Term->char_blank;
1023 /* Physically erase the entire window */
1024 term_xtra(TERM_XTRA_CLEAR, 0);
1026 /* clear all "cursor" data */
1027 old->cv = old->cu = false;
1028 old->cx = old->cy = 0;
1031 for (TERM_LEN y = 0; y < h; y++) {
1032 auto &aa = old->a[y];
1033 auto &cc = old->c[y];
1035 auto &taa = old->ta[y];
1036 auto &tcc = old->tc[y];
1038 /* Wipe each column */
1039 for (TERM_LEN x = 0; x < w; x++) {
1040 /* Wipe each grid */
1049 /* Redraw every row */
1051 Term->y2 = y2 = h - 1;
1053 /* Redraw every column */
1054 for (TERM_LEN y = 0; y < h; y++) {
1056 Term->x2[y] = w - 1;
1059 /* Forget "total erase" */
1060 Term->total_erase = false;
1063 /* Cursor update -- Erase old Cursor */
1064 if (Term->soft_cursor) {
1065 /* Cursor was visible */
1066 if (!old->cu && old->cv) {
1068 TERM_LEN tx = old->cx;
1069 TERM_LEN ty = old->cy;
1071 const auto &old_aa = old->a[ty];
1072 const auto &old_cc = old->c[ty];
1074 const auto &old_taa = old->ta[ty];
1075 const auto &old_tcc = old->tc[ty];
1077 TERM_COLOR ota = old_taa[tx];
1078 char otc = old_tcc[tx];
1081 if (tx + 1 < Term->wid && !(old_aa[tx] & AF_TILE1) && iskanji(old_cc[tx]))
1084 /* Use "term_pict()" always */
1085 if (Term->always_pict)
1086 (void)((*Term->pict_hook)(tx, ty, csize, &old_aa[tx], &old_cc[tx], &ota, &otc));
1088 /* Use "term_pict()" sometimes */
1089 else if (Term->higher_pict && (old_aa[tx] & AF_TILE1) && (old_cc[tx] & 0x80))
1090 (void)((*Term->pict_hook)(tx, ty, 1, &old_aa[tx], &old_cc[tx], &ota, &otc));
1093 * Restore the actual character
1094 * 元の文字の描画範囲がカーソルより小さいと、
1095 * 上書きされなかった部分がゴミとして残る。
1096 * wipe_hook でカーソルを消去して text_hook で書き直す。
1098 else if (old_aa[tx] || Term->always_text) {
1099 (void)((*Term->wipe_hook)(tx, ty, 1));
1100 (void)((*Term->text_hook)(tx, ty, csize, (unsigned char)(old_aa[tx] & 0xf), &old_cc[tx]));
1103 /* Erase the grid */
1105 (void)((*Term->wipe_hook)(tx, ty, 1));
1109 /* Hide the hardware cursor while drawing */
1111 /* Cursor will be invisible */
1112 term_xtra(TERM_XTRA_SHAPE, 0);
1115 /* Something to update */
1117 /* Handle "icky corner" */
1118 if (Term->icky_corner) {
1119 /* Avoid the corner */
1121 /* Avoid the corner */
1122 if (Term->x2[h - 1] > w - 2) {
1123 /* Avoid the corner */
1124 Term->x2[h - 1] = w - 2;
1129 /* Scan the "modified" rows */
1130 for (TERM_LEN y = y1; y <= y2; ++y) {
1131 TERM_LEN x1 = Term->x1[y];
1132 TERM_LEN x2 = Term->x2[y];
1134 /* Flush each "modified" row */
1136 /* Always use "term_pict()" */
1137 if (Term->always_pict) {
1139 term_fresh_row_pict(y, x1, x2);
1142 /* Sometimes use "term_pict()" */
1143 else if (Term->higher_pict) {
1145 term_fresh_row_both(y, x1, x2);
1148 /* Never use "term_pict()" */
1151 term_fresh_row_text(y, x1, x2);
1154 /* This row is all done */
1158 /* Flush that row (if allowed) */
1159 if (!Term->never_frosh)
1160 term_xtra(TERM_XTRA_FROSH, y);
1164 /* No rows are invalid */
1169 /* Cursor update -- Show new Cursor */
1170 if (Term->soft_cursor) {
1171 /* Draw the cursor */
1172 if (!scr->cu && scr->cv) {
1174 if ((scr->cx + 1 < w)
1175 && ((old->a[scr->cy][scr->cx + 1] & AF_BIGTILE2) == AF_BIGTILE2
1176 || (!(old->a[scr->cy][scr->cx] & AF_TILE1) && iskanji(old->c[scr->cy][scr->cx]))))
1178 if ((scr->cx + 1 < w) && (old->a[scr->cy][scr->cx + 1] & AF_BIGTILE2) == AF_BIGTILE2)
1181 /* Double width cursor for the Bigtile mode */
1182 (void)((*Term->bigcurs_hook)(scr->cx, scr->cy));
1184 /* Call the cursor display routine */
1185 (void)((*Term->curs_hook)(scr->cx, scr->cy));
1190 /* Cursor Update -- Show new Cursor */
1193 /* Paranoia -- Put the cursor NEAR where it belongs */
1194 (void)((*Term->curs_hook)(w - 1, scr->cy));
1196 /* Put the cursor where it belongs */
1197 (void)((*Term->curs_hook)(scr->cx, scr->cy));
1201 /* Save the "cursor state" */
1207 /* Actually flush the output */
1208 term_xtra(TERM_XTRA_FRESH, 0);
1210 if (!Term->soft_cursor && !scr->cu && scr->cv) {
1211 /* The cursor is visible, display it correctly */
1212 term_xtra(TERM_XTRA_SHAPE, 1);
1219 * @brief never_freshの値を無視して強制的にterm_freshを行う。
1221 errr term_fresh_force(void)
1223 bool old = Term->never_fresh;
1224 Term->never_fresh = false;
1225 errr err = term_fresh();
1226 Term->never_fresh = old;
1230 /*** Output routines ***/
1233 * Set the cursor visibility
1235 errr term_set_cursor(int v)
1238 if (Term->scr->cv == (bool)v)
1242 Term->scr->cv = (bool)v;
1247 * Place the cursor at a given location
1249 * Note -- "illegal" requests do not move the cursor.
1251 errr term_gotoxy(TERM_LEN x, TERM_LEN y)
1257 if ((x < 0) || (x >= w))
1259 if ((y < 0) || (y >= h))
1262 /* Remember the cursor */
1266 /* The cursor is not useless */
1272 * At a given location, place an attr/char
1273 * Do not change the cursor position
1274 * No visual changes until "term_fresh()".
1276 errr term_draw(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c)
1281 if ((x < 0) || (x >= w))
1283 if ((y < 0) || (y >= h))
1286 /* Paranoia -- illegal char */
1290 /* Queue it for later */
1291 term_queue_char(x, y, a, c, 0, 0);
1296 * Using the given attr, add the given char at the cursor.
1298 * We return "-2" if the character is "illegal". XXX XXX
1300 * We return "-1" if the cursor is currently unusable.
1302 * We queue the given attr/char for display at the current
1303 * cursor location, and advance the cursor to the right,
1304 * marking it as unuable and returning "1" if it leaves
1305 * the screen, and otherwise returning "0".
1307 * So when this function, or the following one, return a
1308 * positive value, future calls to either function will
1309 * return negative ones.
1311 errr term_addch(TERM_COLOR a, char c)
1313 TERM_LEN w = Term->wid;
1315 /* Handle "unusable" cursor */
1319 /* Paranoia -- no illegal chars */
1323 /* Queue the given character for display */
1324 term_queue_char(Term->scr->cx, Term->scr->cy, a, c, 0, 0);
1326 /* Advance the cursor */
1330 if (Term->scr->cx < w)
1333 /* Note "Useless" cursor */
1336 /* Note "Useless" cursor */
1341 * Bigtile version of term_addch().
1343 * If use_bigtile is FALSE, simply call term_addch() .
1345 * Otherwise, queue a pair of attr/char for display at the current
1346 * cursor location, and advance the cursor to the right by two.
1348 errr term_add_bigch(TERM_COLOR a, char c)
1351 return term_addch(a, c);
1353 /* Handle "unusable" cursor */
1357 /* Paranoia -- no illegal chars */
1361 /* Queue the given character for display */
1362 term_queue_bigchar(Term->scr->cx, Term->scr->cy, a, c, 0, 0);
1364 /* Advance the cursor */
1368 if (Term->scr->cx < Term->wid)
1371 /* Note "Useless" cursor */
1374 /* Note "Useless" cursor */
1379 * At the current location, using an attr, add a string
1381 * We also take a length "n", using negative values to imply
1382 * the largest possible value, and then we use the minimum of
1383 * this length and the "actual" length of the string as the
1384 * actual number of characters to attempt to display, never
1385 * displaying more characters than will actually fit, since
1386 * we do NOT attempt to "wrap" the cursor at the screen edge.
1388 * We return "-1" if the cursor is currently unusable.
1389 * We return "N" if we were "only" able to write "N" chars,
1390 * even if all of the given characters fit on the screen,
1391 * and mark the cursor as unusable for future attempts.
1393 * So when this function, or the preceding one, return a
1394 * positive value, future calls to either function will
1395 * return negative ones.
1397 errr term_addstr(int n, TERM_COLOR a, concptr s)
1400 TERM_LEN w = Term->wid;
1403 /* Handle "unusable" cursor */
1407 /* Obtain maximal length */
1408 k = (n < 0) ? (w + 1) : n;
1410 /* Obtain the usable string length */
1411 for (n = 0; (n < k) && s[n]; n++) /* loop */
1414 /* React to reaching the edge of the screen */
1415 if (Term->scr->cx + n >= w)
1416 res = n = w - Term->scr->cx;
1418 /* Queue the first "n" characters for display */
1419 term_queue_chars(Term->scr->cx, Term->scr->cy, n, a, s);
1421 /* Advance the cursor */
1424 /* Notice "Useless" cursor */
1432 * Move to a location and, using an attr, add a char
1434 errr term_putch(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c)
1439 if ((res = term_gotoxy(x, y)) != 0)
1442 /* Then add the char */
1443 if ((res = term_addch(a, c)) != 0)
1450 * Move to a location and, using an attr, add a string
1452 errr term_putstr(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr s)
1457 if ((res = term_gotoxy(x, y)) != 0)
1460 /* Then add the string */
1461 if ((res = term_addstr(n, a, s)) != 0)
1468 * Place cursor at (x,y), and clear the next "n" chars
1470 errr term_erase(TERM_LEN x, TERM_LEN y, int n)
1472 TERM_LEN w = Term->wid;
1473 /* int h = Term->hgt; */
1478 int na = Term->attr_blank;
1479 int nc = Term->char_blank;
1482 if (term_gotoxy(x, y))
1485 /* Force legal size */
1490 auto &scr_aa = Term->scr->a[y];
1491 auto &scr_cc = Term->scr->c[y];
1493 auto &scr_taa = Term->scr->ta[y];
1494 auto &scr_tcc = Term->scr->tc[y];
1498 * 全角文字の右半分から文字を表示する場合、
1501 if (n > 0 && (((scr_aa[x] & AF_KANJI2) && !(scr_aa[x] & AF_TILE1)) || (scr_aa[x] & AF_BIGTILE2) == AF_BIGTILE2))
1503 if (n > 0 && (scr_aa[x] & AF_BIGTILE2) == AF_BIGTILE2)
1510 /* Scan every column */
1511 for (int i = 0; i < n; i++, x++) {
1515 /* Ignore "non-changes" */
1516 if ((oa == na) && (oc == nc))
1521 * 全角文字の左半分で表示を終了する場合、
1524 * 2001/04/29 -- Habu
1525 * 行の右端の場合はこの処理をしないように修正。
1527 if ((oa & AF_KANJI1) && (i + 1) == n && x != w - 1)
1530 /* Save the "literal" information */
1531 scr_aa[x] = (byte)na;
1532 scr_cc[x] = (char)nc;
1537 /* Track minimum changed column */
1541 /* Track maximum changed column */
1545 /* Expand the "change area" as needed */
1547 /* Check for new min/max row info */
1553 /* Check for new min/max col info in this row */
1554 if (x1 < Term->x1[y])
1556 if (x2 > Term->x2[y])
1564 * Clear the entire window, and move to the top left corner
1566 * Note the use of the special "total_erase" code
1568 errr term_clear(void)
1570 TERM_LEN w = Term->wid;
1571 TERM_LEN h = Term->hgt;
1573 TERM_COLOR na = Term->attr_blank;
1574 char nc = Term->char_blank;
1579 /* Cursor to the top left */
1580 Term->scr->cx = Term->scr->cy = 0;
1583 for (TERM_LEN y = 0; y < h; y++) {
1584 auto &scr_aa = Term->scr->a[y];
1585 auto &scr_cc = Term->scr->c[y];
1587 auto &scr_taa = Term->scr->ta[y];
1588 auto &scr_tcc = Term->scr->tc[y];
1590 /* Wipe each column */
1591 for (TERM_LEN x = 0; x < w; x++) {
1599 /* This row has changed */
1601 Term->x2[y] = w - 1;
1604 /* Every row has changed */
1608 /* Force "total erase" */
1609 Term->total_erase = true;
1614 * Redraw (and refresh) the whole window.
1616 errr term_redraw(void)
1618 /* Force "total erase" */
1619 Term->total_erase = true;
1625 * Redraw part of a window.
1627 errr term_redraw_section(TERM_LEN x1, TERM_LEN y1, TERM_LEN x2, TERM_LEN y2)
1629 /* Bounds checking */
1630 if (y2 >= Term->hgt)
1632 if (x2 >= Term->wid)
1643 /* Set the x limits */
1644 for (int i = Term->y1; i <= Term->y2; i++) {
1650 if (Term->scr->a[i][x1j] & AF_KANJI2)
1654 if (x2j < Term->wid - 1) {
1655 if (Term->scr->a[i][x2j] & AF_KANJI1)
1662 auto &g_ptr = Term->old->c[i];
1664 /* Clear the section so it is redrawn */
1665 for (int j = x1j; j <= x2j; j++) {
1666 /* Hack - set the old character to "none" */
1673 auto &g_ptr = Term->old->c[i];
1675 /* Clear the section so it is redrawn */
1676 for (int j = x1; j <= x2; j++) {
1677 /* Hack - set the old character to "none" */
1687 /*** Access routines ***/
1690 * Extract the cursor visibility
1692 errr term_get_cursor(int *v)
1694 /* Extract visibility */
1695 (*v) = Term->scr->cv;
1700 * Extract the current window size
1702 errr term_get_size(TERM_LEN *w, TERM_LEN *h)
1704 /* Access the cursor */
1711 * Extract the current cursor location
1713 errr term_locate(TERM_LEN *x, TERM_LEN *y)
1715 /* Access the cursor */
1716 (*x) = Term->scr->cx;
1717 (*y) = Term->scr->cy;
1719 /* Warn about "useless" cursor */
1727 * At a given location, determine the "current" attr and char
1728 * Note that this refers to what will be on the window after the
1729 * next call to "term_fresh()". It may or may not already be there.
1731 errr term_what(TERM_LEN x, TERM_LEN y, TERM_COLOR *a, char *c)
1733 TERM_LEN w = Term->wid;
1734 TERM_LEN h = Term->hgt;
1736 if ((x < 0) || (x >= w))
1738 if ((y < 0) || (y >= h))
1742 (*a) = Term->scr->a[y][x];
1743 (*c) = Term->scr->c[y][x];
1747 /*** Input routines ***/
1750 * Flush and forget the input
1752 errr term_flush(void)
1754 /* Flush all events */
1755 term_xtra(TERM_XTRA_FLUSH, 0);
1757 /* Forget all keypresses */
1758 Term->key_head = Term->key_tail = 0;
1763 * Add a keypress to the FRONT of the "queue"
1765 errr term_key_push(int k)
1767 /* Refuse to enqueue non-keys */
1771 /* Overflow may induce circular queue */
1772 if (Term->key_tail == 0)
1773 Term->key_tail = Term->key_size;
1775 /* Back up, Store the char */
1776 Term->key_queue[--Term->key_tail] = (char)k;
1778 if (Term->key_head != Term->key_tail)
1785 * Check for a pending keypress on the key queue.
1787 * Store the keypress, if any, in "ch", and return "0".
1788 * Otherwise store "zero" in "ch", and return "1".
1790 * Wait for a keypress if "wait" is true.
1792 * Remove the keypress if "take" is true.
1794 errr term_inkey(char *ch, bool wait, bool take)
1800 if (!Term->never_bored) {
1801 /* Process random events */
1802 term_xtra(TERM_XTRA_BORED, 0);
1807 /* Process pending events while necessary */
1808 while (Term->key_head == Term->key_tail) {
1809 /* Process events (wait for one) */
1810 term_xtra(TERM_XTRA_EVENT, true);
1816 /* Process pending events if necessary */
1817 if (Term->key_head == Term->key_tail) {
1818 /* Process events (do not wait) */
1819 term_xtra(TERM_XTRA_EVENT, false);
1823 /* No keys are ready */
1824 if (Term->key_head == Term->key_tail)
1827 /* Extract the next keypress */
1828 (*ch) = Term->key_queue[Term->key_tail];
1830 /* If requested, advance the queue, wrap around if necessary */
1831 if (take && (++Term->key_tail == Term->key_size))
1837 /*** Extra routines ***/
1840 * Save the "requested" screen into the "memorized" screen
1842 * Every "term_save()" should match exactly one "term_load()"
1844 errr term_save(void)
1847 Term->mem_stack.push(Term->scr->clone());
1853 * Restore the "requested" contents (see above).
1855 * Every "term_save()" should match exactly one "term_load()"
1857 errr term_load(bool load_all)
1859 TERM_LEN w = Term->wid;
1860 TERM_LEN h = Term->hgt;
1862 if (Term->mem_stack.empty())
1867 while (Term->mem_stack.size() > 1)
1868 Term->mem_stack.pop();
1872 Term->scr.swap(Term->mem_stack.top());
1873 Term->scr->resize(w, h);
1876 Term->mem_stack.pop();
1879 for (TERM_LEN y = 0; y < h; y++) {
1882 Term->x2[y] = w - 1;
1892 * Exchange the "requested" screen with the "tmp" screen
1894 errr term_exchange(void)
1896 TERM_LEN w = Term->wid;
1897 TERM_LEN h = Term->hgt;
1901 /* Allocate window */
1902 Term->tmp = term_win::create(w, h);
1906 Term->scr.swap(Term->tmp);
1909 for (TERM_LEN y = 0; y < h; y++) {
1912 Term->x2[y] = w - 1;
1922 * React to a new physical window size.
1924 errr term_resize(TERM_LEN w, TERM_LEN h)
1926 /* Resizing is forbidden */
1927 if (Term->fixed_shape)
1930 /* Ignore illegal changes */
1931 if ((w < 1) || (h < 1))
1934 /* Ignore non-changes */
1935 if ((Term->wid == w) && (Term->hgt == h) && (arg_bigtile == use_bigtile))
1938 use_bigtile = arg_bigtile;
1940 /* Resize windows */
1941 Term->old->resize(w, h);
1942 Term->scr->resize(w, h);
1944 Term->tmp->resize(w, h);
1946 /* Resize scanners */
1954 /* Force "total erase" */
1955 Term->total_erase = true;
1958 for (int i = 0; i < h; i++) {
1961 Term->x2[i] = w - 1;
1968 /* Execute the "resize_hook" hook, if available */
1969 if (Term->resize_hook)
1970 Term->resize_hook();
1976 * Activate a new Term (and deactivate the current Term)
1978 * This function is extremely important, and also somewhat bizarre.
1979 * It is the only function that should "modify" the value of "Term".
1981 * To "create" a valid "term", one should do "term_init(t)", then
1982 * set the various flags and hooks, and then do "term_activate(t)".
1984 errr term_activate(term_type *t)
1990 /* Deactivate the old Term */
1992 term_xtra(TERM_XTRA_LEVEL, 0);
1994 /* Call the special "init" hook */
1995 if (t && !t->active_flag) {
1996 /* Call the "init" hook */
2001 t->active_flag = true;
2004 t->mapped_flag = true;
2007 /* Remember the Term */
2010 /* Activate the new Term */
2012 term_xtra(TERM_XTRA_LEVEL, 1);
2018 * Initialize a term, using a window of the given size.
2019 * Also prepare the "input queue" for "k" keypresses
2020 * By default, the cursor starts out "invisible"
2021 * By default, we "erase" using "black spaces"
2023 errr term_init(term_type *t, TERM_LEN w, TERM_LEN h, int k)
2028 /* Prepare the input queue */
2029 t->key_head = t->key_tail = 0;
2031 /* Determine the input queue size */
2032 t->key_size = (uint16_t)k;
2034 /* Allocate the input queue */
2035 t->key_queue.resize(t->key_size);
2041 /* Allocate change arrays */
2045 /* Allocate "displayed" */
2046 t->old = term_win::create(w, h);
2048 /* Allocate "requested" */
2049 t->scr = term_win::create(w, h);
2052 for (TERM_LEN y = 0; y < h; y++) {
2062 /* Force "total erase" */
2063 t->total_erase = true;
2065 /* Default "blank" */
2067 t->char_blank = ' ';
2069 /* Prepare "fake" hooks to prevent core dumps */
2070 t->curs_hook = term_curs_hack;
2071 t->bigcurs_hook = term_bigcurs_hack;
2072 t->wipe_hook = term_wipe_hack;
2073 t->text_hook = term_text_hack;
2074 t->pict_hook = term_pict_hack;
2080 * Move to a location and, using an attr, add a string vertically
2082 errr term_putstr_v(TERM_LEN x, TERM_LEN y, int n, byte a, concptr s)
2087 for (int i = 0; i < n && s[i] != 0; i++) {
2089 if ((res = term_gotoxy(x, y0)) != 0)
2092 if (iskanji(s[i])) {
2093 if ((res = term_addstr(2, a, &s[i])) != 0)
2100 if ((res = term_addstr(1, a, &s[i])) != 0)
2111 errr term_nuke(term_type *t)
2113 if (t->active_flag) {
2117 t->active_flag = false;
2118 t->mapped_flag = false;
2124 while (!t->mem_stack.empty())
2129 t->key_queue.clear();