mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-30 17:02:19 +00:00
4f1be34f24
This reverts commit ea0eaa31e6
.
1244 lines
40 KiB
C++
1244 lines
40 KiB
C++
// Copyright (c) 2013- 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 "ppsspp_config.h"
|
|
#include <algorithm>
|
|
#include <deque>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
|
|
#include "Common/Render/TextureAtlas.h"
|
|
#include "Common/UI/Root.h"
|
|
#include "Common/UI/UI.h"
|
|
#include "Common/UI/Context.h"
|
|
#include "Common/UI/View.h"
|
|
#include "Common/UI/ViewGroup.h"
|
|
#include "Common/VR/PPSSPPVR.h"
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/Data/Color/RGBAUtil.h"
|
|
#include "Common/Data/Text/I18n.h"
|
|
#include "Common/Input/KeyCodes.h"
|
|
#include "Common/Input/InputState.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/System/Display.h"
|
|
#include "Common/System/System.h"
|
|
#include "Common/System/Request.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Core/KeyMap.h"
|
|
#include "Core/HLE/sceCtrl.h"
|
|
#include "Core/System.h"
|
|
#include "Core/Config.h"
|
|
#include "UI/ControlMappingScreen.h"
|
|
#include "UI/GameSettingsScreen.h"
|
|
#include "UI/JoystickHistoryView.h"
|
|
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
#include "android/jni/app-android.h"
|
|
#endif
|
|
|
|
using KeyMap::MultiInputMapping;
|
|
|
|
class SingleControlMapper : public UI::LinearLayout {
|
|
public:
|
|
SingleControlMapper(int pspKey, std::string keyName, ScreenManager *scrm, UI::LinearLayoutParams *layoutParams = nullptr);
|
|
|
|
int GetPspKey() const { return pspKey_; }
|
|
|
|
private:
|
|
void Refresh();
|
|
|
|
UI::EventReturn OnAdd(UI::EventParams ¶ms);
|
|
UI::EventReturn OnAddMouse(UI::EventParams ¶ms);
|
|
UI::EventReturn OnDelete(UI::EventParams ¶ms);
|
|
UI::EventReturn OnReplace(UI::EventParams ¶ms);
|
|
UI::EventReturn OnReplaceAll(UI::EventParams ¶ms);
|
|
|
|
void MappedCallback(MultiInputMapping key);
|
|
|
|
enum Action {
|
|
NONE,
|
|
REPLACEONE,
|
|
REPLACEALL,
|
|
ADD,
|
|
};
|
|
|
|
UI::Choice *addButton_ = nullptr;
|
|
UI::Choice *replaceAllButton_ = nullptr;
|
|
std::vector<UI::View *> rows_;
|
|
Action action_ = NONE;
|
|
int actionIndex_;
|
|
int pspKey_;
|
|
std::string keyName_;
|
|
ScreenManager *scrm_;
|
|
};
|
|
|
|
SingleControlMapper::SingleControlMapper(int pspKey, std::string keyName, ScreenManager *scrm, UI::LinearLayoutParams *layoutParams)
|
|
: UI::LinearLayout(UI::ORIENT_VERTICAL, layoutParams), pspKey_(pspKey), keyName_(keyName), scrm_(scrm) {
|
|
Refresh();
|
|
}
|
|
|
|
void SingleControlMapper::Refresh() {
|
|
Clear();
|
|
auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);
|
|
|
|
std::map<std::string, ImageID> keyImages;
|
|
keyImages["Circle"] = ImageID("I_CIRCLE");
|
|
keyImages["Cross"] = ImageID("I_CROSS");
|
|
keyImages["Square"] = ImageID("I_SQUARE");
|
|
keyImages["Triangle"] = ImageID("I_TRIANGLE");
|
|
keyImages["Start"] = ImageID("I_START");
|
|
keyImages["Select"] = ImageID("I_SELECT");
|
|
keyImages["L"] = ImageID("I_L");
|
|
keyImages["R"] = ImageID("I_R");
|
|
|
|
using namespace UI;
|
|
|
|
float itemH = 55.0f;
|
|
|
|
float leftColumnWidth = 200;
|
|
float rightColumnWidth = 350; // TODO: Should be flexible somehow. Maybe we need to implement Measure.
|
|
|
|
LinearLayout *root = Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
|
|
root->SetSpacing(3.0f);
|
|
|
|
auto iter = keyImages.find(keyName_);
|
|
// First, look among images.
|
|
if (iter != keyImages.end()) {
|
|
replaceAllButton_ = new Choice(iter->second, new LinearLayoutParams(leftColumnWidth, itemH));
|
|
} else {
|
|
// No image? Let's translate.
|
|
replaceAllButton_ = new Choice(mc->T(keyName_.c_str()), new LinearLayoutParams(leftColumnWidth, itemH));
|
|
replaceAllButton_->SetCentered(true);
|
|
}
|
|
root->Add(replaceAllButton_)->OnClick.Handle(this, &SingleControlMapper::OnReplaceAll);
|
|
|
|
addButton_ = root->Add(new Choice(" + ", new LayoutParams(WRAP_CONTENT, itemH)));
|
|
addButton_->OnClick.Handle(this, &SingleControlMapper::OnAdd);
|
|
if (g_Config.bMouseControl) {
|
|
Choice *p = root->Add(new Choice("M", new LayoutParams(WRAP_CONTENT, itemH)));
|
|
p->OnClick.Handle(this, &SingleControlMapper::OnAddMouse);
|
|
}
|
|
|
|
LinearLayout *rightColumn = root->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(rightColumnWidth, WRAP_CONTENT)));
|
|
rightColumn->SetSpacing(2.0f);
|
|
std::vector<MultiInputMapping> mappings;
|
|
KeyMap::InputMappingsFromPspButton(pspKey_, &mappings, false);
|
|
|
|
rows_.clear();
|
|
for (size_t i = 0; i < mappings.size(); i++) {
|
|
std::string multiMappingString = mappings[i].ToVisualString();
|
|
LinearLayout *row = rightColumn->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
|
|
row->SetSpacing(2.0f);
|
|
rows_.push_back(row);
|
|
|
|
Choice *c = row->Add(new Choice(multiMappingString, new LinearLayoutParams(FILL_PARENT, itemH, 1.0f)));
|
|
c->SetTag(StringFromFormat("%d_Change%d", (int)i, pspKey_));
|
|
c->OnClick.Handle(this, &SingleControlMapper::OnReplace);
|
|
|
|
Choice *d = row->Add(new Choice(ImageID("I_TRASHCAN"), new LayoutParams(WRAP_CONTENT, itemH)));
|
|
d->SetTag(StringFromFormat("%d_Del%d", (int)i, pspKey_));
|
|
d->OnClick.Handle(this, &SingleControlMapper::OnDelete);
|
|
}
|
|
|
|
if (mappings.size() == 0) {
|
|
// look like an empty line
|
|
Choice *c = rightColumn->Add(new Choice("", new LinearLayoutParams(FILL_PARENT, itemH)));
|
|
c->OnClick.Handle(this, &SingleControlMapper::OnAdd);
|
|
}
|
|
}
|
|
|
|
void SingleControlMapper::MappedCallback(MultiInputMapping kdf) {
|
|
if (kdf.empty()) {
|
|
// Don't want to try to add this.
|
|
return;
|
|
}
|
|
|
|
switch (action_) {
|
|
case ADD:
|
|
KeyMap::SetInputMapping(pspKey_, kdf, false);
|
|
addButton_->SetFocus();
|
|
break;
|
|
case REPLACEALL:
|
|
KeyMap::SetInputMapping(pspKey_, kdf, true);
|
|
replaceAllButton_->SetFocus();
|
|
break;
|
|
case REPLACEONE:
|
|
{
|
|
bool success = KeyMap::ReplaceSingleKeyMapping(pspKey_, actionIndex_, kdf);
|
|
if (!success) {
|
|
replaceAllButton_->SetFocus(); // Last got removed as a duplicate
|
|
} else if (actionIndex_ < (int)rows_.size()) {
|
|
rows_[actionIndex_]->SetFocus();
|
|
} else {
|
|
SetFocus();
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
SetFocus();
|
|
break;
|
|
}
|
|
KeyMap::UpdateNativeMenuKeys();
|
|
g_Config.bMapMouse = false;
|
|
}
|
|
|
|
UI::EventReturn SingleControlMapper::OnReplace(UI::EventParams ¶ms) {
|
|
actionIndex_ = atoi(params.v->Tag().c_str());
|
|
action_ = REPLACEONE;
|
|
scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&SingleControlMapper::MappedCallback, this, std::placeholders::_1), I18NCat::KEYMAPPING));
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn SingleControlMapper::OnReplaceAll(UI::EventParams ¶ms) {
|
|
action_ = REPLACEALL;
|
|
scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&SingleControlMapper::MappedCallback, this, std::placeholders::_1), I18NCat::KEYMAPPING));
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn SingleControlMapper::OnAdd(UI::EventParams ¶ms) {
|
|
action_ = ADD;
|
|
scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&SingleControlMapper::MappedCallback, this, std::placeholders::_1), I18NCat::KEYMAPPING));
|
|
return UI::EVENT_DONE;
|
|
}
|
|
UI::EventReturn SingleControlMapper::OnAddMouse(UI::EventParams ¶ms) {
|
|
action_ = ADD;
|
|
g_Config.bMapMouse = true;
|
|
scrm_->push(new KeyMappingNewMouseKeyDialog(pspKey_, true, std::bind(&SingleControlMapper::MappedCallback, this, std::placeholders::_1), I18NCat::KEYMAPPING));
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn SingleControlMapper::OnDelete(UI::EventParams ¶ms) {
|
|
int index = atoi(params.v->Tag().c_str());
|
|
KeyMap::DeleteNthMapping(pspKey_, index);
|
|
|
|
if (index + 1 < (int)rows_.size())
|
|
rows_[index]->SetFocus();
|
|
else
|
|
SetFocus();
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
void ControlMappingScreen::CreateViews() {
|
|
using namespace UI;
|
|
mappers_.clear();
|
|
|
|
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
|
|
|
|
root_ = new LinearLayout(ORIENT_HORIZONTAL);
|
|
|
|
LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(200, FILL_PARENT, Margins(10, 0, 0, 10)));
|
|
leftColumn->Add(new Choice(km->T("Clear All")))->OnClick.Handle(this, &ControlMappingScreen::OnClearMapping);
|
|
leftColumn->Add(new Choice(km->T("Default All")))->OnClick.Handle(this, &ControlMappingScreen::OnDefaultMapping);
|
|
|
|
std::string sysName = System_GetProperty(SYSPROP_NAME);
|
|
// If there's a builtin controller, restore to default should suffice. No need to conf the controller on top.
|
|
if (!KeyMap::HasBuiltinController(sysName) && KeyMap::GetSeenPads().size()) {
|
|
leftColumn->Add(new Choice(km->T("Autoconfigure")))->OnClick.Handle(this, &ControlMappingScreen::OnAutoConfigure);
|
|
}
|
|
|
|
leftColumn->Add(new Choice(km->T("Show PSP")))->OnClick.Handle(this, &ControlMappingScreen::OnVisualizeMapping);
|
|
|
|
leftColumn->Add(new Spacer(new LinearLayoutParams(1.0f)));
|
|
AddStandardBack(leftColumn);
|
|
|
|
rightScroll_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f));
|
|
rightScroll_->SetTag("ControlMapping");
|
|
LinearLayout *rightColumn = new LinearLayoutList(ORIENT_VERTICAL);
|
|
rightScroll_->Add(rightColumn);
|
|
|
|
root_->Add(leftColumn);
|
|
root_->Add(rightScroll_);
|
|
|
|
std::vector<KeyMap::KeyMap_IntStrPair> mappableKeys = KeyMap::GetMappableKeys();
|
|
for (size_t i = 0; i < mappableKeys.size(); i++) {
|
|
SingleControlMapper *mapper = rightColumn->Add(
|
|
new SingleControlMapper(mappableKeys[i].key, mappableKeys[i].name, screenManager(),
|
|
new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
|
|
mapper->SetTag(StringFromFormat("KeyMap%s", mappableKeys[i].name));
|
|
mappers_.push_back(mapper);
|
|
}
|
|
|
|
keyMapGeneration_ = KeyMap::g_controllerMapGeneration;
|
|
}
|
|
|
|
void ControlMappingScreen::update() {
|
|
if (KeyMap::HasChanged(keyMapGeneration_)) {
|
|
RecreateViews();
|
|
}
|
|
|
|
UIDialogScreenWithGameBackground::update();
|
|
SetVRAppMode(VRAppMode::VR_MENU_MODE);
|
|
}
|
|
|
|
UI::EventReturn ControlMappingScreen::OnClearMapping(UI::EventParams ¶ms) {
|
|
KeyMap::ClearAllMappings();
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn ControlMappingScreen::OnDefaultMapping(UI::EventParams ¶ms) {
|
|
KeyMap::RestoreDefault();
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn ControlMappingScreen::OnAutoConfigure(UI::EventParams ¶ms) {
|
|
std::vector<std::string> items;
|
|
const auto seenPads = KeyMap::GetSeenPads();
|
|
for (auto s = seenPads.begin(), end = seenPads.end(); s != end; ++s) {
|
|
items.push_back(*s);
|
|
}
|
|
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
|
|
UI::ListPopupScreen *autoConfList = new UI::ListPopupScreen(km->T("Autoconfigure for device"), items, -1);
|
|
if (params.v)
|
|
autoConfList->SetPopupOrigin(params.v);
|
|
screenManager()->push(autoConfList);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn ControlMappingScreen::OnVisualizeMapping(UI::EventParams ¶ms) {
|
|
VisualMappingScreen *visualMapping = new VisualMappingScreen(gamePath_);
|
|
screenManager()->push(visualMapping);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
void ControlMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) {
|
|
if (result == DR_OK && std::string(dialog->tag()) == "listpopup") {
|
|
UI::ListPopupScreen *popup = (UI::ListPopupScreen *)dialog;
|
|
KeyMap::AutoConfForPad(popup->GetChoiceString());
|
|
}
|
|
}
|
|
|
|
void KeyMappingNewKeyDialog::CreatePopupContents(UI::ViewGroup *parent) {
|
|
using namespace UI;
|
|
|
|
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
|
|
auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);
|
|
|
|
std::string pspButtonName = KeyMap::GetPspButtonName(this->pspBtn_);
|
|
|
|
parent->Add(new TextView(std::string(km->T("Map a new key for")) + " " + mc->T(pspButtonName), new LinearLayoutParams(Margins(10, 0))));
|
|
parent->Add(new TextView(std::string(mapping_.ToVisualString()), new LinearLayoutParams(Margins(10, 0))));
|
|
|
|
SetVRAppMode(VRAppMode::VR_CONTROLLER_MAPPING_MODE);
|
|
}
|
|
|
|
bool KeyMappingNewKeyDialog::key(const KeyInput &key) {
|
|
if (ignoreInput_)
|
|
return true;
|
|
if (time_now_d() < delayUntil_)
|
|
return true;
|
|
if (key.flags & KEY_DOWN) {
|
|
if (key.keyCode == NKCODE_EXT_MOUSEBUTTON_1) {
|
|
// Don't map
|
|
return true;
|
|
}
|
|
|
|
if (pspBtn_ == VIRTKEY_SPEED_ANALOG && !UI::IsEscapeKey(key)) {
|
|
// Only map analog values to this mapping.
|
|
return true;
|
|
}
|
|
|
|
InputMapping newMapping(key.deviceId, key.keyCode);
|
|
|
|
if (!(key.flags & KEY_IS_REPEAT)) {
|
|
if (!mapping_.mappings.contains(newMapping)) {
|
|
mapping_.mappings.push_back(newMapping);
|
|
RecreateViews();
|
|
}
|
|
}
|
|
}
|
|
if (key.flags & KEY_UP) {
|
|
// If the key released wasn't part of the mapping, ignore it here. Some device can cause
|
|
// stray key-up events.
|
|
InputMapping upMapping(key.deviceId, key.keyCode);
|
|
if (!mapping_.mappings.contains(upMapping)) {
|
|
return true;
|
|
}
|
|
|
|
if (callback_)
|
|
callback_(mapping_);
|
|
TriggerFinish(DR_YES);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void KeyMappingNewKeyDialog::SetDelay(float t) {
|
|
delayUntil_ = time_now_d() + t;
|
|
}
|
|
|
|
void KeyMappingNewMouseKeyDialog::CreatePopupContents(UI::ViewGroup *parent) {
|
|
using namespace UI;
|
|
|
|
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
|
|
|
|
parent->Add(new TextView(std::string(km->T("You can press ESC to cancel.")), new LinearLayoutParams(Margins(10, 0))));
|
|
SetVRAppMode(VRAppMode::VR_CONTROLLER_MAPPING_MODE);
|
|
}
|
|
|
|
bool KeyMappingNewMouseKeyDialog::key(const KeyInput &key) {
|
|
if (mapped_)
|
|
return false;
|
|
if (ignoreInput_)
|
|
return true;
|
|
if (key.flags & KEY_DOWN) {
|
|
if (key.keyCode == NKCODE_ESCAPE) {
|
|
TriggerFinish(DR_OK);
|
|
g_Config.bMapMouse = false;
|
|
return false;
|
|
}
|
|
|
|
mapped_ = true;
|
|
|
|
MultiInputMapping kdf(InputMapping(key.deviceId, key.keyCode));
|
|
|
|
TriggerFinish(DR_YES);
|
|
g_Config.bMapMouse = false;
|
|
if (callback_)
|
|
callback_(kdf);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool IgnoreAxisForMapping(int axis) {
|
|
switch (axis) {
|
|
case JOYSTICK_AXIS_ACCELEROMETER_X:
|
|
case JOYSTICK_AXIS_ACCELEROMETER_Y:
|
|
case JOYSTICK_AXIS_ACCELEROMETER_Z:
|
|
// Ignore the accelerometer for mapping for now.
|
|
// We use tilt control for these.
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void KeyMappingNewKeyDialog::axis(const AxisInput &axis) {
|
|
if (time_now_d() < delayUntil_)
|
|
return;
|
|
if (IgnoreAxisForMapping(axis.axisId))
|
|
return;
|
|
if (ignoreInput_)
|
|
return;
|
|
|
|
if (axis.value > AXIS_BIND_THRESHOLD) {
|
|
InputMapping mapping(axis.deviceId, axis.axisId, 1);
|
|
triggeredAxes_.insert(mapping);
|
|
if (!mapping_.mappings.contains(mapping)) {
|
|
mapping_.mappings.push_back(mapping);
|
|
RecreateViews();
|
|
}
|
|
} else if (axis.value < -AXIS_BIND_THRESHOLD) {
|
|
InputMapping mapping(axis.deviceId, axis.axisId, -1);
|
|
triggeredAxes_.insert(mapping);
|
|
if (!mapping_.mappings.contains(mapping)) {
|
|
mapping_.mappings.push_back(mapping);
|
|
RecreateViews();
|
|
}
|
|
} else if (fabsf(axis.value) < AXIS_BIND_RELEASE_THRESHOLD) {
|
|
InputMapping neg(axis.deviceId, axis.axisId, -1);
|
|
InputMapping pos(axis.deviceId, axis.axisId, 1);
|
|
if (triggeredAxes_.find(neg) != triggeredAxes_.end() || triggeredAxes_.find(pos) != triggeredAxes_.end()) {
|
|
// "Key-up" the axis.
|
|
TriggerFinish(DR_YES);
|
|
if (callback_)
|
|
callback_(mapping_);
|
|
}
|
|
}
|
|
}
|
|
|
|
void KeyMappingNewMouseKeyDialog::axis(const AxisInput &axis) {
|
|
if (mapped_)
|
|
return;
|
|
if (IgnoreAxisForMapping(axis.axisId))
|
|
return;
|
|
|
|
if (axis.value > AXIS_BIND_THRESHOLD) {
|
|
mapped_ = true;
|
|
MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, 1));
|
|
TriggerFinish(DR_YES);
|
|
if (callback_)
|
|
callback_(kdf);
|
|
}
|
|
|
|
if (axis.value < -AXIS_BIND_THRESHOLD) {
|
|
mapped_ = true;
|
|
MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, -1));
|
|
TriggerFinish(DR_YES);
|
|
if (callback_)
|
|
callback_(kdf);
|
|
}
|
|
}
|
|
|
|
AnalogSetupScreen::AnalogSetupScreen(const Path &gamePath) : UIDialogScreenWithGameBackground(gamePath) {
|
|
mapper_.SetCallbacks(
|
|
[](int vkey, bool down) {},
|
|
[](int vkey, float analogValue) {},
|
|
[&](uint32_t bitsToSet, uint32_t bitsToClear) {},
|
|
[&](int stick, float x, float y) {
|
|
analogX_[stick] = x;
|
|
analogY_[stick] = y;
|
|
},
|
|
[&](int stick, float x, float y) {
|
|
rawX_[stick] = x;
|
|
rawY_[stick] = y;
|
|
});
|
|
}
|
|
|
|
void AnalogSetupScreen::update() {
|
|
mapper_.Update();
|
|
// We ignore the secondary stick for now and just use the two views
|
|
// for raw and psp input.
|
|
if (stickView_[0]) {
|
|
stickView_[0]->SetXY(analogX_[0], analogY_[0]);
|
|
}
|
|
if (stickView_[1]) {
|
|
stickView_[1]->SetXY(rawX_[0], rawY_[0]);
|
|
}
|
|
UIScreen::update();
|
|
}
|
|
|
|
bool AnalogSetupScreen::key(const KeyInput &key) {
|
|
bool retval = UIScreen::key(key);
|
|
|
|
// Allow testing auto-rotation. If it collides with UI keys, too bad.
|
|
bool pauseTrigger = false;
|
|
mapper_.Key(key, &pauseTrigger);
|
|
|
|
if (UI::IsEscapeKey(key)) {
|
|
TriggerFinish(DR_BACK);
|
|
return retval;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
void AnalogSetupScreen::axis(const AxisInput &axis) {
|
|
// We DON'T call UIScreen::Axis here! Otherwise it'll try to move the UI focus around.
|
|
// UIScreen::axis(axis);
|
|
|
|
// Instead we just send the input directly to the mapper, that we'll visualize.
|
|
mapper_.Axis(axis);
|
|
}
|
|
|
|
void AnalogSetupScreen::CreateViews() {
|
|
using namespace UI;
|
|
|
|
auto di = GetI18NCategory(I18NCat::DIALOG);
|
|
|
|
root_ = new LinearLayout(ORIENT_HORIZONTAL);
|
|
|
|
LinearLayout *leftColumn = root_->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300.0f, FILL_PARENT)));
|
|
LinearLayout *rightColumn = root_->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f)));
|
|
|
|
auto co = GetI18NCategory(I18NCat::CONTROLS);
|
|
ScrollView *scroll = leftColumn->Add(new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0)));
|
|
|
|
LinearLayout *scrollContents = scroll->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300.0f, WRAP_CONTENT)));
|
|
|
|
scrollContents->Add(new ItemHeader(co->T("Analog Settings", "Analog Settings")));
|
|
|
|
// TODO: Would be nicer if these didn't pop up...
|
|
scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogDeadzone, 0.0f, 0.5f, 0.15f, co->T("Deadzone radius"), 0.01f, screenManager(), "/ 1.0"));
|
|
scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogInverseDeadzone, 0.0f, 1.0f, 0.0f, co->T("Low end radius"), 0.01f, screenManager(), "/ 1.0"));
|
|
scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogSensitivity, 0.0f, 2.0f, 1.1f, co->T("Sensitivity (scale)", "Sensitivity"), 0.01f, screenManager(), "x"));
|
|
// TODO: This should probably be a slider.
|
|
scrollContents->Add(new CheckBox(&g_Config.bAnalogIsCircular, co->T("Circular stick input")));
|
|
scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogAutoRotSpeed, 0.1f, 20.0f, 8.0f, co->T("Auto-rotation speed"), 1.0f, screenManager()));
|
|
scrollContents->Add(new Choice(co->T("Reset to defaults")))->OnClick.Handle(this, &AnalogSetupScreen::OnResetToDefaults);
|
|
|
|
LinearLayout *theTwo = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(1.0f));
|
|
|
|
stickView_[0] = theTwo->Add(new JoystickHistoryView(StickHistoryViewType::OUTPUT, co->T("Calibrated"), new LinearLayoutParams(1.0f)));
|
|
stickView_[1] = theTwo->Add(new JoystickHistoryView(StickHistoryViewType::INPUT, co->T("Raw input"), new LinearLayoutParams(1.0f)));
|
|
|
|
rightColumn->Add(theTwo);
|
|
|
|
leftColumn->Add(new Button(di->T("Back"), new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
|
|
}
|
|
|
|
UI::EventReturn AnalogSetupScreen::OnResetToDefaults(UI::EventParams &e) {
|
|
g_Config.fAnalogDeadzone = 0.15f;
|
|
g_Config.fAnalogInverseDeadzone = 0.0f;
|
|
g_Config.fAnalogSensitivity = 1.1f;
|
|
g_Config.bAnalogIsCircular = false;
|
|
g_Config.fAnalogAutoRotSpeed = 8.0f;
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
void TouchTestScreen::touch(const TouchInput &touch) {
|
|
UIDialogScreenWithGameBackground::touch(touch);
|
|
if (touch.flags & TOUCH_DOWN) {
|
|
bool found = false;
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; i++) {
|
|
if (touches_[i].id == touch.id) {
|
|
WARN_LOG(SYSTEM, "Double touch");
|
|
touches_[i].x = touch.x;
|
|
touches_[i].y = touch.y;
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found) {
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; i++) {
|
|
if (touches_[i].id == -1) {
|
|
touches_[i].id = touch.id;
|
|
touches_[i].x = touch.x;
|
|
touches_[i].y = touch.y;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (touch.flags & TOUCH_MOVE) {
|
|
bool found = false;
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; i++) {
|
|
if (touches_[i].id == touch.id) {
|
|
touches_[i].x = touch.x;
|
|
touches_[i].y = touch.y;
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found) {
|
|
WARN_LOG(SYSTEM, "Move without touch down: %d", touch.id);
|
|
}
|
|
}
|
|
if (touch.flags & TOUCH_UP) {
|
|
bool found = false;
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; i++) {
|
|
if (touches_[i].id == touch.id) {
|
|
found = true;
|
|
touches_[i].id = -1;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
WARN_LOG(SYSTEM, "Touch release without touch down");
|
|
}
|
|
}
|
|
}
|
|
|
|
void TouchTestScreen::CreateViews() {
|
|
using namespace UI;
|
|
|
|
auto di = GetI18NCategory(I18NCat::DIALOG);
|
|
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
|
|
root_ = new LinearLayout(ORIENT_VERTICAL);
|
|
LinearLayout *theTwo = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f));
|
|
|
|
lastLastKeyEvent_ = theTwo->Add(new TextView("-", new LayoutParams(FILL_PARENT, WRAP_CONTENT)));
|
|
lastLastKeyEvent_->SetTextColor(0x80FFFFFF); // semi-transparent
|
|
lastKeyEvent_ = theTwo->Add(new TextView("-", new LayoutParams(FILL_PARENT, WRAP_CONTENT)));
|
|
|
|
root_->Add(theTwo);
|
|
|
|
#if !PPSSPP_PLATFORM(UWP)
|
|
static const char *renderingBackend[] = { "OpenGL", "Direct3D 9", "Direct3D 11", "Vulkan" };
|
|
PopupMultiChoice *renderingBackendChoice = root_->Add(new PopupMultiChoice(&g_Config.iGPUBackend, gr->T("Backend"), renderingBackend, (int)GPUBackend::OPENGL, ARRAY_SIZE(renderingBackend), I18NCat::GRAPHICS, screenManager()));
|
|
renderingBackendChoice->OnChoice.Handle(this, &TouchTestScreen::OnRenderingBackend);
|
|
|
|
if (!g_Config.IsBackendEnabled(GPUBackend::OPENGL))
|
|
renderingBackendChoice->HideChoice((int)GPUBackend::OPENGL);
|
|
if (!g_Config.IsBackendEnabled(GPUBackend::DIRECT3D9))
|
|
renderingBackendChoice->HideChoice((int)GPUBackend::DIRECT3D9);
|
|
if (!g_Config.IsBackendEnabled(GPUBackend::DIRECT3D11))
|
|
renderingBackendChoice->HideChoice((int)GPUBackend::DIRECT3D11);
|
|
if (!g_Config.IsBackendEnabled(GPUBackend::VULKAN))
|
|
renderingBackendChoice->HideChoice((int)GPUBackend::VULKAN);
|
|
#endif
|
|
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
root_->Add(new Choice(gr->T("Recreate Activity")))->OnClick.Handle(this, &TouchTestScreen::OnRecreateActivity);
|
|
#endif
|
|
root_->Add(new CheckBox(&g_Config.bImmersiveMode, gr->T("FullScreen", "Full Screen")))->OnClick.Handle(this, &TouchTestScreen::OnImmersiveModeChange);
|
|
root_->Add(new Button(di->T("Back")))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
|
|
}
|
|
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
extern int display_xres;
|
|
extern int display_yres;
|
|
#endif
|
|
|
|
bool TouchTestScreen::key(const KeyInput &key) {
|
|
char buf[512];
|
|
snprintf(buf, sizeof(buf), "Keycode: %d Device ID: %d [%s%s%s%s]", key.keyCode, key.deviceId,
|
|
(key.flags & KEY_IS_REPEAT) ? "REP" : "",
|
|
(key.flags & KEY_UP) ? "UP" : "",
|
|
(key.flags & KEY_DOWN) ? "DOWN" : "",
|
|
(key.flags & KEY_CHAR) ? "CHAR" : "");
|
|
if (lastLastKeyEvent_ && lastKeyEvent_) {
|
|
lastLastKeyEvent_->SetText(lastKeyEvent_->GetText());
|
|
lastKeyEvent_->SetText(buf);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TouchTestScreen::axis(const AxisInput &axis) {
|
|
// This is mainly to catch axis events that would otherwise get translated
|
|
// into arrow keys, since seeing keyboard arrow key events appear when using
|
|
// a controller would be confusing for the user.
|
|
if (IgnoreAxisForMapping(axis.axisId))
|
|
return;
|
|
|
|
const float AXIS_LOG_THRESHOLD = AXIS_BIND_THRESHOLD * 0.5f;
|
|
if (axis.value > AXIS_LOG_THRESHOLD || axis.value < -AXIS_LOG_THRESHOLD) {
|
|
char buf[512];
|
|
snprintf(buf, sizeof(buf), "Axis: %d (value %1.3f) Device ID: %d",
|
|
axis.axisId, axis.value, axis.deviceId);
|
|
// Null-check just in case they weren't created yet.
|
|
if (lastLastKeyEvent_ && lastKeyEvent_) {
|
|
lastLastKeyEvent_->SetText(lastKeyEvent_->GetText());
|
|
lastKeyEvent_->SetText(buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TouchTestScreen::render() {
|
|
UIDialogScreenWithGameBackground::render();
|
|
UIContext *ui_context = screenManager()->getUIContext();
|
|
Bounds bounds = ui_context->GetLayoutBounds();
|
|
|
|
ui_context->BeginNoTex();
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; i++) {
|
|
if (touches_[i].id != -1) {
|
|
ui_context->Draw()->Circle(touches_[i].x, touches_[i].y, 100.0, 3.0, 80, 0.0f, 0xFFFFFFFF, 1.0);
|
|
}
|
|
}
|
|
ui_context->Flush();
|
|
|
|
ui_context->Begin();
|
|
|
|
char buffer[4096];
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; i++) {
|
|
if (touches_[i].id != -1) {
|
|
ui_context->Draw()->Circle(touches_[i].x, touches_[i].y, 100.0, 3.0, 80, 0.0f, 0xFFFFFFFF, 1.0);
|
|
snprintf(buffer, sizeof(buffer), "%0.1fx%0.1f", touches_[i].x, touches_[i].y);
|
|
ui_context->DrawText(buffer, touches_[i].x, touches_[i].y + (touches_[i].y > g_display.dp_yres - 100.0f ? -135.0f : 95.0f), 0xFFFFFFFF, ALIGN_HCENTER | FLAG_DYNAMIC_ASCII);
|
|
}
|
|
}
|
|
|
|
char extra_debug[2048]{};
|
|
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
truncate_cpy(extra_debug, Android_GetInputDeviceDebugString().c_str());
|
|
#endif
|
|
|
|
snprintf(buffer, sizeof(buffer),
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
"display_res: %dx%d\n"
|
|
#endif
|
|
"dp_res: %dx%d pixel_res: %dx%d\n"
|
|
"g_dpi: %0.3f g_dpi_scale: %0.3fx%0.3f\n"
|
|
"g_dpi_scale_real: %0.3fx%0.3f\n%s",
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
display_xres, display_yres,
|
|
#endif
|
|
g_display.dp_xres, g_display.dp_yres,
|
|
g_display.pixel_xres, g_display.pixel_yres,
|
|
g_display.dpi,
|
|
g_display.dpi_scale_x, g_display.dpi_scale_y,
|
|
g_display.dpi_scale_real_x, g_display.dpi_scale_real_y, extra_debug);
|
|
|
|
// On Android, also add joystick debug data.
|
|
|
|
|
|
ui_context->DrawTextShadow(buffer, bounds.centerX(), bounds.y + 20.0f, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
|
|
ui_context->Flush();
|
|
}
|
|
|
|
void RecreateActivity() {
|
|
const int SYSTEM_JELLYBEAN = 16;
|
|
if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= SYSTEM_JELLYBEAN) {
|
|
INFO_LOG(SYSTEM, "Sending recreate");
|
|
System_Notify(SystemNotification::FORCE_RECREATE_ACTIVITY);
|
|
INFO_LOG(SYSTEM, "Got back from recreate");
|
|
} else {
|
|
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
|
|
System_Toast(gr->T("Must Restart", "You must restart PPSSPP for this change to take effect"));
|
|
}
|
|
}
|
|
|
|
UI::EventReturn TouchTestScreen::OnImmersiveModeChange(UI::EventParams &e) {
|
|
System_Notify(SystemNotification::IMMERSIVE_MODE_CHANGE);
|
|
if (g_Config.iAndroidHwScale != 0) {
|
|
RecreateActivity();
|
|
}
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn TouchTestScreen::OnRenderingBackend(UI::EventParams &e) {
|
|
g_Config.Save("GameSettingsScreen::RenderingBackend");
|
|
System_RestartApp("--touchscreentest");
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn TouchTestScreen::OnRecreateActivity(UI::EventParams &e) {
|
|
RecreateActivity();
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
class Backplate : public UI::InertView {
|
|
public:
|
|
Backplate(float scale, UI::LayoutParams *layoutParams = nullptr) : InertView(layoutParams), scale_(scale) {
|
|
}
|
|
|
|
void Draw(UIContext &dc) override {
|
|
for (float dy = 0.0f; dy <= 4.0f; dy += 1.0f) {
|
|
for (float dx = 0.0f; dx <= 4.0f; dx += 1.0f) {
|
|
if (dx == 2.0f && dy == 2.0f)
|
|
continue;
|
|
DrawPSP(dc, dx, dy, 0x06C1B6B6);
|
|
}
|
|
}
|
|
DrawPSP(dc, 2.0f, 2.0f, 0xC01C1818);
|
|
}
|
|
|
|
void DrawPSP(UIContext &dc, float xoff, float yoff, uint32_t color) {
|
|
using namespace UI;
|
|
|
|
const AtlasImage *whiteImage = dc.Draw()->GetAtlas()->getImage(dc.theme->whiteImage);
|
|
float centerU = (whiteImage->u1 + whiteImage->u2) * 0.5f;
|
|
float centerV = (whiteImage->v1 + whiteImage->v2) * 0.5f;
|
|
|
|
auto V = [&](float x, float y) {
|
|
dc.Draw()->V(bounds_.x + (x + xoff) * scale_, bounds_.y + (y + yoff) * scale_, color, centerU, centerV);
|
|
};
|
|
auto R = [&](float x1, float y1, float x2, float y2) {
|
|
V(x1, y1); V(x2, y1); V(x2, y2);
|
|
V(x1, y1); V(x2, y2); V(x1, y2);
|
|
};
|
|
|
|
// Curved left side.
|
|
V(12.0f, 44.0f); V(30.0f, 16.0f); V(30.0f, 44.0f);
|
|
V(0.0f, 80.0f); V(12.0f, 44.0f); V(12.0f, 80.0f);
|
|
R(12.0f, 44.0f, 30.0f, 80.0f);
|
|
R(0.0f, 80.0f, 30.0f, 114.0f);
|
|
V(0.0f, 114.0f); V(12.0f, 114.0f); V(12.0f, 154.0f);
|
|
R(12.0f, 114.0f, 30.0f, 154.0f);
|
|
V(12.0f, 154.0f); V(30.0f, 154.0f); V(30.0f, 180.0f);
|
|
// Left side.
|
|
V(30.0f, 16.0f); V(64.0f, 13.0f); V(64.0f, 184.0f);
|
|
V(30.0f, 16.0f); V(64.0f, 184.0f); V(30.0f, 180.0f);
|
|
V(64.0f, 13.0f); V(76.0f, 0.0f); V(76.0f, 13.0f);
|
|
V(64.0f, 184.0f); V(76.0f, 200.0f); V(76.0f, 184.0f);
|
|
R(64.0f, 13.0f, 76.0f, 184.0f);
|
|
// Center.
|
|
R(76.0f, 0.0f, 400.0f, 13.0f);
|
|
R(76.0f, 167.0f, 400.0f, 200.0f);
|
|
R(76.0f, 13.0f, 99.0f, 167.0f);
|
|
R(377.0f, 13.0f, 400.0f, 167.0f);
|
|
// Right side.
|
|
V(400.0f, 0.0f); V(412.0f, 13.0f); V(400.0f, 13.0f);
|
|
V(400.0f, 184.0f); V(412.0f, 184.0f); V(400.0f, 200.0f);
|
|
R(400.0f, 13.0f, 412.0f, 184.0f);
|
|
V(412.0f, 13.0f); V(446.0f, 16.0f); V(446.0f, 180.0f);
|
|
V(412.0f, 13.0f); V(446.0f, 180.0f); V(412.0f, 184.0f);
|
|
// Curved right side.
|
|
V(446.0f, 16.0f); V(462.0f, 44.0f); V(446.0f, 44.0f);
|
|
V(462.0f, 44.0f); V(474.0f, 80.0f); V(462.0f, 80.0f);
|
|
R(446.0f, 44.0f, 462.0f, 80.0f);
|
|
R(446.0f, 80.0f, 474.0f, 114.0f);
|
|
V(462.0f, 114.0f); V(474.0f, 114.0f); V(462.0f, 154.0f);
|
|
R(446.0f, 114.0f, 462.0f, 154.0f);
|
|
V(446.0f, 154.0f); V(462.0f, 154.0f); V(446.0f, 180.0f);
|
|
}
|
|
|
|
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
|
|
w = 478.0f * scale_;
|
|
h = 204.0f * scale_;
|
|
}
|
|
|
|
protected:
|
|
float scale_ = 1.0f;
|
|
};
|
|
|
|
class MockScreen : public UI::InertView {
|
|
public:
|
|
MockScreen(UI::LayoutParams *layoutParams = nullptr) : InertView(layoutParams) {
|
|
}
|
|
|
|
void Draw(UIContext &dc) override {
|
|
ImageID bg = ImageID("I_PSP_DISPLAY");
|
|
dc.Draw()->DrawImageStretch(bg, bounds_, 0x7FFFFFFF);
|
|
}
|
|
};
|
|
|
|
class MockButton : public UI::Clickable {
|
|
public:
|
|
MockButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *layoutParams = nullptr)
|
|
: Clickable(layoutParams), button_(button), img_(img), bgImg_(bg), angle_(angle) {
|
|
}
|
|
|
|
void Draw(UIContext &dc) override {
|
|
uint32_t c = 0xFFFFFFFF;
|
|
if (HasFocus() || Selected())
|
|
c = dc.theme->itemFocusedStyle.background.color;
|
|
|
|
float scales[2]{};
|
|
if (bgImg_.isValid())
|
|
dc.Draw()->DrawImageRotatedStretch(bgImg_, bounds_, scales, angle_, c, flipHBG_);
|
|
if (img_.isValid()) {
|
|
scales[0] *= scaleX_;
|
|
scales[1] *= scaleY_;
|
|
if (timeLastPressed_ >= 0.0) {
|
|
double sincePress = time_now_d() - timeLastPressed_;
|
|
if (sincePress < 1.0) {
|
|
c = colorBlend(c, dc.theme->itemDownStyle.background.color, (float)sincePress);
|
|
}
|
|
}
|
|
dc.Draw()->DrawImageRotatedStretch(img_, bounds_.Offset(offsetX_, offsetY_), scales, angle_, c);
|
|
}
|
|
}
|
|
|
|
MockButton *SetScale(float s) {
|
|
scaleX_ = s;
|
|
scaleY_ = s;
|
|
return this;
|
|
}
|
|
|
|
MockButton *SetScale(float x, float y) {
|
|
scaleX_ = x;
|
|
scaleY_ = y;
|
|
return this;
|
|
}
|
|
|
|
MockButton *SetFlipHBG(float f) {
|
|
flipHBG_ = f;
|
|
return this;
|
|
}
|
|
|
|
MockButton *SetOffset(float x, float y) {
|
|
offsetX_ = x;
|
|
offsetY_ = y;
|
|
return this;
|
|
}
|
|
|
|
MockButton *SetSelectedButton(int *s) {
|
|
selectedButton_ = s;
|
|
return this;
|
|
}
|
|
|
|
bool Selected() {
|
|
return selectedButton_ && *selectedButton_ == button_;
|
|
}
|
|
|
|
int Button() {
|
|
return button_;
|
|
}
|
|
|
|
void NotifyPressed() {
|
|
timeLastPressed_ = time_now_d();
|
|
}
|
|
|
|
private:
|
|
int button_;
|
|
ImageID img_;
|
|
ImageID bgImg_;
|
|
float angle_;
|
|
float scaleX_ = 1.0f;
|
|
float scaleY_ = 1.0f;
|
|
float offsetX_ = 0.0f;
|
|
float offsetY_ = 0.0f;
|
|
bool flipHBG_ = false;
|
|
int *selectedButton_ = nullptr;
|
|
double timeLastPressed_ = -1.0;
|
|
};
|
|
|
|
class MockPSP : public UI::AnchorLayout {
|
|
public:
|
|
static constexpr float SCALE = 1.4f;
|
|
|
|
MockPSP(UI::LayoutParams *layoutParams = nullptr);
|
|
void SelectButton(int btn);
|
|
void FocusButton(int btn);
|
|
void NotifyPressed(int btn);
|
|
float GetPopupOffset();
|
|
|
|
bool SubviewFocused(View *view) override;
|
|
|
|
UI::Event ButtonClick;
|
|
|
|
private:
|
|
UI::AnchorLayoutParams *LayoutAt(float l, float t, float r, float b);
|
|
UI::AnchorLayoutParams *LayoutSize(float w, float h, float l, float t);
|
|
MockButton *AddButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *lp);
|
|
|
|
UI::EventReturn OnSelectButton(UI::EventParams &e);
|
|
|
|
std::unordered_map<int, MockButton *> buttons_;
|
|
UI::TextView *labelView_ = nullptr;
|
|
int selectedButton_ = 0;
|
|
};
|
|
|
|
MockPSP::MockPSP(UI::LayoutParams *layoutParams) : AnchorLayout(layoutParams) {
|
|
Add(new Backplate(SCALE));
|
|
Add(new MockScreen(LayoutAt(99.0f, 13.0f, 97.0f, 33.0f)));
|
|
|
|
// Left side.
|
|
AddButton(VIRTKEY_AXIS_Y_MAX, ImageID("I_STICK_LINE"), ImageID("I_STICK_BG_LINE"), 0.0f, LayoutSize(34.0f, 34.0f, 35.0f, 133.0f));
|
|
AddButton(CTRL_LEFT, ImageID("I_ARROW"), ImageID("I_DIR_LINE"), M_PI * 0.0f, LayoutSize(28.0f, 20.0f, 14.0f, 75.0f))->SetOffset(-4.0f * SCALE, 0.0f);
|
|
AddButton(CTRL_UP, ImageID("I_ARROW"), ImageID("I_DIR_LINE"), M_PI * 0.5f, LayoutSize(20.0f, 28.0f, 40.0f, 50.0f))->SetOffset(0.0f, -4.0f * SCALE);
|
|
AddButton(CTRL_RIGHT, ImageID("I_ARROW"), ImageID("I_DIR_LINE"), M_PI * 1.0f, LayoutSize(28.0f, 20.0f, 58.0f, 75.0f))->SetOffset(4.0f * SCALE, 0.0f);
|
|
AddButton(CTRL_DOWN, ImageID("I_ARROW"), ImageID("I_DIR_LINE"), M_PI * 1.5f, LayoutSize(20.0f, 28.0f, 40.0f, 92.0f))->SetOffset(0.0f, 4.0f * SCALE);
|
|
|
|
// Top.
|
|
AddButton(CTRL_LTRIGGER, ImageID("I_L"), ImageID("I_SHOULDER_LINE"), 0.0f, LayoutSize(50.0f, 16.0f, 29.0f, 0.0f));
|
|
AddButton(CTRL_RTRIGGER, ImageID("I_R"), ImageID("I_SHOULDER_LINE"), 0.0f, LayoutSize(50.0f, 16.0f, 397.0f, 0.0f))->SetFlipHBG(true);
|
|
|
|
// Bottom.
|
|
AddButton(CTRL_HOME, ImageID("I_HOME"), ImageID("I_RECT_LINE"), 0.0f, LayoutSize(28.0f, 14.0f, 88.0f, 181.0f))->SetScale(1.0f, 0.65f);
|
|
AddButton(CTRL_SELECT, ImageID("I_SELECT"), ImageID("I_RECT_LINE"), 0.0f, LayoutSize(28.0f, 14.0f, 330.0f, 181.0f));
|
|
AddButton(CTRL_START, ImageID("I_START"), ImageID("I_RECT_LINE"), 0.0f, LayoutSize(28.0f, 14.0f, 361.0f, 181.0f));
|
|
|
|
// Right side.
|
|
AddButton(CTRL_TRIANGLE, ImageID("I_TRIANGLE"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 419.0f, 46.0f))->SetScale(0.7f)->SetOffset(0.0f, -1.0f * SCALE);
|
|
AddButton(CTRL_CIRCLE, ImageID("I_CIRCLE"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 446.0f, 74.0f))->SetScale(0.7f);
|
|
AddButton(CTRL_CROSS, ImageID("I_CROSS"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 419.0f, 102.0f))->SetScale(0.7f);
|
|
AddButton(CTRL_SQUARE, ImageID("I_SQUARE"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 392.0f, 74.0f))->SetScale(0.7f);
|
|
|
|
labelView_ = Add(new UI::TextView(""));
|
|
labelView_->SetShadow(true);
|
|
labelView_->SetVisibility(UI::V_GONE);
|
|
}
|
|
|
|
void MockPSP::SelectButton(int btn) {
|
|
selectedButton_ = btn;
|
|
}
|
|
|
|
void MockPSP::FocusButton(int btn) {
|
|
MockButton *view = buttons_[btn];
|
|
if (view) {
|
|
view->SetFocus();
|
|
} else {
|
|
labelView_->SetVisibility(UI::V_GONE);
|
|
}
|
|
}
|
|
|
|
void MockPSP::NotifyPressed(int btn) {
|
|
MockButton *view = buttons_[btn];
|
|
if (view)
|
|
view->NotifyPressed();
|
|
}
|
|
|
|
bool MockPSP::SubviewFocused(View *view) {
|
|
for (auto it : buttons_) {
|
|
if (view == it.second) {
|
|
labelView_->SetVisibility(UI::V_VISIBLE);
|
|
labelView_->SetText(KeyMap::GetPspButtonName(it.first));
|
|
|
|
const Bounds &pos = view->GetBounds().Offset(-GetBounds().x, -GetBounds().y);
|
|
labelView_->ReplaceLayoutParams(new UI::AnchorLayoutParams(pos.centerX(), pos.y2() + 5, UI::NONE, UI::NONE));
|
|
}
|
|
}
|
|
return AnchorLayout::SubviewFocused(view);
|
|
}
|
|
|
|
float MockPSP::GetPopupOffset() {
|
|
MockButton *view = buttons_[selectedButton_];
|
|
if (!view)
|
|
return 0.0f;
|
|
|
|
float ypos = view->GetBounds().centerY();
|
|
if (ypos > bounds_.centerY()) {
|
|
return -0.25f;
|
|
}
|
|
return 0.25f;
|
|
}
|
|
|
|
UI::AnchorLayoutParams *MockPSP::LayoutAt(float l, float t, float r, float b) {
|
|
return new UI::AnchorLayoutParams(l * SCALE, t * SCALE, r * SCALE, b * SCALE);
|
|
}
|
|
UI::AnchorLayoutParams *MockPSP::LayoutSize(float w, float h, float l, float t) {
|
|
return new UI::AnchorLayoutParams(w * SCALE, h * SCALE, l * SCALE, t * SCALE, UI::NONE, UI::NONE);
|
|
}
|
|
|
|
MockButton *MockPSP::AddButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *lp) {
|
|
MockButton *view = Add(new MockButton(button, img, bg, angle, lp));
|
|
view->OnClick.Handle(this, &MockPSP::OnSelectButton);
|
|
view->SetSelectedButton(&selectedButton_);
|
|
buttons_[button] = view;
|
|
return view;
|
|
}
|
|
|
|
UI::EventReturn MockPSP::OnSelectButton(UI::EventParams &e) {
|
|
auto view = (MockButton *)e.v;
|
|
e.a = view->Button();
|
|
return ButtonClick.Dispatch(e);
|
|
}
|
|
|
|
static std::vector<int> bindAllOrder{
|
|
CTRL_LTRIGGER,
|
|
CTRL_RTRIGGER,
|
|
CTRL_UP,
|
|
CTRL_DOWN,
|
|
CTRL_LEFT,
|
|
CTRL_RIGHT,
|
|
VIRTKEY_AXIS_Y_MAX,
|
|
VIRTKEY_AXIS_Y_MIN,
|
|
VIRTKEY_AXIS_X_MIN,
|
|
VIRTKEY_AXIS_X_MAX,
|
|
CTRL_HOME,
|
|
CTRL_SELECT,
|
|
CTRL_START,
|
|
CTRL_CROSS,
|
|
CTRL_CIRCLE,
|
|
CTRL_TRIANGLE,
|
|
CTRL_SQUARE,
|
|
};
|
|
|
|
void VisualMappingScreen::CreateViews() {
|
|
using namespace UI;
|
|
|
|
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
|
|
|
|
root_ = new LinearLayout(ORIENT_HORIZONTAL);
|
|
|
|
constexpr float leftColumnWidth = 200.0f;
|
|
LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(leftColumnWidth, FILL_PARENT, Margins(10, 0, 0, 10)));
|
|
leftColumn->Add(new Choice(km->T("Bind All")))->OnClick.Handle(this, &VisualMappingScreen::OnBindAll);
|
|
leftColumn->Add(new CheckBox(&replace_, km->T("Replace"), ""));
|
|
|
|
leftColumn->Add(new Spacer(new LinearLayoutParams(1.0f)));
|
|
AddStandardBack(leftColumn);
|
|
|
|
Bounds bounds = screenManager()->getUIContext()->GetLayoutBounds();
|
|
// Account for left side.
|
|
bounds.w -= leftColumnWidth + 10.0f;
|
|
|
|
AnchorLayout *rightColumn = new AnchorLayout(new LinearLayoutParams(bounds.w, FILL_PARENT, 1.0f));
|
|
psp_ = rightColumn->Add(new MockPSP(new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, true)));
|
|
psp_->ButtonClick.Handle(this, &VisualMappingScreen::OnMapButton);
|
|
|
|
root_->Add(leftColumn);
|
|
root_->Add(rightColumn);
|
|
}
|
|
|
|
bool VisualMappingScreen::key(const KeyInput &key) {
|
|
if (key.flags & KEY_DOWN) {
|
|
std::vector<int> pspKeys;
|
|
KeyMap::InputMappingToPspButton(InputMapping(key.deviceId, key.keyCode), &pspKeys);
|
|
for (int pspKey : pspKeys) {
|
|
switch (pspKey) {
|
|
case VIRTKEY_AXIS_X_MIN:
|
|
case VIRTKEY_AXIS_Y_MIN:
|
|
case VIRTKEY_AXIS_X_MAX:
|
|
case VIRTKEY_AXIS_Y_MAX:
|
|
psp_->NotifyPressed(VIRTKEY_AXIS_Y_MAX);
|
|
break;
|
|
default:
|
|
psp_->NotifyPressed(pspKey);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return UIDialogScreenWithGameBackground::key(key);
|
|
}
|
|
|
|
void VisualMappingScreen::axis(const AxisInput &axis) {
|
|
std::vector<int> results;
|
|
if (axis.value >= g_Config.fAnalogDeadzone * 0.7f)
|
|
KeyMap::InputMappingToPspButton(InputMapping(axis.deviceId, axis.axisId, 1), &results);
|
|
if (axis.value <= g_Config.fAnalogDeadzone * -0.7f)
|
|
KeyMap::InputMappingToPspButton(InputMapping(axis.deviceId, axis.axisId, -1), &results);
|
|
|
|
for (int result : results) {
|
|
switch (result) {
|
|
case VIRTKEY_AXIS_X_MIN:
|
|
case VIRTKEY_AXIS_Y_MIN:
|
|
case VIRTKEY_AXIS_X_MAX:
|
|
case VIRTKEY_AXIS_Y_MAX:
|
|
psp_->NotifyPressed(VIRTKEY_AXIS_Y_MAX);
|
|
break;
|
|
default:
|
|
psp_->NotifyPressed(result);
|
|
break;
|
|
}
|
|
}
|
|
UIDialogScreenWithGameBackground::axis(axis);
|
|
}
|
|
|
|
void VisualMappingScreen::resized() {
|
|
UIDialogScreenWithGameBackground::resized();
|
|
RecreateViews();
|
|
}
|
|
|
|
UI::EventReturn VisualMappingScreen::OnMapButton(UI::EventParams &e) {
|
|
nextKey_ = e.a;
|
|
MapNext(false);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn VisualMappingScreen::OnBindAll(UI::EventParams &e) {
|
|
bindAll_ = 0;
|
|
nextKey_ = bindAllOrder[bindAll_];
|
|
MapNext(false);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
void VisualMappingScreen::HandleKeyMapping(MultiInputMapping key) {
|
|
KeyMap::SetInputMapping(nextKey_, key, replace_);
|
|
KeyMap::UpdateNativeMenuKeys();
|
|
|
|
if (bindAll_ < 0) {
|
|
// For analog, we do each direction in a row.
|
|
if (nextKey_ == VIRTKEY_AXIS_Y_MAX)
|
|
nextKey_ = VIRTKEY_AXIS_Y_MIN;
|
|
else if (nextKey_ == VIRTKEY_AXIS_Y_MIN)
|
|
nextKey_ = VIRTKEY_AXIS_X_MIN;
|
|
else if (nextKey_ == VIRTKEY_AXIS_X_MIN)
|
|
nextKey_ = VIRTKEY_AXIS_X_MAX;
|
|
else {
|
|
if (nextKey_ == VIRTKEY_AXIS_X_MAX)
|
|
psp_->FocusButton(VIRTKEY_AXIS_Y_MAX);
|
|
else
|
|
psp_->FocusButton(nextKey_);
|
|
nextKey_ = 0;
|
|
}
|
|
} else if ((size_t)bindAll_ + 1 < bindAllOrder.size()) {
|
|
bindAll_++;
|
|
nextKey_ = bindAllOrder[bindAll_];
|
|
} else {
|
|
bindAll_ = -1;
|
|
nextKey_ = 0;
|
|
}
|
|
}
|
|
|
|
void VisualMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) {
|
|
if (result == DR_YES && nextKey_ != 0) {
|
|
MapNext(true);
|
|
} else {
|
|
// This means they canceled.
|
|
if (nextKey_ != 0)
|
|
psp_->FocusButton(nextKey_);
|
|
nextKey_ = 0;
|
|
bindAll_ = -1;
|
|
psp_->SelectButton(0);
|
|
}
|
|
}
|
|
|
|
void VisualMappingScreen::MapNext(bool successive) {
|
|
if (nextKey_ == VIRTKEY_AXIS_Y_MIN || nextKey_ == VIRTKEY_AXIS_X_MIN || nextKey_ == VIRTKEY_AXIS_X_MAX) {
|
|
psp_->SelectButton(VIRTKEY_AXIS_Y_MAX);
|
|
} else {
|
|
psp_->SelectButton(nextKey_);
|
|
}
|
|
auto dialog = new KeyMappingNewKeyDialog(nextKey_, true, std::bind(&VisualMappingScreen::HandleKeyMapping, this, std::placeholders::_1), I18NCat::KEYMAPPING);
|
|
|
|
Bounds bounds = screenManager()->getUIContext()->GetLayoutBounds();
|
|
dialog->SetPopupOffset(psp_->GetPopupOffset() * bounds.h);
|
|
dialog->SetDelay(successive ? 0.5f : 0.1f);
|
|
screenManager()->push(dialog);
|
|
}
|