tuikit.c (9997B)
1 /* tuikit.c - piezas de la TUI comunes a los juegos. */ 2 #include <locale.h> 3 #include <poll.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include "tuikit.h" 7 8 /* ------------------------------------------------------------------ colores */ 9 10 short tk_xterm(Color c) 11 { 12 static int16_t cache[65536]; 13 static bool ready; 14 if (!ready) { for (int i = 0; i < 65536; i++) cache[i] = -1; ready = true; } 15 if (cache[c] >= 0) return cache[c]; 16 int r = (c >> 11) << 3, g = ((c >> 5) & 63) << 2, b = (c & 31) << 3; 17 static const int LV[6] = { 0, 95, 135, 175, 215, 255 }; 18 int ri = 0, gi = 0, bi = 0; 19 for (int i = 1; i < 6; i++) { 20 if (abs(LV[i] - r) < abs(LV[ri] - r)) ri = i; 21 if (abs(LV[i] - g) < abs(LV[gi] - g)) gi = i; 22 if (abs(LV[i] - b) < abs(LV[bi] - b)) bi = i; 23 } 24 int dc = (LV[ri] - r) * (LV[ri] - r) + (LV[gi] - g) * (LV[gi] - g) + (LV[bi] - b) * (LV[bi] - b); 25 int gray = (r + g + b) / 3, gk = (gray - 8 + 5) / 10; 26 if (gk < 0) gk = 0; 27 if (gk > 23) gk = 23; 28 int gv = 8 + gk * 10; 29 int dg = (gv - r) * (gv - r) + (gv - g) * (gv - g) + (gv - b) * (gv - b); 30 short idx = (short)(dg < dc ? 232 + gk : 16 + 36 * ri + 6 * gi + bi); 31 cache[c] = idx; 32 return idx; 33 } 34 35 void tk_att(short fg, short bg, attr_t a) 36 { 37 if (fg < 0) fg = TK_FG; 38 if (bg < 0) bg = TK_BG; 39 int p = alloc_pair(fg, bg); 40 if (p < 0) p = 0; 41 attr_set(a, 0, &p); 42 } 43 44 void tk_attc(Color fg, Color bg, attr_t a) { tk_att(tk_xterm(fg), tk_xterm(bg), a); } 45 46 /* ------------------------------------------------------------------ texto */ 47 48 int tk_put(int y, int x, const char *s, int max) 49 { 50 int n = 0; 51 const char *p = s; 52 while (*p && n < max) { 53 const char *q = p; 54 int c = (unsigned char)*q++; 55 if (c >= 0xC0) while ((*q & 0xC0) == 0x80) q++; 56 n++; 57 p = q; 58 } 59 mvaddnstr(y, x, s, (int)(p - s)); 60 return n; 61 } 62 63 int tk_ulen(const char *s) 64 { 65 int n = 0; 66 for (; *s; s++) if (((unsigned char)*s & 0xC0) != 0x80) n++; 67 return n; 68 } 69 70 void tk_fill(int y, int x, int w, const char *ch) 71 { 72 for (int i = 0; i < w; i++) mvaddstr(y, x + i, ch); 73 } 74 75 void tk_box(int y, int x, int h, int w, const char *title, Color accent) 76 { 77 tk_att(TK_DEF, 236, 0); 78 for (int j = 0; j < h; j++) tk_fill(y + j, x, w, " "); 79 tk_att(tk_xterm(accent), 236, A_BOLD); 80 mvaddstr(y, x, "┌"); mvaddstr(y, x + w - 1, "┐"); 81 mvaddstr(y + h - 1, x, "└"); mvaddstr(y + h - 1, x + w - 1, "┘"); 82 for (int i = 1; i < w - 1; i++) { mvaddstr(y, x + i, "─"); mvaddstr(y + h - 1, x + i, "─"); } 83 for (int j = 1; j < h - 1; j++) { mvaddstr(y + j, x, "│"); mvaddstr(y + j, x + w - 1, "│"); } 84 if (title && *title) { 85 tk_att(tk_xterm(accent), 236, A_BOLD); 86 mvaddstr(y, x + 2, " "); 87 int n = tk_put(y, x + 3, title, w - 6); 88 mvaddstr(y, x + 3 + n, " "); 89 } 90 } 91 92 void tk_section(int y, int x, int w, const char *title) 93 { 94 tk_att(244, TK_DEF, 0); 95 tk_fill(y, x, w, "─"); 96 tk_att(tk_xterm(C_ACCENT), TK_DEF, A_BOLD); 97 mvaddstr(y, x + 1, " "); 98 int n = tk_put(y, x + 2, title, w - 4); 99 mvaddstr(y, x + 2 + n, " "); 100 } 101 102 void tk_clear(void) 103 { 104 erase(); 105 tk_att(TK_DEF, TK_DEF, 0); 106 for (int y = 0; y < LINES; y++) tk_fill(y, 0, COLS, " "); 107 } 108 109 /* ------------------------------------------------------------------ lienzo */ 110 111 static Color *pix; 112 static int pix_w, pix_h; 113 static Surface pix_surf; 114 115 static void span_pix(Surface *s, int x, int y, int n, Color c) 116 { 117 (void)s; 118 Color *p = pix + y * pix_w + x; 119 while (n--) *p++ = c; 120 } 121 122 Surface *tk_pix_begin(int w, int h) 123 { 124 if (w * 2 * h > pix_w * pix_h || !pix) { 125 free(pix); 126 pix = malloc(sizeof(Color) * (size_t)w * (size_t)h * 2); 127 } 128 pix_w = w; pix_h = h * 2; 129 surf_init(&pix_surf, pix_w, pix_h, span_pix, 0); 130 return &pix_surf; 131 } 132 133 void tk_pix_blit(int y0, int x0) 134 { 135 for (int r = 0; r < pix_h / 2; r++) 136 for (int col = 0; col < pix_w; col++) { 137 Color top = pix[(2 * r) * pix_w + col], bot = pix[(2 * r + 1) * pix_w + col]; 138 tk_att(tk_xterm(top), tk_xterm(bot), 0); 139 mvaddstr(y0 + r, x0 + col, top == bot ? " " : "▀"); 140 } 141 } 142 143 void tk_pix_text(int y0, int x0, int px, int py, const char *s, Color fg, Color bg, attr_t a) 144 { 145 int col = x0 + px, row = y0 + py / 2; 146 if (px < 0 || py < 0 || px >= pix_w || py >= pix_h) return; 147 tk_attc(fg, bg, a); 148 mvaddstr(row, col, s); 149 } 150 151 /* ------------------------------------------------------------------ formularios */ 152 153 void (*tk_form_logo)(int cols); 154 155 void tk_draw_form(const Form *form, HitList *hits) 156 { 157 hits->n = 0; 158 tk_clear(); 159 int H = LINES, W = COLS; 160 if (tk_form_logo) tk_form_logo(W); 161 int rows = form->n + 6; 162 int w = 60 < W - 2 ? 60 : W - 2, h = rows < H - 4 ? rows : H - 4; 163 int x = (W - w) / 2, y = 3 + (H - 4 - h) / 2; 164 tk_box(y, x, h, w, form->title, C_ACCENT); 165 int line = y + 2; 166 for (int i = 0; i < form->n && line < y + h - 2; i++) { 167 const Field *fd = &form->f[i]; 168 if (fd->hidden) continue; 169 bool sel = i == form->cur; 170 char buf[160]; 171 switch (fd->type) { 172 case F_LABEL: 173 tk_att(246, 236, 0); 174 tk_put(line, x + 3, fd->label, w - 6); 175 break; 176 case F_BUTTON: 177 snprintf(buf, sizeof buf, " %s ", fd->label); 178 if (sel) tk_att(tk_xterm(C_BLACK), tk_xterm(C_ACCENT), A_BOLD); 179 else tk_att(fd->disabled ? 242 : 255, 238, 0); 180 tk_put(line, x + 3, buf, w - 6); 181 break; 182 case F_CHOICE: 183 tk_att(sel ? tk_xterm(C_ACCENT) : 250, 236, sel ? A_BOLD : 0); 184 tk_put(line, x + 3, fd->label, 22); 185 snprintf(buf, sizeof buf, "◀ %s ▶", fd->nchoices ? fd->choices[fd->value] : ""); 186 if (sel) tk_att(tk_xterm(C_BLACK), tk_xterm(C_ACCENT), A_BOLD); 187 else tk_att(255, 238, 0); 188 tk_put(line, x + 26, buf, w - 29); 189 break; 190 case F_TEXT: 191 tk_att(sel ? tk_xterm(C_ACCENT) : 250, 236, sel ? A_BOLD : 0); 192 tk_put(line, x + 3, fd->label, 22); 193 tk_att(255, 240, 0); 194 tk_fill(line, x + 26, w - 30, " "); 195 { 196 char sh[64]; 197 const char *t = form_shown(fd, sh, sizeof sh); 198 tk_put(line, x + 27, t, w - 32); 199 if (sel) { tk_att(tk_xterm(C_BLACK), tk_xterm(C_ACCENT), 0); mvaddstr(line, x + 27 + tk_ulen(t), " "); } 200 } 201 break; 202 } 203 hit_add(hits, x + 1, line, w - 2, 1, HIT_APP, i); 204 line++; 205 } 206 if (form->msg[0]) { 207 tk_att(tk_xterm(form->msg_err ? C_ERR : C_OK), 236, A_BOLD); 208 tk_put(y + h - 2, x + 3, form->msg, w - 6); 209 } 210 tk_att(244, TK_DEF, 0); 211 tk_put(H - 1, 1, g_lang ? "↑↓ move · ←→ change · Enter select · Esc back" 212 : "↑↓ mover · ←→ cambiar · Enter elegir · Esc volver", W - 2); 213 refresh(); 214 } 215 216 /* ------------------------------------------------------------------ input */ 217 218 static int translate(int ch) 219 { 220 switch (ch) { 221 case KEY_UP: return K_UP; 222 case KEY_DOWN: return K_DOWN; 223 case KEY_LEFT: return K_LEFT; 224 case KEY_RIGHT: return K_RIGHT; 225 case KEY_ENTER: case '\n': case '\r': return K_OK; 226 case 27: return K_BACK; 227 case '\t': case KEY_BTAB: return K_TAB; 228 case KEY_BACKSPACE: case 127: case 8: return K_BKSP; 229 case KEY_DC: return K_DEL; 230 case KEY_HOME: return K_HOME; 231 case KEY_END: return K_END; 232 case KEY_PPAGE: return K_PGUP; 233 case KEY_NPAGE: return K_PGDN; 234 } 235 if (ch >= KEY_F(1) && ch <= KEY_F(10)) return K_F1 + ch - KEY_F(1); 236 return ch; 237 } 238 239 static int read_one(InEvent *ev) 240 { 241 wint_t wc; 242 int r = get_wch(&wc); 243 if (r == ERR) return 0; 244 if (r == KEY_CODE_YES) { 245 if (wc == KEY_RESIZE) { ev->type = EV_RESIZE; return 1; } 246 if (wc == KEY_MOUSE) { 247 MEVENT me; 248 if (getmouse(&me) != OK) return 0; 249 if (me.bstate & (BUTTON1_PRESSED | BUTTON1_CLICKED)) { 250 ev->type = EV_CLICK; ev->x = me.x; ev->y = me.y; 251 return 1; 252 } 253 #ifdef BUTTON5_PRESSED 254 if (me.bstate & (BUTTON4_PRESSED | BUTTON5_PRESSED)) { 255 ev->type = EV_WHEEL; ev->key = (me.bstate & BUTTON4_PRESSED) ? 1 : -1; ev->x = me.x; ev->y = me.y; 256 return 1; 257 } 258 #endif 259 return 0; 260 } 261 /* las teclas de ncurses que no conocemos se descartan: sus codigos 262 * se pisan con los K_* (KEY_HOME caia como K_TAB) */ 263 int k = translate((int)wc); 264 if (k == (int)wc) return 0; 265 ev->type = EV_KEY; ev->key = k; 266 return 1; 267 } 268 ev->type = EV_KEY; 269 ev->key = wc < 256 ? translate((int)wc) : '?'; 270 return 1; 271 } 272 273 int tk_wait(int timeout_ms, const int *fds, int nfds, InEvent *ev) 274 { 275 ev->type = EV_NONE; 276 if (read_one(ev)) return 1; 277 struct pollfd pf[16]; 278 pf[0].fd = 0; pf[0].events = POLLIN; 279 int n = 1; 280 for (int i = 0; i < nfds && n < 16; i++) { pf[n].fd = fds[i]; pf[n].events = POLLIN; n++; } 281 int r = poll(pf, (nfds_t)n, timeout_ms); 282 if (r > 0 && (pf[0].revents & POLLIN)) return read_one(ev); 283 return 0; 284 } 285 286 /* ------------------------------------------------------------------ inicio */ 287 288 void tk_close(void) 289 { 290 endwin(); 291 free(pix); 292 pix = 0; 293 pix_w = pix_h = 0; 294 } 295 296 void tk_open(void) 297 { 298 setlocale(LC_ALL, ""); 299 initscr(); 300 cbreak(); 301 noecho(); 302 keypad(stdscr, TRUE); 303 nodelay(stdscr, TRUE); 304 curs_set(0); 305 set_escdelay(25); 306 start_color(); 307 use_default_colors(); 308 { 309 int p = alloc_pair(TK_FG, TK_BG); 310 if (p > 0 && p < 256) bkgd((chtype)COLOR_PAIR(p) | ' '); 311 } 312 #ifdef BUTTON5_PRESSED 313 mousemask(BUTTON1_PRESSED | BUTTON1_CLICKED | BUTTON4_PRESSED | BUTTON5_PRESSED, 0); 314 #else 315 mousemask(BUTTON1_PRESSED | BUTTON1_CLICKED, 0); 316 #endif 317 mouseinterval(0); 318 }