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 *game_term = nullptr;
36 /*** Local routines ***/
39 * @brief オブジェクトが生存している間、画面表示関数の座標をずらす。
41 * 引数でずらすX座標オフセット、Y座標オフセットをそれぞれ指定する。
42 * 正方向のオフセットのみ有効。負の値が指定された場合、オフセット位置は 0 とする。
43 * 指定された座標が std::nullopt の場合、現在のオフセットを維持する。
48 TermOffsetSetter::TermOffsetSetter(std::optional<TERM_LEN> x, std::optional<TERM_LEN> y)
50 , orig_offset_x(game_term != nullptr ? game_term->offset_x : 0)
51 , orig_offset_y(game_term != nullptr ? game_term->offset_y : 0)
53 if (this->term == nullptr) {
58 this->term->offset_x = (x.value() > 0) ? x.value() : 0;
61 this->term->offset_y = (y.value() > 0) ? y.value() : 0;
65 TermOffsetSetter::~TermOffsetSetter()
67 if (this->term == nullptr) {
71 this->term->offset_x = this->orig_offset_x;
72 this->term->offset_y = this->orig_offset_y;
76 * @brief オブジェクトが生存している間、画面表示関数の座標をずらす。
78 * 表示に使用する領域の大きさを指定し、その領域が画面中央に表示されるように座標をずらす。
79 * 引数で領域の横幅、縦幅をそれぞれ指定する。
80 * 画面の幅より大きな値が指定された場合はオフセット 0 になる。
81 * 指定された幅が std::nullopt の場合、画面の幅全体を使用する(オフセット 0 になる)。
83 * @param width 表示に使用する領域の横幅
84 * @param height 表示に使用する領域の縦幅
86 TermCenteredOffsetSetter::TermCenteredOffsetSetter(std::optional<TERM_LEN> width, std::optional<TERM_LEN> height)
88 , orig_centered_wid(game_term != nullptr ? game_term->centered_wid : std::nullopt)
89 , orig_centered_hgt(game_term != nullptr ? game_term->centered_hgt : std::nullopt)
91 if (game_term == nullptr) {
95 const auto offset_x = width.has_value() ? (game_term->wid - width.value()) / 2 : 0;
96 const auto offset_y = height.has_value() ? (game_term->hgt - height.value()) / 2 : 0;
97 this->tos.emplace(offset_x, offset_y);
99 game_term->centered_wid = (width < game_term->wid) ? width : std::nullopt;
100 game_term->centered_hgt = (height < game_term->hgt) ? height : std::nullopt;
103 TermCenteredOffsetSetter::~TermCenteredOffsetSetter()
105 if (this->term == nullptr) {
109 this->term->centered_wid = this->orig_centered_wid;
110 this->term->centered_hgt = this->orig_centered_hgt;
114 * Initialize a "term_win" (using the given window size)
116 term_win::term_win(TERM_LEN w, TERM_LEN h)
117 : a(h, std::vector<TERM_COLOR>(w))
118 , c(h, std::vector<char>(w))
119 , ta(h, std::vector<TERM_COLOR>(w))
120 , tc(h, std::vector<char>(w))
124 std::unique_ptr<term_win> term_win::create(TERM_LEN w, TERM_LEN h)
126 // privateコンストラクタを呼び出すための補助クラス
127 struct impl : term_win {
128 impl(TERM_LEN w, TERM_LEN h)
133 return std::make_unique<impl>(w, h);
136 std::unique_ptr<term_win> term_win::clone() const
138 return std::make_unique<term_win>(*this);
141 void term_win::resize(TERM_LEN w, TERM_LEN h)
143 /* Ignore non-changes */
144 if (this->a.size() == static_cast<size_t>(h) && this->a[0].size() == static_cast<size_t>(w)) {
148 this->a.resize(h, std::vector<TERM_COLOR>(w));
149 this->c.resize(h, std::vector<char>(w));
150 this->ta.resize(h, std::vector<TERM_COLOR>(w));
151 this->tc.resize(h, std::vector<char>(w));
153 for (TERM_LEN y = 0; y < h; y++) {
154 this->a[y].resize(w);
155 this->c[y].resize(w);
156 this->ta[y].resize(w);
157 this->tc[y].resize(w);
169 /*** External hooks ***/
172 * Execute the "Term->user_hook" hook, if available (see above).
174 errr term_user(int n)
176 /* Verify the hook */
177 if (!game_term->user_hook) {
182 return (*game_term->user_hook)(n);
186 * Execute the "Term->xtra_hook" hook, if available (see above).
188 errr term_xtra(int n, int v)
190 /* Verify the hook */
191 if (!game_term->xtra_hook) {
196 return (*game_term->xtra_hook)(n, v);
202 * Fake hook for "term_curs()" (see above)
204 static errr term_curs_hack(TERM_LEN x, TERM_LEN y)
214 * Fake hook for "term_bigcurs()" (see above)
216 static errr term_bigcurs_hack(TERM_LEN x, TERM_LEN y)
218 return (*game_term->curs_hook)(x, y);
222 * Fake hook for "term_wipe()" (see above)
224 static errr term_wipe_hack(TERM_LEN x, TERM_LEN y, int n)
235 * Fake hook for "term_text()" (see above)
237 static errr term_text_hack(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
250 * Fake hook for "term_pict()" (see above)
252 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)
266 /*** Efficient routines ***/
269 * Mentally draw an attr/char at a given location
270 * Assumes given location and values are valid.
272 static void term_queue_char_aux(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c, TERM_COLOR ta, char tc)
274 if ((x < 0) || (x >= game_term->wid)) {
277 if ((y < 0) || (y >= game_term->hgt)) {
281 const auto &scrn = game_term->scr;
283 TERM_COLOR *scr_aa = &scrn->a[y][x];
284 char *scr_cc = &scrn->c[y][x];
286 TERM_COLOR *scr_taa = &scrn->ta[y][x];
287 char *scr_tcc = &scrn->tc[y][x];
289 /* Ignore non-changes */
290 if ((*scr_aa == a) && (*scr_cc == c) && (*scr_taa == ta) && (*scr_tcc == tc)) {
294 /* Save the "literal" information */
301 /* Check for new min/max row info */
302 if (y < game_term->y1) {
305 if (y > game_term->y2) {
309 /* Check for new min/max col info for this row */
310 if (x < game_term->x1[y]) {
311 game_term->x1[y] = x;
313 if (x > game_term->x2[y]) {
314 game_term->x2[y] = x;
318 if (((scrn->a[y][x] & AF_BIGTILE2) == AF_BIGTILE2) || (scrn->a[y][x] & AF_KANJI2))
320 if ((scrn->a[y][x] & AF_BIGTILE2) == AF_BIGTILE2)
322 if ((x - 1) < game_term->x1[y]) {
327 void term_queue_char(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c, TERM_COLOR ta, char tc)
329 term_queue_char_aux(x + game_term->offset_x, y + game_term->offset_y, a, c, ta, tc);
333 * Bigtile version of term_queue_char().
334 * If use_bigtile is FALSE, simply call term_queue_char().
335 * Otherwise, mentally draw a pair of attr/char at a given location.
336 * Assumes given location and values are valid.
338 void term_queue_bigchar(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c, TERM_COLOR ta, char tc)
342 * A table which relates each ascii character to a multibyte
345 * 「■」は二倍幅豆腐の内部コードに使用。
347 static char ascii_to_zenkaku[] = " !”#$%&’()*+,-./"
358 const auto ch_x = x + game_term->offset_x;
359 const auto ch_y = y + game_term->offset_y;
361 /* If non bigtile mode, call orginal function */
363 term_queue_char_aux(ch_x, ch_y, a, c, ta, tc);
367 /* A tile becomes a Bigtile */
368 if ((a & AF_TILE1) && (c & 0x80)) {
369 /* Mark it as a Bigtile */
374 /* Ignore non-tile background */
375 if (!((ta & AF_TILE1) && (tc & 0x80))) {
383 * Use a multibyte character instead of a dirty pair of ASCII
386 else if (' ' <= c) /* isprint(c) */
388 c2 = ascii_to_zenkaku[2 * (c - ' ') + 1];
389 c = ascii_to_zenkaku[2 * (c - ' ')];
391 /* Mark it as a Kanji */
398 /* Dirty pair of ASCII characters */
403 /* Display pair of attr/char */
404 term_queue_char_aux(ch_x, ch_y, a, c, ta, tc);
405 term_queue_char_aux(ch_x + 1, ch_y, a2, c2, 0, 0);
409 * Mentally draw a string of attr/chars at a given location
410 * Assumes given location and values are valid.
411 * This function is designed to be fast, with no consistancy checking.
412 * It is used to update the map in the game.
414 void term_queue_line(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR *a, char *c, TERM_COLOR *ta, char *tc)
416 const auto &scrn = game_term->scr;
421 TERM_COLOR *scr_aa = &scrn->a[y][x];
422 char *scr_cc = &scrn->c[y][x];
424 TERM_COLOR *scr_taa = &scrn->ta[y][x];
425 char *scr_tcc = &scrn->tc[y][x];
428 /* Ignore non-changes */
429 if ((*scr_aa == *a) && (*scr_cc == *c) && (*scr_taa == *ta) && (*scr_tcc == *tc)) {
442 /* Save the "literal" information */
446 /* Save the "literal" information */
450 /* Track minimum changed column */
455 /* Track maximum changed column */
461 /* Expand the "change area" as needed */
463 /* Check for new min/max row info */
464 if (y < game_term->y1) {
467 if (y > game_term->y2) {
471 /* Check for new min/max col info in this row */
472 if (x1 < game_term->x1[y]) {
473 game_term->x1[y] = x1;
475 if (x2 > game_term->x2[y]) {
476 game_term->x2[y] = x2;
482 * Mentally draw some attr/chars at a given location
484 * Assumes that (x,y) is a valid location, that the first "n" characters
485 * of the string "s" are all valid (non-zero), and that (x+n-1,y) is also
486 * a valid location, so the first "n" characters of "s" can all be added
487 * starting at (x,y) without causing any illegal operations.
489 static void term_queue_chars(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, std::string_view sv)
491 TERM_LEN x1 = -1, x2 = -1;
493 auto &scr_aa = game_term->scr->a[y];
495 auto &scr_cc = game_term->scr->c[y];
497 auto &scr_taa = game_term->scr->ta[y];
498 auto &scr_tcc = game_term->scr->tc[y];
500 auto &scr_cc = game_term->scr->c[y];
502 auto &scr_taa = game_term->scr->ta[y];
503 auto &scr_tcc = game_term->scr->tc[y];
508 if (n == 0 || sv.empty()) {
512 * 全角文字の右半分から文字を表示する場合、
516 if ((scr_aa[x] & AF_KANJI2) && (scr_aa[x] & AF_BIGTILE2) != AF_BIGTILE2) {
518 scr_aa[x - 1] &= AF_KANJIC;
522 /* Queue the attr/chars */
523 for (auto s = sv.begin(); (n > 0) && (s != sv.end()); x++, s++, n--) {
525 /* 特殊文字としてMSBが立っている可能性がある */
526 /* その場合attrのMSBも立っているのでこれで識別する */
528 if (!(a & AF_TILE1) && iskanji(*s)) {
532 byte na1 = (a | AF_KANJI1);
533 byte na2 = (a | AF_KANJI2);
535 if ((--n == 0) || !nc2) {
539 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) && (scr_tcc[x - 1] == 0) && (scr_tcc[x] == 0)) {
554 TERM_COLOR oa = scr_aa[x];
557 TERM_COLOR ota = scr_taa[x];
558 char otc = scr_tcc[x];
560 /* Ignore non-changes */
561 if ((oa == a) && (oc == *s) && (ota == 0) && (otc == 0)) {
565 /* Save the "literal" information */
572 /* Note the "range" of window updates */
584 * 全角文字の左半分で表示を終了する場合、
586 * (条件追加:タイルの1文字目でない事を確かめるように。)
589 if ((x < game_term->wid) && !(scr_aa[x] & AF_TILE1) && (scr_aa[x] & AF_KANJI2)) {
591 scr_aa[x] &= AF_KANJIC;
599 /* Expand the "change area" as needed */
601 /* Check for new min/max row info */
602 if (y < game_term->y1) {
605 if (y > game_term->y2) {
609 /* Check for new min/max col info in this row */
610 if (x1 < game_term->x1[y]) {
611 game_term->x1[y] = x1;
613 if (x2 > game_term->x2[y]) {
614 game_term->x2[y] = x2;
619 /*** Refresh routines ***/
622 * Flush a row of the current window (see "term_fresh")
623 * Display text using "term_pict()"
625 static void term_fresh_row_pict(TERM_LEN y, TERM_LEN x1, TERM_LEN x2)
627 auto &old_aa = game_term->old->a[y];
628 auto &old_cc = game_term->old->c[y];
630 const auto &scr_aa = game_term->scr->a[y];
631 const auto &scr_cc = game_term->scr->c[y];
633 auto &old_taa = game_term->old->ta[y];
634 auto &old_tcc = game_term->old->tc[y];
636 const auto &scr_taa = game_term->scr->ta[y];
637 const auto &scr_tcc = game_term->scr->tc[y];
661 /* Scan "modified" columns */
662 for (TERM_LEN x = x1; x <= x2; x++) {
663 /* See what is currently here */
667 /* See what is desired there */
680 /* 特殊文字としてMSBが立っている可能性がある */
681 /* その場合attrのMSBも立っているのでこれで識別する */
683 kanji = (iskanji(nc) && !(na & AF_TILE1));
692 /* Handle unchanged grids */
694 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc) && (!kanji || (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])))
696 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc))
701 /* Draw pending attr/char pairs */
702 (void)((*game_term->pict_hook)(fx, y, fn, &scr_aa[fx], &scr_cc[fx], &scr_taa[fx], &scr_tcc[fx]));
719 /* Save new contents */
726 /* Restart and Advance */
734 /* Draw pending attr/char pairs */
735 (void)((*game_term->pict_hook)(fx, y, fn, &scr_aa[fx], &scr_cc[fx], &scr_taa[fx], &scr_tcc[fx]));
740 * Flush a row of the current window (see "term_fresh")
742 * Display text using "term_text()" and "term_wipe()",
743 * but use "term_pict()" for high-bit attr/char pairs
745 static void term_fresh_row_both(TERM_LEN y, int x1, int x2)
747 auto &old_aa = game_term->old->a[y];
748 auto &old_cc = game_term->old->c[y];
750 const auto &scr_aa = game_term->scr->a[y];
751 const auto &scr_cc = game_term->scr->c[y];
753 auto &old_taa = game_term->old->ta[y];
754 auto &old_tcc = game_term->old->tc[y];
755 const auto &scr_taa = game_term->scr->ta[y];
756 const auto &scr_tcc = game_term->scr->tc[y];
763 /* The "always_text" flag */
764 int always_text = game_term->always_text;
773 byte fa = game_term->attr_blank;
785 /* Scan "modified" columns */
786 for (TERM_LEN x = x1; x <= x2; x++) {
787 /* See what is currently here */
791 /* See what is desired there */
804 /* 特殊文字としてMSBが立っている可能性がある */
805 /* その場合attrのMSBも立っているのでこれで識別する */
807 /* kanji = (iskanji(nc)); */
808 kanji = (iskanji(nc) && !(na & AF_TILE1));
817 /* Handle unchanged grids */
819 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc) && (!kanji || (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])))
821 if ((na == oa) && (nc == oc) && (nta == ota) && (ntc == otc))
826 /* Draw pending chars (normal) */
827 if (fa || always_text) {
828 (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
831 /* Draw pending chars (black) */
833 (void)((*game_term->wipe_hook)(fx, y, fn));
852 /* Save new contents */
859 /* 2nd byte of bigtile */
860 if ((na & AF_BIGTILE2) == AF_BIGTILE2) {
864 /* Handle high-bit attr/chars */
865 if ((na & AF_TILE1) && (nc & 0x80)) {
868 /* Draw pending chars (normal) */
869 if (fa || always_text) {
870 (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
873 /* Draw pending chars (black) */
875 (void)((*game_term->wipe_hook)(fx, y, fn));
882 /* Draw the special attr/char pair */
883 (void)((*game_term->pict_hook)(x, y, 1, &na, &nc, &nta, &ntc));
889 /* Notice new color */
891 if (fa != (na & AF_KANJIC))
899 /* Draw the pending chars */
900 if (fa || always_text) {
901 (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
904 /* Erase "leading" spaces */
906 (void)((*game_term->wipe_hook)(fx, y, fn));
913 /* Save the new color */
915 fa = (na & AF_KANJIC);
921 /* Restart and Advance */
929 /* Draw pending chars (normal) */
930 if (fa || always_text) {
931 (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
934 /* Draw pending chars (black) */
936 (void)((*game_term->wipe_hook)(fx, y, fn));
942 * Flush a row of the current window (see "term_fresh")
944 * Display text using "term_text()" and "term_wipe()"
946 static void term_fresh_row_text(TERM_LEN y, TERM_LEN x1, TERM_LEN x2)
948 auto &old_aa = game_term->old->a[y];
949 auto &old_cc = game_term->old->c[y];
951 const auto &scr_aa = game_term->scr->a[y];
952 const auto &scr_cc = game_term->scr->c[y];
954 /* The "always_text" flag */
955 int always_text = game_term->always_text;
964 byte fa = game_term->attr_blank;
976 for (TERM_LEN x = 0; x < x1; x++) {
977 if (!(old_aa[x] & AF_TILE1) && iskanji(old_cc[x])) {
987 /* Scan "modified" columns */
988 for (TERM_LEN x = x1; x <= x2; x++) {
989 /* See what is currently here */
993 /* See what is desired there */
1006 /* 特殊文字としてMSBが立っている可能性がある */
1007 /* その場合attrのMSBも立っているのでこれで識別する */
1009 kanji = (iskanji(nc) && !(na & AF_TILE1));
1011 /* Handle unchanged grids */
1013 if ((na == oa) && (nc == oc) && (!kanji || (scr_aa[x + 1] == old_aa[x + 1] && scr_cc[x + 1] == old_cc[x + 1])))
1015 if ((na == oa) && (nc == oc))
1021 /* Draw pending chars (normal) */
1022 if (fa || always_text) {
1023 (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
1026 /* Draw pending chars (black) */
1028 (void)((*game_term->wipe_hook)(fx, y, fn));
1036 /* 全角文字の時は再開位置は+1 */
1047 /* Save new contents */
1051 /* Notice new color */
1053 if (fa != (na & AF_KANJIC))
1061 /* Draw the pending chars */
1062 if (fa || always_text) {
1063 (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
1066 /* Erase "leading" spaces */
1068 (void)((*game_term->wipe_hook)(fx, y, fn));
1075 /* Save the new color */
1077 fa = (na & AF_KANJIC);
1083 /* Restart and Advance */
1091 /* Draw pending chars (normal) */
1092 if (fa || always_text) {
1093 (void)((*game_term->text_hook)(fx, y, fn, fa, &scr_cc[fx]));
1096 /* Draw pending chars (black) */
1098 (void)((*game_term->wipe_hook)(fx, y, fn));
1104 * @brief Actually perform all requested changes to the window
1106 errr term_fresh(void)
1108 int w = game_term->wid;
1109 int h = game_term->hgt;
1111 int y1 = game_term->y1;
1112 int y2 = game_term->y2;
1114 const auto &old = game_term->old;
1115 const auto &scr = game_term->scr;
1117 /* Before initialize (Advice from Mr.shimitei)*/
1122 if (game_term->never_fresh) {
1126 /* Do nothing unless "mapped" */
1127 if (!game_term->mapped_flag) {
1131 /* Trivial Refresh */
1132 if ((y1 > y2) && (scr->cu == old->cu) && (scr->cv == old->cv) && (scr->cx == old->cx) && (scr->cy == old->cy) && !(game_term->total_erase)) {
1137 /* Handle "total erase" */
1138 if (game_term->total_erase) {
1139 byte na = game_term->attr_blank;
1140 char nc = game_term->char_blank;
1142 /* Physically erase the entire window */
1143 term_xtra(TERM_XTRA_CLEAR, 0);
1145 /* clear all "cursor" data */
1146 old->cv = old->cu = false;
1147 old->cx = old->cy = 0;
1150 for (TERM_LEN y = 0; y < h; y++) {
1151 auto &aa = old->a[y];
1152 auto &cc = old->c[y];
1154 auto &taa = old->ta[y];
1155 auto &tcc = old->tc[y];
1157 /* Wipe each column */
1158 for (TERM_LEN x = 0; x < w; x++) {
1159 /* Wipe each grid */
1168 /* Redraw every row */
1169 game_term->y1 = y1 = 0;
1170 game_term->y2 = y2 = h - 1;
1172 /* Redraw every column */
1173 for (TERM_LEN y = 0; y < h; y++) {
1174 game_term->x1[y] = 0;
1175 game_term->x2[y] = w - 1;
1178 /* Forget "total erase" */
1179 game_term->total_erase = false;
1182 /* Cursor update -- Erase old Cursor */
1183 if (game_term->soft_cursor) {
1184 /* Cursor was visible */
1185 if (!old->cu && old->cv) {
1187 TERM_LEN tx = old->cx;
1188 TERM_LEN ty = old->cy;
1190 const auto &old_aa = old->a[ty];
1191 const auto &old_cc = old->c[ty];
1193 const auto &old_taa = old->ta[ty];
1194 const auto &old_tcc = old->tc[ty];
1196 TERM_COLOR ota = old_taa[tx];
1197 char otc = old_tcc[tx];
1200 if (tx + 1 < game_term->wid && !(old_aa[tx] & AF_TILE1) && iskanji(old_cc[tx])) {
1204 /* Use "term_pict()" always */
1205 if (game_term->always_pict) {
1206 (void)((*game_term->pict_hook)(tx, ty, csize, &old_aa[tx], &old_cc[tx], &ota, &otc));
1209 /* Use "term_pict()" sometimes */
1210 else if (game_term->higher_pict && (old_aa[tx] & AF_TILE1) && (old_cc[tx] & 0x80)) {
1211 (void)((*game_term->pict_hook)(tx, ty, 1, &old_aa[tx], &old_cc[tx], &ota, &otc));
1215 * Restore the actual character
1216 * 元の文字の描画範囲がカーソルより小さいと、
1217 * 上書きされなかった部分がゴミとして残る。
1218 * wipe_hook でカーソルを消去して text_hook で書き直す。
1220 else if (old_aa[tx] || game_term->always_text) {
1221 (void)((*game_term->wipe_hook)(tx, ty, 1));
1222 (void)((*game_term->text_hook)(tx, ty, csize, (unsigned char)(old_aa[tx] & 0xf), &old_cc[tx]));
1225 /* Erase the grid */
1227 (void)((*game_term->wipe_hook)(tx, ty, 1));
1232 /* Hide the hardware cursor while drawing */
1234 /* Cursor will be invisible */
1235 term_xtra(TERM_XTRA_SHAPE, 0);
1238 /* Something to update */
1240 /* Handle "icky corner" */
1241 if (game_term->icky_corner) {
1242 /* Avoid the corner */
1244 /* Avoid the corner */
1245 if (game_term->x2[h - 1] > w - 2) {
1246 /* Avoid the corner */
1247 game_term->x2[h - 1] = w - 2;
1252 /* Scan the "modified" rows */
1253 for (TERM_LEN y = y1; y <= y2; ++y) {
1254 TERM_LEN x1 = game_term->x1[y];
1255 TERM_LEN x2 = game_term->x2[y];
1257 /* Flush each "modified" row */
1259 /* Always use "term_pict()" */
1260 if (game_term->always_pict) {
1262 term_fresh_row_pict(y, x1, x2);
1265 /* Sometimes use "term_pict()" */
1266 else if (game_term->higher_pict) {
1268 term_fresh_row_both(y, x1, x2);
1271 /* Never use "term_pict()" */
1274 term_fresh_row_text(y, x1, x2);
1277 /* This row is all done */
1278 game_term->x1[y] = w;
1279 game_term->x2[y] = 0;
1281 /* Flush that row (if allowed) */
1282 if (!game_term->never_frosh) {
1283 term_xtra(TERM_XTRA_FROSH, y);
1288 /* No rows are invalid */
1293 /* Cursor update -- Show new Cursor */
1294 if (game_term->soft_cursor) {
1295 /* Draw the cursor */
1296 if (!scr->cu && scr->cv) {
1298 if ((scr->cx + 1 < w) && ((old->a[scr->cy][scr->cx + 1] & AF_BIGTILE2) == AF_BIGTILE2 || (!(old->a[scr->cy][scr->cx] & AF_TILE1) && iskanji(old->c[scr->cy][scr->cx]))))
1300 if ((scr->cx + 1 < w) && (old->a[scr->cy][scr->cx + 1] & AF_BIGTILE2) == AF_BIGTILE2)
1303 /* Double width cursor for the Bigtile mode */
1304 (void)((*game_term->bigcurs_hook)(scr->cx, scr->cy));
1306 /* Call the cursor display routine */
1307 (void)((*game_term->curs_hook)(scr->cx, scr->cy));
1312 /* Cursor Update -- Show new Cursor */
1315 /* Paranoia -- Put the cursor NEAR where it belongs */
1316 (void)((*game_term->curs_hook)(w - 1, scr->cy));
1318 /* Put the cursor where it belongs */
1319 (void)((*game_term->curs_hook)(scr->cx, scr->cy));
1323 /* Save the "cursor state" */
1329 /* Actually flush the output */
1330 term_xtra(TERM_XTRA_FRESH, 0);
1332 if (!game_term->soft_cursor && !scr->cu && scr->cv) {
1333 /* The cursor is visible, display it correctly */
1334 term_xtra(TERM_XTRA_SHAPE, 1);
1341 * @brief never_freshの値を無視して強制的にterm_freshを行う。
1343 errr term_fresh_force(void)
1345 bool old = game_term->never_fresh;
1346 game_term->never_fresh = false;
1347 errr err = term_fresh();
1348 game_term->never_fresh = old;
1352 /*** Output routines ***/
1355 * Set the cursor visibility
1357 errr term_set_cursor(int v)
1360 if (game_term->scr->cv == (bool)v) {
1365 game_term->scr->cv = (bool)v;
1370 * Place the cursor at a given location
1372 * Note -- "illegal" requests do not move the cursor.
1374 errr term_gotoxy(TERM_LEN x, TERM_LEN y)
1376 int w = game_term->wid;
1377 int h = game_term->hgt;
1379 x += game_term->offset_x;
1380 y += game_term->offset_y;
1383 if ((x < 0) || (x >= w)) {
1386 if ((y < 0) || (y >= h)) {
1390 /* Remember the cursor */
1391 game_term->scr->cx = x;
1392 game_term->scr->cy = y;
1394 /* The cursor is not useless */
1395 game_term->scr->cu = 0;
1400 * At a given location, place an attr/char
1401 * Do not change the cursor position
1402 * No visual changes until "term_fresh()".
1404 errr term_draw(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c)
1406 if (auto res = term_gotoxy(x, y); res != 0) {
1410 /* Paranoia -- illegal char */
1415 /* Queue it for later */
1416 term_queue_char_aux(game_term->scr->cx, game_term->scr->cy, a, c, 0, 0);
1421 * Using the given attr, add the given char at the cursor.
1423 * We return "-2" if the character is "illegal". XXX XXX
1425 * We return "-1" if the cursor is currently unusable.
1427 * We queue the given attr/char for display at the current
1428 * cursor location, and advance the cursor to the right,
1429 * marking it as unuable and returning "1" if it leaves
1430 * the screen, and otherwise returning "0".
1432 * So when this function, or the following one, return a
1433 * positive value, future calls to either function will
1434 * return negative ones.
1436 errr term_addch(TERM_COLOR a, char c)
1438 TERM_LEN w = game_term->wid;
1440 /* Handle "unusable" cursor */
1441 if (game_term->scr->cu) {
1445 /* Paranoia -- no illegal chars */
1450 /* Queue the given character for display */
1451 term_queue_char_aux(game_term->scr->cx, game_term->scr->cy, a, c, 0, 0);
1453 /* Advance the cursor */
1454 game_term->scr->cx++;
1457 if (game_term->scr->cx < w) {
1461 /* Note "Useless" cursor */
1462 game_term->scr->cu = 1;
1464 /* Note "Useless" cursor */
1469 * Bigtile version of term_addch().
1471 * If use_bigtile is FALSE, simply call term_addch() .
1473 * Otherwise, queue a pair of attr/char for display at the current
1474 * cursor location, and advance the cursor to the right by two.
1476 errr term_add_bigch(TERM_COLOR a, char c)
1479 return term_addch(a, c);
1482 /* Handle "unusable" cursor */
1483 if (game_term->scr->cu) {
1487 /* Paranoia -- no illegal chars */
1492 /* Queue the given character for display */
1493 term_queue_bigchar(game_term->scr->cx, game_term->scr->cy, a, c, 0, 0);
1495 /* Advance the cursor */
1496 game_term->scr->cx += 2;
1499 if (game_term->scr->cx < game_term->wid) {
1503 /* Note "Useless" cursor */
1504 game_term->scr->cu = 1;
1506 /* Note "Useless" cursor */
1511 * At the current location, using an attr, add a string
1513 * We also take a length "n", using negative values to imply
1514 * the largest possible value, and then we use the minimum of
1515 * this length and the "actual" length of the string as the
1516 * actual number of characters to attempt to display, never
1517 * displaying more characters than will actually fit, since
1518 * we do NOT attempt to "wrap" the cursor at the screen edge.
1520 * We return "-1" if the cursor is currently unusable.
1521 * We return "N" if we were "only" able to write "N" chars,
1522 * even if all of the given characters fit on the screen,
1523 * and mark the cursor as unusable for future attempts.
1525 * So when this function, or the preceding one, return a
1526 * positive value, future calls to either function will
1527 * return negative ones.
1529 errr term_addstr(int n, TERM_COLOR a, std::string_view sv)
1531 TERM_LEN w = game_term->wid;
1534 /* Handle "unusable" cursor */
1535 if (game_term->scr->cu) {
1539 /* Obtain maximal length */
1540 const auto max_len = (n < 0) ? (w + 1) : n;
1542 /* Obtain the usable string length */
1543 auto len = std::min<int>(max_len, sv.length());
1545 /* React to reaching the edge of the screen */
1546 if (game_term->scr->cx + len >= w) {
1547 res = len = w - game_term->scr->cx;
1550 /* Queue the first "n" characters for display */
1551 term_queue_chars(game_term->scr->cx, game_term->scr->cy, len, a, sv);
1553 /* Advance the cursor */
1554 game_term->scr->cx += len;
1556 /* Notice "Useless" cursor */
1558 game_term->scr->cu = 1;
1565 * Move to a location and, using an attr, add a char
1567 errr term_putch(TERM_LEN x, TERM_LEN y, TERM_COLOR a, char c)
1572 if ((res = term_gotoxy(x, y)) != 0) {
1576 /* Then add the char */
1577 if ((res = term_addch(a, c)) != 0) {
1585 * Move to a location and, using an attr, add a string
1587 errr term_putstr(TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, std::string_view sv)
1592 if ((res = term_gotoxy(x, y)) != 0) {
1596 /* Then add the string */
1597 if ((res = term_addstr(n, a, sv)) != 0) {
1605 * Place cursor at (x,y), and clear the next "n" chars
1607 errr term_erase(TERM_LEN x, TERM_LEN y, int n)
1609 TERM_LEN w = game_term->wid;
1610 /* int h = Term->hgt; */
1615 int na = game_term->attr_blank;
1616 int nc = game_term->char_blank;
1619 if (term_gotoxy(x, y)) {
1623 x = game_term->scr->cx;
1624 y = game_term->scr->cy;
1626 /* Force legal size */
1632 auto &scr_aa = game_term->scr->a[y];
1633 auto &scr_cc = game_term->scr->c[y];
1635 auto &scr_taa = game_term->scr->ta[y];
1636 auto &scr_tcc = game_term->scr->tc[y];
1640 * 全角文字の右半分から文字を表示する場合、
1643 if (n > 0 && (((scr_aa[x] & AF_KANJI2) && !(scr_aa[x] & AF_TILE1)) || (scr_aa[x] & AF_BIGTILE2) == AF_BIGTILE2))
1645 if (n > 0 && (scr_aa[x] & AF_BIGTILE2) == AF_BIGTILE2)
1652 /* Scan every column */
1653 for (int i = 0; i < n; i++, x++) {
1657 /* Ignore "non-changes" */
1658 if ((oa == na) && (oc == nc)) {
1664 * 全角文字の左半分で表示を終了する場合、
1667 * 2001/04/29 -- Habu
1668 * 行の右端の場合はこの処理をしないように修正。
1670 if ((oa & AF_KANJI1) && (i + 1) == n && x != w - 1) {
1674 /* Save the "literal" information */
1675 scr_aa[x] = (byte)na;
1676 scr_cc[x] = (char)nc;
1681 /* Track minimum changed column */
1686 /* Track maximum changed column */
1690 /* Expand the "change area" as needed */
1692 /* Check for new min/max row info */
1693 if (y < game_term->y1) {
1696 if (y > game_term->y2) {
1700 /* Check for new min/max col info in this row */
1701 if (x1 < game_term->x1[y]) {
1702 game_term->x1[y] = x1;
1704 if (x2 > game_term->x2[y]) {
1705 game_term->x2[y] = x2;
1713 * Clear the entire window, and move to the top left corner
1715 * Note the use of the special "total_erase" code
1717 errr term_clear(void)
1719 TERM_LEN w = game_term->wid;
1720 TERM_LEN h = game_term->hgt;
1722 TERM_COLOR na = game_term->attr_blank;
1723 char nc = game_term->char_blank;
1726 game_term->scr->cu = 0;
1728 /* Cursor to the top left */
1729 game_term->scr->cx = game_term->scr->cy = 0;
1732 for (TERM_LEN y = 0; y < h; y++) {
1733 auto &scr_aa = game_term->scr->a[y];
1734 auto &scr_cc = game_term->scr->c[y];
1736 auto &scr_taa = game_term->scr->ta[y];
1737 auto &scr_tcc = game_term->scr->tc[y];
1739 /* Wipe each column */
1740 for (TERM_LEN x = 0; x < w; x++) {
1748 /* This row has changed */
1749 game_term->x1[y] = 0;
1750 game_term->x2[y] = w - 1;
1753 /* Every row has changed */
1755 game_term->y2 = h - 1;
1757 /* Force "total erase" */
1758 game_term->total_erase = true;
1763 * Redraw (and refresh) the whole window.
1765 errr term_redraw(void)
1767 /* Force "total erase" */
1768 game_term->total_erase = true;
1774 * Redraw part of a window.
1776 errr term_redraw_section(TERM_LEN x1, TERM_LEN y1, TERM_LEN x2, TERM_LEN y2)
1778 /* Bounds checking */
1779 if (y2 >= game_term->hgt) {
1780 y2 = game_term->hgt - 1;
1782 if (x2 >= game_term->wid) {
1783 x2 = game_term->wid - 1;
1796 /* Set the x limits */
1797 for (int i = game_term->y1; i <= game_term->y2; i++) {
1803 if (game_term->scr->a[i][x1j] & AF_KANJI2) {
1808 if (x2j < game_term->wid - 1) {
1809 if (game_term->scr->a[i][x2j] & AF_KANJI1) {
1814 game_term->x1[i] = x1j;
1815 game_term->x2[i] = x2j;
1817 auto &g_ptr = game_term->old->c[i];
1819 /* Clear the section so it is redrawn */
1820 for (int j = x1j; j <= x2j; j++) {
1821 /* Hack - set the old character to "none" */
1825 game_term->x1[i] = x1;
1826 game_term->x2[i] = x2;
1828 auto &g_ptr = game_term->old->c[i];
1830 /* Clear the section so it is redrawn */
1831 for (int j = x1; j <= x2; j++) {
1832 /* Hack - set the old character to "none" */
1842 /*** Access routines ***/
1845 * Extract the cursor visibility
1847 errr term_get_cursor(int *v)
1849 /* Extract visibility */
1850 (*v) = game_term->scr->cv;
1855 * Extract the current window size
1857 errr term_get_size(TERM_LEN *w, TERM_LEN *h)
1859 (*w) = game_term->centered_wid.value_or(game_term->wid);
1860 (*h) = game_term->centered_hgt.value_or(game_term->hgt);
1865 * Extract the current cursor location
1867 errr term_locate(TERM_LEN *x, TERM_LEN *y)
1869 /* Access the cursor */
1870 *x = game_term->scr->cx - game_term->offset_x;
1871 *y = game_term->scr->cy - game_term->offset_y;
1873 /* Warn about "useless" cursor */
1874 if (game_term->scr->cu) {
1882 * At a given location, determine the "current" attr and char
1883 * Note that this refers to what will be on the window after the
1884 * next call to "term_fresh()". It may or may not already be there.
1886 errr term_what(TERM_LEN x, TERM_LEN y, TERM_COLOR *a, char *c)
1888 TERM_LEN w = game_term->wid;
1889 TERM_LEN h = game_term->hgt;
1891 x += game_term->offset_x;
1892 y += game_term->offset_y;
1894 if ((x < 0) || (x >= w)) {
1897 if ((y < 0) || (y >= h)) {
1902 (*a) = game_term->scr->a[y][x];
1903 (*c) = game_term->scr->c[y][x];
1907 /*** Input routines ***/
1910 * Flush and forget the input
1912 errr term_flush(void)
1914 /* Flush all events */
1915 term_xtra(TERM_XTRA_FLUSH, 0);
1917 /* Forget all keypresses */
1918 game_term->key_head = game_term->key_tail = 0;
1923 * Add a keypress to the FRONT of the "queue"
1925 errr term_key_push(int k)
1927 /* Refuse to enqueue non-keys */
1932 /* Overflow may induce circular queue */
1933 if (game_term->key_tail == 0) {
1934 game_term->key_tail = game_term->key_size;
1937 /* Back up, Store the char */
1938 game_term->key_queue[--game_term->key_tail] = (char)k;
1940 if (game_term->key_head != game_term->key_tail) {
1948 * Check for a pending keypress on the key queue.
1950 * Store the keypress, if any, in "ch", and return "0".
1951 * Otherwise store "zero" in "ch", and return "1".
1953 * Wait for a keypress if "wait" is true.
1955 * Remove the keypress if "take" is true.
1957 errr term_inkey(char *ch, bool wait, bool take)
1963 if (!game_term->never_bored) {
1964 /* Process random events */
1965 term_xtra(TERM_XTRA_BORED, 0);
1970 /* Process pending events while necessary */
1971 while (game_term->key_head == game_term->key_tail) {
1972 /* Process events (wait for one) */
1973 term_xtra(TERM_XTRA_EVENT, true);
1979 /* Process pending events if necessary */
1980 if (game_term->key_head == game_term->key_tail) {
1981 /* Process events (do not wait) */
1982 term_xtra(TERM_XTRA_EVENT, false);
1986 /* No keys are ready */
1987 if (game_term->key_head == game_term->key_tail) {
1991 /* Extract the next keypress */
1992 (*ch) = game_term->key_queue[game_term->key_tail];
1994 /* If requested, advance the queue, wrap around if necessary */
1995 if (take && (++game_term->key_tail == game_term->key_size)) {
1996 game_term->key_tail = 0;
2002 /*** Extra routines ***/
2005 * Save the "requested" screen into the "memorized" screen
2007 * Every "term_save()" should match exactly one "term_load()"
2009 errr term_save(void)
2012 game_term->mem_stack.push(game_term->scr->clone());
2018 * Restore the "requested" contents (see above).
2020 * Every "term_save()" should match exactly one "term_load()"
2022 errr term_load(bool load_all)
2024 TERM_LEN w = game_term->wid;
2025 TERM_LEN h = game_term->hgt;
2027 if (game_term->mem_stack.empty()) {
2033 while (game_term->mem_stack.size() > 1) {
2034 game_term->mem_stack.pop();
2039 game_term->scr.swap(game_term->mem_stack.top());
2040 game_term->scr->resize(w, h);
2043 game_term->mem_stack.pop();
2046 for (TERM_LEN y = 0; y < h; y++) {
2048 game_term->x1[y] = 0;
2049 game_term->x2[y] = w - 1;
2054 game_term->y2 = h - 1;
2059 * Exchange the "requested" screen with the "tmp" screen
2061 errr term_exchange(void)
2063 TERM_LEN w = game_term->wid;
2064 TERM_LEN h = game_term->hgt;
2067 if (!game_term->tmp) {
2068 /* Allocate window */
2069 game_term->tmp = term_win::create(w, h);
2073 game_term->scr.swap(game_term->tmp);
2076 for (TERM_LEN y = 0; y < h; y++) {
2078 game_term->x1[y] = 0;
2079 game_term->x2[y] = w - 1;
2084 game_term->y2 = h - 1;
2089 * React to a new physical window size.
2091 errr term_resize(TERM_LEN w, TERM_LEN h)
2093 /* Resizing is forbidden */
2094 if (game_term->fixed_shape) {
2098 /* Ignore illegal changes */
2099 if ((w < 1) || (h < 1)) {
2103 /* Ignore non-changes */
2104 if ((game_term->wid == w) && (game_term->hgt == h) && (arg_bigtile == use_bigtile)) {
2108 use_bigtile = arg_bigtile;
2110 /* Resize windows */
2111 game_term->old->resize(w, h);
2112 game_term->scr->resize(w, h);
2113 if (game_term->tmp) {
2114 game_term->tmp->resize(w, h);
2117 /* Resize scanners */
2118 game_term->x1.resize(h);
2119 game_term->x2.resize(h);
2125 /* Force "total erase" */
2126 game_term->total_erase = true;
2129 for (int i = 0; i < h; i++) {
2131 game_term->x1[i] = 0;
2132 game_term->x2[i] = w - 1;
2137 game_term->y2 = h - 1;
2139 /* Execute the "resize_hook" hook, if available */
2140 if (game_term->resize_hook) {
2141 game_term->resize_hook();
2148 * Activate a new Term (and deactivate the current Term)
2150 * This function is extremely important, and also somewhat bizarre.
2151 * It is the only function that should "modify" the value of "Term".
2153 * To "create" a valid "term", one should do "term_init(t)", then
2154 * set the various flags and hooks, and then do "term_activate(t)".
2156 errr term_activate(term_type *t)
2159 if (game_term == t) {
2163 /* Deactivate the old Term */
2165 term_xtra(TERM_XTRA_LEVEL, 0);
2168 /* Call the special "init" hook */
2169 if (t && !t->active_flag) {
2170 /* Call the "init" hook */
2176 t->active_flag = true;
2179 t->mapped_flag = true;
2182 /* Remember the Term */
2185 /* Activate the new Term */
2187 term_xtra(TERM_XTRA_LEVEL, 1);
2194 * Initialize a term, using a window of the given size.
2195 * Also prepare the "input queue" for "k" keypresses
2196 * By default, the cursor starts out "invisible"
2197 * By default, we "erase" using "black spaces"
2199 errr term_init(term_type *t, TERM_LEN w, TERM_LEN h, int k)
2204 /* Prepare the input queue */
2205 t->key_head = t->key_tail = 0;
2207 /* Determine the input queue size */
2208 t->key_size = (uint16_t)k;
2210 /* Allocate the input queue */
2211 t->key_queue.resize(t->key_size);
2217 /* Allocate change arrays */
2221 /* Allocate "displayed" */
2222 t->old = term_win::create(w, h);
2224 /* Allocate "requested" */
2225 t->scr = term_win::create(w, h);
2228 for (TERM_LEN y = 0; y < h; y++) {
2238 /* Force "total erase" */
2239 t->total_erase = true;
2241 /* Default "blank" */
2243 t->char_blank = ' ';
2245 /* Prepare "fake" hooks to prevent core dumps */
2246 t->curs_hook = term_curs_hack;
2247 t->bigcurs_hook = term_bigcurs_hack;
2248 t->wipe_hook = term_wipe_hack;
2249 t->text_hook = term_text_hack;
2250 t->pict_hook = term_pict_hack;
2256 * Move to a location and, using an attr, add a string vertically
2258 errr term_putstr_v(TERM_LEN x, TERM_LEN y, int n, byte a, concptr s)
2263 for (int i = 0; i < n && s[i] != 0; i++) {
2265 if ((res = term_gotoxy(x, y0)) != 0) {
2269 if (iskanji(s[i])) {
2270 if ((res = term_addstr(2, a, &s[i])) != 0) {
2279 if ((res = term_addstr(1, a, &s[i])) != 0) {
2291 errr term_nuke(term_type *t)
2293 if (t->active_flag) {
2298 t->active_flag = false;
2299 t->mapped_flag = false;
2305 while (!t->mem_stack.empty()) {
2311 t->key_queue.clear();