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
This commit is contained in:
UnknownShadow200 2019-11-16 14:50:14 +11:00
parent ef5a0f64f3
commit bece154d2b
7 changed files with 163 additions and 100 deletions

128
src/Gui.c
View File

@ -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 */
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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