nuclear@37: #include nuclear@37: #include nuclear@37: #include nuclear@37: #include nuclear@37: #include nuclear@37: #include nuclear@37: #include "opengl.h" nuclear@37: #include "console.h" nuclear@37: nuclear@37: Console::Console() nuclear@41: : prompt("> ") nuclear@37: { nuclear@37: visible = false; nuclear@37: nlines = 16; nuclear@37: ncolumns = 80; nuclear@37: nuclear@37: inpq_front = inpq_back = 0; nuclear@37: nuclear@37: max_hist_lines = 64; nuclear@37: max_output_lines = 128; nuclear@37: nuclear@42: hist_iter_valid = false; nuclear@42: nuclear@37: echo = true; nuclear@37: font = 0; nuclear@37: font_size = 0; nuclear@37: nuclear@37: cursor = 0; nuclear@41: input_win = 0; nuclear@41: } nuclear@41: nuclear@41: void Console::set_cursor(int x) nuclear@41: { nuclear@41: if(x < 0 || x > (int)input.size()) { nuclear@41: return; nuclear@41: } nuclear@41: cursor = x; nuclear@41: nuclear@41: int max_chars = ncolumns - (int)prompt.size() - 1; nuclear@41: nuclear@41: if(cursor < input_win) { nuclear@41: input_win = cursor; nuclear@41: } else if(cursor > input_win + max_chars) { nuclear@41: input_win = std::max(cursor - max_chars, 0); nuclear@41: } nuclear@41: } nuclear@41: nuclear@41: int Console::get_cursor() const nuclear@41: { nuclear@41: return cursor; nuclear@37: } nuclear@37: nuclear@37: void Console::set_font(dtx_font *font, int sz) nuclear@37: { nuclear@37: this->font = font; nuclear@37: font_size = sz; nuclear@37: } nuclear@37: nuclear@37: void Console::set_history_size(int hsz) nuclear@37: { nuclear@37: max_hist_lines = hsz; nuclear@37: nuclear@37: int drop = hist.size() - hsz; nuclear@37: if(drop > 0) { nuclear@37: auto it = hist.begin(); nuclear@37: for(int i=0; i 0) { nuclear@37: output.erase(output.begin(), output.begin() + drop); nuclear@37: } nuclear@37: } nuclear@37: nuclear@37: void Console::set_command_func(std::function func) nuclear@37: { nuclear@37: cmd_handler = func; nuclear@37: } nuclear@37: nuclear@37: void Console::set_echo(bool echo) nuclear@37: { nuclear@37: this->echo = echo; nuclear@37: } nuclear@37: nuclear@37: bool Console::get_echo() const nuclear@37: { nuclear@37: return echo; nuclear@37: } nuclear@37: nuclear@37: void Console::set_visible(bool v) nuclear@37: { nuclear@37: visible = v; nuclear@37: } nuclear@37: nuclear@37: bool Console::is_visible() const nuclear@37: { nuclear@37: return visible; nuclear@37: } nuclear@37: nuclear@37: void Console::set_size(int lines, int columns) nuclear@37: { nuclear@37: nlines = lines; nuclear@37: ncolumns = columns; nuclear@37: } nuclear@37: nuclear@37: int Console::get_size_lines() const nuclear@37: { nuclear@37: return nlines; nuclear@37: } nuclear@37: nuclear@37: int Console::get_size_columns() const nuclear@37: { nuclear@37: return ncolumns; nuclear@37: } nuclear@37: nuclear@37: bool Console::update() nuclear@37: { nuclear@37: bool must_redraw = false; nuclear@37: nuclear@37: while(inpq_front != inpq_back) { nuclear@37: int c = keybuf[inpq_front]; nuclear@37: inpq_front = (inpq_front + 1) % INPUTQ_SIZE; nuclear@37: nuclear@37: switch(c) { nuclear@37: case '\n': nuclear@37: case '\r': nuclear@37: if(echo) { nuclear@37: puts(input.c_str()); nuclear@37: putchar('\n'); nuclear@37: } nuclear@41: if(!input.empty() && cmd_handler) { nuclear@41: cmd_handler(input.c_str()); nuclear@41: } nuclear@37: nuclear@37: hist.push_back(std::move(input)); // move the input string into the history buffer nuclear@37: if((int)hist.size() > max_hist_lines) { nuclear@37: hist.pop_front(); nuclear@37: } nuclear@42: hist_iter_valid = false; nuclear@42: nuclear@41: set_cursor(0); nuclear@37: must_redraw = true; nuclear@37: break; nuclear@37: nuclear@37: case '\t': nuclear@37: // TODO: completion nuclear@37: break; nuclear@37: nuclear@37: case '\b': nuclear@37: if(!input.empty()) { nuclear@37: if(cursor == (int)input.size()) { nuclear@37: input.pop_back(); nuclear@41: set_cursor(get_cursor() - 1); nuclear@37: must_redraw = true; nuclear@37: } else if(cursor > 0) { nuclear@38: input.erase(cursor - 1, 1); nuclear@41: set_cursor(get_cursor() - 1); nuclear@37: must_redraw = true; nuclear@37: } nuclear@37: } nuclear@37: break; nuclear@37: nuclear@42: case KEY_UP: nuclear@42: if(!hist.empty()) { nuclear@42: if(!hist_iter_valid) { nuclear@42: hist_iter = hist.rbegin(); nuclear@42: hist_iter_valid = true; nuclear@42: input = *hist_iter; nuclear@42: } else { nuclear@42: if(++hist_iter == hist.rend()) { nuclear@42: --hist_iter; nuclear@42: break; nuclear@42: } nuclear@42: input = *hist_iter; nuclear@42: } nuclear@42: set_cursor(input.size()); nuclear@42: } nuclear@42: break; nuclear@42: nuclear@42: case KEY_DOWN: nuclear@42: if(!hist.empty()) { nuclear@42: if(!hist_iter_valid) { nuclear@42: hist_iter = hist.rbegin(); nuclear@42: hist_iter_valid = true; nuclear@42: } nuclear@42: if(hist_iter != hist.rbegin()) { nuclear@42: input = *--hist_iter; nuclear@42: set_cursor(input.size()); nuclear@42: } nuclear@42: } nuclear@42: break; nuclear@42: nuclear@37: case KEY_LEFT: nuclear@37: if(cursor > 0) { nuclear@41: set_cursor(get_cursor() - 1); nuclear@37: must_redraw = true; nuclear@37: } nuclear@37: break; nuclear@37: nuclear@37: case KEY_RIGHT: nuclear@37: if(cursor < (int)input.size()) { nuclear@41: set_cursor(get_cursor() + 1); nuclear@37: must_redraw = true; nuclear@37: } nuclear@37: break; nuclear@37: nuclear@37: case KEY_HOME: nuclear@41: set_cursor(0); nuclear@37: must_redraw = true; nuclear@37: break; nuclear@37: nuclear@37: case KEY_END: nuclear@41: set_cursor(input.size()); nuclear@37: must_redraw = true; nuclear@37: break; nuclear@37: nuclear@37: case KEY_PGUP: nuclear@37: case KEY_PGDOWN: nuclear@42: // TODO scroll output buffer nuclear@37: break; nuclear@37: nuclear@37: default: nuclear@37: if(c < 256 && isprint(c)) { nuclear@38: if(cursor == (int)input.size()) { nuclear@38: input.push_back(c); nuclear@38: } else { nuclear@38: input.insert(cursor, 1, c); nuclear@38: } nuclear@41: set_cursor(get_cursor() + 1); nuclear@37: must_redraw = true; nuclear@37: } nuclear@37: } nuclear@37: } nuclear@37: return must_redraw; nuclear@37: } nuclear@37: nuclear@37: void Console::draw() const nuclear@37: { nuclear@37: if(!font || !visible) return; nuclear@37: dtx_use_font(font, font_size); nuclear@37: nuclear@37: int vp[4]; nuclear@37: glGetIntegerv(GL_VIEWPORT, vp); nuclear@37: nuclear@37: glMatrixMode(GL_PROJECTION); nuclear@37: glPushMatrix(); nuclear@37: glLoadIdentity(); nuclear@37: glOrtho(vp[0], vp[0] + vp[2], vp[1], vp[1] + vp[3], -1, 1); nuclear@37: nuclear@37: glMatrixMode(GL_MODELVIEW); nuclear@37: glPushMatrix(); nuclear@37: glLoadIdentity(); nuclear@41: glTranslatef(0, vp[3], 0); nuclear@37: nuclear@37: std::string buflines; nuclear@37: nuclear@37: int num_lines = std::min(nlines, (int)output.size()); nuclear@37: auto it = output.cbegin() + (output.size() - num_lines); nuclear@37: nuclear@37: float max_width = dtx_glyph_width('Q') * ncolumns; nuclear@37: nuclear@37: while(it != output.cend()) { nuclear@37: const std::string &line = *it++; nuclear@37: buflines += line + std::string("\n"); nuclear@37: nuclear@41: //max_width = std::max(max_width, dtx_string_width(line.c_str())); nuclear@37: } nuclear@37: nuclear@37: // draw the output box nuclear@38: float line_height = dtx_line_height(); nuclear@38: float outbox_height = nlines * line_height; nuclear@41: float console_height = outbox_height + line_height * 1.5; nuclear@37: nuclear@37: glPushAttrib(GL_ENABLE_BIT); nuclear@37: glEnable(GL_BLEND); nuclear@40: glDisable(GL_TEXTURE_2D); nuclear@37: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); nuclear@37: nuclear@41: glEnable(GL_SCISSOR_TEST); nuclear@41: glScissor(0, vp[3] - console_height, max_width, console_height); nuclear@41: nuclear@37: glBegin(GL_QUADS); nuclear@37: glColor4f(0.1, 0.2, 0.5, 0.7); nuclear@37: glVertex2f(0, -outbox_height); nuclear@37: glVertex2f(max_width, -outbox_height); nuclear@37: glVertex2f(max_width, 0); nuclear@37: glVertex2f(0, 0); nuclear@37: nuclear@37: glColor4f(0.5, 0.2, 0.1, 0.7); nuclear@41: glVertex2f(0, -console_height); nuclear@41: glVertex2f(max_width, -console_height); nuclear@37: glVertex2f(max_width, -outbox_height); nuclear@37: glVertex2f(0, -outbox_height); nuclear@37: glEnd(); nuclear@37: nuclear@37: // draw the output text nuclear@37: glPushMatrix(); nuclear@38: glTranslatef(0, -line_height, 0); nuclear@37: glColor4f(1, 1, 1, 0.7); nuclear@37: dtx_string(buflines.c_str()); nuclear@37: glPopMatrix(); nuclear@37: nuclear@37: // draw the input line nuclear@38: glTranslatef(0, -outbox_height - line_height, 0); nuclear@38: nuclear@41: std::string inpline = prompt + input.substr(input_win); nuclear@38: dtx_string(inpline.c_str()); nuclear@38: glDisable(GL_TEXTURE_2D); nuclear@38: nuclear@41: float cursor_x = dtx_char_pos(inpline.c_str(), cursor - input_win + prompt.size()); nuclear@38: glBegin(GL_QUADS); nuclear@38: glColor4f(1, 1, 1, 0.7); nuclear@42: glVertex2f(cursor_x, -2); nuclear@42: glVertex2f(cursor_x + 2, -2); nuclear@38: glVertex2f(cursor_x + 2, line_height - 2); nuclear@38: glVertex2f(cursor_x, line_height - 2); nuclear@38: glEnd(); nuclear@37: nuclear@37: glPopAttrib(); nuclear@37: nuclear@37: glMatrixMode(GL_PROJECTION); nuclear@37: glPopMatrix(); nuclear@37: glMatrixMode(GL_MODELVIEW); nuclear@37: glPopMatrix(); nuclear@37: } nuclear@37: nuclear@37: void Console::input_key(int key) nuclear@37: { nuclear@37: keybuf[inpq_back] = key; nuclear@37: inpq_back = (inpq_back + 1) % INPUTQ_SIZE; nuclear@37: } nuclear@37: nuclear@37: void Console::putchar(char c) nuclear@37: { nuclear@37: if(output.empty()) { nuclear@37: output.push_back(std::string("")); nuclear@37: } nuclear@37: nuclear@37: if(c == '\n' || c == '\r') { nuclear@37: output.push_back(std::string("")); nuclear@37: if((int)output.size() > max_output_lines) { nuclear@37: output.erase(output.begin()); nuclear@37: } nuclear@37: } else { nuclear@37: output.back().push_back(c); nuclear@37: } nuclear@37: } nuclear@37: nuclear@37: void Console::puts(const char *str) nuclear@37: { nuclear@37: while(*str) { nuclear@37: putchar(*str++); nuclear@37: } nuclear@37: } nuclear@37: nuclear@37: void Console::printf(const char *fmt, ...) nuclear@37: { nuclear@37: static char buf[1024]; nuclear@37: va_list ap; nuclear@37: nuclear@37: va_start(ap, fmt); nuclear@37: vsnprintf(buf, sizeof buf, fmt, ap); nuclear@37: va_end(ap); nuclear@37: nuclear@37: puts(buf); nuclear@37: } nuclear@37: nuclear@37: void Console::debug() nuclear@37: { nuclear@37: fprintf(stderr, "visible: %s\n", visible ? "true" : "false"); nuclear@37: fprintf(stderr, "size: %d lines x %d columns\n", nlines, ncolumns); nuclear@37: fprintf(stderr, "cursor: %d\n", cursor); nuclear@37: nuclear@37: int qsize = 0; nuclear@37: int qidx = inpq_front; nuclear@37: while(qidx != inpq_back) { nuclear@37: qsize++; nuclear@37: qidx = (qidx + 1) % INPUTQ_SIZE; nuclear@37: } nuclear@37: nuclear@37: fprintf(stderr, "input queue size: %d\n", qsize); nuclear@37: fprintf(stderr, "current input line: \"%s\"\n", input.c_str()); nuclear@37: fprintf(stderr, "%d saved inputs in the history (max %d)\n", (int)hist.size(), max_hist_lines); nuclear@37: auto it = hist.begin(); nuclear@37: int idx = 0; nuclear@37: while(it != hist.end()) { nuclear@37: fprintf(stderr, " h(%d): \"%s\"\n", idx++, it++->c_str()); nuclear@37: } nuclear@37: nuclear@37: fprintf(stderr, "output buffer (lines: %d/%d):\n", (int)output.size(), max_output_lines); nuclear@37: for(size_t i=0; i