CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/UI/GamepadEmu.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>1819#include "Common/Data/Color/RGBAUtil.h"20#include "Common/Data/Text/I18n.h"21#include "Common/System/Display.h"22#include "Common/System/System.h"23#include "Common/Render/TextureAtlas.h"24#include "Common/Math/math_util.h"25#include "Common/UI/Context.h"2627#include "Common/Log.h"28#include "Common/TimeUtil.h"29#include "Core/Config.h"30#include "Core/Core.h"31#include "Core/System.h"32#include "Core/HLE/sceCtrl.h"33#include "Core/ControlMapper.h"34#include "UI/GamepadEmu.h"3536const float TOUCH_SCALE_FACTOR = 1.5f;3738static uint32_t usedPointerMask = 0;39static uint32_t analogPointerMask = 0;4041static float g_gamepadOpacity;42static double g_lastTouch;4344void GamepadUpdateOpacity(float force) {45if (force >= 0.0f) {46g_gamepadOpacity = force;47return;48}49if (coreState != CORE_RUNNING) {50g_gamepadOpacity = 0.0f;51return;52}5354float fadeAfterSeconds = g_Config.iTouchButtonHideSeconds;55float fadeTransitionSeconds = std::min(fadeAfterSeconds, 0.5f);56float opacity = g_Config.iTouchButtonOpacity / 100.0f;5758float multiplier = 1.0f;59float secondsWithoutTouch = time_now_d() - g_lastTouch;60if (secondsWithoutTouch >= fadeAfterSeconds && fadeAfterSeconds > 0.0f) {61if (secondsWithoutTouch >= fadeAfterSeconds + fadeTransitionSeconds) {62multiplier = 0.0f;63} else {64float secondsIntoFade = secondsWithoutTouch - fadeAfterSeconds;65multiplier = 1.0f - (secondsIntoFade / fadeTransitionSeconds);66}67}6869g_gamepadOpacity = opacity * multiplier;70}7172void GamepadTouch(bool reset) {73g_lastTouch = reset ? 0.0f : time_now_d();74}7576float GamepadGetOpacity() {77return g_gamepadOpacity;78}7980static u32 GetButtonColor() {81return g_Config.iTouchButtonStyle != 0 ? 0xFFFFFF : 0xc0b080;82}8384GamepadView::GamepadView(const char *key, UI::LayoutParams *layoutParams) : UI::View(layoutParams), key_(key) {}8586std::string GamepadView::DescribeText() const {87auto co = GetI18NCategory(I18NCat::CONTROLS);88return std::string(co->T(key_));89}9091void MultiTouchButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const {92const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(bgImg_);93if (image) {94w = image->w * scale_;95h = image->h * scale_;96} else {97w = 0.0f;98h = 0.0f;99}100}101102bool MultiTouchButton::Touch(const TouchInput &input) {103bool retval = GamepadView::Touch(input);104if ((input.flags & TOUCH_DOWN) && bounds_.Contains(input.x, input.y)) {105pointerDownMask_ |= 1 << input.id;106usedPointerMask |= 1 << input.id;107}108if (input.flags & TOUCH_MOVE) {109if (bounds_.Contains(input.x, input.y) && !(analogPointerMask & (1 << input.id)))110pointerDownMask_ |= 1 << input.id;111else112pointerDownMask_ &= ~(1 << input.id);113}114if (input.flags & TOUCH_UP) {115pointerDownMask_ &= ~(1 << input.id);116usedPointerMask &= ~(1 << input.id);117}118if (input.flags & TOUCH_RELEASE_ALL) {119pointerDownMask_ = 0;120usedPointerMask = 0;121}122return retval;123}124125void MultiTouchButton::Draw(UIContext &dc) {126float opacity = g_gamepadOpacity;127if (opacity <= 0.0f)128return;129130float scale = scale_;131if (IsDown()) {132if (g_Config.iTouchButtonStyle == 2) {133opacity *= 1.35f;134} else {135scale *= TOUCH_SCALE_FACTOR;136opacity *= 1.15f;137}138}139140uint32_t colorBg = colorAlpha(GetButtonColor(), opacity);141uint32_t downBg = colorAlpha(0xFFFFFF, opacity * 0.5f);142uint32_t color = colorAlpha(0xFFFFFF, opacity);143144if (IsDown() && g_Config.iTouchButtonStyle == 2) {145if (bgImg_ != bgDownImg_)146dc.Draw()->DrawImageRotated(bgDownImg_, bounds_.centerX(), bounds_.centerY(), scale, bgAngle_ * (M_PI * 2 / 360.0f), downBg, flipImageH_);147}148149dc.Draw()->DrawImageRotated(bgImg_, bounds_.centerX(), bounds_.centerY(), scale, bgAngle_ * (M_PI * 2 / 360.0f), colorBg, flipImageH_);150151int y = bounds_.centerY();152// Hack round the fact that the center of the rectangular picture the triangle is contained in153// is not at the "weight center" of the triangle.154if (img_ == ImageID("I_TRIANGLE"))155y -= 2.8f * scale;156dc.Draw()->DrawImageRotated(img_, bounds_.centerX(), y, scale, angle_ * (M_PI * 2 / 360.0f), color);157}158159bool BoolButton::Touch(const TouchInput &input) {160bool lastDown = pointerDownMask_ != 0;161bool retval = MultiTouchButton::Touch(input);162bool down = pointerDownMask_ != 0;163164if (down != lastDown) {165*value_ = down;166UI::EventParams params{ this };167params.a = down;168OnChange.Trigger(params);169}170return retval;171}172173bool PSPButton::Touch(const TouchInput &input) {174bool lastDown = pointerDownMask_ != 0;175bool retval = MultiTouchButton::Touch(input);176bool down = pointerDownMask_ != 0;177if (down && !lastDown) {178if (g_Config.bHapticFeedback) {179System_Vibrate(HAPTIC_VIRTUAL_KEY);180}181__CtrlUpdateButtons(pspButtonBit_, 0);182} else if (lastDown && !down) {183__CtrlUpdateButtons(0, pspButtonBit_);184}185return retval;186}187188bool CustomButton::IsDown() {189return (toggle_ && on_) || (!toggle_ && pointerDownMask_ != 0);190}191192void CustomButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const {193MultiTouchButton::GetContentDimensions(dc, w, h);194if (invertedContextDimension_) {195float tmp = w;196w = h;197h = tmp;198}199}200201bool CustomButton::Touch(const TouchInput &input) {202using namespace CustomKeyData;203bool lastDown = pointerDownMask_ != 0;204bool retval = MultiTouchButton::Touch(input);205bool down = pointerDownMask_ != 0;206207if (down && !lastDown) {208if (g_Config.bHapticFeedback)209System_Vibrate(HAPTIC_VIRTUAL_KEY);210211if (!repeat_) {212for (int i = 0; i < ARRAY_SIZE(customKeyList); i++) {213if (pspButtonBit_ & (1ULL << i)) {214controlMapper_->PSPKey(DEVICE_ID_TOUCH, customKeyList[i].c, (on_ && toggle_) ? KEY_UP : KEY_DOWN);215}216}217}218on_ = toggle_ ? !on_ : true;219} else if (!toggle_ && lastDown && !down) {220if (!repeat_) {221for (int i = 0; i < ARRAY_SIZE(customKeyList); i++) {222if (pspButtonBit_ & (1ULL << i)) {223controlMapper_->PSPKey(DEVICE_ID_TOUCH, customKeyList[i].c, KEY_UP);224}225}226}227on_ = false;228}229return retval;230}231232void CustomButton::Update() {233MultiTouchButton::Update();234using namespace CustomKeyData;235236if (repeat_) {237// Give the game some time to process the input, frame based so it's faster when fast-forwarding.238static constexpr int DOWN_FRAME = 5;239240if (pressedFrames_ == 2*DOWN_FRAME) {241pressedFrames_ = 0;242} else if (pressedFrames_ == DOWN_FRAME) {243for (int i = 0; i < ARRAY_SIZE(customKeyList); i++) {244if (pspButtonBit_ & (1ULL << i)) {245controlMapper_->PSPKey(DEVICE_ID_TOUCH, customKeyList[i].c, KEY_UP);246}247}248} else if (on_ && pressedFrames_ == 0) {249for (int i = 0; i < ARRAY_SIZE(customKeyList); i++) {250if (pspButtonBit_ & (1ULL << i)) {251controlMapper_->PSPKey(DEVICE_ID_TOUCH, customKeyList[i].c, KEY_DOWN);252}253}254pressedFrames_ = 1;255}256257if (pressedFrames_ > 0)258pressedFrames_++;259}260}261262bool PSPButton::IsDown() {263return (__CtrlPeekButtonsVisual() & pspButtonBit_) != 0;264}265266PSPDpad::PSPDpad(ImageID arrowIndex, const char *key, ImageID arrowDownIndex, ImageID overlayIndex, float scale, float spacing, UI::LayoutParams *layoutParams)267: GamepadView(key, layoutParams), arrowIndex_(arrowIndex), arrowDownIndex_(arrowDownIndex), overlayIndex_(overlayIndex),268scale_(scale), spacing_(spacing), dragPointerId_(-1), down_(0) {269}270271void PSPDpad::GetContentDimensions(const UIContext &dc, float &w, float &h) const {272const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(arrowIndex_);273if (image) {274w = 2.0f * D_pad_Radius * spacing_ + image->w * scale_;275h = w;276} else {277w = 0.0f;278h = 0.0f;279}280}281282bool PSPDpad::Touch(const TouchInput &input) {283bool retval = GamepadView::Touch(input);284285if (input.flags & TOUCH_DOWN) {286if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y)) {287dragPointerId_ = input.id;288usedPointerMask |= 1 << input.id;289ProcessTouch(input.x, input.y, true);290}291}292if (input.flags & TOUCH_MOVE) {293if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y) && !(analogPointerMask & (1 << input.id))) {294dragPointerId_ = input.id;295}296if (input.id == dragPointerId_) {297ProcessTouch(input.x, input.y, true);298}299}300if (input.flags & TOUCH_UP) {301if (input.id == dragPointerId_) {302dragPointerId_ = -1;303usedPointerMask &= ~(1 << input.id);304ProcessTouch(input.x, input.y, false);305}306}307return retval;308}309310void PSPDpad::ProcessTouch(float x, float y, bool down) {311float stick_size = bounds_.w;312float inv_stick_size = 1.0f / stick_size;313const float deadzone = 0.05f;314315float dx = (x - bounds_.centerX()) * inv_stick_size;316float dy = (y - bounds_.centerY()) * inv_stick_size;317float rad = sqrtf(dx * dx + dy * dy);318if (!g_Config.bStickyTouchDPad && (rad < deadzone || fabs(dx) > 0.5f || fabs(dy) > 0.5))319down = false;320321int ctrlMask = 0;322bool fourWay = g_Config.bDisableDpadDiagonals || rad < 0.2f;323if (down) {324if (fourWay) {325int direction = (int)(floorf((atan2f(dy, dx) / (2 * M_PI) * 4) + 0.5f)) & 3;326switch (direction) {327case 0: ctrlMask = CTRL_RIGHT; break;328case 1: ctrlMask = CTRL_DOWN; break;329case 2: ctrlMask = CTRL_LEFT; break;330case 3: ctrlMask = CTRL_UP; break;331}332// 4 way pad333} else {334// 8 way pad335int direction = (int)(floorf((atan2f(dy, dx) / (2 * M_PI) * 8) + 0.5f)) & 7;336switch (direction) {337case 0: ctrlMask = CTRL_RIGHT; break;338case 1: ctrlMask = CTRL_RIGHT | CTRL_DOWN; break;339case 2: ctrlMask = CTRL_DOWN; break;340case 3: ctrlMask = CTRL_DOWN | CTRL_LEFT; break;341case 4: ctrlMask = CTRL_LEFT; break;342case 5: ctrlMask = CTRL_UP | CTRL_LEFT; break;343case 6: ctrlMask = CTRL_UP; break;344case 7: ctrlMask = CTRL_UP | CTRL_RIGHT; break;345}346}347}348349int lastDown = down_;350int pressed = ctrlMask & ~lastDown;351int released = (~ctrlMask) & lastDown;352down_ = ctrlMask;353bool vibrate = false;354static const int dir[4] = { CTRL_RIGHT, CTRL_DOWN, CTRL_LEFT, CTRL_UP };355for (int i = 0; i < 4; i++) {356if (pressed & dir[i]) {357vibrate = true;358__CtrlUpdateButtons(dir[i], 0);359}360if (released & dir[i]) {361__CtrlUpdateButtons(0, dir[i]);362}363}364if (vibrate && g_Config.bHapticFeedback) {365System_Vibrate(HAPTIC_VIRTUAL_KEY);366}367}368369void PSPDpad::Draw(UIContext &dc) {370float opacity = g_gamepadOpacity;371if (opacity <= 0.0f)372return;373374static const float xoff[4] = {1, 0, -1, 0};375static const float yoff[4] = {0, 1, 0, -1};376static const int dir[4] = {CTRL_RIGHT, CTRL_DOWN, CTRL_LEFT, CTRL_UP};377int buttons = __CtrlPeekButtons();378float r = D_pad_Radius * spacing_;379for (int i = 0; i < 4; i++) {380bool isDown = (buttons & dir[i]) != 0;381382float x = bounds_.centerX() + xoff[i] * r;383float y = bounds_.centerY() + yoff[i] * r;384float x2 = bounds_.centerX() + xoff[i] * (r + 10.f * scale_);385float y2 = bounds_.centerY() + yoff[i] * (r + 10.f * scale_);386float angle = i * (M_PI / 2.0f);387float imgScale = isDown ? scale_ * TOUCH_SCALE_FACTOR : scale_;388float imgOpacity = opacity;389390if (isDown && g_Config.iTouchButtonStyle == 2) {391imgScale = scale_;392imgOpacity *= 1.35f;393394uint32_t downBg = colorAlpha(0x00FFFFFF, imgOpacity * 0.5f);395if (arrowIndex_ != arrowDownIndex_)396dc.Draw()->DrawImageRotated(arrowDownIndex_, x, y, imgScale, angle + PI, downBg, false);397}398399uint32_t colorBg = colorAlpha(GetButtonColor(), imgOpacity);400uint32_t color = colorAlpha(0xFFFFFF, imgOpacity);401402dc.Draw()->DrawImageRotated(arrowIndex_, x, y, imgScale, angle + PI, colorBg, false);403if (overlayIndex_.isValid())404dc.Draw()->DrawImageRotated(overlayIndex_, x2, y2, imgScale, angle + PI, color);405}406}407408PSPStick::PSPStick(ImageID bgImg, const char *key, ImageID stickImg, ImageID stickDownImg, int stick, float scale, UI::LayoutParams *layoutParams)409: GamepadView(key, layoutParams), dragPointerId_(-1), bgImg_(bgImg), stickImageIndex_(stickImg), stickDownImg_(stickDownImg), stick_(stick), scale_(scale), centerX_(-1), centerY_(-1) {410stick_size_ = 50;411}412413void PSPStick::GetContentDimensions(const UIContext &dc, float &w, float &h) const {414dc.Draw()->GetAtlas()->measureImage(bgImg_, &w, &h);415w *= scale_;416h *= scale_;417}418419void PSPStick::Draw(UIContext &dc) {420float opacity = g_gamepadOpacity;421if (opacity <= 0.0f)422return;423424if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2) {425opacity *= 1.35f;426}427428uint32_t colorBg = colorAlpha(GetButtonColor(), opacity);429uint32_t downBg = colorAlpha(0x00FFFFFF, opacity * 0.5f);430431if (centerX_ < 0.0f) {432centerX_ = bounds_.centerX();433centerY_ = bounds_.centerY();434}435436float stickX = centerX_;437float stickY = centerY_;438439float dx, dy;440__CtrlPeekAnalog(stick_, &dx, &dy);441442if (!g_Config.bHideStickBackground)443dc.Draw()->DrawImage(bgImg_, stickX, stickY, 1.0f * scale_, colorBg, ALIGN_CENTER);444float headScale = stick_ ? g_Config.fRightStickHeadScale : g_Config.fLeftStickHeadScale;445if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2 && stickDownImg_ != stickImageIndex_)446dc.Draw()->DrawImage(stickDownImg_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, downBg, ALIGN_CENTER);447dc.Draw()->DrawImage(stickImageIndex_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, colorBg, ALIGN_CENTER);448}449450bool PSPStick::Touch(const TouchInput &input) {451bool retval = GamepadView::Touch(input);452if (input.flags & TOUCH_RELEASE_ALL) {453dragPointerId_ = -1;454centerX_ = bounds_.centerX();455centerY_ = bounds_.centerY();456__CtrlSetAnalogXY(stick_, 0.0f, 0.0f);457usedPointerMask = 0;458analogPointerMask = 0;459return retval;460}461if (input.flags & TOUCH_DOWN) {462float fac = 0.5f*(stick_ ? g_Config.fRightStickHeadScale : g_Config.fLeftStickHeadScale)-0.5f;463if (dragPointerId_ == -1 && bounds_.Expand(bounds_.w*fac, bounds_.h*fac).Contains(input.x, input.y)) {464if (g_Config.bAutoCenterTouchAnalog) {465centerX_ = input.x;466centerY_ = input.y;467} else {468centerX_ = bounds_.centerX();469centerY_ = bounds_.centerY();470}471dragPointerId_ = input.id;472usedPointerMask |= 1 << input.id;473analogPointerMask |= 1 << input.id;474ProcessTouch(input.x, input.y, true);475retval = true;476}477}478if (input.flags & TOUCH_MOVE) {479if (input.id == dragPointerId_) {480ProcessTouch(input.x, input.y, true);481retval = true;482}483}484if (input.flags & TOUCH_UP) {485if (input.id == dragPointerId_) {486dragPointerId_ = -1;487centerX_ = bounds_.centerX();488centerY_ = bounds_.centerY();489usedPointerMask &= ~(1 << input.id);490analogPointerMask &= ~(1 << input.id);491ProcessTouch(input.x, input.y, false);492retval = true;493}494}495return retval;496}497498void PSPStick::ProcessTouch(float x, float y, bool down) {499if (down && centerX_ >= 0.0f) {500float inv_stick_size = 1.0f / (stick_size_ * scale_);501502float dx = (x - centerX_) * inv_stick_size;503float dy = (y - centerY_) * inv_stick_size;504// Do not clamp to a circle! The PSP has nearly square range!505506// Old code to clamp to a circle507// float len = sqrtf(dx * dx + dy * dy);508// if (len > 1.0f) {509// dx /= len;510// dy /= len;511//}512513// Still need to clamp to a square514dx = std::min(1.0f, std::max(-1.0f, dx));515dy = std::min(1.0f, std::max(-1.0f, dy));516517__CtrlSetAnalogXY(stick_, dx, -dy);518} else {519__CtrlSetAnalogXY(stick_, 0.0f, 0.0f);520}521}522523PSPCustomStick::PSPCustomStick(ImageID bgImg, const char *key, ImageID stickImg, ImageID stickDownImg, int stick, float scale, UI::LayoutParams *layoutParams)524: PSPStick(bgImg, key, stickImg, stickDownImg, stick, scale, layoutParams) {525}526527void PSPCustomStick::Draw(UIContext &dc) {528float opacity = g_gamepadOpacity;529if (opacity <= 0.0f)530return;531532if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2) {533opacity *= 1.35f;534}535536uint32_t colorBg = colorAlpha(GetButtonColor(), opacity);537uint32_t downBg = colorAlpha(0x00FFFFFF, opacity * 0.5f);538539if (centerX_ < 0.0f) {540centerX_ = bounds_.centerX();541centerY_ = bounds_.centerY();542}543544float stickX = centerX_;545float stickY = centerY_;546547float dx, dy;548dx = posX_;549dy = -posY_;550551if (!g_Config.bHideStickBackground)552dc.Draw()->DrawImage(bgImg_, stickX, stickY, 1.0f * scale_, colorBg, ALIGN_CENTER);553if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2 && stickDownImg_ != stickImageIndex_)554dc.Draw()->DrawImage(stickDownImg_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f*scale_*g_Config.fRightStickHeadScale, downBg, ALIGN_CENTER);555dc.Draw()->DrawImage(stickImageIndex_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f*scale_*g_Config.fRightStickHeadScale, colorBg, ALIGN_CENTER);556}557558bool PSPCustomStick::Touch(const TouchInput &input) {559bool retval = GamepadView::Touch(input);560if (input.flags & TOUCH_RELEASE_ALL) {561dragPointerId_ = -1;562centerX_ = bounds_.centerX();563centerY_ = bounds_.centerY();564posX_ = 0.0f;565posY_ = 0.0f;566usedPointerMask = 0;567analogPointerMask = 0;568return false;569}570if (input.flags & TOUCH_DOWN) {571float fac = 0.5f*g_Config.fRightStickHeadScale-0.5f;572if (dragPointerId_ == -1 && bounds_.Expand(bounds_.w*fac, bounds_.h*fac).Contains(input.x, input.y)) {573if (g_Config.bAutoCenterTouchAnalog) {574centerX_ = input.x;575centerY_ = input.y;576} else {577centerX_ = bounds_.centerX();578centerY_ = bounds_.centerY();579}580dragPointerId_ = input.id;581usedPointerMask |= 1 << input.id;582analogPointerMask |= 1 << input.id;583ProcessTouch(input.x, input.y, true);584retval = true;585}586}587if (input.flags & TOUCH_MOVE) {588if (input.id == dragPointerId_) {589ProcessTouch(input.x, input.y, true);590retval = true;591}592}593if (input.flags & TOUCH_UP) {594if (input.id == dragPointerId_) {595dragPointerId_ = -1;596centerX_ = bounds_.centerX();597centerY_ = bounds_.centerY();598usedPointerMask &= ~(1 << input.id);599analogPointerMask &= ~(1 << input.id);600ProcessTouch(input.x, input.y, false);601retval = true;602}603}604return retval;605}606607void PSPCustomStick::ProcessTouch(float x, float y, bool down) {608static const int buttons[] = {0, CTRL_LTRIGGER, CTRL_RTRIGGER, CTRL_SQUARE, CTRL_TRIANGLE, CTRL_CIRCLE, CTRL_CROSS, CTRL_UP, CTRL_DOWN, CTRL_LEFT, CTRL_RIGHT, CTRL_START, CTRL_SELECT};609static const int analogs[] = {VIRTKEY_AXIS_RIGHT_Y_MAX, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, VIRTKEY_AXIS_Y_MAX, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX};610u32 press = 0;611u32 release = 0;612613auto toggle = [&](int config, bool simpleCheck, bool diagCheck = true) {614if (config <= 0 || (size_t)config >= ARRAY_SIZE(buttons))615return;616617if (simpleCheck && (!g_Config.bRightAnalogDisableDiagonal || diagCheck))618press |= buttons[config];619else620release |= buttons[config];621};622623auto analog = [&](float dx, float dy) {624if (g_Config.bRightAnalogDisableDiagonal) {625if (fabs(dx) > fabs(dy)) {626dy = 0.0f;627} else {628dx = 0.0f;629}630}631632auto assign = [&](int config, float value) {633if (config < ARRAY_SIZE(buttons) || config >= ARRAY_SIZE(buttons) + ARRAY_SIZE(analogs)) {634return;635}636switch(analogs[config - ARRAY_SIZE(buttons)]) {637case VIRTKEY_AXIS_Y_MAX:638__CtrlSetAnalogY(0, value);639break;640case VIRTKEY_AXIS_Y_MIN:641__CtrlSetAnalogY(0, value * -1.0f);642break;643case VIRTKEY_AXIS_X_MIN:644__CtrlSetAnalogX(0, value * -1.0f);645break;646case VIRTKEY_AXIS_X_MAX:647__CtrlSetAnalogX(0, value);648break;649case VIRTKEY_AXIS_RIGHT_Y_MAX:650__CtrlSetAnalogY(1, value);651break;652case VIRTKEY_AXIS_RIGHT_Y_MIN:653__CtrlSetAnalogY(1, value * -1.0f);654break;655case VIRTKEY_AXIS_RIGHT_X_MIN:656__CtrlSetAnalogX(1, value * -1.0f);657break;658case VIRTKEY_AXIS_RIGHT_X_MAX:659__CtrlSetAnalogX(1, value);660break;661}662};663664// if/when we ever get iLeftAnalog settings, check stick_ for the config to use665// let 0.0f through during centering666if (dy >= 0.0f) {667// down668assign(g_Config.iRightAnalogUp, 0.0f);669assign(g_Config.iRightAnalogDown, dy);670}671if (dy <= 0.0f) {672// up673assign(g_Config.iRightAnalogDown, 0.0f);674assign(g_Config.iRightAnalogUp, dy * -1.0f);675}676if (dx <= 0.0f) {677// left678assign(g_Config.iRightAnalogRight, 0.0f);679assign(g_Config.iRightAnalogLeft, dx * -1.0f);680}681if (dx >= 0.0f) {682// right683assign(g_Config.iRightAnalogLeft, 0.0f);684assign(g_Config.iRightAnalogRight, dx);685}686};687688if (down && centerX_ >= 0.0f) {689float inv_stick_size = 1.0f / (stick_size_ * scale_);690691float dx = (x - centerX_) * inv_stick_size;692float dy = (y - centerY_) * inv_stick_size;693694dx = std::min(1.0f, std::max(-1.0f, dx));695dy = std::min(1.0f, std::max(-1.0f, dy));696697toggle(g_Config.iRightAnalogRight, dx > 0.5f, fabs(dx) > fabs(dy));698toggle(g_Config.iRightAnalogLeft, dx < -0.5f, fabs(dx) > fabs(dy));699toggle(g_Config.iRightAnalogUp, dy < -0.5f, fabs(dx) <= fabs(dy));700toggle(g_Config.iRightAnalogDown, dy > 0.5f, fabs(dx) <= fabs(dy));701toggle(g_Config.iRightAnalogPress, true);702703analog(dx, dy);704705posX_ = dx;706posY_ = dy;707708} else {709toggle(g_Config.iRightAnalogRight, false);710toggle(g_Config.iRightAnalogLeft, false);711toggle(g_Config.iRightAnalogUp, false);712toggle(g_Config.iRightAnalogDown, false);713toggle(g_Config.iRightAnalogPress, false);714715analog(0.0f, 0.0f);716717posX_ = 0.0f;718posY_ = 0.0f;719}720721if (release || press) {722__CtrlUpdateButtons(press, release);723}724}725726void InitPadLayout(float xres, float yres, float globalScale) {727const float scale = globalScale;728const int halfW = xres / 2;729730auto initTouchPos = [=](ConfigTouchPos &touch, float x, float y) {731if (touch.x == -1.0f || touch.y == -1.0f) {732touch.x = x / xres;733touch.y = std::max(y, 20.0f * globalScale) / yres;734touch.scale = scale;735}736};737738// PSP buttons (triangle, circle, square, cross)---------------------739// space between the PSP buttons (triangle, circle, square and cross)740if (g_Config.fActionButtonSpacing < 0) {741g_Config.fActionButtonSpacing = 1.0f;742}743744// Position of the circle button (the PSP circle button). It is the farthest to the left745float Action_button_spacing = g_Config.fActionButtonSpacing * baseActionButtonSpacing;746int Action_button_center_X = xres - Action_button_spacing * 2;747int Action_button_center_Y = yres - Action_button_spacing * 2;748if (g_Config.touchRightAnalogStick.show) {749Action_button_center_Y -= 150 * scale;750}751initTouchPos(g_Config.touchActionButtonCenter, Action_button_center_X, Action_button_center_Y);752753//D-PAD (up down left right) (aka PSP cross)----------------------------754//radius to the D-pad755// TODO: Make configurable756757int D_pad_X = 2.5 * D_pad_Radius * scale;758int D_pad_Y = yres - D_pad_Radius * scale;759if (g_Config.touchAnalogStick.show) {760D_pad_Y -= 200 * scale;761}762initTouchPos(g_Config.touchDpad, D_pad_X, D_pad_Y);763764//analog stick-------------------------------------------------------765//keep the analog stick right below the D pad766int analog_stick_X = D_pad_X;767int analog_stick_Y = yres - 80 * scale;768initTouchPos(g_Config.touchAnalogStick, analog_stick_X, analog_stick_Y);769770//right analog stick-------------------------------------------------771//keep the right analog stick right below the face buttons772int right_analog_stick_X = Action_button_center_X;773int right_analog_stick_Y = yres - 80 * scale;774initTouchPos(g_Config.touchRightAnalogStick, right_analog_stick_X, right_analog_stick_Y);775776//select, start, throttle--------------------------------------------777//space between the bottom keys (space between select, start and un-throttle)778float bottom_key_spacing = 100;779if (g_display.dp_xres < 750) {780bottom_key_spacing *= 0.8f;781}782783// On IOS, nudge the bottom button up a little to avoid the task switcher.784#if PPSSPP_PLATFORM(IOS)785const float bottom_button_Y = 80.0f;786#else787const float bottom_button_Y = 60.0f;788#endif789790int start_key_X = halfW + bottom_key_spacing * scale;791int start_key_Y = yres - bottom_button_Y * scale;792initTouchPos(g_Config.touchStartKey, start_key_X, start_key_Y);793794int select_key_X = halfW;795int select_key_Y = yres - bottom_button_Y * scale;796initTouchPos(g_Config.touchSelectKey, select_key_X, select_key_Y);797798int fast_forward_key_X = halfW - bottom_key_spacing * scale;799int fast_forward_key_Y = yres - bottom_button_Y * scale;800initTouchPos(g_Config.touchFastForwardKey, fast_forward_key_X, fast_forward_key_Y);801802// L and R------------------------------------------------------------803// Put them above the analog stick / above the buttons to the right.804// The corners were very hard to reach..805806int l_key_X = 60 * scale;807int l_key_Y = yres - 380 * scale;808initTouchPos(g_Config.touchLKey, l_key_X, l_key_Y);809810int r_key_X = xres - 60 * scale;811int r_key_Y = l_key_Y;812initTouchPos(g_Config.touchRKey, r_key_X, r_key_Y);813814struct { float x; float y; } customButtonPositions[10] = {815{ 1.2f, 0.5f },816{ 2.2f, 0.5f },817{ 3.2f, 0.5f },818{ 1.2f, 0.333f },819{ 2.2f, 0.333f },820{ -1.2f, 0.5f },821{ -2.2f, 0.5f },822{ -3.2f, 0.5f },823{ -1.2f, 0.333f },824{ -2.2f, 0.333f },825};826827for (int i = 0; i < Config::CUSTOM_BUTTON_COUNT; i++) {828float y_offset = (float)(i / 10) * 0.08333f;829830int combo_key_X = halfW + bottom_key_spacing * scale * customButtonPositions[i % 10].x;831int combo_key_Y = yres * (y_offset + customButtonPositions[i % 10].y);832833initTouchPos(g_Config.touchCustom[i], combo_key_X, combo_key_Y);834}835}836837UI::ViewGroup *CreatePadLayout(float xres, float yres, bool *pause, bool showPauseButton, ControlMapper* controllMapper) {838using namespace UI;839840AnchorLayout *root = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));841if (!g_Config.bShowTouchControls) {842return root;843}844845struct ButtonOffset {846float x;847float y;848};849auto buttonLayoutParams = [=](const ConfigTouchPos &touch, ButtonOffset off = { 0, 0 }) {850return new AnchorLayoutParams(touch.x * xres + off.x, touch.y * yres + off.y, NONE, NONE, true);851};852853// Space between the PSP buttons (traingle, circle, square and cross)854const float actionButtonSpacing = g_Config.fActionButtonSpacing * baseActionButtonSpacing;855// Position of the circle button (the PSP circle button). It is the farthest to the right.856ButtonOffset circleOffset{ actionButtonSpacing, 0.0f };857ButtonOffset crossOffset{ 0.0f, actionButtonSpacing };858ButtonOffset triangleOffset{ 0.0f, -actionButtonSpacing };859ButtonOffset squareOffset{ -actionButtonSpacing, 0.0f };860861const int halfW = xres / 2;862863const ImageID roundImage = g_Config.iTouchButtonStyle ? ImageID("I_ROUND_LINE") : ImageID("I_ROUND");864const ImageID rectImage = g_Config.iTouchButtonStyle ? ImageID("I_RECT_LINE") : ImageID("I_RECT");865const ImageID shoulderImage = g_Config.iTouchButtonStyle ? ImageID("I_SHOULDER_LINE") : ImageID("I_SHOULDER");866const ImageID stickImage = g_Config.iTouchButtonStyle ? ImageID("I_STICK_LINE") : ImageID("I_STICK");867const ImageID stickBg = g_Config.iTouchButtonStyle ? ImageID("I_STICK_BG_LINE") : ImageID("I_STICK_BG");868869auto addPSPButton = [=](int buttonBit, const char *key, ImageID bgImg, ImageID bgDownImg, ImageID img, const ConfigTouchPos &touch, ButtonOffset off = { 0, 0 }) -> PSPButton * {870if (touch.show) {871return root->Add(new PSPButton(buttonBit, key, bgImg, bgDownImg, img, touch.scale, buttonLayoutParams(touch, off)));872}873return nullptr;874};875auto addBoolButton = [=](bool *value, const char *key, ImageID bgImg, ImageID bgDownImg, ImageID img, const ConfigTouchPos &touch) -> BoolButton * {876if (touch.show) {877return root->Add(new BoolButton(value, key, bgImg, bgDownImg, img, touch.scale, buttonLayoutParams(touch)));878}879return nullptr;880};881auto addCustomButton = [=](const ConfigCustomButton& cfg, const char *key, const ConfigTouchPos &touch) -> CustomButton * {882using namespace CustomKeyData;883if (touch.show) {884_dbg_assert_(cfg.shape < ARRAY_SIZE(customKeyShapes));885_dbg_assert_(cfg.image < ARRAY_SIZE(customKeyImages));886887// Note: cfg.shape and cfg.image are bounds-checked elsewhere.888auto aux = root->Add(new CustomButton(cfg.key, key, cfg.toggle, cfg.repeat, controllMapper,889g_Config.iTouchButtonStyle == 0 ? customKeyShapes[cfg.shape].i : customKeyShapes[cfg.shape].l, customKeyShapes[cfg.shape].i,890customKeyImages[cfg.image].i, touch.scale, customKeyShapes[cfg.shape].d, buttonLayoutParams(touch)));891aux->SetAngle(customKeyImages[cfg.image].r, customKeyShapes[cfg.shape].r);892aux->FlipImageH(customKeyShapes[cfg.shape].f);893return aux;894}895return nullptr;896};897898if (showPauseButton) {899root->Add(new BoolButton(pause, "Pause button", roundImage, ImageID("I_ROUND"), ImageID("I_ARROW"), 1.0f, new AnchorLayoutParams(halfW, 20, NONE, NONE, true)))->SetAngle(90);900}901902// touchActionButtonCenter.show will always be true, since that's the default.903if (g_Config.bShowTouchCircle)904addPSPButton(CTRL_CIRCLE, "Circle button", roundImage, ImageID("I_ROUND"), ImageID("I_CIRCLE"), g_Config.touchActionButtonCenter, circleOffset);905if (g_Config.bShowTouchCross)906addPSPButton(CTRL_CROSS, "Cross button", roundImage, ImageID("I_ROUND"), ImageID("I_CROSS"), g_Config.touchActionButtonCenter, crossOffset);907if (g_Config.bShowTouchTriangle)908addPSPButton(CTRL_TRIANGLE, "Triangle button", roundImage, ImageID("I_ROUND"), ImageID("I_TRIANGLE"), g_Config.touchActionButtonCenter, triangleOffset);909if (g_Config.bShowTouchSquare)910addPSPButton(CTRL_SQUARE, "Square button", roundImage, ImageID("I_ROUND"), ImageID("I_SQUARE"), g_Config.touchActionButtonCenter, squareOffset);911912addPSPButton(CTRL_START, "Start button", rectImage, ImageID("I_RECT"), ImageID("I_START"), g_Config.touchStartKey);913addPSPButton(CTRL_SELECT, "Select button", rectImage, ImageID("I_RECT"), ImageID("I_SELECT"), g_Config.touchSelectKey);914915BoolButton *fastForward = addBoolButton(&PSP_CoreParameter().fastForward, "Fast-forward button", rectImage, ImageID("I_RECT"), ImageID("I_ARROW"), g_Config.touchFastForwardKey);916if (fastForward) {917fastForward->SetAngle(180.0f);918fastForward->OnChange.Add([](UI::EventParams &e) {919if (e.a && coreState == CORE_STEPPING) {920Core_EnableStepping(false);921}922return UI::EVENT_DONE;923});924}925926addPSPButton(CTRL_LTRIGGER, "Left shoulder button", shoulderImage, ImageID("I_SHOULDER"), ImageID("I_L"), g_Config.touchLKey);927PSPButton *rTrigger = addPSPButton(CTRL_RTRIGGER, "Right shoulder button", shoulderImage, ImageID("I_SHOULDER"), ImageID("I_R"), g_Config.touchRKey);928if (rTrigger)929rTrigger->FlipImageH(true);930931if (g_Config.touchDpad.show) {932const ImageID dirImage = g_Config.iTouchButtonStyle ? ImageID("I_DIR_LINE") : ImageID("I_DIR");933root->Add(new PSPDpad(dirImage, "D-pad", ImageID("I_DIR"), ImageID("I_ARROW"), g_Config.touchDpad.scale, g_Config.fDpadSpacing, buttonLayoutParams(g_Config.touchDpad)));934}935936if (g_Config.touchAnalogStick.show)937root->Add(new PSPStick(stickBg, "Left analog stick", stickImage, ImageID("I_STICK"), 0, g_Config.touchAnalogStick.scale, buttonLayoutParams(g_Config.touchAnalogStick)));938939if (g_Config.touchRightAnalogStick.show) {940if (g_Config.bRightAnalogCustom)941root->Add(new PSPCustomStick(stickBg, "Right analog stick", stickImage, ImageID("I_STICK"), 1, g_Config.touchRightAnalogStick.scale, buttonLayoutParams(g_Config.touchRightAnalogStick)));942else943root->Add(new PSPStick(stickBg, "Right analog stick", stickImage, ImageID("I_STICK"), 1, g_Config.touchRightAnalogStick.scale, buttonLayoutParams(g_Config.touchRightAnalogStick)));944}945946// Sanitize custom button images, while adding them.947for (int i = 0; i < Config::CUSTOM_BUTTON_COUNT; i++) {948if (g_Config.CustomButton[i].shape >= ARRAY_SIZE(CustomKeyData::customKeyShapes)) {949g_Config.CustomButton[i].shape = 0;950}951if (g_Config.CustomButton[i].image >= ARRAY_SIZE(CustomKeyData::customKeyImages)) {952g_Config.CustomButton[i].image = 0;953}954955char temp[64];956snprintf(temp, sizeof(temp), "Custom %d button", i + 1);957addCustomButton(g_Config.CustomButton[i], temp, g_Config.touchCustom[i]);958}959960if (g_Config.bGestureControlEnabled)961root->Add(new GestureGamepad(controllMapper));962963return root;964}965966bool GestureGamepad::Touch(const TouchInput &input) {967if (usedPointerMask & (1 << input.id)) {968if (input.id == dragPointerId_)969dragPointerId_ = -1;970return false;971}972973if (input.flags & TOUCH_RELEASE_ALL) {974dragPointerId_ = -1;975return false;976}977978if (input.flags & TOUCH_DOWN) {979if (dragPointerId_ == -1) {980dragPointerId_ = input.id;981lastX_ = input.x;982lastY_ = input.y;983downX_ = input.x;984downY_ = input.y;985const float now = time_now_d();986if (now - lastTapRelease_ < 0.3f && !haveDoubleTapped_) {987if (g_Config.iDoubleTapGesture != 0 )988controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iDoubleTapGesture-1], KEY_DOWN);989haveDoubleTapped_ = true;990}991992lastTouchDown_ = now;993}994}995if (input.flags & TOUCH_MOVE) {996if (input.id == dragPointerId_) {997deltaX_ += input.x - lastX_;998deltaY_ += input.y - lastY_;999lastX_ = input.x;1000lastY_ = input.y;10011002if (g_Config.bAnalogGesture) {1003const float k = g_Config.fAnalogGestureSensibility * 0.02;1004float dx = (input.x - downX_)*g_display.dpi_scale_x * k;1005float dy = (input.y - downY_)*g_display.dpi_scale_y * k;1006dx = std::min(1.0f, std::max(-1.0f, dx));1007dy = std::min(1.0f, std::max(-1.0f, dy));1008__CtrlSetAnalogXY(0, dx, -dy);1009}1010}1011}1012if (input.flags & TOUCH_UP) {1013if (input.id == dragPointerId_) {1014dragPointerId_ = -1;1015if (time_now_d() - lastTouchDown_ < 0.3f)1016lastTapRelease_ = time_now_d();10171018if (haveDoubleTapped_) {1019if (g_Config.iDoubleTapGesture != 0)1020controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iDoubleTapGesture-1], KEY_UP);1021haveDoubleTapped_ = false;1022}10231024if (g_Config.bAnalogGesture)1025__CtrlSetAnalogXY(0, 0, 0);1026}1027}1028return true;1029}10301031void GestureGamepad::Draw(UIContext &dc) {1032float opacity = g_Config.iTouchButtonOpacity / 100.0;1033if (opacity <= 0.0f)1034return;10351036uint32_t colorBg = colorAlpha(GetButtonColor(), opacity);10371038if (g_Config.bAnalogGesture && dragPointerId_ != -1) {1039dc.Draw()->DrawImage(ImageID("I_CIRCLE"), downX_, downY_, 0.7f, colorBg, ALIGN_CENTER);1040}1041}10421043void GestureGamepad::Update() {1044const float th = 1.0f;1045float dx = deltaX_ * g_display.dpi_scale_x * g_Config.fSwipeSensitivity;1046float dy = deltaY_ * g_display.dpi_scale_y * g_Config.fSwipeSensitivity;1047if (g_Config.iSwipeRight != 0) {1048if (dx > th) {1049controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeRight-1], KEY_DOWN);1050swipeRightReleased_ = false;1051} else if (!swipeRightReleased_) {1052controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeRight-1], KEY_UP);1053swipeRightReleased_ = true;1054}1055}1056if (g_Config.iSwipeLeft != 0) {1057if (dx < -th) {1058controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeLeft-1], KEY_DOWN);1059swipeLeftReleased_ = false;1060} else if (!swipeLeftReleased_) {1061controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeLeft-1], KEY_UP);1062swipeLeftReleased_ = true;1063}1064}1065if (g_Config.iSwipeUp != 0) {1066if (dy < -th) {1067controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeUp-1], KEY_DOWN);1068swipeUpReleased_ = false;1069} else if (!swipeUpReleased_) {1070controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeUp-1], KEY_UP);1071swipeUpReleased_ = true;1072}1073}1074if (g_Config.iSwipeDown != 0) {1075if (dy > th) {1076controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeDown-1], KEY_DOWN);1077swipeDownReleased_ = false;1078} else if (!swipeDownReleased_) {1079controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[g_Config.iSwipeDown-1], KEY_UP);1080swipeDownReleased_ = true;1081}1082}1083deltaX_ *= g_Config.fSwipeSmoothing;1084deltaY_ *= g_Config.fSwipeSmoothing;1085}108610871088