From 155d2523f10fc6b0c087029379fb58c6da076bc0 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Mon, 4 May 2026 18:13:47 +0300 Subject: [PATCH] Textbox padding with Android IME fixed --- .../erroratline0/infinipaint/InfiniPaint.java | 4 - .../infinipaint/InfiniPaintSurface.java | 11 ++ src/GUIStuff/Elements/TextBox.impl.hpp | 4 + src/InputManager.cpp | 136 ++++++++++++------ src/InputManager.hpp | 14 +- src/main.cpp | 4 + 6 files changed, 121 insertions(+), 52 deletions(-) diff --git a/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaint.java b/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaint.java index 03e6749..7fa5036 100644 --- a/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaint.java +++ b/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaint.java @@ -28,10 +28,6 @@ public class InfiniPaint extends SDLActivity { // window.setNavigationBarContrastEnforced(false); } - protected void get_window_insets() { - Window window = this.getWindow(); - } - protected SDLSurface createSDLSurface(Context context) { return new InfiniPaintSurface(context); } diff --git a/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaintSurface.java b/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaintSurface.java index 70b1999..da23356 100644 --- a/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaintSurface.java +++ b/android-project/app/src/main/java/com/erroratline0/infinipaint/InfiniPaintSurface.java @@ -14,6 +14,8 @@ public class InfiniPaintSurface extends SDLSurface { super(context); } + public static native void nativeInfiniPaintUpdateIMESafeArea(int top, int bottom, int left, int right); + // Window inset calculations are different from SDL defaults @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { @@ -24,6 +26,15 @@ public class InfiniPaintSurface extends SDLSurface { WindowInsets.Type.tappableElement() | WindowInsets.Type.displayCutout()); + Insets imeInsets = insets.getInsets(WindowInsets.Type.systemBars() | + //WindowInsets.Type.systemGestures() | + //WindowInsets.Type.mandatorySystemGestures() | + WindowInsets.Type.tappableElement() | + WindowInsets.Type.displayCutout() | + WindowInsets.Type.ime()); + + nativeInfiniPaintUpdateIMESafeArea(imeInsets.top, imeInsets.bottom, imeInsets.left, imeInsets.right); + SDLActivity.onNativeInsetsChanged(combined.left, combined.right, combined.top, combined.bottom); if (insets.isVisible(WindowInsets.Type.ime())) { diff --git a/src/GUIStuff/Elements/TextBox.impl.hpp b/src/GUIStuff/Elements/TextBox.impl.hpp index 587bbf3..de37b0c 100644 --- a/src/GUIStuff/Elements/TextBox.impl.hpp +++ b/src/GUIStuff/Elements/TextBox.impl.hpp @@ -32,6 +32,10 @@ template void TextBox::update() { oldDD.cur = *cur; gui.invalidate_draw_element(this); } + if(edit.has_value()) { + SCollision::AABB rect = {boundingBox.value().min * gui.io.guiScaleMultiplier, boundingBox.value().max * gui.io.guiScaleMultiplier}; + gui.io.input->update_textbox_rectangle(edit.value().textboxInputID, rect); + } } template void TextBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 8331b26..65cda86 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -11,7 +11,9 @@ #include #include +#include "MainProgram.hpp" +InputManager* globalInputManager = nullptr; #ifdef _WIN32 #include @@ -30,10 +32,22 @@ extern "C" { } #endif -#include "MainProgram.hpp" +#ifdef __ANDROID__ +#include + +extern "C" +JNIEXPORT void JNICALL +Java_com_erroratline0_infinipaint_InfiniPaintSurface_nativeInfiniPaintUpdateIMESafeArea(JNIEnv *env, jclass clazz, jint top, jint bottom, jint left, jint right) { + if(globalInputManager) + globalInputManager->safeAreaWithoutIME = {{left, top}, {globalInputManager->main.window.size.x() - right, globalInputManager->main.window.size.y() - bottom}}; +} + +#endif InputManager::InputManager(MainProgram& initMain): main(initMain) { + globalInputManager = this; + frame_reset({0, 0}); defaultKeyAssignments[{0, SDLK_W}] = KEY_CAMERA_ROTATE_COUNTERCLOCKWISE; @@ -101,7 +115,7 @@ unsigned InputManager::set_text_box_front(const std::optional InputManager::Text::current_textbox_editing_id() { return textBoxes.front().id; } -void InputManager::Text::update_accepting_input(SDL_Window* window) { - if(!textBoxes.empty()) { - auto& textbox = textBoxes.front(); - if(!propID.has_value()) - propID = SDL_CreateProperties(); - SDL_PropertiesID propIDVal = propID.value(); +void InputManager::update_textbox_rectangle(unsigned textboxID, const SCollision::AABB& textboxRect) { + auto it = std::find_if(text.textBoxes.begin(), text.textBoxes.end(), [&](const auto& t) {return t.id == textboxID;}); + it->rect = textboxRect; +} + +void InputManager::update_accepting_input(SDL_Window* window) { + if(!text.textBoxes.empty()) { + auto& textbox = text.textBoxes.front(); + if(!text.propID.has_value()) + text.propID = SDL_CreateProperties(); + SDL_PropertiesID propIDVal = text.propID.value(); // There is a bug where decimal places disappear when textinput type is set to number on android. We'll just set those textinputs back to text type SDL_SetNumberProperty(propIDVal, SDL_PROP_TEXTINPUT_TYPE_NUMBER, (textbox.textInputProperties.inputType == SDL_TEXTINPUT_TYPE_NUMBER) ? SDL_TEXTINPUT_TYPE_TEXT : textbox.textInputProperties.inputType); SDL_SetNumberProperty(propIDVal, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, textbox.textInputProperties.capitalization); SDL_SetNumberProperty(propIDVal, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, textbox.textInputProperties.autocorrect); SDL_SetNumberProperty(propIDVal, SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, textbox.textInputProperties.multiline); SDL_SetNumberProperty(propIDVal, SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER, (textbox.textInputProperties.androidInputType & ANDROIDTEXT_TYPE_CLASS_NUMBER) ? ANDROIDTEXT_TYPE_CLASS_TEXT : textbox.textInputProperties.androidInputType); - if(!textBoxes.front().rect) - SDL_SetTextInputArea(window, nullptr, 0); - else { - SDL_Rect r = textBoxes.front().rect->get_sdl_rect(); - SDL_SetTextInputArea(window, &r, 0); - } + if(text.textBoxes.front().rect) + excludeIMEFromSafeArea = false; + else + excludeIMEFromSafeArea = true; SDL_StartTextInputWithProperties(window, propIDVal); #ifdef __EMSCRIPTEN__ isAcceptingInputEmscripten = 1; @@ -157,7 +174,8 @@ void InputManager::Text::update_accepting_input(SDL_Window* window) { } else { SDL_StopTextInput(window); - SDL_SetTextInputArea(window, nullptr, 0); + excludeIMEFromSafeArea = false; + screenOffset = {0.0f, 0.0f}; #ifdef __EMSCRIPTEN__ isAcceptingInputEmscripten = 0; #endif @@ -364,8 +382,24 @@ void InputManager::backend_input_text_event(const std::string& str) { main.input_text_callback(TextCallbackArgs{ .str = str }); } +Vector2f InputManager::backend_cursor_pos_calculation(const Vector2f& cursorPos) { + return cursorPos * main.window.density - screenOffset; +} + +Vector2f InputManager::backend_cursor_delta_calculation(const Vector2f& cursorDelta) { + return cursorDelta * main.window.density; +} + +Vector2f InputManager::backend_touch_cursor_pos_calculation(const Vector2f& cursorPos) { + return Vector2f{cursorPos.x() * main.window.size.x() * main.window.density, cursorPos.y() * main.window.size.y() * main.window.density} - screenOffset; +} + +Vector2f InputManager::backend_touch_cursor_delta_calculation(const Vector2f& cursorDelta) { + return Vector2f{cursorDelta.x() * main.window.size.x() * main.window.density, cursorDelta.y() * main.window.size.y() * main.window.density}; +} + void InputManager::backend_drop_file_event(const SDL_DropEvent& e) { - Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mousePos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mousePos); main.input_drop_file_callback({ @@ -376,7 +410,7 @@ void InputManager::backend_drop_file_event(const SDL_DropEvent& e) { } void InputManager::backend_drop_text_event(const SDL_DropEvent& e) { - Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mousePos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mousePos); main.input_drop_text_callback({ @@ -387,7 +421,7 @@ void InputManager::backend_drop_text_event(const SDL_DropEvent& e) { } void InputManager::backend_mouse_button_up_update(const SDL_MouseButtonEvent& e) { - Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mousePos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mousePos); if(e.button == 1) @@ -409,7 +443,7 @@ void InputManager::backend_mouse_button_up_update(const SDL_MouseButtonEvent& e) } void InputManager::backend_mouse_button_down_update(const SDL_MouseButtonEvent& e) { - Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mousePos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mousePos); if(e.button == 1) @@ -432,10 +466,10 @@ void InputManager::backend_mouse_button_down_update(const SDL_MouseButtonEvent& } void InputManager::backend_mouse_motion_update(const SDL_MouseMotionEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mouseNewPos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); - Vector2f mouseRel = {e.xrel * main.window.density, e.yrel * main.window.density}; + Vector2f mouseRel = backend_cursor_delta_calculation({e.xrel, e.yrel}); main.input_mouse_motion_callback({ .deviceType = MouseDeviceType::MOUSE, .pos = mouseNewPos, @@ -446,7 +480,7 @@ void InputManager::backend_mouse_motion_update(const SDL_MouseMotionEvent& e) { } void InputManager::backend_mouse_wheel_update(const SDL_MouseWheelEvent& e) { - Vector2f mouseNewPos = {e.mouse_x * main.window.density, e.mouse_y * main.window.density}; + Vector2f mouseNewPos = backend_cursor_pos_calculation({e.mouse_x, e.mouse_y}); mouse.set_pos(mouseNewPos); main.input_mouse_wheel_callback({ @@ -459,7 +493,7 @@ void InputManager::backend_mouse_wheel_update(const SDL_MouseWheelEvent& e) { } void InputManager::backend_pen_button_down_update(const SDL_PenButtonEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mouseNewPos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); pen.previousPos = mouseNewPos; @@ -495,7 +529,7 @@ void InputManager::backend_pen_button_down_update(const SDL_PenButtonEvent& e) { } void InputManager::backend_pen_button_up_update(const SDL_PenButtonEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mouseNewPos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); pen.previousPos = mouseNewPos; @@ -531,7 +565,7 @@ void InputManager::backend_pen_button_up_update(const SDL_PenButtonEvent& e) { } void InputManager::backend_pen_touch_down_update(const SDL_PenTouchEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mouseNewPos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); pen.previousPos = mouseNewPos; @@ -560,7 +594,7 @@ void InputManager::backend_pen_touch_down_update(const SDL_PenTouchEvent& e) { } void InputManager::backend_pen_touch_up_update(const SDL_PenTouchEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mouseNewPos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); pen.previousPos = mouseNewPos; @@ -586,7 +620,7 @@ void InputManager::backend_pen_touch_up_update(const SDL_PenTouchEvent& e) { } void InputManager::backend_pen_motion_update(const SDL_PenMotionEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mouseNewPos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); Vector2f mouseRel = mouseNewPos - pen.previousPos; @@ -607,7 +641,7 @@ void InputManager::backend_pen_motion_update(const SDL_PenMotionEvent& e) { } void InputManager::backend_pen_axis_update(const SDL_PenAxisEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density}; + Vector2f mouseNewPos = backend_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); pen.previousPos = mouseNewPos; @@ -645,20 +679,20 @@ InputManager::MouseMotionCallbackArgs InputManager::convert_finger_motion_to_mou std::vector InputManager::get_multiple_finger_positions() { std::vector pos; for(const SDL_TouchFingerEvent& fingerEvent : touch.fingers) - pos.emplace_back(fingerEvent.x * main.window.size.x() * main.window.density, fingerEvent.y * main.window.size.y() * main.window.density); + pos.emplace_back(backend_touch_cursor_pos_calculation({fingerEvent.x, fingerEvent.y})); return pos; } std::vector InputManager::get_multiple_finger_motions() { std::vector pos; for(const SDL_TouchFingerEvent& fingerEvent : touch.fingers) - pos.emplace_back(fingerEvent.dx * main.window.size.x() * main.window.density, fingerEvent.dy * main.window.size.y() * main.window.density); + pos.emplace_back(backend_touch_cursor_delta_calculation({fingerEvent.dx, fingerEvent.dy})); return pos; } void InputManager::touch_finger_do_mouse_down() { auto& prevTouch = touch.fingers[0]; - Vector2f mouseNewPos = {prevTouch.x * main.window.size.x() * main.window.density, prevTouch.y * main.window.size.y() * main.window.density}; + Vector2f mouseNewPos = backend_touch_cursor_pos_calculation({prevTouch.x, prevTouch.y}); mouse.set_pos(mouseNewPos); mouse.leftDown = true; @@ -677,7 +711,7 @@ void InputManager::touch_finger_do_mouse_down() { } void InputManager::touch_finger_do_mouse_up(const SDL_TouchFingerEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density}; + Vector2f mouseNewPos = backend_touch_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); mouse.leftDown = false; @@ -691,9 +725,9 @@ void InputManager::touch_finger_do_mouse_up(const SDL_TouchFingerEvent& e) { } void InputManager::touch_finger_do_mouse_motion(const SDL_TouchFingerEvent& e) { - Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density}; + Vector2f mouseNewPos = backend_touch_cursor_pos_calculation({e.x, e.y}); mouse.set_pos(mouseNewPos); - Vector2f mouseRel = {e.dx * main.window.size.x() * main.window.density, e.dy * main.window.size.y() * main.window.density}; + Vector2f mouseRel = backend_touch_cursor_pos_calculation({e.dx, e.dy}); main.input_mouse_motion_callback({ .deviceType = MouseDeviceType::TOUCH, .pos = mouseNewPos, @@ -782,7 +816,7 @@ void InputManager::backend_touch_finger_up_update(const SDL_TouchFingerEvent& e) main.input_finger_touch_callback({ .fingerID = e.fingerID, .down = false, - .pos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density}, + .pos = backend_touch_cursor_pos_calculation({e.x, e.y}), .fingerDownCount = touch.fingers.size(), .fingerTapCount = 0 }); @@ -826,8 +860,8 @@ void InputManager::backend_touch_finger_motion_update(const SDL_TouchFingerEvent main.input_finger_motion_callback({ .fingerID = e.fingerID, - .pos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density}, - .move = {e.dx * main.window.size.x() * main.window.density, e.dy * main.window.size.y() * main.window.density}, + .pos = backend_touch_cursor_pos_calculation({e.x, e.y}), + .move = backend_touch_cursor_pos_calculation({e.x, e.y}), .fingerDownCount = touch.fingers.size() }); @@ -840,6 +874,9 @@ void InputManager::update_safe_area() { main.window.safeArea = SCollision::AABB({safeArea.x, safeArea.y}, {safeArea.x + safeArea.w, safeArea.y + safeArea.h}); else main.window.safeArea = SCollision::AABB({0, 0}, {main.window.size.x(), main.window.size.y()}); + + if(excludeIMEFromSafeArea && safeAreaWithoutIME.has_value()) + main.window.safeArea = safeAreaWithoutIME.value(); } void InputManager::backend_window_resize_update() { @@ -1108,15 +1145,22 @@ void InputManager::update() { touch_finger_do_mouse_down(); touch.touchEventType = Touch::ONE_FINGER_EVENT; } + constexpr float PADDING_BETWEEN_TEXTBOX_AND_IME = 15.0f; if(!text.textBoxes.empty()) { - //auto& textbox = text.textBoxes.front(); - //if(textbox.rect) { - // SDL_Rect r = textbox.rect->get_sdl_rect(); - // //SDL_SetTextInputArea(main.window.sdlWindow, &r, textbox.cursor->pos.fTextByteIndex); - //} - //else - // //SDL_SetTextInputArea(main.window.sdlWindow, nullptr, textbox.cursor->pos.fTextByteIndex); + auto& r = text.textBoxes.front().rect; + if(r.has_value() && safeAreaWithoutIME.has_value()) { + float bottomY = r.value().max.y(); + float yLimit = safeAreaWithoutIME.value().max.y() - PADDING_BETWEEN_TEXTBOX_AND_IME; + if(bottomY >= yLimit) + screenOffset = {0.0f, -(bottomY - yLimit)}; + else + screenOffset = {0.0f, 0.0f}; + } + else + screenOffset = {0.0f, 0.0f}; } + else + screenOffset = {0.0f, 0.0f}; } void InputManager::frame_reset(const Vector2i& windowSize) { diff --git a/src/InputManager.hpp b/src/InputManager.hpp index 315097f..8faa4e3 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -154,10 +154,13 @@ struct InputManager { unsigned set_text_box_front(const std::optional>& textboxRect, const TextInputProperties& inputProps); unsigned set_text_box_back(const std::optional>& textboxRect, const TextInputProperties& inputProps); void remove_text_box(unsigned textboxID); + void update_accepting_input(SDL_Window* window); + void update_textbox_rectangle(unsigned textboxID, const SCollision::AABB& textboxRect); struct Text { bool is_accepting_input(); std::optional current_textbox_editing_id(); + void update_textbox_rectangle(); private: struct TextBoxInfo { @@ -166,8 +169,6 @@ struct InputManager { unsigned id; }; - void update_accepting_input(SDL_Window* window); - std::optional propID; std::deque textBoxes; @@ -256,6 +257,11 @@ struct InputManager { void touch_finger_do_mouse_up(const SDL_TouchFingerEvent& f); void touch_finger_do_mouse_motion(const SDL_TouchFingerEvent& f); + Vector2f backend_cursor_pos_calculation(const Vector2f& cursorPos); + Vector2f backend_cursor_delta_calculation(const Vector2f& cursorDelta); + Vector2f backend_touch_cursor_pos_calculation(const Vector2f& cursorPos); + Vector2f backend_touch_cursor_delta_calculation(const Vector2f& cursorDelta); + void backend_input_text_event(const std::string& str); void backend_drop_file_event(const SDL_DropEvent& e); void backend_drop_text_event(const SDL_DropEvent& e); @@ -408,6 +414,10 @@ struct InputManager { float scale; }; + std::optional> safeAreaWithoutIME; + bool excludeIMEFromSafeArea = false; + Vector2f screenOffset = {0.0f, 0.0f}; + MainProgram& main; }; diff --git a/src/main.cpp b/src/main.cpp index d78d579..0db54e0 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -561,6 +561,7 @@ void regular_draw(MainStruct& mS) { if(mS.m->window.intermediateSurfaceMSAA) { SkCanvas* intermediateCanvas = mS.m->window.intermediateSurfaceMSAA->getCanvas(); intermediateCanvas->save(); + intermediateCanvas->translate(mS.m->input.screenOffset.x(), mS.m->input.screenOffset.y()); mS.m->draw(intermediateCanvas); intermediateCanvas->restore(); @@ -580,7 +581,10 @@ void regular_draw(MainStruct& mS) { mS.ctx->flushAndSubmit(); mS.vulkanWindowContext->swapBuffers(); #elif USE_BACKEND_OPENGL + mS.canvas->save(); + mS.canvas->translate(mS.m->input.screenOffset.x(), mS.m->input.screenOffset.y()); mS.m->draw(mS.canvas); + mS.canvas->restore(); mS.ctx->flushAndSubmit(); SDL_GL_SwapWindow(mS.window); #endif