diff --git a/CMakeLists.txt b/CMakeLists.txt
index 24130ada9d..2c76bfb520 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1032,7 +1032,8 @@ if(NOT LIBRETRO)
if(ANDROID OR IOS)
cmrc_add_resources(flycast-resources
WHENCE resources
- resources/picture/buttons.png)
+ resources/picture/buttons.png
+ resources/picture/buttons-arcade.png)
endif()
endif()
@@ -1617,6 +1618,10 @@ if(NOT LIBRETRO)
target_sources(${PROJECT_NAME} PRIVATE
shell/android-studio/flycast/src/main/jni/src/Android.cpp
shell/android-studio/flycast/src/main/jni/src/android_gamepad.h
+ shell/android-studio/flycast/src/main/jni/src/android_storage.h
+ shell/android-studio/flycast/src/main/jni/src/http_client.h
+ shell/android-studio/flycast/src/main/jni/src/jni_util.h
+ shell/android-studio/flycast/src/main/jni/src/android_input.cpp
shell/android-studio/flycast/src/main/jni/src/android_keyboard.h)
target_link_libraries(${PROJECT_NAME} PRIVATE android log)
diff --git a/core/input/gamepad.h b/core/input/gamepad.h
index 88bce6fe7b..e4fdc049bd 100644
--- a/core/input/gamepad.h
+++ b/core/input/gamepad.h
@@ -52,6 +52,7 @@ enum DreamcastKey
EMU_BTN_SAVESTATE,
EMU_BTN_BYPASS_KB,
EMU_BTN_SCREENSHOT,
+ EMU_BTN_SRVMODE, // used internally by virtual gamepad
// Real axes
DC_AXIS_TRIGGERS = 0x1000000,
diff --git a/core/input/virtual_gamepad.h b/core/input/virtual_gamepad.h
new file mode 100644
index 0000000000..69adc28285
--- /dev/null
+++ b/core/input/virtual_gamepad.h
@@ -0,0 +1,117 @@
+/*
+ Copyright 2024 flyinghead
+
+ This file is part of Flycast.
+
+ Flycast is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ Flycast is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Flycast. If not, see .
+*/
+#pragma once
+#include "gamepad_device.h"
+#include "ui/vgamepad.h"
+
+class VirtualGamepad : public GamepadDevice
+{
+public:
+ VirtualGamepad(const char *api_name, int maple_port = 0)
+ : GamepadDevice(maple_port, api_name, false)
+ {
+ _name = "Virtual Gamepad";
+ _unique_id = "virtual_gamepad_uid";
+ input_mapper = std::make_shared();
+ // hasAnalogStick = true; // TODO has an analog stick but input mapping isn't persisted
+
+ leftTrigger = DC_AXIS_LT;
+ rightTrigger = DC_AXIS_RT;
+ }
+
+ bool is_virtual_gamepad() override {
+ return true;
+ };
+
+ // normalized coordinates [-1, 1]
+ void joystickInput(float x, float y)
+ {
+ vgamepad::setAnalogStick(x, y);
+ int joyx = std::round(x * 32767.f);
+ int joyy = std::round(y * 32767.f);
+ if (joyx >= 0)
+ gamepad_axis_input(DC_AXIS_RIGHT, joyx);
+ else
+ gamepad_axis_input(DC_AXIS_LEFT, -joyx);
+ if (joyy >= 0)
+ gamepad_axis_input(DC_AXIS_DOWN, joyy);
+ else
+ gamepad_axis_input(DC_AXIS_UP, -joyy);
+ }
+
+ void releaseAll()
+ {
+ for (int i = 0; i < 32; i++)
+ if (buttonState & (1 << i))
+ gamepad_btn_input(1 << i, false);
+ buttonState = 0;
+ joystickInput(0, 0);
+ gamepad_axis_input(DC_AXIS_LT, 0);
+ gamepad_axis_input(DC_AXIS_RT, 0);
+ if (previousFastForward)
+ gamepad_btn_input(EMU_BTN_FFORWARD, false);
+ previousFastForward = false;
+ }
+
+ virtual bool handleButtonInput(u32& state, u32 key, bool pressed) {
+ // can be overridden in derived classes to handle specific key combos
+ // (iOS up+down or left+right)
+ return false;
+ }
+
+ void buttonInput(vgamepad::ControlId controlId, bool pressed)
+ {
+ u32 kcode = vgamepad::controlToDcKey(controlId);
+ if (kcode == 0)
+ return;
+ if (handleButtonInput(buttonState, kcode, pressed))
+ return;
+ if (kcode == DC_AXIS_LT) {
+ gamepad_axis_input(DC_AXIS_LT, pressed ? 0x7fff : 0);
+ }
+ else if (kcode == DC_AXIS_RT) {
+ gamepad_axis_input(DC_AXIS_RT, pressed ? 0x7fff : 0);
+ }
+ else if (kcode == EMU_BTN_SRVMODE) {
+ if (pressed)
+ vgamepad::toggleServiceMode();
+ }
+ else
+ {
+ if (pressed)
+ buttonState |= kcode;
+ else
+ buttonState &= ~kcode;
+ if ((kcode & (DC_DPAD_LEFT | DC_DPAD_RIGHT)) != 0
+ && (kcode & (DC_DPAD_UP | DC_DPAD_DOWN)) != 0)
+ {
+ // diagonals
+ gamepad_btn_input(kcode & (DC_DPAD_LEFT | DC_DPAD_RIGHT), pressed);
+ gamepad_btn_input(kcode & (DC_DPAD_UP | DC_DPAD_DOWN), pressed);
+ }
+ else {
+ gamepad_btn_input(kcode, pressed);
+ }
+ }
+ }
+
+private:
+ u32 buttonState = 0;
+ bool previousFastForward = false;
+};
diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp
index 6d6ceb4943..e826a6b77f 100644
--- a/core/ui/gui.cpp
+++ b/core/ui/gui.cpp
@@ -1467,7 +1467,7 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad)
#if defined(__ANDROID__) || defined(TARGET_IPHONE)
vgamepad::ImguiVGamepadTexture tex;
- ImGui::Image(tex.getId(), ScaledVec2(300, 112.5f), ImVec2(0, 1), ImVec2(1, 0.25f));
+ ImGui::Image(tex.getId(), ScaledVec2(300.f, 150.f), ImVec2(0, 1), ImVec2(1, 0));
#endif
const char *gamepadPngTitle = "Select a PNG file";
if (ImGui::Button("Choose Image...", ScaledVec2(150, 30)))
diff --git a/core/ui/vgamepad.cpp b/core/ui/vgamepad.cpp
index b4d7e1d586..53867fdf51 100644
--- a/core/ui/vgamepad.cpp
+++ b/core/ui/vgamepad.cpp
@@ -31,14 +31,13 @@
#include "cfg/cfg.h"
#include "input/gamepad.h"
#include "hw/naomi/naomi_cart.h"
+#include "hw/naomi/card_reader.h"
#include "hw/maple/maple_devs.h"
#include
namespace vgamepad
{
-static void loadLayout();
-
struct Control
{
Control() = default;
@@ -53,9 +52,11 @@ struct Control
};
static Control Controls[_Count];
static bool Visible = true;
+static bool serviceMode;
static float AlphaTrans = 1.f;
static ImVec2 StickPos; // analog stick position [-1, 1]
constexpr char const *BTN_PATH = "picture/buttons.png";
+constexpr char const *BTN_PATH_ARCADE = "picture/buttons-arcade.png";
constexpr char const *CFG_SECTION = "vgamepad";
void displayCommands()
@@ -87,6 +88,14 @@ void displayCommands()
ImGui::End();
}
+static const char *getButtonsResPath() {
+ return settings.platform.isConsole() ? BTN_PATH : BTN_PATH_ARCADE;
+}
+
+static const char *getButtonsCfgName() {
+ return settings.platform.isConsole() ? "image" : "image_arcade";
+}
+
static bool loadOSDButtons(const std::string& path)
{
if (path.empty())
@@ -102,7 +111,7 @@ static bool loadOSDButtons(const std::string& path)
if (image_data == nullptr)
return false;
try {
- imguiDriver->updateTexture(BTN_PATH, image_data, width, height, false);
+ imguiDriver->updateTexture(getButtonsResPath(), image_data, width, height, false);
} catch (...) {
// vulkan can throw during resizing
}
@@ -115,25 +124,28 @@ static ImTextureID loadOSDButtons()
{
ImTextureID id{};
// custom image
- std::string path = cfgLoadStr(CFG_SECTION, "image", "");
+ std::string path = cfgLoadStr(CFG_SECTION, getButtonsCfgName(), "");
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;
+ if (settings.platform.isConsole())
+ {
+ // 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);
+ std::unique_ptr data = resource::load(getButtonsResPath(), 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);
+ id = imguiDriver->updateTexture(getButtonsResPath(), image_data, width, height, false);
} catch (...) {
// vulkan can throw during resizing
}
@@ -144,42 +156,100 @@ static ImTextureID loadOSDButtons()
ImTextureID ImguiVGamepadTexture::getId()
{
- ImTextureID id = imguiDriver->getTexture(BTN_PATH);
+ ImTextureID id = imguiDriver->getTexture(getButtonsResPath());
if (id == ImTextureID())
id = loadOSDButtons();
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 vjoy_tex[_Count][4] = {
+ // L
+ { 0, 0, 64, 64 },
+ // U
+ { 64, 0, 64, 64 },
+ // R
+ { 128, 0, 64, 64 },
+ // D
+ { 192, 0, 64, 64 },
+ // Y, btn3
+ { 256, 0, 64, 64 },
+ // X, btn2
+ { 320, 0, 64, 64 },
+ // B, btn1
+ { 384, 0, 64, 64 },
+ // A, btn0
+ { 448, 0, 64, 64 },
+
+ // Start
+ { 0, 64, 64, 64 },
+ // LT
+ { 64, 64, 90, 64 },
+ // RT
+ { 154, 64, 90, 64 },
+ // Analog
+ { 244, 64, 128, 128 },
+ // Stick
+ { 372, 64, 64, 64 },
+ // Fast forward
+ { 436, 64, 64, 64 },
+
+ // C, btn4
+ { 0, 128, 64, 64 },
+ // Z, btn5
+ { 64, 128, 64, 64 },
+
+ // service mode
+ { 0, 192, 64, 64 },
+ // insert card
+ { 64, 192, 64, 64 },
+
+ // Special controls
+ // service
+ { 128, 128, 64, 64 },
+ // coin
+ { 384, 128, 64, 64 },
+ // test
+ { 448, 128, 64, 64 },
};
+static ImVec2 coinUV0, coinUV1;
+static ImVec2 serviceUV0, serviceUV1;
+static ImVec2 testUV0, testUV1;
+
constexpr float OSD_TEX_W = 512.f;
constexpr float OSD_TEX_H = 256.f;
static void setUV()
{
- float u = 0;
- float v = 0;
int i = 0;
for (auto& control : Controls)
{
- control.uv0.x = (u + 1) / OSD_TEX_W;
- control.uv0.y = 1.f - (v + 1) / OSD_TEX_H;
- control.uv1.x = (u + vjoy_sz[0][i] - 1) / OSD_TEX_W;
- control.uv1.y = 1.f - (v + vjoy_sz[1][i] - 1) / OSD_TEX_H;
-
- u += vjoy_sz[0][i];
- if (u >= OSD_TEX_W) {
- u -= OSD_TEX_W;
- v += vjoy_sz[1][i];
- }
+ control.uv0.x = (vjoy_tex[i][0] + 1) / OSD_TEX_W;
+ control.uv0.y = 1.f - (vjoy_tex[i][1] + 1) / OSD_TEX_H;
+ control.uv1.x = (vjoy_tex[i][0] + vjoy_tex[i][2] - 1) / OSD_TEX_W;
+ control.uv1.y = 1.f - (vjoy_tex[i][1] + vjoy_tex[i][3] - 1) / OSD_TEX_H;
i++;
+ if (i >= _VisibleCount)
+ break;
}
+ serviceUV0.x = (vjoy_tex[i][0] + 1) / OSD_TEX_W;
+ serviceUV0.y = 1.f - (vjoy_tex[i][1] + 1) / OSD_TEX_H;
+ serviceUV1.x = (vjoy_tex[i][0] + vjoy_tex[i][2] - 1) / OSD_TEX_W;
+ serviceUV1.y = 1.f - (vjoy_tex[i][1] + vjoy_tex[i][3] - 1) / OSD_TEX_H;
+ i++;
+ coinUV0.x = (vjoy_tex[i][0] + 1) / OSD_TEX_W;
+ coinUV0.y = 1.f - (vjoy_tex[i][1] + 1) / OSD_TEX_H;
+ coinUV1.x = (vjoy_tex[i][0] + vjoy_tex[i][2] - 1) / OSD_TEX_W;
+ coinUV1.y = 1.f - (vjoy_tex[i][1] + vjoy_tex[i][3] - 1) / OSD_TEX_H;
+ i++;
+ testUV0.x = (vjoy_tex[i][0] + 1) / OSD_TEX_W;
+ testUV0.y = 1.f - (vjoy_tex[i][1] + 1) / OSD_TEX_H;
+ testUV1.x = (vjoy_tex[i][0] + vjoy_tex[i][2] - 1) / OSD_TEX_W;
+ testUV1.y = 1.f - (vjoy_tex[i][1] + vjoy_tex[i][3] - 1) / OSD_TEX_H;
+ i++;
+
}
static OnLoad _(&setUV);
@@ -191,18 +261,6 @@ void hide() {
Visible = false;
}
-void setPosition(ControlId id, float x, float y, float w, float h)
-{
- verify(id >= 0 && id < _VisibleCount);
- auto& control = Controls[id];
- control.pos.x = x;
- control.pos.y = y;
- if (w != 0)
- control.size.x = w;
- if (h != 0)
- control.size.y = h;
-}
-
ControlId hitTest(float x, float y)
{
for (const auto& control : Controls)
@@ -215,16 +273,17 @@ ControlId hitTest(float x, float y)
u32 controlToDcKey(ControlId control)
{
+ const bool arcade = settings.platform.isArcade();
switch (control)
{
case Left: return DC_DPAD_LEFT;
case Up: return DC_DPAD_UP;
case Right: return DC_DPAD_RIGHT;
case Down: return DC_DPAD_DOWN;
- case X: return DC_BTN_X;
- case Y: return DC_BTN_Y;
- case B: return DC_BTN_B;
- case A: return DC_BTN_A;
+ case X: return serviceMode ? DC_DPAD2_DOWN : arcade ? DC_BTN_C : DC_BTN_X;
+ case Y: return arcade ? DC_BTN_X : DC_BTN_Y;
+ case B: return serviceMode ? DC_DPAD2_UP : DC_BTN_B;
+ case A: return serviceMode ? DC_BTN_D : DC_BTN_A;
case Start: return DC_BTN_START;
case LeftTrigger: return DC_AXIS_LT;
case RightTrigger: return DC_AXIS_RT;
@@ -233,6 +292,11 @@ u32 controlToDcKey(ControlId control)
case RightUp: return DC_DPAD_RIGHT | DC_DPAD_UP;
case LeftDown: return DC_DPAD_LEFT | DC_DPAD_DOWN;
case RightDown: return DC_DPAD_RIGHT | DC_DPAD_DOWN;
+ // Arcade
+ case Btn4: return DC_BTN_Y;
+ case Btn5: return DC_BTN_Z;
+ case InsertCard: return DC_BTN_INSERT_CARD;
+ case ServiceMode: return EMU_BTN_SRVMODE;
default: return 0;
}
}
@@ -246,6 +310,19 @@ float getControlWidth(ControlId control) {
return Controls[control].size.x;
}
+void toggleServiceMode()
+{
+ serviceMode = !serviceMode;
+ if (serviceMode) {
+ Controls[A].disabled = false;
+ Controls[B].disabled = false;
+ Controls[X].disabled = false;
+ }
+ else {
+ startGame();
+ }
+}
+
static void drawButtonDim(ImDrawList *drawList, const Control& control, int state)
{
if (control.disabled)
@@ -255,15 +332,37 @@ static void drawButtonDim(ImDrawList *drawList, const Control& control, int stat
ImVec2 pos = control.pos * scale_h;
ImVec2 size = control.size * scale_h;
pos.x += offs_x;
- if (static_cast(&control - &Controls[0]) == AnalogStick)
+ ControlId controlId = static_cast(&control - &Controls[0]);
+ if (controlId == AnalogStick)
pos += StickPos * size;
float col = (0.5f - 0.25f * state / 255) * AlphaTrans;
float alpha = (100.f - config::VirtualGamepadTransparency) / 100.f * AlphaTrans;
ImVec4 color(col, col, col, alpha);
+ const ImVec2* uv0 = &control.uv0;
+ const ImVec2* uv1 = &control.uv1;
+ if (serviceMode)
+ switch (controlId)
+ {
+ case A:
+ uv0 = &coinUV0;
+ uv1 = &coinUV1;
+ break;
+ case B:
+ uv0 = &serviceUV0;
+ uv1 = &serviceUV1;
+ break;
+ case X:
+ uv0 = &testUV0;
+ uv1 = &testUV1;
+ break;
+ default:
+ break;
+ }
+
ImguiVGamepadTexture tex;
- tex.draw(drawList, pos, size, control.uv0, control.uv1, color);
+ tex.draw(drawList, pos, size, *uv0, *uv1, color);
}
static void drawButton(ImDrawList *drawList, const Control& control, bool state) {
@@ -272,7 +371,6 @@ static void drawButton(ImDrawList *drawList, const Control& control, bool state)
void draw()
{
-#ifndef __ANDROID__
if (Controls[Left].pos.x == 0.f)
{
loadLayout();
@@ -280,7 +378,6 @@ void draw()
// mark done
Controls[Left].pos.x = 1e-12f;
}
-#endif
ImDrawList *drawList = ImGui::GetBackgroundDrawList();
drawButton(drawList, Controls[Left], kcode[0] & DC_DPAD_LEFT);
@@ -288,10 +385,10 @@ void draw()
drawButton(drawList, Controls[Right], kcode[0] & DC_DPAD_RIGHT);
drawButton(drawList, Controls[Down], kcode[0] & DC_DPAD_DOWN);
- drawButton(drawList, Controls[X], kcode[0] & (settings.platform.isConsole() ? DC_BTN_X : DC_BTN_C));
+ drawButton(drawList, Controls[X], kcode[0] & (serviceMode ? DC_DPAD2_DOWN : settings.platform.isConsole() ? DC_BTN_X : DC_BTN_C));
drawButton(drawList, Controls[Y], kcode[0] & (settings.platform.isConsole() ? DC_BTN_Y : DC_BTN_X));
- drawButton(drawList, Controls[B], kcode[0] & DC_BTN_B);
- drawButton(drawList, Controls[A], kcode[0] & DC_BTN_A);
+ drawButton(drawList, Controls[B], kcode[0] & (serviceMode ? DC_DPAD2_UP : DC_BTN_B));
+ drawButton(drawList, Controls[A], kcode[0] & (serviceMode ? DC_BTN_D : DC_BTN_A));
drawButton(drawList, Controls[Start], kcode[0] & DC_BTN_START);
@@ -303,6 +400,12 @@ void draw()
drawButton(drawList, Controls[AnalogStick], false);
drawButton(drawList, Controls[FastForward], false);
+
+ drawButton(drawList, Controls[Btn4], kcode[0] & DC_BTN_Y);
+ drawButton(drawList, Controls[Btn5], kcode[0] & DC_BTN_Z);
+ drawButton(drawList, Controls[ServiceMode], !serviceMode);
+ drawButton(drawList, Controls[InsertCard], kcode[0] & DC_BTN_INSERT_CARD);
+
AlphaTrans += ((float)Visible - AlphaTrans) / 2;
}
@@ -349,6 +452,7 @@ struct LayoutElement
void reset()
{
+ applyUiScale();
scale = 1.f;
const float dcw = 480.f * (float)settings.display.width / settings.display.height;
const float uiscale = getUIScale();
@@ -374,6 +478,11 @@ static LayoutElement Layout[] {
{ "RT", -32.f,-240.f, 90.f, 64.f },
{ "analog", 40.f,-320.f, 128.f, 128.f },
{ "fforward", -24.f, 24.f, 64.f, 64.f },
+
+ { "btn4", -24.f,-216.f, 64.f, 64.f },
+ { "btn5", -152.f,-216.f, 64.f, 64.f },
+ { "service", -24.f, 96.f, 64.f, 64.f },
+ { "inscard", 40.f,-250.f, 64.f, 64.f },
};
static void applyLayout()
@@ -440,6 +549,26 @@ static void applyLayout()
scale = Layout[Elem_FForward].scale * uiscale;
Controls[FastForward].pos = { Layout[Elem_FForward].x * dcw - dx, Layout[Elem_FForward].y * 480.f };
Controls[FastForward].size = { Layout[Elem_FForward].dw * scale, Layout[Elem_FForward].dh * scale };
+
+ // ARCADE
+ // Button 4
+ scale = Layout[Elem_Btn4].scale * uiscale;
+ Controls[Btn4].pos = { Layout[Elem_Btn4].x * dcw - dx, Layout[Elem_Btn4].y * 480.f };
+ Controls[Btn4].size = { Layout[Elem_Btn4].dw * scale, Layout[Elem_Btn4].dh * scale };
+ // Button 5
+ scale = Layout[Elem_Btn5].scale * uiscale;
+ Controls[Btn5].pos = { Layout[Elem_Btn5].x * dcw - dx, Layout[Elem_Btn5].y * 480.f };
+ Controls[Btn5].size = { Layout[Elem_Btn5].dw * scale, Layout[Elem_Btn5].dh * scale };
+
+ // Service Mode
+ scale = Layout[Elem_ServiceMode].scale * uiscale;
+ Controls[ServiceMode].pos = { Layout[Elem_ServiceMode].x * dcw - dx, Layout[Elem_ServiceMode].y * 480.f };
+ Controls[ServiceMode].size = { Layout[Elem_ServiceMode].dw * scale, Layout[Elem_ServiceMode].dh * scale };
+
+ // Insert Card
+ scale = Layout[Elem_InsertCard].scale * uiscale;
+ Controls[InsertCard].pos = { Layout[Elem_InsertCard].x * dcw - dx, Layout[Elem_InsertCard].y * 480.f };
+ Controls[InsertCard].size = { Layout[Elem_InsertCard].dw * scale, Layout[Elem_InsertCard].dh * scale };
}
void applyUiScale() {
@@ -447,7 +576,7 @@ void applyUiScale() {
element.applyUiScale();
}
-static void loadLayout()
+void loadLayout()
{
for (auto& element : Layout) {
element.reset();
@@ -456,7 +585,7 @@ static void loadLayout()
applyLayout();
}
-static void saveLayout()
+void saveLayout()
{
cfgSetAutoSave(false);
for (auto& element : Layout)
@@ -500,11 +629,11 @@ void scaleElement(Element element, float factor)
void loadImage(const std::string& path)
{
if (path.empty()) {
- cfgSaveStr(CFG_SECTION, "image", "");
+ cfgSaveStr(CFG_SECTION, getButtonsCfgName(), "");
loadOSDButtons();
}
else if (loadOSDButtons(path)) {
- cfgSaveStr(CFG_SECTION, "image", path);
+ cfgSaveStr(CFG_SECTION, getButtonsCfgName(), path);
}
}
@@ -554,8 +683,13 @@ static void disableControl(ControlId ctrlId)
void startGame()
{
enableAllControls();
+ serviceMode = false;
if (settings.platform.isConsole())
{
+ disableControl(Btn4);
+ disableControl(Btn5);
+ disableControl(ServiceMode);
+ disableControl(InsertCard);
switch (config::MapleMainDevices[0])
{
case MDT_LightGun:
@@ -590,9 +724,17 @@ void startGame()
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 (!card_reader::readerAvailable())
+ disableControl(InsertCard);
+ if (settings.platform.isAtomiswave()) {
+ disableControl(Btn5);
+ }
+ else if (settings.platform.isSystemSP())
+ {
+ disableControl(Y);
+ disableControl(Btn4);
+ disableControl(Btn5);
+ }
if (NaomiGameInputs != nullptr)
{
bool fullAnalog = false;
@@ -618,6 +760,14 @@ void startGame()
}
if (!fullAnalog)
disableControl(AnalogArea);
+ if (!lt)
+ disableControl(LeftTrigger);
+ else
+ disableControl(Btn5);
+ if (!rt)
+ disableControl(RightTrigger);
+ else
+ disableControl(Btn4);
u32 usedButtons = 0;
for (const auto& button : NaomiGameInputs->buttons)
{
@@ -627,21 +777,17 @@ void startGame()
}
if (settings.platform.isAtomiswave())
{
- // button order: A B X Y RT
- /* these ones are always needed for now
+ // button order: A B X Y B4
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_BTN4_KEY) == 0)
+ disableControl(Btn4);
if ((usedButtons & AWAVE_UP_KEY) == 0)
disableControl(Up);
if ((usedButtons & AWAVE_DOWN_KEY) == 0)
@@ -650,10 +796,30 @@ void startGame()
disableControl(Left);
if ((usedButtons & AWAVE_RIGHT_KEY) == 0)
disableControl(Right);
+ if ((usedButtons & AWAVE_START_KEY) == 0)
+ disableControl(Start);
+ }
+ else if (settings.platform.isSystemSP())
+ {
+ if ((usedButtons & DC_BTN_A) == 0)
+ disableControl(A);
+ if ((usedButtons & DC_BTN_B) == 0)
+ disableControl(B);
+ if ((usedButtons & DC_BTN_C) == 0)
+ disableControl(X);
+ if ((usedButtons & DC_DPAD_UP) == 0)
+ disableControl(Up);
+ if ((usedButtons & DC_DPAD_DOWN) == 0)
+ disableControl(Down);
+ if ((usedButtons & DC_DPAD_LEFT) == 0)
+ disableControl(Left);
+ if ((usedButtons & DC_DPAD_RIGHT) == 0)
+ disableControl(Right);
+ if ((usedButtons & DC_BTN_START) == 0)
+ disableControl(Start);
}
else
{
- /* these ones are always needed for now
if ((usedButtons & NAOMI_BTN0_KEY) == 0)
disableControl(A);
if ((usedButtons & NAOMI_BTN1_KEY) == 0)
@@ -661,16 +827,15 @@ void startGame()
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)
+ if ((usedButtons & NAOMI_BTN4_KEY) == 0)
+ // Y
+ disableControl(Btn4);
+ if ((usedButtons & NAOMI_BTN5_KEY) == 0)
// Z
- disableControl(LeftTrigger);
+ disableControl(Btn5);
if ((usedButtons & NAOMI_UP_KEY) == 0)
disableControl(Up);
if ((usedButtons & NAOMI_DOWN_KEY) == 0)
@@ -679,28 +844,41 @@ void startGame()
disableControl(Left);
if ((usedButtons & NAOMI_RIGHT_KEY) == 0)
disableControl(Right);
+ if ((usedButtons & NAOMI_START_KEY) == 0)
+ disableControl(Start);
}
}
- 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);
+ if (settings.input.lightgunGame)
+ {
+ // TODO enable mouse?
+ disableControl(A);
+ disableControl(X);
+ disableControl(Y);
+ disableControl(Btn4);
+ disableControl(Btn5);
+ disableControl(AnalogArea);
+ disableControl(LeftTrigger);
+ disableControl(RightTrigger);
+ disableControl(Up);
+ disableControl(Down);
+ disableControl(Left);
+ disableControl(Right);
+ }
+ else
+ {
+ // all analog games *should* have an input description
+ disableControl(AnalogArea);
+ disableControl(LeftTrigger);
+ disableControl(RightTrigger);
+ }
}
}
- bool enabledState[_Count];
- for (int i = 0; i < _Count; i++)
- enabledState[i] = !Controls[i].disabled;
- setEnabledControls(enabledState);
+}
+
+void resetEditing() {
+ resetLayout();
}
#ifndef __ANDROID__
@@ -721,13 +899,6 @@ void stopEditing(bool canceled)
saveLayout();
}
-void resetEditing() {
- resetLayout();
-}
-
-void setEnabledControls(bool enabled[_Count]) {
-}
-
#endif
} // namespace vgamepad
diff --git a/core/ui/vgamepad.h b/core/ui/vgamepad.h
index 86b099aa77..d97fb21f0d 100644
--- a/core/ui/vgamepad.h
+++ b/core/ui/vgamepad.h
@@ -40,13 +40,18 @@ enum ControlId
AnalogStick,
FastForward,
+ Btn4,
+ Btn5,
+ ServiceMode,
+ InsertCard,
+
LeftUp,
RightUp,
LeftDown,
RightDown,
_Count,
- _VisibleCount = FastForward + 1,
+ _VisibleCount = LeftUp,
};
enum Element
@@ -59,6 +64,10 @@ enum Element
Elem_RT,
Elem_Analog,
Elem_FForward,
+ Elem_Btn4,
+ Elem_Btn5,
+ Elem_ServiceMode,
+ Elem_InsertCard,
};
class ImguiVGamepadTexture : public ImguiTexture
@@ -69,8 +78,6 @@ class ImguiVGamepadTexture : public ImguiTexture
#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();
@@ -87,11 +94,14 @@ ControlId hitTest(float x, float y);
u32 controlToDcKey(ControlId control);
void setAnalogStick(float x, float y);
float getControlWidth(ControlId);
+void toggleServiceMode();
void applyUiScale();
Element layoutHitTest(float x, float y);
void translateElement(Element element, float dx, float dy);
void scaleElement(Element element, float factor);
+void loadLayout();
+void saveLayout();
#else
diff --git a/resources/picture/buttons-arcade.png b/resources/picture/buttons-arcade.png
new file mode 100644
index 0000000000..980212cffc
Binary files /dev/null and b/resources/picture/buttons-arcade.png differ
diff --git a/resources/picture/buttons-arcade.svg b/resources/picture/buttons-arcade.svg
new file mode 100644
index 0000000000..db982d2ffb
--- /dev/null
+++ b/resources/picture/buttons-arcade.svg
@@ -0,0 +1,2279 @@
+
+
+
+
diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/BaseGLActivity.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/BaseGLActivity.java
index 4766949585..01505f39c9 100644
--- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/BaseGLActivity.java
+++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/BaseGLActivity.java
@@ -49,7 +49,6 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
private static final int AUDIO_PERM_REQUEST = 1002;
protected SharedPreferences prefs;
- protected float[][] vjoy_d_cached; // Used for VJoy editing
private AudioBackend audioBackend;
protected Handler handler = new Handler();
private boolean audioPermissionRequested = false;
@@ -406,7 +405,7 @@ else if (requestCode == STORAGE_PERM_REQUEST) {
}
//setup mic
- if (Emulator.micPluggedIn())
+ if (InputDeviceManager.isMicPluggedIn())
requestRecordAudioPermission();
}
diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/Emulator.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/Emulator.java
index 28d8ba14e2..5a089d2bf0 100644
--- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/Emulator.java
+++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/Emulator.java
@@ -10,7 +10,8 @@
import androidx.appcompat.app.AppCompatDelegate;
import com.flycast.emulator.config.Config;
-import com.flycast.emulator.emu.JNIdc;
+import com.flycast.emulator.emu.VGamepad;
+import com.flycast.emulator.periph.InputDeviceManager;
public class Emulator extends Application {
private static Context context;
@@ -18,32 +19,14 @@ public class Emulator extends Application {
private WifiManager wifiManager = null;
private WifiManager.MulticastLock multicastLock = null;
- // see MapleDeviceType in hw/maple/maple_devs.h
- public static final int MDT_Microphone = 2;
- public static final int MDT_None = 8;
-
public static int vibrationPower = 80;
- public static int[] maple_devices = {
- MDT_None,
- MDT_None,
- MDT_None,
- MDT_None
- };
- public static int[][] maple_expansion_devices = {
- { MDT_None, MDT_None },
- { MDT_None, MDT_None },
- { MDT_None, MDT_None },
- { MDT_None, MDT_None },
- };
-
/**
* Load the settings from native code
*
*/
public void getConfigurationPrefs() {
- Emulator.vibrationPower = JNIdc.getVirtualGamepadVibration();
- JNIdc.getControllers(maple_devices, maple_expansion_devices);
+ Emulator.vibrationPower = VGamepad.getVibrationPower();
}
/**
@@ -54,26 +37,17 @@ public void SaveAndroidSettings(String homeDirectory)
{
Log.i("flycast", "SaveAndroidSettings: saving preferences");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- Emulator.vibrationPower = JNIdc.getVirtualGamepadVibration();
- JNIdc.getControllers(maple_devices, maple_expansion_devices);
+ Emulator.vibrationPower = VGamepad.getVibrationPower();
prefs.edit()
.putString(Config.pref_home, homeDirectory).apply();
- if (micPluggedIn() && currentActivity instanceof BaseGLActivity) {
+ if (InputDeviceManager.isMicPluggedIn() && currentActivity instanceof BaseGLActivity) {
+ Log.i("flycast", "SaveAndroidSettings: MIC PLUGGED IN");
((BaseGLActivity)currentActivity).requestRecordAudioPermission();
}
}
- public static boolean micPluggedIn() {
- JNIdc.getControllers(maple_devices, maple_expansion_devices);
- for (int[] maple_expansion_device : maple_expansion_devices)
- if (maple_expansion_device[0] == MDT_Microphone
- || maple_expansion_device[1] == MDT_Microphone)
- return true;
- return false;
- }
-
@Override
public void onCreate() {
super.onCreate();
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 4f84b6e2b5..a729a92b65 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
@@ -16,10 +16,8 @@
import androidx.annotation.Nullable;
-import com.flycast.emulator.emu.JNIdc;
import com.flycast.emulator.emu.NativeGLView;
import com.flycast.emulator.periph.InputDeviceManager;
-import com.flycast.emulator.periph.VJoy;
public final class NativeGLActivity extends BaseGLActivity {
@@ -61,38 +59,35 @@ public boolean isSurfaceReady() {
return mView != null && mView.isSurfaceReady();
}
- // Called from native code
- private void VJoyStartEditing() {
- vjoy_d_cached = VJoy.readCustomVjoyValues(getApplicationContext());
- JNIdc.showVirtualGamepad();
- mView.setEditVjoyMode(true);
+ @Override
+ public void onGameStateChange(boolean started) {
+ super.onGameStateChange(started);
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (started)
+ mView.showVGamepad();
+ }
+ });
}
+
// Called from native code
- private void VJoyResetEditing() {
- VJoy.resetCustomVjoyValues(getApplicationContext());
- mView.readCustomVjoyValues();
- mView.resetEditMode();
+ private void VJoyStartEditing() {
handler.post(new Runnable() {
@Override
public void run() {
- mView.requestLayout();
+ mView.setEditVjoyMode(true);
}
});
}
// Called from native code
- private void VJoyStopEditing(final boolean canceled) {
+ private void VJoyStopEditing() {
handler.post(new Runnable() {
@Override
public void run() {
- if (canceled)
- mView.restoreCustomVjoyValues(vjoy_d_cached);
mView.setEditVjoyMode(false);
}
});
}
- 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/EditVirtualJoystickDelegate.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/EditVirtualJoystickDelegate.java
new file mode 100644
index 0000000000..422dfd1456
--- /dev/null
+++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/EditVirtualJoystickDelegate.java
@@ -0,0 +1,111 @@
+/*
+ Copyright 2024 flyinghead
+
+ This file is part of Flycast.
+
+ Flycast is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ Flycast is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Flycast. If not, see .
+*/
+package com.flycast.emulator.emu;
+
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+public class EditVirtualJoystickDelegate implements TouchEventHandler
+{
+ private View view;
+ private ScaleGestureDetector scaleGestureDetector;
+ private int currentElement = -1;
+ private float lastX, lastY;
+
+ public EditVirtualJoystickDelegate(View view) {
+ this.view = view;
+ scaleGestureDetector = new ScaleGestureDetector(view.getContext(), new EditVirtualJoystickDelegate.ScaleGestureListener());
+ }
+
+ @Override
+ public void stop() {
+ }
+ @Override
+ public void show() {
+ VGamepad.show();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event, int width, int height)
+ {
+ scaleGestureDetector.onTouchEvent(event);
+ if (scaleGestureDetector.isInProgress())
+ return true;
+
+ int actionMasked = event.getActionMasked();
+ int actionIndex = event.getActionIndex();
+ switch (actionMasked)
+ {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ currentElement = -1;
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ lastX = event.getX(actionIndex) / view.getWidth();
+ lastY = event.getY(actionIndex) / view.getHeight();
+ currentElement = VGamepad.layoutHitTest(lastX, lastY);
+ return currentElement != -1;
+
+ case MotionEvent.ACTION_MOVE:
+ if (currentElement != -1 && event.getPointerCount() == 1)
+ {
+ float x = event.getX(actionIndex) / view.getWidth();
+ float y = event.getY(actionIndex) / view.getHeight();
+ VGamepad.translateElement(currentElement, x - lastX, y - lastY);
+ lastX = x;
+ lastY = y;
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ private class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
+ private int elemId = -1;
+ @Override
+ public boolean onScaleBegin(@NonNull ScaleGestureDetector detector)
+ {
+ elemId = VGamepad.layoutHitTest(detector.getFocusX() / view.getWidth(), detector.getFocusY() / view.getHeight());
+ return elemId != -1;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector)
+ {
+ if (elemId == -1)
+ return false;
+ VGamepad.scaleElement(elemId, detector.getScaleFactor());
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ elemId = -1;
+ }
+ }
+}
diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/JNIdc.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/JNIdc.java
index 45aecceea3..ff06fd2376 100644
--- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/JNIdc.java
+++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/JNIdc.java
@@ -18,12 +18,7 @@ public final class JNIdc
public static native void rendinitNative(Surface surface, int w, int h);
- public static native void vjoy(int id, float x, float y, float w, float h);
-
- public static native void getControllers(int[] controllers, int[][] peripherals);
-
public static native void setupMic(SipEmulator sip);
- public static native int getVirtualGamepadVibration();
public static native void screenCharacteristics(float screenDpi, float refreshRate);
public static native void guiOpenSettings();
@@ -31,6 +26,4 @@ public final class JNIdc
public static native boolean guiIsContentBrowser();
public static native void guiSetInsets(int left, int right, int top, int bottom);
- public static native void showVirtualGamepad();
- public static native void hideVirtualGamepad();
}
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 b70cda441c..da10864e1c 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
@@ -10,6 +10,7 @@
import android.util.Log;
import android.view.Display;
import android.view.DisplayCutout;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
@@ -24,11 +25,7 @@
public class NativeGLView extends SurfaceView implements SurfaceHolder.Callback {
private boolean surfaceReady = false;
private boolean paused = false;
- VirtualJoystickDelegate vjoyDelegate;
-
- public void restoreCustomVjoyValues(float[][] vjoy_d_cached) {
- vjoyDelegate.restoreCustomVjoyValues(vjoy_d_cached);
- }
+ private TouchEventHandler vjoyDelegate = null;
public NativeGLView(Context context) {
this(context, null);
@@ -63,20 +60,21 @@ public void onSystemUiVisibilityChange(int visibility) {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- vjoyDelegate = new VirtualJoystickDelegate(this);
+ if (InputDeviceManager.getInstance().hasTouchscreen())
+ vjoyDelegate = new VirtualJoystickDelegate(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- vjoyDelegate.stop();
+ if (vjoyDelegate != null)
+ vjoyDelegate.stop();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
super.onLayout(changed, left, top, right, bottom);
- vjoyDelegate.layout(getWidth(), getHeight());
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
Display d;
@@ -101,10 +99,6 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
}
}
- public void resetEditMode() {
- vjoyDelegate.resetEditMode();
- }
-
@Override
public boolean onTouchEvent(final MotionEvent event)
{
@@ -113,13 +107,13 @@ public boolean onTouchEvent(final MotionEvent event)
InputDeviceManager.getInstance().mouseEvent(Math.round(event.getX()), Math.round(event.getY()), event.getButtonState());
return true;
}
- else
+ if (vjoyDelegate != null && (event.getSource() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN)
return vjoyDelegate.onTouchEvent(event, getWidth(), getHeight());
+ return false;
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
-
}
@Override
@@ -179,14 +173,18 @@ public void onWindowFocusChanged(boolean hasFocus) {
}
}
- public void readCustomVjoyValues() {
- vjoyDelegate.readCustomVjoyValues();
+ public void setEditVjoyMode(boolean editVjoyMode)
+ {
+ if (!InputDeviceManager.getInstance().hasTouchscreen())
+ return;
+ if (editVjoyMode && !(vjoyDelegate instanceof EditVirtualJoystickDelegate))
+ vjoyDelegate = new EditVirtualJoystickDelegate(this);
+ else if (!editVjoyMode && !(vjoyDelegate instanceof VirtualJoystickDelegate))
+ vjoyDelegate = new VirtualJoystickDelegate(this);
}
- public void setEditVjoyMode(boolean editVjoyMode) {
- vjoyDelegate.setEditVjoyMode(editVjoyMode);
- }
- public void enableVjoy(boolean[] state) {
- vjoyDelegate.enableVjoy(state);
+ public void showVGamepad() {
+ if (vjoyDelegate != null)
+ vjoyDelegate.show();
}
}
diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/TouchEventHandler.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/TouchEventHandler.java
new file mode 100644
index 0000000000..40f3513371
--- /dev/null
+++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/TouchEventHandler.java
@@ -0,0 +1,27 @@
+/*
+ Copyright 2024 flyinghead
+
+ This file is part of Flycast.
+
+ Flycast is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ Flycast is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Flycast. If not, see .
+*/
+package com.flycast.emulator.emu;
+
+import android.view.MotionEvent;
+
+public interface TouchEventHandler {
+ boolean onTouchEvent(MotionEvent event, int width, int height);
+ void stop();
+ void show();
+}
diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/VGamepad.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/VGamepad.java
new file mode 100644
index 0000000000..4b85c8ce68
--- /dev/null
+++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/emu/VGamepad.java
@@ -0,0 +1,35 @@
+/*
+ Copyright 2024 flyinghead
+
+ This file is part of Flycast.
+
+ Flycast is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ Flycast is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Flycast. If not, see .
+*/
+package com.flycast.emulator.emu;
+
+public class VGamepad
+{
+ static { System.loadLibrary("flycast"); }
+
+ public static native int getVibrationPower();
+
+ public static native void show();
+ public static native void hide();
+ public static native int hitTest(float x, float y);
+ public static native float getControlWidth(int controlId);
+
+ public static native int layoutHitTest(float x, float y);
+ public static native void scaleElement(int elemId, float scale);
+ public static native void translateElement(int elemId, float x, float y);
+}
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 813ef79815..a0276225f7 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
@@ -1,409 +1,273 @@
+/*
+ Copyright 2024 flyinghead
+
+ This file is part of Flycast.
+
+ Flycast is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ Flycast is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Flycast. If not, see .
+*/
package com.flycast.emulator.emu;
import android.content.Context;
-import android.content.res.Configuration;
import android.os.Handler;
-import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.View;
import com.flycast.emulator.periph.InputDeviceManager;
-import com.flycast.emulator.periph.VJoy;
import com.flycast.emulator.periph.VibratorThread;
-import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
-public class VirtualJoystickDelegate {
- private VibratorThread vibratorThread;
-
- private boolean editVjoyMode = false;
- private int selectedVjoyElement = VJoy.ELEM_NONE;
- private ScaleGestureDetector scaleGestureDetector;
+public class VirtualJoystickDelegate implements TouchEventHandler
+{
+ private static final int CTLID_ANARING = 11;
+ private static final int CTLID_ANASTICK = 12;
+ private VibratorThread vibratorThread;
private Handler handler = new Handler();
private Runnable hideVGamepadRunnable = new Runnable() {
@Override
public void run() {
- JNIdc.hideVirtualGamepad();
+ VGamepad.hide();
}
};
-
- private float[][] vjoy_d_custom;
- private boolean[] vjoy_enabled;
-
- private static final float[][] vjoy = VJoy.baseVJoy();
-
private Context context;
private View view;
+ private int joyPointerId = -1;
+ private float joyBiasX, joyBiasY;
+ private Map pidToControlId = new HashMap<>();
+ private int mouseButtons = 0;
+ private int[] mousePos = { -32768, -32768 };
+ private int mousePid = -1;
public VirtualJoystickDelegate(View view) {
this.view = view;
this.context = view.getContext();
vibratorThread = VibratorThread.getInstance();
-
- readCustomVjoyValues();
- vjoy_enabled = new boolean[VJoy.VJoyCount + 4]; // include diagonals
- Arrays.fill(vjoy_enabled, true);
- scaleGestureDetector = new ScaleGestureDetector(context, new OscOnScaleGestureListener());
}
+ @Override
public void stop() {
vibratorThread.stopThread();
vibratorThread = null;
}
- public void readCustomVjoyValues() {
- vjoy_d_custom = VJoy.readCustomVjoyValues(context);
- }
-
- public void restoreCustomVjoyValues(float[][] vjoy_d_cached) {
- vjoy_d_custom = vjoy_d_cached;
- VJoy.writeCustomVjoyValues(vjoy_d_cached, context);
-
- resetEditMode();
- view.requestLayout();
- }
-
- private void reset_analog()
- {
-
- int j=11;
- vjoy[j+1][0]=vjoy[j][0]+vjoy[j][2]/2-vjoy[j+1][2]/2;
- vjoy[j+1][1]=vjoy[j][1]+vjoy[j][3]/2-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]);
- }
-
- private int get_anal(int j, int axis)
- {
- return (int) (((vjoy[j+1][axis]+vjoy[j+1][axis+2]/2) - vjoy[j][axis] - vjoy[j][axis+2]/2)*254/vjoy[j][axis+2]);
- }
-
- private float vbase(float p, float m, float scl)
- {
- return (int) ( m - (m -p)*scl);
- }
-
- private float vbase(float p, float scl)
+ private boolean touchMouseEvent(MotionEvent event)
{
- return (int) (p*scl );
- }
-
- private boolean isTablet() {
- return (context.getResources().getConfiguration().screenLayout
- & Configuration.SCREENLAYOUT_SIZE_MASK)
- >= Configuration.SCREENLAYOUT_SIZE_LARGE;
- }
-
- public void layout(int width, int height)
- {
- //dcpx/cm = dcpx/px * px/cm
- float magic = isTablet() ? 0.8f : 0.7f;
- float scl = 480.0f / height * context.getResources().getDisplayMetrics().density * magic;
- float scl_dc = height / 480.0f;
- float tx = (width - 640.0f * scl_dc) / 2 / scl_dc;
-
- float a_x = -tx + 24 * scl;
- float a_y = -24 * scl;
-
- // Not sure how this can happen
- if (vjoy_d_custom == null)
- return;
-
- float[][] vjoy_d = VJoy.getVjoy_d(vjoy_d_custom);
-
- for (int i=0;i vjoy[j][0] && x <= (vjoy[j][0] + vjoy[j][2])
- && 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_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_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 (editVjoyMode) {
- selectedVjoyElement = getElementIdFromButtonId(j);
- resetEditMode();
- } else if (vjoy[j][4] == VJoy.key_CONT_FFORWARD)
- fastForward = true;
- else
- rv &= ~(int)vjoy[j][4];
- }
- }
- }
- }
- } 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]))
- x = vjoy[11][0] + vjoy[11][2];
-
- if (y < vjoy[11][1])
- y = vjoy[11][1];
- else if (y > (vjoy[11][1] + vjoy[11][3]))
- y = vjoy[11][1] + vjoy[11][3];
-
- int j = 11;
- 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]);
-
- }
- }
-
- for (int j = 0; j < vjoy.length; j++) {
- if (vjoy[j][5] == 2)
- vjoy[j][5] = 1;
- else if (vjoy[j][5] == 1)
- vjoy[j][5] = 0;
- }
- }
-
- switch(aid)
+ int actionIndex = event.getActionIndex();
+ switch (event.getActionMasked())
{
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- selectedVjoyElement = -1;
- reset_analog();
- anal_id = -1;
- rv = 0xFFFFFFFF;
- fastForward = false;
- right_trigger = 0;
- left_trigger = 0;
- lt_id = -1;
- rt_id = -1;
- for (int j= 0 ;j < vjoy.length; j++)
- vjoy[j][5] = 0;
- mouse_btns = 0;
+ // Release all
+ pidToControlId.clear();
+ joyPointerId = -1;
+ InputDeviceManager.getInstance().virtualReleaseAll();
break;
- case MotionEvent.ACTION_POINTER_UP:
- if (event.getPointerId(event.getActionIndex())==anal_id)
- {
- reset_analog();
- anal_id = -1;
- }
- else if (event.getPointerId(event.getActionIndex())==lt_id)
- {
- left_trigger = 0;
- lt_id = -1;
- }
- else if (event.getPointerId(event.getActionIndex())==rt_id)
+ case MotionEvent.ACTION_DOWN:
+ // First release all
+ pidToControlId.clear();
+ joyPointerId = -1;
+ InputDeviceManager.getInstance().virtualReleaseAll();
+ // Release the mouse too
+ mousePid = -1;
+ mouseButtons = 0;
+ InputDeviceManager.getInstance().mouseEvent(mousePos[0], mousePos[1], mouseButtons);
+ // Then fall through
+ case MotionEvent.ACTION_POINTER_DOWN:
+ {
+ Point p = new Point(event.getX(actionIndex), event.getY(actionIndex));
+ p = translateCoords(p, new Point(width, height));
+ int control = VGamepad.hitTest(p.x, p.y);
+ if (control != -1)
{
- right_trigger = 0;
- rt_id = -1;
+ int pid = event.getPointerId(actionIndex);
+ if (control == CTLID_ANARING || control == CTLID_ANASTICK)
+ {
+ if (joyPointerId == -1)
+ {
+ // Analog stick down
+ joyPointerId = pid;
+ joyBiasX = p.x;
+ joyBiasY = p.y;
+ InputDeviceManager.getInstance().virtualJoystick(0, 0);
+ return true;
+ }
+ }
+ else
+ {
+ // Button down
+ InputDeviceManager.getInstance().virtualButtonInput(control, true);
+ pidToControlId.put(pid, control);
+ vibratorThread.click();
+ return true;
+ }
}
break;
+ }
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_DOWN:
- if (event.getPointerCount() != 1)
- {
- mouse_btns = 0;
- }
- else
+ case MotionEvent.ACTION_MOVE:
+ for (int i = 0; i < event.getPointerCount(); i++)
{
- mouse_pos[0] = Math.round(event.getX());
- mouse_pos[1] = Math.round(event.getY());
- mouse_btns = MotionEvent.BUTTON_PRIMARY; // Mouse left button down
+ int pid = event.getPointerId(i);
+ Point p = new Point(event.getX(i), event.getY(i));
+ p = translateCoords(p, new Point(width, height));
+ if (joyPointerId == pid)
+ {
+ // Analog stick
+ float dx = p.x - joyBiasX;
+ float dy = p.y - joyBiasY;
+ float sz = VGamepad.getControlWidth(CTLID_ANASTICK);
+ dx = Math.max(Math.min(1.f, dx / sz), -1.f);
+ dy = Math.max(Math.min(1.f, dy / sz), -1.f);
+ InputDeviceManager.getInstance().virtualJoystick(dx, dy);
+ continue;
+ }
+ // Buttons
+ int control = VGamepad.hitTest(p.x, p.y);
+ int oldControl = pidToControlId.containsKey(pid) ? pidToControlId.get(pid) : -1;
+ if (oldControl == control)
+ // same button still pressed, or none at all
+ continue;
+ if (oldControl != -1) {
+ // Previous button up
+ InputDeviceManager.getInstance().virtualButtonInput(oldControl, false);
+ pidToControlId.remove(pid);
+ }
+ if (control != -1 && control != CTLID_ANARING && control != CTLID_ANASTICK)
+ {
+ // New button down
+ InputDeviceManager.getInstance().virtualButtonInput(control, true);
+ pidToControlId.put(pid, control);
+ vibratorThread.click();
+ }
}
break;
- case MotionEvent.ACTION_MOVE:
- if (event.getPointerCount() == 1)
+ case MotionEvent.ACTION_POINTER_UP:
+ {
+ int pid = event.getPointerId(actionIndex);
+ if (joyPointerId == pid)
+ {
+ // Analog up
+ InputDeviceManager.getInstance().virtualJoystick(0, 0);
+ joyPointerId = -1;
+ return true;
+ }
+ if (pidToControlId.containsKey(pid))
{
- mouse_pos[0] = Math.round(event.getX());
- mouse_pos[1] = Math.round(event.getY());
+ // Button up
+ int controlId = pidToControlId.get(pid);
+ InputDeviceManager.getInstance().virtualButtonInput(controlId, false);
+ return true;
}
break;
- }
- int joyx = get_anal(11, 0);
- int joyy = get_anal(11, 1);
- InputDeviceManager.getInstance().virtualGamepadEvent(rv, joyx, joyy, left_trigger, right_trigger, fastForward);
- // Only register the mouse event if no virtual gamepad button is down
- if (!editVjoyMode && ((rv == 0xFFFFFFFF && left_trigger == 0 && right_trigger == 0 && joyx == 0 && joyy == 0 && !fastForward)
- || JNIdc.guiIsOpen()))
- InputDeviceManager.getInstance().mouseEvent(mouse_pos[0], mouse_pos[1], mouse_btns);
- return(true);
- }
-
- public void setEditVjoyMode(boolean editVjoyMode) {
- this.editVjoyMode = editVjoyMode;
- selectedVjoyElement = -1;
- 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 {
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- if (editVjoyMode && selectedVjoyElement != -1) {
- vjoy_d_custom[selectedVjoyElement][2] *= detector.getScaleFactor();
- view.requestLayout();
-
- return true;
}
-
- return false;
}
+ return touchMouseEvent(event);
+ }
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- selectedVjoyElement = -1;
- }
+ @Override
+ public void show()
+ {
+ VGamepad.show();
+ this.handler.removeCallbacks(hideVGamepadRunnable);
+ this.handler.postDelayed(hideVGamepadRunnable, 10000);
}
}
diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/InputDeviceManager.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/InputDeviceManager.java
index a8078f0634..09947d9440 100644
--- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/InputDeviceManager.java
+++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/InputDeviceManager.java
@@ -24,6 +24,8 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene
private InputManager inputManager;
private int maple_port = 0;
+ private boolean hasTouchscreen = false;
+
private static class VibrationParams {
float power;
float inclination;
@@ -39,9 +41,10 @@ public InputDeviceManager()
public void startListening(Context applicationContext)
{
maple_port = 0;
- if (applicationContext.getPackageManager().hasSystemFeature("android.hardware.touchscreen"))
- joystickAdded(VIRTUAL_GAMEPAD_ID, "Virtual Gamepad", 0, "virtual_gamepad_uid",
- new int[0], new int[0], getVibrator(VIRTUAL_GAMEPAD_ID) != null);
+ hasTouchscreen = applicationContext.getPackageManager().hasSystemFeature("android.hardware.touchscreen");
+ if (hasTouchscreen)
+ joystickAdded(VIRTUAL_GAMEPAD_ID, null, 0, null,
+ null, null, getVibrator(VIRTUAL_GAMEPAD_ID) != null);
int[] ids = InputDevice.getDeviceIds();
for (int id : ids)
onInputDeviceAdded(id);
@@ -202,12 +205,18 @@ public void stopRumble()
}
}
+ public boolean hasTouchscreen() {
+ return hasTouchscreen;
+ }
+
public static InputDeviceManager getInstance() {
return INSTANCE;
}
public native void init();
- public native void virtualGamepadEvent(int kcode, int joyx, int joyy, int lt, int rt, boolean fastForward);
+ public native void virtualReleaseAll();
+ public native void virtualJoystick(float x, float y);
+ public native void virtualButtonInput(int key, boolean pressed);
public native boolean joystickButtonEvent(int id, int button, boolean pressed);
public native boolean joystickAxisEvent(int id, int button, int value);
public native void mouseEvent(int xpos, int ypos, int buttons);
@@ -216,4 +225,5 @@ public static InputDeviceManager getInstance() {
private native void joystickRemoved(int id);
public native boolean keyboardEvent(int key, boolean pressed);
public native void keyboardText(int c);
+ public static native boolean isMicPluggedIn();
}
diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/VJoy.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/VJoy.java
deleted file mode 100644
index c94950aed8..0000000000
--- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/VJoy.java
+++ /dev/null
@@ -1,219 +0,0 @@
-package com.flycast.emulator.periph;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-
-public class VJoy {
-
- public static final int key_CONT_C = 0x0001;
- public static final int key_CONT_B = 0x0002;
- public static final int key_CONT_A = 0x0004;
- public static final int key_CONT_START = 0x0008;
- public static final int key_CONT_DPAD_UP = 0x0010;
- public static final int key_CONT_DPAD_DOWN = 0x0020;
- public static final int key_CONT_DPAD_LEFT = 0x0040;
- public static final int key_CONT_DPAD_RIGHT = 0x0080;
- public static final int key_CONT_Y = 0x0200;
- public static final int key_CONT_X = 0x0400;
- public static final int key_CONT_FFORWARD = 0x3000002;
-
- public static final int BTN_LTRIG = -1;
- public static final int BTN_RTRIG = -2;
- public static final int BTN_ANARING = -3;
- public static final int BTN_ANAPOINT = -4;
-
- public static final int ELEM_NONE = -1;
- public static final int ELEM_DPAD = 0;
- public static final int ELEM_BUTTONS = 1;
- public static final int ELEM_START = 2;
- public static final int ELEM_LTRIG = 3;
- public static final int ELEM_RTRIG = 4;
- public static final int ELEM_ANALOG = 5;
- public static final int ELEM_FFORWARD = 6;
-
- public static int VJoyCount = 14;
-
- public static float[][] baseVJoy() {
- return new float[][] {
- new float[] { 24, 24+64, 64,64, key_CONT_DPAD_LEFT, 0},
- new float[] { 24+64, 24, 64,64, key_CONT_DPAD_UP, 0},
- new float[] { 24+128, 24+64, 64,64, key_CONT_DPAD_RIGHT, 0},
- new float[] { 24+64, 24+128, 64,64, key_CONT_DPAD_DOWN, 0},
-
- new float[] { 440, 280+64, 64,64, key_CONT_X, 0},
- new float[] { 440+64, 280, 64,64, key_CONT_Y, 0},
- new float[] { 440+128, 280+64, 64,64, key_CONT_B, 0},
- new float[] { 440+64, 280+128,64,64, key_CONT_A, 0},
-
- new float[] { 320-32, 360+32, 64,64, key_CONT_START, 0},
-
- new float[] { 440, 200, 90,64, BTN_LTRIG, 0}, // LT
- new float[] { 542, 200, 90,64, BTN_RTRIG, 0}, // RT
-
- new float[] { 0, 128+224,128,128,BTN_ANARING, 0}, // Analog ring
- new float[] { 32, 128+256,64,64, BTN_ANAPOINT, 0}, // Analog point
-
- new float[] { 320-32, 12, 64,64, key_CONT_FFORWARD, 0}, // Fast-forward
-
- new float[] { 20, 288, 64,64, key_CONT_DPAD_LEFT|key_CONT_DPAD_UP, 0}, // DPad diagonals
- new float[] { 20+128, 288, 64,64, key_CONT_DPAD_RIGHT|key_CONT_DPAD_UP, 0},
- new float[] { 20, 288+128,64,64, key_CONT_DPAD_LEFT|key_CONT_DPAD_DOWN, 0},
- new float[] { 20+128, 288+128,64,64, key_CONT_DPAD_RIGHT|key_CONT_DPAD_DOWN, 0},
- };
- }
-
- public static float[][] readCustomVjoyValues(Context context) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-
- return new float[][] {
- // x-shift, y-shift, sizing-factor
- new float[] { prefs.getFloat("touch_x_shift_dpad", 0),
- prefs.getFloat("touch_y_shift_dpad", 0),
- prefs.getFloat("touch_scale_dpad", 1)
- }, // DPAD
- new float[] { prefs.getFloat("touch_x_shift_buttons", 0),
- prefs.getFloat("touch_y_shift_buttons", 0),
- prefs.getFloat("touch_scale_buttons", 1)
- }, // X, Y, B, A Buttons
- new float[] { prefs.getFloat("touch_x_shift_start", 0),
- prefs.getFloat("touch_y_shift_start", 0),
- prefs.getFloat("touch_scale_start", 1)
- }, // Start
- new float[] { prefs.getFloat("touch_x_shift_left_trigger", 0),
- prefs.getFloat("touch_y_shift_left_trigger", 0),
- prefs.getFloat("touch_scale_left_trigger", 1)
- }, // Left Trigger
- new float[] { prefs.getFloat("touch_x_shift_right_trigger", 0),
- prefs.getFloat("touch_y_shift_right_trigger", 0),
- prefs.getFloat("touch_scale_right_trigger", 1)
- }, // Right Trigger
- new float[] { prefs.getFloat("touch_x_shift_analog", 0),
- prefs.getFloat("touch_y_shift_analog", 0),
- prefs.getFloat("touch_scale_analog", 1)
- }, // Analog Stick
- new float[] { prefs.getFloat("touch_x_shift_fforward", 0),
- prefs.getFloat("touch_y_shift_fforward", 0),
- prefs.getFloat("touch_scale_fforward", 1)
- } // Fast-forward
- };
- }
-
- public static float[][] getVjoy_d(float[][] vjoy_d_custom) {
- return new float[][] {
- // LEFT, UP, RIGHT, DOWN
- new float[] { 20+0*vjoy_d_custom[0][2]+vjoy_d_custom[0][0], 288+64*vjoy_d_custom[0][2]+vjoy_d_custom[0][1],
- 64*vjoy_d_custom[0][2],64*vjoy_d_custom[0][2], key_CONT_DPAD_LEFT},
- new float[] { 20+64*vjoy_d_custom[0][2]+vjoy_d_custom[0][0], 288+0*vjoy_d_custom[0][2]+vjoy_d_custom[0][1],
- 64*vjoy_d_custom[0][2],64*vjoy_d_custom[0][2], key_CONT_DPAD_UP},
- new float[] { 20+128*vjoy_d_custom[0][2]+vjoy_d_custom[0][0], 288+64*vjoy_d_custom[0][2]+vjoy_d_custom[0][1],
- 64*vjoy_d_custom[0][2],64*vjoy_d_custom[0][2], key_CONT_DPAD_RIGHT},
- new float[] { 20+64*vjoy_d_custom[0][2]+vjoy_d_custom[0][0], 288+128*vjoy_d_custom[0][2]+vjoy_d_custom[0][1],
- 64*vjoy_d_custom[0][2],64*vjoy_d_custom[0][2], key_CONT_DPAD_DOWN},
-
- // X, Y, B, A
- new float[] { 448+0*vjoy_d_custom[1][2]+vjoy_d_custom[1][0], 288+64*vjoy_d_custom[1][2]+vjoy_d_custom[1][1],
- 64*vjoy_d_custom[1][2],64*vjoy_d_custom[1][2], key_CONT_X},
- new float[] { 448+64*vjoy_d_custom[1][2]+vjoy_d_custom[1][0], 288+0*vjoy_d_custom[1][2]+vjoy_d_custom[1][1],
- 64*vjoy_d_custom[1][2],64*vjoy_d_custom[1][2], key_CONT_Y},
- new float[] { 448+128*vjoy_d_custom[1][2]+vjoy_d_custom[1][0], 288+64*vjoy_d_custom[1][2]+vjoy_d_custom[1][1],
- 64*vjoy_d_custom[1][2],64*vjoy_d_custom[1][2], key_CONT_B},
- new float[] { 448+64*vjoy_d_custom[1][2]+vjoy_d_custom[1][0], 288+128*vjoy_d_custom[1][2]+vjoy_d_custom[1][1],
- 64*vjoy_d_custom[1][2],64*vjoy_d_custom[1][2], key_CONT_A},
-
- // START
- new float[] { 320-32+vjoy_d_custom[2][0], 288+128+vjoy_d_custom[2][1],
- 64*vjoy_d_custom[2][2],64*vjoy_d_custom[2][2], key_CONT_START},
-
- // LT, RT
- new float[] { 440+vjoy_d_custom[3][0], 200+vjoy_d_custom[3][1],
- 90*vjoy_d_custom[3][2],64*vjoy_d_custom[3][2], -1},
- new float[] { 542+vjoy_d_custom[4][0], 200+vjoy_d_custom[4][1],
- 90*vjoy_d_custom[4][2],64*vjoy_d_custom[4][2], -2},
-
- // Analog ring and point
- new float[] { 16+vjoy_d_custom[5][0], 24+32+vjoy_d_custom[5][1],
- 128*vjoy_d_custom[5][2],128*vjoy_d_custom[5][2],-3},
- new float[] { 48+vjoy_d_custom[5][0], 24+64+vjoy_d_custom[5][1],
- 64*vjoy_d_custom[5][2],64*vjoy_d_custom[5][2], -4},
-
- // Fast-forward
- new float[] { 320-32+vjoy_d_custom[6][0], 12+vjoy_d_custom[6][1],
- 64*vjoy_d_custom[6][2],64*vjoy_d_custom[6][2], -5},
-
- // DPad diagonals
- new float[] { 20+0*vjoy_d_custom[0][2]+vjoy_d_custom[0][0], 288+0*vjoy_d_custom[0][2]+vjoy_d_custom[0][1],
- 64*vjoy_d_custom[0][2],64*vjoy_d_custom[0][2], key_CONT_DPAD_LEFT|key_CONT_DPAD_UP},
- new float[] { 20+128*vjoy_d_custom[0][2]+vjoy_d_custom[0][0], 288+0*vjoy_d_custom[0][2]+vjoy_d_custom[0][1],
- 64*vjoy_d_custom[0][2],64*vjoy_d_custom[0][2], key_CONT_DPAD_RIGHT|key_CONT_DPAD_UP},
- new float[] { 20+0*vjoy_d_custom[0][2]+vjoy_d_custom[0][0], 288+128*vjoy_d_custom[0][2]+vjoy_d_custom[0][1],
- 64*vjoy_d_custom[0][2],64*vjoy_d_custom[0][2], key_CONT_DPAD_LEFT|key_CONT_DPAD_DOWN},
- new float[] { 20+128*vjoy_d_custom[0][2]+vjoy_d_custom[0][0], 288+128*vjoy_d_custom[0][2]+vjoy_d_custom[0][1],
- 64*vjoy_d_custom[0][2],64*vjoy_d_custom[0][2], key_CONT_DPAD_RIGHT|key_CONT_DPAD_DOWN},
- };
- }
-
- public static void writeCustomVjoyValues(float[][] vjoy_d_custom, Context context) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-
- prefs.edit().putFloat("touch_x_shift_dpad", vjoy_d_custom[0][0]).apply();
- prefs.edit().putFloat("touch_y_shift_dpad", vjoy_d_custom[0][1]).apply();
- prefs.edit().putFloat("touch_scale_dpad", vjoy_d_custom[0][2]).apply();
-
- prefs.edit().putFloat("touch_x_shift_buttons", vjoy_d_custom[1][0]).apply();
- prefs.edit().putFloat("touch_y_shift_buttons", vjoy_d_custom[1][1]).apply();
- prefs.edit().putFloat("touch_scale_buttons", vjoy_d_custom[1][2]).apply();
-
- prefs.edit().putFloat("touch_x_shift_start", vjoy_d_custom[2][0]).apply();
- prefs.edit().putFloat("touch_y_shift_start", vjoy_d_custom[2][1]).apply();
- prefs.edit().putFloat("touch_scale_start", vjoy_d_custom[2][2]).apply();
-
- prefs.edit().putFloat("touch_x_shift_left_trigger", vjoy_d_custom[3][0]).apply();
- prefs.edit().putFloat("touch_y_shift_left_trigger", vjoy_d_custom[3][1]).apply();
- prefs.edit().putFloat("touch_scale_left_trigger", vjoy_d_custom[3][2]).apply();
-
- prefs.edit().putFloat("touch_x_shift_right_trigger", vjoy_d_custom[4][0]).apply();
- prefs.edit().putFloat("touch_y_shift_right_trigger", vjoy_d_custom[4][1]).apply();
- prefs.edit().putFloat("touch_scale_right_trigger", vjoy_d_custom[4][2]).apply();
-
- prefs.edit().putFloat("touch_x_shift_analog", vjoy_d_custom[5][0]).apply();
- prefs.edit().putFloat("touch_y_shift_analog", vjoy_d_custom[5][1]).apply();
- prefs.edit().putFloat("touch_scale_analog", vjoy_d_custom[5][2]).apply();
-
- prefs.edit().putFloat("touch_x_shift_fforward", vjoy_d_custom[6][0]).apply();
- prefs.edit().putFloat("touch_y_shift_fforward", vjoy_d_custom[6][1]).apply();
- prefs.edit().putFloat("touch_scale_fforward", vjoy_d_custom[6][2]).apply();
- }
-
- public static void resetCustomVjoyValues(Context context) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-
- prefs.edit().remove("touch_x_shift_dpad").apply();
- prefs.edit().remove("touch_y_shift_dpad").apply();
- prefs.edit().remove("touch_scale_dpad").apply();
-
- prefs.edit().remove("touch_x_shift_buttons").apply();
- prefs.edit().remove("touch_y_shift_buttons").apply();
- prefs.edit().remove("touch_scale_buttons").apply();
-
- prefs.edit().remove("touch_x_shift_start").apply();
- prefs.edit().remove("touch_y_shift_start").apply();
- prefs.edit().remove("touch_scale_start").apply();
-
- prefs.edit().remove("touch_x_shift_left_trigger").apply();
- prefs.edit().remove("touch_y_shift_left_trigger").apply();
- prefs.edit().remove("touch_scale_left_trigger").apply();
-
- prefs.edit().remove("touch_x_shift_right_trigger").apply();
- prefs.edit().remove("touch_y_shift_right_trigger").apply();
- prefs.edit().remove("touch_scale_right_trigger").apply();
-
- prefs.edit().remove("touch_x_shift_analog").apply();
- prefs.edit().remove("touch_y_shift_analog").apply();
- prefs.edit().remove("touch_scale_analog").apply();
-
- prefs.edit().remove("touch_x_shift_fforward").apply();
- prefs.edit().remove("touch_y_shift_fforward").apply();
- prefs.edit().remove("touch_scale_fforward").apply();
- }
-}
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 3fa0fabc87..50bbd45e36 100644
--- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp
+++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp
@@ -1,12 +1,8 @@
#include "types.h"
-#include "hw/maple/maple_cfg.h"
-#include "hw/maple/maple_devs.h"
-#include "hw/maple/maple_if.h"
#include "hw/naomi/naomi_cart.h"
#include "audio/audiostream.h"
#include "imgread/common.h"
#include "ui/gui.h"
-#include "ui/vgamepad.h"
#include "rend/osd.h"
#include "cfg/cfg.h"
#include "log/LogManager.h"
@@ -39,24 +35,14 @@ namespace jni
thread_local JVMAttacher jvm_attacher;
}
-#include "android_gamepad.h"
-#include "android_keyboard.h"
#include "http_client.h"
-extern "C" JNIEXPORT jint JNICALL Java_com_flycast_emulator_emu_JNIdc_getVirtualGamepadVibration(JNIEnv *env, jobject obj)
-{
- return (jint)config::VirtualGamepadVibration;
-}
-
extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_JNIdc_screenCharacteristics(JNIEnv *env, jobject obj, jfloat screenDpi, jfloat refreshRate)
{
settings.display.dpi = screenDpi;
settings.display.refreshRate = refreshRate;
}
-std::shared_ptr mouse;
-std::shared_ptr keyboard;
-
static bool game_started;
//stuff for saving prefs
@@ -65,12 +51,10 @@ jmethodID saveAndroidSettingsMid;
static ANativeWindow *g_window = 0;
// Activity
-static jobject g_activity;
-static jmethodID VJoyStartEditingMID;
-static jmethodID VJoyStopEditingMID;
-static jmethodID VJoyResetEditingMID;
-static jmethodID VJoyEnableControlsMID;
-static jmethodID showScreenKeyboardMid;
+jobject g_activity;
+extern jmethodID VJoyStartEditingMID;
+extern jmethodID VJoyStopEditingMID;
+extern jmethodID showScreenKeyboardMid;
static jmethodID onGameStateChangeMid;
static void emuEventCallback(Event event, void *)
@@ -353,42 +337,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_JNIdc_rendinitNa
}
}
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_JNIdc_vjoy(JNIEnv * env, jobject obj,int id,float x, float y, float w, float h)
-{
- vgamepad::setPosition(static_cast(id), x, y, w, h);
-}
-
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_JNIdc_showVirtualGamepad(JNIEnv * env, jobject obj)
-{
- vgamepad::show();
-}
-
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_JNIdc_hideVirtualGamepad(JNIEnv * env, jobject obj)
-{
- vgamepad::hide();
-}
-
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_JNIdc_getControllers(JNIEnv *env, jobject obj, jintArray controllers, jobjectArray peripherals)
-{
- // might be called before JNIdc.initEnvironment()
- if (g_jvm == NULL)
- env->GetJavaVM(&g_jvm);
-
- jni::IntArray jcontrollers(controllers, false);
- std::vector devs;
- for (u32 i = 0; i < config::MapleMainDevices.size(); i++)
- devs.push_back((MapleDeviceType)config::MapleMainDevices[i]);
- jcontrollers.setData(devs.data());
-
- jni::ObjectArray jperipherals(peripherals, false);
- int obj_len = jperipherals.size();
- for (int i = 0; i < obj_len; ++i)
- {
- std::vector devs { (MapleDeviceType)config::MapleExpansionDevices[i][0], (MapleDeviceType)config::MapleExpansionDevices[i][1] };
- jperipherals[i].setData(devs.data());
- }
-}
-
extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_JNIdc_guiOpenSettings(JNIEnv *env, jobject obj)
{
gui_open_settings();
@@ -495,92 +443,6 @@ void SaveAndroidSettings()
jni::env()->CallVoidMethod(g_emulator, saveAndroidSettingsMid, (jstring)homeDirectory);
}
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_init(JNIEnv *env, jobject obj)
-{
- input_device_manager = env->NewGlobalRef(obj);
- input_device_manager_rumble = env->GetMethodID(env->GetObjectClass(obj), "rumble", "(IFFI)Z");
- // FIXME Don't connect it by default or any screen touch will register as button A press
- mouse = std::make_shared(-1);
- GamepadDevice::Register(mouse);
- keyboard = std::make_shared();
- GamepadDevice::Register(keyboard);
- gui_setOnScreenKeyboardCallback([](bool show) {
- if (g_activity != nullptr)
- jni::env()->CallVoidMethod(g_activity, showScreenKeyboardMid, show);
- });
-}
-
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_joystickAdded(JNIEnv *env, jobject obj, jint id, jstring name,
- jint maple_port, jstring junique_id, jintArray fullAxes, jintArray halfAxes, jboolean hasRumble)
-{
- std::string joyname = jni::String(name, false);
- std::string unique_id = jni::String(junique_id, false);
- std::vector full = jni::IntArray(fullAxes, false);
- std::vector half = jni::IntArray(halfAxes, false);
-
- std::shared_ptr gamepad = std::make_shared(maple_port, id, joyname.c_str(), unique_id.c_str(), full, half);
- AndroidGamepadDevice::AddAndroidGamepad(gamepad);
- gamepad->setRumbleEnabled(hasRumble);
-}
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_joystickRemoved(JNIEnv *env, jobject obj, jint id)
-{
- std::shared_ptr device = AndroidGamepadDevice::GetAndroidGamepad(id);
- if (device != NULL)
- AndroidGamepadDevice::RemoveAndroidGamepad(device);
-}
-
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_virtualGamepadEvent(JNIEnv *env, jobject obj, jint kcode, jint joyx, jint joyy, jint lt, jint rt, jboolean fastForward)
-{
- std::shared_ptr device = AndroidGamepadDevice::GetAndroidGamepad(AndroidGamepadDevice::VIRTUAL_GAMEPAD_ID);
- if (device != NULL)
- device->virtual_gamepad_event(kcode, joyx, joyy, lt, rt, fastForward);
-}
-
-extern "C" JNIEXPORT jboolean JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_joystickButtonEvent(JNIEnv *env, jobject obj, jint id, jint key, jboolean pressed)
-{
- std::shared_ptr device = AndroidGamepadDevice::GetAndroidGamepad(id);
- if (device != NULL)
- return device->gamepad_btn_input(key, pressed);
- else
- return false;
-
-}
-
-extern "C" JNIEXPORT jboolean JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_keyboardEvent(JNIEnv *env, jobject obj, jint key, jboolean pressed)
-{
- keyboard->input(key, pressed);
- return true;
-}
-
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_keyboardText(JNIEnv *env, jobject obj, jint c)
-{
- gui_keyboard_input((u16)c);
-}
-
-static std::map, jint> previous_axis_values;
-
-extern "C" JNIEXPORT jboolean JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_joystickAxisEvent(JNIEnv *env, jobject obj, jint id, jint key, jint value)
-{
- std::shared_ptr device = AndroidGamepadDevice::GetAndroidGamepad(id);
- if (device != nullptr)
- return device->gamepad_axis_input(key, value);
- else
- return false;
-}
-
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_mouseEvent(JNIEnv *env, jobject obj, jint xpos, jint ypos, jint buttons)
-{
- mouse->setAbsPos(xpos, ypos, settings.display.width, settings.display.height);
- mouse->setButton(Mouse::LEFT_BUTTON, (buttons & 1) != 0);
- mouse->setButton(Mouse::RIGHT_BUTTON, (buttons & 2) != 0);
- mouse->setButton(Mouse::MIDDLE_BUTTON, (buttons & 4) != 0);
-}
-
-extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_mouseScrollEvent(JNIEnv *env, jobject obj, jint scrollValue)
-{
- mouse->setWheel(scrollValue);
-}
-
extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_BaseGLActivity_register(JNIEnv *env, jobject obj, jobject activity)
{
if (g_activity != nullptr) {
@@ -592,40 +454,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_BaseGLActivity_regis
g_activity = env->NewGlobalRef(activity);
jclass actClass = env->GetObjectClass(activity);
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");
+ VJoyStopEditingMID = env->GetMethodID(actClass, "VJoyStopEditing", "()V");
showScreenKeyboardMid = env->GetMethodID(actClass, "showScreenKeyboard", "(Z)V");
onGameStateChangeMid = env->GetMethodID(actClass, "onGameStateChange", "(Z)V");
}
}
-namespace vgamepad
-{
-
-void startEditing() {
- enableAllControls();
- jni::env()->CallVoidMethod(g_activity, VJoyStartEditingMID);
-}
-void pauseEditing() {
- stopEditing(false);
-}
-void resetEditing() {
- jni::env()->CallVoidMethod(g_activity, VJoyResetEditingMID);
-}
-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)
{
JNIEnv *env = jni::env();
diff --git a/shell/android-studio/flycast/src/main/jni/src/android_gamepad.h b/shell/android-studio/flycast/src/main/jni/src/android_gamepad.h
index 2208b741c1..79d1cf9850 100644
--- a/shell/android-studio/flycast/src/main/jni/src/android_gamepad.h
+++ b/shell/android-studio/flycast/src/main/jni/src/android_gamepad.h
@@ -20,6 +20,7 @@
#include "input/gamepad_device.h"
#include "input/mouse.h"
+#include "input/virtual_gamepad.h"
#include "jni_util.h"
#include
@@ -97,26 +98,18 @@ class AndroidGamepadDevice : public GamepadDevice
public:
AndroidGamepadDevice(int maple_port, int id, const char *name, const char *unique_id,
const std::vector& fullAxes, const std::vector& halfAxes)
- : GamepadDevice(maple_port, "Android", id != VIRTUAL_GAMEPAD_ID), android_id(id),
+ : GamepadDevice(maple_port, "Android"), android_id(id),
fullAxes(fullAxes), halfAxes(halfAxes)
{
_name = name;
_unique_id = unique_id;
INFO_LOG(INPUT, "Android: Opened joystick %d on port %d: '%s' descriptor '%s'", id, maple_port, _name.c_str(), _unique_id.c_str());
- if (id == VIRTUAL_GAMEPAD_ID)
- {
- input_mapper = std::make_shared();
- // hasAnalogStick = true; // TODO has an analog stick but input mapping isn't persisted
- }
- else
- {
- loadMapping();
- save_mapping();
- hasAnalogStick = !fullAxes.empty();
- }
+
+ loadMapping();
+ save_mapping();
+ hasAnalogStick = !fullAxes.empty();
}
- ~AndroidGamepadDevice() override
- {
+ ~AndroidGamepadDevice() override {
INFO_LOG(INPUT, "Android: Joystick '%s' on port %d disconnected", _name.c_str(), maple_port());
}
@@ -247,66 +240,6 @@ class AndroidGamepadDevice : public GamepadDevice
GamepadDevice::Unregister(gamepad);
};
- void virtual_gamepad_event(int kcode, int joyx, int joyy, int lt, int rt, bool fastForward)
- {
- // No virtual gamepad when the GUI is open: touch events only
- if (gui_is_open() && gui_state != GuiState::VJoyEdit)
- {
- kcode = 0xffffffff;
- joyx = joyy = rt = lt = 0;
- }
- if (settings.platform.isArcade())
- {
- if (rt > 0)
- {
- if ((kcode & DC_BTN_A) == 0)
- // RT + A -> D (coin)
- kcode &= ~DC_BTN_D;
- if ((kcode & DC_BTN_B) == 0)
- // RT + B -> Service
- kcode &= ~DC_DPAD2_UP;
- if ((kcode & DC_BTN_X) == 0)
- // RT + X -> Test
- kcode &= ~DC_DPAD2_DOWN;
- }
- // arcade mapping: X -> btn2, Y -> btn3
- if ((kcode & DC_BTN_X) == 0)
- {
- kcode &= ~DC_BTN_C;
- kcode |= DC_BTN_X;
- }
- if ((kcode & DC_BTN_Y) == 0)
- {
- kcode &= ~DC_BTN_X;
- kcode |= DC_BTN_Y;
- }
- if (rt > 0)
- // naomi btn4
- kcode &= ~DC_BTN_Y;
- if (lt > 0)
- // naomi btn5
- kcode &= ~DC_BTN_Z;
- }
- u32 changes = kcode ^ previous_kcode;
- for (int i = 0; i < 32; i++)
- if (changes & (1 << i))
- gamepad_btn_input(1 << i, (kcode & (1 << i)) == 0);
- if (joyx >= 0)
- gamepad_axis_input(DC_AXIS_RIGHT, joyx | (joyx << 8));
- else
- gamepad_axis_input(DC_AXIS_LEFT, -joyx | (-joyx << 8));
- if (joyy >= 0)
- gamepad_axis_input(DC_AXIS_DOWN, joyy | (joyy << 8));
- else
- gamepad_axis_input(DC_AXIS_UP, -joyy | (-joyy << 8));
- gamepad_axis_input(DC_AXIS_LT, lt == 0 ? 0 : 0x7fff);
- gamepad_axis_input(DC_AXIS_RT, rt == 0 ? 0 : 0x7fff);
- previous_kcode = kcode;
- if (fastForward != previousFastForward)
- gamepad_btn_input(EMU_BTN_FFORWARD, fastForward);
- previousFastForward = fastForward;
- }
-
void rumble(float power, float inclination, u32 duration_ms) override
{
power *= rumblePower / 100.f;
@@ -317,8 +250,6 @@ class AndroidGamepadDevice : public GamepadDevice
this->rumbleEnabled = rumbleEnabled;
}
- bool is_virtual_gamepad() override { return android_id == VIRTUAL_GAMEPAD_ID; }
-
bool hasHalfAxis(int axis) const { return std::find(halfAxes.begin(), halfAxes.end(), axis) != halfAxes.end(); }
bool hasFullAxis(int axis) const { return std::find(fullAxes.begin(), fullAxes.end(), axis) != fullAxes.end(); }
@@ -336,13 +267,9 @@ class AndroidGamepadDevice : public GamepadDevice
input_mapper = std::make_shared>(*this);
}
- static const int VIRTUAL_GAMEPAD_ID = 0x12345678; // must match the Java definition
-
private:
int android_id;
static std::map> android_gamepads;
- u32 previous_kcode = 0xffffffff;
- bool previousFastForward = false;
std::vector fullAxes;
std::vector halfAxes;
};
@@ -481,3 +408,19 @@ class AndroidMouse : public SystemMouse
}
};
+class AndroidVirtualGamepad : public VirtualGamepad
+{
+public:
+ AndroidVirtualGamepad(bool rumbleEnabled) : VirtualGamepad("Android") {
+ this->rumbleEnabled = rumbleEnabled;
+ }
+
+ void rumble(float power, float inclination, u32 duration_ms) override
+ {
+ power *= rumblePower / 100.f;
+ jboolean has_vibrator = jni::env()->CallBooleanMethod(input_device_manager, input_device_manager_rumble, GAMEPAD_ID, power, inclination, duration_ms);
+ rumbleEnabled = has_vibrator;
+ }
+
+ static constexpr int GAMEPAD_ID = 0x12345678; // must match the Java definition
+};
diff --git a/shell/android-studio/flycast/src/main/jni/src/android_input.cpp b/shell/android-studio/flycast/src/main/jni/src/android_input.cpp
new file mode 100644
index 0000000000..2fde7d1a73
--- /dev/null
+++ b/shell/android-studio/flycast/src/main/jni/src/android_input.cpp
@@ -0,0 +1,225 @@
+/*
+ Copyright 2024 flyinghead
+
+ This file is part of Flycast.
+
+ Flycast is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ Flycast is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Flycast. If not, see .
+*/
+#include "android_gamepad.h"
+#include "android_keyboard.h"
+#include "ui/vgamepad.h"
+#include "cfg/option.h"
+#include "hw/maple/maple_if.h"
+
+std::shared_ptr mouse;
+std::shared_ptr keyboard;
+std::shared_ptr virtualGamepad;
+
+extern jobject g_activity;
+jmethodID VJoyStartEditingMID;
+jmethodID VJoyStopEditingMID;
+jmethodID VJoyEnableControlsMID;
+jmethodID showScreenKeyboardMid;
+
+//
+// VGamepad
+//
+extern "C" JNIEXPORT jint JNICALL Java_com_flycast_emulator_emu_VGamepad_getVibrationPower(JNIEnv *env, jobject obj) {
+ return (jint)config::VirtualGamepadVibration;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_VGamepad_show(JNIEnv * env, jobject obj) {
+ vgamepad::show();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_VGamepad_hide(JNIEnv * env, jobject obj) {
+ vgamepad::hide();
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_com_flycast_emulator_emu_VGamepad_hitTest(JNIEnv * env, jobject obj,
+ jfloat x, jfloat y) {
+ return vgamepad::hitTest(x, y);
+}
+
+extern "C" JNIEXPORT jfloat JNICALL Java_com_flycast_emulator_emu_VGamepad_getControlWidth(JNIEnv * env, jobject obj,
+ jint controlId) {
+ return vgamepad::getControlWidth(static_cast(controlId));
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_com_flycast_emulator_emu_VGamepad_layoutHitTest(JNIEnv * env, jobject obj,
+ jfloat x, jfloat y) {
+ return vgamepad::layoutHitTest(x, y);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_VGamepad_scaleElement(JNIEnv * env, jobject obj,
+ jint elemId, jfloat scale) {
+ vgamepad::scaleElement(static_cast(elemId), scale);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_emu_VGamepad_translateElement(JNIEnv * env, jobject obj,
+ jint elemId, jfloat x, jfloat y) {
+ vgamepad::translateElement(static_cast(elemId), x, y);
+}
+
+namespace vgamepad
+{
+
+void startEditing()
+{
+ // FIXME code dup with vgamepad.cpp
+ enableAllControls();
+ show();
+ jni::env()->CallVoidMethod(g_activity, VJoyStartEditingMID);
+}
+void pauseEditing() {
+ // needed? could be used by iOS to avoid relying on gui state
+ jni::env()->CallVoidMethod(g_activity, VJoyStopEditingMID);
+}
+void stopEditing(bool canceled)
+{
+ // FIXME code dup with vgamepad.cpp
+ jni::env()->CallVoidMethod(g_activity, VJoyStopEditingMID);
+ if (canceled)
+ loadLayout();
+ else
+ saveLayout();
+}
+
+}
+
+//
+// InputDeviceManager
+//
+extern "C" JNIEXPORT jboolean JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_isMicPluggedIn(JNIEnv *env, jobject obj)
+{
+ for (const auto& devices : config::MapleExpansionDevices)
+ if (static_cast(devices[0]) == MDT_Microphone
+ || static_cast(devices[1]) == MDT_Microphone)
+ return true;
+
+ return false;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_init(JNIEnv *env, jobject obj)
+{
+ input_device_manager = env->NewGlobalRef(obj);
+ input_device_manager_rumble = env->GetMethodID(env->GetObjectClass(obj), "rumble", "(IFFI)Z");
+ // FIXME Don't connect it by default or any screen touch will register as button A press
+ mouse = std::make_shared(-1);
+ GamepadDevice::Register(mouse);
+ keyboard = std::make_shared();
+ GamepadDevice::Register(keyboard);
+ gui_setOnScreenKeyboardCallback([](bool show) {
+ if (g_activity != nullptr)
+ jni::env()->CallVoidMethod(g_activity, showScreenKeyboardMid, show);
+ });
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_joystickAdded(JNIEnv *env, jobject obj,
+ jint id, jstring name, jint maple_port, jstring junique_id, jintArray fullAxes, jintArray halfAxes, jboolean hasRumble)
+{
+ if (id == AndroidVirtualGamepad::GAMEPAD_ID) {
+ virtualGamepad = std::make_shared(hasRumble);
+ GamepadDevice::Register(virtualGamepad);
+ }
+ else
+ {
+ std::string joyname = jni::String(name, false);
+ std::string unique_id = jni::String(junique_id, false);
+ std::vector full = jni::IntArray(fullAxes, false);
+ std::vector half = jni::IntArray(halfAxes, false);
+
+ std::shared_ptr gamepad = std::make_shared(maple_port, id, joyname.c_str(), unique_id.c_str(), full, half);
+ AndroidGamepadDevice::AddAndroidGamepad(gamepad);
+ gamepad->setRumbleEnabled(hasRumble);
+ }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_joystickRemoved(JNIEnv *env, jobject obj,
+ jint id)
+{
+ if (id == AndroidVirtualGamepad::GAMEPAD_ID) {
+ GamepadDevice::Unregister(virtualGamepad);
+ virtualGamepad.reset();
+ }
+ else {
+ std::shared_ptr device = AndroidGamepadDevice::GetAndroidGamepad(id);
+ if (device)
+ AndroidGamepadDevice::RemoveAndroidGamepad(device);
+ }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_virtualReleaseAll(JNIEnv *env, jobject obj) {
+ if (virtualGamepad)
+ virtualGamepad->releaseAll();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_virtualJoystick(JNIEnv *env, jobject obj,
+ jfloat x, jfloat y) {
+ if (virtualGamepad)
+ virtualGamepad->joystickInput(x, y);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_virtualButtonInput(JNIEnv *env, jobject obj,
+ jint controlId, jboolean pressed) {
+ if (virtualGamepad)
+ virtualGamepad->buttonInput(static_cast(controlId), pressed);
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_joystickButtonEvent(JNIEnv *env, jobject obj,
+ jint id, jint key, jboolean pressed)
+{
+ std::shared_ptr device = AndroidGamepadDevice::GetAndroidGamepad(id);
+ if (device != NULL)
+ return device->gamepad_btn_input(key, pressed);
+ else
+ return false;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_keyboardEvent(JNIEnv *env, jobject obj,
+ jint key, jboolean pressed) {
+ keyboard->input(key, pressed);
+ return true;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_keyboardText(JNIEnv *env, jobject obj,
+ jint c) {
+ gui_keyboard_input((u16)c);
+}
+
+static std::map, jint> previous_axis_values;
+
+extern "C" JNIEXPORT jboolean JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_joystickAxisEvent(JNIEnv *env, jobject obj,
+ jint id, jint key, jint value)
+{
+ std::shared_ptr device = AndroidGamepadDevice::GetAndroidGamepad(id);
+ if (device != nullptr)
+ return device->gamepad_axis_input(key, value);
+ else
+ return false;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_mouseEvent(JNIEnv *env, jobject obj,
+ jint xpos, jint ypos, jint buttons)
+{
+ mouse->setAbsPos(xpos, ypos, settings.display.width, settings.display.height);
+ mouse->setButton(Mouse::LEFT_BUTTON, (buttons & 1) != 0);
+ mouse->setButton(Mouse::RIGHT_BUTTON, (buttons & 2) != 0);
+ mouse->setButton(Mouse::MIDDLE_BUTTON, (buttons & 4) != 0);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_periph_InputDeviceManager_mouseScrollEvent(JNIEnv *env, jobject obj,
+ jint scrollValue) {
+ mouse->setWheel(scrollValue);
+}
diff --git a/shell/android-studio/flycast/src/main/jni/src/jni_util.h b/shell/android-studio/flycast/src/main/jni/src/jni_util.h
index 2fc0b992a6..73e57034d7 100644
--- a/shell/android-studio/flycast/src/main/jni/src/jni_util.h
+++ b/shell/android-studio/flycast/src/main/jni/src/jni_util.h
@@ -107,7 +107,7 @@ class Object
bool isNull() const { return object == nullptr; }
operator jobject() const { return object; }
- Class getClass() const;
+ inline Class getClass() const;
template
T globalRef() {
diff --git a/shell/apple/emulator-ios/emulator/PadViewController.mm b/shell/apple/emulator-ios/emulator/PadViewController.mm
index 19b678ee59..97dedb34fd 100644
--- a/shell/apple/emulator-ios/emulator/PadViewController.mm
+++ b/shell/apple/emulator-ios/emulator/PadViewController.mm
@@ -65,7 +65,7 @@ - (void)showController:(UIView *)parentView
- (void)hideController
{
- [self resetTouch];
+ [self resetAnalog];
[hideTimer invalidate];
[self.view removeFromSuperview];
}
@@ -89,12 +89,10 @@ -(void)hideTimer {
vgamepad::hide();
}
-- (void)resetTouch
+- (void)resetAnalog
{
joyTouch = nil;
- virtualGamepad->gamepad_axis_input(DC_AXIS_LEFT, 0);
- virtualGamepad->gamepad_axis_input(DC_AXIS_UP, 0);
- vgamepad::setAnalogStick(0, 0);
+ virtualGamepad->joystickInput(0, 0);
}
static CGPoint translateCoords(const CGPoint& pos, const CGSize& size)
@@ -114,20 +112,20 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
CGPoint point = [touch locationInView:self.view];
point = translateCoords(point, self.view.bounds.size);
vgamepad::ControlId control = vgamepad::hitTest(point.x, point.y);
- if (joyTouch == nil && control == vgamepad::AnalogArea)
+ if (joyTouch == nil && (control == vgamepad::AnalogArea || control == vgamepad::AnalogStick))
{
- [self resetTouch];
+ [self resetAnalog];
joyTouch = touch;
joyBias = point;
continue;
}
NSValue *key = [NSValue valueWithPointer:(const void *)touch];
if (control != vgamepad::None && control != vgamepad::AnalogArea
- && touchToButton[key] == nil)
+ && control != vgamepad::AnalogStick && touchToButton[key] == nil)
{
touchToButton[key] = [NSNumber numberWithInt:control];
// button down
- virtualGamepad->gamepad_btn_input(vgamepad::controlToDcKey(control), true);
+ virtualGamepad->buttonInput(control, true);
}
}
[super touchesBegan:touches withEvent:event];
@@ -138,7 +136,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
for (UITouch *touch in touches)
{
if (touch == joyTouch) {
- [self resetTouch];
+ [self resetAnalog];
continue;
}
NSValue *key = [NSValue valueWithPointer:(const void *)touch];
@@ -146,7 +144,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
if (control != nil) {
[touchToButton removeObjectForKey:key];
// button up
- virtualGamepad->gamepad_btn_input(vgamepad::controlToDcKey((vgamepad::ControlId)control.intValue), false);
+ virtualGamepad->buttonInput(static_cast(control.intValue), false);
}
}
[super touchesEnded:touches withEvent:event];
@@ -166,17 +164,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
double sz = vgamepad::getControlWidth(vgamepad::AnalogStick);
point.x = std::max(std::min(1.0, point.x / sz), -1.0);
point.y = std::max(std::min(1.0, point.y / sz), -1.0);
- vgamepad::setAnalogStick(point.x, point.y);
- point.x *= 32767.0;
- point.y *= 32767.0;
- if (point.x >= 0)
- virtualGamepad->gamepad_axis_input(DC_AXIS_RIGHT, (int)std::round(point.x));
- else
- virtualGamepad->gamepad_axis_input(DC_AXIS_LEFT, -(int)std::round(point.x));
- if (point.y >= 0)
- virtualGamepad->gamepad_axis_input(DC_AXIS_DOWN, (int)std::round(point.y));
- else
- virtualGamepad->gamepad_axis_input(DC_AXIS_UP, -(int)std::round(point.y));
+ virtualGamepad->joystickInput(point.x, point.y);
continue;
}
vgamepad::ControlId control = vgamepad::hitTest(point.x, point.y);
@@ -186,10 +174,10 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
continue;
if (prevControl != nil && prevControl.intValue != vgamepad::None && prevControl.intValue != vgamepad::AnalogArea) {
// button up
- virtualGamepad->gamepad_btn_input(vgamepad::controlToDcKey((vgamepad::ControlId)prevControl.intValue), false);
+ virtualGamepad->buttonInput(static_cast(prevControl.intValue), false);
}
// button down
- virtualGamepad->gamepad_btn_input(vgamepad::controlToDcKey(control), true);
+ virtualGamepad->buttonInput(control, true);
touchToButton[key] = [NSNumber numberWithInt:control];
}
[super touchesMoved:touches withEvent:event];
@@ -199,7 +187,7 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
{
for (UITouch *touch in touches) {
if (touch == joyTouch) {
- [self resetTouch];
+ [self resetAnalog];
continue;
}
NSValue *key = [NSValue valueWithPointer:(const void *)touch];
@@ -207,7 +195,7 @@ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
if (control != nil) {
[touchToButton removeObjectForKey:key];
// button up
- virtualGamepad->gamepad_btn_input(vgamepad::controlToDcKey((vgamepad::ControlId)control.intValue), false);
+ virtualGamepad->buttonInput(static_cast(control.intValue), false);
}
}
[super touchesCancelled:touches withEvent:event];
diff --git a/shell/apple/emulator-ios/emulator/ios_gamepad.h b/shell/apple/emulator-ios/emulator/ios_gamepad.h
index 214744c3ea..e3aa0683a7 100644
--- a/shell/apple/emulator-ios/emulator/ios_gamepad.h
+++ b/shell/apple/emulator-ios/emulator/ios_gamepad.h
@@ -23,6 +23,7 @@
#include
#include "input/gamepad_device.h"
#include "input/mouse.h"
+#include "input/virtual_gamepad.h"
#include "ui/gui.h"
enum IOSButton {
@@ -480,110 +481,30 @@ class IOSGamepad : public GamepadDevice
static std::map> controllers;
};
-class IOSVirtualGamepad : public GamepadDevice
+class IOSVirtualGamepad : public VirtualGamepad
{
public:
- IOSVirtualGamepad() : GamepadDevice(0, "iOS", false) {
- _name = "Virtual Gamepad";
- _unique_id = "ios-virtual-gamepad";
- input_mapper = std::make_shared();
- //hasAnalogStick = true; // TODO has an analog stick but input mapping isn't persisted
+ IOSVirtualGamepad() : VirtualGamepad("iOS") {
}
- bool is_virtual_gamepad() override { return true; }
-
- std::shared_ptr getDefaultMapping() override {
- return std::make_shared>();
- }
-
- bool gamepad_btn_input(u32 code, bool pressed) override
+ bool handleButtonInput(u32& state, u32 key, bool pressed) override
{
- if (pressed)
- buttonState |= code;
- else
- buttonState &= ~code;
- switch (code)
+ if (!pressed
+ || (key != DC_DPAD_UP && key != DC_DPAD_DOWN && key != DC_DPAD_LEFT && key != DC_DPAD_RIGHT))
+ return false;
+ if (((state | key) & (DC_DPAD_UP | DC_DPAD_DOWN)) == (DC_DPAD_UP | DC_DPAD_DOWN)
+ || ((state | key) & (DC_DPAD_LEFT | DC_DPAD_RIGHT)) == (DC_DPAD_LEFT | DC_DPAD_RIGHT))
{
- case DC_AXIS_LT:
- gamepad_axis_input(DC_AXIS_LT, pressed ? 0x7fff : 0);
- if (settings.platform.isArcade())
- GamepadDevice::gamepad_btn_input(DC_BTN_Z, pressed); // btn5
- return true;
- case DC_AXIS_RT:
- if (!pressed && maple_port() >= 0 && maple_port() <= 3)
- kcode[maple_port()] |= DC_DPAD2_UP | DC_BTN_D | DC_DPAD2_DOWN;
- gamepad_axis_input(DC_AXIS_RT, pressed ? 0x7fff : 0);
- if (settings.platform.isArcade())
- GamepadDevice::gamepad_btn_input(DC_BTN_Y, pressed); // btn4
- return true;
- default:
- if ((buttonState & (DC_DPAD_UP | DC_DPAD_DOWN)) == (DC_DPAD_UP | DC_DPAD_DOWN)
- || (buttonState & (DC_DPAD_LEFT | DC_DPAD_RIGHT)) == (DC_DPAD_LEFT | DC_DPAD_RIGHT))
- {
- GamepadDevice::gamepad_btn_input(DC_DPAD_UP, false);
- GamepadDevice::gamepad_btn_input(DC_DPAD_DOWN, false);
- GamepadDevice::gamepad_btn_input(DC_DPAD_LEFT, false);
- GamepadDevice::gamepad_btn_input(DC_DPAD_RIGHT, false);
- buttonState = 0;
- gui_open_settings();
- return true;
- }
- if (settings.platform.isArcade() && maple_port() >= 0 && maple_port() <= 3)
- {
- u32& keycode = kcode[maple_port()];
- if ((buttonState & DC_AXIS_RT) != 0)
- {
- switch (code) {
- case DC_BTN_A:
- // RT + A -> D (coin)
- keycode = pressed ? keycode & ~DC_BTN_D : keycode | DC_BTN_D;
- break;
- case DC_BTN_B:
- // RT + B -> Service
- keycode = pressed ? keycode & ~DC_DPAD2_UP : keycode | DC_DPAD2_UP;
- break;
- case DC_BTN_X:
- // RT + X -> Test
- keycode = pressed ? keycode & ~DC_DPAD2_DOWN : keycode | DC_DPAD2_DOWN;
- break;
- default:
- break;
- }
- }
- // arcade mapping: X -> btn2, Y -> btn3
- if (code == DC_BTN_X)
- code = DC_BTN_C; // btn2
- if (code == DC_BTN_Y)
- code = DC_BTN_X; // btn3
- }
- switch (code)
- {
- case DC_DPAD_UP | DC_DPAD_RIGHT:
- GamepadDevice::gamepad_btn_input(DC_DPAD_UP, pressed);
- code = DC_DPAD_RIGHT;
- break;
- case DC_DPAD_DOWN | DC_DPAD_RIGHT:
- GamepadDevice::gamepad_btn_input(DC_DPAD_DOWN, pressed);
- code = DC_DPAD_RIGHT;
- break;
- case DC_DPAD_DOWN | DC_DPAD_LEFT:
- GamepadDevice::gamepad_btn_input(DC_DPAD_DOWN, pressed);
- code = DC_DPAD_LEFT;
- break;
- case DC_DPAD_UP | DC_DPAD_LEFT:
- GamepadDevice::gamepad_btn_input(DC_DPAD_UP, pressed);
- code = DC_DPAD_LEFT;
- break;
- default:
- break;
- }
-
- return GamepadDevice::gamepad_btn_input(code, pressed);
+ gamepad_btn_input(DC_DPAD_UP, false);
+ gamepad_btn_input(DC_DPAD_DOWN, false);
+ gamepad_btn_input(DC_DPAD_LEFT, false);
+ gamepad_btn_input(DC_DPAD_RIGHT, false);
+ state = 0;
+ gui_open_settings();
+ return true;
}
+ return false;
}
-
-private:
- u32 buttonState = 0;
};
class IOSTouchMouse : public SystemMouse