summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-27 18:21:13 +0100
committersalmonik <salmonik@tuta.io>2026-10-27 18:21:13 +0100
commit304441c71997ee61e618146f4d4302c6d2b952ad (patch)
treee7a00d279b257f88e7a590da59023533cae6ae80
parentGUI: allow movement while open; freeze look and block bind capture (diff)
downloadhl2h-304441c71997ee61e618146f4d4302c6d2b952ad.tar.gz
hl2h-304441c71997ee61e618146f4d4302c6d2b952ad.zip
GUI: Insert-only close, freeze mouse look at the source
Feedback pass: - Insert now toggles the GUI; the old text tree moved to F1. - Remove the title-bar X. Insert is the only close path; Esc only cancels bind capture. - Discard accumulated mouse deltas in CInput::CreateMove before the game can apply them (CInput::ClearStates), so the camera no longer moves for a frame and snaps back. Also skip ExtraMouseSample and zero relative mouse state while the GUI is open. - Strip attack/use/impulse/weapon selection from the command while open; keep jump, duck and walk. Skip combat command edits (spread seed) too. - Widen System page buttons so "Reload config" and "Reset defaults" fit. - Update CONTEXT/ADR for the Insert-only close behavior.
Diffstat (limited to '')
-rw-r--r--CONTEXT.md17
-rw-r--r--docs/adr/0001-custom-isurface-gui.md6
-rw-r--r--src/cinput.cpp38
-rw-r--r--src/gui.cpp71
-rw-r--r--src/hl2h.cpp3
-rw-r--r--src/hl2h.h3
-rw-r--r--src/menu.cpp9
7 files changed, 110 insertions, 37 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 53c662e..6f6e59e 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -94,14 +94,14 @@ Relevant vtable slots (this build):
94 94
95## Menu 95## Menu
96 96
97Starts fully collapsed on first open. `Insert` toggles; `Up`/`Down` navigate; 97Preview key: `F1` (the final GUI will replace this tree). Starts fully collapsed on first open. `F1` toggles; `Up`/`Down` navigate;
98`Enter` expands/toggles; `Left`/`Right` edit values; `Escape` closes. 98`Enter` expands/toggles; `Left`/`Right` edit values; `Escape` closes.
99Bind rows enter bind-select mode: press a key/button to assign, `Escape` sets 99Bind rows enter bind-select mode: press a key/button to assign, `Escape` sets
100None (always-on), `Enter` cancels. 100None (always-on), `Enter` cancels.
101 101
102> The text menu below is being replaced by the GUI described in the next 102> The text menu below is being replaced by the GUI described in the next
103> section. The old tree is kept only until the GUI reaches parity. The current 103> section. The old tree is kept only until the GUI reaches parity. During the
104> preview shell opens with F2; Insert still opens the text tree. 104> preview, **Insert** toggles the GUI and the old text tree is on **F1**.
105 105
106``` 106```
107HL2hake 107HL2hake
@@ -149,10 +149,11 @@ Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604.
149- **Card**: per-feature panel. Header always shows Active + Bind; body is a 149- **Card**: per-feature panel. Header always shows Active + Bind; body is a
150 collapsible set of settings. 150 collapsible set of settings.
151- **Freeze**: while the GUI is open, look/attack/use/impulse/weapon selection 151- **Freeze**: while the GUI is open, look/attack/use/impulse/weapon selection
152 are blocked and combat features are skipped, but normal WASD/jump/duck 152 are blocked and combat features are skipped, but normal WASD/jump/duck/walk
153 movement is allowed. Bind capture blocks all game input until it ends. The 153 movement is allowed. Mouse deltas are discarded before `CInput::CreateMove`
154 game keeps simulating and ESP/FOV circle/speed graph keep drawing. No 154 can apply them (`CInput::ClearStates`), so the view cannot drift for a frame.
155 invulnerability. 155 Bind capture blocks all game input until it ends. The game keeps simulating
156 and ESP/FOV circle/speed graph keep drawing. No invulnerability.
156- **Native cursor**: on open, leave SDL relative-mouse mode and show the OS 157- **Native cursor**: on open, leave SDL relative-mouse mode and show the OS
157 cursor; restore on close. Absolute mouse coordinates are used. 158 cursor; restore on close. Absolute mouse coordinates are used.
158- **Bind capture**: click Bind, press a key/button; Esc cancels capture but 159- **Bind capture**: click Bind, press a key/button; Esc cancels capture but
@@ -165,7 +166,7 @@ Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604.
165 that restores feature settings/binds but keeps GUI layout. 166 that restores feature settings/binds but keeps GUI layout.
166- Window: fixed-size, draggable by title bar, clamped to screen, position, 167- Window: fixed-size, draggable by title bar, clamped to screen, position,
167 category and expanded cards persisted under `[gui]`. Esc does not close the 168 category and expanded cards persisted under `[gui]`. Esc does not close the
168 GUI; the toggle key or title-bar X does. 169 GUI; the toggle key (Insert) is the only close path. There is no title-bar X.
169 170
170 171
171## Bones / ESP 172## Bones / ESP
diff --git a/docs/adr/0001-custom-isurface-gui.md b/docs/adr/0001-custom-isurface-gui.md
index 9595ca8..5525844 100644
--- a/docs/adr/0001-custom-isurface-gui.md
+++ b/docs/adr/0001-custom-isurface-gui.md
@@ -54,6 +54,6 @@ path.
54- The GUI remains inside the engine's VGUI paint path, so it inherits the 54- The GUI remains inside the engine's VGUI paint path, so it inherits the
55 existing font registration and in-game overlay behavior. 55 existing font registration and in-game overlay behavior.
56- The old text menu is removed once the GUI reaches feature parity. During 56- The old text menu is removed once the GUI reaches feature parity. During
57 development the GUI is built alongside the text menu and opened with a 57 development the GUI is built alongside the text menu; Insert toggles the
58 temporary F2 key; the replacement is complete only when the old tree is 58 GUI and F1 keeps the old tree accessible. The replacement is complete only
59 deleted. 59 when the old tree is deleted.
diff --git a/src/cinput.cpp b/src/cinput.cpp
index a75cc4f..ad7face 100644
--- a/src/cinput.cpp
+++ b/src/cinput.cpp
@@ -85,12 +85,42 @@ void fix_verified_crc(int seq, uint32_t crc) {
85 85
86using InputCreateMoveFn = void (*)(void *, int, float, bool); 86using InputCreateMoveFn = void (*)(void *, int, float, bool);
87static InputCreateMoveFn oInputCreateMove = nullptr; 87static InputCreateMoveFn oInputCreateMove = nullptr;
88using InputExtraMouseFn = void (*)(void *, float, bool);
89static InputExtraMouseFn oInputExtraMouseSample = nullptr;
90
91// ExtraMouseSample runs AdjustAngles at render-frame rate, between movement
92// commands. While the GUI is open it must not touch the view; movement still
93// comes through CreateMove.
94static void hkdInputExtraMouseSample(void *self, float frametime, bool active) {
95 if (gui_is_open())
96 return;
97 if (oInputExtraMouseSample)
98 oInputExtraMouseSample(self, frametime, active);
99}
100
101void input_clear_mouse_states() {
102 if (!g_input)
103 return;
104 // CInput::ClearStates (IInput slot 20): zeros both the software mouse
105 // accumulator and the engine raw-input accumulator.
106 using Fn = void (*)(void *);
107 vfunc<Fn>((void *)g_input, 20)((void *)g_input);
108}
88 109
89static void hkdInputCreateMove(void *self, int seq, float frametime, 110static void hkdInputCreateMove(void *self, int seq, float frametime,
90 bool active) { 111 bool active) {
112 // Mouse deltas are accumulated between ticks. With the custom GUI open we
113 // must discard them before CInput::CreateMove applies mouse look, otherwise
114 // the view moves for one frame and snaps back when do_move freezes it.
115 if (gui_is_open())
116 input_clear_mouse_states();
91 if (oInputCreateMove) 117 if (oInputCreateMove)
92 oInputCreateMove(self, seq, frametime, active); 118 oInputCreateMove(self, seq, frametime, active);
93 119
120 // No combat features may modify the command while the GUI is open.
121 if (gui_is_open())
122 return;
123
94 CUserCmd *cmd = get_usercmd(seq); 124 CUserCmd *cmd = get_usercmd(seq);
95 if (!cmd) 125 if (!cmd)
96 return; 126 return;
@@ -125,8 +155,14 @@ static bool install_input_hook() {
125 memcpy(nvt, vt, N * sizeof(void *)); 155 memcpy(nvt, vt, N * sizeof(void *));
126 oInputCreateMove = (InputCreateMoveFn)vt[3]; 156 oInputCreateMove = (InputCreateMoveFn)vt[3];
127 nvt[3] = (void *)&hkdInputCreateMove; 157 nvt[3] = (void *)&hkdInputCreateMove;
158 if (N > 4) {
159 oInputExtraMouseSample = (InputExtraMouseFn)vt[4];
160 nvt[4] = (void *)&hkdInputExtraMouseSample;
161 }
128 VTABLE((void *)g_input) = nvt; 162 VTABLE((void *)g_input) = nvt;
129 logf("CInput CreateMove hooked: slot 3 orig=%p", (void *)oInputCreateMove); 163 logf("CInput CreateMove hooked: slot 3 orig=%p; ExtraMouseSample slot 4 "
164 "orig=%p",
165 (void *)oInputCreateMove, (void *)oInputExtraMouseSample);
130 return true; 166 return true;
131} 167}
132 168
diff --git a/src/gui.cpp b/src/gui.cpp
index 923cd4d..eb9062e 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -1,4 +1,4 @@
1// gui.cpp — custom immediate-mode GUI shell (temporary F2 preview) 1// gui.cpp — custom immediate-mode GUI shell (temporary Insert preview)
2// 2//
3// This is the first vertical slice: the visual shell, categories, cards, 3// This is the first vertical slice: the visual shell, categories, cards,
4// dummy controls, cursor handling and input freeze. It does not read or write 4// dummy controls, cursor handling and input freeze. It does not read or write
@@ -575,6 +575,7 @@ static void close_gui() {
575 g_dragging = false; 575 g_dragging = false;
576 g_scroll_drag = false; 576 g_scroll_drag = false;
577 g_slider_drag = -1; 577 g_slider_drag = -1;
578 input_clear_mouse_states();
578 if (g_sdl_ready) { 579 if (g_sdl_ready) {
579 if (sd_set_relative) 580 if (sd_set_relative)
580 sd_set_relative(g_prev_relative ? 1 : 0); 581 sd_set_relative(g_prev_relative ? 1 : 0);
@@ -611,6 +612,7 @@ static void open_gui() {
611 if (sd_show_cursor) 612 if (sd_show_cursor)
612 sd_show_cursor(1); 613 sd_show_cursor(1);
613 } 614 }
615 input_clear_mouse_states();
614 g_open = true; 616 g_open = true;
615 g_capture_card = -1; 617 g_capture_card = -1;
616 g_wheel.exchange(0); 618 g_wheel.exchange(0);
@@ -659,7 +661,7 @@ static void handle_capture(const unsigned char *keys) {
659 for (int k = 0; k < 512; k++) { 661 for (int k = 0; k < 512; k++) {
660 if (!keys[k] || g_prev_keys[k]) 662 if (!keys[k] || g_prev_keys[k])
661 continue; 663 continue;
662 if (k == 73 || k == 59) // Insert reserved; F2 closes the preview 664 if (k == 73) // Insert is reserved for opening/closing the GUI
663 continue; 665 continue;
664 g_cards[g_capture_card].bind = k; 666 g_cards[g_capture_card].bind = k;
665 g_capture_card = -1; 667 g_capture_card = -1;
@@ -674,12 +676,7 @@ static void handle_mouse_click() {
674 676
675 // title bar: drag or close 677 // title bar: drag or close
676 Rect title{g_win_x, g_win_y, g_win_w, S(38.0f)}; 678 Rect title{g_win_x, g_win_y, g_win_w, S(38.0f)};
677 Rect close{g_win_x + g_win_w - S(38.0f), g_win_y, S(38.0f), S(38.0f)};
678 if (title.has(mx, my)) { 679 if (title.has(mx, my)) {
679 if (close.has(mx, my)) {
680 close_gui();
681 return;
682 }
683 g_dragging = true; 680 g_dragging = true;
684 g_drag_dx = mx - g_win_x; 681 g_drag_dx = mx - g_win_x;
685 g_drag_dy = my - g_win_y; 682 g_drag_dy = my - g_win_y;
@@ -787,7 +784,7 @@ static void handle_mouse_click() {
787 return; 784 return;
788 } 785 }
789 if (ct.kind == CK_BUTTON) { 786 if (ct.kind == CK_BUTTON) {
790 Rect b{cr.x + cr.w - S(130.0f), by + S(3.0f), S(114.0f), 787 Rect b{cr.x + cr.w - S(176.0f), by + S(3.0f), S(160.0f),
791 S(26.0f)}; 788 S(26.0f)};
792 if (b.has(mx, my)) { 789 if (b.has(mx, my)) {
793 logf("gui: dummy action %s", ct.label ? ct.label : "?"); 790 logf("gui: dummy action %s", ct.label ? ct.label : "?");
@@ -858,8 +855,8 @@ void gui_handle_input() {
858 if (nk > 512) nk = 512; 855 if (nk > 512) nk = 512;
859 update_mouse(); 856 update_mouse();
860 857
861 bool f2 = (nk > 59) && keys[59]; 858 bool toggle_key = (nk > 73) && keys[73];
862 bool f2_edge = f2 && !g_prev_keys[59]; 859 bool toggle_edge = toggle_key && !g_prev_keys[73];
863 bool esc = (nk > 41) && keys[41]; 860 bool esc = (nk > 41) && keys[41];
864 bool esc_edge = esc && !g_prev_keys[41]; 861 bool esc_edge = esc && !g_prev_keys[41];
865 bool left = (nk > 80) && keys[80]; 862 bool left = (nk > 80) && keys[80];
@@ -867,7 +864,7 @@ void gui_handle_input() {
867 bool left_edge = left && !g_prev_keys[80]; 864 bool left_edge = left && !g_prev_keys[80];
868 bool right_edge = right && !g_prev_keys[79]; 865 bool right_edge = right && !g_prev_keys[79];
869 866
870 if (f2_edge) { 867 if (toggle_edge) {
871 if (g_open) 868 if (g_open)
872 close_gui(); 869 close_gui();
873 else 870 else
@@ -887,6 +884,11 @@ void gui_handle_input() {
887 sd_set_relative(0); 884 sd_set_relative(0);
888 if (sd_show_cursor) 885 if (sd_show_cursor)
889 sd_show_cursor(1); 886 sd_show_cursor(1);
887 if (g_iface.engine) {
888 QAngle f = g_frozen;
889 using SetViewFn = void (*)(void *, QAngle *);
890 vfunc<SetViewFn>(g_iface.engine, 20)(g_iface.engine, &f);
891 }
890 892
891 handle_capture(keys); 893 handle_capture(keys);
892 894
@@ -1063,7 +1065,7 @@ static void draw_card(const Rect &clip, int index, int y) {
1063 text_in(clip, cr.x + S(16.0f), by + S(1.0f), ct.label, g_font_body, 1065 text_in(clip, cr.x + S(16.0f), by + S(1.0f), ct.label, g_font_body,
1064 g_body_h, cr.w - S(24.0f), C_DIM); 1066 g_body_h, cr.w - S(24.0f), C_DIM);
1065 } else if (ct.kind == CK_BUTTON) { 1067 } else if (ct.kind == CK_BUTTON) {
1066 Rect b{cr.x + cr.w - S(130.0f), by + S(3.0f), S(114.0f), S(26.0f)}; 1068 Rect b{cr.x + cr.w - S(176.0f), by + S(3.0f), S(160.0f), S(26.0f)};
1067 fill(b, Col{45, 50, 60, 255}, &clip); 1069 fill(b, Col{45, 50, 60, 255}, &clip);
1068 outline(b, C_BORDER, &clip); 1070 outline(b, C_BORDER, &clip);
1069 text_in(clip, b.x + S(10.0f), b.y + S(4.0f), ct.label, g_font_body, 1071 text_in(clip, b.x + S(10.0f), b.y + S(4.0f), ct.label, g_font_body,
@@ -1126,12 +1128,6 @@ void gui_draw() {
1126 g_title_h, win.w - S(80.0f), C_TEXT); 1128 g_title_h, win.w - S(80.0f), C_TEXT);
1127 text_in(win, win.x + S(110.0f), win.y + S(10.0f), "GUI shell preview", 1129 text_in(win, win.x + S(110.0f), win.y + S(10.0f), "GUI shell preview",
1128 g_font_body, g_body_h, win.w - S(180.0f), C_DIM); 1130 g_font_body, g_body_h, win.w - S(180.0f), C_DIM);
1129 Rect close{win.x + win.w - S(38.0f), win.y, S(38.0f), S(38.0f)};
1130 fill(close, Col{45, 48, 56, 255});
1131 line(close.x + S(13.0f), close.y + S(12.0f), close.x + S(25.0f),
1132 close.y + S(25.0f), C_DIM);
1133 line(close.x + S(25.0f), close.y + S(12.0f), close.x + S(13.0f),
1134 close.y + S(25.0f), C_DIM);
1135 1131
1136 Rect side{win.x, win.y + S(38.0f), S(190.0f), 1132 Rect side{win.x, win.y + S(38.0f), S(190.0f),
1137 win.h - S(38.0f) - S(30.0f)}; 1133 win.h - S(38.0f) - S(30.0f)};
@@ -1185,7 +1181,7 @@ void gui_draw() {
1185 "config: ~/.config/hl2hake/config", g_font_body, g_body_h, 1181 "config: ~/.config/hl2hake/config", g_font_body, g_body_h,
1186 foot.w - S(220.0f), C_DIM); 1182 foot.w - S(220.0f), C_DIM);
1187 text_in(foot, foot.x + foot.w - S(200.0f), foot.y + S(6.0f), 1183 text_in(foot, foot.x + foot.w - S(200.0f), foot.y + S(6.0f),
1188 g_capture_card >= 0 ? "press a key / Esc" : "F2 close | Insert text", 1184 g_capture_card >= 0 ? "press a key / Esc" : "INS close | F1 text menu",
1189 g_font_body, g_body_h, S(190.0f), C_DIM); 1185 g_font_body, g_body_h, S(190.0f), C_DIM);
1190 1186
1191 draw_tooltip(); 1187 draw_tooltip();
@@ -1281,3 +1277,40 @@ SDL_WarpMouseGlobal(int x, int y) {
1281 if (real_warp_global) 1277 if (real_warp_global)
1282 real_warp_global(x, y); 1278 real_warp_global(x, y);
1283} 1279}
1280
1281static unsigned int (*real_get_rel_mouse)(int *, int *) = nullptr;
1282static int (*real_poll_event)(void *) = nullptr;
1283
1284extern "C" __attribute__((visibility("default"))) unsigned int
1285SDL_GetRelativeMouseState(int *x, int *y) {
1286 if (!real_get_rel_mouse)
1287 real_get_rel_mouse =
1288 (unsigned int (*)(int *, int *))dlsym(RTLD_NEXT,
1289 "SDL_GetRelativeMouseState");
1290 unsigned int mask = real_get_rel_mouse ? real_get_rel_mouse(x, y) : 0;
1291 if (g_open) {
1292 if (x) *x = 0;
1293 if (y) *y = 0;
1294 }
1295 return mask;
1296}
1297
1298extern "C" __attribute__((visibility("default"))) int
1299SDL_PollEvent(void *event) {
1300 if (!real_poll_event)
1301 real_poll_event = (int (*)(void *))dlsym(RTLD_NEXT, "SDL_PollEvent");
1302 if (!real_poll_event)
1303 return 0;
1304 for (;;) {
1305 int r = real_poll_event(event);
1306 if (!r)
1307 return 0;
1308 if (!g_open || !event)
1309 return r;
1310 unsigned int type = *(unsigned int *)event;
1311 if (type != 0x400) // SDL_MOUSEMOTION
1312 return r;
1313 // Drop motion while the GUI is open: the game must not accumulate look
1314 // deltas. The GUI uses absolute SDL_GetMouseState polling.
1315 }
1316}
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
index a0e6b53..1460577 100644
--- a/src/hl2h.cpp
+++ b/src/hl2h.cpp
@@ -561,7 +561,8 @@ static void do_move(int seq, float frametime) {
561 cmd->buttons = 0; 561 cmd->buttons = 0;
562 } else { 562 } else {
563 // Movement is allowed while the GUI is open, but not look/attack/use. 563 // Movement is allowed while the GUI is open, but not look/attack/use.
564 cmd->buttons &= (IN_JUMP | IN_DUCK); 564 // Keep locomotion buttons (jump, duck, walk) only.
565 cmd->buttons &= (IN_JUMP | IN_DUCK | IN_SPEED);
565 } 566 }
566 cmd->impulse = 0; 567 cmd->impulse = 0;
567 cmd->weaponselect = 0; 568 cmd->weaponselect = 0;
diff --git a/src/hl2h.h b/src/hl2h.h
index 8a81439..d5a9dc3 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -129,7 +129,7 @@ struct CUserCmd {
129// engine constants 129// engine constants
130// --------------------------------------------------------------------------- 130// ---------------------------------------------------------------------------
131 131
132enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5, IN_ATTACK2 = 1 << 11 }; 132enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5, IN_ATTACK2 = 1 << 11, IN_SPEED = 1 << 17 };
133enum { FL_ONGROUND = 1 << 0, FL_DUCKING = 1 << 1, FL_INWATER = 1 << 9 }; 133enum { FL_ONGROUND = 1 << 0, FL_DUCKING = 1 << 1, FL_INWATER = 1 << 9 };
134enum { MOVETYPE_WALK = 2, MOVETYPE_NOCLIP = 8, MOVETYPE_LADDER = 9 }; 134enum { MOVETYPE_WALK = 2, MOVETYPE_NOCLIP = 8, MOVETYPE_LADDER = 9 };
135 135
@@ -180,6 +180,7 @@ enum EspOption {
180// --------------------------------------------------------------------------- 180// ---------------------------------------------------------------------------
181void note_modified(const CUserCmd *cmd, int flags, const float *vel); 181void note_modified(const CUserCmd *cmd, int flags, const float *vel);
182CUserCmd *get_usercmd(int seq); 182CUserCmd *get_usercmd(int seq);
183void input_clear_mouse_states();
183void fix_verified_crc(int seq, uint32_t crc); 184void fix_verified_crc(int seq, uint32_t crc);
184uint32_t usercmd_checksum(const CUserCmd *c); 185uint32_t usercmd_checksum(const CUserCmd *c);
185bool find_input_global(); 186bool find_input_global();
diff --git a/src/menu.cpp b/src/menu.cpp
index 56fe6fa..a0cf323 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -467,7 +467,8 @@ static void config_load() {
467// --------------------------------------------------------------------------- 467// ---------------------------------------------------------------------------
468 468
469// SDL scancodes for navigation keys (matches the game's scancode set) 469// SDL scancodes for navigation keys (matches the game's scancode set)
470static constexpr int SCAN_INSERT = 73; 470static constexpr int SCAN_INSERT = 73; // reserved for the GUI
471static constexpr int SCAN_TEXT_MENU = 58; // F1 preview key for the old tree
471static constexpr int SCAN_UP = 82; // these are SDL scancodes 472static constexpr int SCAN_UP = 82; // these are SDL scancodes
472static constexpr int SCAN_DOWN = 81; 473static constexpr int SCAN_DOWN = 81;
473static constexpr int SCAN_LEFT = 80; 474static constexpr int SCAN_LEFT = 80;
@@ -637,7 +638,7 @@ static void menu_handle_input() {
637 // SDL state can be sampled several times per rendered frame (both ISurface 638 // SDL state can be sampled several times per rendered frame (both ISurface
638 // hooks call outer_draw). Use a physical-key latch instead of the one-poll 639 // hooks call outer_draw). Use a physical-key latch instead of the one-poll
639 // edge so one press cannot open and close the menu in the same frame. 640 // edge so one press cannot open and close the menu in the same frame.
640 bool insert_down = key_held(SCAN_INSERT); 641 bool insert_down = key_held(SCAN_TEXT_MENU);
641 bool insert_edge = insert_down && !g_insert_latch; 642 bool insert_edge = insert_down && !g_insert_latch;
642 if (insert_down) 643 if (insert_down)
643 g_insert_latch = true; 644 g_insert_latch = true;
@@ -662,8 +663,8 @@ static void menu_handle_input() {
662 for (int i = 0; i < MOUSE_BASE + 5; i++) { 663 for (int i = 0; i < MOUSE_BASE + 5; i++) {
663 if (!g_key_down[i]) 664 if (!g_key_down[i])
664 continue; 665 continue;
665 if (i == SCAN_INSERT || i == SCAN_UP || i == SCAN_DOWN || 666 if (i == SCAN_INSERT || i == SCAN_TEXT_MENU || i == SCAN_UP ||
666 i == SCAN_LEFT || i == SCAN_RIGHT) { 667 i == SCAN_DOWN || i == SCAN_LEFT || i == SCAN_RIGHT) {
667 g_bind_mode = false; 668 g_bind_mode = false;
668 return; // cancel bind select 669 return; // cancel bind select
669 } 670 }