scummvm/backends/platform/3ds/osystem-events.cpp
2021-12-26 18:48:43 +01:00

485 lines
16 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#include "backends/platform/3ds/osystem.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper-defaults.h"
#include "backends/keymapper/hardware-input.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/standard-actions.h"
#include "backends/platform/3ds/config.h"
#include "backends/platform/3ds/options-dialog.h"
#include "backends/timer/default/default-timer.h"
#include "common/translation.h"
#include "engines/engine.h"
#include "gui/gui-manager.h"
namespace N3DS {
static Common::Mutex *eventMutex;
static InputMode inputMode = MODE_DRAG;
static InputMode savedInputMode = MODE_DRAG;
static aptHookCookie cookie;
static const Common::HardwareInputTableEntry ctrJoystickButtons[] = {
{ "JOY_A", Common::JOYSTICK_BUTTON_A, _s("A") },
{ "JOY_B", Common::JOYSTICK_BUTTON_B, _s("B") },
{ "JOY_X", Common::JOYSTICK_BUTTON_X, _s("X") },
{ "JOY_Y", Common::JOYSTICK_BUTTON_Y, _s("Y") },
{ "JOY_BACK", Common::JOYSTICK_BUTTON_BACK, _s("Select") },
{ "JOY_START", Common::JOYSTICK_BUTTON_START, _s("Start") },
{ "JOY_LEFT_STICK", Common::JOYSTICK_BUTTON_LEFT_STICK, _s("ZL") },
{ "JOY_RIGHT_STICK", Common::JOYSTICK_BUTTON_RIGHT_STICK, _s("ZR") },
{ "JOY_LEFT_SHOULDER", Common::JOYSTICK_BUTTON_LEFT_SHOULDER, _s("L") },
{ "JOY_RIGHT_SHOULDER", Common::JOYSTICK_BUTTON_RIGHT_SHOULDER, _s("R") },
{ "JOY_UP", Common::JOYSTICK_BUTTON_DPAD_UP, _s("D-pad Up") },
{ "JOY_DOWN", Common::JOYSTICK_BUTTON_DPAD_DOWN, _s("D-pad Down") },
{ "JOY_LEFT", Common::JOYSTICK_BUTTON_DPAD_LEFT, _s("D-pad Left") },
{ "JOY_RIGHT", Common::JOYSTICK_BUTTON_DPAD_RIGHT, _s("D-pad Right") },
{ nullptr, 0, nullptr }
};
static const Common::AxisTableEntry ctrJoystickAxes[] = {
{ "JOY_LEFT_STICK_X", Common::JOYSTICK_AXIS_LEFT_STICK_X, Common::kAxisTypeFull, _s("C-Pad X") },
{ "JOY_LEFT_STICK_Y", Common::JOYSTICK_AXIS_LEFT_STICK_Y, Common::kAxisTypeFull, _s("C-Pad Y") },
{ nullptr, 0, Common::kAxisTypeFull, nullptr }
};
const Common::HardwareInputTableEntry ctrMouseButtons[] = {
{ "MOUSE_LEFT", Common::MOUSE_BUTTON_LEFT, _s("Touch") },
{ nullptr, 0, nullptr }
};
static const int16 CIRCLE_MAX = 160;
static void pushEventQueue(Common::Queue<Common::Event> *queue, Common::Event &event) {
Common::StackLock lock(*eventMutex);
queue->push(event);
}
static void doJoyEvent(Common::Queue<Common::Event> *queue, u32 keysPressed, u32 keysReleased, u32 ctrKey, uint8 svmButton) {
if (keysPressed & ctrKey || keysReleased & ctrKey) {
Common::Event event;
event.type = (keysPressed & ctrKey) ? Common::EVENT_JOYBUTTON_DOWN : Common::EVENT_JOYBUTTON_UP;
event.joystick.button = svmButton;
pushEventQueue(queue, event);
}
}
static void eventThreadFunc(void *arg) {
OSystem_3DS *osys = dynamic_cast<OSystem_3DS *>(g_system);
Common::Queue<Common::Event> *eventQueue = (Common::Queue<Common::Event> *)arg;
uint32 touchStartTime = osys->getMillis();
touchPosition lastTouch = {0, 0};
circlePosition lastCircle = {0, 0};
int borderSnapZone = 6;
Common::Event event;
while (!osys->exiting) {
do {
osys->delayMillis(10);
} while (osys->sleeping && !osys->exiting);
hidScanInput();
u32 held = hidKeysHeld();
u32 keysPressed = hidKeysDown();
u32 keysReleased = hidKeysUp();
// Touch screen events
if (held & KEY_TOUCH) {
touchPosition touch;
hidTouchRead(&touch);
if (config.snapToBorder) {
if (touch.px < borderSnapZone) {
touch.px = 0;
}
if (touch.px > 319 - borderSnapZone) {
touch.px = 319;
}
if (touch.py < borderSnapZone) {
touch.py = 0;
}
if (touch.py > 239 - borderSnapZone) {
touch.py = 239;
}
}
osys->transformPoint(touch);
event.mouse.x = touch.px;
event.mouse.y = touch.py;
if (keysPressed & KEY_TOUCH) {
touchStartTime = osys->getMillis();
if (inputMode == MODE_DRAG) {
event.type = Common::EVENT_LBUTTONDOWN;
pushEventQueue(eventQueue, event);
}
} else if (touch.px != lastTouch.px || touch.py != lastTouch.py) {
event.type = Common::EVENT_MOUSEMOVE;
pushEventQueue(eventQueue, event);
}
lastTouch = touch;
} else if (keysReleased & KEY_TOUCH) {
event.mouse.x = lastTouch.px;
event.mouse.y = lastTouch.py;
if (inputMode == MODE_DRAG) {
event.type = Common::EVENT_LBUTTONUP;
pushEventQueue(eventQueue, event);
} else if (osys->getMillis() - touchStartTime < 200) {
// Process click in MODE_HOVER
event.type = Common::EVENT_MOUSEMOVE;
pushEventQueue(eventQueue, event);
event.type = Common::EVENT_LBUTTONDOWN;
pushEventQueue(eventQueue, event);
event.type = Common::EVENT_LBUTTONUP;
pushEventQueue(eventQueue, event);
}
}
// C-Pad events
circlePosition circle;
hidCircleRead(&circle);
if (circle.dx != lastCircle.dx) {
int32 position = (int32)circle.dx * Common::JOYAXIS_MAX / CIRCLE_MAX;
event.type = Common::EVENT_JOYAXIS_MOTION;
event.joystick.axis = Common::JOYSTICK_AXIS_LEFT_STICK_X;
event.joystick.position = CLIP<int32>(position, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
pushEventQueue(eventQueue, event);
}
if (circle.dy != lastCircle.dy) {
int32 position = -(int32)circle.dy * Common::JOYAXIS_MAX / CIRCLE_MAX;
event.type = Common::EVENT_JOYAXIS_MOTION;
event.joystick.axis = Common::JOYSTICK_AXIS_LEFT_STICK_Y;
event.joystick.position = CLIP<int32>(position, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
pushEventQueue(eventQueue, event);
}
lastCircle = circle;
// Button events
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_L, Common::JOYSTICK_BUTTON_LEFT_SHOULDER);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_R, Common::JOYSTICK_BUTTON_RIGHT_SHOULDER);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_A, Common::JOYSTICK_BUTTON_A);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_B, Common::JOYSTICK_BUTTON_B);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_X, Common::JOYSTICK_BUTTON_X);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_Y, Common::JOYSTICK_BUTTON_Y);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DUP, Common::JOYSTICK_BUTTON_DPAD_UP);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DDOWN, Common::JOYSTICK_BUTTON_DPAD_DOWN);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DLEFT, Common::JOYSTICK_BUTTON_DPAD_LEFT);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_DRIGHT, Common::JOYSTICK_BUTTON_DPAD_RIGHT);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_START, Common::JOYSTICK_BUTTON_START);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_SELECT, Common::JOYSTICK_BUTTON_BACK);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_ZL, Common::JOYSTICK_BUTTON_LEFT_STICK);
doJoyEvent(eventQueue, keysPressed, keysReleased, KEY_ZR, Common::JOYSTICK_BUTTON_RIGHT_STICK);
}
}
static void aptHookFunc(APT_HookType hookType, void *param) {
OSystem_3DS *osys = dynamic_cast<OSystem_3DS *>(g_system);
switch (hookType) {
case APTHOOK_ONSUSPEND:
case APTHOOK_ONSLEEP:
if (g_engine) {
osys->_sleepPauseToken = g_engine->pauseEngine();
}
osys->sleeping = true;
if (R_SUCCEEDED(gspLcdInit())) {
GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH);
gspLcdExit();
}
break;
case APTHOOK_ONRESTORE:
case APTHOOK_ONWAKEUP:
if (g_engine) {
osys->_sleepPauseToken.clear();
}
osys->sleeping = false;
loadConfig();
break;
case APTHOOK_ONEXIT:
break;
default:
warning("Unhandled APT hook, type: %d", hookType);
}
}
static void timerThreadFunc(void *arg) {
OSystem_3DS *osys = (OSystem_3DS *)arg;
DefaultTimerManager *tm = (DefaultTimerManager *)osys->getTimerManager();
while (!osys->exiting) {
g_system->delayMillis(10);
tm->handler();
}
}
Common::HardwareInputSet *OSystem_3DS::getHardwareInputSet() {
using namespace Common;
CompositeHardwareInputSet *inputSet = new CompositeHardwareInputSet();
// Touch input sends mouse events for now, so we need to declare we have a mouse...
inputSet->addHardwareInputSet(new MouseHardwareInputSet(ctrMouseButtons));
inputSet->addHardwareInputSet(new JoystickHardwareInputSet(ctrJoystickButtons, ctrJoystickAxes));
return inputSet;
}
void OSystem_3DS::initEvents() {
eventMutex = new Common::Mutex();
s32 prio = 0;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
_timerThread = threadCreate(&timerThreadFunc, this, 32 * 1024, prio - 1, -2, false);
_eventThread = threadCreate(&eventThreadFunc, &_eventQueue, 32 * 1024, prio - 1, -2, false);
aptHook(&cookie, aptHookFunc, this);
_eventManager->getEventDispatcher()->registerObserver(this, 10, false);
}
void OSystem_3DS::destroyEvents() {
_eventManager->getEventDispatcher()->unregisterObserver(this);
threadJoin(_timerThread, U64_MAX);
threadFree(_timerThread);
threadJoin(_eventThread, U64_MAX);
threadFree(_eventThread);
delete eventMutex;
}
void OSystem_3DS::transformPoint(touchPosition &point) {
if (!_overlayVisible) {
point.px = static_cast<float>(point.px) / _gameBottomTexture.getScaleX() - _gameBottomTexture.getPosX();
point.py = static_cast<float>(point.py) / _gameBottomTexture.getScaleY() - _gameBottomTexture.getPosY();
}
clipPoint(point);
}
void OSystem_3DS::clipPoint(touchPosition &point) {
if (_overlayVisible) {
point.px = CLIP<uint16>(point.px, 0, getOverlayWidth() - 1);
point.py = CLIP<uint16>(point.py, 0, getOverlayHeight() - 1);
} else {
point.px = CLIP<uint16>(point.px, 0, _gameTopTexture.actualWidth - 1);
point.py = CLIP<uint16>(point.py, 0, _gameTopTexture.actualHeight - 1);
}
}
enum _3DSCustomEvent {
k3DSEventToggleDragMode,
k3DSEventToggleMagnifyMode,
k3DSEventOpenSettings
};
Common::KeymapArray OSystem_3DS::getGlobalKeymaps() {
using namespace Common;
Keymap *keymap = new Keymap(Keymap::kKeymapTypeGlobal, "3ds", "3DS");
Action *act;
act = new Action("DRAGM", _("Toggle Drag Mode"));
act->setCustomBackendActionEvent(k3DSEventToggleDragMode);
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
keymap->addAction(act);
act = new Action("MAGM", _("Toggle Magnify Mode"));
act->setCustomBackendActionEvent(k3DSEventToggleMagnifyMode);
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
keymap->addAction(act);
act = new Action("OPTS", _("Open 3DS Settings"));
act->setCustomBackendActionEvent(k3DSEventOpenSettings);
act->addDefaultInputMapping("JOY_BACK");
keymap->addAction(act);
return Keymap::arrayOf(keymap);
}
Common::KeymapperDefaultBindings *OSystem_3DS::getKeymapperDefaultBindings() {
Common::KeymapperDefaultBindings *keymapperDefaultBindings = new Common::KeymapperDefaultBindings();
// Bind the virtual keyboard to X so SELECT can be used for the 3DS options dialog
keymapperDefaultBindings->setDefaultBinding(Common::kGlobalKeymapName, "VIRT", "JOY_X");
// Unmap the main menu standard action so LEFT_SHOULDER can be used for drag mode
keymapperDefaultBindings->setDefaultBinding("engine-default", Common::kStandardActionOpenMainMenu, "");
return keymapperDefaultBindings;
}
bool OSystem_3DS::pollEvent(Common::Event &event) {
if (!aptMainLoop()) {
// The system requested us to quit
if (_sleepPauseToken.isActive()) {
_sleepPauseToken.clear();
}
event.type = Common::EVENT_QUIT;
return true;
}
// If magnify mode is on when returning to Launcher, turn it off
if (_eventManager->shouldReturnToLauncher()) {
if (_magnifyMode == MODE_MAGON) {
_magnifyMode = MODE_MAGOFF;
updateSize();
if (savedInputMode == MODE_DRAG) {
inputMode = savedInputMode;
displayMessageOnOSD(_("Magnify Mode Off. Reactivating Drag Mode.\nReturning to Launcher..."));
} else {
displayMessageOnOSD(_("Magnify Mode Off. Returning to Launcher..."));
}
}
}
Common::StackLock lock(*eventMutex);
if (_eventQueue.empty()) {
return false;
}
event = _eventQueue.pop();
if (Common::isMouseEvent(event)) {
warpMouse(event.mouse.x, event.mouse.y);
}
return true;
}
bool OSystem_3DS::notifyEvent(const Common::Event &event) {
if (event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_START
&& event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_END) {
return false; // We're only interested in custom backend events
}
if (event.type == Common::EVENT_CUSTOM_BACKEND_ACTION_END) {
return true; // We'll say we have handled the event so it is not propagated
}
switch ((_3DSCustomEvent)event.customType) {
case k3DSEventToggleDragMode:
if (inputMode == MODE_DRAG) {
inputMode = savedInputMode = MODE_HOVER;
displayMessageOnOSD(_("Hover Mode"));
} else {
if (_magnifyMode == MODE_MAGOFF) {
inputMode = savedInputMode = MODE_DRAG;
displayMessageOnOSD(_("Drag Mode"));
} else {
displayMessageOnOSD(_("Cannot Switch to Drag Mode while Magnify Mode is On"));
}
}
return true;
case k3DSEventToggleMagnifyMode:
if (_overlayVisible) {
displayMessageOnOSD(_("Magnify Mode cannot be activated in menus."));
} else if (config.screen != kScreenBoth && _magnifyMode == MODE_MAGOFF) {
// TODO: Automatically enable both screens while magnify mode is on
displayMessageOnOSD(_("Magnify Mode can only be activated\n when both screens are enabled."));
} else if (_gameWidth <= 400 && _gameHeight <= 240) {
displayMessageOnOSD(_("In-game resolution too small to magnify."));
} else {
if (_magnifyMode == MODE_MAGOFF) {
_magnifyMode = MODE_MAGON;
if (inputMode == MODE_DRAG) {
inputMode = MODE_HOVER;
displayMessageOnOSD(_("Magnify Mode On. Switching to Hover Mode..."));
} else {
displayMessageOnOSD(_("Magnify Mode On"));
}
} else {
_magnifyMode = MODE_MAGOFF;
updateSize();
if (savedInputMode == MODE_DRAG) {
inputMode = savedInputMode;
displayMessageOnOSD(_("Magnify Mode Off. Reactivating Drag Mode..."));
} else {
displayMessageOnOSD(_("Magnify Mode Off"));
}
}
}
return true;
case k3DSEventOpenSettings:
runOptionsDialog();
return true;
}
return false;
}
void OSystem_3DS::runOptionsDialog() {
static bool optionsDialogRunning = false;
// Prevent opening the options dialog multiple times
if (optionsDialogRunning) {
return;
}
optionsDialogRunning = true;
PauseToken pauseToken;
OptionsDialog dialog;
if (g_engine) {
pauseToken = g_engine->pauseEngine();
}
int result = dialog.runModal();
if (g_engine) {
pauseToken.clear();
}
if (result > 0) {
int oldScreen = config.screen;
config.showCursor = dialog.getShowCursor();
config.snapToBorder = dialog.getSnapToBorder();
config.stretchToFit = dialog.getStretchToFit();
config.screen = dialog.getScreen();
saveConfig();
loadConfig();
if (config.screen != oldScreen) {
_screenChangeId++;
g_gui.checkScreenChange();
}
}
optionsDialogRunning = false;
}
} // namespace N3DS