From 36c2f5137cd0569c7e1d0243288b3947a89433c7 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Thu, 28 Nov 2024 17:09:09 +0100 Subject: [PATCH] ios, android: load custom vgamepad image. disable unused controls User can choose a custom vgamepad image or reset to default. Unused controls are disabled and hidden based on the game (arcade) or maple device (console). --- core/ui/gui.cpp | 48 ++- core/ui/gui_android.cpp | 339 +++++++++++++++--- core/ui/gui_android.h | 13 + .../flycast/emulator/NativeGLActivity.java | 3 + .../flycast/emulator/emu/NativeGLView.java | 3 + .../emulator/emu/VirtualJoystickDelegate.java | 100 +++--- .../flycast/src/main/jni/src/Android.cpp | 10 + 7 files changed, 410 insertions(+), 106 deletions(-) diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 57015c632..331b8baee 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -111,6 +111,7 @@ static void emuEventCallback(Event event, void *) { case Event::Resume: game_started = true; + vgamepad::startGame(); break; case Event::Start: GamepadDevice::load_system_mappings(); @@ -1400,13 +1401,21 @@ static void controller_mapping_popup(const std::shared_ptr& gamep } } +static void gamepadPngFileSelected(bool cancelled, std::string path) +{ + if (!cancelled) + gui_runOnUiThread([path]() { + vgamepad::loadImage(path); + }); +} + static void gamepadSettingsPopup(const std::shared_ptr& gamepad) { centerNextWindow(); ImGui::SetNextWindowSize(min(ImGui::GetIO().DisplaySize, ScaledVec2(450.f, 300.f))); ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); - if (ImGui::BeginPopupModal("Gamepad Settings", NULL, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + if (ImGui::BeginPopupModal("Gamepad Settings", NULL, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_DragScrolling)) { if (ImGui::Button("Done", ScaledVec2(100, 30))) { @@ -1449,12 +1458,38 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) ImGui::NewLine(); if (gamepad->is_virtual_gamepad()) { -#ifdef __ANDROID__ - header("Haptic"); - OptionSlider("Power", config::VirtualGamepadVibration, 0, 100, "Haptic feedback power", "%d%%"); -#endif + if (gamepad->is_rumble_enabled()) { + header("Haptic"); + OptionSlider("Power", config::VirtualGamepadVibration, 0, 100, "Haptic feedback power", "%d%%"); + } header("View"); OptionSlider("Transparency", config::VirtualGamepadTransparency, 0, 100, "Virtual gamepad buttons transparency", "%d%%"); + +#if defined(__ANDROID__) || defined(TARGET_IPHONE) + vgamepad::ImguiVGamepadTexture tex; + ImGui::Image(tex.getId(), ScaledVec2(300, 112.5f), ImVec2(0, 1), ImVec2(1, 0.25f)); +#endif + const char *gamepadPngTitle = "Select a PNG file"; + if (ImGui::Button("Choose Image...", ScaledVec2(150, 30))) +#ifdef __ANDROID__ + { + if (!hostfs::addStorage(false, false, gamepadPngTitle, gamepadPngFileSelected, "image/png")) + ImGui::OpenPopup(gamepadPngTitle); + } +#else + { + ImGui::OpenPopup(gamepadPngTitle); + } +#endif + ImGui::SameLine(); + if (ImGui::Button("Use Default", ScaledVec2(150, 30))) + vgamepad::loadImage(""); + + select_file_popup(gamepadPngTitle, [](bool cancelled, std::string selection) + { + gamepadPngFileSelected(cancelled, selection); + return true; + }, true, "png"); } else if (gamepad->is_rumble_enabled()) { @@ -1483,6 +1518,8 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) ShowHelpMarker("Value sent to the game at 100% thumbstick deflection. " "Values greater than 100% will saturate before full deflection of the thumbstick."); } + scrollWhenDraggingOnVoid(); + windowDragScroll(); ImGui::EndPopup(); } } @@ -2996,6 +3033,7 @@ static void gui_display_settings() { maple_ReconnectDevices(); reset_vmus(); + vgamepad::startGame(); } } SaveSettings(); diff --git a/core/ui/gui_android.cpp b/core/ui/gui_android.cpp index 4c9ba44af..1705d8a59 100644 --- a/core/ui/gui_android.cpp +++ b/core/ui/gui_android.cpp @@ -22,14 +22,16 @@ #include "gui.h" #include "stdclass.h" #include "imgui.h" -#include "gui_util.h" #include "rend/osd.h" #include "imgui_driver.h" #include "input/gamepad.h" #include "input/gamepad_device.h" +#include "oslib/storage.h" #include "oslib/resources.h" #include "cfg/cfg.h" #include "input/gamepad.h" +#include "hw/naomi/naomi_cart.h" +#include "hw/maple/maple_devs.h" #include namespace vgamepad @@ -47,12 +49,14 @@ struct Control ImVec2 size; ImVec2 uv0; ImVec2 uv1; + bool disabled = false; }; static Control Controls[_Count]; static bool Visible = true; static float AlphaTrans = 1.f; static ImVec2 StickPos; // analog stick position [-1, 1] constexpr char const *BTN_PATH = "picture/buttons.png"; +constexpr char const *CFG_SECTION = "vgamepad"; void displayCommands() { @@ -83,58 +87,74 @@ void displayCommands() ImGui::End(); } -static u8 *loadOSDButtons(int &width, int &height) +static bool loadOSDButtons(const std::string& path) { - int n; - stbi_set_flip_vertically_on_load(1); - - FILE *file = nowide::fopen(get_readonly_data_path("buttons.png").c_str(), "rb"); + if (path.empty()) + return false; + FILE *file = hostfs::storage().openFile(path, "rb"); if (file == nullptr) - // also try the home folder (android) - file = nowide::fopen(get_readonly_config_path("buttons.png").c_str(), "rb"); - u8 *image_data = nullptr; - if (file != nullptr) - { - image_data = stbi_load_from_file(file, &width, &height, &n, STBI_rgb_alpha); - std::fclose(file); - } + return false; + + stbi_set_flip_vertically_on_load(1); + int width, height, n; + u8 *image_data = stbi_load_from_file(file, &width, &height, &n, STBI_rgb_alpha); + std::fclose(file); if (image_data == nullptr) - { - size_t size; - std::unique_ptr data = resource::load(BTN_PATH, size); - image_data = stbi_load_from_memory(data.get(), (int)size, &width, &height, &n, STBI_rgb_alpha); - } - return image_data; + return false; + try { + imguiDriver->updateTexture(BTN_PATH, image_data, width, height, false); + } catch (...) { + // vulkan can throw during resizing + } + free(image_data); + + return true; } -class ImguiVGamepadTexture : public ImguiTexture +static ImTextureID loadOSDButtons() { -public: - ImTextureID getId() override - { - ImTextureID id = imguiDriver->getTexture(BTN_PATH); - if (id == ImTextureID()) - { - int width, height; - u8 *imgData = loadOSDButtons(width, height); - if (imgData != nullptr) - { - try { - id = imguiDriver->updateTextureAndAspectRatio(BTN_PATH, imgData, width, height, nearestSampling); - } catch (...) { - // vulkan can throw during resizing - } - free(imgData); - } - } + ImTextureID id{}; + // custom image + std::string path = cfgLoadStr(CFG_SECTION, "image", ""); + if (loadOSDButtons(path)) return id; - } -}; + // legacy buttons.png in data folder + if (loadOSDButtons(get_readonly_data_path("buttons.png"))) + return id; + // also try the home folder (android) + if (loadOSDButtons(get_readonly_config_path("buttons.png"))) + return id; + // default in resource + size_t size; + std::unique_ptr data = resource::load(BTN_PATH, size); + stbi_set_flip_vertically_on_load(1); + int width, height, n; + u8 *image_data = stbi_load_from_memory(data.get(), (int)size, &width, &height, &n, STBI_rgb_alpha); + if (image_data != nullptr) + { + try { + id = imguiDriver->updateTexture(BTN_PATH, image_data, width, height, false); + } catch (...) { + // vulkan can throw during resizing + } + free(image_data); + } + return id; +} + +ImTextureID ImguiVGamepadTexture::getId() +{ + ImTextureID id = imguiDriver->getTexture(BTN_PATH); + if (id == ImTextureID()) + id = loadOSDButtons(); -constexpr float vjoy_sz[2][14] = { - // L U R D X Y B A St LT RT Ana Stck FF - { 64,64,64,64, 64,64,64,64, 64, 90,90, 128, 64, 64 }, - { 64,64,64,64, 64,64,64,64, 64, 64,64, 128, 64, 64 }, + return id; +} + +constexpr float vjoy_sz[2][_Count] = { + // L U R D X Y B A St LT RT Ana Stck FF LU RU LD RD + { 64,64,64,64, 64,64,64,64, 64, 90,90, 128, 64, 64, 64,64,64,64 }, + { 64,64,64,64, 64,64,64,64, 64, 64,64, 128, 64, 64, 64,64,64,64 }, }; constexpr float OSD_TEX_W = 512.f; @@ -186,8 +206,9 @@ void setPosition(ControlId id, float x, float y, float w, float h) ControlId hitTest(float x, float y) { for (const auto& control : Controls) - if (x >= control.pos.x && x < control.pos.x + control.size.x - && y >= control.pos.y && y < control.pos.y + control.size.y) + if (!control.disabled + && x >= control.pos.x && x < control.pos.x + control.size.x + && y >= control.pos.y && y < control.pos.y + control.size.y) return static_cast(&control - &Controls[0]); return None; } @@ -227,6 +248,8 @@ float getControlWidth(ControlId control) { static void drawButtonDim(ImDrawList *drawList, const Control& control, int state) { + if (control.disabled) + return; float scale_h = settings.display.height / 480.f; float offs_x = (settings.display.width - scale_h * 640.f) / 2.f; ImVec2 pos = control.pos * scale_h; @@ -284,7 +307,7 @@ void draw() } static float getUIScale() { - // scale is 1.1@ for a 320 dpi screen of height 750 + // scale is 1.1 for a 320 dpi screen of height 750 return 1.1f * 750.f / settings.display.height * settings.display.dpi / 320.f; } @@ -300,15 +323,15 @@ struct LayoutElement void load() { - x = cfgLoadFloat(SECTION, name + "_x", x); - y = cfgLoadFloat(SECTION, name + "_y", y); - scale = cfgLoadFloat(SECTION, name + "_scale", scale); + x = cfgLoadFloat(CFG_SECTION, name + "_x", x); + y = cfgLoadFloat(CFG_SECTION, name + "_y", y); + scale = cfgLoadFloat(CFG_SECTION, name + "_scale", scale); } void save() const { - cfgSaveFloat(SECTION, name + "_x", x); - cfgSaveFloat(SECTION, name + "_y", y); - cfgSaveFloat(SECTION, name + "_scale", scale); + cfgSaveFloat(CFG_SECTION, name + "_x", x); + cfgSaveFloat(CFG_SECTION, name + "_y", y); + cfgSaveFloat(CFG_SECTION, name + "_scale", scale); } bool hitTest(float nx, float ny) const { @@ -342,8 +365,6 @@ struct LayoutElement else y = 1.f - h + dy / 480.f * uiscale; } - - static constexpr char const *SECTION = "vgamepad"; }; static LayoutElement Layout[] { { "dpad", 32.f, -24.f, 192.f, 192.f }, @@ -476,9 +497,210 @@ void scaleElement(Element element, float factor) translateElement(element, -dx, -dy); } +void loadImage(const std::string& path) +{ + if (path.empty()) { + cfgSaveStr(CFG_SECTION, "image", ""); + loadOSDButtons(); + } + else if (loadOSDButtons(path)) { + cfgSaveStr(CFG_SECTION, "image", path); + } +} + +void enableAllControls() +{ + for (auto& control : Controls) + control.disabled = false; +} + +static void disableControl(ControlId ctrlId) +{ + Controls[ctrlId].disabled = true; + switch (ctrlId) + { + case Left: + Controls[LeftUp].disabled = true; + Controls[LeftDown].disabled = true; + break; + case Right: + Controls[RightUp].disabled = true; + Controls[RightDown].disabled = true; + break; + case Up: + Controls[LeftUp].disabled = true; + Controls[RightUp].disabled = true; + break; + case Down: + Controls[LeftDown].disabled = true; + Controls[RightDown].disabled = true; + break; + case AnalogArea: + case AnalogStick: + Controls[AnalogArea].disabled = true; + Controls[AnalogStick].disabled = true; + break; + default: + break; + } +} + +void startGame() +{ + enableAllControls(); + if (settings.platform.isConsole()) + { + switch (config::MapleMainDevices[0]) + { + case MDT_LightGun: + // TODO enable mouse? + disableControl(AnalogArea); + disableControl(LeftTrigger); + disableControl(RightTrigger); + disableControl(A); + disableControl(X); + disableControl(Y); + break; + case MDT_AsciiStick: + // TODO add CZ + disableControl(AnalogArea); + disableControl(LeftTrigger); + disableControl(RightTrigger); + break; + case MDT_PopnMusicController: + // TODO add C btn + disableControl(AnalogArea); + disableControl(LeftTrigger); + disableControl(RightTrigger); + break; + case MDT_RacingController: + disableControl(X); + disableControl(Y); + break; + default: + break; + } + } + else + { + // arcade game + // FIXME RT is used as mod key for coin, test, service (ABX) + // FIXME RT and LT are buttons 4 & 5 in arcade mode + // TODO insert card button for card games + if (NaomiGameInputs != nullptr) + { + bool fullAnalog = false; + bool rt = false; + bool lt = false; + for (const auto& axis : NaomiGameInputs->axes) + { + if (axis.name == nullptr) + break; + switch (axis.axis) + { + case 0: + case 1: + fullAnalog = true; + break; + case 4: + rt = true; + break; + case 5: + lt = true; + break; + } + } + if (!fullAnalog) + disableControl(AnalogArea); + u32 usedButtons = 0; + for (const auto& button : NaomiGameInputs->buttons) + { + if (button.name == nullptr) + break; + usedButtons |= button.source; + } + if (settings.platform.isAtomiswave()) + { + // button order: A B X Y RT + /* these ones are always needed for now + if ((usedButtons & AWAVE_BTN0_KEY) == 0) + disableControl(A); + if ((usedButtons & AWAVE_BTN1_KEY) == 0) + disableControl(B); + if ((usedButtons & AWAVE_BTN2_KEY) == 0) + disableControl(X); + if ((usedButtons & AWAVE_BTN4_KEY) == 0 && !rt) + disableControl(RightTrigger); + */ + if ((usedButtons & AWAVE_BTN3_KEY) == 0) + disableControl(Y); + if (!lt) + disableControl(LeftTrigger); + if ((usedButtons & AWAVE_UP_KEY) == 0) + disableControl(Up); + if ((usedButtons & AWAVE_DOWN_KEY) == 0) + disableControl(Down); + if ((usedButtons & AWAVE_LEFT_KEY) == 0) + disableControl(Left); + if ((usedButtons & AWAVE_RIGHT_KEY) == 0) + disableControl(Right); + } + else + { + /* these ones are always needed for now + if ((usedButtons & NAOMI_BTN0_KEY) == 0) + disableControl(A); + if ((usedButtons & NAOMI_BTN1_KEY) == 0) + disableControl(B); + if ((usedButtons & NAOMI_BTN2_KEY) == 0) + // C + disableControl(X); + if ((usedButtons & NAOMI_BTN4_KEY) == 0 && !rt) + // Y + disableControl(RightTrigger); + */ + if ((usedButtons & NAOMI_BTN3_KEY) == 0) + // X + disableControl(Y); + if ((usedButtons & NAOMI_BTN5_KEY) == 0 && !lt) + // Z + disableControl(LeftTrigger); + if ((usedButtons & NAOMI_UP_KEY) == 0) + disableControl(Up); + if ((usedButtons & NAOMI_DOWN_KEY) == 0) + disableControl(Down); + if ((usedButtons & NAOMI_LEFT_KEY) == 0) + disableControl(Left); + if ((usedButtons & NAOMI_RIGHT_KEY) == 0) + disableControl(Right); + } + } + else if (settings.input.lightgunGame) + { + disableControl(Y); + disableControl(AnalogArea); + disableControl(LeftTrigger); + disableControl(Up); + disableControl(Down); + disableControl(Left); + disableControl(Right); + } + else + { + // all analog games *should* have an input description + disableControl(AnalogArea); + } + } + bool enabledState[_Count]; + for (int i = 0; i < _Count; i++) + enabledState[i] = !Controls[i].disabled; + setEnabledControls(enabledState); +} + #ifndef __ANDROID__ void startEditing() { + enableAllControls(); show(); } @@ -497,7 +719,10 @@ void resetEditing() { resetLayout(); } +void setEnabledControls(bool enabled[_Count]) { +} + #endif } // namespace vgamepad -#endif // __ANDROID__ +#endif // __ANDROID__ || TARGET_IPHONE diff --git a/core/ui/gui_android.h b/core/ui/gui_android.h index a84bcd098..86b099aa7 100644 --- a/core/ui/gui_android.h +++ b/core/ui/gui_android.h @@ -18,6 +18,7 @@ */ #pragma once #include "types.h" +#include "gui_util.h" namespace vgamepad { @@ -60,9 +61,17 @@ enum Element Elem_FForward, }; +class ImguiVGamepadTexture : public ImguiTexture +{ +public: + ImTextureID getId() override; +}; + #if defined(__ANDROID__) || defined(TARGET_IPHONE) void setPosition(ControlId id, float x, float y, float w = 0.f, float h = 0.f); // Legacy android +void setEnabledControls(bool enabled[_Count]); // Legacy android +void enableAllControls(); void show(); void hide(); void draw(); @@ -71,6 +80,8 @@ void pauseEditing(); void stopEditing(bool canceled); void resetEditing(); void displayCommands(); +void loadImage(const std::string& path); +void startGame(); ControlId hitTest(float x, float y); u32 controlToDcKey(ControlId control); @@ -91,6 +102,8 @@ void startEditing() {} void pauseEditing() {} void displayCommands() {} void applyUiScale() {} +void loadImage(const std::string& path) {} +void startGame() {} #endif } // namespace vgamepad diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/NativeGLActivity.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/NativeGLActivity.java index 9369b936f..4f84b6e2b 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/NativeGLActivity.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/NativeGLActivity.java @@ -90,6 +90,9 @@ public void run() { } }); } + private void VJoyEnableControls(boolean[] state) { + mView.enableVjoy(state); + } // On-screen keyboard borrowed from SDL core android code class ShowTextInputTask implements Runnable { diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/NativeGLView.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/NativeGLView.java index 0a928ac7c..b70cda441 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/NativeGLView.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/NativeGLView.java @@ -186,4 +186,7 @@ public void readCustomVjoyValues() { public void setEditVjoyMode(boolean editVjoyMode) { vjoyDelegate.setEditVjoyMode(editVjoyMode); } + public void enableVjoy(boolean[] state) { + vjoyDelegate.enableVjoy(state); + } } diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/VirtualJoystickDelegate.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/VirtualJoystickDelegate.java index aaebc99dd..813ef7981 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/VirtualJoystickDelegate.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/VirtualJoystickDelegate.java @@ -12,6 +12,8 @@ import com.flycast.emulator.periph.VJoy; import com.flycast.emulator.periph.VibratorThread; +import java.util.Arrays; + public class VirtualJoystickDelegate { private VibratorThread vibratorThread; @@ -28,6 +30,7 @@ public void run() { }; private float[][] vjoy_d_custom; + private boolean[] vjoy_enabled; private static final float[][] vjoy = VJoy.baseVJoy(); @@ -41,6 +44,8 @@ public VirtualJoystickDelegate(View view) { vibratorThread = VibratorThread.getInstance(); readCustomVjoyValues(); + vjoy_enabled = new boolean[VJoy.VJoyCount + 4]; // include diagonals + Arrays.fill(vjoy_enabled, true); scaleGestureDetector = new ScaleGestureDetector(context, new OscOnScaleGestureListener()); } @@ -215,62 +220,63 @@ public boolean onTouchEvent(MotionEvent event, int width, int height) continue; for (int j = 0; j < vjoy.length; j++) { - if (x > vjoy[j][0] && x <= (vjoy[j][0] + vjoy[j][2])) + if (!editVjoyMode && !vjoy_enabled[j]) + continue; + if (x > vjoy[j][0] && x <= (vjoy[j][0] + vjoy[j][2]) + && y > vjoy[j][1] && y <= (vjoy[j][1] + vjoy[j][3])) { - if (y > vjoy[j][1] && y <= (vjoy[j][1] + vjoy[j][3])) - { - if (vjoy[j][4] >= VJoy.BTN_RTRIG) { - // Not for analog - if (vjoy[j][5] == 0) - if (!editVjoyMode) { - vibratorThread.click(); - } - vjoy[j][5] = 2; - } + if (vjoy[j][4] >= VJoy.BTN_RTRIG) { + // Not for analog + if (vjoy[j][5] == 0) + if (!editVjoyMode) { + vibratorThread.click(); + } + vjoy[j][5] = 2; + } - if (vjoy[j][4] == VJoy.BTN_ANARING) { + if (vjoy[j][4] == VJoy.BTN_ANARING) { + if (editVjoyMode) { + selectedVjoyElement = VJoy.ELEM_ANALOG; + resetEditMode(); + } else { + vjoy[j + 1][0] = x - vjoy[j + 1][2] / 2; + vjoy[j + 1][1] = y - vjoy[j + 1][3] / 2; + + JNIdc.vjoy(j + 1, vjoy[j + 1][0], vjoy[j + 1][1], vjoy[j + 1][2], vjoy[j + 1][3]); + anal_id = event.getPointerId(i); + } + } else if (vjoy[j][4] != VJoy.BTN_ANAPOINT) { + if (vjoy[j][4] == VJoy.BTN_LTRIG) { if (editVjoyMode) { - selectedVjoyElement = VJoy.ELEM_ANALOG; + selectedVjoyElement = VJoy.ELEM_LTRIG; resetEditMode(); } else { - vjoy[j + 1][0] = x - vjoy[j + 1][2] / 2; - vjoy[j + 1][1] = y - vjoy[j + 1][3] / 2; - - JNIdc.vjoy(j + 1, vjoy[j + 1][0], vjoy[j + 1][1], vjoy[j + 1][2], vjoy[j + 1][3]); - anal_id = event.getPointerId(i); + left_trigger = 255; + lt_id = event.getPointerId(i); } - } else if (vjoy[j][4] != VJoy.BTN_ANAPOINT) { - if (vjoy[j][4] == VJoy.BTN_LTRIG) { - if (editVjoyMode) { - selectedVjoyElement = VJoy.ELEM_LTRIG; - resetEditMode(); - } else { - left_trigger = 255; - lt_id = event.getPointerId(i); - } - } else if (vjoy[j][4] == VJoy.BTN_RTRIG) { - if (editVjoyMode) { - selectedVjoyElement = VJoy.ELEM_RTRIG; - resetEditMode(); - } else { - right_trigger = 255; - rt_id = event.getPointerId(i); - } + } else if (vjoy[j][4] == VJoy.BTN_RTRIG) { + if (editVjoyMode) { + selectedVjoyElement = VJoy.ELEM_RTRIG; + resetEditMode(); } else { - if (editVjoyMode) { - selectedVjoyElement = getElementIdFromButtonId(j); - resetEditMode(); - } else if (vjoy[j][4] == VJoy.key_CONT_FFORWARD) - fastForward = true; - else - rv &= ~(int)vjoy[j][4]; + right_trigger = 255; + rt_id = event.getPointerId(i); } + } else { + if (editVjoyMode) { + selectedVjoyElement = getElementIdFromButtonId(j); + resetEditMode(); + } else if (vjoy[j][4] == VJoy.key_CONT_FFORWARD) + fastForward = true; + else + rv &= ~(int)vjoy[j][4]; } } } } - } else { + } else if (vjoy_enabled[11]) { + // Analog stick if (x < vjoy[11][0]) x = vjoy[11][0]; else if (x > (vjoy[11][0] + vjoy[11][2])) @@ -369,11 +375,17 @@ else if (event.getPointerId(event.getActionIndex())==rt_id) public void setEditVjoyMode(boolean editVjoyMode) { this.editVjoyMode = editVjoyMode; selectedVjoyElement = -1; - if (editVjoyMode) + if (editVjoyMode) { this.handler.removeCallbacks(hideVGamepadRunnable); + Arrays.fill(vjoy_enabled, true); + } resetEditMode(); } + public void enableVjoy(boolean[] state) { + vjoy_enabled = state; + } + private class OscOnScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { diff --git a/shell/android-studio/flycast/src/main/jni/src/Android.cpp b/shell/android-studio/flycast/src/main/jni/src/Android.cpp index 3547f48b8..8b041cb0b 100644 --- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp @@ -69,6 +69,7 @@ static jobject g_activity; static jmethodID VJoyStartEditingMID; static jmethodID VJoyStopEditingMID; static jmethodID VJoyResetEditingMID; +static jmethodID VJoyEnableControlsMID; static jmethodID showScreenKeyboardMid; static jmethodID onGameStateChangeMid; @@ -593,6 +594,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_BaseGLActivity_regis VJoyStartEditingMID = env->GetMethodID(actClass, "VJoyStartEditing", "()V"); VJoyStopEditingMID = env->GetMethodID(actClass, "VJoyStopEditing", "(Z)V"); VJoyResetEditingMID = env->GetMethodID(actClass, "VJoyResetEditing", "()V"); + VJoyEnableControlsMID = env->GetMethodID(actClass, "VJoyEnableControls", "([Z)V"); showScreenKeyboardMid = env->GetMethodID(actClass, "showScreenKeyboard", "(Z)V"); onGameStateChangeMid = env->GetMethodID(actClass, "onGameStateChange", "(Z)V"); } @@ -602,6 +604,7 @@ namespace vgamepad { void startEditing() { + enableAllControls(); jni::env()->CallVoidMethod(g_activity, VJoyStartEditingMID); } void pauseEditing() { @@ -614,6 +617,13 @@ void stopEditing(bool canceled) { jni::env()->CallVoidMethod(g_activity, VJoyStopEditingMID, canceled); } +void setEnabledControls(bool enabled[_Count]) +{ + jni::BooleanArray jb{_Count}; + jb.setData(enabled, 0, _Count); + jni::env()->CallVoidMethod(g_activity, VJoyEnableControlsMID, (jbooleanArray)jb); +} + } void enableNetworkBroadcast(bool enable)