android,ios: arcade vgamepad layout. android vgamepad rewrite

Refactor common virtual gamepad code.
Distinct layouts for console and arcade.
Add arcade buttons 5 and 6, insert card button, service mode toggle.
System SP support
This commit is contained in:
Flyinghead 2024-12-03 21:04:51 +01:00
parent 78fa3ee09e
commit 21f9e9fbc2
25 changed files with 3405 additions and 1124 deletions

View File

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

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#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<IdentityInputMapping>();
// 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;
};

View File

@ -1467,7 +1467,7 @@ static void gamepadSettingsPopup(const std::shared_ptr<GamepadDevice>& 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)))

View File

@ -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 <stb_image.h>
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<u8[]> data = resource::load(BTN_PATH, size);
std::unique_ptr<u8[]> 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<ControlId>(&control - &Controls[0]) == AnalogStick)
ControlId controlId = static_cast<ControlId>(&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

View File

@ -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 @@ public:
#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

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -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 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
}
//setup mic
if (Emulator.micPluggedIn())
if (InputDeviceManager.isMicPluggedIn())
requestRecordAudioPermission();
}

View File

@ -10,7 +10,8 @@ import android.util.Log;
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 class Emulator extends Application {
{
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();

View File

@ -16,10 +16,8 @@ import android.widget.RelativeLayout;
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 final class NativeGLActivity extends BaseGLActivity {
return mView != null && mView.isSurfaceReady();
}
@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 VJoyStartEditing() {
vjoy_d_cached = VJoy.readCustomVjoyValues(getApplicationContext());
JNIdc.showVirtualGamepad();
mView.setEditVjoyMode(true);
}
// Called from native code
private void VJoyResetEditing() {
VJoy.resetCustomVjoyValues(getApplicationContext());
mView.readCustomVjoyValues();
mView.resetEditMode();
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 {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
}

View File

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

View File

@ -10,6 +10,7 @@ import android.util.DisplayMetrics;
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 @@ import com.flycast.emulator.periph.InputDeviceManager;
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 class NativeGLView extends SurfaceView implements SurfaceHolder.Callback
@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 @@ public class NativeGLView extends SurfaceView implements SurfaceHolder.Callback
}
}
public void resetEditMode() {
vjoyDelegate.resetEditMode();
}
@Override
public boolean onTouchEvent(final MotionEvent event)
{
@ -113,13 +107,13 @@ public class NativeGLView extends SurfaceView implements SurfaceHolder.Callback
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 class NativeGLView extends SurfaceView implements SurfaceHolder.Callback
}
}
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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.flycast.emulator.emu;
import android.view.MotionEvent;
public interface TouchEventHandler {
boolean onTouchEvent(MotionEvent event, int width, int height);
void stop();
void show();
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 implements TouchEventHandler
{
private static final int CTLID_ANARING = 11;
private static final int CTLID_ANASTICK = 12;
public class VirtualJoystickDelegate {
private VibratorThread vibratorThread;
private boolean editVjoyMode = false;
private int selectedVjoyElement = VJoy.ELEM_NONE;
private ScaleGestureDetector scaleGestureDetector;
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<Integer, Integer> 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()
private boolean touchMouseEvent(MotionEvent event)
{
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)
{
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.length;i++)
{
// FIXME this hack causes the slight "jump" when first moving a screen-centered button
if (vjoy_d[i][0] == 288)
vjoy[i][0] = vjoy_d[i][0];
else if (vjoy_d[i][0]-vjoy_d_custom[getElementIdFromButtonId(i)][0] < 320)
vjoy[i][0] = a_x + vbase(vjoy_d[i][0],scl);
else
vjoy[i][0] = -a_x + vbase(vjoy_d[i][0],640,scl);
vjoy[i][1] = a_y + vbase(vjoy_d[i][1],480,scl);
vjoy[i][2] = vbase(vjoy_d[i][2],scl);
vjoy[i][3] = vbase(vjoy_d[i][3],scl);
}
for (int i=0;i<VJoy.VJoyCount;i++)
JNIdc.vjoy(i,vjoy[i][0],vjoy[i][1],vjoy[i][2],vjoy[i][3]);
reset_analog();
VJoy.writeCustomVjoyValues(vjoy_d_custom, context);
}
private int anal_id=-1, lt_id=-1, rt_id=-1;
public void resetEditMode() {
editLastX = 0;
editLastY = 0;
}
private static int getElementIdFromButtonId(int buttonId) {
if (buttonId <= 3)
return VJoy.ELEM_DPAD; // DPAD
else if (buttonId <= 7)
return VJoy.ELEM_BUTTONS; // X, Y, B, A Buttons
else if (buttonId == 8)
return VJoy.ELEM_START; // Start
else if (buttonId == 9)
return VJoy.ELEM_LTRIG; // Left Trigger
else if (buttonId == 10)
return VJoy.ELEM_RTRIG; // Right Trigger
else if (buttonId <= 12)
return VJoy.ELEM_ANALOG; // Analog
else if (buttonId == 13)
return VJoy.ELEM_FFORWARD; // Fast-forward
else
return VJoy.ELEM_DPAD; // DPAD diagonals
}
private static int left_trigger = 0;
private static int right_trigger = 0;
private static int[] mouse_pos = { -32768, -32768 };
private static int mouse_btns = 0;
private float editLastX = 0, editLastY = 0;
public boolean onTouchEvent(MotionEvent event, int width, int height)
{
if ((event.getSource() & InputDevice.SOURCE_TOUCHSCREEN) != InputDevice.SOURCE_TOUCHSCREEN)
// Ignore real mice, trackballs, etc.
return false;
JNIdc.showVirtualGamepad();
this.handler.removeCallbacks(hideVGamepadRunnable);
if (!editVjoyMode)
this.handler.postDelayed(hideVGamepadRunnable, 10000);
scaleGestureDetector.onTouchEvent(event);
float ty = 0.0f;
float scl = height / 480.0f;
float tx = (width - 640.0f * scl) / 2;
int rv = 0xFFFFFFFF;
boolean fastForward = false;
int aid = event.getActionMasked();
int pid = event.getActionIndex();
if (!JNIdc.guiIsOpen() || editVjoyMode) {
if (editVjoyMode && selectedVjoyElement != VJoy.ELEM_NONE && aid == MotionEvent.ACTION_MOVE && !scaleGestureDetector.isInProgress()) {
float x = (event.getX() - tx) / scl;
float y = (event.getY() - ty) / scl;
if (editLastX != 0 && editLastY != 0) {
float deltaX = x - editLastX;
float deltaY = y - editLastY;
vjoy_d_custom[selectedVjoyElement][0] += isTablet() ? deltaX * 2 : deltaX;
vjoy_d_custom[selectedVjoyElement][1] += isTablet() ? deltaY * 2 : deltaY;
view.requestLayout();
}
editLastX = x;
editLastY = y;
return true;
}
for (int i = 0; i < event.getPointerCount(); i++) {
float x = (event.getX(i) - tx) / scl;
float y = (event.getY(i) - ty) / scl;
if (anal_id != event.getPointerId(i)) {
if (aid == MotionEvent.ACTION_POINTER_UP && pid == i)
continue;
for (int j = 0; j < vjoy.length; j++)
{
if (!editVjoyMode && !vjoy_enabled[j])
continue;
if (x > vjoy[j][0] && x <= (vjoy[j][0] + vjoy[j][2])
&& y > vjoy[j][1] && y <= (vjoy[j][1] + vjoy[j][3]))
{
if (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 actionMasked = event.getActionMasked();
int actionIndex = event.getActionIndex();
switch (actionMasked)
{
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;
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)
{
right_trigger = 0;
rt_id = -1;
}
break;
mousePid = -1;
mouseButtons = 0;
InputDeviceManager.getInstance().mouseEvent(mousePos[0], mousePos[1], mouseButtons);
return true;
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_DOWN:
if (event.getPointerCount() != 1)
if (mousePid == -1 || actionMasked == MotionEvent.ACTION_DOWN)
{
mouse_btns = 0;
mousePid = event.getPointerId(actionIndex);
mousePos[0] = Math.round(event.getX(actionIndex));
mousePos[1] = Math.round(event.getY(actionIndex));
mouseButtons = MotionEvent.BUTTON_PRIMARY; // Mouse left button down
InputDeviceManager.getInstance().mouseEvent(mousePos[0], mousePos[1], mouseButtons);
return true;
}
else
{
mouse_pos[0] = Math.round(event.getX());
mouse_pos[1] = Math.round(event.getY());
mouse_btns = MotionEvent.BUTTON_PRIMARY; // Mouse left button down
}
break;
return false;
case MotionEvent.ACTION_MOVE:
if (event.getPointerCount() == 1)
for (int i = 0; i < event.getPointerCount(); i++)
{
mouse_pos[0] = Math.round(event.getX());
mouse_pos[1] = Math.round(event.getY());
if (event.getPointerId(i) == mousePid) {
mousePos[0] = Math.round(event.getX(i));
mousePos[1] = Math.round(event.getY(i));
InputDeviceManager.getInstance().mouseEvent(mousePos[0], mousePos[1], mouseButtons);
break;
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (event.getPointerId(actionIndex) == mousePid)
{
mousePid = -1;
mousePos[0] = Math.round(event.getX(actionIndex));
mousePos[1] = Math.round(event.getY(actionIndex));
mouseButtons = 0;
InputDeviceManager.getInstance().mouseEvent(mousePos[0], mousePos[1], mouseButtons);
return true;
}
break;
default:
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);
return false;
}
public void setEditVjoyMode(boolean editVjoyMode) {
this.editVjoyMode = editVjoyMode;
selectedVjoyElement = -1;
if (editVjoyMode) {
this.handler.removeCallbacks(hideVGamepadRunnable);
Arrays.fill(vjoy_enabled, true);
static class Point
{
Point() {
this.x = 0.f;
this.y = 0.f;
}
resetEditMode();
Point(float x, float y) {
this.x = x;
this.y = y;
}
float x;
float y;
}
public void enableVjoy(boolean[] state) {
vjoy_enabled = state;
static Point translateCoords(Point pos, Point size)
{
float hscale = 480.f / size.y;
Point p = new Point();
p.y = pos.y * hscale;
p.x = (pos.x - (size.x - 640.f / hscale) / 2.f) * hscale;
return p;
}
private class OscOnScaleGestureListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onTouchEvent(MotionEvent event, int width, int height)
{
if (JNIdc.guiIsOpen())
return touchMouseEvent(event);
show();
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (editVjoyMode && selectedVjoyElement != -1) {
vjoy_d_custom[selectedVjoyElement][2] *= detector.getScaleFactor();
view.requestLayout();
int actionIndex = event.getActionIndex();
switch (event.getActionMasked())
{
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// Release all
pidToControlId.clear();
joyPointerId = -1;
InputDeviceManager.getInstance().virtualReleaseAll();
break;
return true;
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)
{
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;
}
return false;
}
case MotionEvent.ACTION_MOVE:
for (int i = 0; i < event.getPointerCount(); i++)
{
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;
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
selectedVjoyElement = -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))
{
// Button up
int controlId = pidToControlId.get(pid);
InputDeviceManager.getInstance().virtualButtonInput(controlId, false);
return true;
}
break;
}
}
return touchMouseEvent(event);
}
@Override
public void show()
{
VGamepad.show();
this.handler.removeCallbacks(hideVGamepadRunnable);
this.handler.postDelayed(hideVGamepadRunnable, 10000);
}
}

View File

@ -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 final class InputDeviceManager implements InputManager.InputDeviceListene
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 final class InputDeviceManager implements InputManager.InputDeviceListene
}
}
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 final class InputDeviceManager implements InputManager.InputDeviceListene
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();
}

View File

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

View File

@ -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<AndroidMouse> mouse;
std::shared_ptr<AndroidKeyboard> 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<vgamepad::ControlId>(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<int> devs;
for (u32 i = 0; i < config::MapleMainDevices.size(); i++)
devs.push_back((MapleDeviceType)config::MapleMainDevices[i]);
jcontrollers.setData(devs.data());
jni::ObjectArray<jni::IntArray> jperipherals(peripherals, false);
int obj_len = jperipherals.size();
for (int i = 0; i < obj_len; ++i)
{
std::vector<int> 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<AndroidMouse>(-1);
GamepadDevice::Register(mouse);
keyboard = std::make_shared<AndroidKeyboard>();
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<int> full = jni::IntArray(fullAxes, false);
std::vector<int> half = jni::IntArray(halfAxes, false);
std::shared_ptr<AndroidGamepadDevice> gamepad = std::make_shared<AndroidGamepadDevice>(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<AndroidGamepadDevice> 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<AndroidGamepadDevice> 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<AndroidGamepadDevice> 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<std::pair<jint, jint>, 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<AndroidGamepadDevice> 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();

View File

@ -20,6 +20,7 @@
#include "input/gamepad_device.h"
#include "input/mouse.h"
#include "input/virtual_gamepad.h"
#include "jni_util.h"
#include <algorithm>
@ -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<int>& fullAxes, const std::vector<int>& 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<IdentityInputMapping>();
// 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 @@ public:
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 @@ public:
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 @@ public:
input_mapper = std::make_shared<DefaultInputMapping<false, false>>(*this);
}
static const int VIRTUAL_GAMEPAD_ID = 0x12345678; // must match the Java definition
private:
int android_id;
static std::map<int, std::shared_ptr<AndroidGamepadDevice>> android_gamepads;
u32 previous_kcode = 0xffffffff;
bool previousFastForward = false;
std::vector<int> fullAxes;
std::vector<int> halfAxes;
};
@ -481,3 +408,19 @@ public:
}
};
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
};

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#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<AndroidMouse> mouse;
std::shared_ptr<AndroidKeyboard> keyboard;
std::shared_ptr<AndroidVirtualGamepad> 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<vgamepad::ControlId>(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<vgamepad::Element>(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<vgamepad::Element>(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<MapleDeviceType>(devices[0]) == MDT_Microphone
|| static_cast<MapleDeviceType>(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<AndroidMouse>(-1);
GamepadDevice::Register(mouse);
keyboard = std::make_shared<AndroidKeyboard>();
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<AndroidVirtualGamepad>(hasRumble);
GamepadDevice::Register(virtualGamepad);
}
else
{
std::string joyname = jni::String(name, false);
std::string unique_id = jni::String(junique_id, false);
std::vector<int> full = jni::IntArray(fullAxes, false);
std::vector<int> half = jni::IntArray(halfAxes, false);
std::shared_ptr<AndroidGamepadDevice> gamepad = std::make_shared<AndroidGamepadDevice>(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<AndroidGamepadDevice> 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<vgamepad::ControlId>(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<AndroidGamepadDevice> 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<std::pair<jint, jint>, 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<AndroidGamepadDevice> 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);
}

View File

@ -107,7 +107,7 @@ public:
bool isNull() const { return object == nullptr; }
operator jobject() const { return object; }
Class getClass() const;
inline Class getClass() const;
template<typename T>
T globalRef() {

View File

@ -65,7 +65,7 @@
- (void)hideController
{
[self resetTouch];
[self resetAnalog];
[hideTimer invalidate];
[self.view removeFromSuperview];
}
@ -89,12 +89,10 @@
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 @@ static CGPoint translateCoords(const CGPoint& pos, const CGSize& size)
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 @@ static CGPoint translateCoords(const CGPoint& pos, const CGSize& size)
for (UITouch *touch in touches)
{
if (touch == joyTouch) {
[self resetTouch];
[self resetAnalog];
continue;
}
NSValue *key = [NSValue valueWithPointer:(const void *)touch];
@ -146,7 +144,7 @@ static CGPoint translateCoords(const CGPoint& pos, const CGSize& size)
if (control != nil) {
[touchToButton removeObjectForKey:key];
// button up
virtualGamepad->gamepad_btn_input(vgamepad::controlToDcKey((vgamepad::ControlId)control.intValue), false);
virtualGamepad->buttonInput(static_cast<vgamepad::ControlId>(control.intValue), false);
}
}
[super touchesEnded:touches withEvent:event];
@ -166,17 +164,7 @@ static CGPoint translateCoords(const CGPoint& pos, const CGSize& size)
double sz = vgamepad::getControlWidth(vgamepad::AnalogStick);
point.x = std::max<CGFloat>(std::min<CGFloat>(1.0, point.x / sz), -1.0);
point.y = std::max<CGFloat>(std::min<CGFloat>(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 @@ static CGPoint translateCoords(const CGPoint& pos, const CGSize& size)
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<vgamepad::ControlId>(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 @@ static CGPoint translateCoords(const CGPoint& pos, const CGSize& size)
{
for (UITouch *touch in touches) {
if (touch == joyTouch) {
[self resetTouch];
[self resetAnalog];
continue;
}
NSValue *key = [NSValue valueWithPointer:(const void *)touch];
@ -207,7 +195,7 @@ static CGPoint translateCoords(const CGPoint& pos, const CGSize& size)
if (control != nil) {
[touchToButton removeObjectForKey:key];
// button up
virtualGamepad->gamepad_btn_input(vgamepad::controlToDcKey((vgamepad::ControlId)control.intValue), false);
virtualGamepad->buttonInput(static_cast<vgamepad::ControlId>(control.intValue), false);
}
}
[super touchesCancelled:touches withEvent:event];

View File

@ -23,6 +23,7 @@
#include <cmath>
#include "input/gamepad_device.h"
#include "input/mouse.h"
#include "input/virtual_gamepad.h"
#include "ui/gui.h"
enum IOSButton {
@ -480,110 +481,30 @@ private:
static std::map<GCController *, std::shared_ptr<IOSGamepad>> 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<IdentityInputMapping>();
//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<InputMapping> getDefaultMapping() override {
return std::make_shared<DefaultIOSMapping<>>();
}
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