Files
ppsspp/UI/GamepadEmu.h
T
Henrik RydgårdandClaude Opus 5.5 0da3129a69 Touch controls: Add a HOME button, and list HOME with the standard PSP controls
POPS (the PS1 emulator) uses HOME for its menu. The touch button is hidden by
default, and HOME still has no default key mapping. It's also appended to the
custom button and gesture key lists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 14:14:17 +02:00

433 lines
14 KiB
C++

// 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/.
#pragma once
#include "Common/Input/InputState.h"
#include "Common/Render/DrawBuffer.h"
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Core/ConfigValues.h"
#include "Core/CoreParameter.h"
#include "Core/HLE/sceCtrl.h"
#include "UI/EmuScreen.h"
struct TouchControlConfig;
class ControlMapper;
class GamepadEmuView : public UI::AnchorLayout {
public:
GamepadEmuView(const TouchControlConfig &config, float xres, float yres, bool *pause, ControlMapper *controlMapper, UI::LayoutParams *layoutParams);
void Update() override;
};
class GamepadComponent : public UI::View {
public:
GamepadComponent(std::string_view key, UI::LayoutParams *layoutParams);
bool Key(const KeyInput &input) override {
return false;
}
std::string DescribeText() const override;
virtual bool IsDownByTouch() const {
return false;
}
bool CanBeFocused() const override { return false; }
protected:
std::string key_;
};
class MultiTouchButton : public GamepadComponent {
public:
MultiTouchButton(std::string_view key, ImageID bgImg, ImageID bgDownImg, ImageID img, float scale, UI::LayoutParams *layoutParams)
: GamepadComponent(key, layoutParams), scale_(scale), bgImg_(bgImg), bgDownImg_(bgDownImg), img_(img) {
}
bool Touch(const TouchInput &input) override;
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
virtual bool IsDownVisually() const { return false; }
bool IsDownByTouch() const override { return pointerDownMask_ != 0; }
// chainable
MultiTouchButton *FlipImageH(bool flip) { flipImageH_ = flip; return this; }
MultiTouchButton *SetAngle(float angle) { angle_ = angle; bgAngle_ = angle; return this; }
MultiTouchButton *SetAngle(float angle, float bgAngle) { angle_ = angle; bgAngle_ = bgAngle; return this; }
bool CanGlide() const;
void SetMinimumAlpha(float minAlpha) { minimumAlpha_ = minAlpha; }
protected:
uint32_t pointerDownMask_ = 0;
float scale_;
private:
ImageID bgImg_;
ImageID bgDownImg_;
ImageID img_;
float bgAngle_ = 0.0f;
float angle_ = 0.0f;
bool flipImageH_ = false;
float minimumAlpha_ = 0.0f;
};
class BoolButton : public MultiTouchButton {
public:
BoolButton(bool *value, std::string_view key, ImageID bgImg, ImageID bgDownImg, ImageID img, float scale, UI::LayoutParams *layoutParams)
: MultiTouchButton(key, bgImg, bgDownImg, img, scale, layoutParams), value_(value) {
}
bool Touch(const TouchInput &input) override;
bool IsDownVisually() const override { return *value_; }
UI::Event OnChange;
private:
bool *value_;
};
class PSPButton : public MultiTouchButton {
public:
PSPButton(int pspButtonBit, std::string_view key, ImageID bgImg, ImageID bgDownImg, ImageID img, float scale, UI::LayoutParams *layoutParams)
: MultiTouchButton(key, bgImg, bgDownImg, img, scale, layoutParams), pspButtonBit_(pspButtonBit) {
}
bool Touch(const TouchInput &input) override;
bool IsDownVisually() const override;
private:
int pspButtonBit_;
};
class PSPDpad : public GamepadComponent {
public:
PSPDpad(ImageID arrowIndex, std::string_view key, ImageID arrowDownIndex, ImageID overlayIndex, float scale, float spacing, UI::LayoutParams *layoutParams);
bool Touch(const TouchInput &input) override;
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
bool IsDownByTouch() const override { return down_ != 0; }
private:
void ProcessTouch(float x, float y, bool down, bool ignorePress);
ImageID arrowIndex_;
ImageID arrowDownIndex_;
ImageID overlayIndex_;
float scale_;
float spacing_;
int dragPointerId_;
int down_;
};
class PSPStick : public GamepadComponent {
public:
PSPStick(ImageID bgImg, std::string_view key, ImageID stickImg, ImageID stickDownImg, int stick, float scale, UI::LayoutParams *layoutParams);
bool Touch(const TouchInput &input) override;
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
bool IsDownByTouch() const override { return dragPointerId_ != -1; }
protected:
int dragPointerId_ = -1;
ImageID bgImg_;
ImageID stickImageIndex_;
ImageID stickDownImg_;
int stick_;
float stick_size_;
float scale_;
float centerX_ = -1.0f;
float centerY_ = -1.0f;
private:
void ProcessTouch(float x, float y, bool down);
};
class PSPCustomStick : public PSPStick {
public:
PSPCustomStick(ImageID bgImg, const char *key, ImageID stickImg, ImageID stickDownImg, int stick, float scale, UI::LayoutParams *layoutParams);
bool Touch(const TouchInput &input) override;
void Draw(UIContext &dc) override;
private:
void ProcessTouch(float x, float y, bool down);
float posX_ = 0.0f;
float posY_ = 0.0f;
};
struct TouchControlConfig;
// Initializes the layout from Config. if a default layout does not exist, it sets up default values
void InitPadLayout(TouchControlConfig *config, DeviceOrientation orientation, float xres, float yres, float globalScale = 1.15f);
UI::ViewGroup *CreatePadLayout(const TouchControlConfig &config, float xres, float yres, bool *pause, ControlMapper *controlMapper);
const int D_pad_Radius = 50;
const int baseActionButtonSpacing = 60;
// Customizable buttons, press a combination of buttons specified by pspButtonBit.
class CustomButton : public MultiTouchButton {
public:
CustomButton(uint64_t pspButtonBit, std::string_view key, bool toggle, bool repeat, ControlMapper* controllMapper, ImageID bgImg, ImageID bgDownImg, ImageID img, float scale, bool invertedContentDimension, UI::LayoutParams *layoutParams)
: MultiTouchButton(key, bgImg, bgDownImg, img, scale, layoutParams), pspButtonBit_(pspButtonBit), toggle_(toggle), repeat_(repeat), controlMapper_(controllMapper), on_(false), invertedContentDimension_(invertedContentDimension) {
}
bool Touch(const TouchInput &input) override;
void Update() override;
bool IsDownVisually() const override; // For visual purpose
bool IsDownByTouch() const override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
private:
uint64_t pspButtonBit_;
bool toggle_;
bool repeat_;
int pressedFrames_ = 0;
ControlMapper* controlMapper_;
bool on_;
bool invertedContentDimension_; // Swap width and height
};
struct GestureControlConfig;
class GestureGamepad : public UI::View {
public:
explicit GestureGamepad(ControlMapper* controlMapper, int zoneIndex, UI::LayoutParams *layoutParams) : UI::View(layoutParams), controlMapper_(controlMapper), zoneIndex_(zoneIndex) {}
~GestureGamepad();
bool Touch(const TouchInput &input) override;
void Update() override;
void Draw(UIContext &dc) override;
protected:
virtual std::string DescribeText() const override { return zoneIndex_ == 0 ? "gesture-left" : "gesture-right"; }
private:
const GestureControlConfig &GetZone();
int zoneIndex_;
float lastX_ = 0.0f;
float lastY_ = 0.0f;
float deltaX_ = 0.0f;
float deltaY_ = 0.0f;
float downX_ = 0.0f;
float downY_ = 0.0f;
float lastTapRelease_ = 0.0f;
float lastTouchDown_ = 0.0f;
int dragPointerId_ = -1;
bool swipeLeftReleased_ = true;
bool swipeRightReleased_ = true;
bool swipeUpReleased_ = true;
bool swipeDownReleased_ = true;
bool haveDoubleTapped_ = false;
ControlMapper* controlMapper_;
};
// Just edit this to add new image, shape or button function
namespace CustomKeyData {
// Image list
struct keyImage {
ImageID i; // ImageID
float r; // Rotation angle in degree
};
static const keyImage customKeyImages[] = {
{ ImageID("I_1"), 0.0f },
{ ImageID("I_2"), 0.0f },
{ ImageID("I_3"), 0.0f },
{ ImageID("I_4"), 0.0f },
{ ImageID("I_5"), 0.0f },
{ ImageID("I_6"), 0.0f },
{ ImageID("I_A"), 0.0f },
{ ImageID("I_B"), 0.0f },
{ ImageID("I_C"), 0.0f },
{ ImageID("I_D"), 0.0f },
{ ImageID("I_E"), 0.0f },
{ ImageID("I_F"), 0.0f },
{ ImageID("I_CIRCLE"), 0.0f },
{ ImageID("I_CROSS"), 0.0f },
{ ImageID("I_SQUARE"), 0.0f },
{ ImageID("I_TRIANGLE"), 0.0f },
{ ImageID("I_L"), 0.0f },
{ ImageID("I_R"), 0.0f },
{ ImageID("I_START"), 0.0f },
{ ImageID("I_SELECT"), 0.0f },
{ ImageID("I_CROSS"), 45.0f },
{ ImageID("I_SQUARE"), 45.0f },
{ ImageID("I_TRIANGLE"), 180.0f },
{ ImageID("I_ARROW"), 90.0f},
{ ImageID("I_ARROW"), 270.0f},
{ ImageID("I_ARROW"), 0.0f},
{ ImageID("I_ARROW"), 180.0f},
{ ImageID("I_GEAR"), 0.0f},
{ ImageID("I_ROTATE_LEFT"), 0.0f},
{ ImageID("I_ROTATE_RIGHT"), 0.0f},
{ ImageID("I_ARROW_LEFT"), 0.0f},
{ ImageID("I_ARROW_RIGHT"), 0.0f},
{ ImageID("I_ARROW_UP"), 0.0f},
{ ImageID("I_ARROW_DOWN"), 0.0f},
{ ImageID("I_THREE_DOTS"), 0.0f},
{ ImageID("I_EMPTY"), 0.0f},
};
// Shape list
struct keyShape {
ImageID i; // ImageID
ImageID l; // ImageID line version
float r; // Rotation angle in dregree
bool f; // Flip Horizontally
bool d; // Invert height and width for context dimension (for example for 90 degree rot)
};
static const keyShape customKeyShapes[] = {
{ ImageID("I_ROUND"), ImageID("I_ROUND_LINE"), 0.0f, false, false },
{ ImageID("I_RECT"), ImageID("I_RECT_LINE"), 0.0f, false, false },
{ ImageID("I_RECT"), ImageID("I_RECT_LINE"), 90.0f, false, true },
{ ImageID("I_SHOULDER"), ImageID("I_SHOULDER_LINE"), 0.0f, false, false },
{ ImageID("I_SHOULDER"), ImageID("I_SHOULDER_LINE"), 0.0f, true, false },
{ ImageID("I_DIR"), ImageID("I_DIR_LINE"), 270.0f, false, true },
{ ImageID("I_DIR"), ImageID("I_DIR_LINE"), 90.0f, false, true },
{ ImageID("I_DIR"), ImageID("I_DIR_LINE"), 180.0f, false, false },
{ ImageID("I_DIR"), ImageID("I_DIR_LINE"), 0.0f, false, false },
{ ImageID("I_SQUARE_SHAPE"), ImageID("I_SQUARE_SHAPE_LINE"), 0.0f, false, false },
{ ImageID("I_EMPTY"), ImageID("I_EMPTY"), 0.0f, false, false },
};
// Button list
struct keyList {
ImageID i; // UI ImageID
uint32_t c; // Key code
};
// For CustomButton. NOTE: This list can NOT be freely reordered! We store a bitmask of the indices.
// NOTE 2: Unfortunately we messed up here, we should NOT have used ifdefs! This breaks the order.
static const keyList g_customKeyList[] = {
{ ImageID("I_SQUARE"), CTRL_SQUARE },
{ ImageID("I_TRIANGLE"), CTRL_TRIANGLE },
{ ImageID("I_CIRCLE"), CTRL_CIRCLE },
{ ImageID("I_CROSS"), CTRL_CROSS },
{ ImageID::invalid(), CTRL_UP },
{ ImageID::invalid(), CTRL_DOWN },
{ ImageID::invalid(), CTRL_LEFT },
{ ImageID::invalid(), CTRL_RIGHT },
{ ImageID("I_START"), CTRL_START },
{ ImageID("I_SELECT"), CTRL_SELECT },
{ ImageID("I_L"), CTRL_LTRIGGER },
{ ImageID("I_R"), CTRL_RTRIGGER },
{ ImageID::invalid(), VIRTKEY_RAPID_FIRE },
{ ImageID::invalid(), VIRTKEY_FASTFORWARD },
{ ImageID::invalid(), VIRTKEY_SPEED_TOGGLE },
{ ImageID::invalid(), VIRTKEY_REWIND },
{ ImageID::invalid(), VIRTKEY_SAVE_STATE },
{ ImageID::invalid(), VIRTKEY_LOAD_STATE },
{ ImageID::invalid(), VIRTKEY_NEXT_SLOT },
#if !defined(MOBILE_DEVICE) // BAD!!
{ ImageID::invalid(), VIRTKEY_TOGGLE_FULLSCREEN },
#endif
{ ImageID::invalid(), VIRTKEY_SPEED_CUSTOM1 },
{ ImageID::invalid(), VIRTKEY_SPEED_CUSTOM2 },
{ ImageID::invalid(), VIRTKEY_TEXTURE_DUMP },
{ ImageID::invalid(), VIRTKEY_TEXTURE_REPLACE },
{ ImageID::invalid(), VIRTKEY_SCREENSHOT },
{ ImageID::invalid(), VIRTKEY_MUTE_TOGGLE },
{ ImageID::invalid(), VIRTKEY_OPENCHAT },
{ ImageID::invalid(), VIRTKEY_ANALOG_ROTATE_CW },
{ ImageID::invalid(), VIRTKEY_ANALOG_ROTATE_CCW },
{ ImageID::invalid(), VIRTKEY_PAUSE },
{ ImageID::invalid(), VIRTKEY_RESET_EMULATION },
{ ImageID::invalid(), VIRTKEY_DEVMENU },
#ifndef MOBILE_DEVICE // BAD!!!
{ ImageID::invalid(), VIRTKEY_RECORD },
#endif
{ ImageID::invalid(), VIRTKEY_AXIS_X_MIN },
{ ImageID::invalid(), VIRTKEY_AXIS_Y_MIN },
{ ImageID::invalid(), VIRTKEY_AXIS_X_MAX },
{ ImageID::invalid(), VIRTKEY_AXIS_Y_MAX },
{ ImageID::invalid(), VIRTKEY_PREVIOUS_SLOT },
{ ImageID::invalid(), VIRTKEY_TOGGLE_TOUCH_CONTROLS }, // 38 if !MOBILE_DEVICE, 36 if MOBILE_DEVICE. See IsDownForFadeoutCheck
{ ImageID::invalid(), VIRTKEY_TOGGLE_DEBUGGER },
{ ImageID::invalid(), VIRTKEY_PAUSE_NO_MENU },
{ ImageID::invalid(), VIRTKEY_TOGGLE_TILT },
{ ImageID("I_HOME"), CTRL_HOME },
// IMPORTANT: Only add at the end!
};
static_assert(ARRAY_SIZE(g_customKeyList) <= 64, "Too many key for a uint64_t bit mask");
// image and shape come straight from the ini and index the tables above, so anything that
// reads them has to run this first.
inline void Sanitize(ConfigCustomButton &cfg) {
if (cfg.image < 0 || cfg.image >= (int)ARRAY_SIZE(customKeyImages)) {
cfg.image = 0;
}
if (cfg.shape < 0 || cfg.shape >= (int)ARRAY_SIZE(customKeyShapes)) {
cfg.shape = 0;
}
}
};
// Gesture key only have virtual button that can work without constant press
namespace GestureKey {
static const uint32_t keyList[] = {
CTRL_SQUARE,
CTRL_TRIANGLE,
CTRL_CIRCLE,
CTRL_CROSS,
CTRL_UP,
CTRL_DOWN,
CTRL_LEFT,
CTRL_RIGHT,
CTRL_START,
CTRL_SELECT,
CTRL_LTRIGGER,
CTRL_RTRIGGER,
VIRTKEY_AXIS_Y_MAX,
VIRTKEY_AXIS_Y_MIN,
VIRTKEY_AXIS_X_MIN,
VIRTKEY_AXIS_X_MAX,
VIRTKEY_SPEED_TOGGLE,
VIRTKEY_REWIND,
VIRTKEY_SAVE_STATE,
VIRTKEY_LOAD_STATE,
VIRTKEY_PREVIOUS_SLOT,
VIRTKEY_NEXT_SLOT,
VIRTKEY_TEXTURE_DUMP,
VIRTKEY_TEXTURE_REPLACE,
VIRTKEY_SCREENSHOT,
VIRTKEY_MUTE_TOGGLE,
VIRTKEY_OPENCHAT,
VIRTKEY_PAUSE,
VIRTKEY_DEVMENU,
#ifndef MOBILE_DEVICE
VIRTKEY_RECORD,
#endif
VIRTKEY_AXIS_RIGHT_X_MIN,
VIRTKEY_AXIS_RIGHT_Y_MIN,
VIRTKEY_AXIS_RIGHT_X_MAX,
VIRTKEY_AXIS_RIGHT_Y_MAX,
VIRTKEY_TOGGLE_DEBUGGER,
VIRTKEY_TOGGLE_TILT,
CTRL_HOME,
};
}
void GamepadTouch();
void GamepadResetTouch();
void GamepadUpdateOpacity(float force = -1.0f);
float GamepadGetOpacity();