diff --git a/.gitignore b/.gitignore index 7c10a7a3a..48b3a92b6 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ es-de.worker.js # Android /android /logback.log +es-core/src/InputOverlay.* es-core/src/utils/PlatformUtilAndroid.* # AppImage diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 0b4949505..f6f1309f2 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -1144,6 +1144,20 @@ void GuiMenu::openInputDeviceOptions() } }); +#if defined(__ANDROID__) + // Whether to enable the touch overlay. + auto inputTouchOverlay = std::make_shared(); + inputTouchOverlay->setState(Settings::getInstance()->getBool("InputTouchOverlay")); + s->addWithLabel("ENABLE TOUCH OVERLAY", inputTouchOverlay); + s->addSaveFunc([inputTouchOverlay, s] { + if (Settings::getInstance()->getBool("InputTouchOverlay") != + inputTouchOverlay->getState()) { + Settings::getInstance()->setBool("InputTouchOverlay", inputTouchOverlay->getState()); + s->setNeedsSaving(); + } + }); +#endif + // Whether to only accept input from the first controller. auto inputOnlyFirstController = std::make_shared(); inputOnlyFirstController->setState( diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index 66a5ea13e..c5ccb0043 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -38,6 +38,7 @@ #include #if defined(__ANDROID__) +#include "InputOverlay.h" #include "utils/PlatformUtilAndroid.h" #endif @@ -890,6 +891,8 @@ int main(int argc, char* argv[]) } #if defined(__ANDROID__) + InputOverlay::getInstance(); + LOG(LogDebug) << "Android API level: " << SDL_GetAndroidSDKVersion(); Utils::Platform::Android::printDeviceInfo(); int storageState {SDL_AndroidGetExternalStorageState()}; diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index 09a3852dd..1ed7ba8f7 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -170,7 +170,9 @@ set(CORE_SOURCES ) if(ANDROID) + set(CORE_HEADERS ${CORE_HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/src/InputOverlay.h) set(CORE_HEADERS ${CORE_HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/PlatformUtilAndroid.h) + set(CORE_HEADERS ${CORE_HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/src/InputOverlay.cpp) set(CORE_SOURCES ${CORE_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/PlatformUtilAndroid.cpp) endif() diff --git a/es-core/src/InputConfig.h b/es-core/src/InputConfig.h index d458b5ca0..1ad30128d 100644 --- a/es-core/src/InputConfig.h +++ b/es-core/src/InputConfig.h @@ -17,12 +17,14 @@ #include #define DEVICE_KEYBOARD -1 -#define DEVICE_CEC -2 +#define DEVICE_TOUCH -2 +#define DEVICE_CEC -3 enum InputType { TYPE_AXIS, TYPE_BUTTON, TYPE_KEY, + TYPE_TOUCH, TYPE_CEC_BUTTON, TYPE_COUNT }; diff --git a/es-core/src/InputManager.cpp b/es-core/src/InputManager.cpp index 127ea996d..469e6c919 100644 --- a/es-core/src/InputManager.cpp +++ b/es-core/src/InputManager.cpp @@ -22,7 +22,8 @@ #include #define KEYBOARD_GUID_STRING "-1" -#define CEC_GUID_STRING "-2" +#define TOUCH_GUID_STRING "-2" +#define CEC_GUID_STRING "-3" namespace { @@ -32,7 +33,12 @@ namespace InputManager::InputManager() noexcept : mWindow {Window::getInstance()} +#if defined(__ANDROID__) + , mInputOverlay {InputOverlay::getInstance()} +#endif , mKeyboardInputConfig {nullptr} + , mTouchInputConfig {nullptr} + , mCECInputConfig {nullptr} { } @@ -81,6 +87,11 @@ void InputManager::init() LOG(LogInfo) << "Added keyboard with default configuration"; } +#if defined(__ANDROID__) + mTouchInputConfig = std::make_unique(DEVICE_TOUCH, "Touch", TOUCH_GUID_STRING); + loadTouchConfig(); +#endif + // Load optional controller mappings. Normally the supported controllers should be compiled // into SDL as a header file, but if a user has a very rare controller that is not supported, // the bundled mapping is incorrect, or the SDL version is a bit older, it makes sense to be @@ -136,6 +147,7 @@ void InputManager::deinit() mInputConfigs.clear(); mKeyboardInputConfig.reset(); + mTouchInputConfig.reset(); mCECInputConfig.reset(); SDL_GameControllerEventState(SDL_DISABLE); @@ -284,6 +296,11 @@ int InputManager::getNumConfiguredDevices() if (mKeyboardInputConfig->isConfigured()) ++num; +#if defined(__ANDROID__) + if (mTouchInputConfig->isConfigured()) + ++num; +#endif + if (mCECInputConfig->isConfigured()) ++num; @@ -313,8 +330,11 @@ std::string InputManager::getDeviceGUIDString(int deviceId) { if (deviceId == DEVICE_KEYBOARD) return KEYBOARD_GUID_STRING; - - if (deviceId == DEVICE_CEC) +#if defined(__ANDROID__) + else if (deviceId == DEVICE_TOUCH) + return TOUCH_GUID_STRING; +#endif + else if (deviceId == DEVICE_CEC) return CEC_GUID_STRING; auto it = mJoysticks.find(deviceId); @@ -333,6 +353,10 @@ InputConfig* InputManager::getInputConfigByDevice(int device) { if (device == DEVICE_KEYBOARD) return mKeyboardInputConfig.get(); +#if defined(__ANDROID__) + else if (device == DEVICE_TOUCH) + return mTouchInputConfig.get(); +#endif else if (device == DEVICE_CEC) return mCECInputConfig.get(); else @@ -510,6 +534,62 @@ bool InputManager::parseEvent(const SDL_Event& event) Input(DEVICE_KEYBOARD, TYPE_KEY, event.key.keysym.sym, 0, false)); return true; } +#if defined(__ANDROID__) + case SDL_FINGERDOWN: { + if (!Settings::getInstance()->getBool("InputTouchOverlay")) + return false; + + const int buttonID {mInputOverlay.getButtonId( + SDL_FINGERDOWN, event.tfinger.fingerId + 1, event.tfinger.x, event.tfinger.y)}; + if (buttonID != -2) { + mWindow->input(getInputConfigByDevice(DEVICE_TOUCH), + Input(DEVICE_TOUCH, TYPE_TOUCH, buttonID, 1, false)); + return true; + } + else { + return false; + } + } + case SDL_FINGERUP: { + if (!Settings::getInstance()->getBool("InputTouchOverlay")) + return false; + + const int buttonID {mInputOverlay.getButtonId(SDL_FINGERUP, event.tfinger.fingerId + 1, + event.tfinger.x, event.tfinger.y)}; + if (buttonID != -2) { + mWindow->input(getInputConfigByDevice(DEVICE_TOUCH), + Input(DEVICE_TOUCH, TYPE_TOUCH, buttonID, 0, false)); + + return true; + } + else { + return false; + } + } + case SDL_FINGERMOTION: { + if (!Settings::getInstance()->getBool("InputTouchOverlay")) + return false; + + bool releasedButton {false}; + const int buttonID { + mInputOverlay.getButtonId(SDL_FINGERMOTION, event.tfinger.fingerId + 1, + event.tfinger.x, event.tfinger.y, &releasedButton)}; + + if (buttonID == -2) + return false; + + if (releasedButton) { + mWindow->input(getInputConfigByDevice(DEVICE_TOUCH), + Input(DEVICE_TOUCH, TYPE_TOUCH, buttonID, 0, false)); + return true; + } + else { + mWindow->input(getInputConfigByDevice(DEVICE_TOUCH), + Input(DEVICE_TOUCH, TYPE_TOUCH, buttonID, 1, false)); + return true; + } + } +#endif case SDL_TEXTINPUT: { mWindow->textInput(event.text.text); break; @@ -646,6 +726,31 @@ void InputManager::loadDefaultControllerConfig(SDL_JoystickID deviceIndex) // clang-format on } +void InputManager::loadTouchConfig() +{ + InputConfig* cfg {mTouchInputConfig.get()}; + + if (cfg->isConfigured()) + return; + + // clang-format off + cfg->mapInput("Up", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_DPAD_UP, 1, true)); + cfg->mapInput("Down", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_DPAD_DOWN, 1, true)); + cfg->mapInput("Left", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_DPAD_LEFT, 1, true)); + cfg->mapInput("Right", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, 1, true)); + cfg->mapInput("Start", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_START, 1, true)); + cfg->mapInput("Back", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_BACK, 1, true)); + cfg->mapInput("A", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_A, 1, true)); + cfg->mapInput("B", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_B, 1, true)); + cfg->mapInput("X", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_X, 1, true)); + cfg->mapInput("Y", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_Y, 1, true)); + cfg->mapInput("LeftShoulder", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, 1, true)); + cfg->mapInput("RightShoulder", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, 1, true)); + cfg->mapInput("LeftTrigger", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 1, true)); + cfg->mapInput("RightTrigger", Input(DEVICE_TOUCH, TYPE_TOUCH, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 1, true)); + // clang-format on +} + void InputManager::addControllerByDeviceIndex(Window* window, int deviceIndex) { // Open joystick and add it to our list. diff --git a/es-core/src/InputManager.h b/es-core/src/InputManager.h index d9f3c921d..d0c8143d1 100644 --- a/es-core/src/InputManager.h +++ b/es-core/src/InputManager.h @@ -12,6 +12,7 @@ #define ES_CORE_INPUT_MANAGER_H #include "CECInput.h" +#include "InputOverlay.h" #include #include @@ -57,12 +58,16 @@ private: bool loadInputConfig(InputConfig* config); void loadDefaultKBConfig(); void loadDefaultControllerConfig(SDL_JoystickID deviceIndex); + void loadTouchConfig(); void addControllerByDeviceIndex(Window* window, int deviceIndex); void removeControllerByJoystickID(Window* window, SDL_JoystickID joyID); Window* mWindow; CECInput mCECInput; +#if defined(__ANDROID__) + InputOverlay& mInputOverlay; +#endif static const int DEADZONE_TRIGGERS = 18000; static const int DEADZONE_THUMBSTICKS = 23000; @@ -73,6 +78,7 @@ private: std::map> mInputConfigs; std::unique_ptr mKeyboardInputConfig; + std::unique_ptr mTouchInputConfig; std::unique_ptr mCECInputConfig; std::map, int> mPrevAxisValues; diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 9165b975f..7eff9f767 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -239,6 +239,9 @@ void Settings::setDefaults() // Input device settings. mStringMap["InputControllerType"] = {"xbox", "xbox"}; +#if defined(__ANDROID__) + mBoolMap["InputTouchOverlay"] = {true, true}; +#endif mBoolMap["InputOnlyFirstController"] = {false, false}; mBoolMap["InputIgnoreKeyboard"] = {false, false}; diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 1321bdd38..5c5a7eb0c 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -18,6 +18,10 @@ #include "guis/GuiInfoPopup.h" #include "resources/Font.h" +#if defined(__ANDROID__) +#include "InputOverlay.h" +#endif + #include #include @@ -662,6 +666,11 @@ void Window::render() if (mRenderScreensaver) mScreensaver->renderScreensaver(); +#if defined(__ANDROID__) + if (Settings::getInstance()->getBool("InputTouchOverlay")) + InputOverlay::getInstance().render(mRenderer->getIdentity()); +#endif + if (Settings::getInstance()->getBool("DisplayGPUStatistics") && mFrameDataText) { mRenderer->setMatrix(mRenderer->getIdentity()); mDefaultFonts.at(1)->renderTextCache(mFrameDataText.get()); diff --git a/resources/graphics/overlay/button_a.svg b/resources/graphics/overlay/button_a.svg new file mode 100644 index 000000000..7c61be77a --- /dev/null +++ b/resources/graphics/overlay/button_a.svg @@ -0,0 +1,22 @@ + + + + + + diff --git a/resources/graphics/overlay/button_b.svg b/resources/graphics/overlay/button_b.svg new file mode 100644 index 000000000..e1350b325 --- /dev/null +++ b/resources/graphics/overlay/button_b.svg @@ -0,0 +1,22 @@ + + + + + + diff --git a/resources/graphics/overlay/button_dpad.svg b/resources/graphics/overlay/button_dpad.svg new file mode 100644 index 000000000..7e258568f --- /dev/null +++ b/resources/graphics/overlay/button_dpad.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/resources/graphics/overlay/button_x.svg b/resources/graphics/overlay/button_x.svg new file mode 100644 index 000000000..8d46642f1 --- /dev/null +++ b/resources/graphics/overlay/button_x.svg @@ -0,0 +1,22 @@ + + + + + + diff --git a/resources/graphics/overlay/button_y.svg b/resources/graphics/overlay/button_y.svg new file mode 100644 index 000000000..9341aaffd --- /dev/null +++ b/resources/graphics/overlay/button_y.svg @@ -0,0 +1,22 @@ + + + + + +