https://github.com/akkartik/mu/blob/master/080display.cc

      1 //: Take raw control of the text-mode display and console, putting it in
      2 //: 'console' mode rather than the usual automatically-scrolling 'typewriter'
      3 //: mode.
      4
      5 //:: Display management
      6
      7 :(before "End Globals")
      8 int Display_row = 0;
      9 int Display_column = 0;
     10
     11 :(before "End Includes")
     12 #define CHECK_SCREEN \
     13     if (!tb_is_active()) { \
     14       if (Run_tests) \
     15         raise << maybe(current_recipe_name()) << "tried to print to real screen in a test!\n" << end(); \
     16       else \
     17         raise << maybe(current_recipe_name()) << "tried to print to real screen before 'open-console' or after 'close-console'\n" << end(); \
     18       break; \
     19     }
     20 #define CHECK_CONSOLE \
     21     if (!tb_is_active()) { \
     22       if (Run_tests) \
     23         raise << maybe(current_recipe_name()) << "tried to read event from real keyboard/mouse in a test!\n" << end(); \
     24       else \
     25         raise << maybe(current_recipe_name()) << "tried to read event from real keyboard/mouse before 'open-console' or after 'close-console'\n" << end(); \
     26       break; \
     27     }
     28
     29 :(before "End Primitive Recipe Declarations")
     30 OPEN_CONSOLE,
     31 :(before "End Primitive Recipe Numbers")
     32 put(Recipe_ordinal, "open-console", OPEN_CONSOLE);
     33 :(before "End Primitive Recipe Checks")
     34 case OPEN_CONSOLE: {
     35   break;
     36 }
     37 :(before "End Primitive Recipe Implementations")
     38 case OPEN_CONSOLE: {
     39   tb_init();
     40   std::setvbuf(stdout, NULL, _IONBF, 0);  // disable buffering in cout
     41   Display_row = Display_column = 0;
     42   int width = tb_width();
     43   int height = tb_height();
     44   if (width > 222 || height > 222) {
     45     if (width > 222)
     46       raise << "sorry, Mu doesn't support windows wider than 222 characters in console mode. Please resize your window.\n" << end();
     47     if (height > 222)
     48       raise << "sorry, Mu doesn't support windows taller than 222 characters in console mode. Please resize your window.\n" << end();
     49     exit(1);
     50   }
     51   break;
     52 }
     53
     54 :(before "End Primitive Recipe Declarations")
     55 CLOSE_CONSOLE,
     56 :(before "End Primitive Recipe Numbers")
     57 put(Recipe_ordinal, "close-console", CLOSE_CONSOLE);
     58 :(before "End Primitive Recipe Checks")
     59 case CLOSE_CONSOLE: {
     60   break;
     61 }
     62 :(before "End Primitive Recipe Implementations")
     63 case CLOSE_CONSOLE: {
     64   tb_shutdown();
     65   break;
     66 }
     67
     68 :(before "End Primitive Recipe Declarations")
     69 CLEAR_DISPLAY,
     70 :(before "End Primitive Recipe Numbers")
     71 put(Recipe_ordinal, "clear-display", CLEAR_DISPLAY);
     72 :(before "End Primitive Recipe Checks")
     73 case CLEAR_DISPLAY: {
     74   break;
     75 }
     76 :(before "End Primitive Recipe Implementations")
     77 case CLEAR_DISPLAY: {
     78   CHECK_SCREEN;
     79   tb_clear();
     80   Display_row = Display_column = 0;
     81   break;
     82 }
     83
     84 :(before "End Primitive Recipe Declarations")
     85 PRINT_CHARACTER_TO_DISPLAY,
     86 :(before "End Primitive Recipe Numbers")
     87 put(Recipe_ordinal, "print-character-to-display", PRINT_CHARACTER_TO_DISPLAY);
     88 :(before "End Primitive Recipe Checks")
     89 case PRINT_CHARACTER_TO_DISPLAY: {
     90   if (inst.ingredients.empty()) {
     91     raise << maybe(get(Recipe, r).name) << "'print-character-to-display' requires at least one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
     92     break;
     93   }
     94   if (!is_mu_number(inst.ingredients.at(0))) {
     95     raise << maybe(get(Recipe, r).name) << "first ingredient of 'print-character-to-display' should be a character, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
     96     break;
     97   }
     98   if (SIZE(inst.ingredients) > 1) {
     99     if (!is_mu_number(inst.ingredients.at(1))) {
    100       raise << maybe(get(Recipe, r).name) << "second ingredient of 'print-character-to-display' should be a foreground color number, but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
    101       break;
    102     }
    103   }
    104   if (SIZE(inst.ingredients) > 2) {
    105     if (!is_mu_number(inst.ingredients.at(2))) {
    106       raise << maybe(get(Recipe, r).name) << "third ingredient of 'print-character-to-display' should be a background color number, but got '" << inst.ingredients.at(2).original_string << "'\n" << end();
    107       break;
    108     }
    109   }
    110   break;
    111 }
    112 :(before "End Primitive Recipe Implementations")
    113 case PRINT_CHARACTER_TO_DISPLAY: {
    114   CHECK_SCREEN;
    115   int h=tb_height(), w=tb_width();
    116   int height = (h >= 0) ? h : 0;
    117   int width = (w >= 0) ? w : 0;
    118   int c = ingredients.at(0).at(0);
    119   int color = TB_WHITE;
    120   if (SIZE(ingredients) > 1) {
    121     color = ingredients.at(1).at(0);
    122   }
    123   int bg_color = TB_BLACK;
    124   if (SIZE(ingredients) > 2) {
    125     bg_color = ingredients.at(2).at(0);
    126     if (bg_color == 0) bg_color = TB_BLACK;
    127   }
    128   tb_print(c, color, bg_color);
    129   // track row and column, mimicking what happens on screen
    130   if (c == '\n') {
    131     if (Display_row < height-1) ++Display_row;  // otherwise we scroll and Display_row remains unchanged
    132   }
    133   else if (c == '\r') {
    134     Display_column = 0;
    135   }
    136   else if (c == '\b') {
    137     if (Display_column > 0) --Display_column;
    138   }
    139   else {
    140     ++Display_column;
    141     if (Display_column >= width) {
    142       Display_column = 0;
    143       if (Display_row < height-1) ++Display_row;
    144     }
    145   }
    146   break;
    147 }
    148
    149 :(before "End Primitive Recipe Declarations")
    150 CURSOR_POSITION_ON_DISPLAY,
    151 :(before "End Primitive Recipe Numbers")
    152 put(Recipe_ordinal, "cursor-position-on-display", CURSOR_POSITION_ON_DISPLAY);
    153 :(before "End Primitive Recipe Checks")
    154 case CURSOR_POSITION_ON_DISPLAY: {
    155   break;
    156 }
    157 :(before "End Primitive Recipe Implementations")
    158 case CURSOR_POSITION_ON_DISPLAY: {
    159   CHECK_SCREEN;
    160   products.resize(2);
    161   products.at(0).push_back(Display_row);
    162   products.at(1).push_back(Display_column);
    163   break;
    164 }
    165
    166 :(before "End Primitive Recipe Declarations")
    167 MOVE_CURSOR_ON_DISPLAY,
    168 :(before "End Primitive Recipe Numbers")
    169 put(Recipe_ordinal, "move-cursor-on-display", MOVE_CURSOR_ON_DISPLAY);
    170 :(before "End Primitive Recipe Checks")
    171 case MOVE_CURSOR_ON_DISPLAY: {
    172   if (SIZE(inst.ingredients) != 2) {
    173     raise << maybe(get(Recipe, r).name) << "'move-cursor-on-display' requires two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
    174     break;
    175   }
    176   if (!is_mu_number(inst.ingredients.at(0))) {
    177     raise << maybe(get(Recipe, r).name) << "first ingredient of 'move-cursor-on-display' should be a row number, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
    178     break;
    179   }
    180   if (!is_mu_number(inst.ingredients.at(1))) {
    181     raise << maybe(get(Recipe, r).name) << "second ingredient of 'move-cursor-on-display' should be a column number, but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
    182     break;
    183   }
    184   break;
    185 }
    186 :(before "End Primitive Recipe Implementations")
    187 case MOVE_CURSOR_ON_DISPLAY: {
    188   CHECK_SCREEN;
    189   Display_row = ingredients.at(0).at(0);
    190   Display_column = ingredients.at(1).at(0);
    191   tb_set_cursor(Display_column, Display_row);
    192   break;
    193 }
    194
    195 :(before "End Primitive Recipe Declarations")
    196 MOVE_CURSOR_DOWN_ON_DISPLAY,
    197 :(before "End Primitive Recipe Numbers")
    198 put(Recipe_ordinal, "move-cursor-down-on-display", MOVE_CURSOR_DOWN_ON_DISPLAY);
    199 :(before "End Primitive Recipe Checks")
    200 case MOVE_CURSOR_DOWN_ON_DISPLAY: {
    201   break;
    202 }
    203 :(before "End Primitive Recipe Implementations")
    204 case MOVE_CURSOR_DOWN_ON_DISPLAY: {
    205   CHECK_SCREEN;
    206   int h=tb_height();
    207   int height = (h >= 0) ? h : 0;
    208   if (Display_row < height-1) {
    209     ++Display_row;
    210     tb_set_cursor(Display_column, Display_row);
    211   }
    212   break;
    213 }
    214
    215 :(before "End Primitive Recipe Declarations")
    216 MOVE_CURSOR_UP_ON_DISPLAY,
    217 :(before "End Primitive Recipe Numbers")
    218 put(Recipe_ordinal, "move-cursor-up-on-display", MOVE_CURSOR_UP_ON_DISPLAY);
    219 :(before "End Primitive Recipe Checks")
    220 case MOVE_CURSOR_UP_ON_DISPLAY: {
    221   break;
    222 }
    223 :(before "End Primitive Recipe Implementations")
    224 case MOVE_CURSOR_UP_ON_DISPLAY: {
    225   CHECK_SCREEN;
    226   if (Display_row > 0) {
    227     --Display_row;
    228     tb_set_cursor(Display_column, Display_row);
    229   }
    230   break;
    231 }
    232
    233 :(before "End Primitive Recipe Declarations")
    234 MOVE_CURSOR_RIGHT_ON_DISPLAY,
    235 :(before "End Primitive Recipe Numbers")
    236 put(Recipe_ordinal, "move-cursor-right-on-display", MOVE_CURSOR_RIGHT_ON_DISPLAY);
    237 :(before "End Primitive Recipe Checks")
    238 case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
    239   break;
    240 }
    241 :(before "End Primitive Recipe Implementations")
    242 case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
    243   CHECK_SCREEN;
    244   int w=tb_width();
    245   int width = (w >= 0) ? w : 0;
    246   if (Display_column < width-1) {
    247     ++Display_column;
    248     tb_set_cursor(Display_column, Display_row);
    249   }
    250   break;
    251 }
    252
    253 :(before "End Primitive Recipe Declarations")
    254 MOVE_CURSOR_LEFT_ON_DISPLAY,
    255 :(before "End Primitive Recipe Numbers")
    256 put(Recipe_ordinal, "move-cursor-left-on-display", MOVE_CURSOR_LEFT_ON_DISPLAY);
    257 :(before "End Primitive Recipe Checks")
    258 case MOVE_CURSOR_LEFT_ON_DISPLAY: {
    259   break;
    260 }
    261 :(before "End Primitive Recipe Implementations")
    262 case MOVE_CURSOR_LEFT_ON_DISPLAY: {
    263   CHECK_SCREEN;
    264   if (Display_column > 0) {
    265     --Display_column;
    266     tb_set_cursor(Display_column, Display_row);
    267   }
    268   break;
    269 }
    270
    271 //: as a convenience, make $print mostly work in console mode
    272 :(before "End $print 10/newline Special-cases")
    273 else if (tb_is_active()) {
    274   move_cursor_to_start_of_next_line_on_display();
    275 }
    276 :(code)
    277 void move_cursor_to_start_of_next_line_on_display() {
    278   if (Display_row < tb_height()-1) ++Display_row;
    279   else Display_row = 0;
    280   Display_column = 0;
    281   tb_set_cursor(Display_column, Display_row);
    282 }
    283
    284 :(before "End Primitive Recipe Declarations")
    285 DISPLAY_WIDTH,
    286 :(before "End Primitive Recipe Numbers")
    287 put(Recipe_ordinal, "display-width", DISPLAY_WIDTH);
    288 :(before "End Primitive Recipe Checks")
    289 case DISPLAY_WIDTH: {
    290   break;
    291 }
    292 :(before "End Primitive Recipe Implementations")
    293 case DISPLAY_WIDTH: {
    294   CHECK_SCREEN;
    295   products.resize(1);
    296   products.at(0).push_back(tb_width());
    297   break;
    298 }
    299
    300 :(before "End Primitive Recipe Declarations")
    301 DISPLAY_HEIGHT,
    302 :(before "End Primitive Recipe Numbers")
    303 put(Recipe_ordinal, "display-height", DISPLAY_HEIGHT);
    304 :(before "End Primitive Recipe Checks")
    305 case DISPLAY_HEIGHT: {
    306   break;
    307 }
    308 :(before "End Primitive Recipe Implementations")
    309 case DISPLAY_HEIGHT: {
    310   CHECK_SCREEN;
    311   products.resize(1);
    312   products.at(0).push_back(tb_height());
    313   break;
    314 }
    315
    316 //:: Keyboard/mouse management
    317
    318 :(before "End Primitive Recipe Declarations")
    319 WAIT_FOR_SOME_INTERACTION,
    320 :(before "End Primitive Recipe Numbers")
    321 put(Recipe_ordinal, "wait-for-some-interaction", WAIT_FOR_SOME_INTERACTION);
    322 :(before "End Primitive Recipe Checks")
    323 case WAIT_FOR_SOME_INTERACTION: {
    324   break;
    325 }
    326 :(before "End Primitive Recipe Implementations")
    327 case WAIT_FOR_SOME_INTERACTION: {
    328   CHECK_SCREEN;
    329   tb_event event;
    330   tb_poll_event(&event);
    331   break;
    332 }
    333
    334 :(before "End Primitive Recipe Declarations")
    335 CHECK_FOR_INTERACTION,
    336 :(before "End Primitive Recipe Numbers")
    337 put(Recipe_ordinal, "check-for-interaction", CHECK_FOR_INTERACTION);
    338 :(before "End Primitive Recipe Checks")
    339 case CHECK_FOR_INTERACTION: {
    340   break;
    341 }
    342 :(before "End Primitive Recipe Implementations")
    343 case CHECK_FOR_INTERACTION: {
    344   CHECK_CONSOLE;
    345   products.resize(2);  // result and status
    346   tb_event event;
    347   int event_type = tb_peek_event(&event, 5/*ms*/);
    348   if (event_type == TB_EVENT_KEY && event.ch) {
    349     products.at(0).push_back(/*text event*/0);
    350     products.at(0).push_back(event.ch);
    351     products.at(0).push_back(0);
    352     products.at(0).push_back(0);
    353     products.at(1).push_back(/*found*/true);
    354     break;
    355   }
    356   // treat keys within ascii as unicode characters
    357   if (event_type == TB_EVENT_KEY && event.key < 0xff) {
    358     products.at(0).push_back(/*text event*/0);
    359     if (event.key == TB_KEY_CTRL_C) exit(1);
    360     if (event.key == TB_KEY_BACKSPACE2) event.key = TB_KEY_BACKSPACE;
    361     if (event.key == TB_KEY_CARRIAGE_RETURN) event.key = TB_KEY_NEWLINE;
    362     products.at(0).push_back(event.key);
    363     products.at(0).push_back(0);
    364     products.at(0).push_back(0);
    365     products.at(1).push_back(/*found*/true);
    366     break;
    367   }
    368   // keys outside ascii aren't unicode characters but arbitrary termbox inventions
    369   if (event_type == TB_EVENT_KEY) {
    370     products.at(0).push_back(/*keycode event*/1);
    371     products.at(0).push_back(event.key);
    372     products.at(0).push_back(0);
    373     products.at(0).push_back(0);
    374     products.at(1).push_back(/*found*/true);
    375     break;
    376   }
    377   if (event_type == TB_EVENT_MOUSE) {
    378     products.at(0).push_back(/*touch event*/2);
    379     products.at(0).push_back(event.key);  // which button, etc.
    380     products.at(0).push_back(event.y);  // row
    381     products.at(0).push_back(event.x);  // column
    382     products.at(1).push_back(/*found*/true);
    383     break;
    384   }
    385   if (event_type == TB_EVENT_RESIZE) {
    386     products.at(0).push_back(/*resize event*/3);
    387     products.at(0).push_back(event.w);  // width
    388     products.at(0).push_back(event.h);  // height
    389     products.at(0).push_back(0);
    390     products.at(1).push_back(/*found*/true);
    391     break;
    392   }
    393   assert(event_type == 0);
    394   products.at(0).push_back(0);
    395   products.at(0).push_back(0);
    396   products.at(0).push_back(0);
    397   products.at(0).push_back(0);
    398   products.at(1).push_back(/*found*/false);
    399   break;
    400 }
    401
    402 :(before "End Primitive Recipe Declarations")
    403 INTERACTIONS_LEFT,
    404 :(before "End Primitive Recipe Numbers")
    405 put(Recipe_ordinal, "interactions-left?", INTERACTIONS_LEFT);
    406 :(before "End Primitive Recipe Checks")
    407 case INTERACTIONS_LEFT: {
    408   break;
    409 }
    410 :(before "End Primitive Recipe Implementations")
    411 case INTERACTIONS_LEFT: {
    412   CHECK_CONSOLE;
    413   products.resize(1);
    414   products.at(0).push_back(tb_event_ready());
    415   break;
    416 }
    417
    418 //: hacks to make text-mode apps more responsive under Unix
    419
    420 :(before "End Primitive Recipe Declarations")
    421 CLEAR_LINE_ON_DISPLAY,
    422 :(before "End Primitive Recipe Numbers")
    423 put(Recipe_ordinal, "clear-line-on-display", CLEAR_LINE_ON_DISPLAY);
    424 :(before "End Primitive Recipe Checks")
    425 case CLEAR_LINE_ON_DISPLAY: {
    426   break;
    427 }
    428 :(before "End Primitive Recipe Implementations")
    429 case CLEAR_LINE_ON_DISPLAY: {
    430   CHECK_SCREEN;
    431   int width = tb_width();
    432   for (int x = Display_column;  x < width;  ++x)
    433     tb_print(' ', TB_WHITE, TB_BLACK);
    434   tb_set_cursor(Display_column, Display_row);
    435   break;
    436 }
    437
    438 :(before "End Primitive Recipe Declarations")
    439 CLEAR_DISPLAY_FROM,
    440 :(before "End Primitive Recipe Numbers")
    441 put(Recipe_ordinal, "clear-display-from", CLEAR_DISPLAY_FROM);
    442 :(before "End Primitive Recipe Checks")
    443 case CLEAR_DISPLAY_FROM: {
    444   break;
    445 }
    446 :(before "End Primitive Recipe Implementations")
    447 case CLEAR_DISPLAY_FROM: {
    448   CHECK_SCREEN;
    449   // todo: error checking
    450   int row = ingredients.at(0).at(0);
    451   int column = ingredients.at(1).at(0);
    452   int left = ingredients.at(2).at(0);
    453   int right = ingredients.at(3).at(0);
    454   int height=tb_height();
    455   for (/*nada*/;  row < height;  ++row, column=left) {  // start column from left in every inner loop except first
    456     tb_set_cursor(column, row);
    457     for (/*nada*/;  column <= right;  ++column)
    458       tb_print(' ', TB_WHITE, TB_BLACK);
    459   }
    460   tb_set_cursor(Display_column, Display_row);
    461   break;
    462 }