From 05a27edc25c889c629618ac44137fb221e8e4b62 Mon Sep 17 00:00:00 2001 From: MrTheShy <49885496+MrTheShy@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:48:01 +0100 Subject: [PATCH] Fix TextInput caret behavior and add proper cursor editing for KBM direct edit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The direct text editing mode introduced for KBM users had several issues with the TextInput control's caret (blinking cursor) and text manipulation: 1. Caret visible when not editing: When navigating to the world name field with keyboard/mouse, Flash's Iggy focus system would show the blinking caret even though the field wasn't active for editing yet (Enter not pressed). This was misleading since typing had no effect in that state. Fix: access the FJ_TextInput's internal m_mcCaret MovieClip and force its visibility based on editing state. This is enforced every tick because setLabel() and Flash focus transitions continuously reset the caret state. 2. No cursor movement during editing: The direct edit implementation treated the text as a simple buffer with push_back/pop_back — there was no concept of cursor position. Backspace only deleted from the end, and arrow keys did nothing. Fix: track cursor position (m_iCursorPos) in C++ and use wstring insert/erase at that position. Arrow keys (Left/Right), Home, End, and Delete now work as expected. The visual caret position is synced to Flash via the FJ_TextInput's SetCaretIndex method. 3. setLabel() resetting caret position: Every call to setLabel() (when text changes) caused Flash to reset the caret to the end of the string, making the cursor jump visually even though the C++ position was correct. Fix: enforce caret position via setCaretIndex every tick during editing, so any Flash-side resets are immediately corrected. New UIControl_TextInput API: - setCaretVisible(bool): toggles m_mcCaret.visible in Flash - setCaretIndex(int): calls FJ_TextInput.SetCaretIndex in Flash --- .../Common/UI/UIControl_TextInput.cpp | 20 +++++++ .../Common/UI/UIControl_TextInput.h | 4 ++ .../Common/UI/UIScene_CreateWorldMenu.cpp | 52 +++++++++++++++++-- .../Common/UI/UIScene_CreateWorldMenu.h | 1 + 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/Minecraft.Client/Common/UI/UIControl_TextInput.cpp b/Minecraft.Client/Common/UI/UIControl_TextInput.cpp index dc7bc5326..fd8024679 100644 --- a/Minecraft.Client/Common/UI/UIControl_TextInput.cpp +++ b/Minecraft.Client/Common/UI/UIControl_TextInput.cpp @@ -16,6 +16,7 @@ bool UIControl_TextInput::setupControl(UIScene *scene, IggyValuePath *parent, co m_textName = registerFastName(L"text"); m_funcChangeState = registerFastName(L"ChangeState"); m_funcSetCharLimit = registerFastName(L"SetCharLimit"); + m_funcSetCaretIndex = registerFastName(L"SetCaretIndex"); return success; } @@ -81,3 +82,22 @@ void UIControl_TextInput::SetCharLimit(int iLimit) value[0].number = iLimit; IggyResult out = IggyPlayerCallMethodRS ( m_parentScene->getMovie() , &result, getIggyValuePath() , m_funcSetCharLimit , 1 , value ); } + +void UIControl_TextInput::setCaretVisible(bool visible) +{ + // Always send to Flash — Iggy's focus system can re-enable the caret at any time + IggyValuePath caretPath; + if (IggyValuePathMakeNameRef(&caretPath, getIggyValuePath(), "m_mcCaret")) + { + IggyValueSetBooleanRS(&caretPath, m_nameVisible, NULL, visible); + } +} + +void UIControl_TextInput::setCaretIndex(int index) +{ + IggyDataValue result; + IggyDataValue value[1]; + value[0].type = IGGY_DATATYPE_number; + value[0].number = index; + IggyResult out = IggyPlayerCallMethodRS ( m_parentScene->getMovie() , &result, getIggyValuePath() , m_funcSetCaretIndex , 1 , value ); +} diff --git a/Minecraft.Client/Common/UI/UIControl_TextInput.h b/Minecraft.Client/Common/UI/UIControl_TextInput.h index 98032d858..4ced89581 100644 --- a/Minecraft.Client/Common/UI/UIControl_TextInput.h +++ b/Minecraft.Client/Common/UI/UIControl_TextInput.h @@ -6,6 +6,7 @@ class UIControl_TextInput : public UIControl_Base { private: IggyName m_textName, m_funcChangeState, m_funcSetCharLimit; + IggyName m_funcSetCaretIndex; bool m_bHasFocus; public: @@ -19,4 +20,7 @@ public: virtual void setFocus(bool focus); void SetCharLimit(int iLimit); + + void setCaretVisible(bool visible); + void setCaretIndex(int index); }; \ No newline at end of file diff --git a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp index 6afb67e49..bdf949870 100644 --- a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp @@ -87,6 +87,7 @@ UIScene_CreateWorldMenu::UIScene_CreateWorldMenu(int iPad, void *initData, UILay #ifdef _WINDOWS64 m_bDirectEditing = false; m_iDirectEditCooldown = 0; + m_iCursorPos = 0; #endif m_bMultiplayerAllowed = ProfileManager.IsSignedInLive( m_iPad ) && ProfileManager.AllowedToPlayMultiplayer(m_iPad); @@ -297,6 +298,15 @@ void UIScene_CreateWorldMenu::tick() if (m_iDirectEditCooldown > 0) m_iDirectEditCooldown--; + // Control caret visibility and position every tick — setLabel() and Flash + // focus changes reset both, so we must continuously enforce them. + if (g_KBMInput.IsKBMActive()) + { + m_editWorldName.setCaretVisible(m_bDirectEditing); + if (m_bDirectEditing) + m_editWorldName.setCaretIndex(m_iCursorPos); + } + if (m_bDirectEditing) { wchar_t ch; @@ -305,9 +315,10 @@ void UIScene_CreateWorldMenu::tick() { if (ch == 0x08) // backspace { - if (!m_worldName.empty()) + if (m_iCursorPos > 0) { - m_worldName.pop_back(); + m_worldName.erase(m_iCursorPos - 1, 1); + m_iCursorPos--; changed = true; } } @@ -316,14 +327,44 @@ void UIScene_CreateWorldMenu::tick() m_bDirectEditing = false; m_iDirectEditCooldown = 4; // absorb the matching ACTION_MENU_OK that follows m_editWorldName.setLabel(m_worldName.c_str()); + m_editWorldName.setCaretVisible(false); + break; } else if ((int)m_worldName.length() < 25) { - m_worldName += ch; + m_worldName.insert(m_iCursorPos, 1, ch); + m_iCursorPos++; changed = true; } } + // Arrow keys move the cursor within the text + if (g_KBMInput.IsKeyPressed(VK_LEFT) && m_iCursorPos > 0) + { + m_iCursorPos--; + m_editWorldName.setCaretIndex(m_iCursorPos); + } + if (g_KBMInput.IsKeyPressed(VK_RIGHT) && m_iCursorPos < (int)m_worldName.length()) + { + m_iCursorPos++; + m_editWorldName.setCaretIndex(m_iCursorPos); + } + if (g_KBMInput.IsKeyPressed(VK_HOME)) + { + m_iCursorPos = 0; + m_editWorldName.setCaretIndex(m_iCursorPos); + } + if (g_KBMInput.IsKeyPressed(VK_END)) + { + m_iCursorPos = (int)m_worldName.length(); + m_editWorldName.setCaretIndex(m_iCursorPos); + } + if (g_KBMInput.IsKeyPressed(VK_DELETE) && m_iCursorPos < (int)m_worldName.length()) + { + m_worldName.erase(m_iCursorPos, 1); + changed = true; + } + // Escape cancels and restores the original name if (m_bDirectEditing && g_KBMInput.IsKeyPressed(VK_ESCAPE)) { @@ -331,11 +372,13 @@ void UIScene_CreateWorldMenu::tick() m_bDirectEditing = false; m_iDirectEditCooldown = 4; m_editWorldName.setLabel(m_worldName.c_str()); + m_editWorldName.setCaretVisible(false); m_buttonCreateWorld.setEnable(!m_worldName.empty()); } else if (changed) { m_editWorldName.setLabel(m_worldName.c_str()); + m_editWorldName.setCaretIndex(m_iCursorPos); m_buttonCreateWorld.setEnable(!m_worldName.empty()); } } @@ -492,7 +535,10 @@ void UIScene_CreateWorldMenu::handlePress(F64 controlId, F64 childId) m_bIgnoreInput = false; // Don't block input - m_bDirectEditing is the guard m_worldNameBeforeEdit = m_worldName; m_bDirectEditing = true; + m_iCursorPos = (int)m_worldName.length(); g_KBMInput.ClearCharBuffer(); + m_editWorldName.setCaretVisible(true); + m_editWorldName.setCaretIndex(m_iCursorPos); } #else InputManager.RequestKeyboard(app.GetString(IDS_CREATE_NEW_WORLD),m_editWorldName.getLabel(),(DWORD)0,25,&UIScene_CreateWorldMenu::KeyboardCompleteWorldNameCallback,this,C_4JInput::EKeyboardMode_Default); diff --git a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.h b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.h index 13f38a3b7..1f941c91b 100644 --- a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.h @@ -55,6 +55,7 @@ private: bool m_bDirectEditing; wstring m_worldNameBeforeEdit; int m_iDirectEditCooldown; + int m_iCursorPos; #endif public: