From bece154d2b46cb0074a6afde735c3b6e8e551e1f Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 16 Nov 2019 14:50:14 +1100 Subject: [PATCH] Properly use full text of HTML input, instead of hackily trying to check length difference to fake presses/backspaces. This should fix text input not really working with firefox for android, and allow using autocomplete in chrome for android --- src/Gui.c | 128 ++++++++++++++++++++++++++++++++------------------ src/Input.c | 13 +---- src/Menus.c | 41 ++++++++++++++-- src/Screens.c | 20 +++++--- src/Widgets.c | 6 +++ src/Widgets.h | 5 ++ src/Window.c | 50 ++++++++------------ 7 files changed, 163 insertions(+), 100 deletions(-) diff --git a/src/Gui.c b/src/Gui.c index 411d34b3e..4be04f088 100644 --- a/src/Gui.c +++ b/src/Gui.c @@ -120,53 +120,6 @@ static void OnContextRecreated(void* obj) { } } -static void OnFontChanged(void* obj) { Gui_RefreshAll(); } - -static void OnFileChanged(void* obj, struct Stream* stream, const String* name) { - if (String_CaselessEqualsConst(name, "gui.png")) { - Game_UpdateTexture(&Gui_GuiTex, stream, name, NULL); - } else if (String_CaselessEqualsConst(name, "gui_classic.png")) { - Game_UpdateTexture(&Gui_GuiClassicTex, stream, name, NULL); - } else if (String_CaselessEqualsConst(name, "icons.png")) { - Game_UpdateTexture(&Gui_IconsTex, stream, name, NULL); - } -} - -static void Gui_Init(void) { - Event_RegisterVoid(&ChatEvents.FontChanged, NULL, OnFontChanged); - Event_RegisterEntry(&TextureEvents.FileChanged, NULL, OnFileChanged); - Event_RegisterVoid(&GfxEvents.ContextLost, NULL, OnContextLost); - Event_RegisterVoid(&GfxEvents.ContextRecreated, NULL, OnContextRecreated); - Gui_LoadOptions(); - Gui_ShowDefault(); -} - -static void Gui_Reset(void) { - /* TODO:Should we reset all screens here.. ? */ -} - -static void Gui_Free(void) { - Event_UnregisterVoid(&ChatEvents.FontChanged, NULL, OnFontChanged); - Event_UnregisterEntry(&TextureEvents.FileChanged, NULL, OnFileChanged); - Event_UnregisterVoid(&GfxEvents.ContextLost, NULL, OnContextLost); - Event_UnregisterVoid(&GfxEvents.ContextRecreated, NULL, OnContextRecreated); - - while (Gui_ScreensCount) Gui_Remove(Gui_Screens[0]); - - Gfx_DeleteTexture(&Gui_GuiTex); - Gfx_DeleteTexture(&Gui_GuiClassicTex); - Gfx_DeleteTexture(&Gui_IconsTex); - Gui_Reset(); -} - -struct IGameComponent Gui_Component = { - Gui_Init, /* Init */ - Gui_Free, /* Free */ - Gui_Reset, /* Reset */ - NULL, /* OnNewMap */ - NULL, /* OnNewMapLoaded */ -}; - void Gui_RefreshAll(void) { OnContextLost(NULL); OnContextRecreated(NULL); @@ -373,3 +326,84 @@ void TextAtlas_AddInt(struct TextAtlas* atlas, int value, VertexP3fT2fC4b** vert TextAtlas_Add(atlas, digits[i] - '0' , vertices); } } + + +/*########################################################################################################################* +*------------------------------------------------------Gui component------------------------------------------------------* +*#########################################################################################################################*/ +static void OnFontChanged(void* obj) { Gui_RefreshAll(); } + +static void OnFileChanged(void* obj, struct Stream* stream, const String* name) { + if (String_CaselessEqualsConst(name, "gui.png")) { + Game_UpdateTexture(&Gui_GuiTex, stream, name, NULL); + } else if (String_CaselessEqualsConst(name, "gui_classic.png")) { + Game_UpdateTexture(&Gui_GuiClassicTex, stream, name, NULL); + } else if (String_CaselessEqualsConst(name, "icons.png")) { + Game_UpdateTexture(&Gui_IconsTex, stream, name, NULL); + } +} + +static void OnKeyPress(void* obj, int keyChar) { + struct Screen* s; + int i; + + for (i = 0; i < Gui_ScreensCount; i++) { + s = Gui_Screens[i]; + if (s->VTABLE->HandlesKeyPress(s, keyChar)) return; + } +} + +#ifdef CC_BUILD_TOUCH +static void OnTextChanged(void* obj, const String* str) { + struct Screen* s; + int i; + + for (i = 0; i < Gui_ScreensCount; i++) { + s = Gui_Screens[i]; + if (s->VTABLE->HandlesTextChanged(s, str)) return; + } +} +#endif + +static void Gui_Init(void) { + Event_RegisterVoid(&ChatEvents.FontChanged, NULL, OnFontChanged); + Event_RegisterEntry(&TextureEvents.FileChanged, NULL, OnFileChanged); + Event_RegisterVoid(&GfxEvents.ContextLost, NULL, OnContextLost); + Event_RegisterVoid(&GfxEvents.ContextRecreated, NULL, OnContextRecreated); + Event_RegisterInt(&InputEvents.Press, NULL, OnKeyPress); +#ifdef CC_BUILD_TOUCH + Event_RegisterString(&InputEvents.TextChanged, NULL, OnTextChanged); +#endif + Gui_LoadOptions(); + Gui_ShowDefault(); +} + +static void Gui_Reset(void) { + /* TODO:Should we reset all screens here.. ? */ +} + +static void Gui_Free(void) { + Event_UnregisterVoid(&ChatEvents.FontChanged, NULL, OnFontChanged); + Event_UnregisterEntry(&TextureEvents.FileChanged, NULL, OnFileChanged); + Event_UnregisterVoid(&GfxEvents.ContextLost, NULL, OnContextLost); + Event_UnregisterVoid(&GfxEvents.ContextRecreated, NULL, OnContextRecreated); + Event_UnregisterInt(&InputEvents.Press, NULL, OnKeyPress); +#ifdef CC_BUILD_TOUCH + Event_UnregisterString(&InputEvents.TextChanged, NULL, OnTextChanged); +#endif + + while (Gui_ScreensCount) Gui_Remove(Gui_Screens[0]); + + Gfx_DeleteTexture(&Gui_GuiTex); + Gfx_DeleteTexture(&Gui_GuiClassicTex); + Gfx_DeleteTexture(&Gui_IconsTex); + Gui_Reset(); +} + +struct IGameComponent Gui_Component = { + Gui_Init, /* Init */ + Gui_Free, /* Free */ + Gui_Reset, /* Reset */ + NULL, /* OnNewMap */ + NULL, /* OnNewMapLoaded */ +}; \ No newline at end of file diff --git a/src/Input.c b/src/Input.c index 59ebb5607..b6a048e16 100644 --- a/src/Input.c +++ b/src/Input.c @@ -992,7 +992,7 @@ static void HandleInputDown(void* obj, int key, cc_bool was) { /* Can't do this in KeyUp, because pressing escape without having */ /* explicitly disabled mouse lock means a KeyUp event isn't sent. */ /* But switching to pause screen disables mouse lock, causing a KeyUp */ - /* event to be sent, triggering the active->closable case which immediately + /* event to be sent, triggering the active->closable case which immediately */ /* closes the pause screen. Hence why the next KeyUp must be supressed. */ suppressEscape = true; #endif @@ -1033,23 +1033,12 @@ static void HandleInputUp(void* obj, int key) { if (key == KeyBinds[KEYBIND_PICK_BLOCK]) MouseStateRelease(MOUSE_MIDDLE); } -static void HandleKeyPress(void* obj, int keyChar) { - struct Screen* s; - int i; - - for (i = 0; i < Gui_ScreensCount; i++) { - s = Gui_Screens[i]; - if (s->VTABLE->HandlesKeyPress(s, keyChar)) return; - } -} - void InputHandler_Init(void) { Event_RegisterMove(&PointerEvents.Moved, NULL, HandlePointerMove); Event_RegisterInt(&PointerEvents.Down, NULL, HandlePointerDown); Event_RegisterInt(&PointerEvents.Up, NULL, HandlePointerUp); Event_RegisterInt(&InputEvents.Down, NULL, HandleInputDown); Event_RegisterInt(&InputEvents.Up, NULL, HandleInputUp); - Event_RegisterInt(&InputEvents.Press, NULL, HandleKeyPress); Event_RegisterFloat(&InputEvents.Wheel, NULL, HandleMouseWheel); Event_RegisterVoid(&UserEvents.HackPermissionsChanged, NULL, InputHandler_CheckZoomFov); diff --git a/src/Menus.c b/src/Menus.c index 04b122138..c0ed662db 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -812,6 +812,14 @@ static int EditHotkeyScreen_KeyPress(void* screen, char keyChar) { return true; } +static int EditHotkeyScreen_TextChanged(void* screen, const String* str) { +#ifdef CC_BUILD_TOUCH + struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen; + InputWidget_SetAndSyncText(&s->input.base, str); +#endif + return true; +} + static int EditHotkeyScreen_KeyDown(void* screen, int key) { struct EditHotkeyScreen* s = (struct EditHotkeyScreen*)screen; if (s->selectedI >= 0) { @@ -887,7 +895,7 @@ static void EditHotkeyScreen_Init(void* screen) { static const struct ScreenVTABLE EditHotkeyScreen_VTABLE = { EditHotkeyScreen_Init, EditHotkeyScreen_Render, Menu_CloseKeyboard, - EditHotkeyScreen_KeyDown, Screen_TInput, EditHotkeyScreen_KeyPress, Screen_TText, + EditHotkeyScreen_KeyDown, Screen_TInput, EditHotkeyScreen_KeyPress, EditHotkeyScreen_TextChanged, Menu_PointerDown, Screen_TPointer, Menu_PointerMove, Screen_TMouseScroll, Menu_Layout, EditHotkeyScreen_ContextLost, EditHotkeyScreen_ContextRecreated }; @@ -993,6 +1001,14 @@ static int GenLevelScreen_KeyPress(void* screen, char keyChar) { return true; } +static int GenLevelScreen_TextChanged(void* screen, const String* str) { +#ifdef CC_BUILD_TOUCH + struct GenLevelScreen* s = (struct GenLevelScreen*)screen; + if (s->selected) InputWidget_SetAndSyncText(&s->selected->base, str); +#endif + return true; +} + static int GenLevelScreen_PointerDown(void* screen, int id, int x, int y) { struct GenLevelScreen* s = (struct GenLevelScreen*)screen; int i = Menu_DoPointerDown(screen, id, x, y); @@ -1058,7 +1074,7 @@ static void GenLevelScreen_Init(void* screen) { static const struct ScreenVTABLE GenLevelScreen_VTABLE = { GenLevelScreen_Init, MenuScreen_Render, Menu_CloseKeyboard, - GenLevelScreen_KeyDown, Screen_TInput, GenLevelScreen_KeyPress, Screen_TText, + GenLevelScreen_KeyDown, Screen_TInput, GenLevelScreen_KeyPress, GenLevelScreen_TextChanged, GenLevelScreen_PointerDown, Screen_TPointer, Menu_PointerMove, Screen_TMouseScroll, Menu_Layout, GenLevelScreen_ContextLost, GenLevelScreen_ContextRecreated }; @@ -1320,6 +1336,15 @@ static int SaveLevelScreen_KeyPress(void* screen, char keyChar) { return true; } +static int SaveLevelScreen_TextChanged(void* screen, const String* str) { +#ifdef CC_BUILD_TOUCH + struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen; + SaveLevelScreen_RemoveOverwrites(s); + InputWidget_SetAndSyncText(&s->input.base, str); +#endif + return true; +} + static int SaveLevelScreen_KeyDown(void* screen, int key) { struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen; if (Elem_HandlesKeyDown(&s->input.base, key)) { @@ -1386,7 +1411,7 @@ static void SaveLevelScreen_Init(void* screen) { static const struct ScreenVTABLE SaveLevelScreen_VTABLE = { SaveLevelScreen_Init, SaveLevelScreen_Render, Menu_CloseKeyboard, - SaveLevelScreen_KeyDown, Screen_TInput, SaveLevelScreen_KeyPress, Screen_TText, + SaveLevelScreen_KeyDown, Screen_TInput, SaveLevelScreen_KeyPress, SaveLevelScreen_TextChanged, Menu_PointerDown, Screen_TPointer, Menu_PointerMove, Screen_TMouseScroll, Menu_Layout, SaveLevelScreen_ContextLost, SaveLevelScreen_ContextRecreated }; @@ -1984,6 +2009,14 @@ static int MenuOptionsScreen_KeyPress(void* screen, char keyChar) { return true; } +static int MenuOptionsScreen_TextChanged(void* screen, const String* str) { +#ifdef CC_BUILD_TOUCH + struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen; + if (s->activeI >= 0) InputWidget_SetAndSyncText(&s->input.base, str); +#endif + return true; +} + static int MenuOptionsScreen_KeyDown(void* screen, int key) { struct MenuOptionsScreen* s = (struct MenuOptionsScreen*)screen; if (s->activeI >= 0) { @@ -2185,7 +2218,7 @@ static void MenuOptionsScreen_ContextRecreated(void* screen) { static const struct ScreenVTABLE MenuOptionsScreen_VTABLE = { MenuOptionsScreen_Init, MenuOptionsScreen_Render, MenuOptionsScreen_Free, - MenuOptionsScreen_KeyDown, Screen_TInput, MenuOptionsScreen_KeyPress, Screen_TText, + MenuOptionsScreen_KeyDown, Screen_TInput, MenuOptionsScreen_KeyPress, MenuOptionsScreen_TextChanged, Menu_PointerDown, Screen_TPointer, MenuOptionsScreen_PointerMove, Screen_TMouseScroll, MenuOptionsScreen_Layout, MenuOptionsScreen_ContextLost, MenuOptionsScreen_ContextRecreated }; diff --git a/src/Screens.c b/src/Screens.c index 287514551..e817b6599 100644 --- a/src/Screens.c +++ b/src/Screens.c @@ -32,7 +32,7 @@ int Screen_FPointer(void* s, int id, int x, int y) { return false; } int Screen_TInput(void* s, int key) { return true; } int Screen_TKeyPress(void* s, char keyChar) { return true; } -int Screen_TText(void* s, const String* str) { return false; } +int Screen_TText(void* s, const String* str) { return true; } int Screen_TMouseScroll(void* s, float delta) { return true; } int Screen_TPointer(void* s, int id, int x, int y) { return true; } static void Screen_NullFunc(void* screen) { } @@ -624,6 +624,17 @@ static int ChatScreen_KeyPress(void* screen, char keyChar) { return true; } +static int ChatScreen_TextChanged(void* screen, const String* str) { +#ifdef CC_BUILD_TOUCH + struct ChatScreen* s = (struct ChatScreen*)screen; + if (!s->grabsInput) return false; + + InputWidget_SetAndSyncText(&s->input.base, str); + ChatScreen_UpdateChatYOffsets(s); +#endif + return true; +} + static int ChatScreen_KeyDown(void* screen, int key) { static const String slash = String_FromConst("/"); struct ChatScreen* s = (struct ChatScreen*)screen; @@ -813,7 +824,7 @@ static void ChatScreen_Free(void* screen) { static const struct ScreenVTABLE ChatScreen_VTABLE = { ChatScreen_Init, ChatScreen_Render, ChatScreen_Free, - ChatScreen_KeyDown, ChatScreen_KeyUp, ChatScreen_KeyPress, Screen_TText, + ChatScreen_KeyDown, ChatScreen_KeyUp, ChatScreen_KeyPress, ChatScreen_TextChanged, ChatScreen_PointerDown, Screen_FPointer, Screen_FPointer, ChatScreen_MouseScroll, ChatScreen_Layout, ChatScreen_ContextLost, ChatScreen_ContextRecreated }; @@ -828,12 +839,7 @@ void ChatScreen_Show(void) { void ChatScreen_OpenInput(const String* text) { struct ChatScreen* s = &ChatScreen_Instance; -#ifdef CC_BUILD_TOUCH - /* TODO: This is the wrong approach. need an event for all text input. */ - s->suppressNextPress = !Input_TouchMode; -#else s->suppressNextPress = true; -#endif s->grabsInput = true; Camera_CheckFocus(); Window_OpenKeyboard(); diff --git a/src/Widgets.c b/src/Widgets.c index 55cb07c86..74924a249 100644 --- a/src/Widgets.c +++ b/src/Widgets.c @@ -1135,6 +1135,12 @@ void InputWidget_UpdateText(struct InputWidget* w) { InputWidget_UpdateCaret(w); } +void InputWidget_SetAndSyncText(struct InputWidget* w, const String* str) { + InputWidget_Clear(w); + InputWidget_AppendString(w, str); + Window_SetKeyboardText(&w->text); +} + static void InputWidget_Free(void* widget) { struct InputWidget* w = (struct InputWidget*)widget; Gfx_DeleteTexture(&w->inputTex.ID); diff --git a/src/Widgets.h b/src/Widgets.h index 6d2d795b5..06357e018 100644 --- a/src/Widgets.h +++ b/src/Widgets.h @@ -130,6 +130,11 @@ CC_NOINLINE void InputWidget_AppendString(struct InputWidget* w, const String* t CC_NOINLINE void InputWidget_Append(struct InputWidget* w, char c); /* Redraws text and recalculates associated state. */ CC_NOINLINE void InputWidget_UpdateText(struct InputWidget* w); +/* Shorthand for InputWidget_Clear followed by InputWidget_AppendString, */ +/* then calls Window_SetKeyboardText with the text in the input widget. */ +/* This way native text input state stays synchronised with the input widget. */ +/* (e.g. may only accept numerical input, so 'c' gets stripped from str) */ +CC_NOINLINE void InputWidget_SetAndSyncText(struct InputWidget* w, const String* str); struct MenuInputDesc; diff --git a/src/Window.c b/src/Window.c index 4ae189da0..95042c455 100644 --- a/src/Window.c +++ b/src/Window.c @@ -3004,24 +3004,22 @@ static EM_BOOL Window_Key(int type, const EmscriptenKeyboardEvent* ev , void* da (key >= KEY_INSERT && key <= KEY_MENU) || (key >= KEY_ENTER && key <= KEY_NUMLOCK && key != KEY_SPACE); } -/* reused for touch keyboard input down in Window_OpenKeyboard */ -EMSCRIPTEN_KEEPALIVE void Window_ProcessKeyChar(int charCode) { - char keyChar; - if (Convert_TryUnicodeToCP437(charCode, &keyChar)) { - Event_RaiseInt(&InputEvents.Press, keyChar); - } -} - static EM_BOOL Window_KeyPress(int type, const EmscriptenKeyboardEvent* ev, void* data) { Window_CorrectFocus(); /* When on-screen keyboard is open, we don't want to intercept any key presses, */ /* because they should be sent to the HTML text input instead. */ - /* If any keys are intercepted, this causes attempting to backspace all text */ - /* later to not actually backspace everything. (because the HTML text input */ - /* does not have these intercepted key presses in its text buffer) */ - /* (e.g. chrome for android sends keypresses sometimes for '0' to '9' keys) */ + /* (Chrome for android sends keypresses sometimes for '0' to '9' keys) */ + /* - If any keys are intercepted, this causes the HTML text input to become */ + /* desynchronised from the chat/menu input widget the user sees in game. */ + /* - This causes problems such as attempting to backspace all text later to */ + /* not actually backspace everything. (because the HTML text input does not */ + /* have these intercepted key presses in its text buffer) */ if (keyboardOpen) return false; - Window_ProcessKeyChar(ev->charCode); + + char keyChar; + if (Convert_TryUnicodeToCP437(ev->charCode, &keyChar)) { + Event_RaiseInt(&InputEvents.Press, keyChar); + } return true; } @@ -3200,9 +3198,14 @@ void Window_AllocFramebuffer(Bitmap* bmp) { } void Window_DrawFramebuffer(Rect2D r) { } void Window_FreeFramebuffer(Bitmap* bmp) { } -EMSCRIPTEN_KEEPALIVE void SendFakeBackspace(void) { - Input_SetPressed(KEY_BACKSPACE, true); - Input_SetPressed(KEY_BACKSPACE, false); +EMSCRIPTEN_KEEPALIVE void Window_OnTextChanged(const char* src) { + char buffer[800]; + int len; + String str; + + String_InitArray(str, buffer); + String_AppendUtf8(&str, src, String_CalcLen(src, 800)); + Event_RaiseString(&InputEvents.TextChanged, &str); } void Window_OpenKeyboard(void) { @@ -3215,23 +3218,10 @@ void Window_OpenKeyboard(void) { if (!elem) { elem = document.createElement('textarea'); elem.setAttribute('style', 'position:absolute; left:0; top:0; width:100%; height:100%; opacity:0.3; resize:none; pointer-events:none;'); - elem.setAttribute('autocomplete', 'off'); - elem.setAttribute('autocorrect', 'off'); - var oldLen = 0|0; elem.addEventListener("input", function(ev) { - var str = ev.target.value; - if (str.length > oldLen) { - for (var i = oldLen; i < str.length; i++) { - ccall('Window_ProcessKeyChar', 'void', ['number'], [str.charCodeAt(i)]); - } - } else { - for (var i = str.length; i < oldLen; i++) { - ccall('SendFakeBackspace', 'void', [], []); - } - } - oldLen = str.length; + ccall('Window_OnTextChanged', 'void', ['string'], [ev.target.value]); }, false); window.cc_inputElem = elem;