juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

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 }