Commit: 01f3c528dfe72fe533b81913b854e0b6a8ddd77f
Parent: 6a78f908852974118c8ac9e644da87bfbaccf866
Author: Randy Palamar
Date: Thu, 14 Nov 2024 06:25:37 -0700
split terminal interaction into a separate pass
Diffstat:
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];