vtgl.c (26616B)
1 /* See LICENSE for copyright details */ 2 #define GL_GLEXT_PROTOTYPES 1 3 #include <GL/glcorearb.h> 4 #include <GL/glext.h> 5 #include <GLFW/glfw3.h> 6 7 #include "util.h" 8 #include "config.h" 9 10 #include "font.c" 11 #include "terminal.c" 12 #ifdef _DEBUG 13 #include "debug.c" 14 #endif 15 16 #define REVERSE_VIDEO_MASK (Colour){.r = 0xff, .g = 0xff, .b = 0xff}.rgba 17 18 #define TEXTURE_GLYPH_COUNT PUSH_BUFFER_CAP 19 20 #define X(name) "u_"#name, 21 static char *render_uniform_names[] = { GL_RENDER_UNIFORMS }; 22 static char *post_uniform_names[] = { GL_POST_UNIFORMS }; 23 #undef X 24 static_assert(ARRAY_COUNT(render_uniform_names) == ARRAY_COUNT(((GLCtx *)0)->render.uniforms), 25 "GLCtx.render.uniforms must be same length as GL_RENDER_UNIFORMS\n"); 26 static_assert(ARRAY_COUNT(post_uniform_names) == ARRAY_COUNT(((GLCtx *)0)->post.uniforms), 27 "GLCtx.post.uniforms must be same length as GL_POST_UNIFORMS\n"); 28 29 static v4 30 normalized_colour(Colour c) 31 { 32 return (v4){.r = c.r / 255.0f, .g = c.g / 255.0f, .b = c.b / 255.0f, .a = c.a / 255.0f}; 33 } 34 35 static void 36 clear_colour(b32 reverse) 37 { 38 Colour c = g_colours.data[g_colours.bgidx]; 39 if (reverse) c.rgba ^= REVERSE_VIDEO_MASK; 40 v4 cc = normalized_colour(c); 41 glClearColor(cc.r, cc.g, cc.b, cc.a); 42 glClear(GL_COLOR_BUFFER_BIT); 43 } 44 45 static void 46 set_projection_matrix(GLCtx *gl) 47 { 48 f32 w = gl->window_size.w; 49 f32 h = gl->window_size.h; 50 51 f32 pmat[4 * 4] = { 52 2.0/w, 0.0, 0.0, -1.0, 53 0.0, 2.0/h, 0.0, -1.0, 54 0.0, 0.0, -1.0, 0.0, 55 0.0, 0.0, 0.0, 1.0, 56 }; 57 58 glUseProgram(gl->programs[SHADER_RENDER]); 59 glUniformMatrix4fv(gl->render.Pmat, 1, GL_TRUE, pmat); 60 glUseProgram(gl->programs[SHADER_POST]); 61 glUniformMatrix4fv(gl->post.Pmat, 1, GL_TRUE, pmat); 62 } 63 64 static v2 65 get_cell_size(Term *t) 66 { 67 v2 result = {.w = t->fa.size.w + g_cell_pad.w, .h = t->fa.size.h + g_cell_pad.h}; 68 return result; 69 } 70 71 static v2 72 get_cell_pad_off(void) 73 { 74 v2 result = {.w = 0.5 * g_cell_pad.w, .h = 0.5 * g_cell_pad.h}; 75 return result; 76 } 77 78 static v2 79 get_occupied_size(Term *t) 80 { 81 v2 cs = get_cell_size(t); 82 v2 result = {.x = t->size.w * cs.w, .y = t->size.h * cs.h}; 83 return result; 84 } 85 86 static v2 87 get_terminal_top_left(Term *t) 88 { 89 /* NOTE: There is trade-off here: you can center the usable area which looks good but 90 * causes the contents to jump around when resizing; or you can only consider the global 91 * padding and have the contents fixed while resizing. We are choosing the former! */ 92 /* IMPORTANT: Assume the glyphs already have subpixel rendering so cells must be aligned 93 * on pixels. No harm if they don't but if they do and we don't align on pixels they 94 * will look like crap. */ 95 v2 os = get_occupied_size(t); 96 v2 delta = {.x = t->gl.window_size.w - os.w, .y = t->gl.window_size.h - os.h}; 97 v2 result = {.x = (u32)(delta.x / 2), .y = (u32)(t->gl.window_size.h - delta.y / 2)}; 98 return result; 99 } 100 101 static v2 102 get_terminal_bot_left(Term *t) 103 { 104 v2 os = get_occupied_size(t); 105 v2 delta = {.x = t->gl.window_size.w - os.w, .y = t->gl.window_size.h - os.h}; 106 v2 result = {.x = delta.x / 2, .y = delta.y / 2}; 107 return result; 108 } 109 110 static void 111 resize(Term *t) 112 { 113 v2 ws = t->gl.window_size; 114 ws.w -= 2 * g_term_pad.w; 115 ws.h -= 2 * g_term_pad.h; 116 117 uv2 old_size = t->size; 118 v2 cs = get_cell_size(t); 119 t->size.w = (u32)(ws.w / cs.w); 120 t->size.h = (u32)(ws.h / cs.h); 121 122 if (!equal_uv2(old_size, t->size)) { 123 os_alloc_framebuffer(&t->views[0].fb, t->size.h, t->size.w); 124 os_alloc_framebuffer(&t->views[1].fb, t->size.h, t->size.w); 125 t->gl.flags |= NEEDS_FULL_REFILL; 126 } 127 128 os_set_term_size(t->child, t->size.h, t->size.w, ws.w, ws.h); 129 t->gl.flags &= ~NEEDS_RESIZE; 130 } 131 132 static void 133 update_font_textures(GLCtx *gl, FontAtlas *fa) 134 { 135 glActiveTexture(GL_TEXTURE0); 136 glBindTexture(GL_TEXTURE_2D_ARRAY, gl->glyph_tex); 137 glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 138 MAX_FONT_SIZE, MAX_FONT_SIZE, TEXTURE_GLYPH_COUNT, 139 0, GL_RED, GL_UNSIGNED_BYTE, 0); 140 141 font_atlas_update(fa); 142 } 143 144 static void 145 update_uniforms(Term *t, enum shader_stages stage) 146 { 147 switch (stage) { 148 case SHADER_RENDER: 149 for (u32 i = 0; i < ARRAY_COUNT(t->gl.render.uniforms); i++) { 150 t->gl.render.uniforms[i] = glGetUniformLocation(t->gl.programs[stage], 151 render_uniform_names[i]); 152 //fprintf(stderr, "uniform (RENDER): %s; id %d\n", 153 // render_uniform_names[i], t->gl.render.uniforms[i]); 154 } 155 t->gl.flags &= ~UPDATE_RENDER_UNIFORMS; 156 break; 157 case SHADER_POST: 158 for (u32 i = 0; i < ARRAY_COUNT(t->gl.post.uniforms); i++) { 159 t->gl.post.uniforms[i] = glGetUniformLocation(t->gl.programs[stage], 160 post_uniform_names[i]); 161 //fprintf(stderr, "uniform (POST): %s; id %d\n", 162 // post_uniform_names[i], t->gl.post.uniforms[i]); 163 } 164 t->gl.flags &= ~UPDATE_POST_UNIFORMS; 165 break; 166 case SHADER_LAST: ASSERT(0); break; 167 } 168 169 set_projection_matrix(&t->gl); 170 /* TODO: this doesn't need to be called so often */ 171 update_font_textures(&t->gl, &t->fa); 172 } 173 174 static i32 175 get_gpu_glyph_index(Arena a, FontAtlas *fa, u32 codepoint, enum face_style style, Glyph *out_glyph) 176 { 177 u32 depth_idx; 178 CachedGlyph *cg; 179 u32 *data = render_glyph(&a, fa, codepoint, style, &cg, &depth_idx); 180 *out_glyph = cg->g; 181 if (data) { 182 ASSERT(depth_idx); 183 glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, depth_idx, 184 out_glyph->size.w, out_glyph->size.h, 1, GL_RGBA, 185 GL_UNSIGNED_BYTE, data); 186 cg->uploaded_to_gpu = 1; 187 } 188 ASSERT(cg->uploaded_to_gpu); 189 return depth_idx; 190 } 191 192 static v2 193 measure_text(Arena a, FontAtlas *fa, s8 text, b32 monospaced) 194 { 195 v2 result = {0}; 196 Glyph g; 197 get_gpu_glyph_index(a, fa, ' ', FS_NORMAL, &g); 198 f32 single_space_width = g.size.w; 199 for (size i = 0; i < text.len; i++) { 200 get_gpu_glyph_index(a, fa, text.data[i], FS_NORMAL, &g); 201 /* TODO: should we consider offset characters in y? */ 202 if (g.size.h > result.y) 203 result.y = g.size.h; 204 result.x += monospaced? single_space_width : g.size.w; 205 } 206 return result; 207 } 208 209 static void 210 flush_render_push_buffer(RenderPushBuffer *rpb, GLCtx *gl) 211 { 212 glUniform2fv(gl->render.vertscale, rpb->count, (f32 *)rpb->vertscales); 213 glUniform2fv(gl->render.vertoff, rpb->count, (f32 *)rpb->vertoffsets); 214 glUniform2fv(gl->render.texscale, rpb->count, (f32 *)rpb->texscales); 215 glUniform2uiv(gl->render.texcolour, rpb->count, (u32 *)rpb->texcolours); 216 glUniform1iv(gl->render.charmap, rpb->count, (i32 *)rpb->charmap); 217 glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, rpb->count); 218 rpb->count = 0; 219 } 220 221 static u32 222 get_render_push_buffer_idx(RenderPushBuffer *rpb, GLCtx *gl, u32 count) 223 { 224 if (rpb->count + count > PUSH_BUFFER_CAP) 225 flush_render_push_buffer(rpb, gl); 226 u32 result = rpb->count; 227 rpb->count += count; 228 return result; 229 } 230 231 static void 232 push_char(RenderPushBuffer *rpb, GLCtx *gl, v2 vertscale, v2 vertoff, v2 texscale, 233 uv2 colours, i32 char_idx) 234 { 235 u32 idx = get_render_push_buffer_idx(rpb, gl, 1); 236 rpb->vertscales[idx] = vertscale; 237 rpb->vertoffsets[idx] = vertoff; 238 rpb->texscales[idx] = texscale; 239 rpb->texcolours[idx] = colours; 240 rpb->charmap[idx] = char_idx; 241 } 242 243 static void 244 push_empty_cell_rect(RenderPushBuffer *rpb, Term *t, u32 minrow, u32 maxrow, u32 mincol, u32 maxcol) 245 { 246 ASSERT(minrow <= maxrow && mincol <= maxcol); 247 ASSERT(maxrow <= t->size.h && maxcol <= t->size.w); 248 249 v2 cs = get_cell_size(t); 250 v2 size = {.x = (maxcol - mincol + 1) * cs.w, .y = (maxrow - minrow + 1) * cs.h}; 251 v2 pos = {.x = mincol * cs.w, .y = t->gl.window_size.h - cs.h * (maxrow + 1)}; 252 253 Colour colour = g_colours.data[g_colours.fgidx]; 254 push_char(rpb, &t->gl, size, pos, (v2){0}, (uv2){.y = colour.rgba}, 0); 255 } 256 257 static void 258 draw_text(RenderPushBuffer *rpb, GLCtx *gl, Arena a, FontAtlas *fa, s8 text, v2 position, Colour colour, b32 monospaced) 259 { 260 Glyph g; 261 get_gpu_glyph_index(a, fa, ' ', FS_NORMAL, &g); 262 f32 single_space_width = g.size.w; 263 for (size i = 0; i < text.len; i++) { 264 u32 cp = text.data[i]; 265 i32 glyph_idx = get_gpu_glyph_index(a, fa, cp, FS_NORMAL, &g); 266 v2 texscale = {.x = g.size.w / MAX_FONT_SIZE, .y = g.size.h / MAX_FONT_SIZE}; 267 v2 vertscale = {.x = g.size.w, .y = g.size.h}; 268 v2 vertoff = {.x = position.x + g.delta.x, .y = position.y + g.delta.y}; 269 push_char(rpb, gl, vertscale, vertoff, texscale, (uv2){.x = colour.rgba}, glyph_idx); 270 position.x += monospaced? single_space_width : g.size.w; 271 } 272 } 273 274 static void 275 draw_rectangle(RenderPushBuffer *rpb, GLCtx *gl, Rect r, Colour colour) 276 { 277 push_char(rpb, gl, r.size, r.pos, (v2){0}, (uv2){.y = colour.rgba}, 0); 278 } 279 280 static void 281 push_cell(RenderPushBuffer *rpb, GLCtx *gl, Arena a, FontAtlas *fa, Cell c, Rect r, v2 cell_font_delta) 282 { 283 u32 idx = get_render_push_buffer_idx(rpb, gl, 2); 284 285 ASSERT(c.cp); 286 Glyph g; 287 i32 style = FS_NORMAL; 288 /* TODO: performance */ 289 if ((c.style.attr & (ATTR_BOLD|ATTR_ITALIC)) == (ATTR_BOLD|ATTR_ITALIC)) 290 style = FS_BOLD_ITALIC; 291 else if (c.style.attr & ATTR_BOLD) 292 style = FS_BOLD; 293 else if (c.style.attr & ATTR_ITALIC) 294 style = FS_ITALIC; 295 296 i32 depth_idx = get_gpu_glyph_index(a, fa, c.cp, style, &g); 297 298 rpb->vertscales[idx + 0] = r.size; 299 rpb->vertscales[idx + 1] = (v2){.x = g.size.w, .y = g.size.h}; 300 301 rpb->vertoffsets[idx + 0] = r.pos; 302 rpb->vertoffsets[idx + 1] = (v2){ 303 .x = r.pos.x + g.delta.x + cell_font_delta.x, 304 .y = r.pos.y + g.delta.y + cell_font_delta.y, 305 }; 306 307 rpb->texscales[idx + 0] = (v2){0}; 308 rpb->texscales[idx + 1] = (v2){ 309 .x = g.size.w / (f32)MAX_FONT_SIZE, 310 .y = g.size.h / (f32)MAX_FONT_SIZE, 311 }; 312 313 rpb->charmap[idx + 0] = depth_idx; 314 rpb->charmap[idx + 1] = depth_idx; 315 316 CellStyle cs = c.style; 317 if (cs.attr & ATTR_FAINT) { 318 if (cs.attr & ATTR_INVERSE) cs.bg.a = 0.5 * 255; 319 else cs.fg.a = 0.5 * 255; 320 } 321 322 u32 fg = (cs.attr & ATTR_INVERSE)? cs.bg.rgba : cs.fg.rgba; 323 u32 bg = (cs.attr & ATTR_INVERSE)? cs.fg.rgba : cs.bg.rgba; 324 u32 rmask = (gl->mode & WIN_MODE_REVERSE)? REVERSE_VIDEO_MASK : 0; 325 326 rpb->texcolours[idx + 0].x = fg ^ rmask; 327 rpb->texcolours[idx + 0].y = bg ^ rmask; 328 rpb->texcolours[idx + 1].x = fg ^ rmask; 329 rpb->texcolours[idx + 1].y = bg ^ rmask; 330 } 331 332 static void 333 push_cell_row(RenderPushBuffer *rpb, GLCtx *gl, Arena a, FontAtlas *fa, Cell *row, u32 len, 334 b32 inverse, Rect cr, v2 cell_font_delta) 335 { 336 ASSERT(inverse == 0 || inverse == 1); 337 v2 cs = cr.size; 338 v2 csw = {.w = cs.w * 2, .h = cs.h}; 339 for (u32 c = 0; c < len; c++) { 340 Cell cell = row[c]; 341 if (cell.style.attr & ATTR_WDUMMY) 342 continue; 343 if (cell.style.attr & ATTR_WIDE) cr.size = csw; 344 else cr.size = cs; 345 cell.style.attr ^= inverse * ATTR_INVERSE; 346 push_cell(rpb, gl, a, fa, cell, cr, cell_font_delta); 347 cr.pos.x += cr.size.w; 348 } 349 } 350 351 /* NOTE: In this program we render to an offscreen render target that is later drawn 352 * to the screen as a full window quad. Therefore render_framebuffer must take care 353 * to handle all necessary padding (window and cell). Outside of here everyone should 354 * simply care about the terminal in terms of rows and columns (t->size). */ 355 static void 356 render_framebuffer(Term *t, RenderPushBuffer *rpb) 357 { 358 v2 tl = get_terminal_top_left(t); 359 v2 cs = get_cell_size(t); 360 Rect cr = {.pos = {.x = tl.x, .y = tl.y - cs.h}, .size = cs}; 361 362 v2 cell_font_delta = get_cell_pad_off(); 363 cell_font_delta.y += t->fa.baseline; 364 365 TermView *tv = t->views + t->view_idx; 366 /* NOTE: draw whole framebuffer */ 367 for (u32 r = 0; r < t->size.h; r++) { 368 push_cell_row(rpb, &t->gl, t->arena_for_frame, &t->fa, tv->fb.rows[r], t->size.w, 0, 369 cr, cell_font_delta); 370 cr.pos.y -= cs.h; 371 } 372 373 /* NOTE: draw selection if active */ 374 /* TODO: combine with original push_cell? */ 375 if (is_valid_range(t->selection.range)) { 376 Range sel = t->selection.range; 377 iv2 curs = sel.start; 378 iv2 end = sel.end; 379 cr.pos = (v2){.x = tl.x + cs.w * curs.x, .y = tl.h - cs.h * (curs.y + 1)}; 380 /* NOTE: do full rows first */ 381 for (; curs.y < end.y; curs.y++) { 382 u32 len = t->size.w - curs.x - 1; 383 push_cell_row(rpb, &t->gl, t->arena_for_frame, &t->fa, tv->fb.rows[curs.y] + curs.x, len, 1, 384 cr, cell_font_delta); 385 curs.x = 0; 386 cr.pos.x = tl.x; 387 cr.pos.y -= cs.h; 388 } 389 /* NOTE: do the last row */ 390 push_cell_row(rpb, &t->gl, t->arena_for_frame, &t->fa, tv->fb.rows[curs.y] + curs.x, 391 end.x - curs.x + 1, 1, cr, cell_font_delta); 392 } 393 394 /* NOTE: draw cursor */ 395 if (!(t->gl.mode & WIN_MODE_HIDECURSOR) && t->scroll_offset == 0) { 396 iv2 curs = t->cursor.pos; 397 cr.pos = (v2){.x = tl.x + cs.w * curs.x, .y = tl.h - cs.h * (curs.y + 1)}; 398 Cell cursor = tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x]; 399 if (cursor.style.attr & ATTR_WDUMMY) { 400 cursor = tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x - 1]; 401 ASSERT(cursor.style.attr & ATTR_WIDE); 402 } 403 if (cursor.style.attr & ATTR_WIDE) 404 cr.size.w *= 2; 405 cursor.style.attr ^= ATTR_INVERSE; 406 push_cell(rpb, &t->gl, t->arena_for_frame, &t->fa, cursor, cr, cell_font_delta); 407 } 408 } 409 410 static iv2 411 mouse_to_cell_space(Term *t, v2 mouse) 412 { 413 iv2 result = {0}; 414 v2 cell_size = get_cell_size(t); 415 v2 bot_left = get_terminal_bot_left(t); 416 417 result.x = (i32)((mouse.x - bot_left.x) / cell_size.w); 418 result.y = (i32)((mouse.y - bot_left.y) / cell_size.h); 419 420 CLAMP(result.x, 0, t->size.w - 1); 421 CLAMP(result.y, 0, t->size.h - 1); 422 423 return result; 424 } 425 426 static void 427 update_selection(Term *t) 428 { 429 Selection *sel = &t->selection; 430 b32 held = glfwGetMouseButton(t->gl.window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; 431 432 sel->click_param -= t->gl.dt; 433 if (sel->click_param < 0) { 434 sel->click_param = 0; 435 if (!held) 436 sel->state = SS_NONE; 437 } 438 439 if (!held) 440 return; 441 442 f64 xpos, ypos; 443 glfwGetCursorPos(t->gl.window, &xpos, &ypos); 444 v2 mouse = {.x = xpos, .y = ypos}; 445 446 if (sel->state != SS_WORDS && (mouse.x == sel->mouse_start.x && mouse.y == sel->mouse_start.y)) 447 return; 448 449 iv2 new_p = mouse_to_cell_space(t, mouse); 450 if (t->views[t->view_idx].fb.rows[new_p.y][new_p.x].style.attr & ATTR_WDUMMY) { 451 ASSERT(t->views[t->view_idx].fb.rows[new_p.y][new_p.x - 1].style.attr & ATTR_WIDE); 452 new_p.x--; 453 } 454 if (sel->state != SS_WORDS) { 455 sel->range.start = sel->anchor.start; 456 sel->range.end = new_p; 457 } else { 458 Range word = get_word_around_cell(t, new_p); 459 if (sel->anchor.start.y < word.start.y) { 460 sel->range.start = sel->anchor.start; 461 sel->range.end = word.end; 462 } else if (sel->anchor.start.y > word.start.y) { 463 sel->range.start = word.start; 464 sel->range.end = sel->anchor.end; 465 } else { 466 if (word.start.x < sel->anchor.start.x) { 467 sel->range.start = word.start; 468 sel->range.end = sel->anchor.end; 469 } else { 470 sel->range.start = sel->anchor.start; 471 sel->range.end = word.end; 472 } 473 } 474 } 475 sel->range = normalize_range(sel->range); 476 t->gl.flags |= NEEDS_BLIT; 477 } 478 479 KEYBIND_FN(copy) 480 { 481 if (!is_valid_range(t->selection.range)) 482 return 1; 483 484 TermView *tv = t->views + t->view_idx; 485 Range sel = t->selection.range; 486 iv2 curs = sel.start; 487 iv2 end = sel.end; 488 i32 buf_curs = 0; 489 490 /* NOTE: super piggy but we are only holding onto it for the function duration */ 491 Arena arena = t->arena_for_frame; 492 size buf_size = 1 * MEGABYTE; 493 char *buf = alloc(&arena, char, buf_size); 494 495 /* NOTE: do full rows first */ 496 u32 last_non_space_idx = 0; 497 for (; curs.y < end.y; curs.y++) { 498 for (; curs.x < t->size.w && buf_curs != buf_size; curs.x++) { 499 Cell c = tv->fb.rows[curs.y][curs.x]; 500 if (c.style.attr & ATTR_WDUMMY) 501 continue; 502 if (!ISSPACE(c.cp)) 503 last_non_space_idx = buf_curs; 504 s8 enc = utf8_encode(c.cp); 505 for (size i = 0; i < enc.len && buf_curs != buf_size; i++) 506 buf[buf_curs++] = enc.data[i]; 507 } 508 buf[last_non_space_idx + 1] = '\n'; 509 buf_curs = last_non_space_idx + 2; 510 curs.x = 0; 511 } 512 513 /* NOTE: do the last row */ 514 for (; curs.x <= end.x && buf_curs != buf_size; curs.x++) { 515 Cell c = tv->fb.rows[curs.y][curs.x]; 516 if (c.style.attr & ATTR_WDUMMY) 517 continue; 518 if (!ISSPACE(c.cp)) 519 last_non_space_idx = buf_curs; 520 s8 enc = utf8_encode(c.cp); 521 for (size i = 0; i < enc.len && buf_curs != buf_size; i++) 522 buf[buf_curs++] = enc.data[i]; 523 } 524 525 CLAMP(buf_curs, 0, buf_size - 1); 526 buf[buf_curs] = 0; 527 glfwSetClipboardString(0, buf); 528 529 return 1; 530 } 531 532 KEYBIND_FN(paste) 533 { 534 s8 text = {.data = (u8 *)glfwGetClipboardString(0)}; 535 b32 bracketed = t->gl.mode & WIN_MODE_BRACKPASTE; 536 /* TODO: we may need to replace '\n' with '\r' */ 537 if (text.data) { 538 for (u8 *t = text.data; *t; t++) 539 text.len++; 540 if (bracketed) os_child_put_s8(t->child, s8("\033[200~")); 541 os_child_put_s8(t->child, text); 542 if (bracketed) os_child_put_s8(t->child, s8("\033[201~")); 543 } 544 return 1; 545 } 546 547 KEYBIND_FN(scroll) 548 { 549 if (t->mode & TM_ALTSCREEN) 550 return 0; 551 552 TermView *tv = t->views + t->view_idx; 553 554 /* NOTE: do nothing if there aren't enough lines to scrollback */ 555 if (tv->lines.filled < t->size.h) 556 return 1; 557 558 t->scroll_offset += a.i; 559 CLAMP(t->scroll_offset, 0, tv->lines.filled - (t->size.h - 1)); 560 561 t->gl.flags |= NEEDS_FULL_REFILL; 562 563 return 1; 564 } 565 566 KEYBIND_FN(zoom) 567 { 568 shift_font_sizes(&t->fa, a.i); 569 update_font_textures(&t->gl, &t->fa); 570 t->gl.flags |= NEEDS_RESIZE; 571 return 1; 572 } 573 574 /* NOTE: called when the window was resized */ 575 static void 576 fb_callback(GLFWwindow *win, i32 w, i32 h) 577 { 578 Term *t = glfwGetWindowUserPointer(win); 579 580 t->gl.window_size = (v2){.w = w, .h = h}; 581 582 glViewport(0, 0, w, h); 583 set_projection_matrix(&t->gl); 584 585 glActiveTexture(GL_TEXTURE0 + t->gl.fb_tex_unit); 586 glBindTexture(GL_TEXTURE_2D, t->gl.fb_tex); 587 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); 588 589 /* NOTE: reactive the glyph texture unit */ 590 glActiveTexture(GL_TEXTURE0); 591 592 t->gl.flags |= NEEDS_RESIZE|NEEDS_BLIT; 593 } 594 595 static void 596 key_callback(GLFWwindow *win, i32 key, i32 sc, i32 act, i32 mods) 597 { 598 Term *t = glfwGetWindowUserPointer(win); 599 600 #ifdef _DEBUG 601 if (key == GLFW_KEY_F1 && act == GLFW_PRESS) { 602 dump_last_line_to_file(t); 603 return; 604 } 605 if (key == GLFW_KEY_F2 && act == GLFW_PRESS) { 606 dump_fb_to_file(t); 607 return; 608 } 609 if (key == GLFW_KEY_F3 && act == GLFW_PRESS) { 610 dump_glyph_cache_to_file(&t->fa); 611 return; 612 } 613 #endif 614 615 /* NOTE: handle mapped keybindings */ 616 u32 enc = ENCODE_KEY(act, mods, key); 617 for (u32 i = 0; i < ARRAY_COUNT(g_hotkeys); i++) { 618 struct hotkey *hk = g_hotkeys + i; 619 if (hk->key == enc) { 620 b32 handled = hk->fn(t, hk->arg); 621 if (handled) 622 return; 623 } 624 } 625 626 /* NOTE: send control sequences */ 627 if (mods & GLFW_MOD_CONTROL && act != GLFW_RELEASE) { 628 if (t->gl.mode & WIN_MODE_8BIT) { 629 if (key < 0x7F) { 630 s8 enc = utf8_encode(key | 0x80); 631 os_child_put_s8(t->child, enc); 632 return; 633 } 634 } else if (BETWEEN(key, 0x40, 0x5F)) { 635 os_child_put_char(t->child, key - 0x40); 636 return; 637 } 638 } 639 640 641 switch (ENCODE_KEY(act, 0, key)) { 642 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_TAB): 643 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_TAB): 644 os_child_put_char(t->child, '\t'); 645 break; 646 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_ENTER): 647 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_ENTER): 648 os_child_put_char(t->child, '\r'); 649 break; 650 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_BACKSPACE): 651 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_BACKSPACE): 652 os_child_put_char(t->child, 0x7F); 653 break; 654 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_UP): 655 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_UP): 656 if (t->gl.mode & WIN_MODE_APPCURSOR) 657 os_child_put_s8(t->child, s8("\x1BOA")); 658 else 659 os_child_put_s8(t->child, s8("\x1B[A")); 660 break; 661 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_DOWN): 662 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_DOWN): 663 if (t->gl.mode & WIN_MODE_APPCURSOR) 664 os_child_put_s8(t->child, s8("\x1BOB")); 665 else 666 os_child_put_s8(t->child, s8("\x1B[B")); 667 break; 668 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_RIGHT): 669 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_RIGHT): 670 if (t->gl.mode & WIN_MODE_APPCURSOR) 671 os_child_put_s8(t->child, s8("\x1BOC")); 672 else 673 os_child_put_s8(t->child, s8("\x1B[C")); 674 break; 675 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_LEFT): 676 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_LEFT): 677 if (t->gl.mode & WIN_MODE_APPCURSOR) 678 os_child_put_s8(t->child, s8("\x1BOD")); 679 else 680 os_child_put_s8(t->child, s8("\x1B[D")); 681 break; 682 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_PAGE_UP): 683 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_PAGE_UP): 684 if (mods & GLFW_MOD_CONTROL) os_child_put_s8(t->child, s8("\x1B[5;5~")); 685 else if (mods & GLFW_MOD_SHIFT) os_child_put_s8(t->child, s8("\x1B[5;2~")); 686 else os_child_put_s8(t->child, s8("\x1B[5~")); 687 break; 688 case ENCODE_KEY(GLFW_PRESS, 0, GLFW_KEY_PAGE_DOWN): 689 case ENCODE_KEY(GLFW_REPEAT, 0, GLFW_KEY_PAGE_DOWN): 690 if (mods & GLFW_MOD_CONTROL) os_child_put_s8(t->child, s8("\x1B[6;5~")); 691 else if (mods & GLFW_MOD_SHIFT) os_child_put_s8(t->child, s8("\x1B[6;2~")); 692 else os_child_put_s8(t->child, s8("\x1B[6~")); 693 break; 694 } 695 } 696 697 static void 698 mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod) 699 { 700 Term *t = glfwGetWindowUserPointer(win); 701 /* TODO: map other mouse buttons */ 702 if (btn != GLFW_MOUSE_BUTTON_LEFT) 703 return; 704 705 if (act == GLFW_RELEASE) 706 return; 707 708 f64 xpos, ypos; 709 glfwGetCursorPos(win, &xpos, &ypos); 710 t->selection.range.end = (iv2){.x = -1, .y = -1}; 711 t->selection.mouse_start = (v2){.x = xpos, .y = ypos}; 712 t->selection.click_param = DOUBLE_CLICK_TIME; 713 714 if (t->selection.state != SS_WORDS) 715 t->selection.state++; 716 717 iv2 cell = mouse_to_cell_space(t, t->selection.mouse_start); 718 if (t->views[t->view_idx].fb.rows[cell.y][cell.x].style.attr & ATTR_WDUMMY) { 719 ASSERT(t->views[t->view_idx].fb.rows[cell.y][cell.x - 1].style.attr & ATTR_WIDE); 720 cell.x--; 721 } 722 723 if (t->selection.state == SS_WORDS) { 724 t->selection.anchor = get_word_around_cell(t, cell); 725 } else { 726 t->selection.anchor = (Range){.start = cell, .end = cell}; 727 t->selection.range.end = INVALID_RANGE_END; 728 } 729 } 730 731 static void 732 char_callback(GLFWwindow *win, u32 codepoint) 733 { 734 Term *t = glfwGetWindowUserPointer(win); 735 if (t->scroll_offset) { 736 t->scroll_offset = 0; 737 t->gl.flags |= NEEDS_FULL_REFILL; 738 } 739 os_child_put_s8(t->child, utf8_encode(codepoint)); 740 } 741 742 static void 743 scroll_callback(GLFWwindow *win, f64 xoff, f64 yoff) 744 { 745 (void)xoff; 746 747 Term *t = glfwGetWindowUserPointer(win); 748 if (t->mode & TM_ALTSCREEN) { 749 b32 left_shift_state = glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS; 750 b32 right_shift_state = glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS; 751 b32 shift_down = left_shift_state || right_shift_state; 752 if (yoff > 0) { 753 if (shift_down) os_child_put_s8(t->child, s8("\x1B[5;2~")); 754 else os_child_put_s8(t->child, s8("\x19")); 755 } else { 756 if (shift_down) os_child_put_s8(t->child, s8("\x1B[6;2~")); 757 else os_child_put_s8(t->child, s8("\x05")); 758 } 759 } else { 760 Arg a = {.i = (i32)yoff}; 761 if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) || 762 glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT)) 763 a.i *= 5; 764 scroll(t, a); 765 } 766 } 767 768 DEBUG_EXPORT iv2 769 init_term(Term *t, Arena *a, iv2 cells) 770 { 771 init_fonts(t, a); 772 for (u32 i = 0; i < ARRAY_COUNT(t->saved_cursors); i++) { 773 cursor_reset(t); 774 cursor_move_to(t, 0, 0); 775 cursor_alt(t, 1); 776 } 777 selection_clear(&t->selection); 778 v2 cs = get_cell_size(t); 779 iv2 requested_size = { 780 .x = cs.x * cells.x + 2 * g_term_pad.x, 781 .y = cs.y * cells.y + 2 * g_term_pad.y, 782 }; 783 return requested_size; 784 } 785 786 DEBUG_EXPORT void 787 init_callbacks(GLCtx *gl) 788 { 789 glfwSetCharCallback(gl->window, char_callback); 790 glfwSetFramebufferSizeCallback(gl->window, fb_callback); 791 glfwSetKeyCallback(gl->window, key_callback); 792 glfwSetMouseButtonCallback(gl->window, mouse_button_callback); 793 //glfwSetWindowRefreshCallback(gl->window, refresh_callback); 794 glfwSetScrollCallback(gl->window, scroll_callback); 795 } 796 797 DEBUG_EXPORT void 798 do_terminal(Term *t) 799 { 800 static f32 last_frame_time; 801 f32 frame_start_time = (f32)glfwGetTime(); 802 803 if (t->gl.flags & UPDATE_RENDER_UNIFORMS) 804 update_uniforms(t, SHADER_RENDER); 805 if (t->gl.flags & UPDATE_POST_UNIFORMS) 806 update_uniforms(t, SHADER_POST); 807 808 if (t->gl.flags & NEEDS_RESIZE) 809 resize(t); 810 811 size parsed_lines = 0; 812 if (os_child_data_available(t->child)) { 813 RingBuf *rb = &t->views[t->view_idx].log; 814 if (os_child_exited(t->child)) { 815 /* TODO: is there a reason to not immediately exit? */ 816 glfwSetWindowShouldClose(t->gl.window, GL_TRUE); 817 return; 818 } 819 t->unprocessed_bytes += os_read_from_child(t->child, t->views + t->view_idx, 820 t->unprocessed_bytes); 821 s8 raw = { 822 .len = t->unprocessed_bytes, 823 .data = rb->buf + (rb->widx - t->unprocessed_bytes) 824 }; 825 parsed_lines = split_raw_input_to_lines(t, raw); 826 t->gl.flags |= NEEDS_REFILL; 827 } 828 829 if (t->gl.flags & (NEEDS_REFILL|NEEDS_FULL_REFILL)) 830 blit_lines(t, t->arena_for_frame, parsed_lines); 831 832 update_selection(t); 833 834 v2 ws = t->gl.window_size; 835 836 if (t->gl.flags & NEEDS_BLIT) { 837 glUseProgram(t->gl.programs[SHADER_RENDER]); 838 glUniform1i(t->gl.render.texslot, 0); 839 glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb); 840 841 RenderPushBuffer *rpb = alloc(&t->arena_for_frame, RenderPushBuffer, 1); 842 clear_colour(t->gl.mode & WIN_MODE_REVERSE); 843 render_framebuffer(t, rpb); 844 845 if (0) { 846 v2 cell_size = get_cell_size(t); 847 v2 cursor_pos = { 848 .x = t->cursor.pos.x * cell_size.w, 849 .y = ws.h - cell_size.h * (t->cursor.pos.y + 1), 850 }; 851 852 v2 src_bl = {0}; 853 v2 src_tr = {.x = ws.w, .y = src_bl.y + ws.h}; 854 855 if (t->cursor.pos.y > t->size.h) { 856 src_tr.y = cursor_pos.y + ws.h; 857 src_bl.y = cursor_pos.y; 858 } 859 860 s8 fps = s8alloc(&t->arena_for_frame, 64); 861 fps.len = snprintf((char *)fps.data, fps.len, "Render Time: %0.02f ms/f", 862 last_frame_time * 1e3); 863 v2 ts = measure_text(t->arena_for_frame, &t->fa, fps, 1); 864 v2 pos = {.x = ws.w - ts.x - 10, .y = src_tr.y - ts.y - 10}; 865 866 Rect r = { 867 .pos = {.x = pos.x - 0.05 * ts.x, .y = pos.y - 0.25 * ts.y}, 868 .size = {.w = ts.w * 1.1, .h = ts.h * 1.2}, 869 }; 870 871 draw_rectangle(rpb, &t->gl, r, (Colour){.rgba = 0x303030ff}); 872 draw_text(rpb, &t->gl, t->arena_for_frame, &t->fa, fps, pos, 873 (Colour){.rgba = 0x1e9e33ff}, 1); 874 } 875 876 flush_render_push_buffer(rpb, &t->gl); 877 t->gl.flags &= ~NEEDS_BLIT; 878 } 879 880 static f32 param = 0; 881 static f32 p_scale = 1; 882 param += p_scale * 0.005 * t->gl.dt; 883 if (param > 1.0f || param < 0) 884 p_scale *= -1.0f; 885 886 glBindFramebuffer(GL_FRAMEBUFFER, 0); 887 888 clear_colour(t->gl.mode & WIN_MODE_REVERSE); 889 glUseProgram(t->gl.programs[SHADER_POST]); 890 glUniform1i(t->gl.post.texslot, t->gl.fb_tex_unit); 891 glUniform1f(t->gl.post.param, param); 892 glUniform2fv(t->gl.post.vertscale, 1, (f32 []){ws.w, ws.h}); 893 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 894 895 last_frame_time = (f32)glfwGetTime() - frame_start_time; 896 }