use fmt; use tb = termbox2; def bg = tb::COLOR.DEFAULT; def fg = tb::COLOR.DEFAULT | tb::ATTRIBUTE.BOLD; def exit_help: str = "Use :q to exit (colon, q, enter). Sad to see you go :("; let wpos: (int, int) = (-1, -1); let msg: str = "Use HJKL to move the widget around"; let colon_mode: bool = false; let exit_attempts: size = 0; type cmd = enum { UP, DOWN, LEFT, RIGHT, COLON, ARROW, Q, ENTER, EXIT_ATTEMPT, NONE, }; fn draw_small_widget_thing(w: size, h: size) void = { if (wpos.0 < 4) wpos.0 += 1 else if (wpos.0 > w: int - 5) wpos.0 -= 1 else if (wpos.1 < 2) wpos.1 += 1 else if (wpos.1 > h: int - 3) wpos.1 -= 1; tb::print(wpos.0, wpos.1 - 1, fg, bg, "K")!; tb::print(wpos.0, wpos.1 + 1, fg, bg, "J")!; tb::print(wpos.0 - 2, wpos.1, fg, bg, "H + L")!; }; fn paint(x: int, y: int, c: tb::uattr) void = tb::set_cell(x, y, ' ', tb::COLOR.DEFAULT, c)!; fn paint_col3(x: int, y: int, c: tb::uattr) void = { paint(x, y-1, c); paint(x, y, c); paint(x, y+1, c); }; fn paint_row5(x: int, y: int, c: tb::uattr) void = { paint(x-2, y, c); paint(x-1, y, c); paint(x, y, c); paint(x+1, y, c); paint(x+2, y, c); }; fn paint_cell(x: int, y: int, c: tb::uattr, s: str) void = { paint_col3(x-2, y, c); paint_row5(x, y-1, c); tb::print(x, y, fg, bg, s)!; paint_row5(x, y+1, c); paint_col3(x+2, y, c); }; fn draw_large_widget_thing(w: size, h: size) void = { if (wpos.0 < 10) wpos.0 += 1 else if (wpos.0 > w: int - 11) wpos.0 -= 1 else if (wpos.1 < 7) wpos.1 += 1 else if (wpos.1 > h: int - 8) wpos.1 -= 1; let c = tb::COLOR.BLUE; paint_cell(wpos.0-6, wpos.1, c, "H"); paint_cell(wpos.0+6, wpos.1, c, "L"); paint_cell(wpos.0, wpos.1-4, c, "K"); paint_cell(wpos.0, wpos.1+4, c, "J"); paint_col3(wpos.0, wpos.1, c); paint_row5(wpos.0, wpos.1, c); }; fn draw_widget_thing(w: size, h: size) void = { if (w < 30 || h < 15) draw_small_widget_thing(w, h) else draw_large_widget_thing(w, h); }; fn draw_bottom_bar(w: size, h: size) void = { tb::print(0, h: int - 1, fg, bg, msg)!; }; fn handle_events_and_exit() bool = { let e = tb::poll_event(); if (e is tb::err) return false; return match (e as tb::event) { case tb::resize_event => yield false; case tb::mouse_event => yield { msg = "A mouse? Are you serious?"; yield false; }; case let ke: tb::key_event => yield handle_key_event_and_exit(ke); }; }; fn is_arrow(k: tb::key) bool = k == tb::KEY.ARROW_UP || k == tb::KEY.ARROW_DOWN || k == tb::KEY.ARROW_LEFT || k == tb::KEY.ARROW_RIGHT; fn is_exit_attempt(k: tb::key) bool = k == tb::KEY.CTRL_Q || k == tb::KEY.CTRL_C || k == tb::KEY.CTRL_X || k == tb::KEY.CTRL_Z || k == tb::KEY.CTRL_D || k == tb::KEY.DELETE || k == tb::KEY.BACKSPACE || k == tb::KEY.ESC; fn get_cmd(ke: tb::key_event) cmd = match (ke.key_or_ch) { case let r: rune => yield if (r == 'h') cmd::LEFT else if (r == 'j') cmd::DOWN else if (r == 'k') cmd::UP else if (r == 'l') cmd::RIGHT else if (r == ':') cmd::COLON else if (r == 'q') cmd::Q else if (r == 'Q') cmd::EXIT_ATTEMPT else cmd::NONE; case let k: tb::key => yield if (k == tb::KEY.ENTER) cmd::ENTER else if (is_arrow(k)) cmd::ARROW else if (is_exit_attempt(k)) cmd::EXIT_ATTEMPT else cmd::NONE; }; fn exit_attempt() void = { exit_attempts += 1; msg = if (exit_attempts < 3) "Trying to get out? :3" else if (exit_attempts < 6) "It's just like Vim!" else if (exit_attempts < 8) "Uhm no, that won't work" else if (exit_attempts < 10) "Ok, I'll tell you..." else exit_help; }; fn handle_key_event_and_exit(ke: tb::key_event) bool = { let c = get_cmd(ke); if (c == cmd::COLON) colon_mode = true; if (colon_mode) { if (msg == ":q" && c == cmd::ENTER) return true; msg = if (msg == ":") { yield if (c == cmd::Q) ":q" else exit_help; } else if (c == cmd::COLON) ":" else exit_help; return false; }; switch (c) { case cmd::UP => wpos.1 -= 1; case cmd::DOWN => wpos.1 += 1; case cmd::LEFT => wpos.0 -= 1; case cmd::RIGHT => wpos.0 += 1; case cmd::ARROW => msg = "No arrows allowed!"; case cmd::COLON => colon_mode = true; case cmd::EXIT_ATTEMPT => exit_attempt(); case cmd::Q => exit_attempt(); case => yield; }; return false; }; export fn main() void = { let e = tb::init(); if (e is tb::err) fmt::fatalf(tb::strerror(e as tb::err)); defer tb::shutdown()!; wpos = (tb::width()!: int / 2, tb::height()!: int / 2); for (true) { tb::clear()!; let w = tb::width()!; let h = tb::height()!; draw_widget_thing(w, h); draw_bottom_bar(w, h); tb::present()!; if (handle_events_and_exit()) break; }; return; };