From 2f39fb2807f668bb17d73669d25fe8891a659877 Mon Sep 17 00:00:00 2001 From: jacob Date: Sun, 29 Mar 2026 01:05:17 -0500 Subject: [PATCH] functional utf-8 textbox navigation --- src/pp/pp_vis/pp_vis_core.c | 152 ++++++++++++------------------------ src/pp/pp_vis/pp_vis_core.h | 6 +- 2 files changed, 55 insertions(+), 103 deletions(-) diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 33c21efc..b7dec5fe 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -274,15 +274,6 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) { V_TextboxDelta delta = tbd_node->delta; - // if (!(delta.flags & V_TextboxDeltaFlag_DontUpdateCursor)) - // { - // tb->start = ClampI64(delta.start, 0, tb->len); - // tb->end = ClampI64(delta.end, 0, tb->len); - // } - - - - // Navigate { b32 navigated = 0; @@ -304,14 +295,14 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) } else if (delta.flags & V_TextboxDeltaFlag_NavWord) { - if (tb->end > 0 && tb->text[tb->end - 1] == ' ') + if (tb->end > 0 && tb->text32[tb->end - 1] == ' ') { - for (i64 pos = tb->end - 1; pos >= 0 && tb->text[pos] == ' '; --pos) + for (i64 pos = tb->end - 1; pos >= 0 && tb->text32[pos] == ' '; --pos) { tb->end = pos; } } - for (i64 pos = tb->end - 1; pos >= 0 && tb->text[pos] != ' '; --pos) + for (i64 pos = tb->end - 1; pos >= 0 && tb->text32[pos] != ' '; --pos) { tb->end = pos; } @@ -319,11 +310,6 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) else { tb->end -= 1; - // Skip utf-8 continuations - // for (i64 pos = tb->end - 1; pos >= 0 && (tb->text[pos] & 0xC0) == 0x80; --pos) - // { - // tb->end = pos; - // } } } if (delta.flags & V_TextboxDeltaFlag_NavRight) @@ -336,14 +322,14 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) } else if (delta.flags & V_TextboxDeltaFlag_NavWord) { - if (tb->end < tb->len && tb->text[tb->end] == ' ') + if (tb->end < tb->len && tb->text32[tb->end] == ' ') { - for (i64 pos = tb->end; pos < tb->len && tb->text[pos] == ' '; ++pos) + for (i64 pos = tb->end; pos < tb->len && tb->text32[pos] == ' '; ++pos) { tb->end = pos + 1; } } - for (i64 pos = tb->end; pos < tb->len && tb->text[pos] != ' '; ++pos) + for (i64 pos = tb->end; pos < tb->len && tb->text32[pos] != ' '; ++pos) { tb->end = pos + 1; } @@ -351,11 +337,6 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) else { tb->end += 1; - // Skip utf-8 continuations - // for (i64 pos = tb->end; pos < tb->len && (tb->text[pos] & 0xC0) == 0x80; ++pos) - // { - // tb->end = pos + 1; - // } } } } @@ -372,6 +353,8 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) delta.text = PLT_GetClipboardText(scratch.arena); } + String32 delta_text32 = String32FromString(scratch.arena, delta.text); + // Update text if (delta.flags & V_TextboxDeltaFlag_UpdateText) { @@ -386,7 +369,7 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) for (i64 src_idx = move.min; src_idx < move.max; ++src_idx) { i64 dst_idx = src_idx - move_stride; - tb->text[dst_idx] = tb->text[src_idx]; + tb->text32[dst_idx] = tb->text32[src_idx]; } } @@ -395,72 +378,41 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) tb->start = tb->end = remove.min; } - - - // Insert new text + if (tb->len < countof(tb->text32)) { - // FIXME: Truncate - i64 insert_pos = tb->end; - i64 insert_len = MinI64(delta.text.len, countof(tb->text) - insert_pos); - if (insert_len > 0) + i64 replace_pos = tb->end; + i64 replace_len = MinI64(delta_text32.len, countof(tb->text32) - tb->len); + RngI64 move_dst = Zi; + move_dst.min = replace_pos + replace_len; + move_dst.max = move_dst.min + (tb->len - replace_pos); + move_dst.min = ClampI64(move_dst.min, 0, countof(tb->text32)); + move_dst.max = ClampI64(move_dst.max, move_dst.min, countof(tb->text32)); + if (replace_len > 0) { - RngI64 move_dst = Zi; - move_dst.min = insert_pos + insert_len; - move_dst.max = move_dst.min + (tb->len - insert_pos); - move_dst.min = ClampI64(move_dst.min, 0, countof(tb->text)); - move_dst.max = ClampI64(move_dst.max, move_dst.min, countof(tb->text)); - i64 move_stride = insert_len; + i64 move_stride = replace_len; for (i64 dst_idx = move_dst.max - 1; dst_idx >= move_dst.min; --dst_idx) { i64 src_idx = dst_idx - move_stride; - tb->text[dst_idx] = tb->text[src_idx]; + tb->text32[dst_idx] = tb->text32[src_idx]; } - for (i64 src_idx = 0; src_idx < insert_len; ++src_idx) + for (i64 src_idx = 0; src_idx < replace_len; ++src_idx) { - i64 dst_idx = insert_pos + src_idx; - tb->text[dst_idx] = delta.text.text[src_idx]; + i64 dst_idx = replace_pos + src_idx; + tb->text32[dst_idx] = delta_text32.text[src_idx]; } } - tb->start = tb->end = insert_pos + insert_len; - tb->len += insert_len; + tb->start = tb->end = replace_pos + replace_len; + tb->len = MaxI64(replace_pos + replace_len, move_dst.max); } - - - - // // Insert new text - // if (delta.text.len > 0) - // { - // i64 insert_len = ClampI64(delta.text.len, 0, countof(tb->text) - replace.min); - - // // Make room - // // FIXME: Truncate - // i64 move_len = ClampI64(tb->len - replace.min, 0, countof(tb->text) - replace.min); - // for (i64 src_pos = replace.min; src_pos < (replace.min + move_len); ++src_pos) - // { - // i64 dst_pos = src_pos + move_len; - // tb->text[dst_pos] = tb->text[src_pos]; - // } - - // // Copy from delta - // for (i64 src_pos = 0; src_pos < insert_len; ++src_pos) - // { - // i64 dst_pos = replace.min + src_pos; - // tb->text[dst_pos] = delta.text.text[src_pos]; - // } - // tb->len += insert_len; - // } - - // tb->end = ClampI64(tb->end, 0, tb->len); - // tb->start = tb->end; } if (delta.flags & V_TextboxDeltaFlag_CopySelectionToClipboard) { - String selection = V_StringFromTextboxSelection(tb); + String selection = V_StringFromTextboxSelection(scratch.arena, tb); if (selection.len > 0) { PLT_SetClipboardText(selection); @@ -504,9 +456,9 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) // } // // Insert new text - // if (delta.text.len > 0) + // if (delta_text32.len > 0) // { - // i64 insert_len = ClampI64(delta.text.len, 0, countof(tb->text) - replace.min); + // i64 insert_len = ClampI64(delta_text32.len, 0, countof(tb->text) - replace.min); // // Make room // // FIXME: Truncate @@ -521,7 +473,7 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) // for (i64 src_pos = 0; src_pos < insert_len; ++src_pos) // { // i64 dst_pos = replace.min + src_pos; - // tb->text[dst_pos] = delta.text.text[src_pos]; + // tb->text[dst_pos] = delta_text32.text[src_pos]; // } // tb->len += insert_len; // } @@ -594,16 +546,16 @@ void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas) EndScratch(scratch); } -String V_StringFromTextbox(V_TextboxState *tb) +String V_StringFromTextbox(Arena *arena, V_TextboxState *tb) { - return STRING(tb->len, tb->text); + return StringFromString32(arena, (String32) { .len = tb->len, .text = tb->text32 }); } -String V_StringFromTextboxSelection(V_TextboxState *tb) +String V_StringFromTextboxSelection(Arena *arena, V_TextboxState *tb) { i64 min = MinI64(tb->start, tb->end); i64 max = MaxI64(tb->start, tb->end); - return STRING(max - min, tb->text + min); + return StringFromString32(arena, (String32) { .len = max - min, .text = tb->text32 + min }); } //////////////////////////////////////////////////////////// @@ -1186,15 +1138,15 @@ void V_TickForever(WaveLaneCtx *lane) //- Convert events into cmds for (u64 cev_idx = 0; cev_idx < window_frame.controller_events.count; ++cev_idx) { - ControllerEvent cev = window_frame.controller_events.events[cev_idx]; - b32 down = cev.kind == ControllerEventKind_ButtonDown; - b32 up = cev.kind == ControllerEventKind_ButtonUp; - String text = cev.kind == ControllerEventKind_Text ? STRING(cev.text_chars_count, cev.text_chars) : STRING(0, 0); + ControllerEvent *cev = &window_frame.controller_events.events[cev_idx]; + b32 down = cev->kind == ControllerEventKind_ButtonDown; + b32 up = cev->kind == ControllerEventKind_ButtonUp; + String text = cev->kind == ControllerEventKind_Text ? STRING(cev->text_chars_count, cev->text_chars) : STRING(0, 0); b32 ignore = 0; if (down) { - if (cev.button == Button_M1 || cev.button == Button_M2 || cev.button == Button_M3) + if (cev->button == Button_M1 || cev->button == Button_M2 || cev->button == Button_M3) { if (!frame->has_mouse_focus) { @@ -1213,7 +1165,7 @@ void V_TickForever(WaveLaneCtx *lane) if (!ignore) { V_Hotkey hotkey = Zi; - hotkey.button = cev.button; + hotkey.button = cev->button; hotkey.ctrl = frame->held_buttons[Button_Ctrl]; hotkey.shift = frame->held_buttons[Button_Shift]; hotkey.alt = frame->held_buttons[Button_Alt]; @@ -1237,43 +1189,43 @@ void V_TickForever(WaveLaneCtx *lane) // Generate text input delta V_TextboxDelta delta = Zi; { - if (down && cev.button == Button_Left) + if (down && cev->button == Button_Left) { delta.flags |= V_TextboxDeltaFlag_NavLeft; } - if (down && cev.button == Button_Right) + if (down && cev->button == Button_Right) { delta.flags |= V_TextboxDeltaFlag_NavRight; } - if (down && cev.button == Button_Home) + if (down && cev->button == Button_Home) { delta.flags |= V_TextboxDeltaFlag_NavLeft | V_TextboxDeltaFlag_NavLine; } - if (down && cev.button == Button_End) + if (down && cev->button == Button_End) { delta.flags |= V_TextboxDeltaFlag_NavRight | V_TextboxDeltaFlag_NavLine; } - if (down && cev.button == Button_A && frame->held_buttons[Button_Ctrl]) + if (down && cev->button == Button_A && frame->held_buttons[Button_Ctrl]) { delta.flags |= V_TextboxDeltaFlag_NavSelect | V_TextboxDeltaFlag_NavDirect; delta.direct_start = 0; delta.direct_end = V_MaxTextboxLen; } - if (down && cev.button == Button_Backspace) + if (down && cev->button == Button_Backspace) { delta.text = Lit(""); delta.flags |= V_TextboxDeltaFlag_OnlyNavIfSelectionEmpty | V_TextboxDeltaFlag_NavSelect | V_TextboxDeltaFlag_NavLeft | V_TextboxDeltaFlag_UpdateText; } - if (down && cev.button == Button_Delete) + if (down && cev->button == Button_Delete) { delta.text = Lit(""); delta.flags |= V_TextboxDeltaFlag_OnlyNavIfSelectionEmpty | V_TextboxDeltaFlag_NavSelect | V_TextboxDeltaFlag_NavRight | V_TextboxDeltaFlag_UpdateText; } - if (down && cev.button == Button_C && frame->held_buttons[Button_Ctrl]) + if (down && cev->button == Button_C && frame->held_buttons[Button_Ctrl]) { delta.flags |= V_TextboxDeltaFlag_CopySelectionToClipboard; } - else if (down && cev.button == Button_V && frame->held_buttons[Button_Ctrl]) + else if (down && cev->button == Button_V && frame->held_buttons[Button_Ctrl]) { delta.flags |= V_TextboxDeltaFlag_InheritTextFromClipboard | V_TextboxDeltaFlag_UpdateText; } @@ -1309,10 +1261,10 @@ void V_TickForever(WaveLaneCtx *lane) //- Compute mouse delta from events for (u64 cev_idx = 0; cev_idx < window_frame.controller_events.count; ++cev_idx) { - ControllerEvent cev = window_frame.controller_events.events[cev_idx]; - if (cev.kind == ControllerEventKind_MouseMove) + ControllerEvent *cev = &window_frame.controller_events.events[cev_idx]; + if (cev->kind == ControllerEventKind_MouseMove) { - mouse_delta = AddVec2(mouse_delta, cev.mouse_delta); + mouse_delta = AddVec2(mouse_delta, cev->mouse_delta); } } } @@ -4574,7 +4526,7 @@ void V_TickForever(WaveLaneCtx *lane) - search_text = V_StringFromTextbox(search_state); + search_text = V_StringFromTextbox(frame->arena, search_state); is_searching = search_text.len != 0; diff --git a/src/pp/pp_vis/pp_vis_core.h b/src/pp/pp_vis/pp_vis_core.h index 9a3b5e8e..0891ea80 100644 --- a/src/pp/pp_vis/pp_vis_core.h +++ b/src/pp/pp_vis/pp_vis_core.h @@ -197,7 +197,7 @@ Struct(V_TextboxDeltaList) Struct(V_TextboxState) { i64 len; - u8 text[V_MaxTextboxLen]; + u32 text32[V_MaxTextboxLen]; i64 start; i64 end; @@ -409,8 +409,8 @@ void V_DrawPoint(Vec2 p, Vec4 srgb); void V_ApplyTextboxDelta(V_TextboxState *tb, V_TextboxDelta delta); void V_ApplyTextboxDeltas(V_TextboxState *tb, V_TextboxDeltaList deltas); -String V_StringFromTextbox(V_TextboxState *tb); -String V_StringFromTextboxSelection(V_TextboxState *tb); +String V_StringFromTextbox(Arena *arena, V_TextboxState *tb); +String V_StringFromTextboxSelection(Arena *arena, V_TextboxState *tb); //////////////////////////////////////////////////////////// //~ Timeline helpers