3 * A Lua library binding for btui, Bruce's Text User Interface library.
10 // The C API changed from 5.1 to 5.2, so these shims help the code compile on >=5.2
11 #if LUA_VERSION_NUM >= 502
12 #define lua_objlen(L, i) lua_rawlen(L, i)
13 #define luaL_register(L, _, R) luaL_setfuncs(L, R, 0)
15 // Lua 5.3 introduced lua_isinteger, fall back to lua_isnumber
16 #if LUA_VERSION_NUM < 503
17 #define lua_isinteger(L, i) lua_isnumber(L, i)
20 const int BTUI_METATABLE, BTUI_ATTRIBUTES, BTUI_INVERSE_ATTRIBUTES;
22 static int Lbtui_enable(lua_State *L)
24 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
25 if (bt == NULL) luaL_error(L, "Not a BTUI object");
26 const char *modestring = luaL_optlstring(L, 2, "TUI", NULL);
27 btui_mode_t mode = BTUI_MODE_TUI;
28 if (strcmp(modestring, "normal") == 0)
29 mode = BTUI_MODE_NORMAL;
30 else if (strcmp(modestring, "TUI") != 0)
31 luaL_error(L, "Invalid BTUI mode");
32 *bt = btui_create(mode);
33 btui_move_cursor(*bt, 0, 0);
38 static int Lbtui_disable(lua_State *L)
40 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
41 if (bt == NULL) luaL_error(L, "Not a BTUI object");
42 if (*bt == NULL) luaL_error(L, "BTUI object not initialized");
47 static int Lbtui_withdisabled(lua_State *L)
49 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
50 if (bt == NULL) luaL_error(L, "Not a BTUI object");
51 int top = lua_gettop(L);
52 if (top < 2) luaL_error(L, "No function provided");
54 lua_pushcfunction(L, Lbtui_disable);
59 lua_call(L, 0, LUA_MULTRET);
60 int top2 = lua_gettop(L);
62 lua_pushcfunction(L, Lbtui_enable);
69 static int Lbtui_getkey(lua_State *L)
71 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
72 if (bt == NULL) luaL_error(L, "Not a BTUI object");
73 if (*bt == NULL) luaL_error(L, "BTUI object not initialized");
74 int mouse_x = -1, mouse_y = -1;
75 int timeout = lua_gettop(L) <= 1 ? -1 : (int)luaL_checkinteger(L, 2);
76 int key = btui_getkey(*bt, timeout, &mouse_x, &mouse_y);
77 if (key == -1) return 0;
79 btui_keyname(key, buf);
80 lua_pushstring(L, buf);
81 if (mouse_x != -1 || mouse_y != -1) {
82 lua_pushinteger(L, mouse_x);
83 lua_pushinteger(L, mouse_y);
89 static int Lbtui_write(lua_State *L)
91 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
92 if (bt == NULL) luaL_error(L, "Not a BTUI object");
93 int n = lua_gettop(L);
94 for (int i = 2; i <= n; i++) {
95 btui_puts(*bt, luaL_tolstring(L, i, NULL));
102 static int Lbtui_clear(lua_State *L)
104 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
105 if (bt == NULL) luaL_error(L, "Not a BTUI object");
106 const char *cleartype = luaL_optlstring(L, 2, "screen", NULL);
107 if (strcmp(cleartype, "screen") == 0) {
108 btui_clear(*bt, BTUI_CLEAR_SCREEN);
109 } else if (strcmp(cleartype, "below") == 0) {
110 btui_clear(*bt, BTUI_CLEAR_BELOW);
111 } else if (strcmp(cleartype, "above") == 0) {
112 btui_clear(*bt, BTUI_CLEAR_ABOVE);
113 } else if (strcmp(cleartype, "right") == 0) {
114 btui_clear(*bt, BTUI_CLEAR_RIGHT);
115 } else if (strcmp(cleartype, "left") == 0) {
116 btui_clear(*bt, BTUI_CLEAR_LEFT);
117 } else if (strcmp(cleartype, "line") == 0) {
118 btui_clear(*bt, BTUI_CLEAR_LINE);
120 lua_pushliteral(L, "unknown clear type");
127 static int Lbtui_flush(lua_State *L)
129 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
130 if (bt == NULL) luaL_error(L, "Not a BTUI object");
135 static int Lbtui_hidecursor(lua_State *L)
137 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
138 if (bt == NULL) luaL_error(L, "Not a BTUI object");
139 btui_hide_cursor(*bt);
144 static int Lbtui_showcursor(lua_State *L)
146 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
147 if (bt == NULL) luaL_error(L, "Not a BTUI object");
148 btui_show_cursor(*bt);
153 static int Lbtui_move(lua_State *L)
155 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
156 if (bt == NULL) luaL_error(L, "Not a BTUI object");
157 if (lua_gettop(L) < 3) luaL_error(L, "Expected x and y values");
158 int x = (int)luaL_checkinteger(L, 2);
159 int y = (int)luaL_checkinteger(L, 3);
160 btui_move_cursor(*bt, x, y);
165 static int Lbtui_setcursor(lua_State *L)
167 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
168 if (bt == NULL) luaL_error(L, "Not a BTUI object");
169 const char *cursortype = luaL_optlstring(L, 2, "default", NULL);
170 if (strcmp(cursortype, "default") == 0) {
171 btui_set_cursor(*bt, CURSOR_DEFAULT);
172 } else if (strcmp(cursortype, "blinking block") == 0) {
173 btui_set_cursor(*bt, CURSOR_BLINKING_BLOCK);
174 } else if (strcmp(cursortype, "block") == 0) {
175 btui_set_cursor(*bt, CURSOR_STEADY_BLOCK);
176 } else if (strcmp(cursortype, "blinking underline") == 0) {
177 btui_set_cursor(*bt, CURSOR_BLINKING_UNDERLINE);
178 } else if (strcmp(cursortype, "underline") == 0) {
179 btui_set_cursor(*bt, CURSOR_STEADY_UNDERLINE);
180 } else if (strcmp(cursortype, "blinking bar") == 0) {
181 btui_set_cursor(*bt, CURSOR_BLINKING_BAR);
182 } else if (strcmp(cursortype, "bar") == 0) {
183 btui_set_cursor(*bt, CURSOR_STEADY_BAR);
185 lua_pushliteral(L, "unknown cursor type");
192 static int Lbtui_setmode(lua_State *L)
194 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
195 if (bt == NULL) luaL_error(L, "Not a BTUI object");
196 const char *modestring = luaL_checkstring(L, 2);
197 btui_mode_t mode = BTUI_MODE_TUI;
198 if (strcmp(modestring, "normal") == 0)
199 mode = BTUI_MODE_NORMAL;
200 else if (strcmp(modestring, "TUI") != 0)
201 luaL_error(L, "Invalid BTUI mode");
202 btui_set_mode(*bt, mode);
206 static int Lbtui_withfg(lua_State *L)
208 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
209 if (bt == NULL) luaL_error(L, "Not a BTUI object");
210 if (lua_gettop(L) < 5) luaL_error(L, "Expected r,g,b values and a function");
211 lua_Number r = luaL_checknumber(L, 2);
212 lua_Number g = luaL_checknumber(L, 3);
213 lua_Number b = luaL_checknumber(L, 4);
215 r < 0.0 ? 0 : (r > 1.0 ? 255 : (int)(255.0 * r)),
216 g < 0.0 ? 0 : (g > 1.0 ? 255 : (int)(255.0 * g)),
217 b < 0.0 ? 0 : (b > 1.0 ? 255 : (int)(255.0 * b)));
218 int top = lua_gettop(L);
219 int status = lua_pcall(L, 0, LUA_MULTRET, 0);
220 btui_set_attributes(*bt, BTUI_FG_NORMAL);
221 if (status != LUA_OK)
223 return lua_gettop(L) - top;
226 static int Lbtui_withbg(lua_State *L)
228 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
229 if (bt == NULL) luaL_error(L, "Not a BTUI object");
230 if (lua_gettop(L) < 5) luaL_error(L, "Expected r,g,b values and a function");
231 lua_Number r = luaL_checknumber(L, 2);
232 lua_Number g = luaL_checknumber(L, 3);
233 lua_Number b = luaL_checknumber(L, 4);
235 r < 0.0 ? 0 : (r > 1.0 ? 255 : (int)(255.0 * r)),
236 g < 0.0 ? 0 : (g > 1.0 ? 255 : (int)(255.0 * g)),
237 b < 0.0 ? 0 : (b > 1.0 ? 255 : (int)(255.0 * b)));
238 int top = lua_gettop(L);
239 int status = lua_pcall(L, 0, LUA_MULTRET, 0);
240 btui_set_attributes(*bt, BTUI_BG_NORMAL);
241 if (status != LUA_OK)
243 return lua_gettop(L) - top;
246 static int Lbtui_linebox(lua_State *L)
248 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
249 if (bt == NULL) luaL_error(L, "Not a BTUI object");
250 int x = (int)luaL_checkinteger(L, 2);
251 int y = (int)luaL_checkinteger(L, 3);
252 int w = (int)luaL_checkinteger(L, 4);
253 int h = (int)luaL_checkinteger(L, 5);
254 btui_draw_linebox(*bt, x, y, w, h);
259 static int Lbtui_fillbox(lua_State *L)
261 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
262 if (bt == NULL) luaL_error(L, "Not a BTUI object");
263 int x = (int)luaL_checkinteger(L, 2);
264 int y = (int)luaL_checkinteger(L, 3);
265 int w = (int)luaL_checkinteger(L, 4);
266 int h = (int)luaL_checkinteger(L, 5);
267 btui_fill_box(*bt, x, y, w, h);
272 static int Lbtui_shadow(lua_State *L)
274 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
275 if (bt == NULL) luaL_error(L, "Not a BTUI object");
276 int x = (int)luaL_checkinteger(L, 2);
277 int y = (int)luaL_checkinteger(L, 3);
278 int w = (int)luaL_checkinteger(L, 4);
279 int h = (int)luaL_checkinteger(L, 5);
280 btui_draw_shadow(*bt, x, y, w, h);
285 static int Lbtui_scroll(lua_State *L)
287 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
288 if (bt == NULL) luaL_error(L, "Not a BTUI object");
289 int firstline = (int)luaL_checkinteger(L, 2);
290 int lastline = (int)luaL_checkinteger(L, 3);
291 int scroll = (int)luaL_checkinteger(L, 4);
292 btui_scroll(*bt, firstline, lastline, scroll);
297 static int Lbtui_setattributes(lua_State *L)
299 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
300 if (bt == NULL) luaL_error(L, "Not a BTUI object");
301 int top = lua_gettop(L);
302 lua_pushlightuserdata(L, (void*)&BTUI_ATTRIBUTES);
303 int attr_table = lua_gettop(L);
304 lua_gettable(L, LUA_REGISTRYINDEX);
305 lua_Unsigned attrs = 0;
306 for (int i = 2; i <= top; i++) {
308 lua_gettable(L, attr_table);
309 if (lua_isnil(L, -1)) {
310 const char *a = lua_tostring(L, i);
311 luaL_error(L, "invalid attribute: %s", a);
313 attrs |= (lua_Unsigned)lua_tointeger(L, -1);
315 btui_set_attributes(*bt, attrs);
319 static int Lbtui_unsetattributes(lua_State *L)
321 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
322 if (bt == NULL) luaL_error(L, "Not a BTUI object");
323 int top = lua_gettop(L);
324 lua_pushlightuserdata(L, (void*)&BTUI_INVERSE_ATTRIBUTES);
325 int attr_table = lua_gettop(L);
326 lua_gettable(L, LUA_REGISTRYINDEX);
327 lua_Unsigned attrs = 0;
328 for (int i = 2; i <= top; i++) {
330 lua_gettable(L, attr_table);
331 if (lua_isnil(L, -1)) {
332 const char *a = lua_tostring(L, i);
333 luaL_error(L, "invalid attribute: %s", a);
335 attrs |= (lua_Unsigned)lua_tointeger(L, -1);
337 btui_set_attributes(*bt, attrs);
341 static int Lbtui_suspend(lua_State *L)
343 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
344 if (bt == NULL) luaL_error(L, "Not a BTUI object");
349 static int Lbtui_withattributes(lua_State *L)
351 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
352 if (bt == NULL) luaL_error(L, "Not a BTUI object");
353 int top = lua_gettop(L);
354 lua_pushcfunction(L, Lbtui_setattributes);
355 for (int i = 1; i < top; i++) {
358 lua_call(L, top - 1, 0);
359 lua_pushvalue(L, top);
360 int status = lua_pcall(L, 0, LUA_MULTRET, 0);
361 int top2 = lua_gettop(L);
362 lua_pushcfunction(L, Lbtui_unsetattributes);
363 for (int i = 1; i < top; i++) {
366 lua_call(L, top - 1, 0);
367 if (status != LUA_OK)
372 static int Lbtui_width(lua_State *L)
374 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
375 if (bt == NULL) luaL_error(L, "Not a BTUI object");
376 lua_pushinteger(L, (*bt)->width);
380 static int Lbtui_height(lua_State *L)
382 btui_t **bt = (btui_t**)lua_touserdata(L, 1);
383 if (bt == NULL) luaL_error(L, "Not a BTUI object");
384 lua_pushinteger(L, (*bt)->height);
388 static int Lbtui_wrap(lua_State *L)
390 if (lua_gettop(L) < 1) luaL_error(L, "expected a callable object");
391 btui_t **bt = (btui_t**)lua_newuserdata(L, sizeof(btui_t*));
392 lua_pushlightuserdata(L, (void*)&BTUI_METATABLE);
393 lua_gettable(L, LUA_REGISTRYINDEX);
394 lua_setmetatable(L, -2);
395 *bt = btui_create(BTUI_MODE_TUI);
396 btui_move_cursor(*bt, 0, 0);
398 int status = lua_pcall(L, 1, 0, 0);
400 if (status != LUA_OK)
405 static int Lbtui_tostring(lua_State *L)
407 lua_pushliteral(L, "<BTUI>");
414 } btui_attributes[] =
416 {"normal", BTUI_NORMAL},
418 {"faint", BTUI_FAINT},
420 {"italic", BTUI_ITALIC},
421 {"underline", BTUI_UNDERLINE},
422 {"blink_slow", BTUI_BLINK_SLOW},
423 {"blink_fast", BTUI_BLINK_FAST},
424 {"reverse", BTUI_REVERSE},
425 {"conceal", BTUI_CONCEAL},
426 {"strikethrough", BTUI_STRIKETHROUGH},
427 {"fraktur", BTUI_FRAKTUR},
428 {"double_underline", BTUI_DOUBLE_UNDERLINE},
429 {"no_bold_or_faint", BTUI_NO_BOLD_OR_FAINT},
430 {"no_italic_or_fraktur", BTUI_NO_ITALIC_OR_FRAKTUR},
431 {"no_underline", BTUI_NO_UNDERLINE},
432 {"no_blink", BTUI_NO_BLINK},
433 {"no_reverse", BTUI_NO_REVERSE},
434 {"no_conceal", BTUI_NO_CONCEAL},
435 {"no_strikethrough", BTUI_NO_STRIKETHROUGH},
436 {"fg_black", BTUI_FG_BLACK},
437 {"fg_red", BTUI_FG_RED},
438 {"fg_green", BTUI_FG_GREEN},
439 {"fg_yellow", BTUI_FG_YELLOW},
440 {"fg_blue", BTUI_FG_BLUE},
441 {"fg_magenta", BTUI_FG_MAGENTA},
442 {"fg_cyan", BTUI_FG_CYAN},
443 {"fg_white", BTUI_FG_WHITE},
444 {"fg_normal", BTUI_FG_NORMAL},
445 {"bg_black", BTUI_BG_BLACK},
446 {"bg_red", BTUI_BG_RED},
447 {"bg_green", BTUI_BG_GREEN},
448 {"bg_yellow", BTUI_BG_YELLOW},
449 {"bg_blue", BTUI_BG_BLUE},
450 {"bg_magenta", BTUI_BG_MAGENTA},
451 {"bg_cyan", BTUI_BG_CYAN},
452 {"bg_white", BTUI_BG_WHITE},
453 {"bg_normal", BTUI_BG_NORMAL},
454 {"framed", BTUI_FRAMED},
455 {"encircled", BTUI_ENCIRCLED},
456 {"overlined", BTUI_OVERLINED},
457 {"no_framed_or_encircled", BTUI_NO_FRAMED_OR_ENCIRCLED},
458 {"no_overlined", BTUI_NO_OVERLINED},
465 } btui_inverse_attributes[] =
467 {"normal", BTUI_NORMAL},
468 {"bold", BTUI_NO_BOLD_OR_FAINT},
469 {"faint", BTUI_NO_BOLD_OR_FAINT},
470 {"dim", BTUI_NO_BOLD_OR_FAINT},
471 {"italic", BTUI_NO_ITALIC_OR_FRAKTUR},
472 {"underline", BTUI_NO_UNDERLINE},
473 {"blink_slow", BTUI_NO_BLINK},
474 {"blink_fast", BTUI_NO_BLINK},
475 {"reverse", BTUI_NO_REVERSE},
476 {"conceal", BTUI_NO_CONCEAL},
477 {"strikethrough", BTUI_NO_STRIKETHROUGH},
478 {"fraktur", BTUI_NO_ITALIC_OR_FRAKTUR},
479 {"double_underline", BTUI_NO_UNDERLINE},
480 {"fg_black", BTUI_FG_NORMAL},
481 {"fg_red", BTUI_FG_NORMAL},
482 {"fg_green", BTUI_FG_NORMAL},
483 {"fg_yellow", BTUI_FG_NORMAL},
484 {"fg_blue", BTUI_FG_NORMAL},
485 {"fg_magenta", BTUI_FG_NORMAL},
486 {"fg_cyan", BTUI_FG_NORMAL},
487 {"fg_white", BTUI_FG_NORMAL},
488 {"fg_normal", BTUI_FG_NORMAL},
489 {"bg_black", BTUI_BG_NORMAL},
490 {"bg_red", BTUI_BG_NORMAL},
491 {"bg_green", BTUI_BG_NORMAL},
492 {"bg_yellow", BTUI_BG_NORMAL},
493 {"bg_blue", BTUI_BG_NORMAL},
494 {"bg_magenta", BTUI_BG_NORMAL},
495 {"bg_cyan", BTUI_BG_NORMAL},
496 {"bg_white", BTUI_BG_NORMAL},
497 {"bg_normal", BTUI_BG_NORMAL},
498 {"framed", BTUI_NO_FRAMED_OR_ENCIRCLED},
499 {"encircled", BTUI_NO_FRAMED_OR_ENCIRCLED},
500 {"overlined", BTUI_NO_OVERLINED},
504 static const luaL_Reg Rclass_metamethods[] =
506 {"__tostring", Lbtui_tostring},
507 {"clear", Lbtui_clear},
508 {"disable", Lbtui_disable},
509 {"enable", Lbtui_enable},
510 {"fillbox", Lbtui_fillbox},
511 {"flush", Lbtui_flush},
512 {"getkey", Lbtui_getkey},
513 {"height", Lbtui_height},
514 {"hidecursor", Lbtui_hidecursor},
515 {"linebox", Lbtui_linebox},
516 {"move", Lbtui_move},
517 {"scroll", Lbtui_scroll},
518 {"setattributes", Lbtui_setattributes},
519 {"setcursor", Lbtui_setcursor},
520 {"setmode", Lbtui_setmode},
521 {"shadow", Lbtui_shadow},
522 {"showcursor", Lbtui_showcursor},
523 {"suspend", Lbtui_suspend},
524 {"unsetattributes", Lbtui_unsetattributes},
525 {"width", Lbtui_width},
526 {"withattributes", Lbtui_withattributes},
527 {"withbg", Lbtui_withbg},
528 {"withdisabled", Lbtui_withdisabled},
529 {"withfg", Lbtui_withfg},
530 {"write", Lbtui_write},
534 LUALIB_API int luaopen_btui(lua_State *L)
537 lua_pushlightuserdata(L, (void*)&BTUI_ATTRIBUTES);
538 lua_createtable(L, 0, 50);
539 for (int i = 0; btui_attributes[i].name; i++) {
540 lua_pushinteger(L, (lua_Integer)btui_attributes[i].code);
541 lua_setfield(L, -2, btui_attributes[i].name);
543 lua_settable(L, LUA_REGISTRYINDEX);
545 // Inverse attributes
546 lua_pushlightuserdata(L, (void*)&BTUI_INVERSE_ATTRIBUTES);
547 lua_createtable(L, 0, 50);
548 for (int i = 0; btui_inverse_attributes[i].name; i++) {
549 lua_pushinteger(L, (lua_Integer)btui_inverse_attributes[i].code);
550 lua_setfield(L, -2, btui_inverse_attributes[i].name);
552 lua_settable(L, LUA_REGISTRYINDEX);
554 // Set up BTUI metatable
555 lua_pushlightuserdata(L, (void*)&BTUI_METATABLE);
556 lua_createtable(L, 0, 16);
557 luaL_register(L, NULL, Rclass_metamethods);
558 lua_pushvalue(L, -1);
559 lua_setfield(L, -2, "__index");
560 lua_settable(L, LUA_REGISTRYINDEX);
562 lua_pushcfunction(L, Lbtui_wrap);