Commit: f46fdce43af7076fd42711e2ce42bb4f03fb188e
Parent: 9cd994eee2ed8eddabc4a9d9a2dc69f5df404207
Author: Randy Palamar
Date: Tue, 20 Aug 2024 22:57:11 -0600
basic mouse selection and copying
Word selection still to come!
Diffstat:
M | main.c | | | 10 | ++++++---- |
M | terminal.c | | | 4 | ++-- |
M | util.h | | | 24 | +++++++++++++++++++++--- |
M | vtgl.c | | | 195 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- |
4 files changed, 208 insertions(+), 25 deletions(-)
diff --git a/main.c b/main.c
@@ -16,7 +16,7 @@ static void do_debug(GLCtx *gl) { }
static char *libname = "./vtgl.so";
static void *libhandle;
-typedef void do_terminal_fn(Term *, Arena);
+typedef void do_terminal_fn(Term *);
static do_terminal_fn *do_terminal;
typedef void init_callbacks_fn(GLCtx *);
@@ -298,11 +298,13 @@ main(void)
check_shaders(&term.gl, memory);
f32 current_time = (f32)glfwGetTime();
- term.gl.dt = current_time - last_time;
- last_time = current_time;
+ term.gl.dt = current_time - last_time;
+ last_time = current_time;
+
+ term.arena_for_frame = memory;
glfwPollEvents();
- do_terminal(&term, memory);
+ do_terminal(&term);
glfwSwapBuffers(term.gl.window);
}
diff --git a/terminal.c b/terminal.c
@@ -257,7 +257,7 @@ dump_csi(CSI *csi)
static void
erase_in_display(Term *t, CSI *csi)
{
- uv2 cpos = t->cursor.pos;
+ iv2 cpos = t->cursor.pos;
switch (csi->argv[0]) {
case 0: /* Erase Below (default) */
fb_clear_region(t, cpos.y, cpos.y, cpos.x, t->size.w);
@@ -283,7 +283,7 @@ erase_in_display(Term *t, CSI *csi)
static void
erase_in_line(Term *t, CSI *csi)
{
- uv2 cpos = t->cursor.pos;
+ iv2 cpos = t->cursor.pos;
switch (csi->argv[0]) {
case 0: /* Erase to Right */
fb_clear_region(t, cpos.y, cpos.y, cpos.x, t->size.w);
diff --git a/util.h b/util.h
@@ -29,13 +29,14 @@
#define MAX(a, b) ((a) >= (b) ? (a) : (b))
#define CLAMP(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x))
+#define ISSPACE(c) ((c) == ' ' || (c) == '\n' || (c) == '\t')
#define ISPRINT(c) BETWEEN((c), ' ', '~')
/* NOTE: GLFW does not sequentially number keys so switch statement will never be optimized */
#define ENCODE_KEY(action, mod, key) (((action) << 24) | ((mod) << 16) | ((key) & 0xFFFF))
#define BACKLOG_SIZE (16 * MEGABYTE)
-#define BACKLOG_LINES (1024UL)
+#define BACKLOG_LINES (8192UL)
#define ALT_BACKLOG_SIZE (2 * MEGABYTE)
#define ALT_BACKLOG_LINES (1024UL)
@@ -108,7 +109,7 @@ typedef struct {
} Cell;
typedef struct {
- uv2 pos;
+ iv2 pos;
CellStyle state;
} Cursor;
@@ -155,7 +156,7 @@ typedef struct {
* a state machine. Any time a line hasn't played to completion we must
* restart it from the original location lest it unintentionally cause a
* screen scroll. */
- uv2 last_cursor_pos;
+ iv2 last_cursor_pos;
size last_line_idx;
} TermView;
@@ -278,6 +279,20 @@ typedef union {
void *v;
} Arg;
+#define DOUBLE_CLICK_TIME 0.5f
+enum selection_states {
+ SS_NONE,
+ SS_CHAR,
+ SS_WORDS,
+};
+
+typedef struct {
+ iv2 start, end;
+ v2 mouse_start;
+ f32 click_param;
+ enum selection_states state;
+} Selection;
+
enum terminal_mode {
TM_ALTSCREEN = 1 << 0,
TM_REPLACE = 1 << 1,
@@ -287,6 +302,9 @@ typedef struct {
GLCtx gl;
FontAtlas fa;
+ Arena arena_for_frame;
+
+ Selection selection;
Cursor cursor;
Cursor saved_cursors[2];
TermView views[2];
diff --git a/vtgl.c b/vtgl.c
@@ -200,9 +200,9 @@ push_empty_cell_rect(RenderPushBuffer *rpb, Term *t, u32 minrow, u32 maxrow, u32
v2 cs = get_cell_size(t);
v2 size = {.x = (maxcol - mincol + 1) * cs.w, .y = (maxrow - minrow + 1) * cs.h};
- v2 pos = {.x = mincol * cs.w, .y = t->gl.window_size.h - cs.h * (minrow + 1)};
+ v2 pos = {.x = mincol * cs.w, .y = t->gl.window_size.h - cs.h * (maxrow + 1)};
- Colour colour = g_colours.data[g_colours.bgidx];
+ Colour colour = g_colours.data[g_colours.fgidx];
push_char(rpb, &t->gl, size, pos, (v2){0}, (uv2){.y = colour.rgba}, 0);
}
@@ -273,28 +273,163 @@ static void
render_framebuffer(Term *t, RenderPushBuffer *rpb)
{
v2 cs = get_cell_size(t);
- Rect cr = {.pos = {.y = t->gl.window_size.h - cs.h}, .size = cs};
TermView *tv = t->views + t->view_idx;
- for (u32 r = 0; r < t->size.h; r++) {
- for (u32 c = 0; c < t->size.w; c++) {
- push_cell(rpb, &t->gl, tv->fb.rows[r][c], cr, t->fa.deltay);
- cr.pos.x += cs.w;
+ {
+ Rect cr = {.pos = {.y = t->gl.window_size.h - cs.h}, .size = cs};
+ for (u32 r = 0; r < t->size.h; r++) {
+ for (u32 c = 0; c < t->size.w; c++) {
+ push_cell(rpb, &t->gl, tv->fb.rows[r][c], cr, t->fa.deltay);
+ cr.pos.x += cs.w;
+ }
+ cr.pos.x = 0;
+ cr.pos.y -= cs.h;
}
- cr.pos.x = 0;
- cr.pos.y -= cs.h;
}
/* NOTE: draw cursor */
/* TODO: hide cursor doesn't get reset properly */
//if (!(t->gl.mode & WIN_MODE_HIDECURSOR))
{
+ Rect cr = {
+ .pos = {
+ .x = cs.w * t->cursor.pos.x,
+ .y = t->gl.window_size.h - cs.h * (t->cursor.pos.y + 1),
+ },
+ .size = cs,
+ };
Cell cursor = tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x];
- cr.pos.y = t->gl.window_size.h - cs.h * (t->cursor.pos.y+ 1);
- cr.pos.x = t->cursor.pos.x* cs.w;
cursor.style.attr ^= ATTR_INVERSE;
push_cell(rpb, &t->gl, cursor, cr, t->fa.deltay);
}
+
+ /* NOTE: draw selection if active */
+ /* TODO: combine with original push_cell? */
+ if (t->selection.end.x != -1) {
+ iv2 curs = t->selection.start;
+ Rect cr = {
+ .pos = {
+ .x = cs.w * curs.x,
+ .y = t->gl.window_size.h - cs.h * (curs.y + 1),
+ },
+ .size = cs,
+ };
+ /* NOTE: do full rows first */
+ for (; curs.y < t->selection.end.y; curs.y++) {
+ for (; curs.x < t->size.w; curs.x++) {
+ Cell cell = tv->fb.rows[curs.y][curs.x];
+ cell.style.attr ^= ATTR_INVERSE;
+ push_cell(rpb, &t->gl, cell, cr, t->fa.deltay);
+ cr.pos.x += cs.w;
+ }
+ curs.x = 0;
+ cr.pos.x = 0;
+ cr.pos.y -= cs.h;
+ }
+ /* NOTE: do the last row */
+ for (; curs.x < t->selection.end.x; curs.x++) {
+ Cell cell = tv->fb.rows[curs.y][curs.x];
+ cell.style.attr ^= ATTR_INVERSE;
+ push_cell(rpb, &t->gl, cell, cr, t->fa.deltay);
+ cr.pos.x += cs.w;
+ }
+ }
+}
+
+static iv2
+mouse_to_cell_space(Term *t, v2 mouse)
+{
+ iv2 result = {0};
+ v2 cell_size = get_cell_size(t);
+
+ /* TODO: this needs to be adjusted for padding */
+ f32 delta_x = t->gl.window_size.x - t->size.w * cell_size.w;
+ f32 delta_y = t->gl.window_size.h - t->size.h * cell_size.h;
+
+ result.x = (i32)((mouse.x + delta_x) / cell_size.w);
+ result.y = (i32)((mouse.y + delta_y) / cell_size.h) - 1;
+
+ CLAMP(result.x, 0, t->size.w - 1);
+ CLAMP(result.y, 0, t->size.h - 1);
+
+ return result;
+}
+
+static void
+update_selection(Term *t)
+{
+ Selection *sel = &t->selection;
+ b32 held = glfwGetMouseButton(t->gl.window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
+
+ sel->click_param -= t->gl.dt;
+ if (sel->click_param < 0) {
+ sel->click_param = 0;
+ if (!held)
+ sel->state = SS_NONE;
+ }
+
+ if (!held)
+ return;
+
+ f64 xpos, ypos;
+ glfwGetCursorPos(t->gl.window, &xpos, &ypos);
+ v2 mouse = {.x = xpos, .y = ypos};
+
+ if (mouse.x == sel->mouse_start.x && mouse.y == sel->mouse_start.y)
+ return;
+
+ iv2 newp = mouse_to_cell_space(t, mouse);
+
+ if (0 && t->selection.state == SS_WORDS) {
+ /* TODO: word selection */
+ } else {
+ if (newp.x <= sel->start.x && newp.y <= sel->start.y) {
+ if (sel->end.x == -1)
+ sel->end = sel->start;
+ sel->start = newp;
+ } else {
+ sel->end = newp;
+ }
+ }
+}
+
+KEYBIND_FN(copy)
+{
+ if (t->selection.end.x == -1)
+ return 1;
+
+ TermView *tv = t->views + t->view_idx;
+ iv2 curs = t->selection.start;
+ i32 buf_curs = 0;
+
+ /* NOTE: super piggy but we are only holding onto it for the function duration */
+ Arena arena = t->arena_for_frame;
+ size buf_size = 1 * MEGABYTE;
+ char *buf = alloc(&arena, char, buf_size);
+
+ /* NOTE: do full rows first */
+ u32 last_non_space_idx = 0;
+ for (; curs.y < t->selection.end.y; curs.y++) {
+ for (; curs.x < t->size.w && buf_curs != buf_size; curs.x++) {
+ /* TODO: handle the utf8 case */
+ u32 cp = tv->fb.rows[curs.y][curs.x].cp;
+ if (!ISSPACE(cp)) last_non_space_idx = buf_curs;
+ buf[buf_curs++] = cp;
+ }
+ buf[last_non_space_idx + 1] = '\n';
+ buf_curs = last_non_space_idx + 2;
+ curs.x = 0;
+ }
+
+ /* NOTE: do the last row */
+ for (; curs.x < t->selection.end.x && buf_curs != buf_size; curs.x++)
+ buf[buf_curs++] = (char)tv->fb.rows[curs.y][curs.x].cp;
+
+ CLAMP(buf_curs, 0, buf_size - 1);
+ buf[buf_curs] = 0;
+ glfwSetClipboardString(0, buf);
+
+ return 1;
}
KEYBIND_FN(paste)
@@ -444,6 +579,30 @@ key_callback(GLFWwindow *win, i32 key, i32 sc, i32 act, i32 mods)
}
static void
+mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod)
+{
+ Term *t = glfwGetWindowUserPointer(win);
+ /* TODO: map other mouse buttons */
+ if (btn != GLFW_MOUSE_BUTTON_LEFT)
+ return;
+
+ if (act == GLFW_RELEASE)
+ return;
+
+ f64 xpos, ypos;
+ glfwGetCursorPos(win, &xpos, &ypos);
+ t->selection.end = (iv2){.x = -1, .y = -1};
+ t->selection.mouse_start = (v2){.x = xpos, .y = ypos};
+ t->selection.click_param = DOUBLE_CLICK_TIME;
+
+ if (t->selection.state != SS_WORDS)
+ t->selection.state++;
+
+ iv2 cell = mouse_to_cell_space(t, t->selection.mouse_start);
+ t->selection.start = cell;
+}
+
+static void
char_callback(GLFWwindow *win, u32 codepoint)
{
Term *t = glfwGetWindowUserPointer(win);
@@ -451,6 +610,7 @@ char_callback(GLFWwindow *win, u32 codepoint)
t->scroll_offset = 0;
t->gl.flags |= NEEDS_FULL_BLIT;
}
+ t->selection.end = (iv2){.x = -1, .y = -1};
os_child_put_char(t->child, codepoint);
}
@@ -497,12 +657,13 @@ init_callbacks(GLCtx *gl)
glfwSetCharCallback(gl->window, char_callback);
glfwSetFramebufferSizeCallback(gl->window, fb_callback);
glfwSetKeyCallback(gl->window, key_callback);
+ glfwSetMouseButtonCallback(gl->window, mouse_button_callback);
//glfwSetWindowRefreshCallback(gl->window, refresh_callback);
glfwSetScrollCallback(gl->window, scroll_callback);
}
DEBUG_EXPORT void
-do_terminal(Term *t, Arena a)
+do_terminal(Term *t)
{
static f32 last_frame_time;
f32 frame_start_time = (f32)glfwGetTime();
@@ -534,14 +695,16 @@ do_terminal(Term *t, Arena a)
}
if (t->gl.flags & (NEEDS_BLIT|NEEDS_FULL_BLIT))
- blit_lines(t, a, parsed_lines);
+ blit_lines(t, t->arena_for_frame, parsed_lines);
+
+ update_selection(t);
/* NOTE: reset the camera/viewport */
glUseProgram(t->gl.programs[SHADER_RENDER]);
glUniform1i(t->gl.render.texslot, 0);
glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb);
- RenderPushBuffer *rpb = alloc(&a, RenderPushBuffer, 1);
+ RenderPushBuffer *rpb = alloc(&t->arena_for_frame, RenderPushBuffer, 1);
clear_colour();
render_framebuffer(t, rpb);
@@ -560,8 +723,8 @@ do_terminal(Term *t, Arena a)
src_bl.y = cursor_pos.y;
}
- {
- s8 fps = s8alloc(&a, 64);
+ if (0) {
+ s8 fps = s8alloc(&t->arena_for_frame, 64);
fps.len = snprintf((char *)fps.data, fps.len, "Render Time: %0.02f ms/f", last_frame_time * 1e3);
v2 ts = measure_text(&t->gl, fps, 1);
v2 pos = {