From 0545e15c4872fab4d94aad5ba14f8f2c8e6bd6cc Mon Sep 17 00:00:00 2001 From: MrTheShy <49885496+MrTheShy@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:19:07 +0100 Subject: [PATCH 1/4] Fix Escape key not opening pause menu during tutorial hints The KBM pause check had a IsTutorialVisible guard that blocked Escape entirely while any tutorial popup was on screen. The controller path never had this restriction. Removed the check so Escape behaves the same as Start on controller. --- Minecraft.Client/Minecraft.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index b87745f03..9bdac7d8c 100644 --- a/Minecraft.Client/Minecraft.cpp +++ b/Minecraft.Client/Minecraft.cpp @@ -1485,7 +1485,7 @@ void Minecraft::run_middle() } // Utility keys always work regardless of KBM active state - if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_PAUSE) && !ui.IsTutorialVisible(i) && !ui.GetMenuDisplayed(i)) + if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_PAUSE) && !ui.GetMenuDisplayed(i)) { localplayers[i]->ullButtonsPressed|=1LL< Date: Fri, 6 Mar 2026 13:23:05 +0100 Subject: [PATCH 2/4] Fix crash in WriteHeader when save buffer is too small for header table When a player enters a new region, RegionFile's constructor calls createFile which adds a FileEntry with length 0 to the file table. This increases the header table size (appended at the end of the save buffer) by sizeof(FileEntrySaveData) per entry, but since no actual data is written to the file, MoveDataBeyond is never called and the committed virtual memory pages are never grown to match. On the next autosave tick, saveLevelData writes level.dat first (before chunkSource->save which would have grown the buffer). If level.dat doesn't need to grow, finalizeWrite calls WriteHeader which tries to memcpy the now-larger header table past the end of committed memory, causing an access violation. This is especially likely in splitscreen where two players exploring at the same time can create multiple new RegionFile entries within a single tick, quickly exhausting the page-alignment slack in the buffer (yes i am working at splitscreen in the meanwhile :) ) The fix was deduced by tracing the crash callstack through the save system: FileHeader, ConsoleSaveFileOriginal, the stream chain, and the RegionFile/RegionFileCache layer. The root cause turned out to be a gap between createFile (which grows the header table) and MoveDataBeyond (the only place that grows the buffer), with finalizeWrite sitting right in between unprotected. The buffer growth check added here mirrors the exact same VirtualAlloc pattern already used in MoveDataBeyond (line 484-497) and in the constructor's decompression path (line 176-190), so it integrates naturally with the existing code. Same types, same page rounding, same error handling. The fast path (no new entries, buffer already big enough) is a single DWORD comparison that doesn't get taken, so there is zero overhead in the common case. This is the right place for the fix because finalizeWrite is the sole caller of WriteHeader, meaning every code path that writes the header (closeHandle, PrepareForWrite, deleteFile, Flush) is now protected by a single check point. --- Minecraft.World/ConsoleSaveFileOriginal.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Minecraft.World/ConsoleSaveFileOriginal.cpp b/Minecraft.World/ConsoleSaveFileOriginal.cpp index 3a2b5ef11..d4db4e25e 100644 --- a/Minecraft.World/ConsoleSaveFileOriginal.cpp +++ b/Minecraft.World/ConsoleSaveFileOriginal.cpp @@ -463,6 +463,23 @@ BOOL ConsoleSaveFileOriginal::closeHandle( FileEntry *file ) void ConsoleSaveFileOriginal::finalizeWrite() { LockSaveAccess(); + + // Ensure buffer is large enough for the full file including header table. + // New file entries (e.g. from RegionFile creation) increase GetFileSize() + // without triggering MoveDataBeyond, so the committed pages may be short. + DWORD currentHeapSize = pagesCommitted * CSF_PAGE_SIZE; + DWORD desiredSize = header.GetFileSize(); + if( desiredSize > currentHeapSize ) + { + unsigned int pagesRequired = ( desiredSize + (CSF_PAGE_SIZE - 1 ) ) / CSF_PAGE_SIZE; + void *pvRet = VirtualAlloc(pvHeap, pagesRequired * CSF_PAGE_SIZE, COMMIT_ALLOCATION, PAGE_READWRITE); + if( pvRet == NULL ) + { + __debugbreak(); + } + pagesCommitted = pagesRequired; + } + header.WriteHeader( pvSaveMem ); ReleaseSaveAccess(); } From 8b23c71b93fb14526b1091d5f771166cb34d145a Mon Sep 17 00:00:00 2001 From: MrTheShy <49885496+MrTheShy@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:42:48 +0100 Subject: [PATCH 3/4] Fix TextInput bugs and refactor direct edit handling into UIScene base class The fake cursor character (_) used for SWFs without m_mcCaret was leaking into saved sign and anvil text. This happened because setLabel() with instant=false only updates the C++-side cache, deferring the Flash write to the next control tick. Any getLabel() call before that tick reads the old Flash value still containing the underscore. Fixed by passing instant=true in confirmDirectEdit, cancelDirectEdit, and the Enter key path inside tickDirectEdit, so the cleaned text hits Flash immediately. Mouse hover over TextInput controls (world name, anvil name, seed field) was not showing the yellow highlight border. The hover code used IggyPlayerSetFocusRS which sets Iggy's internal dispatch focus but does not trigger Flash's ChangeState callback, so no visual feedback appeared. Buttons worked fine because Iggy draws its own focus ring on them, but TextInput relies entirely on ChangeState(0) for the yellow border. Switched to SetFocusToElement which goes through the Flash-side SetFocus path, then immediately call setCaretVisible(false) to suppress the blinking caret that comes with focus. No visual flicker since rendering happens after both tickInput and scene tick complete. While direct editing, mouse hover was able to move focus away to other TextInputs on the same scene (most noticeably on the sign editor, where hovering a different line would steal focus from the line being typed). Added an isDirectEditBlocking() check in the hover path to skip focus changes when any input on the scene is actively being edited. The Done button in SignEntryMenu was unresponsive to mouse clicks during direct editing. The root cause is execution order: handleMouseClick runs before handleInput in the frame. The base handleMouseClick found the Done button and called handlePress, but handlePress bailed out because of the isDirectEditing guard. The click was marked consumed, so handleInput never saw it. Fixed by overriding handleMouseClick in SignEntryMenu to detect the Done button hit while editing and confirm + close directly. Added click-outside-to-deselect for anvil and world name text inputs. Both scenes previously required Enter to confirm the edit, which felt wrong. Now clicking anywhere outside the text field bounds confirms the current text, matching standard UI behavior. The anvil menu now updates the item name in real time while typing, like Java edition. Previously the name was only applied on Enter, so the repair cost display was stale until confirmation. The biggest change is structural: every scene that used direct editing (AnvilMenu, CreateWorldMenu, SignEntryMenu, LaunchMoreOptionsMenu, DebugCreateSchematic, DebugSetCamera) had its own copy of the same boilerplate -- tickDirectEdit loops in tick(), click-outside hit testing in handleMouseClick(), cooldown guard checks in handleInput/handlePress, and result dispatch with switch/if chains. This was around 200 lines of near-identical code scattered across 6 files, each with its own slight variations and its own bugs waiting to happen. Pulled all of it into UIScene with two virtual methods: getDirectEditInputs() where scenes register their text inputs, and onDirectEditFinished() where they handle confirmed/cancelled results. The base class tick() drives tickDirectEdit on all registered inputs, handleMouseClick() does the click-outside-to-deselect hit test generically using panel offsets, and isDirectEditBlocking() replaces all the inline cooldown checks. Scenes now just override those two methods and get everything for free. Also removed the m_activeDirectEditControl enum tracking from the debug scenes (DebugCreateSchematic, DebugSetCamera) since the base class handles lifecycle tracking through the controls themselves. --- .../Common/UI/UIControl_TextInput.cpp | 6 +- Minecraft.Client/Common/UI/UIController.cpp | 29 ++- Minecraft.Client/Common/UI/UIScene.cpp | 47 ++++ Minecraft.Client/Common/UI/UIScene.h | 8 + .../Common/UI/UIScene_AnvilMenu.cpp | 27 ++- .../Common/UI/UIScene_AnvilMenu.h | 4 + .../Common/UI/UIScene_CreateWorldMenu.cpp | 25 ++- .../Common/UI/UIScene_CreateWorldMenu.h | 4 + .../UI/UIScene_DebugCreateSchematic.cpp | 81 ++++--- .../Common/UI/UIScene_DebugCreateSchematic.h | 5 +- .../Common/UI/UIScene_DebugSetCamera.cpp | 82 +++---- .../Common/UI/UIScene_DebugSetCamera.h | 3 +- .../UI/UIScene_LaunchMoreOptionsMenu.cpp | 23 +- .../Common/UI/UIScene_LaunchMoreOptionsMenu.h | 4 + .../Common/UI/UIScene_SignEntryMenu.cpp | 202 ++++++++++++++---- .../Common/UI/UIScene_SignEntryMenu.h | 8 + 16 files changed, 381 insertions(+), 177 deletions(-) diff --git a/Minecraft.Client/Common/UI/UIControl_TextInput.cpp b/Minecraft.Client/Common/UI/UIControl_TextInput.cpp index 193dbf41f..0bfa0bbd3 100644 --- a/Minecraft.Client/Common/UI/UIControl_TextInput.cpp +++ b/Minecraft.Client/Common/UI/UIControl_TextInput.cpp @@ -193,7 +193,7 @@ UIControl_TextInput::EDirectEditResult UIControl_TextInput::tickDirectEdit() { m_bDirectEditing = false; m_iDirectEditCooldown = 4; - setLabel(m_editBuffer.c_str()); + setLabel(m_editBuffer.c_str(), true); setCaretVisible(false); return eDirectEdit_Confirmed; } @@ -263,7 +263,7 @@ void UIControl_TextInput::cancelDirectEdit() m_editBuffer = m_textBeforeEdit; m_bDirectEditing = false; m_iDirectEditCooldown = 4; - setLabel(m_editBuffer.c_str()); + setLabel(m_editBuffer.c_str(), true); setCaretVisible(false); } } @@ -273,7 +273,7 @@ void UIControl_TextInput::confirmDirectEdit() if (m_bDirectEditing) { m_bDirectEditing = false; - setLabel(m_editBuffer.c_str()); + setLabel(m_editBuffer.c_str(), true); setCaretVisible(false); } } diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index f059d8211..3907ed45c 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -861,7 +861,7 @@ void UIController::tickInput() { int hitControlId = -1; S32 hitArea = INT_MAX; - bool hitIsTextInput = false; + UIControl *hitCtrl = NULL; for (size_t i = 0; i < controls->size(); ++i) { UIControl *ctrl = (*controls)[i]; @@ -898,6 +898,7 @@ void UIController::tickInput() (S32)sceneMouseX, (S32)sceneMouseY, false); hitControlId = -1; hitArea = INT_MAX; + hitCtrl = NULL; break; // ButtonList takes priority } S32 area = cw * ch; @@ -905,17 +906,31 @@ void UIController::tickInput() { hitControlId = ctrl->getId(); hitArea = area; - hitIsTextInput = (type == UIControl::eTextInput); + hitCtrl = ctrl; } } } - // Set focus directly via Flash AS, matching the click path. - // Skip TextInput — its Iggy focus is set on click (below) - // to avoid showing the caret on mere hover. - if (hitControlId >= 0 && !hitIsTextInput && pScene->getControlFocus() != hitControlId) + if (hitControlId >= 0 && pScene->getControlFocus() != hitControlId) { - pScene->SetFocusToElement(hitControlId); + // During direct editing, don't let hover move focus + // away to other TextInputs (e.g. sign lines). + if (hitCtrl && hitCtrl->getControlType() == UIControl::eTextInput + && pScene->isDirectEditBlocking()) + { + // Skip — keep focus on the actively-edited input + } + else + { + pScene->SetFocusToElement(hitControlId); + // TextInput: SetFocusToElement triggers ChangeState which + // shows the caret. Hide it immediately — the render pass + // happens after both tickInput and scene tick, so no flicker. + if (hitCtrl && hitCtrl->getControlType() == UIControl::eTextInput) + { + ((UIControl_TextInput *)hitCtrl)->setCaretVisible(false); + } + } } } } diff --git a/Minecraft.Client/Common/UI/UIScene.cpp b/Minecraft.Client/Common/UI/UIScene.cpp index 0aedbf18f..061f9832f 100644 --- a/Minecraft.Client/Common/UI/UIScene.cpp +++ b/Minecraft.Client/Common/UI/UIScene.cpp @@ -447,6 +447,19 @@ void UIScene::tick() IggyPlayerTickRS( swf ); m_hasTickedOnce = true; } + +#ifdef _WINDOWS64 + { + vector inputs; + getDirectEditInputs(inputs); + for (size_t i = 0; i < inputs.size(); i++) + { + UIControl_TextInput::EDirectEditResult result = inputs[i]->tickDirectEdit(); + if (result != UIControl_TextInput::eDirectEdit_Continue) + onDirectEditFinished(inputs[i], result); + } + } +#endif } UIControl* UIScene::GetMainPanel() @@ -455,6 +468,18 @@ UIControl* UIScene::GetMainPanel() } #ifdef _WINDOWS64 +bool UIScene::isDirectEditBlocking() +{ + vector inputs; + getDirectEditInputs(inputs); + for (size_t i = 0; i < inputs.size(); i++) + { + if (inputs[i]->isDirectEditing() || inputs[i]->getDirectEditCooldown() > 0) + return true; + } + return false; +} + bool UIScene::handleMouseClick(F32 x, F32 y) { S32 panelOffsetX = 0, panelOffsetY = 0; @@ -466,6 +491,28 @@ bool UIScene::handleMouseClick(F32 x, F32 y) panelOffsetY = pMainPanel->getYPos(); } + // Click-outside-to-deselect: confirm any active direct edit if + // the click landed outside the editing text input. + { + vector deInputs; + getDirectEditInputs(deInputs); + for (size_t i = 0; i < deInputs.size(); i++) + { + if (!deInputs[i]->isDirectEditing()) + continue; + deInputs[i]->UpdateControl(); + S32 cx = deInputs[i]->getXPos() + panelOffsetX; + S32 cy = deInputs[i]->getYPos() + panelOffsetY; + S32 cw = deInputs[i]->getWidth(); + S32 ch = deInputs[i]->getHeight(); + if (!(cw > 0 && ch > 0 && x >= cx && x <= cx + cw && y >= cy && y <= cy + ch)) + { + deInputs[i]->confirmDirectEdit(); + onDirectEditFinished(deInputs[i], UIControl_TextInput::eDirectEdit_Confirmed); + } + } + } + vector *controls = GetControls(); if (!controls) return false; diff --git a/Minecraft.Client/Common/UI/UIScene.h b/Minecraft.Client/Common/UI/UIScene.h index df2bc840d..8fb4983bd 100644 --- a/Minecraft.Client/Common/UI/UIScene.h +++ b/Minecraft.Client/Common/UI/UIScene.h @@ -6,6 +6,7 @@ using namespace std; #include "UIEnums.h" #include "UIControl_Base.h" +#include "UIControl_TextInput.h" class ItemRenderer; class UILayer; @@ -184,6 +185,13 @@ public: virtual UIControl* GetMainPanel(); #ifdef _WINDOWS64 + // Direct edit support: scenes override to register their text inputs. + // Base class handles tickDirectEdit in tick(), click-outside-to-deselect + // in handleMouseClick(), and provides isDirectEditBlocking() for guards. + virtual void getDirectEditInputs(vector &inputs) {} + virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result) {} + bool isDirectEditBlocking(); + // Mouse click dispatch. Hit-tests C++ controls and picks the smallest-area // match, then calls handlePress. Override for custom behaviour (e.g. crafting). virtual bool handleMouseClick(F32 x, F32 y); diff --git a/Minecraft.Client/Common/UI/UIScene_AnvilMenu.cpp b/Minecraft.Client/Common/UI/UIScene_AnvilMenu.cpp index 0d6918661..4d43a638b 100644 --- a/Minecraft.Client/Common/UI/UIScene_AnvilMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_AnvilMenu.cpp @@ -97,11 +97,15 @@ void UIScene_AnvilMenu::tick() UIScene_AbstractContainerMenu::tick(); #ifdef _WINDOWS64 - UIControl_TextInput::EDirectEditResult editResult = m_textInputAnvil.tickDirectEdit(); - if (editResult == UIControl_TextInput::eDirectEdit_Confirmed) + // Live update: sync item name per-keystroke while editing (like Java edition) + if (m_textInputAnvil.isDirectEditing()) { - m_itemName = m_textInputAnvil.getEditBuffer(); - updateItemName(); + const wstring& buf = m_textInputAnvil.getEditBuffer(); + if (buf != m_itemName) + { + m_itemName = buf; + updateItemName(); + } } #endif @@ -315,6 +319,19 @@ UIControl *UIScene_AnvilMenu::getSection(ESceneSection eSection) return control; } +#ifdef _WINDOWS64 +void UIScene_AnvilMenu::getDirectEditInputs(vector &inputs) +{ + inputs.push_back(&m_textInputAnvil); +} + +void UIScene_AnvilMenu::onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result) +{ + m_itemName = input->getEditBuffer(); + updateItemName(); +} +#endif + int UIScene_AnvilMenu::KeyboardCompleteCallback(LPVOID lpParam,bool bRes) { UIScene_AnvilMenu *pClass=(UIScene_AnvilMenu *)lpParam; @@ -344,7 +361,7 @@ int UIScene_AnvilMenu::KeyboardCompleteCallback(LPVOID lpParam,bool bRes) void UIScene_AnvilMenu::handleEditNamePressed() { #ifdef _WINDOWS64 - if (m_textInputAnvil.isDirectEditing() || m_textInputAnvil.getDirectEditCooldown() > 0) + if (isDirectEditBlocking()) return; if (g_KBMInput.IsKBMActive()) diff --git a/Minecraft.Client/Common/UI/UIScene_AnvilMenu.h b/Minecraft.Client/Common/UI/UIScene_AnvilMenu.h index 3afc63338..44f759929 100644 --- a/Minecraft.Client/Common/UI/UIScene_AnvilMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_AnvilMenu.h @@ -55,6 +55,10 @@ protected: virtual UIControl *getSection(ESceneSection eSection); +#ifdef _WINDOWS64 + virtual void getDirectEditInputs(vector &inputs); + virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result); +#endif static int KeyboardCompleteCallback(LPVOID lpParam,bool bRes); virtual void handleEditNamePressed(); virtual void setEditNameValue(const wstring &name); diff --git a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp index 364c9ebda..a9cd9853f 100644 --- a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp @@ -289,14 +289,6 @@ void UIScene_CreateWorldMenu::tick() { UIScene::tick(); -#ifdef _WINDOWS64 - UIControl_TextInput::EDirectEditResult editResult = m_editWorldName.tickDirectEdit(); - if (editResult == UIControl_TextInput::eDirectEdit_Confirmed || editResult == UIControl_TextInput::eDirectEdit_Cancelled) - { - m_worldName = m_editWorldName.getEditBuffer(); - m_buttonCreateWorld.setEnable(!m_worldName.empty()); - } -#endif if(m_iSetTexturePackDescription >= 0 ) { @@ -360,11 +352,24 @@ int UIScene_CreateWorldMenu::ContinueOffline(void *pParam,int iPad,C4JStorage::E #endif +#ifdef _WINDOWS64 +void UIScene_CreateWorldMenu::getDirectEditInputs(vector &inputs) +{ + inputs.push_back(&m_editWorldName); +} + +void UIScene_CreateWorldMenu::onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result) +{ + m_worldName = input->getEditBuffer(); + m_buttonCreateWorld.setEnable(!m_worldName.empty()); +} +#endif + void UIScene_CreateWorldMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled) { if(m_bIgnoreInput) return; #ifdef _WINDOWS64 - if (m_editWorldName.isDirectEditing() || m_editWorldName.getDirectEditCooldown() > 0) { handled = true; return; } + if (isDirectEditBlocking()) { handled = true; return; } #endif ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released); @@ -421,7 +426,7 @@ void UIScene_CreateWorldMenu::handlePress(F64 controlId, F64 childId) { if(m_bIgnoreInput) return; #ifdef _WINDOWS64 - if (m_editWorldName.isDirectEditing() || m_editWorldName.getDirectEditCooldown() > 0) return; + if (isDirectEditBlocking()) return; #endif //CD - Added for audio diff --git a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.h b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.h index 49ab6ec50..75bfe602b 100644 --- a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.h @@ -78,6 +78,10 @@ protected: public: // INPUT virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled); +#ifdef _WINDOWS64 + virtual void getDirectEditInputs(vector &inputs); + virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result); +#endif private: void StartSharedLaunchFlow(); diff --git a/Minecraft.Client/Common/UI/UIScene_DebugCreateSchematic.cpp b/Minecraft.Client/Common/UI/UIScene_DebugCreateSchematic.cpp index 2eee6ce6a..d3da08709 100644 --- a/Minecraft.Client/Common/UI/UIScene_DebugCreateSchematic.cpp +++ b/Minecraft.Client/Common/UI/UIScene_DebugCreateSchematic.cpp @@ -34,10 +34,6 @@ UIScene_DebugCreateSchematic::UIScene_DebugCreateSchematic(int iPad, void *initD m_buttonCreate.init(L"Create",eControl_Create); m_data = new ConsoleSchematicFile::XboxSchematicInitParam(); - -#ifdef _WINDOWS64 - m_activeDirectEditControl = eControl_Create; // sentinel: no active edit -#endif } wstring UIScene_DebugCreateSchematic::getMoviePath() @@ -61,9 +57,41 @@ UIControl_TextInput* UIScene_DebugCreateSchematic::getTextInputForControl(eContr } #ifdef _WINDOWS64 +void UIScene_DebugCreateSchematic::getDirectEditInputs(vector &inputs) +{ + inputs.push_back(&m_textInputName); + inputs.push_back(&m_textInputStartX); + inputs.push_back(&m_textInputStartY); + inputs.push_back(&m_textInputStartZ); + inputs.push_back(&m_textInputEndX); + inputs.push_back(&m_textInputEndY); + inputs.push_back(&m_textInputEndZ); +} + +void UIScene_DebugCreateSchematic::onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result) +{ + wstring value = input->getEditBuffer(); + int iVal = 0; + if (!value.empty()) + iVal = _fromString(value); + + if (input == &m_textInputName) + { + if (!value.empty()) + swprintf(m_data->name, 64, L"%ls", value.c_str()); + else + swprintf(m_data->name, 64, L"schematic"); + } + else if (input == &m_textInputStartX) m_data->startX = iVal; + else if (input == &m_textInputStartY) m_data->startY = iVal; + else if (input == &m_textInputStartZ) m_data->startZ = iVal; + else if (input == &m_textInputEndX) m_data->endX = iVal; + else if (input == &m_textInputEndY) m_data->endY = iVal; + else if (input == &m_textInputEndZ) m_data->endZ = iVal; +} + bool UIScene_DebugCreateSchematic::handleMouseClick(F32 x, F32 y) { - if (m_activeDirectEditControl != eControl_Create) return true; UIScene::handleMouseClick(x, y); return true; // always consume to prevent Iggy re-entry on empty space } @@ -72,48 +100,12 @@ bool UIScene_DebugCreateSchematic::handleMouseClick(F32 x, F32 y) void UIScene_DebugCreateSchematic::tick() { UIScene::tick(); - -#ifdef _WINDOWS64 - UIControl_TextInput* allInputs[] = { &m_textInputName, &m_textInputStartX, &m_textInputStartY, &m_textInputStartZ, &m_textInputEndX, &m_textInputEndY, &m_textInputEndZ }; - for (int i = 0; i < 7; i++) - allInputs[i]->tickDirectEdit(); - - if (m_activeDirectEditControl != eControl_Create) - { - UIControl_TextInput* active = getTextInputForControl(m_activeDirectEditControl); - if (active && !active->isDirectEditing()) - { - // Edit finished — apply value - wstring value = active->getEditBuffer(); - int iVal = 0; - if (!value.empty() && m_activeDirectEditControl != eControl_Name) - iVal = _fromString(value); - - switch (m_activeDirectEditControl) - { - case eControl_Name: - if (!value.empty()) - swprintf(m_data->name, 64, L"%ls", value.c_str()); - else - swprintf(m_data->name, 64, L"schematic"); - break; - case eControl_StartX: m_data->startX = iVal; break; - case eControl_StartY: m_data->startY = iVal; break; - case eControl_StartZ: m_data->startZ = iVal; break; - case eControl_EndX: m_data->endX = iVal; break; - case eControl_EndY: m_data->endY = iVal; break; - case eControl_EndZ: m_data->endZ = iVal; break; - } - m_activeDirectEditControl = eControl_Create; - } - } -#endif } void UIScene_DebugCreateSchematic::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled) { #ifdef _WINDOWS64 - if (m_activeDirectEditControl != eControl_Create) return; + if (isDirectEditBlocking()) return; #endif ui.AnimateKeyPress(iPad, key, repeat, pressed, released); @@ -140,7 +132,7 @@ void UIScene_DebugCreateSchematic::handleInput(int iPad, int key, bool repeat, b void UIScene_DebugCreateSchematic::handlePress(F64 controlId, F64 childId) { #ifdef _WINDOWS64 - if (m_activeDirectEditControl != eControl_Create) return; + if (isDirectEditBlocking()) return; #endif switch((int)controlId) { @@ -192,8 +184,7 @@ void UIScene_DebugCreateSchematic::handlePress(F64 controlId, F64 childId) #ifdef _WINDOWS64 if (g_KBMInput.IsKBMActive()) { - m_activeDirectEditControl = m_keyboardCallbackControl; - UIControl_TextInput* input = getTextInputForControl(m_activeDirectEditControl); + UIControl_TextInput* input = getTextInputForControl(m_keyboardCallbackControl); if (input) input->beginDirectEdit(25); } else diff --git a/Minecraft.Client/Common/UI/UIScene_DebugCreateSchematic.h b/Minecraft.Client/Common/UI/UIScene_DebugCreateSchematic.h index f830727a2..e18d9f5d0 100644 --- a/Minecraft.Client/Common/UI/UIScene_DebugCreateSchematic.h +++ b/Minecraft.Client/Common/UI/UIScene_DebugCreateSchematic.h @@ -24,9 +24,6 @@ private: ConsoleSchematicFile::XboxSchematicInitParam *m_data; -#ifdef _WINDOWS64 - eControls m_activeDirectEditControl; -#endif public: UIScene_DebugCreateSchematic(int iPad, void *initData, UILayer *parentLayer); @@ -63,6 +60,8 @@ protected: virtual wstring getMoviePath(); #ifdef _WINDOWS64 + virtual void getDirectEditInputs(vector &inputs); + virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result); virtual bool handleMouseClick(F32 x, F32 y); #endif diff --git a/Minecraft.Client/Common/UI/UIScene_DebugSetCamera.cpp b/Minecraft.Client/Common/UI/UIScene_DebugSetCamera.cpp index 440d51b4c..62ee60b13 100644 --- a/Minecraft.Client/Common/UI/UIScene_DebugSetCamera.cpp +++ b/Minecraft.Client/Common/UI/UIScene_DebugSetCamera.cpp @@ -56,9 +56,6 @@ UIScene_DebugSetCamera::UIScene_DebugSetCamera(int iPad, void *initData, UILayer m_labelCamZ.init(L"CamZ"); m_labelYRotElev.init(L"Y-Rot & Elevation (Degs)"); -#ifdef _WINDOWS64 - m_activeDirectEditControl = eControl_Teleport; // sentinel — no active edit -#endif } wstring UIScene_DebugSetCamera::getMoviePath() @@ -80,30 +77,30 @@ UIControl_TextInput* UIScene_DebugSetCamera::getTextInputForControl(eControls ct } } +void UIScene_DebugSetCamera::getDirectEditInputs(vector &inputs) +{ + inputs.push_back(&m_textInputX); + inputs.push_back(&m_textInputY); + inputs.push_back(&m_textInputZ); + inputs.push_back(&m_textInputYRot); + inputs.push_back(&m_textInputElevation); +} + +void UIScene_DebugSetCamera::onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result) +{ + wstring value = input->getEditBuffer(); + double val = 0; + if (!value.empty()) val = _fromString(value); + + if (input == &m_textInputX) currentPosition->m_camX = val; + else if (input == &m_textInputY) currentPosition->m_camY = val; + else if (input == &m_textInputZ) currentPosition->m_camZ = val; + else if (input == &m_textInputYRot) currentPosition->m_yRot = val; + else if (input == &m_textInputElevation) currentPosition->m_elev = val; +} + bool UIScene_DebugSetCamera::handleMouseClick(F32 x, F32 y) { - // If currently editing, confirm the current edit before processing the click - if (m_activeDirectEditControl != eControl_Teleport) - { - UIControl_TextInput* active = getTextInputForControl(m_activeDirectEditControl); - if (active && active->isDirectEditing()) - { - wstring value = active->getEditBuffer(); - double val = 0; - if (!value.empty()) val = _fromString(value); - switch (m_activeDirectEditControl) - { - case eControl_CamX: currentPosition->m_camX = val; break; - case eControl_CamY: currentPosition->m_camY = val; break; - case eControl_CamZ: currentPosition->m_camZ = val; break; - case eControl_YRot: currentPosition->m_yRot = val; break; - case eControl_Elevation: currentPosition->m_elev = val; break; - } - active->confirmDirectEdit(); - } - m_activeDirectEditControl = eControl_Teleport; - } - UIScene::handleMouseClick(x, y); return true; // always consume to prevent Iggy re-entry on empty space } @@ -112,40 +109,12 @@ bool UIScene_DebugSetCamera::handleMouseClick(F32 x, F32 y) void UIScene_DebugSetCamera::tick() { UIScene::tick(); - -#ifdef _WINDOWS64 - UIControl_TextInput* inputs[] = { &m_textInputX, &m_textInputY, &m_textInputZ, &m_textInputYRot, &m_textInputElevation }; - for (int i = 0; i < 5; i++) - { - UIControl_TextInput::EDirectEditResult result = inputs[i]->tickDirectEdit(); - if (result == UIControl_TextInput::eDirectEdit_Confirmed) - { - wstring value = inputs[i]->getEditBuffer(); - double val = 0; - if (!value.empty()) val = _fromString(value); - eControls ctrl = (eControls)i; // eControl_CamX=0, CamY=1, CamZ=2, YRot=3, Elevation=4 - switch (ctrl) - { - case eControl_CamX: currentPosition->m_camX = val; break; - case eControl_CamY: currentPosition->m_camY = val; break; - case eControl_CamZ: currentPosition->m_camZ = val; break; - case eControl_YRot: currentPosition->m_yRot = val; break; - case eControl_Elevation: currentPosition->m_elev = val; break; - } - m_activeDirectEditControl = eControl_Teleport; - } - else if (result == UIControl_TextInput::eDirectEdit_Cancelled) - { - m_activeDirectEditControl = eControl_Teleport; - } - } -#endif } void UIScene_DebugSetCamera::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled) { #ifdef _WINDOWS64 - if (m_activeDirectEditControl != eControl_Teleport) { handled = true; return; } + if (isDirectEditBlocking()) { handled = true; return; } #endif ui.AnimateKeyPress(iPad, key, repeat, pressed, released); @@ -172,7 +141,7 @@ void UIScene_DebugSetCamera::handleInput(int iPad, int key, bool repeat, bool pr void UIScene_DebugSetCamera::handlePress(F64 controlId, F64 childId) { #ifdef _WINDOWS64 - if (m_activeDirectEditControl != eControl_Teleport) return; + if (isDirectEditBlocking()) return; #endif switch((int)controlId) { @@ -190,8 +159,7 @@ void UIScene_DebugSetCamera::handlePress(F64 controlId, F64 childId) #ifdef _WINDOWS64 if (g_KBMInput.IsKBMActive()) { - m_activeDirectEditControl = m_keyboardCallbackControl; - UIControl_TextInput* input = getTextInputForControl(m_activeDirectEditControl); + UIControl_TextInput* input = getTextInputForControl(m_keyboardCallbackControl); if (input) input->beginDirectEdit(25); } else diff --git a/Minecraft.Client/Common/UI/UIScene_DebugSetCamera.h b/Minecraft.Client/Common/UI/UIScene_DebugSetCamera.h index 95b72f761..d758e049e 100644 --- a/Minecraft.Client/Common/UI/UIScene_DebugSetCamera.h +++ b/Minecraft.Client/Common/UI/UIScene_DebugSetCamera.h @@ -27,7 +27,6 @@ private: eControls m_keyboardCallbackControl; #ifdef _WINDOWS64 - eControls m_activeDirectEditControl; UIControl_TextInput* getTextInputForControl(eControls ctrl); #endif @@ -60,6 +59,8 @@ protected: virtual wstring getMoviePath(); virtual void tick(); #ifdef _WINDOWS64 + virtual void getDirectEditInputs(vector &inputs); + virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result); virtual bool handleMouseClick(F32 x, F32 y); #endif diff --git a/Minecraft.Client/Common/UI/UIScene_LaunchMoreOptionsMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LaunchMoreOptionsMenu.cpp index 7c3ec0138..96dd744e9 100644 --- a/Minecraft.Client/Common/UI/UIScene_LaunchMoreOptionsMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LaunchMoreOptionsMenu.cpp @@ -211,12 +211,6 @@ void UIScene_LaunchMoreOptionsMenu::tick() { UIScene::tick(); -#ifdef _WINDOWS64 - UIControl_TextInput::EDirectEditResult editResult = m_editSeed.tickDirectEdit(); - if (editResult == UIControl_TextInput::eDirectEdit_Confirmed) - m_params->seed = m_editSeed.getEditBuffer(); -#endif - bool bMultiplayerAllowed = ProfileManager.IsSignedInLive(m_params->iPad) && ProfileManager.AllowedToPlayMultiplayer(m_params->iPad); if (bMultiplayerAllowed != m_bMultiplayerAllowed) @@ -264,7 +258,7 @@ void UIScene_LaunchMoreOptionsMenu::handleInput(int iPad, int key, bool repeat, { if(m_bIgnoreInput) return; #ifdef _WINDOWS64 - if (m_editSeed.isDirectEditing() || m_editSeed.getDirectEditCooldown() > 0) return; + if (isDirectEditBlocking()) return; #endif //app.DebugPrintf("UIScene_DebugOverlay handling input for pad %d, key %d, down- %s, pressed- %s, released- %s\n", iPad, key, down?"TRUE":"FALSE", pressed?"TRUE":"FALSE", released?"TRUE":"FALSE"); @@ -581,11 +575,24 @@ int UIScene_LaunchMoreOptionsMenu::KeyboardCompleteSeedCallback(LPVOID lpParam,b return 0; } +#ifdef _WINDOWS64 +void UIScene_LaunchMoreOptionsMenu::getDirectEditInputs(vector &inputs) +{ + inputs.push_back(&m_editSeed); +} + +void UIScene_LaunchMoreOptionsMenu::onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result) +{ + if (result == UIControl_TextInput::eDirectEdit_Confirmed) + m_params->seed = input->getEditBuffer(); +} +#endif + void UIScene_LaunchMoreOptionsMenu::handlePress(F64 controlId, F64 childId) { if(m_bIgnoreInput) return; #ifdef _WINDOWS64 - if (m_editSeed.isDirectEditing() || m_editSeed.getDirectEditCooldown() > 0) return; + if (isDirectEditBlocking()) return; #endif switch((int)controlId) diff --git a/Minecraft.Client/Common/UI/UIScene_LaunchMoreOptionsMenu.h b/Minecraft.Client/Common/UI/UIScene_LaunchMoreOptionsMenu.h index e7da49183..caf9a71f0 100644 --- a/Minecraft.Client/Common/UI/UIScene_LaunchMoreOptionsMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_LaunchMoreOptionsMenu.h @@ -140,6 +140,10 @@ protected: public: virtual void tick(); virtual void handleDestroy(); +#ifdef _WINDOWS64 + virtual void getDirectEditInputs(vector &inputs); + virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result); +#endif // INPUT virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled); virtual void handleFocusChange(F64 controlId, F64 childId); diff --git a/Minecraft.Client/Common/UI/UIScene_SignEntryMenu.cpp b/Minecraft.Client/Common/UI/UIScene_SignEntryMenu.cpp index cfb1624bf..9f049c8c9 100644 --- a/Minecraft.Client/Common/UI/UIScene_SignEntryMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_SignEntryMenu.cpp @@ -18,8 +18,11 @@ UIScene_SignEntryMenu::UIScene_SignEntryMenu(int iPad, void *_initData, UILayer m_bConfirmed = false; m_bIgnoreInput = false; + m_iSignCursorFrame = 0; #ifdef _WINDOWS64 m_iActiveDirectEditLine = -1; + m_bNeedsInitialEdit = true; + m_bSkipTickNav = false; #endif m_buttonConfirm.init(app.GetString(IDS_DONE), eControl_Confirm); @@ -56,6 +59,7 @@ UIScene_SignEntryMenu::UIScene_SignEntryMenu(int iPad, void *_initData, UILayer UIScene_SignEntryMenu::~UIScene_SignEntryMenu() { + m_sign->SetSelectedLine(-1); m_parentLayer->removeComponent(eUIComponent_MenuBackground); } @@ -81,17 +85,78 @@ void UIScene_SignEntryMenu::tick() UIScene::tick(); #ifdef _WINDOWS64 - for (int i = 0; i < 4; i++) - m_textInputLines[i].tickDirectEdit(); - - if (m_iActiveDirectEditLine >= 0) + // On first tick, auto-start editing line 1 if KBM is active (Java-style flow) + if (m_bNeedsInitialEdit) { - UIControl_TextInput& line = m_textInputLines[m_iActiveDirectEditLine]; - if (!line.isDirectEditing()) - m_iActiveDirectEditLine = -1; + m_bNeedsInitialEdit = false; + if (g_KBMInput.IsKBMActive()) + { + SetFocusToElement(eControl_Line1); + m_iActiveDirectEditLine = 0; + m_textInputLines[0].beginDirectEdit(15); + } } + + // UP/DOWN navigation — must happen after tickDirectEdit (so typed chars are consumed) + // and before sign cursor update (so the cursor is correct for this frame's render) + // m_bSkipTickNav prevents double-processing when handleInput auto-started editing this frame + if (m_iActiveDirectEditLine >= 0 && !m_bSkipTickNav) + { + int navDir = 0; + if (g_KBMInput.IsKeyPressed(VK_DOWN)) navDir = 1; + else if (g_KBMInput.IsKeyPressed(VK_UP)) navDir = -1; + + if (navDir != 0) + { + int newLine = m_iActiveDirectEditLine + navDir; + if (newLine >= eControl_Line1 && newLine <= eControl_Line4) + { + m_textInputLines[m_iActiveDirectEditLine].confirmDirectEdit(); + SetFocusToElement(newLine); + m_iActiveDirectEditLine = newLine; + m_textInputLines[newLine].beginDirectEdit(15); + } + else if (navDir > 0) + { + m_textInputLines[m_iActiveDirectEditLine].confirmDirectEdit(); + SetFocusToElement(eControl_Confirm); + m_iActiveDirectEditLine = -1; + } + } + } + m_bSkipTickNav = false; + + if (m_iActiveDirectEditLine >= 0 && !m_textInputLines[m_iActiveDirectEditLine].isDirectEditing()) + m_iActiveDirectEditLine = -1; #endif + // Blinking > text < cursor on the 3D sign + m_iSignCursorFrame++; + if (m_iSignCursorFrame / 6 % 2 == 0) + { +#ifdef _WINDOWS64 + if (m_iActiveDirectEditLine >= 0) + m_sign->SetSelectedLine(m_iActiveDirectEditLine); + else +#endif + { + int focusedLine = -1; + for (int i = eControl_Line1; i <= eControl_Line4; i++) + { + if (controlHasFocus(i)) + { + focusedLine = i; + break; + } + } + m_sign->SetSelectedLine(focusedLine); + } + } + else + { + m_sign->SetSelectedLine(-1); + } + if(m_bConfirmed) { m_bConfirmed = false; @@ -123,28 +188,7 @@ void UIScene_SignEntryMenu::handleInput(int iPad, int key, bool repeat, bool pre { if(m_bConfirmed || m_bIgnoreInput) return; #ifdef _WINDOWS64 - if (m_iActiveDirectEditLine >= 0) - { - // Mouse click while editing — confirm current line and let click through - if (key == ACTION_MENU_OK && pressed && g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_LEFT)) - { - m_textInputLines[m_iActiveDirectEditLine].confirmDirectEdit(); - m_iActiveDirectEditLine = -1; - } - else - { - handled = true; - return; - } - } - for (int i = 0; i < 4; i++) - { - if (m_textInputLines[i].getDirectEditCooldown() > 0) - { - handled = true; - return; - } - } + if (isDirectEditBlocking()) { handled = true; return; } #endif ui.AnimateKeyPress(iPad, key, repeat, pressed, released); @@ -171,14 +215,98 @@ void UIScene_SignEntryMenu::handleInput(int iPad, int key, bool repeat, bool pre #ifdef __ORBIS__ case ACTION_MENU_TOUCHPAD_PRESS: #endif + sendInputToMovie(key, repeat, pressed, released); + handled = true; + break; case ACTION_MENU_UP: case ACTION_MENU_DOWN: sendInputToMovie(key, repeat, pressed, released); +#ifdef _WINDOWS64 + // Auto-start editing if focus moved to a line (e.g. UP from Confirm) + if (g_KBMInput.IsKBMActive()) + { + for (int i = eControl_Line1; i <= eControl_Line4; i++) + { + if (controlHasFocus(i)) + { + m_iActiveDirectEditLine = i; + m_textInputLines[i].beginDirectEdit(15); + m_bSkipTickNav = true; + break; + } + } + } +#endif handled = true; break; } } +#ifdef _WINDOWS64 +void UIScene_SignEntryMenu::getDirectEditInputs(vector &inputs) +{ + for (int i = 0; i < 4; i++) + inputs.push_back(&m_textInputLines[i]); +} + +void UIScene_SignEntryMenu::onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result) +{ + int line = -1; + for (int i = 0; i < 4; i++) + { + if (input == &m_textInputLines[i]) { line = i; break; } + } + if (line != m_iActiveDirectEditLine) return; + + if (result == UIControl_TextInput::eDirectEdit_Confirmed) + { + int newLine = line + 1; + if (newLine <= eControl_Line4) + { + SetFocusToElement(newLine); + m_iActiveDirectEditLine = newLine; + m_textInputLines[newLine].beginDirectEdit(15); + } + else + { + m_iActiveDirectEditLine = -1; + m_bConfirmed = true; + } + } + else if (result == UIControl_TextInput::eDirectEdit_Cancelled) + { + m_iActiveDirectEditLine = -1; + wstring temp = L""; + for (int j = 0; j < 4; j++) + m_sign->SetMessage(j, temp); + navigateBack(); + ui.PlayUISFX(eSFX_Back); + } +} + +bool UIScene_SignEntryMenu::handleMouseClick(F32 x, F32 y) +{ + if (m_iActiveDirectEditLine >= 0) + { + // During direct edit, only the Done button is clickable. + // Hit-test it manually — all other clicks are consumed but ignored. + m_buttonConfirm.UpdateControl(); + S32 cx = m_buttonConfirm.getXPos(); + S32 cy = m_buttonConfirm.getYPos(); + S32 cw = m_buttonConfirm.getWidth(); + S32 ch = m_buttonConfirm.getHeight(); + if (cw > 0 && ch > 0 && x >= cx && x <= cx + cw && y >= cy && y <= cy + ch) + { + m_textInputLines[m_iActiveDirectEditLine].confirmDirectEdit(); + m_iActiveDirectEditLine = -1; + m_bConfirmed = true; + } + return true; + } + return UIScene::handleMouseClick(x, y); +} +#endif + int UIScene_SignEntryMenu::KeyboardCompleteCallback(LPVOID lpParam,bool bRes) { UIScene_SignEntryMenu *pClass=(UIScene_SignEntryMenu *)lpParam; @@ -203,13 +331,7 @@ int UIScene_SignEntryMenu::KeyboardCompleteCallback(LPVOID lpParam,bool bRes) void UIScene_SignEntryMenu::handlePress(F64 controlId, F64 childId) { #ifdef _WINDOWS64 - // After direct edit ends (Enter/Escape), skip input for a few frames - // to absorb the matching ACTION_MENU_OK that would re-open the edit. - for (int i = 0; i < 4; i++) - { - if (m_textInputLines[i].isDirectEditing() || m_textInputLines[i].getDirectEditCooldown() > 0) - return; - } + if (isDirectEditBlocking()) return; #endif switch((int)controlId) { @@ -227,8 +349,12 @@ void UIScene_SignEntryMenu::handlePress(F64 controlId, F64 childId) #ifdef _WINDOWS64 if (g_KBMInput.IsKBMActive()) { - m_iActiveDirectEditLine = m_iEditingLine; - m_textInputLines[m_iEditingLine].beginDirectEdit(15); + // Only start editing from keyboard (Enter on focused line), not mouse clicks + if (!g_KBMInput.IsMouseButtonPressed(KeyboardMouseInput::MOUSE_LEFT)) + { + m_iActiveDirectEditLine = m_iEditingLine; + m_textInputLines[m_iEditingLine].beginDirectEdit(15); + } } else { diff --git a/Minecraft.Client/Common/UI/UIScene_SignEntryMenu.h b/Minecraft.Client/Common/UI/UIScene_SignEntryMenu.h index 68767be74..4e1c2a5ba 100644 --- a/Minecraft.Client/Common/UI/UIScene_SignEntryMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_SignEntryMenu.h @@ -21,8 +21,11 @@ private: int m_iEditingLine; bool m_bConfirmed; bool m_bIgnoreInput; + int m_iSignCursorFrame; #ifdef _WINDOWS64 int m_iActiveDirectEditLine; + bool m_bNeedsInitialEdit; + bool m_bSkipTickNav; #endif UIControl_Button m_buttonConfirm; @@ -53,6 +56,11 @@ protected: public: // INPUT virtual void handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled); +#ifdef _WINDOWS64 + virtual void getDirectEditInputs(vector &inputs); + virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result); + virtual bool handleMouseClick(F32 x, F32 y); +#endif protected: void handlePress(F64 controlId, F64 childId); From fbadb6ac392cf64b03f99ab87a64336acd847077 Mon Sep 17 00:00:00 2001 From: MrTheShy <49885496+MrTheShy@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:13:39 +0100 Subject: [PATCH 4/4] Remap scroll wheel to LEFT/RIGHT for horizontal controls The scroll wheel was always remapped to UP/DOWN, which is fine for vertical lists but useless on horizontal controls like sliders and the texture pack selector. Track whether the mouse is hovering a horizontal control during the hover hit-test (new bool m_bMouseHoverHorizontalList, set for eTexturePackList and eSlider). When the flag is set, handleKeyPress emits LEFT/RIGHT instead of UP/DOWN for wheel events. TexturePackList is also now part of the mouse hover system with proper hit-testing, relative-coord SetTouchFocus and GetRealHeight for accurate bounds. --- Minecraft.Client/Common/UI/UIController.cpp | 33 ++++++++++++++++++--- Minecraft.Client/Common/UI/UIController.h | 1 + 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index 3907ed45c..489a08e82 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -3,6 +3,7 @@ #include "UI.h" #include "UIScene.h" #include "UIControl_Slider.h" +#include "UIControl_TexturePackList.h" #include "..\..\..\Minecraft.World\StringHelpers.h" #include "..\..\LocalPlayer.h" #include "..\..\DLCTexturePack.h" @@ -238,6 +239,7 @@ UIController::UIController() m_mouseDraggingSliderScene = eUIScene_COUNT; m_mouseDraggingSliderId = -1; m_mouseClickConsumedByScene = false; + m_bMouseHoverHorizontalList = false; m_lastHoverMouseX = -1; m_lastHoverMouseY = -1; m_accumulatedTicks = 0; @@ -856,6 +858,7 @@ void UIController::tickInput() // use their own SetTouchFocus for Flash-side hit testing. if (mouseMoved) { + m_bMouseHoverHorizontalList = false; vector *controls = pScene->GetControls(); if (controls) { @@ -871,7 +874,7 @@ void UIController::tickInput() UIControl::eUIControlType type = ctrl->getControlType(); if (type != UIControl::eButton && type != UIControl::eTextInput && type != UIControl::eCheckBox && type != UIControl::eSlider && - type != UIControl::eButtonList) + type != UIControl::eButtonList && type != UIControl::eTexturePackList) continue; // If the scene has an active panel (e.g. tab menus), @@ -884,6 +887,10 @@ void UIController::tickInput() S32 cy = ctrl->getYPos() + panelOffsetY; S32 cw = ctrl->getWidth(); S32 ch = ctrl->getHeight(); + // TexturePackList origin is where the slot area starts, + // not the top-left of the whole control — use GetRealHeight. + if (type == UIControl::eTexturePackList) + ch = ((UIControl_TexturePackList *)ctrl)->GetRealHeight(); if (cw <= 0 || ch <= 0) continue; @@ -901,12 +908,27 @@ void UIController::tickInput() hitCtrl = NULL; break; // ButtonList takes priority } + if (type == UIControl::eTexturePackList) + { + // TexturePackList expects coords relative to its origin. + UIControl_TexturePackList *pList = (UIControl_TexturePackList *)ctrl; + pScene->SetFocusToElement(ctrl->getId()); + pList->SetTouchFocus( + (S32)(sceneMouseX - cx), (S32)(sceneMouseY - cy), false); + m_bMouseHoverHorizontalList = true; + hitControlId = -1; + hitArea = INT_MAX; + hitCtrl = NULL; + break; + } S32 area = cw * ch; if (area < hitArea) { hitControlId = ctrl->getId(); hitArea = area; hitCtrl = ctrl; + if (type == UIControl::eSlider) + m_bMouseHoverHorizontalList = true; } } } @@ -1344,11 +1366,14 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key) down = true; } - // Remap scroll wheel to UP/DOWN so all scenes get it without - // needing per-scene OTHER_STICK handling. + // Remap scroll wheel to navigation actions. Use LEFT/RIGHT when + // hovering a horizontal list (e.g. TexturePackList), UP/DOWN otherwise. if (pressed && g_KBMInput.IsKBMActive()) { - key = (key == ACTION_MENU_OTHER_STICK_UP) ? ACTION_MENU_UP : ACTION_MENU_DOWN; + if (m_bMouseHoverHorizontalList) + key = (key == ACTION_MENU_OTHER_STICK_UP) ? ACTION_MENU_LEFT : ACTION_MENU_RIGHT; + else + key = (key == ACTION_MENU_OTHER_STICK_UP) ? ACTION_MENU_UP : ACTION_MENU_DOWN; } } } diff --git a/Minecraft.Client/Common/UI/UIController.h b/Minecraft.Client/Common/UI/UIController.h index 46983c100..7a0c6075c 100644 --- a/Minecraft.Client/Common/UI/UIController.h +++ b/Minecraft.Client/Common/UI/UIController.h @@ -161,6 +161,7 @@ private: EUIScene m_mouseDraggingSliderScene; int m_mouseDraggingSliderId; bool m_mouseClickConsumedByScene; + bool m_bMouseHoverHorizontalList; int m_lastHoverMouseX; int m_lastHoverMouseY; //bool m_bSysUIShowing;