// Copyright (c) 2012- PPSSPP Project. // 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, version 2.0 or later versions. // 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 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include #include "Common/Data/Color/RGBAUtil.h" #include "Common/Data/Text/I18n.h" #include "Common/System/Display.h" #include "Common/System/System.h" #include "Common/Render/TextureAtlas.h" #include "Common/Math/math_util.h" #include "Common/UI/Context.h" #include "Common/Log.h" #include "Common/TimeUtil.h" #include "Core/Config.h" #include "Core/Core.h" #include "Core/System.h" #include "Core/HLE/sceCtrl.h" #include "Core/ControlMapper.h" #include "UI/GamepadEmu.h" const float TOUCH_SCALE_FACTOR = 1.5f; static uint32_t usedPointerMask = 0; static uint32_t analogPointerMask = 0; static float g_gamepadOpacity; static double g_lastTouch; MultiTouchButton *primaryButton[TOUCH_MAX_POINTERS]{}; std::set g_activeGesturePointers; void GamepadUpdateOpacity(float force) { if (force >= 0.0f) { g_gamepadOpacity = force; return; } if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_POWERDOWN || coreState == CORE_STEPPING_GE) { // No need to show the controls. g_gamepadOpacity = 0.0f; return; } float fadeAfterSeconds = g_Config.iTouchButtonHideSeconds; float fadeTransitionSeconds = std::min(fadeAfterSeconds, 0.5f); float opacity = g_Config.iTouchButtonOpacity / 100.0f; float multiplier = 1.0f; float secondsWithoutTouch = time_now_d() - g_lastTouch; if (secondsWithoutTouch >= fadeAfterSeconds && fadeAfterSeconds > 0.0f) { if (secondsWithoutTouch >= fadeAfterSeconds + fadeTransitionSeconds) { multiplier = 0.0f; } else { float secondsIntoFade = secondsWithoutTouch - fadeAfterSeconds; multiplier = 1.0f - (secondsIntoFade / fadeTransitionSeconds); } } g_gamepadOpacity = opacity * multiplier; } void GamepadResetTouch() { g_lastTouch = 0.0f; } void GamepadTouch() { g_lastTouch = time_now_d(); } float GamepadGetOpacity() { return g_gamepadOpacity; } static u32 GetButtonColor() { return g_Config.iTouchButtonStyle != 0 ? 0xFFFFFF : 0xc0b080; } GamepadComponent::GamepadComponent(std::string_view key, UI::LayoutParams *layoutParams) : UI::View(layoutParams), key_(key) {} static void rotateTouchHelper(float &dx, float &dy) { // rotates dx and dy depending on rotation count // modified from controlmapper.cpp if (!g_Config.displayLayoutLandscape.bRotateControlsWithScreen) { return; } int rotations = 0; switch (g_Config.displayLayoutLandscape.iInternalScreenRotation) { case ROTATION_LOCKED_HORIZONTAL180: rotations = 2; break; case ROTATION_LOCKED_VERTICAL: rotations = 3; break; case ROTATION_LOCKED_VERTICAL180: rotations = 1; break; } for (int i = 0; i < rotations; i ++) { float tempVal = dx; dx = - dy; dy = tempVal; } } std::string GamepadComponent::DescribeText() const { return key_; } void MultiTouchButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const { const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(bgImg_); if (image) { w = image->w * scale_; h = image->h * scale_; } else { w = 0.0f; h = 0.0f; } } bool MultiTouchButton::CanGlide() const { return g_Config.bTouchGliding; } bool MultiTouchButton::Touch(const TouchInput &input) { _dbg_assert_(input.id >= 0 && input.id < TOUCH_MAX_POINTERS); bool retval = GamepadComponent::Touch(input); if ((input.flags & TouchInputFlags::DOWN) && bounds_.Contains(input.x, input.y)) { pointerDownMask_ |= 1 << input.id; usedPointerMask |= 1 << input.id; if (CanGlide() && !primaryButton[input.id]) primaryButton[input.id] = this; } if (input.flags & TouchInputFlags::MOVE) { if (!(input.flags & TouchInputFlags::MOUSE) || input.buttons) { bool ignorePress = false; if (g_activeGesturePointers.count(input.id)) { // Don't start a button touch if dragging and this pointer is already being used for a gesture. ignorePress = true; } if (bounds_.Contains(input.x, input.y) && !(analogPointerMask & (1 << input.id))) { if (!ignorePress) { if (CanGlide() && !primaryButton[input.id]) { primaryButton[input.id] = this; } pointerDownMask_ |= 1 << input.id; } } else if (primaryButton[input.id] != this) { pointerDownMask_ &= ~(1 << input.id); } } } if (input.flags & TouchInputFlags::UP) { pointerDownMask_ &= ~(1 << input.id); usedPointerMask &= ~(1 << input.id); primaryButton[input.id] = nullptr; } if (input.flags & TouchInputFlags::RELEASE_ALL) { pointerDownMask_ = 0; usedPointerMask = 0; } return retval; } void MultiTouchButton::Draw(UIContext &dc) { float opacity = std::max(g_gamepadOpacity, minimumAlpha_); if (opacity <= 0.0f) return; float scale = scale_; if (IsDownVisually()) { if (g_Config.iTouchButtonStyle == 2) { opacity *= 1.35f; } else { scale *= TOUCH_SCALE_FACTOR; opacity *= 1.15f; } } uint32_t colorBg = colorAlpha(GetButtonColor(), opacity); uint32_t downBg = colorAlpha(0xFFFFFF, opacity * 0.5f); uint32_t color = colorAlpha(0xFFFFFF, opacity); if (IsDownVisually() && g_Config.iTouchButtonStyle == 2) { if (bgImg_ != bgDownImg_) dc.Draw()->DrawImageRotated(bgDownImg_, bounds_.centerX(), bounds_.centerY(), scale, bgAngle_ * (M_PI * 2 / 360.0f), downBg, flipImageH_); } dc.Draw()->DrawImageRotated(bgImg_, bounds_.centerX(), bounds_.centerY(), scale, bgAngle_ * (M_PI * 2 / 360.0f), colorBg, flipImageH_); float x = bounds_.centerX(); float y = bounds_.centerY(); // Hack round the fact that the center of the rectangular picture the triangle is contained in // is not at the "weight center" of the triangle. Same for fast-forward. if (img_ == ImageID("I_TRIANGLE")) y -= 2.8f * scale; if (img_ == ImageID("I_FAST_FORWARD_LINE")) x += 2.0f * scale; dc.Draw()->DrawImageRotated(img_, x, y, scale, angle_ * (M_PI * 2 / 360.0f), color); } bool BoolButton::Touch(const TouchInput &input) { bool lastDown = pointerDownMask_ != 0; bool retval = MultiTouchButton::Touch(input); bool down = pointerDownMask_ != 0; if (down != lastDown) { *value_ = down; UI::EventParams params{ this }; params.a = down; OnChange.Trigger(params); } return retval; } bool PSPButton::Touch(const TouchInput &input) { bool lastDown = pointerDownMask_ != 0; bool retval = MultiTouchButton::Touch(input); bool down = pointerDownMask_ != 0; if (down && !lastDown) { if (g_Config.bHapticFeedback) { System_Vibrate(HAPTIC_VIRTUAL_KEY); } __CtrlUpdateButtons(pspButtonBit_, 0); } else if (lastDown && !down) { __CtrlUpdateButtons(0, pspButtonBit_); } return retval; } bool CustomButton::IsDownVisually() const { return (toggle_ && on_) || (!toggle_ && pointerDownMask_ != 0); } bool CustomButton::IsDownByTouch() const { // This check is due to a stupid mistake, see the header. #ifdef MOBILE_DEVICE constexpr int bitNumber = 36; #else constexpr int bitNumber = 38; #endif if (pspButtonBit_ & (1ULL << bitNumber)) { return false; } // VIRTKEY_TOGGLE_TOUCH_CONTROLS from g_customKeyList return !toggle_ && pointerDownMask_ != 0; } void CustomButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const { MultiTouchButton::GetContentDimensions(dc, w, h); if (invertedContentDimension_) { float tmp = w; w = h; h = tmp; } } bool CustomButton::Touch(const TouchInput &input) { using namespace CustomKeyData; bool lastDown = pointerDownMask_ != 0; bool retval = MultiTouchButton::Touch(input); bool down = pointerDownMask_ != 0; if (down && !lastDown) { if (g_Config.bHapticFeedback) System_Vibrate(HAPTIC_VIRTUAL_KEY); if (!repeat_) { for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) { if (pspButtonBit_ & (1ULL << i)) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, g_customKeyList[i].c, (on_ && toggle_) ? KeyInputFlags::UP : KeyInputFlags::DOWN); } } } on_ = toggle_ ? !on_ : true; } else if (!toggle_ && lastDown && !down) { if (!repeat_) { for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) { if (pspButtonBit_ & (1ULL << i)) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, g_customKeyList[i].c, KeyInputFlags::UP); } } } on_ = false; } return retval; } void CustomButton::Update() { MultiTouchButton::Update(); using namespace CustomKeyData; if (repeat_) { // Give the game some time to process the input, frame based so it's faster when fast-forwarding. static constexpr int DOWN_FRAME = 5; if (pressedFrames_ == 2*DOWN_FRAME) { pressedFrames_ = 0; } else if (pressedFrames_ == DOWN_FRAME) { for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) { if (pspButtonBit_ & (1ULL << i)) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, g_customKeyList[i].c, KeyInputFlags::UP); } } } else if (on_ && pressedFrames_ == 0) { for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) { if (pspButtonBit_ & (1ULL << i)) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, g_customKeyList[i].c, KeyInputFlags::DOWN); } } pressedFrames_ = 1; } if (pressedFrames_ > 0) pressedFrames_++; } } bool PSPButton::IsDownVisually() const { return (__CtrlPeekButtonsVisual() & pspButtonBit_) != 0; } PSPDpad::PSPDpad(ImageID arrowIndex, std::string_view key, ImageID arrowDownIndex, ImageID overlayIndex, float scale, float spacing, UI::LayoutParams *layoutParams) : GamepadComponent(key, layoutParams), arrowIndex_(arrowIndex), arrowDownIndex_(arrowDownIndex), overlayIndex_(overlayIndex), scale_(scale), spacing_(spacing), dragPointerId_(-1), down_(0) { } void PSPDpad::GetContentDimensions(const UIContext &dc, float &w, float &h) const { const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(arrowIndex_); if (image) { w = 2.0f * D_pad_Radius * spacing_ + image->w * scale_; h = w; } else { w = 0.0f; h = 0.0f; } } bool PSPDpad::Touch(const TouchInput &input) { bool retval = GamepadComponent::Touch(input); if (input.flags & TouchInputFlags::DOWN) { if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y)) { dragPointerId_ = input.id; usedPointerMask |= 1 << input.id; ProcessTouch(input.x, input.y, true, false); } } if (input.flags & TouchInputFlags::MOVE) { if (!(input.flags & TouchInputFlags::MOUSE) || input.buttons) { bool ignorePress = false; if (g_activeGesturePointers.count(input.id)) { // Don't start a dpad touch if dragging and this pointer is already being used for a gesture. ignorePress = true; } if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y) && !(analogPointerMask & (1 << input.id))) { dragPointerId_ = input.id; } if (input.id == dragPointerId_) { ProcessTouch(input.x, input.y, true, ignorePress); } } } if (input.flags & TouchInputFlags::UP) { if (input.id == dragPointerId_) { dragPointerId_ = -1; usedPointerMask &= ~(1 << input.id); ProcessTouch(input.x, input.y, false, false); } } return retval; } void PSPDpad::ProcessTouch(float x, float y, bool down, bool ignorePress) { float stick_size = bounds_.w; float inv_stick_size = 1.0f / stick_size; const float deadzone = 0.05f; float dx = (x - bounds_.centerX()) * inv_stick_size; float dy = (y - bounds_.centerY()) * inv_stick_size; float rad = sqrtf(dx * dx + dy * dy); rotateTouchHelper(dx, dy); if (!g_Config.bStickyTouchDPad && (rad < deadzone || fabs(dx) > 0.5f || fabs(dy) > 0.5)) down = false; int ctrlMask = 0; bool fourWay = g_Config.bDisableDpadDiagonals || rad < 0.2f; if (down) { if (fourWay) { int direction = (int)(floorf((atan2f(dy, dx) / (2 * M_PI) * 4) + 0.5f)) & 3; switch (direction) { case 0: ctrlMask = CTRL_RIGHT; break; case 1: ctrlMask = CTRL_DOWN; break; case 2: ctrlMask = CTRL_LEFT; break; case 3: ctrlMask = CTRL_UP; break; } // 4 way pad } else { // 8 way pad int direction = (int)(floorf((atan2f(dy, dx) / (2 * M_PI) * 8) + 0.5f)) & 7; switch (direction) { case 0: ctrlMask = CTRL_RIGHT; break; case 1: ctrlMask = CTRL_RIGHT | CTRL_DOWN; break; case 2: ctrlMask = CTRL_DOWN; break; case 3: ctrlMask = CTRL_DOWN | CTRL_LEFT; break; case 4: ctrlMask = CTRL_LEFT; break; case 5: ctrlMask = CTRL_UP | CTRL_LEFT; break; case 6: ctrlMask = CTRL_UP; break; case 7: ctrlMask = CTRL_UP | CTRL_RIGHT; break; } } } int lastDown = down_; int pressed = ctrlMask & ~lastDown; int released = (~ctrlMask) & lastDown; down_ = ctrlMask; bool vibrate = false; static const int dir[4] = { CTRL_RIGHT, CTRL_DOWN, CTRL_LEFT, CTRL_UP }; for (int i = 0; i < 4; i++) { if (!ignorePress && (pressed & dir[i])) { vibrate = true; __CtrlUpdateButtons(dir[i], 0); } if (released & dir[i]) { __CtrlUpdateButtons(0, dir[i]); } } if (vibrate && g_Config.bHapticFeedback) { System_Vibrate(HAPTIC_VIRTUAL_KEY); } } void PSPDpad::Draw(UIContext &dc) { float opacity = g_gamepadOpacity; if (opacity <= 0.0f) return; // from control mapper.cpp int rotations = 0; if (g_Config.displayLayoutLandscape.bRotateControlsWithScreen) { switch (g_Config.displayLayoutLandscape.iInternalScreenRotation) { case ROTATION_LOCKED_HORIZONTAL180: rotations = 2; break; case ROTATION_LOCKED_VERTICAL: rotations = 3; break; case ROTATION_LOCKED_VERTICAL180: rotations = 1; break; } } static const float xoff[4] = {1, 0, -1, 0}; static const float yoff[4] = {0, 1, 0, -1}; static const int dir[4] = {CTRL_RIGHT, CTRL_DOWN, CTRL_LEFT, CTRL_UP}; // shitfs right by amount of rotations (accounts for overflow) int rotatedDir[4]; for (int i = 0; i < 4; i ++) { rotatedDir[i] = dir[(i+rotations)%4]; } int buttons = __CtrlPeekButtons(); float r = D_pad_Radius * spacing_; for (int i = 0; i < 4; i++) { bool isDown = (buttons & rotatedDir[i]) != 0; float x = bounds_.centerX() + xoff[i] * r; float y = bounds_.centerY() + yoff[i] * r; float x2 = bounds_.centerX() + xoff[i] * (r + 10.f * scale_); float y2 = bounds_.centerY() + yoff[i] * (r + 10.f * scale_); float angle = i * (M_PI / 2.0f); float imgScale = isDown ? scale_ * TOUCH_SCALE_FACTOR : scale_; float imgOpacity = opacity; if (isDown && g_Config.iTouchButtonStyle == 2) { imgScale = scale_; imgOpacity *= 1.35f; uint32_t downBg = colorAlpha(0x00FFFFFF, imgOpacity * 0.5f); if (arrowIndex_ != arrowDownIndex_) dc.Draw()->DrawImageRotated(arrowDownIndex_, x, y, imgScale, angle + PI, downBg, false); } uint32_t colorBg = colorAlpha(GetButtonColor(), imgOpacity); uint32_t color = colorAlpha(0xFFFFFF, imgOpacity); dc.Draw()->DrawImageRotated(arrowIndex_, x, y, imgScale, angle + PI, colorBg, false); if (overlayIndex_.isValid()) dc.Draw()->DrawImageRotated(overlayIndex_, x2, y2, imgScale, angle + PI, color); } } PSPStick::PSPStick(ImageID bgImg, std::string_view key, ImageID stickImg, ImageID stickDownImg, int stick, float scale, UI::LayoutParams *layoutParams) : GamepadComponent(key, layoutParams), bgImg_(bgImg), stickImageIndex_(stickImg), stickDownImg_(stickDownImg), stick_(stick), scale_(scale) { stick_size_ = 50; } void PSPStick::GetContentDimensions(const UIContext &dc, float &w, float &h) const { dc.Draw()->GetAtlas()->measureImage(bgImg_, &w, &h); w *= scale_; h *= scale_; } void PSPStick::Draw(UIContext &dc) { float opacity = g_gamepadOpacity; if (opacity <= 0.0f) return; if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2) { opacity *= 1.35f; } uint32_t colorBg = colorAlpha(GetButtonColor(), opacity); uint32_t downBg = colorAlpha(0x00FFFFFF, opacity * 0.5f); if (centerX_ < 0.0f) { centerX_ = bounds_.centerX(); centerY_ = bounds_.centerY(); } float stickX = centerX_; float stickY = centerY_; float dx, dy; __CtrlPeekAnalog(stick_, &dx, &dy); rotateTouchHelper(dx, dy); const TouchControlConfig &config = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation()); if (!config.bHideStickBackground) dc.Draw()->DrawImage(bgImg_, stickX, stickY, 1.0f * scale_, colorBg, ALIGN_CENTER); float headScale = stick_ ? config.fRightStickHeadScale : config.fLeftStickHeadScale; if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2 && stickDownImg_ != stickImageIndex_) dc.Draw()->DrawImage(stickDownImg_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, downBg, ALIGN_CENTER); dc.Draw()->DrawImage(stickImageIndex_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, colorBg, ALIGN_CENTER); } bool PSPStick::Touch(const TouchInput &input) { bool retval = GamepadComponent::Touch(input); if (input.flags & TouchInputFlags::RELEASE_ALL) { dragPointerId_ = -1; centerX_ = bounds_.centerX(); centerY_ = bounds_.centerY(); __CtrlSetAnalogXY(stick_, 0.0f, 0.0f); usedPointerMask = 0; analogPointerMask = 0; return retval; } if (input.flags & TouchInputFlags::DOWN) { const TouchControlConfig &config = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation()); float fac = 0.5f * (stick_ ? config.fRightStickHeadScale : config.fLeftStickHeadScale)-0.5f; if (dragPointerId_ == -1 && bounds_.Expand(bounds_.w*fac, bounds_.h*fac).Contains(input.x, input.y)) { if (g_Config.bAutoCenterTouchAnalog) { centerX_ = input.x; centerY_ = input.y; } else { centerX_ = bounds_.centerX(); centerY_ = bounds_.centerY(); } dragPointerId_ = input.id; usedPointerMask |= 1 << input.id; analogPointerMask |= 1 << input.id; ProcessTouch(input.x, input.y, true); retval = true; } } if (input.flags & TouchInputFlags::MOVE) { if (input.id == dragPointerId_) { ProcessTouch(input.x, input.y, true); retval = true; } } if (input.flags & TouchInputFlags::UP) { if (input.id == dragPointerId_) { dragPointerId_ = -1; centerX_ = bounds_.centerX(); centerY_ = bounds_.centerY(); usedPointerMask &= ~(1 << input.id); analogPointerMask &= ~(1 << input.id); ProcessTouch(input.x, input.y, false); retval = true; } } return retval; } void PSPStick::ProcessTouch(float x, float y, bool down) { if (down && centerX_ >= 0.0f) { float inv_stick_size = 1.0f / (stick_size_ * scale_); float dx = (x - centerX_) * inv_stick_size; float dy = (y - centerY_) * inv_stick_size; rotateTouchHelper(dx, dy); // Do not clamp to a circle! The PSP has nearly square range! // Old code to clamp to a circle // float len = sqrtf(dx * dx + dy * dy); // if (len > 1.0f) { // dx /= len; // dy /= len; //} // Still need to clamp to a square dx = std::min(1.0f, std::max(-1.0f, dx)); dy = std::min(1.0f, std::max(-1.0f, dy)); __CtrlSetAnalogXY(stick_, dx, -dy); } else { __CtrlSetAnalogXY(stick_, 0.0f, 0.0f); } } PSPCustomStick::PSPCustomStick(ImageID bgImg, const char *key, ImageID stickImg, ImageID stickDownImg, int stick, float scale, UI::LayoutParams *layoutParams) : PSPStick(bgImg, key, stickImg, stickDownImg, stick, scale, layoutParams) { } void PSPCustomStick::Draw(UIContext &dc) { float opacity = g_gamepadOpacity; if (opacity <= 0.0f) return; if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2) { opacity *= 1.35f; } uint32_t colorBg = colorAlpha(GetButtonColor(), opacity); uint32_t downBg = colorAlpha(0x00FFFFFF, opacity * 0.5f); if (centerX_ < 0.0f) { centerX_ = bounds_.centerX(); centerY_ = bounds_.centerY(); } float stickX = centerX_; float stickY = centerY_; float dx, dy; dx = posX_; dy = -posY_; const TouchControlConfig &config = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation()); const float headScale = config.fRightStickHeadScale; if (!config.bHideStickBackground) dc.Draw()->DrawImage(bgImg_, stickX, stickY, 1.0f * scale_, colorBg, ALIGN_CENTER); if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2 && stickDownImg_ != stickImageIndex_) dc.Draw()->DrawImage(stickDownImg_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, downBg, ALIGN_CENTER); dc.Draw()->DrawImage(stickImageIndex_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, colorBg, ALIGN_CENTER); } bool PSPCustomStick::Touch(const TouchInput &input) { bool retval = GamepadComponent::Touch(input); if (input.flags & TouchInputFlags::RELEASE_ALL) { dragPointerId_ = -1; centerX_ = bounds_.centerX(); centerY_ = bounds_.centerY(); posX_ = 0.0f; posY_ = 0.0f; usedPointerMask = 0; analogPointerMask = 0; return false; } if (input.flags & TouchInputFlags::DOWN) { const TouchControlConfig &config = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation()); float fac = 0.5f * config.fRightStickHeadScale - 0.5f; if (dragPointerId_ == -1 && bounds_.Expand(bounds_.w*fac, bounds_.h*fac).Contains(input.x, input.y)) { if (g_Config.bAutoCenterTouchAnalog) { centerX_ = input.x; centerY_ = input.y; } else { centerX_ = bounds_.centerX(); centerY_ = bounds_.centerY(); } dragPointerId_ = input.id; usedPointerMask |= 1 << input.id; analogPointerMask |= 1 << input.id; ProcessTouch(input.x, input.y, true); retval = true; } } if (input.flags & TouchInputFlags::MOVE) { if (input.id == dragPointerId_) { ProcessTouch(input.x, input.y, true); retval = true; } } if (input.flags & TouchInputFlags::UP) { if (input.id == dragPointerId_) { dragPointerId_ = -1; centerX_ = bounds_.centerX(); centerY_ = bounds_.centerY(); usedPointerMask &= ~(1 << input.id); analogPointerMask &= ~(1 << input.id); ProcessTouch(input.x, input.y, false); retval = true; } } return retval; } void PSPCustomStick::ProcessTouch(float x, float y, bool down) { static 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}; static 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}; u32 press = 0; u32 release = 0; auto toggle = [&](int config, bool simpleCheck, bool diagCheck = true) { if (config <= 0 || (size_t)config >= ARRAY_SIZE(buttons)) return; if (simpleCheck && (!g_Config.bRightAnalogDisableDiagonal || diagCheck)) press |= buttons[config]; else release |= buttons[config]; }; auto analog = [&](float dx, float dy) { if (g_Config.bRightAnalogDisableDiagonal) { if (fabs(dx) > fabs(dy)) { dy = 0.0f; } else { dx = 0.0f; } } auto assign = [&](int config, float value) { if (config < ARRAY_SIZE(buttons) || config >= ARRAY_SIZE(buttons) + ARRAY_SIZE(analogs)) { return; } switch(analogs[config - ARRAY_SIZE(buttons)]) { case VIRTKEY_AXIS_Y_MAX: __CtrlSetAnalogY(0, value); break; case VIRTKEY_AXIS_Y_MIN: __CtrlSetAnalogY(0, value * -1.0f); break; case VIRTKEY_AXIS_X_MIN: __CtrlSetAnalogX(0, value * -1.0f); break; case VIRTKEY_AXIS_X_MAX: __CtrlSetAnalogX(0, value); break; case VIRTKEY_AXIS_RIGHT_Y_MAX: __CtrlSetAnalogY(1, value); break; case VIRTKEY_AXIS_RIGHT_Y_MIN: __CtrlSetAnalogY(1, value * -1.0f); break; case VIRTKEY_AXIS_RIGHT_X_MIN: __CtrlSetAnalogX(1, value * -1.0f); break; case VIRTKEY_AXIS_RIGHT_X_MAX: __CtrlSetAnalogX(1, value); break; } }; // if/when we ever get iLeftAnalog settings, check stick_ for the config to use // let 0.0f through during centering if (dy >= 0.0f) { // down assign(g_Config.iRightAnalogUp, 0.0f); assign(g_Config.iRightAnalogDown, dy); } if (dy <= 0.0f) { // up assign(g_Config.iRightAnalogDown, 0.0f); assign(g_Config.iRightAnalogUp, dy * -1.0f); } if (dx <= 0.0f) { // left assign(g_Config.iRightAnalogRight, 0.0f); assign(g_Config.iRightAnalogLeft, dx * -1.0f); } if (dx >= 0.0f) { // right assign(g_Config.iRightAnalogLeft, 0.0f); assign(g_Config.iRightAnalogRight, dx); } }; if (down && centerX_ >= 0.0f) { float inv_stick_size = 1.0f / (stick_size_ * scale_); float dx = (x - centerX_) * inv_stick_size; float dy = (y - centerY_) * inv_stick_size; dx = std::min(1.0f, std::max(-1.0f, dx)); dy = std::min(1.0f, std::max(-1.0f, dy)); toggle(g_Config.iRightAnalogRight, dx > 0.5f, fabs(dx) > fabs(dy)); toggle(g_Config.iRightAnalogLeft, dx < -0.5f, fabs(dx) > fabs(dy)); toggle(g_Config.iRightAnalogUp, dy < -0.5f, fabs(dx) <= fabs(dy)); toggle(g_Config.iRightAnalogDown, dy > 0.5f, fabs(dx) <= fabs(dy)); toggle(g_Config.iRightAnalogPress, true); analog(dx, dy); posX_ = dx; posY_ = dy; } else { toggle(g_Config.iRightAnalogRight, false); toggle(g_Config.iRightAnalogLeft, false); toggle(g_Config.iRightAnalogUp, false); toggle(g_Config.iRightAnalogDown, false); toggle(g_Config.iRightAnalogPress, false); analog(0.0f, 0.0f); posX_ = 0.0f; posY_ = 0.0f; } if (release || press) { __CtrlUpdateButtons(press, release); } } void InitPadLayout(TouchControlConfig *config, DeviceOrientation orientation, float xres, float yres, float globalScale) { const float scale = globalScale; const int halfW = xres / 2; const float screenBottom = orientation == DeviceOrientation::Portrait ? (yres - yres * 0.13f) : yres; auto initTouchPos = [=](ConfigTouchPos *touch, float x, float y, float extraScale = 1.0f) { if (touch->x == -1.0f || touch->y == -1.0f) { touch->x = x / xres; touch->y = std::max(y, 20.0f * globalScale) / yres; touch->scale = scale * extraScale; } }; // Pause button. Has some special handling, it MUST be visible on some platforms. float Pause_button_center_X = halfW; float Pause_button_center_Y = 28.0f; if (!System_GetPropertyBool(SYSPROP_HAS_BACK_BUTTON)) { // Make really sure the pause button will be visible. Setting to -1 ensures reinit. if (config->touchPauseKey.x < 0.0f || config->touchPauseKey.x > 1.0f) { config->touchPauseKey.x = -1; } if (config->touchPauseKey.y < 0.0f || config->touchPauseKey.y > 1.0f) { config->touchPauseKey.y = -1; } config->touchPauseKey.show = true; } initTouchPos(&config->touchPauseKey, Pause_button_center_X, Pause_button_center_Y, 0.8f); // PSP buttons (triangle, circle, square, cross)--------------------- // space between the PSP buttons (triangle, circle, square and cross) if (config->fActionButtonSpacing < 0) { config->fActionButtonSpacing = 1.0f; } // Position of the circle button (the PSP circle button). It is the farthest to the left float Action_button_spacing = config->fActionButtonSpacing * baseActionButtonSpacing; int Action_button_center_X = xres - Action_button_spacing * 2; int Action_button_center_Y = screenBottom - Action_button_spacing * 2; if (config->touchRightAnalogStick.show) { Action_button_center_Y -= 150 * scale; } initTouchPos(&config->touchActionButtonCenter, Action_button_center_X, Action_button_center_Y); //D-PAD (up down left right) (aka PSP cross)---------------------------- //radius to the D-pad // TODO: Make configurable int D_pad_X = 2.5 * D_pad_Radius * scale; int D_pad_Y = screenBottom - D_pad_Radius * scale; if (config->touchAnalogStick.show) { D_pad_Y -= 200 * scale; } initTouchPos(&config->touchDpad, D_pad_X, D_pad_Y); //analog stick------------------------------------------------------- //keep the analog stick right below the D pad int analog_stick_X = D_pad_X; int analog_stick_Y = screenBottom - 80 * scale; initTouchPos(&config->touchAnalogStick, analog_stick_X, analog_stick_Y); //right analog stick------------------------------------------------- //keep the right analog stick right below the face buttons int right_analog_stick_X = Action_button_center_X; int right_analog_stick_Y = screenBottom - 80 * scale; initTouchPos(&config->touchRightAnalogStick, right_analog_stick_X, right_analog_stick_Y); //select, start, throttle-------------------------------------------- //space between the bottom keys (space between select, start and un-throttle) float bottom_key_spacing = 100; if (g_display.dp_xres < 750) { bottom_key_spacing *= 0.8f; } // On IOS, nudge the bottom button up a little to avoid the task switcher. // Additionally, in portrait mode on any platform, move the defaults up by quite a bit - much // more comfortable to reach. #if PPSSPP_PLATFORM(IOS) constexpr float bottom_button_Y = 80.0f; #else constexpr float bottom_button_Y = 60.0f; #endif if (orientation == DeviceOrientation::Portrait) { int start_key_X = halfW; int start_key_Y = screenBottom - bottom_button_Y * scale; initTouchPos(&config->touchStartKey, start_key_X, start_key_Y); int select_key_X = halfW; int select_key_Y = screenBottom - bottom_button_Y * scale - 50; initTouchPos(&config->touchSelectKey, select_key_X, select_key_Y); int fast_forward_key_X = halfW; int fast_forward_key_Y = screenBottom - bottom_button_Y * scale - 100; initTouchPos(&config->touchFastForwardKey, fast_forward_key_X, fast_forward_key_Y); } else { int start_key_X = halfW + bottom_key_spacing * scale; int start_key_Y = screenBottom - bottom_button_Y * scale; initTouchPos(&config->touchStartKey, start_key_X, start_key_Y); int select_key_X = halfW; int select_key_Y = screenBottom - bottom_button_Y * scale; initTouchPos(&config->touchSelectKey, select_key_X, select_key_Y); int fast_forward_key_X = halfW - bottom_key_spacing * scale; int fast_forward_key_Y = screenBottom - bottom_button_Y * scale; initTouchPos(&config->touchFastForwardKey, fast_forward_key_X, fast_forward_key_Y); } // L and R------------------------------------------------------------ // Put them above the analog stick / above the buttons to the right. // The corners were very hard to reach.. int l_key_X = 60 * scale; int l_key_Y = screenBottom - 380 * scale; initTouchPos(&config->touchLKey, l_key_X, l_key_Y); int r_key_X = xres - 60 * scale; int r_key_Y = l_key_Y; initTouchPos(&config->touchRKey, r_key_X, r_key_Y); struct { float x; float y; } customButtonPositions[10] = { { 1.2f, 0.5f }, { 2.2f, 0.5f }, { 3.2f, 0.5f }, { 1.2f, 0.333f }, { 2.2f, 0.333f }, { -1.2f, 0.5f }, { -2.2f, 0.5f }, { -3.2f, 0.5f }, { -1.2f, 0.333f }, { -2.2f, 0.333f }, }; for (int i = 0; i < TouchControlConfig::CUSTOM_BUTTON_COUNT; i++) { const float y_offset = (float)(i / 10) * 0.08333f; int combo_key_X = halfW + bottom_key_spacing * scale * customButtonPositions[i % 10].x; int combo_key_Y = screenBottom * (y_offset + customButtonPositions[i % 10].y); initTouchPos(&config->touchCustom[i], combo_key_X, combo_key_Y); } } UI::ViewGroup *CreatePadLayout(const TouchControlConfig &config, float xres, float yres, bool *pause, ControlMapper *controlMapper) { using namespace UI; return new GamepadEmuView(config, xres, yres, pause, controlMapper, new UI::LayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)); } GamepadEmuView::GamepadEmuView(const TouchControlConfig &config, float xres, float yres, bool *pause, ControlMapper *controlMapper, UI::LayoutParams *layoutParams) : UI::AnchorLayout(layoutParams) { if (!g_Config.bShowTouchControls) { return; } using namespace UI; struct ButtonOffset { float x; float y; }; auto buttonLayoutParams = [=](const ConfigTouchPos &touch, ButtonOffset off = { 0, 0 }) { return new AnchorLayoutParams(touch.x * xres + off.x, touch.y * yres + off.y, NONE, NONE, Centering::Both); }; // Space between the PSP buttons (triangle, circle, square and cross) const float actionButtonSpacing = config.fActionButtonSpacing * baseActionButtonSpacing; // Position of the circle button (the PSP circle button). It is the farthest to the right. ButtonOffset circleOffset{ actionButtonSpacing, 0.0f }; ButtonOffset crossOffset{ 0.0f, actionButtonSpacing }; ButtonOffset triangleOffset{ 0.0f, -actionButtonSpacing }; ButtonOffset squareOffset{ -actionButtonSpacing, 0.0f }; const int halfW = xres / 2; const ImageID roundImage = g_Config.iTouchButtonStyle ? ImageID("I_ROUND_LINE") : ImageID("I_ROUND"); const ImageID rectImage = g_Config.iTouchButtonStyle ? ImageID("I_RECT_LINE") : ImageID("I_RECT"); const ImageID shoulderImage = g_Config.iTouchButtonStyle ? ImageID("I_SHOULDER_LINE") : ImageID("I_SHOULDER"); const ImageID stickImage = g_Config.iTouchButtonStyle ? ImageID("I_STICK_LINE") : ImageID("I_STICK"); const ImageID stickBg = g_Config.iTouchButtonStyle ? ImageID("I_STICK_BG_LINE") : ImageID("I_STICK_BG"); auto addPSPButton = [this, buttonLayoutParams](int buttonBit, const char *key, ImageID bgImg, ImageID bgDownImg, ImageID img, const ConfigTouchPos &touch, ButtonOffset off = { 0, 0 }) -> PSPButton * { if (touch.show) { return Add(new PSPButton(buttonBit, key, bgImg, bgDownImg, img, touch.scale, buttonLayoutParams(touch, off))); } return nullptr; }; auto addBoolButton = [this, buttonLayoutParams](bool *value, const char *key, ImageID bgImg, ImageID bgDownImg, ImageID img, const ConfigTouchPos &touch) -> BoolButton * { if (touch.show) { return Add(new BoolButton(value, key, bgImg, bgDownImg, img, touch.scale, buttonLayoutParams(touch))); } return nullptr; }; auto addCustomButton = [this, buttonLayoutParams, controlMapper](const ConfigCustomButton& cfg, const char *key, const ConfigTouchPos &touch) -> CustomButton * { using namespace CustomKeyData; if (touch.show) { _dbg_assert_(cfg.shape < ARRAY_SIZE(customKeyShapes)); _dbg_assert_(cfg.image < ARRAY_SIZE(customKeyImages)); // Note: cfg.shape and cfg.image are bounds-checked elsewhere. auto aux = Add(new CustomButton(cfg.key, key, cfg.toggle, cfg.repeat, controlMapper, g_Config.iTouchButtonStyle == 0 ? customKeyShapes[cfg.shape].i : customKeyShapes[cfg.shape].l, customKeyShapes[cfg.shape].i, customKeyImages[cfg.image].i, touch.scale, customKeyShapes[cfg.shape].d, buttonLayoutParams(touch))); aux->SetAngle(customKeyImages[cfg.image].r, customKeyShapes[cfg.shape].r); aux->FlipImageH(customKeyShapes[cfg.shape].f); return aux; } return nullptr; }; if (config.touchPauseKey.show) { auto button = addBoolButton(pause, "Pause button", roundImage, ImageID("I_ROUND"), ImageID("I_HAMBURGER"), config.touchPauseKey); if (button) { // The user is not allowed to hide this completely on some platforms - it must be findable. button->SetMinimumAlpha(0.1f); } } // touchActionButtonCenter.show will always be true, since that's the default. if (config.bShowTouchCircle) addPSPButton(CTRL_CIRCLE, "Circle button", roundImage, ImageID("I_ROUND"), ImageID("I_CIRCLE"), config.touchActionButtonCenter, circleOffset); if (config.bShowTouchCross) addPSPButton(CTRL_CROSS, "Cross button", roundImage, ImageID("I_ROUND"), ImageID("I_CROSS"), config.touchActionButtonCenter, crossOffset); if (config.bShowTouchTriangle) addPSPButton(CTRL_TRIANGLE, "Triangle button", roundImage, ImageID("I_ROUND"), ImageID("I_TRIANGLE"), config.touchActionButtonCenter, triangleOffset); if (config.bShowTouchSquare) addPSPButton(CTRL_SQUARE, "Square button", roundImage, ImageID("I_ROUND"), ImageID("I_SQUARE"), config.touchActionButtonCenter, squareOffset); addPSPButton(CTRL_START, "Start button", rectImage, ImageID("I_RECT"), ImageID("I_START"), config.touchStartKey); addPSPButton(CTRL_SELECT, "Select button", rectImage, ImageID("I_RECT"), ImageID("I_SELECT"), config.touchSelectKey); BoolButton *fastForward = addBoolButton(&PSP_CoreParameter().fastForward, "Fast-forward button", rectImage, ImageID("I_RECT"), ImageID("I_FAST_FORWARD_LINE"), config.touchFastForwardKey); if (fastForward) { fastForward->OnChange.Add([](UI::EventParams &e) { if (e.a && coreState == CORE_STEPPING_CPU) { Core_Resume(); } }); } addPSPButton(CTRL_LTRIGGER, "Left shoulder button", shoulderImage, ImageID("I_SHOULDER"), ImageID("I_L"), config.touchLKey); PSPButton *rTrigger = addPSPButton(CTRL_RTRIGGER, "Right shoulder button", shoulderImage, ImageID("I_SHOULDER"), ImageID("I_R"), config.touchRKey); if (rTrigger) rTrigger->FlipImageH(true); if (config.touchDpad.show) { const ImageID dirImage = g_Config.iTouchButtonStyle ? ImageID("I_DIR_LINE") : ImageID("I_DIR"); Add(new PSPDpad(dirImage, "D-pad", ImageID("I_DIR"), ImageID("I_ARROW"), config.touchDpad.scale, config.fDpadSpacing, buttonLayoutParams(config.touchDpad))); } if (config.touchAnalogStick.show) Add(new PSPStick(stickBg, "Left analog stick", stickImage, ImageID("I_STICK"), 0, config.touchAnalogStick.scale, buttonLayoutParams(config.touchAnalogStick))); if (config.touchRightAnalogStick.show) { if (g_Config.bRightAnalogCustom) Add(new PSPCustomStick(stickBg, "Right analog stick", stickImage, ImageID("I_STICK"), 1, config.touchRightAnalogStick.scale, buttonLayoutParams(config.touchRightAnalogStick))); else Add(new PSPStick(stickBg, "Right analog stick", stickImage, ImageID("I_STICK"), 1, config.touchRightAnalogStick.scale, buttonLayoutParams(config.touchRightAnalogStick))); } // Sanitize custom button images, while adding them. for (int i = 0; i < TouchControlConfig::CUSTOM_BUTTON_COUNT; i++) { if (g_Config.CustomButton[i].shape >= ARRAY_SIZE(CustomKeyData::customKeyShapes)) { g_Config.CustomButton[i].shape = 0; } if (g_Config.CustomButton[i].image >= ARRAY_SIZE(CustomKeyData::customKeyImages)) { g_Config.CustomButton[i].image = 0; } char temp[64]; snprintf(temp, sizeof(temp), "Custom %d button", i + 1); addCustomButton(g_Config.CustomButton[i], temp, config.touchCustom[i]); } // Add the two gesture zones. for (int i = 0; i < 2; i++) { if (g_Config.gestureControls[i].bGestureControlEnabled) { // We have them both cover the whole surface, then limit in the touch handler. // This is because there's no easy way to do "half the screen" in AnchorLayout. // We can do more complex layout combinations, but meh. Add(new GestureGamepad(controlMapper, i, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, 0.0f, 0.0f, 0.0f, 0.0f))); } } } void GamepadEmuView::Update() { AnchorLayout::Update(); GamepadUpdateOpacity(); bool anyDown = false; for (auto view : views_) { GamepadComponent *component = dynamic_cast(view); if (component && component->IsDownByTouch()) { // INFO_LOG(Log::System, "GamepadEmuView::Update: component is down by touch: %s", component->DescribeText().c_str()); anyDown = true; } } if (anyDown) { // INFO_LOG(Log::System, "last touch in update"); g_lastTouch = time_now_d(); } } const GestureControlConfig &GestureGamepad::GetZone() { // TODO: Add support for multiple zones. return g_Config.gestureControls[zoneIndex_]; } GestureGamepad::~GestureGamepad() { if (dragPointerId_ != -1) { g_activeGesturePointers.erase(dragPointerId_); } } bool GestureGamepad::Touch(const TouchInput &input) { const GestureControlConfig &zone = GetZone(); if (usedPointerMask & (1 << input.id)) { if (input.id == dragPointerId_) dragPointerId_ = -1; return false; } if (input.flags & TouchInputFlags::RELEASE_ALL) { dragPointerId_ = -1; g_activeGesturePointers.clear(); return false; } if (input.flags & TouchInputFlags::DOWN) { // Handle the zones here, easier than with AnchorLayout. const float minX = zoneIndex_ == 0 ? 0.0f : bounds_.w * 0.5f; const float maxX = zoneIndex_ == 0 ? bounds_.w * 0.5f : bounds_.w; if (dragPointerId_ == -1 && input.x >= minX && input.x < maxX) { dragPointerId_ = input.id; lastX_ = input.x; lastY_ = input.y; downX_ = input.x; downY_ = input.y; const float now = time_now_d(); if (now - lastTapRelease_ < 0.3f && !haveDoubleTapped_) { if (zone.iDoubleTapGesture != 0 ) controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[zone.iDoubleTapGesture - 1], KeyInputFlags::DOWN); haveDoubleTapped_ = true; } lastTouchDown_ = now; } } if (input.flags & TouchInputFlags::MOVE) { if (input.id == dragPointerId_) { deltaX_ += input.x - lastX_; deltaY_ += input.y - lastY_; lastX_ = input.x; lastY_ = input.y; const float distance = sqrtf((input.x - downX_) * (input.x - downX_) + (input.y - downY_) * (input.y - downY_)); // TODO: Configurable distance? if (distance > 50.0f * g_display.dpi_scale_x) { // The user has dragged some distance from the initial touch. Start ignoring button presses. g_activeGesturePointers.insert(input.id); } else { g_activeGesturePointers.erase(input.id); } if (zone.bAnalogGesture) { const float k = zone.fAnalogGestureSensitivity * 0.02; float dx = (input.x - downX_) * g_display.dpi_scale_x * k; float dy = (input.y - downY_) * g_display.dpi_scale_y * k; dx = std::clamp(dx, -1.0f, 1.0f); dy = std::clamp(dy, -1.0f, 1.0f); __CtrlSetAnalogXY(0, dx, -dy); } } } if (input.flags & TouchInputFlags::UP) { g_activeGesturePointers.erase(input.id); if (input.id == dragPointerId_) { dragPointerId_ = -1; if (time_now_d() - lastTouchDown_ < 0.3f) lastTapRelease_ = time_now_d(); if (haveDoubleTapped_) { if (zone.iDoubleTapGesture != 0) controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[zone.iDoubleTapGesture - 1], KeyInputFlags::UP); haveDoubleTapped_ = false; } if (zone.bAnalogGesture) __CtrlSetAnalogXY(0, 0, 0); } } return true; } void GestureGamepad::Draw(UIContext &dc) { float opacity = g_Config.iTouchButtonOpacity / 100.0; if (opacity <= 0.0f) return; uint32_t colorBg = colorAlpha(GetButtonColor(), opacity); if (GetZone().bAnalogGesture && dragPointerId_ != -1) { dc.Draw()->DrawImage(ImageID("I_CIRCLE"), downX_, downY_, 0.7f, colorBg, ALIGN_CENTER); } } void GestureGamepad::Update() { const float th = 1.0f; float dx = deltaX_ * g_display.dpi_scale_x * GetZone().fSwipeSensitivity; float dy = deltaY_ * g_display.dpi_scale_y * GetZone().fSwipeSensitivity; if (GetZone().iSwipeRight != 0) { if (dx > th) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeRight - 1], KeyInputFlags::DOWN); swipeRightReleased_ = false; } else if (!swipeRightReleased_) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeRight -1], KeyInputFlags::UP); swipeRightReleased_ = true; } } if (GetZone().iSwipeLeft != 0) { if (dx < -th) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeLeft - 1], KeyInputFlags::DOWN); swipeLeftReleased_ = false; } else if (!swipeLeftReleased_) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeLeft - 1], KeyInputFlags::UP); swipeLeftReleased_ = true; } } if (GetZone().iSwipeUp != 0) { if (dy < -th) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeUp - 1], KeyInputFlags::DOWN); swipeUpReleased_ = false; } else if (!swipeUpReleased_) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeUp - 1], KeyInputFlags::UP); swipeUpReleased_ = true; } } if (GetZone().iSwipeDown != 0) { if (dy > th) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeDown - 1], KeyInputFlags::DOWN); swipeDownReleased_ = false; } else if (!swipeDownReleased_) { controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeDown - 1], KeyInputFlags::UP); swipeDownReleased_ = true; } } const float smoothing = GetZone().fSwipeSmoothing; deltaX_ *= smoothing; deltaY_ *= smoothing; }