tui.c (12968B)
1 /* tui.c - la calculadora en la terminal (ncursesw + tuikit). La expresion se arma 2 * con el mismo motor de layout que en pixeles, con una metrica de celdas. */ 3 #include <locale.h> 4 #include <stdio.h> 5 #include "tuikit.h" 6 #include "calc.h" 7 #include "layout.h" 8 #include "store.h" 9 10 static const Metric METRIC_CELLS = { 11 .cw = { 1, 1 }, .asc = { 1, 1 }, .desc = { 0, 0 }, .ax = { 1, 1 }, 12 .gap = 0, .bar = 1, .pad = 0, .rad_w = 1, .rad_gap = 0, .raise = { 1, 1 }, .drop = 1, 13 .abs_w = 1, .cells = true, 14 }; 15 16 #define BG RGB(18, 22, 30) 17 #define PANEL RGB(30, 36, 50) 18 #define FG RGB(236, 236, 236) 19 #define DIM RGB(130, 138, 156) 20 #define ACC RGB(255, 200, 60) 21 #define RESC RGB(140, 220, 255) 22 #define SEL RGB(48, 58, 80) 23 #define ERRC RGB(255, 100, 90) 24 25 static Layout L_in, L_res, L_h; 26 static Expr tmp; 27 28 /* ------------------------------------------------------------ dibujar un layout */ 29 30 static const char *sup(int ch) 31 { 32 static const char *const D[] = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; 33 if (ch >= '0' && ch <= '9') return D[ch - '0']; 34 switch (ch) { 35 case '-': return "⁻"; case '+': return "⁺"; case '(': return "⁽"; case ')': return "⁾"; 36 case 'n': return "ⁿ"; case 'x': return "ˣ"; case 'i': return "ⁱ"; case 'y': return "ʸ"; 37 case '.': return "·"; case CF_PI: return "ᵖ"; 38 } 39 return 0; 40 } 41 42 static const char *sub(int ch) 43 { 44 static const char *const D[] = { "₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉" }; 45 if (ch >= '0' && ch <= '9') return D[ch - '0']; 46 if (ch == '-') return "₋"; 47 if (ch == '+') return "₊"; 48 if (ch == '.') return "."; 49 return 0; 50 } 51 52 static void cell(int y, int x, const char *s, int x0, int w) 53 { 54 if (x < x0 || x >= x0 + w || y < 0 || y >= LINES) return; 55 mvaddstr(y, x, s); 56 } 57 58 /* dibuja el layout con la esquina en (y, x); cols visibles desde x0 con ancho w */ 59 void draw_layout(const Layout *L, int y, int x, Color fg, Color bg, int x0, int w) 60 { 61 char u[8]; 62 tk_attc(fg, bg, 0); 63 for (int i = 0; i < L->n; i++) { 64 const LItem *it = &L->it[i]; 65 int X = x + it->x, Y = y + it->y; 66 switch (it->kind) { 67 case LI_GLYPH: case LI_BOX: { 68 const char *s = 0; 69 if (it->lvl == 1) s = sup(it->code); 70 else if (it->lvl == 2) s = sub(it->code); 71 if (!s) { cf_utf8(it->code, u); s = u; } 72 if (it->kind == LI_BOX) { tk_attc(DIM, bg, 0); cell(Y, X, "□", x0, w); tk_attc(fg, bg, 0); } 73 else cell(Y, X, s, x0, w); 74 break; 75 } 76 case LI_HBAR: 77 for (int k = 0; k < it->w; k++) cell(Y, X + k, "─", x0, w); 78 break; 79 case LI_RAD: 80 for (int k = 0; k < it->h - 1; k++) if (k) cell(Y + k, X, "│", x0, w); 81 cell(Y + it->h - 1, X, "√", x0, w); 82 break; 83 case LI_PAREN: { 84 bool o = it->code == '('; 85 for (int k = 0; k < it->h; k++) 86 cell(Y + k, X, k == 0 ? (o ? "⎛" : "⎞") : k == it->h - 1 ? (o ? "⎝" : "⎠") : (o ? "⎜" : "⎟"), x0, w); 87 break; 88 } 89 case LI_ABS: 90 for (int k = 0; k < it->h; k++) cell(Y + k, X, "│", x0, w); 91 break; 92 case LI_INTEG: 93 if (it->h < 2) { cell(Y, X, "∫", x0, w); break; } 94 for (int k = 0; k < it->h; k++) cell(Y + k, X, k == 0 ? "⌠" : k == it->h - 1 ? "⌡" : "⎮", x0, w); 95 break; 96 case LI_BIGOP: 97 cell(Y + it->h - 1, X, it->code == 'S' ? "Σ" : "Π", x0, w); 98 break; 99 } 100 } 101 } 102 103 void text(int y, int x, const char *s, Color fg, Color bg) 104 { 105 tk_attc(fg, bg, 0); 106 tk_put(y, x, s, COLS - x); 107 } 108 109 void fill_row(int y, Color bg) 110 { 111 tk_attc(FG, bg, 0); 112 tk_fill(y, 0, COLS, " "); 113 } 114 115 /* ------------------------------------------------------------ pantallas */ 116 117 static int panel_hist(const Calc *c) 118 { 119 if (c->hsel >= 0) return c->hsel; 120 return c->shown && c->nh ? c->nh - 1 : -1; 121 } 122 123 void status(const Calc *c) 124 { 125 fill_row(0, PANEL); 126 int x = 1; 127 if (c->shift) { text(0, x, " S ", BG, ACC); } 128 x += 4; 129 text(0, x, calc_ang_name(c->ang), FG, PANEL); 130 x += 2; 131 static const char *const FM[] = { "", "", "FIX", "SCI", "ENG" }; 132 if (c->fmt.mode >= FMT_FIX) { text(0, x, FM[c->fmt.mode], FG, PANEL); x += 4; } 133 if (c->dec_default) { text(0, x, "Dec", FG, PANEL); x += 4; } 134 const char *mn = c->msg[0] ? c->msg : calc_mode_name(c->mode); 135 text(0, (COLS - tk_ulen(mn)) / 2, mn, c->msg[0] ? ACC : DIM, PANEL); 136 } 137 138 void help(const char *s) 139 { 140 fill_row(LINES - 1, PANEL); 141 text(LINES - 1, 1, s, DIM, PANEL); 142 } 143 144 int cur_y = -1, cur_x; 145 146 static void main_screen(Calc *c) 147 { 148 tk_attc(FG, BG, 0); 149 for (int y = 0; y < LINES; y++) tk_fill(y, 0, COLS, " "); 150 status(c); 151 layout_expr(&c->in, &METRIC_CELLS, &L_in); 152 int in_h = L_in.asc + L_in.desc; 153 if (in_h < 1) in_h = 1; 154 int res_h = 1; 155 bool mres = c->mode == MODE_MATRIX && c->shown && !c->err && c->mt.res.kind != MV_SCALAR; 156 if (mres) res_h = c->mt.res.r; 157 else if (c->shown && !c->err) { 158 calc_res_expr(c, &tmp); 159 layout_expr(&tmp, &METRIC_CELLS, &L_res); 160 res_h = L_res.asc + L_res.desc; 161 } 162 int py = LINES - 2 - (in_h + res_h + 2); 163 for (int y = py; y < LINES - 1; y++) { tk_attc(FG, PANEL, 0); tk_fill(y, 0, COLS, " "); } 164 tk_attc(c->hsel >= 0 ? ACC : DIM, PANEL, 0); 165 tk_fill(py, 0, COLS, "─"); 166 /* entrada con desplazamiento horizontal */ 167 static int scroll; 168 int avail = COLS - 4, cx = L_in.gx[c->in.cur]; 169 if (L_in.w <= avail) scroll = 0; 170 else { 171 if (cx - scroll > avail - 2) scroll = cx - avail + 2; 172 if (cx - scroll < 2) scroll = cx - 2; 173 if (scroll < 0) scroll = 0; 174 } 175 draw_layout(&L_in, py + 1, 2 - scroll, FG, PANEL, 2, avail); 176 cur_y = -1; 177 if (!c->shown) { cur_y = py + 1 + L_in.gy[c->in.cur]; cur_x = 2 - scroll + cx; } 178 int ry = py + 1 + in_h; 179 if (c->shown) { 180 if (c->err) text(ry, COLS - 2 - tk_ulen(calc_err_text(c->err)), calc_err_text(c->err), ERRC, PANEL); 181 else if (mres) { 182 /* matriz: columnas alineadas a la derecha, corchetes ⎡⎢⎣ */ 183 const MVal *m = &c->mt.res; 184 char b[48]; 185 int cw[MAT_N] = { 0 }, w = 0; 186 for (int j = 0; j < m->c; j++) { 187 for (int i = 0; i < m->r; i++) { 188 fmt_plain(mv_get(m, i, j), &c->fmt, false, b, sizeof b); 189 if (tk_ulen(b) > cw[j]) cw[j] = tk_ulen(b); 190 } 191 w += cw[j] + 2; 192 } 193 int x = COLS - 4 - w; 194 for (int i = 0; i < m->r; i++) { 195 const char *lb = m->r == 1 ? "[" : i == 0 ? "⎡" : i == m->r - 1 ? "⎣" : "⎢"; 196 const char *rb = m->r == 1 ? "]" : i == 0 ? "⎤" : i == m->r - 1 ? "⎦" : "⎥"; 197 text(ry + i, x - 1, lb, RESC, PANEL); 198 int mx = x + 1; 199 for (int j = 0; j < m->c; j++) { 200 fmt_plain(mv_get(m, i, j), &c->fmt, false, b, sizeof b); 201 text(ry + i, mx + cw[j] - tk_ulen(b), b, RESC, PANEL); 202 mx += cw[j] + 2; 203 } 204 text(ry + i, mx - 1, rb, RESC, PANEL); 205 } 206 } else { 207 draw_layout(&L_res, ry, COLS - 2 - L_res.w, RESC, PANEL, 0, COLS); 208 if (c->want_dec) text(ry + L_res.asc - 1, 2, "≈", DIM, PANEL); 209 } 210 } 211 /* historial hacia arriba */ 212 int last = panel_hist(c); 213 last = last >= 0 ? last - 1 : c->nh - 1; 214 int y = py; 215 for (int i = last; i >= 0 && y > 1; i--) { 216 Hist *h = &c->hist[i]; 217 int hr = 1; 218 if (!h->err) { 219 calc_hist_expr(c, h, &tmp); 220 layout_expr(&tmp, &METRIC_CELLS, &L_res); 221 hr = L_res.asc + L_res.desc; 222 } 223 layout_expr(&h->in, &METRIC_CELLS, &L_h); 224 int hi = L_h.asc + L_h.desc; 225 if (y - hr - hi - 1 < 1) break; 226 y -= hr; 227 if (!h->err) draw_layout(&L_res, y, COLS - 2 - L_res.w, RESC, BG, 0, COLS); 228 else text(y, COLS - 7, "Error", ERRC, BG); 229 y -= hi; 230 draw_layout(&L_h, y, 2, DIM, BG, 2, COLS - 4); 231 y -= 1; 232 } 233 help(c->sto ? "Elegí la variable: A-F, M, X, Y" : 234 c->shown ? "Tab S⇔D > guardar ↑ historial F2/@ menú F3/? funciones Ctrl-C salir" : 235 "/ fracción ^ potencia # √ $ ⁿ√ | abs Enter = F2/@ menú F3/? funciones F5 ajustes"); 236 } 237 238 static void menu_screen(const Calc *c) 239 { 240 tk_attc(FG, BG, 0); 241 for (int y = 0; y < LINES; y++) tk_fill(y, 0, COLS, " "); 242 status(c); 243 static const char *const SUB[] = { "cuentas, ∫ d/dx Σ Π", "sistemas, polinomios, SOLVE", "f(x) en una tabla", 244 "funciones", "hex, bin, operaciones de bits", "datos y regresión", "a+bi, forma polar r∠θ", 245 "MatA-D, VctA-D, Det, inversa", "ángulo, formato" }; 246 text(2, 2, "Menú", ACC, BG); 247 for (int i = 0; i <= NMODES; i++) { 248 bool sel = i == c->menu_sel; 249 Color bg = sel ? SEL : BG; 250 tk_attc(FG, bg, 0); 251 tk_fill(4 + i, 2, 50, " "); 252 char b[80]; 253 snprintf(b, sizeof b, "%d %-13s", i + 1, i < NMODES ? calc_mode_name(i) : "Ajustes"); 254 text(4 + i, 3, b, sel ? ACC : FG, bg); 255 text(4 + i, 21, SUB[i], DIM, bg); 256 } 257 help("Flechas y Enter, o el número. Esc vuelve"); 258 } 259 260 static void catalog_screen(const Calc *c) 261 { 262 tk_attc(FG, BG, 0); 263 for (int y = 0; y < LINES; y++) tk_fill(y, 0, COLS, " "); 264 status(c); 265 text(2, 2, c->cat < 0 ? "Funciones" : CATALOG[c->cat].name, ACC, BG); 266 int n = c->cat < 0 ? CATALOG_N : CATALOG[c->cat].n, rows = LINES - 6; 267 int top = c->cat_sel >= rows ? c->cat_sel - rows + 1 : 0; 268 for (int i = top; i < n && i < top + rows; i++) { 269 bool sel = i == c->cat_sel; 270 Color bg = sel ? SEL : BG; 271 tk_attc(FG, bg, 0); 272 tk_fill(4 + i - top, 2, 40, " "); 273 char num[4] = { (char)(i < 9 ? '1' + i : ' '), 0 }; 274 text(4 + i - top, 3, num, DIM, bg); 275 text(4 + i - top, 6, c->cat < 0 ? CATALOG[i].name : CATALOG[c->cat].items[i].label, sel ? ACC : FG, bg); 276 } 277 help(c->cat < 0 ? "Enter abre Esc vuelve" : "Enter inserta ← vuelve Esc vuelve"); 278 } 279 280 static void keytest_screen(const Calc *c) 281 { 282 tk_attc(FG, BG, 0); 283 for (int y = 0; y < LINES; y++) tk_fill(y, 0, COLS, " "); 284 status(c); 285 text(2, 2, "Probar el teclado (Esc dos veces sale)", ACC, BG); 286 char b[64]; 287 int k = c->keytest; 288 if (k < 0) snprintf(b, sizeof b, "-"); 289 else if (k >= K_RAW) snprintf(b, sizeof b, "crudo 0x%02X", k & 0xFF); 290 else if (k >= K_F1 && k < K_F1 + 10) snprintf(b, sizeof b, "F%d", k - K_F1 + 1); 291 else if (k >= 0x100) snprintf(b, sizeof b, "especial %d", k - 0x100); 292 else snprintf(b, sizeof b, "'%c' (%d)", k, k); 293 text(5, 4, b, FG, BG); 294 } 295 296 void tui_modes_paint(Calc *c); 297 298 static void draw(Calc *c) 299 { 300 cur_y = -1; 301 switch (c->scr) { 302 case SCR_MENU: menu_screen(c); break; 303 case SCR_FORM: { static HitList hl; tk_draw_form(&c->form, &hl); break; } 304 case SCR_CATALOG: catalog_screen(c); break; 305 case SCR_KEYTEST: keytest_screen(c); break; 306 default: 307 if (calc_main_screen(c)) main_screen(c); 308 else tui_modes_paint(c); 309 } 310 if (cur_y >= 0) { curs_set(1); move(cur_y, cur_x); } 311 else curs_set(0); 312 refresh(); 313 } 314 315 static bool vert(Calc *c, int dir) 316 { 317 if (c->scr != SCR_MAIN || c->shown) return false; 318 if (!calc_main_screen(c)) { 319 /* en los modos solo si se esta editando una expresion */ 320 bool ed = (c->mode == MODE_EQN && (c->eq.editing || (c->eq.type == EQ_SOLVE && c->eq.phase == 0))) || 321 (c->mode == MODE_STAT && c->st.editing) || (c->mode == MODE_TABLE && c->tb.phase < 2) || 322 (c->mode == MODE_GRAPH && c->gr.phase < 2); 323 if (!ed) return false; 324 } 325 layout_expr(&c->in, &METRIC_CELLS, &L_in); 326 int k = layout_cursor_vert(&L_in, &c->in, c->in.cur, dir); 327 if (k < 0) return false; 328 c->in.cur = k; 329 c->version++; 330 return true; 331 } 332 333 /* para el grafico en la terminal (tui_modes.c) */ 334 const Metric *tui_metric(void) { return &METRIC_CELLS; } 335 336 int tui_run(Calc *c) 337 { 338 setlocale(LC_ALL, ""); 339 tk_open(); 340 draw(c); 341 for (;;) { 342 InEvent ev; 343 if (!tk_wait(-1, 0, 0, &ev)) continue; 344 if (ev.type == EV_QUIT) break; 345 if (ev.type == EV_RESIZE) { draw(c); continue; } 346 if (ev.type != EV_KEY) continue; 347 int k = ev.key; 348 if (k == 3) break; /* Ctrl-C */ 349 if ((k == K_UP || k == K_DOWN) && vert(c, k == K_UP ? -1 : 1)) { draw(c); continue; } 350 calc_key(c, k); 351 if (c->dirty_save) store_save(c); 352 draw(c); 353 } 354 tk_close(); 355 store_save(c); 356 return 0; 357 }