vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: 01f3c528dfe72fe533b81913b854e0b6a8ddd77f
Parent: 6a78f908852974118c8ac9e644da87bfbaccf866
Author: Randy Palamar
Date:   Thu, 14 Nov 2024 06:25:37 -0700

split terminal interaction into a separate pass

Diffstat:
Mdebug.c | 3+++
Mplatform_linux_x11.c | 1+
Mutil.h | 40+++++++++++++++++++++++++++++++++++++---
Mvtgl.c | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mvtgl.h | 1+
5 files changed, 154 insertions(+), 51 deletions(-)

diff --git a/debug.c b/debug.c @@ -237,6 +237,9 @@ draw_debug_bar_chart(Term *t, DebugState *ds, TerminalInput *input, RenderCtx *r if (point_in_rect(input->mouse, r)) { hot_region = dr; hot_region_secs = cycs * cycs_to_secs; + /* TODO: actually use the interaction system; for now we + * just mask to prevent selection behind debug overlay */ + t->interaction.hot_interaction_state = IS_DEBUG; } pos.x += r.size.w; } diff --git a/platform_linux_x11.c b/platform_linux_x11.c @@ -264,6 +264,7 @@ update_input(PlatformCtx *ctx) ctx->input.executable_reloaded = 0; /* NOTE: mouse */ + input->last_mouse = input->mouse; input->mouse_scroll = (v2){0}; f64 mouse_x, mouse_y; diff --git a/util.h b/util.h @@ -411,7 +411,41 @@ typedef union { void *v; } Arg; -#define DOUBLE_CLICK_TIME 0.5f +enum variable_type { + VT_NULL, + VT_B32, + VT_RANGE, + VT_GROUP, +}; + +typedef struct { + enum variable_type type; + void *value; +} Variable; + + +#define INTERACTION_MULTI_CLICK_TIME 0.5f + +enum interaction_states { + IS_NONE, + IS_NOP, + IS_SET, + IS_DRAG, + + IS_DEBUG, + IS_TERM, +}; + +typedef struct { + Variable hot; + Variable next_hot; + Variable active; + u32 hot_interaction_state; + u32 click_count; + f32 multi_click_t; + enum interaction_states state; +} InteractionState; + enum selection_states { SS_NONE, SS_CHAR, @@ -421,8 +455,6 @@ enum selection_states { typedef struct { Range range; Range anchor; - v2 last_mouse; - f32 click_param; enum selection_states state; } Selection; @@ -467,6 +499,8 @@ typedef struct Term { Arena arena_for_frame; TempArena temp_arena; + InteractionState interaction; + Selection selection; Cursor cursor; Cursor saved_cursors[2]; diff --git a/vtgl.c b/vtgl.c @@ -642,48 +642,36 @@ stream_push_selection(Stream *s, TermView *tv, Range sel, u32 term_width) } static void -update_selection(Term *t, v2 mouse, ButtonState *mouse_left) +begin_selection(Term *t, u32 click_count, v2 mouse) { Selection *sel = &t->selection; + sel->state = CLAMP(click_count, SS_NONE, SS_WORDS); + sel->range.end = INVALID_RANGE_END; - if (mouse_left->transitions && mouse_left->ended_down) { - if (is_valid_range(sel->range)) - t->gl.flags |= UPDATE_RENDER_BUFFER; - - sel->range.end = INVALID_RANGE_END; - sel->last_mouse = mouse; - sel->click_param = DOUBLE_CLICK_TIME; + iv2 cell = mouse_to_cell_space(t, mouse); + if (t->views[t->view_idx].fb.rows[cell.y][cell.x].bg & ATTR_WDUMMY) { + ASSERT(t->views[t->view_idx].fb.rows[cell.y][cell.x - 1].bg & ATTR_WIDE); + cell.x--; + } - if (sel->state != SS_WORDS) - sel->state++; + if (sel->state == SS_WORDS) sel->anchor = sel->range = get_word_around_cell(t, cell); + else sel->anchor = (Range){.start = cell, .end = cell}; - iv2 cell = mouse_to_cell_space(t, sel->last_mouse); - if (t->views[t->view_idx].fb.rows[cell.y][cell.x].bg & ATTR_WDUMMY) { - ASSERT(t->views[t->view_idx].fb.rows[cell.y][cell.x - 1].bg & ATTR_WIDE); - cell.x--; - } - - if (sel->state == SS_WORDS) sel->anchor = get_word_around_cell(t, cell); - else sel->anchor = (Range){.start = cell, .end = cell}; + t->gl.flags |= UPDATE_RENDER_BUFFER; +} +static void +update_selection(Term *t, TerminalInput *input) +{ + if (!input->keys[MOUSE_LEFT].ended_down) return; - } - sel->click_param -= dt_for_frame; - if (sel->click_param < 0) { - sel->click_param = 0; - if (!mouse_left->ended_down) - sel->state = SS_NONE; - } - - if (!mouse_left->ended_down) + if (input->mouse.x == input->last_mouse.x && input->mouse.y == input->last_mouse.y) return; - if (sel->state != SS_WORDS && (mouse.x == sel->last_mouse.x && mouse.y == sel->last_mouse.y)) - return; - sel->last_mouse = mouse; + Selection *sel = &t->selection; + iv2 new_p = mouse_to_cell_space(t, input->mouse); - iv2 new_p = mouse_to_cell_space(t, mouse); if (t->views[t->view_idx].fb.rows[new_p.y][new_p.x].bg & ATTR_WDUMMY) { ASSERT(t->views[t->view_idx].fb.rows[new_p.y][new_p.x - 1].bg & ATTR_WIDE); new_p.x--; @@ -691,7 +679,7 @@ update_selection(Term *t, v2 mouse, ButtonState *mouse_left) if (sel->state != SS_WORDS) { sel->range.start = sel->anchor.start; - sel->range.end = new_p; + sel->range.end = new_p; } else { Range word = get_word_around_cell(t, new_p); if (sel->anchor.start.y < word.start.y) { @@ -877,9 +865,6 @@ key_callback(GLFWwindow *win, i32 key, i32 sc, i32 act, i32 mods) static void handle_keybindings(Term *t, TerminalInput *input, PlatformAPI *platform) { - /* TODO: map other mouse buttons */ - update_selection(t, input->mouse, input->keys + MOUSE_LEFT); - GLFWwindow *win = t->gl.window; if (input->mouse_scroll.y) { if (t->mode & TM_ALTSCREEN) { @@ -901,9 +886,77 @@ handle_keybindings(Term *t, TerminalInput *input, PlatformAPI *platform) scroll(t, platform, a); } } +} + +static void +begin_interaction(InteractionState *is, TerminalInput *input) +{ + is->click_count++; + if (is->multi_click_t < 0) + is->multi_click_t = INTERACTION_MULTI_CLICK_TIME; + + if (is->hot.value) { + if (is->hot_interaction_state) { + is->state = is->hot_interaction_state; + } else { + //switch (is->hot.type) { + // /* TODO: start the interaction */ + //} + } + + if (is->state != IS_NONE) + is->active = is->hot; + + if (is->state == IS_TERM) + begin_selection(is->active.value, is->click_count, input->mouse); + } else { + is->state = IS_NOP; + } +} - if (pressed_last_frame(input->keys + MOUSE_MIDDLE)) - paste(t, platform, (Arg){.i = CLIPBOARD_1}); +static void +end_interaction(InteractionState *is) +{ + /* TODO: store the value */ + is->state = IS_NONE; + is->active = (Variable){0}; +} + +static void +handle_interactions(Term *t, TerminalInput *input, PlatformAPI *platform) +{ + InteractionState *is = &t->interaction; + + is->multi_click_t -= dt_for_frame; + + ButtonState *mouse_left = input->keys + MOUSE_LEFT; + if (is->state != IS_NONE) { + if (pressed_last_frame(mouse_left)) { + end_interaction(is); + begin_interaction(is, input); + } + + if (!mouse_left->ended_down && is->multi_click_t < 0) { + is->click_count = 0; + } + + switch (is->state) { + case IS_NONE: break; + case IS_NOP: break; + case IS_SET: end_interaction(is); break; + case IS_DRAG: /* TODO */ break; + case IS_TERM: { + update_selection(t, input); + /* TODO: mouse scroll also goes here */ + if (pressed_last_frame(input->keys + MOUSE_MIDDLE)) + paste(t, platform, (Arg){.i = CLIPBOARD_1}); + } break; + case IS_DEBUG: /* TODO */ break; + } + } else { + is->state = is->hot_interaction_state; + is->hot = is->next_hot; + } } static void @@ -1112,6 +1165,28 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) if (t->gl.flags & NEEDS_RESIZE) resize(t, input->window_size); + + /* NOTE: handle this stuff first since it is based on what the user saw last frame */ + BEGIN_NAMED_BLOCK(mouse_and_keyboard_input); + if (input->character_input.len) { + if (t->scroll_offset) { + t->scroll_offset = 0; + t->gl.flags |= NEEDS_FULL_REFILL; + } + memory->platform_api.write(t->child, input->character_input, 0); + } + handle_keybindings(t, input, &memory->platform_api); + + /* NOTE: do this after the keybinds so that the user can rebind mouse buttons */ + handle_interactions(t, input, &memory->platform_api); + + /* NOTE: default state which can be overwritten later in the frame */ + /* TODO: if (!t->ui_active) */ + t->interaction.hot_interaction_state = IS_TERM; + t->interaction.next_hot = (Variable){.value = t}; + + END_NAMED_BLOCK(mouse_and_keyboard_input); + /* NOTE: this needs to be bound for blitting lines because that function can * access the font cache. * TODO: cleanup */ @@ -1138,17 +1213,6 @@ DEBUG_EXPORT VTGL_FRAME_STEP_FN(vtgl_frame_step) END_NAMED_BLOCK(input_from_child); - BEGIN_NAMED_BLOCK(mouse_and_keyboard_input); - if (input->character_input.len) { - if (t->scroll_offset) { - t->scroll_offset = 0; - t->gl.flags |= NEEDS_FULL_REFILL; - } - memory->platform_api.write(t->child, input->character_input, 0); - } - handle_keybindings(t, input, &memory->platform_api); - END_NAMED_BLOCK(mouse_and_keyboard_input); - if (t->gl.flags & (NEEDS_REFILL|NEEDS_FULL_REFILL)) { blit_lines(t, t->arena_for_frame, 0); t->gl.flags |= UPDATE_RENDER_BUFFER; diff --git a/vtgl.h b/vtgl.h @@ -78,6 +78,7 @@ typedef struct TerminalInput { iv2 window_size; v2 mouse; + v2 last_mouse; v2 mouse_scroll; ButtonState keys[INPUT_KEY_COUNT];