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