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.
This commit is contained in:
MrTheShy 2026-03-06 15:42:48 +01:00
parent 8583e99d94
commit 8b23c71b93
16 changed files with 381 additions and 177 deletions

View file

@ -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);
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -447,6 +447,19 @@ void UIScene::tick()
IggyPlayerTickRS( swf );
m_hasTickedOnce = true;
}
#ifdef _WINDOWS64
{
vector<UIControl_TextInput*> 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<UIControl_TextInput*> 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<UIControl_TextInput*> 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<UIControl *> *controls = GetControls();
if (!controls) return false;

View file

@ -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<UIControl_TextInput*> &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);

View file

@ -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<UIControl_TextInput*> &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())

View file

@ -55,6 +55,10 @@ protected:
virtual UIControl *getSection(ESceneSection eSection);
#ifdef _WINDOWS64
virtual void getDirectEditInputs(vector<UIControl_TextInput*> &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);

View file

@ -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<UIControl_TextInput*> &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

View file

@ -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<UIControl_TextInput*> &inputs);
virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result);
#endif
private:
void StartSharedLaunchFlow();

View file

@ -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<UIControl_TextInput*> &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<int>(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<int>(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

View file

@ -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<UIControl_TextInput*> &inputs);
virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result);
virtual bool handleMouseClick(F32 x, F32 y);
#endif

View file

@ -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<UIControl_TextInput*> &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<double>(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<double>(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<double>(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

View file

@ -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<UIControl_TextInput*> &inputs);
virtual void onDirectEditFinished(UIControl_TextInput *input, UIControl_TextInput::EDirectEditResult result);
virtual bool handleMouseClick(F32 x, F32 y);
#endif

View file

@ -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<UIControl_TextInput*> &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)

View file

@ -140,6 +140,10 @@ protected:
public:
virtual void tick();
virtual void handleDestroy();
#ifdef _WINDOWS64
virtual void getDirectEditInputs(vector<UIControl_TextInput*> &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);

View file

@ -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<UIControl_TextInput*> &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
{

View file

@ -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<UIControl_TextInput*> &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);