erebus

annotate src/console.cc @ 41:2e817711d0f6

console: proper input line windowing and clipping
author John Tsiombikas <nuclear@member.fsf.org>
date Tue, 10 Jun 2014 12:28:56 +0300
parents 9d6368850fe1
children b9294cd6b9dc
rev   line source
nuclear@37 1 #include <stdio.h>
nuclear@37 2 #include <stdlib.h>
nuclear@37 3 #include <string.h>
nuclear@37 4 #include <stdarg.h>
nuclear@37 5 #include <assert.h>
nuclear@37 6 #include <algorithm>
nuclear@37 7 #include "opengl.h"
nuclear@37 8 #include "console.h"
nuclear@37 9
nuclear@37 10 Console::Console()
nuclear@41 11 : prompt("> ")
nuclear@37 12 {
nuclear@37 13 visible = false;
nuclear@37 14 nlines = 16;
nuclear@37 15 ncolumns = 80;
nuclear@37 16
nuclear@37 17 inpq_front = inpq_back = 0;
nuclear@37 18
nuclear@37 19 max_hist_lines = 64;
nuclear@37 20 max_output_lines = 128;
nuclear@37 21
nuclear@37 22 echo = true;
nuclear@37 23 font = 0;
nuclear@37 24 font_size = 0;
nuclear@37 25
nuclear@37 26 cursor = 0;
nuclear@41 27 input_win = 0;
nuclear@41 28 }
nuclear@41 29
nuclear@41 30 void Console::set_cursor(int x)
nuclear@41 31 {
nuclear@41 32 if(x < 0 || x > (int)input.size()) {
nuclear@41 33 return;
nuclear@41 34 }
nuclear@41 35 cursor = x;
nuclear@41 36
nuclear@41 37 int max_chars = ncolumns - (int)prompt.size() - 1;
nuclear@41 38
nuclear@41 39 if(cursor < input_win) {
nuclear@41 40 input_win = cursor;
nuclear@41 41 } else if(cursor > input_win + max_chars) {
nuclear@41 42 input_win = std::max(cursor - max_chars, 0);
nuclear@41 43 }
nuclear@41 44 }
nuclear@41 45
nuclear@41 46 int Console::get_cursor() const
nuclear@41 47 {
nuclear@41 48 return cursor;
nuclear@37 49 }
nuclear@37 50
nuclear@37 51 void Console::set_font(dtx_font *font, int sz)
nuclear@37 52 {
nuclear@37 53 this->font = font;
nuclear@37 54 font_size = sz;
nuclear@37 55 }
nuclear@37 56
nuclear@37 57 void Console::set_history_size(int hsz)
nuclear@37 58 {
nuclear@37 59 max_hist_lines = hsz;
nuclear@37 60
nuclear@37 61 int drop = hist.size() - hsz;
nuclear@37 62 if(drop > 0) {
nuclear@37 63 auto it = hist.begin();
nuclear@37 64 for(int i=0; i<drop; i++) {
nuclear@37 65 ++it;
nuclear@37 66 }
nuclear@37 67 hist.erase(hist.begin(), it);
nuclear@37 68 }
nuclear@37 69 }
nuclear@37 70
nuclear@37 71 void Console::set_output_buffer_size(int sz)
nuclear@37 72 {
nuclear@37 73 max_output_lines = sz;
nuclear@37 74
nuclear@37 75 int drop = output.size() - sz;
nuclear@37 76 if(drop > 0) {
nuclear@37 77 output.erase(output.begin(), output.begin() + drop);
nuclear@37 78 }
nuclear@37 79 }
nuclear@37 80
nuclear@37 81 void Console::set_command_func(std::function<void (const char*)> func)
nuclear@37 82 {
nuclear@37 83 cmd_handler = func;
nuclear@37 84 }
nuclear@37 85
nuclear@37 86 void Console::set_echo(bool echo)
nuclear@37 87 {
nuclear@37 88 this->echo = echo;
nuclear@37 89 }
nuclear@37 90
nuclear@37 91 bool Console::get_echo() const
nuclear@37 92 {
nuclear@37 93 return echo;
nuclear@37 94 }
nuclear@37 95
nuclear@37 96 void Console::set_visible(bool v)
nuclear@37 97 {
nuclear@37 98 visible = v;
nuclear@37 99 }
nuclear@37 100
nuclear@37 101 bool Console::is_visible() const
nuclear@37 102 {
nuclear@37 103 return visible;
nuclear@37 104 }
nuclear@37 105
nuclear@37 106 void Console::set_size(int lines, int columns)
nuclear@37 107 {
nuclear@37 108 nlines = lines;
nuclear@37 109 ncolumns = columns;
nuclear@37 110 }
nuclear@37 111
nuclear@37 112 int Console::get_size_lines() const
nuclear@37 113 {
nuclear@37 114 return nlines;
nuclear@37 115 }
nuclear@37 116
nuclear@37 117 int Console::get_size_columns() const
nuclear@37 118 {
nuclear@37 119 return ncolumns;
nuclear@37 120 }
nuclear@37 121
nuclear@37 122 bool Console::update()
nuclear@37 123 {
nuclear@37 124 bool must_redraw = false;
nuclear@37 125
nuclear@37 126 while(inpq_front != inpq_back) {
nuclear@37 127 int c = keybuf[inpq_front];
nuclear@37 128 inpq_front = (inpq_front + 1) % INPUTQ_SIZE;
nuclear@37 129
nuclear@37 130 switch(c) {
nuclear@37 131 case '\n':
nuclear@37 132 case '\r':
nuclear@37 133 if(echo) {
nuclear@37 134 puts(input.c_str());
nuclear@37 135 putchar('\n');
nuclear@37 136 }
nuclear@41 137 if(!input.empty() && cmd_handler) {
nuclear@41 138 cmd_handler(input.c_str());
nuclear@41 139 }
nuclear@37 140
nuclear@37 141 hist.push_back(std::move(input)); // move the input string into the history buffer
nuclear@37 142 if((int)hist.size() > max_hist_lines) {
nuclear@37 143 hist.pop_front();
nuclear@37 144 }
nuclear@41 145 set_cursor(0);
nuclear@37 146 must_redraw = true;
nuclear@37 147 break;
nuclear@37 148
nuclear@37 149 case '\t':
nuclear@37 150 // TODO: completion
nuclear@37 151 break;
nuclear@37 152
nuclear@37 153 case '\b':
nuclear@37 154 if(!input.empty()) {
nuclear@37 155 if(cursor == (int)input.size()) {
nuclear@37 156 input.pop_back();
nuclear@41 157 set_cursor(get_cursor() - 1);
nuclear@37 158 must_redraw = true;
nuclear@37 159 } else if(cursor > 0) {
nuclear@38 160 input.erase(cursor - 1, 1);
nuclear@41 161 set_cursor(get_cursor() - 1);
nuclear@37 162 must_redraw = true;
nuclear@37 163 }
nuclear@37 164 }
nuclear@37 165 break;
nuclear@37 166
nuclear@37 167 case KEY_LEFT:
nuclear@37 168 if(cursor > 0) {
nuclear@41 169 set_cursor(get_cursor() - 1);
nuclear@37 170 must_redraw = true;
nuclear@37 171 }
nuclear@37 172 break;
nuclear@37 173
nuclear@37 174 case KEY_RIGHT:
nuclear@37 175 if(cursor < (int)input.size()) {
nuclear@41 176 set_cursor(get_cursor() + 1);
nuclear@37 177 must_redraw = true;
nuclear@37 178 }
nuclear@37 179 break;
nuclear@37 180
nuclear@37 181 case KEY_HOME:
nuclear@41 182 set_cursor(0);
nuclear@37 183 must_redraw = true;
nuclear@37 184 break;
nuclear@37 185
nuclear@37 186 case KEY_END:
nuclear@41 187 set_cursor(input.size());
nuclear@37 188 must_redraw = true;
nuclear@37 189 break;
nuclear@37 190
nuclear@37 191 case KEY_PGUP:
nuclear@37 192 case KEY_PGDOWN:
nuclear@37 193 // TODO history
nuclear@37 194 break;
nuclear@37 195
nuclear@37 196 default:
nuclear@37 197 if(c < 256 && isprint(c)) {
nuclear@38 198 if(cursor == (int)input.size()) {
nuclear@38 199 input.push_back(c);
nuclear@38 200 } else {
nuclear@38 201 input.insert(cursor, 1, c);
nuclear@38 202 }
nuclear@41 203 set_cursor(get_cursor() + 1);
nuclear@37 204 must_redraw = true;
nuclear@37 205 }
nuclear@37 206 }
nuclear@37 207 }
nuclear@37 208 return must_redraw;
nuclear@37 209 }
nuclear@37 210
nuclear@37 211 void Console::draw() const
nuclear@37 212 {
nuclear@37 213 if(!font || !visible) return;
nuclear@37 214 dtx_use_font(font, font_size);
nuclear@37 215
nuclear@37 216 int vp[4];
nuclear@37 217 glGetIntegerv(GL_VIEWPORT, vp);
nuclear@37 218
nuclear@37 219 glMatrixMode(GL_PROJECTION);
nuclear@37 220 glPushMatrix();
nuclear@37 221 glLoadIdentity();
nuclear@37 222 glOrtho(vp[0], vp[0] + vp[2], vp[1], vp[1] + vp[3], -1, 1);
nuclear@37 223
nuclear@37 224 glMatrixMode(GL_MODELVIEW);
nuclear@37 225 glPushMatrix();
nuclear@37 226 glLoadIdentity();
nuclear@41 227 glTranslatef(0, vp[3], 0);
nuclear@37 228
nuclear@37 229 std::string buflines;
nuclear@37 230
nuclear@37 231 int num_lines = std::min(nlines, (int)output.size());
nuclear@37 232 auto it = output.cbegin() + (output.size() - num_lines);
nuclear@37 233
nuclear@37 234 float max_width = dtx_glyph_width('Q') * ncolumns;
nuclear@37 235
nuclear@37 236 while(it != output.cend()) {
nuclear@37 237 const std::string &line = *it++;
nuclear@37 238 buflines += line + std::string("\n");
nuclear@37 239
nuclear@41 240 //max_width = std::max(max_width, dtx_string_width(line.c_str()));
nuclear@37 241 }
nuclear@37 242
nuclear@37 243 // draw the output box
nuclear@38 244 float line_height = dtx_line_height();
nuclear@38 245 float outbox_height = nlines * line_height;
nuclear@41 246 float console_height = outbox_height + line_height * 1.5;
nuclear@37 247
nuclear@37 248 glPushAttrib(GL_ENABLE_BIT);
nuclear@37 249 glEnable(GL_BLEND);
nuclear@40 250 glDisable(GL_TEXTURE_2D);
nuclear@37 251 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
nuclear@37 252
nuclear@41 253 glEnable(GL_SCISSOR_TEST);
nuclear@41 254 glScissor(0, vp[3] - console_height, max_width, console_height);
nuclear@41 255
nuclear@37 256 glBegin(GL_QUADS);
nuclear@37 257 glColor4f(0.1, 0.2, 0.5, 0.7);
nuclear@37 258 glVertex2f(0, -outbox_height);
nuclear@37 259 glVertex2f(max_width, -outbox_height);
nuclear@37 260 glVertex2f(max_width, 0);
nuclear@37 261 glVertex2f(0, 0);
nuclear@37 262
nuclear@37 263 glColor4f(0.5, 0.2, 0.1, 0.7);
nuclear@41 264 glVertex2f(0, -console_height);
nuclear@41 265 glVertex2f(max_width, -console_height);
nuclear@37 266 glVertex2f(max_width, -outbox_height);
nuclear@37 267 glVertex2f(0, -outbox_height);
nuclear@37 268 glEnd();
nuclear@37 269
nuclear@37 270 // draw the output text
nuclear@37 271 glPushMatrix();
nuclear@38 272 glTranslatef(0, -line_height, 0);
nuclear@37 273 glColor4f(1, 1, 1, 0.7);
nuclear@37 274 dtx_string(buflines.c_str());
nuclear@37 275 glPopMatrix();
nuclear@37 276
nuclear@37 277 // draw the input line
nuclear@38 278 glTranslatef(0, -outbox_height - line_height, 0);
nuclear@38 279
nuclear@41 280 std::string inpline = prompt + input.substr(input_win);
nuclear@38 281 dtx_string(inpline.c_str());
nuclear@38 282 glDisable(GL_TEXTURE_2D);
nuclear@38 283
nuclear@41 284 float cursor_x = dtx_char_pos(inpline.c_str(), cursor - input_win + prompt.size());
nuclear@38 285 glBegin(GL_QUADS);
nuclear@38 286 glColor4f(1, 1, 1, 0.7);
nuclear@40 287 glVertex2f(cursor_x, 0);
nuclear@40 288 glVertex2f(cursor_x + 2, 0);
nuclear@38 289 glVertex2f(cursor_x + 2, line_height - 2);
nuclear@38 290 glVertex2f(cursor_x, line_height - 2);
nuclear@38 291 glEnd();
nuclear@37 292
nuclear@37 293 glPopAttrib();
nuclear@37 294
nuclear@37 295 glMatrixMode(GL_PROJECTION);
nuclear@37 296 glPopMatrix();
nuclear@37 297 glMatrixMode(GL_MODELVIEW);
nuclear@37 298 glPopMatrix();
nuclear@37 299 }
nuclear@37 300
nuclear@37 301 void Console::input_key(int key)
nuclear@37 302 {
nuclear@37 303 keybuf[inpq_back] = key;
nuclear@37 304 inpq_back = (inpq_back + 1) % INPUTQ_SIZE;
nuclear@37 305 }
nuclear@37 306
nuclear@37 307 void Console::putchar(char c)
nuclear@37 308 {
nuclear@37 309 if(output.empty()) {
nuclear@37 310 output.push_back(std::string(""));
nuclear@37 311 }
nuclear@37 312
nuclear@37 313 if(c == '\n' || c == '\r') {
nuclear@37 314 output.push_back(std::string(""));
nuclear@37 315 if((int)output.size() > max_output_lines) {
nuclear@37 316 output.erase(output.begin());
nuclear@37 317 }
nuclear@37 318 } else {
nuclear@37 319 output.back().push_back(c);
nuclear@37 320 }
nuclear@37 321 }
nuclear@37 322
nuclear@37 323 void Console::puts(const char *str)
nuclear@37 324 {
nuclear@37 325 while(*str) {
nuclear@37 326 putchar(*str++);
nuclear@37 327 }
nuclear@37 328 }
nuclear@37 329
nuclear@37 330 void Console::printf(const char *fmt, ...)
nuclear@37 331 {
nuclear@37 332 static char buf[1024];
nuclear@37 333 va_list ap;
nuclear@37 334
nuclear@37 335 va_start(ap, fmt);
nuclear@37 336 vsnprintf(buf, sizeof buf, fmt, ap);
nuclear@37 337 va_end(ap);
nuclear@37 338
nuclear@37 339 puts(buf);
nuclear@37 340 }
nuclear@37 341
nuclear@37 342 void Console::debug()
nuclear@37 343 {
nuclear@37 344 fprintf(stderr, "visible: %s\n", visible ? "true" : "false");
nuclear@37 345 fprintf(stderr, "size: %d lines x %d columns\n", nlines, ncolumns);
nuclear@37 346 fprintf(stderr, "cursor: %d\n", cursor);
nuclear@37 347
nuclear@37 348 int qsize = 0;
nuclear@37 349 int qidx = inpq_front;
nuclear@37 350 while(qidx != inpq_back) {
nuclear@37 351 qsize++;
nuclear@37 352 qidx = (qidx + 1) % INPUTQ_SIZE;
nuclear@37 353 }
nuclear@37 354
nuclear@37 355 fprintf(stderr, "input queue size: %d\n", qsize);
nuclear@37 356 fprintf(stderr, "current input line: \"%s\"\n", input.c_str());
nuclear@37 357 fprintf(stderr, "%d saved inputs in the history (max %d)\n", (int)hist.size(), max_hist_lines);
nuclear@37 358 auto it = hist.begin();
nuclear@37 359 int idx = 0;
nuclear@37 360 while(it != hist.end()) {
nuclear@37 361 fprintf(stderr, " h(%d): \"%s\"\n", idx++, it++->c_str());
nuclear@37 362 }
nuclear@37 363
nuclear@37 364 fprintf(stderr, "output buffer (lines: %d/%d):\n", (int)output.size(), max_output_lines);
nuclear@37 365 for(size_t i=0; i<output.size(); i++) {
nuclear@37 366 fprintf(stderr, "o(%d): \"%s\"\n", (int)i, output[i].c_str());
nuclear@37 367 }
nuclear@37 368 }