1 //: Take raw control of the text-mode display and console, putting it in
2 //: 'console' mode rather than the usual automatically-scrolling 'typewriter'
5 //:: Display management
7 :(before
"End Globals")
9 int Display_column
= 0;
11 :(before
"End Includes")
12 #define CHECK_SCREEN \
13 if (!tb_is_active()) { \
15 raise << maybe(current_recipe_name()) << "tried to print to real screen in a test!\n" << end(); \
17 raise << maybe(current_recipe_name()) << "tried to print to real screen before 'open-console' or after 'close-console'\n" << end(); \
20 #define CHECK_CONSOLE \
21 if (!tb_is_active()) { \
23 raise << maybe(current_recipe_name()) << "tried to read event from real keyboard/mouse in a test!\n" << end(); \
25 raise << maybe(current_recipe_name()) << "tried to read event from real keyboard/mouse before 'open-console' or after 'close-console'\n" << end(); \
29 :(before
"End Primitive Recipe Declarations")
31 :(before
"End Primitive Recipe Numbers")
32 put(Recipe_ordinal
, "open-console", OPEN_CONSOLE
);
33 :(before
"End Primitive Recipe Checks")
37 :(before
"End Primitive Recipe Implementations")
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) {
46 raise
<< "sorry, Mu doesn't support windows wider than 222 characters in console mode. Please resize your window.\n" << end();
48 raise
<< "sorry, Mu doesn't support windows taller than 222 characters in console mode. Please resize your window.\n" << end();
54 :(before
"End Primitive Recipe Declarations")
56 :(before
"End Primitive Recipe Numbers")
57 put(Recipe_ordinal
, "close-console", CLOSE_CONSOLE
);
58 :(before
"End Primitive Recipe Checks")
62 :(before
"End Primitive Recipe Implementations")
68 :(before
"End Primitive Recipe Declarations")
70 :(before
"End Primitive Recipe Numbers")
71 put(Recipe_ordinal
, "clear-display", CLEAR_DISPLAY
);
72 :(before
"End Primitive Recipe Checks")
76 :(before
"End Primitive Recipe Implementations")
80 Display_row
= Display_column
= 0;
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();
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();
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();
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();
112 :(before
"End Primitive Recipe Implementations")
113 case PRINT_CHARACTER_TO_DISPLAY
: {
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);
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
;
128 tb_print(c
, color
, bg_color
);
129 // track row and column, mimicking what happens on screen
131 if (Display_row
< height
-1) ++Display_row
; // otherwise we scroll and Display_row remains unchanged
133 else if (c
== '\r') {
136 else if (c
== '\b') {
137 if (Display_column
> 0) --Display_column
;
141 if (Display_column
>= width
) {
143 if (Display_row
< height
-1) ++Display_row
;
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
: {
157 :(before
"End Primitive Recipe Implementations")
158 case CURSOR_POSITION_ON_DISPLAY
: {
161 products
.at(0).push_back(Display_row
);
162 products
.at(1).push_back(Display_column
);
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();
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();
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();
186 :(before
"End Primitive Recipe Implementations")
187 case MOVE_CURSOR_ON_DISPLAY
: {
189 Display_row
= ingredients
.at(0).at(0);
190 Display_column
= ingredients
.at(1).at(0);
191 tb_set_cursor(Display_column
, Display_row
);
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
: {
203 :(before
"End Primitive Recipe Implementations")
204 case MOVE_CURSOR_DOWN_ON_DISPLAY
: {
207 int height
= (h
>= 0) ? h
: 0;
208 if (Display_row
< height
-1) {
210 tb_set_cursor(Display_column
, Display_row
);
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
: {
223 :(before
"End Primitive Recipe Implementations")
224 case MOVE_CURSOR_UP_ON_DISPLAY
: {
226 if (Display_row
> 0) {
228 tb_set_cursor(Display_column
, Display_row
);
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
: {
241 :(before
"End Primitive Recipe Implementations")
242 case MOVE_CURSOR_RIGHT_ON_DISPLAY
: {
245 int width
= (w
>= 0) ? w
: 0;
246 if (Display_column
< width
-1) {
248 tb_set_cursor(Display_column
, Display_row
);
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
: {
261 :(before
"End Primitive Recipe Implementations")
262 case MOVE_CURSOR_LEFT_ON_DISPLAY
: {
264 if (Display_column
> 0) {
266 tb_set_cursor(Display_column
, Display_row
);
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();
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;
281 tb_set_cursor(Display_column
, Display_row
);
284 :(before
"End Primitive Recipe Declarations")
286 :(before
"End Primitive Recipe Numbers")
287 put(Recipe_ordinal
, "display-width", DISPLAY_WIDTH
);
288 :(before
"End Primitive Recipe Checks")
289 case DISPLAY_WIDTH
: {
292 :(before
"End Primitive Recipe Implementations")
293 case DISPLAY_WIDTH
: {
296 products
.at(0).push_back(tb_width());
300 :(before
"End Primitive Recipe Declarations")
302 :(before
"End Primitive Recipe Numbers")
303 put(Recipe_ordinal
, "display-height", DISPLAY_HEIGHT
);
304 :(before
"End Primitive Recipe Checks")
305 case DISPLAY_HEIGHT
: {
308 :(before
"End Primitive Recipe Implementations")
309 case DISPLAY_HEIGHT
: {
312 products
.at(0).push_back(tb_height());
316 //:: Keyboard/mouse management
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
: {
326 :(before
"End Primitive Recipe Implementations")
327 case WAIT_FOR_SOME_INTERACTION
: {
330 tb_poll_event(&event
);
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
: {
342 :(before
"End Primitive Recipe Implementations")
343 case CHECK_FOR_INTERACTION
: {
345 products
.resize(2); // result and status
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);
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);
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);
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);
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);
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);
402 :(before
"End Primitive Recipe Declarations")
404 :(before
"End Primitive Recipe Numbers")
405 put(Recipe_ordinal
, "interactions-left?", INTERACTIONS_LEFT
);
406 :(before
"End Primitive Recipe Checks")
407 case INTERACTIONS_LEFT
: {
410 :(before
"End Primitive Recipe Implementations")
411 case INTERACTIONS_LEFT
: {
414 products
.at(0).push_back(tb_event_ready());
418 //: hacks to make text-mode apps more responsive under Unix
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
: {
428 :(before
"End Primitive Recipe Implementations")
429 case CLEAR_LINE_ON_DISPLAY
: {
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
);
438 :(before
"End Primitive Recipe Declarations")
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
: {
446 :(before
"End Primitive Recipe Implementations")
447 case CLEAR_DISPLAY_FROM
: {
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
);
460 tb_set_cursor(Display_column
, Display_row
);