CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/UI/ControlMappingScreen.cpp
Views: 1401
// Copyright (c) 2013- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "ppsspp_config.h"18#include <algorithm>19#include <deque>20#include <mutex>21#include <unordered_map>2223#include "Common/Render/TextureAtlas.h"24#include "Common/UI/Root.h"25#include "Common/UI/UI.h"26#include "Common/UI/Context.h"27#include "Common/UI/View.h"28#include "Common/UI/ViewGroup.h"29#include "Common/VR/PPSSPPVR.h"3031#include "Common/Log.h"32#include "Common/Data/Color/RGBAUtil.h"33#include "Common/Data/Text/I18n.h"34#include "Common/Input/KeyCodes.h"35#include "Common/Input/InputState.h"36#include "Common/StringUtils.h"37#include "Common/System/Display.h"38#include "Common/System/System.h"39#include "Common/System/Request.h"40#include "Common/TimeUtil.h"41#include "Core/KeyMap.h"42#include "Core/HLE/sceCtrl.h"43#include "Core/System.h"44#include "Core/Config.h"45#include "UI/ControlMappingScreen.h"46#include "UI/GameSettingsScreen.h"47#include "UI/JoystickHistoryView.h"48#include "UI/OnScreenDisplay.h"4950#if PPSSPP_PLATFORM(ANDROID)51#include "android/jni/app-android.h"52#endif5354using KeyMap::MultiInputMapping;5556class SingleControlMapper : public UI::LinearLayout {57public:58SingleControlMapper(int pspKey, std::string keyName, ScreenManager *scrm, UI::LinearLayoutParams *layoutParams = nullptr);5960int GetPspKey() const { return pspKey_; }6162private:63void Refresh();6465UI::EventReturn OnAdd(UI::EventParams ¶ms);66UI::EventReturn OnAddMouse(UI::EventParams ¶ms);67UI::EventReturn OnDelete(UI::EventParams ¶ms);68UI::EventReturn OnReplace(UI::EventParams ¶ms);69UI::EventReturn OnReplaceAll(UI::EventParams ¶ms);7071void MappedCallback(const MultiInputMapping &key);7273enum Action {74NONE,75REPLACEONE,76REPLACEALL,77ADD,78};7980UI::Choice *addButton_ = nullptr;81UI::Choice *replaceAllButton_ = nullptr;82std::vector<UI::View *> rows_;83Action action_ = NONE;84int actionIndex_;85int pspKey_;86std::string keyName_;87ScreenManager *scrm_;88};8990SingleControlMapper::SingleControlMapper(int pspKey, std::string keyName, ScreenManager *scrm, UI::LinearLayoutParams *layoutParams)91: UI::LinearLayout(UI::ORIENT_VERTICAL, layoutParams), pspKey_(pspKey), keyName_(keyName), scrm_(scrm) {92Refresh();93}9495void SingleControlMapper::Refresh() {96Clear();97auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);9899std::map<std::string, ImageID> keyImages = {100{ "Circle", ImageID("I_CIRCLE") },101{ "Cross", ImageID("I_CROSS") },102{ "Square", ImageID("I_SQUARE") },103{ "Triangle", ImageID("I_TRIANGLE") },104{ "Start", ImageID("I_START") },105{ "Select", ImageID("I_SELECT") },106{ "L", ImageID("I_L") },107{ "R", ImageID("I_R") }108};109110using namespace UI;111112float itemH = 55.0f;113114float leftColumnWidth = 200;115float rightColumnWidth = 350; // TODO: Should be flexible somehow. Maybe we need to implement Measure.116117LinearLayout *root = Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));118root->SetSpacing(3.0f);119120auto iter = keyImages.find(keyName_);121// First, look among images.122if (iter != keyImages.end()) {123replaceAllButton_ = new Choice(iter->second, new LinearLayoutParams(leftColumnWidth, itemH));124} else {125// No image? Let's translate.126replaceAllButton_ = new Choice(mc->T(keyName_.c_str()), new LinearLayoutParams(leftColumnWidth, itemH));127replaceAllButton_->SetCentered(true);128}129root->Add(replaceAllButton_)->OnClick.Handle(this, &SingleControlMapper::OnReplaceAll);130131addButton_ = root->Add(new Choice(" + ", new LayoutParams(WRAP_CONTENT, itemH)));132addButton_->OnClick.Handle(this, &SingleControlMapper::OnAdd);133if (g_Config.bMouseControl) {134Choice *p = root->Add(new Choice("M", new LayoutParams(WRAP_CONTENT, itemH)));135p->OnClick.Handle(this, &SingleControlMapper::OnAddMouse);136}137138LinearLayout *rightColumn = root->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(rightColumnWidth, WRAP_CONTENT)));139rightColumn->SetSpacing(2.0f);140std::vector<MultiInputMapping> mappings;141KeyMap::InputMappingsFromPspButton(pspKey_, &mappings, false);142143rows_.clear();144for (size_t i = 0; i < mappings.size(); i++) {145std::string multiMappingString = mappings[i].ToVisualString();146LinearLayout *row = rightColumn->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));147row->SetSpacing(2.0f);148rows_.push_back(row);149150Choice *c = row->Add(new Choice(multiMappingString, new LinearLayoutParams(FILL_PARENT, itemH, 1.0f)));151c->SetTag(StringFromFormat("%d_Change%d", (int)i, pspKey_));152c->OnClick.Handle(this, &SingleControlMapper::OnReplace);153154Choice *d = row->Add(new Choice(ImageID("I_TRASHCAN"), new LayoutParams(WRAP_CONTENT, itemH)));155d->SetTag(StringFromFormat("%d_Del%d", (int)i, pspKey_));156d->OnClick.Handle(this, &SingleControlMapper::OnDelete);157}158159if (mappings.size() == 0) {160// look like an empty line161Choice *c = rightColumn->Add(new Choice("", new LinearLayoutParams(FILL_PARENT, itemH)));162c->OnClick.Handle(this, &SingleControlMapper::OnAdd);163}164}165166void SingleControlMapper::MappedCallback(const MultiInputMapping &kdf) {167if (kdf.empty()) {168// Don't want to try to add this.169return;170}171172switch (action_) {173case ADD:174KeyMap::SetInputMapping(pspKey_, kdf, false);175addButton_->SetFocus();176break;177case REPLACEALL:178KeyMap::SetInputMapping(pspKey_, kdf, true);179replaceAllButton_->SetFocus();180break;181case REPLACEONE:182{183bool success = KeyMap::ReplaceSingleKeyMapping(pspKey_, actionIndex_, kdf);184if (!success) {185replaceAllButton_->SetFocus(); // Last got removed as a duplicate186} else if (actionIndex_ < (int)rows_.size()) {187rows_[actionIndex_]->SetFocus();188} else {189SetFocus();190}191break;192}193default:194SetFocus();195break;196}197KeyMap::UpdateNativeMenuKeys();198g_Config.bMapMouse = false;199}200201UI::EventReturn SingleControlMapper::OnReplace(UI::EventParams ¶ms) {202actionIndex_ = atoi(params.v->Tag().c_str());203action_ = REPLACEONE;204scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&SingleControlMapper::MappedCallback, this, std::placeholders::_1), I18NCat::KEYMAPPING));205return UI::EVENT_DONE;206}207208UI::EventReturn SingleControlMapper::OnReplaceAll(UI::EventParams ¶ms) {209action_ = REPLACEALL;210scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&SingleControlMapper::MappedCallback, this, std::placeholders::_1), I18NCat::KEYMAPPING));211return UI::EVENT_DONE;212}213214UI::EventReturn SingleControlMapper::OnAdd(UI::EventParams ¶ms) {215action_ = ADD;216scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&SingleControlMapper::MappedCallback, this, std::placeholders::_1), I18NCat::KEYMAPPING));217return UI::EVENT_DONE;218}219UI::EventReturn SingleControlMapper::OnAddMouse(UI::EventParams ¶ms) {220action_ = ADD;221g_Config.bMapMouse = true;222scrm_->push(new KeyMappingNewMouseKeyDialog(pspKey_, true, std::bind(&SingleControlMapper::MappedCallback, this, std::placeholders::_1), I18NCat::KEYMAPPING));223return UI::EVENT_DONE;224}225226UI::EventReturn SingleControlMapper::OnDelete(UI::EventParams ¶ms) {227int index = atoi(params.v->Tag().c_str());228KeyMap::DeleteNthMapping(pspKey_, index);229230if (index + 1 < (int)rows_.size())231rows_[index]->SetFocus();232else233SetFocus();234return UI::EVENT_DONE;235}236237238struct BindingCategory {239const char *catName;240int firstKey;241};242243// Category name, first input from psp_button_names.244static const BindingCategory cats[] = {245{"Standard PSP controls", CTRL_UP},246{"Control modifiers", VIRTKEY_ANALOG_ROTATE_CW},247{"Emulator controls", VIRTKEY_FASTFORWARD},248{"Extended PSP controls", VIRTKEY_AXIS_RIGHT_Y_MAX},249{}, // sentinel250};251252void ControlMappingScreen::CreateViews() {253using namespace UI;254mappers_.clear();255256auto km = GetI18NCategory(I18NCat::KEYMAPPING);257258root_ = new LinearLayout(ORIENT_HORIZONTAL);259260LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(200, FILL_PARENT, Margins(10, 0, 0, 10)));261leftColumn->Add(new Choice(km->T("Clear All")))->OnClick.Add([](UI::EventParams &) {262KeyMap::ClearAllMappings();263return UI::EVENT_DONE;264});265leftColumn->Add(new Choice(km->T("Default All")))->OnClick.Add([](UI::EventParams &) {266KeyMap::RestoreDefault();267return UI::EVENT_DONE;268});269std::string sysName = System_GetProperty(SYSPROP_NAME);270// If there's a builtin controller, restore to default should suffice. No need to conf the controller on top.271if (!KeyMap::HasBuiltinController(sysName) && KeyMap::GetSeenPads().size()) {272leftColumn->Add(new Choice(km->T("Autoconfigure")))->OnClick.Handle(this, &ControlMappingScreen::OnAutoConfigure);273}274275leftColumn->Add(new Choice(km->T("Show PSP")))->OnClick.Add([=](UI::EventParams &) {276screenManager()->push(new VisualMappingScreen(gamePath_));277return UI::EVENT_DONE;278});279leftColumn->Add(new CheckBox(&g_Config.bAllowMappingCombos, km->T("Allow combo mappings")));280leftColumn->Add(new CheckBox(&g_Config.bStrictComboOrder, km->T("Strict combo input order")));281282leftColumn->Add(new Spacer(new LinearLayoutParams(1.0f)));283AddStandardBack(leftColumn);284285rightScroll_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f));286rightScroll_->SetTag("ControlMapping");287LinearLayout *rightColumn = new LinearLayoutList(ORIENT_VERTICAL);288rightScroll_->Add(rightColumn);289290root_->Add(leftColumn);291root_->Add(rightScroll_);292293size_t numMappableKeys = 0;294const KeyMap::KeyMap_IntStrPair *mappableKeys = KeyMap::GetMappableKeys(&numMappableKeys);295296int curCat = -1;297CollapsibleSection *curSection = nullptr;298for (size_t i = 0; i < numMappableKeys; i++) {299if (curCat < (int)ARRAY_SIZE(cats) && mappableKeys[i].key == cats[curCat + 1].firstKey) {300if (curCat >= 0) {301curSection->SetOpenPtr(&categoryToggles_[curCat]);302}303curCat++;304curSection = rightColumn->Add(new CollapsibleSection(km->T(cats[curCat].catName)));305curSection->SetSpacing(6.0f);306}307SingleControlMapper *mapper = curSection->Add(308new SingleControlMapper(mappableKeys[i].key, mappableKeys[i].name, screenManager(),309new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));310mapper->SetTag(StringFromFormat("KeyMap%s", mappableKeys[i].name));311mappers_.push_back(mapper);312}313if (curCat >= 0 && curSection) {314curSection->SetOpenPtr(&categoryToggles_[curCat]);315}316_dbg_assert_(curCat == ARRAY_SIZE(cats) - 2); // count the sentinel317318keyMapGeneration_ = KeyMap::g_controllerMapGeneration;319}320321void ControlMappingScreen::update() {322if (KeyMap::HasChanged(keyMapGeneration_)) {323RecreateViews();324}325326UIDialogScreenWithGameBackground::update();327SetVRAppMode(VRAppMode::VR_MENU_MODE);328}329330UI::EventReturn ControlMappingScreen::OnAutoConfigure(UI::EventParams ¶ms) {331std::vector<std::string> items;332const auto seenPads = KeyMap::GetSeenPads();333for (auto s = seenPads.begin(), end = seenPads.end(); s != end; ++s) {334items.push_back(*s);335}336auto km = GetI18NCategory(I18NCat::KEYMAPPING);337UI::ListPopupScreen *autoConfList = new UI::ListPopupScreen(km->T("Autoconfigure for device"), items, -1);338if (params.v)339autoConfList->SetPopupOrigin(params.v);340screenManager()->push(autoConfList);341return UI::EVENT_DONE;342}343344void ControlMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) {345if (result == DR_OK && std::string(dialog->tag()) == "listpopup") {346UI::ListPopupScreen *popup = (UI::ListPopupScreen *)dialog;347KeyMap::AutoConfForPad(popup->GetChoiceString());348}349}350351void KeyMappingNewKeyDialog::CreatePopupContents(UI::ViewGroup *parent) {352using namespace UI;353354auto km = GetI18NCategory(I18NCat::KEYMAPPING);355auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);356357std::string pspButtonName = KeyMap::GetPspButtonName(this->pspBtn_);358359parent->Add(new TextView(std::string(km->T("Map a new key for")) + " " + std::string(mc->T(pspButtonName)), new LinearLayoutParams(Margins(10, 0))));360parent->Add(new TextView(std::string(mapping_.ToVisualString()), new LinearLayoutParams(Margins(10, 0))));361362comboMappingsNotEnabled_ = parent->Add(new NoticeView(NoticeLevel::WARN, km->T("Combo mappings are not enabled"), "", new LinearLayoutParams(Margins(10, 0))));363comboMappingsNotEnabled_->SetVisibility(UI::V_GONE);364365SetVRAppMode(VRAppMode::VR_CONTROLLER_MAPPING_MODE);366}367368bool KeyMappingNewKeyDialog::key(const KeyInput &key) {369if (ignoreInput_)370return true;371if (time_now_d() < delayUntil_)372return true;373374if (key.flags & KEY_DOWN) {375if (key.keyCode == NKCODE_EXT_MOUSEBUTTON_1) {376// Don't map377return true;378}379380if (pspBtn_ == VIRTKEY_SPEED_ANALOG && !UI::IsEscapeKey(key)) {381// Only map analog values to this mapping.382return true;383}384385InputMapping newMapping(key.deviceId, key.keyCode);386387if (!(key.flags & KEY_IS_REPEAT)) {388if (!g_Config.bAllowMappingCombos && !mapping_.mappings.empty()) {389comboMappingsNotEnabled_->SetVisibility(UI::V_VISIBLE);390} else if (!mapping_.mappings.contains(newMapping)) {391mapping_.mappings.push_back(newMapping);392RecreateViews();393}394}395}396if (key.flags & KEY_UP) {397// If the key released wasn't part of the mapping, ignore it here. Some device can cause398// stray key-up events.399InputMapping upMapping(key.deviceId, key.keyCode);400if (!mapping_.mappings.contains(upMapping)) {401return true;402}403404if (callback_)405callback_(mapping_);406TriggerFinish(DR_YES);407}408return true;409}410411void KeyMappingNewKeyDialog::SetDelay(float t) {412delayUntil_ = time_now_d() + t;413}414415void KeyMappingNewMouseKeyDialog::CreatePopupContents(UI::ViewGroup *parent) {416using namespace UI;417418auto km = GetI18NCategory(I18NCat::KEYMAPPING);419420parent->Add(new TextView(std::string(km->T("You can press ESC to cancel.")), new LinearLayoutParams(Margins(10, 0))));421SetVRAppMode(VRAppMode::VR_CONTROLLER_MAPPING_MODE);422}423424bool KeyMappingNewMouseKeyDialog::key(const KeyInput &key) {425if (mapped_)426return false;427if (ignoreInput_)428return true;429if (key.flags & KEY_DOWN) {430if (key.keyCode == NKCODE_ESCAPE) {431TriggerFinish(DR_OK);432g_Config.bMapMouse = false;433return false;434}435436mapped_ = true;437438TriggerFinish(DR_YES);439g_Config.bMapMouse = false;440if (callback_) {441MultiInputMapping kdf(InputMapping(key.deviceId, key.keyCode));442callback_(kdf);443}444}445return true;446}447448// Only used during the bind process. In other places, it's configurable for some types of axis, like trigger.449const float AXIS_BIND_THRESHOLD = 0.75f;450const float AXIS_BIND_RELEASE_THRESHOLD = 0.35f; // Used during mapping only to detect a "key-up" reliably.451452void KeyMappingNewKeyDialog::axis(const AxisInput &axis) {453if (time_now_d() < delayUntil_)454return;455if (ignoreInput_)456return;457458if (axis.value > AXIS_BIND_THRESHOLD) {459InputMapping mapping(axis.deviceId, axis.axisId, 1);460triggeredAxes_.insert(mapping);461if (!g_Config.bAllowMappingCombos && !mapping_.mappings.empty()) {462if (mapping_.mappings.size() == 1 && mapping != mapping_.mappings[0])463comboMappingsNotEnabled_->SetVisibility(UI::V_VISIBLE);464} else if (!mapping_.mappings.contains(mapping)) {465mapping_.mappings.push_back(mapping);466RecreateViews();467}468} else if (axis.value < -AXIS_BIND_THRESHOLD) {469InputMapping mapping(axis.deviceId, axis.axisId, -1);470triggeredAxes_.insert(mapping);471if (!g_Config.bAllowMappingCombos && !mapping_.mappings.empty()) {472if (mapping_.mappings.size() == 1 && mapping != mapping_.mappings[0])473comboMappingsNotEnabled_->SetVisibility(UI::V_VISIBLE);474} else if (!mapping_.mappings.contains(mapping)) {475mapping_.mappings.push_back(mapping);476RecreateViews();477}478} else if (fabsf(axis.value) < AXIS_BIND_RELEASE_THRESHOLD) {479InputMapping neg(axis.deviceId, axis.axisId, -1);480InputMapping pos(axis.deviceId, axis.axisId, 1);481if (triggeredAxes_.find(neg) != triggeredAxes_.end() || triggeredAxes_.find(pos) != triggeredAxes_.end()) {482// "Key-up" the axis.483TriggerFinish(DR_YES);484if (callback_)485callback_(mapping_);486}487}488}489490void KeyMappingNewMouseKeyDialog::axis(const AxisInput &axis) {491if (mapped_)492return;493494if (axis.value > AXIS_BIND_THRESHOLD) {495mapped_ = true;496TriggerFinish(DR_YES);497if (callback_) {498MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, 1));499callback_(kdf);500}501}502503if (axis.value < -AXIS_BIND_THRESHOLD) {504mapped_ = true;505TriggerFinish(DR_YES);506if (callback_) {507MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, -1));508callback_(kdf);509}510}511}512513AnalogSetupScreen::AnalogSetupScreen(const Path &gamePath) : UIDialogScreenWithGameBackground(gamePath) {514mapper_.SetCallbacks(515[](int vkey, bool down) {},516[](int vkey, float analogValue) {},517[&](uint32_t bitsToSet, uint32_t bitsToClear) {},518[&](int stick, float x, float y) {519analogX_[stick] = x;520analogY_[stick] = y;521},522[&](int stick, float x, float y) {523rawX_[stick] = x;524rawY_[stick] = y;525});526}527528void AnalogSetupScreen::update() {529mapper_.Update(time_now_d());530// We ignore the secondary stick for now and just use the two views531// for raw and psp input.532if (stickView_[0]) {533stickView_[0]->SetXY(analogX_[0], analogY_[0]);534}535if (stickView_[1]) {536stickView_[1]->SetXY(rawX_[0], rawY_[0]);537}538UIScreen::update();539}540541bool AnalogSetupScreen::key(const KeyInput &key) {542bool retval = UIScreen::key(key);543544// Allow testing auto-rotation. If it collides with UI keys, too bad.545bool pauseTrigger = false;546mapper_.Key(key, &pauseTrigger);547548if (UI::IsEscapeKey(key)) {549TriggerFinish(DR_BACK);550return retval;551}552return retval;553}554555void AnalogSetupScreen::axis(const AxisInput &axis) {556// We DON'T call UIScreen::Axis here! Otherwise it'll try to move the UI focus around.557// UIScreen::axis(axis);558559// Instead we just send the input directly to the mapper, that we'll visualize.560mapper_.Axis(&axis, 1);561}562563void AnalogSetupScreen::CreateViews() {564using namespace UI;565566auto di = GetI18NCategory(I18NCat::DIALOG);567568root_ = new LinearLayout(ORIENT_HORIZONTAL);569570LinearLayout *leftColumn = root_->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300.0f, FILL_PARENT)));571LinearLayout *rightColumn = root_->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f)));572573auto co = GetI18NCategory(I18NCat::CONTROLS);574ScrollView *scroll = leftColumn->Add(new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0)));575576LinearLayout *scrollContents = scroll->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300.0f, WRAP_CONTENT)));577578scrollContents->Add(new ItemHeader(co->T("Analog Settings", "Analog Settings")));579580// TODO: Would be nicer if these didn't pop up...581scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogDeadzone, 0.0f, 0.5f, 0.15f, co->T("Deadzone radius"), 0.01f, screenManager(), "/ 1.0"));582scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogInverseDeadzone, 0.0f, 1.0f, 0.0f, co->T("Low end radius"), 0.01f, screenManager(), "/ 1.0"));583scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogSensitivity, 0.0f, 2.0f, 1.1f, co->T("Sensitivity (scale)", "Sensitivity"), 0.01f, screenManager(), "x"));584// TODO: This should probably be a slider.585scrollContents->Add(new CheckBox(&g_Config.bAnalogIsCircular, co->T("Circular stick input")));586scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogAutoRotSpeed, 0.1f, 20.0f, 8.0f, co->T("Auto-rotation speed"), 1.0f, screenManager()));587scrollContents->Add(new Choice(co->T("Reset to defaults")))->OnClick.Handle(this, &AnalogSetupScreen::OnResetToDefaults);588589LinearLayout *theTwo = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(1.0f));590591stickView_[0] = theTwo->Add(new JoystickHistoryView(StickHistoryViewType::OUTPUT, co->T("Calibrated"), new LinearLayoutParams(1.0f)));592stickView_[1] = theTwo->Add(new JoystickHistoryView(StickHistoryViewType::INPUT, co->T("Raw input"), new LinearLayoutParams(1.0f)));593594rightColumn->Add(theTwo);595596leftColumn->Add(new Button(di->T("Back"), new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);597}598599UI::EventReturn AnalogSetupScreen::OnResetToDefaults(UI::EventParams &e) {600g_Config.fAnalogDeadzone = 0.15f;601g_Config.fAnalogInverseDeadzone = 0.0f;602g_Config.fAnalogSensitivity = 1.1f;603g_Config.bAnalogIsCircular = false;604g_Config.fAnalogAutoRotSpeed = 8.0f;605return UI::EVENT_DONE;606}607608class Backplate : public UI::InertView {609public:610Backplate(float scale, UI::LayoutParams *layoutParams = nullptr) : InertView(layoutParams), scale_(scale) {}611612void Draw(UIContext &dc) override {613for (float dy = 0.0f; dy <= 4.0f; dy += 1.0f) {614for (float dx = 0.0f; dx <= 4.0f; dx += 1.0f) {615if (dx == 2.0f && dy == 2.0f)616continue;617DrawPSP(dc, dx, dy, 0x06C1B6B6);618}619}620DrawPSP(dc, 2.0f, 2.0f, 0xC01C1818);621}622623void DrawPSP(UIContext &dc, float xoff, float yoff, uint32_t color) {624using namespace UI;625626const AtlasImage *whiteImage = dc.Draw()->GetAtlas()->getImage(dc.theme->whiteImage);627float centerU = (whiteImage->u1 + whiteImage->u2) * 0.5f;628float centerV = (whiteImage->v1 + whiteImage->v2) * 0.5f;629630auto V = [&](float x, float y) {631dc.Draw()->V(bounds_.x + (x + xoff) * scale_, bounds_.y + (y + yoff) * scale_, color, centerU, centerV);632};633auto R = [&](float x1, float y1, float x2, float y2) {634V(x1, y1); V(x2, y1); V(x2, y2);635V(x1, y1); V(x2, y2); V(x1, y2);636};637638// Curved left side.639V(12.0f, 44.0f); V(30.0f, 16.0f); V(30.0f, 44.0f);640V(0.0f, 80.0f); V(12.0f, 44.0f); V(12.0f, 80.0f);641R(12.0f, 44.0f, 30.0f, 80.0f);642R(0.0f, 80.0f, 30.0f, 114.0f);643V(0.0f, 114.0f); V(12.0f, 114.0f); V(12.0f, 154.0f);644R(12.0f, 114.0f, 30.0f, 154.0f);645V(12.0f, 154.0f); V(30.0f, 154.0f); V(30.0f, 180.0f);646// Left side.647V(30.0f, 16.0f); V(64.0f, 13.0f); V(64.0f, 184.0f);648V(30.0f, 16.0f); V(64.0f, 184.0f); V(30.0f, 180.0f);649V(64.0f, 13.0f); V(76.0f, 0.0f); V(76.0f, 13.0f);650V(64.0f, 184.0f); V(76.0f, 200.0f); V(76.0f, 184.0f);651R(64.0f, 13.0f, 76.0f, 184.0f);652// Center.653R(76.0f, 0.0f, 400.0f, 13.0f);654R(76.0f, 167.0f, 400.0f, 200.0f);655R(76.0f, 13.0f, 99.0f, 167.0f);656R(377.0f, 13.0f, 400.0f, 167.0f);657// Right side.658V(400.0f, 0.0f); V(412.0f, 13.0f); V(400.0f, 13.0f);659V(400.0f, 184.0f); V(412.0f, 184.0f); V(400.0f, 200.0f);660R(400.0f, 13.0f, 412.0f, 184.0f);661V(412.0f, 13.0f); V(446.0f, 16.0f); V(446.0f, 180.0f);662V(412.0f, 13.0f); V(446.0f, 180.0f); V(412.0f, 184.0f);663// Curved right side.664V(446.0f, 16.0f); V(462.0f, 44.0f); V(446.0f, 44.0f);665V(462.0f, 44.0f); V(474.0f, 80.0f); V(462.0f, 80.0f);666R(446.0f, 44.0f, 462.0f, 80.0f);667R(446.0f, 80.0f, 474.0f, 114.0f);668V(462.0f, 114.0f); V(474.0f, 114.0f); V(462.0f, 154.0f);669R(446.0f, 114.0f, 462.0f, 154.0f);670V(446.0f, 154.0f); V(462.0f, 154.0f); V(446.0f, 180.0f);671}672673void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {674w = 478.0f * scale_;675h = 204.0f * scale_;676}677678protected:679float scale_ = 1.0f;680};681682class MockScreen : public UI::InertView {683public:684MockScreen(UI::LayoutParams *layoutParams = nullptr) : InertView(layoutParams) {685}686687void Draw(UIContext &dc) override {688ImageID bg = ImageID("I_PSP_DISPLAY");689dc.Draw()->DrawImageStretch(bg, bounds_, 0x7FFFFFFF);690}691};692693class MockButton : public UI::Clickable {694public:695MockButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *layoutParams = nullptr)696: Clickable(layoutParams), button_(button), img_(img), bgImg_(bg), angle_(angle) {697}698699void Draw(UIContext &dc) override {700uint32_t c = 0xFFFFFFFF;701if (HasFocus() || Selected())702c = dc.theme->itemFocusedStyle.background.color;703704float scales[2]{};705if (bgImg_.isValid())706dc.Draw()->DrawImageRotatedStretch(bgImg_, bounds_, scales, angle_, c, flipHBG_);707if (img_.isValid()) {708scales[0] *= scaleX_;709scales[1] *= scaleY_;710if (timeLastPressed_ >= 0.0) {711double sincePress = time_now_d() - timeLastPressed_;712if (sincePress < 1.0) {713c = colorBlend(c, dc.theme->itemDownStyle.background.color, (float)sincePress);714}715}716dc.Draw()->DrawImageRotatedStretch(img_, bounds_.Offset(offsetX_, offsetY_), scales, angle_, c);717}718}719720MockButton *SetScale(float s) {721scaleX_ = s;722scaleY_ = s;723return this;724}725726MockButton *SetScale(float x, float y) {727scaleX_ = x;728scaleY_ = y;729return this;730}731732MockButton *SetFlipHBG(float f) {733flipHBG_ = f;734return this;735}736737MockButton *SetOffset(float x, float y) {738offsetX_ = x;739offsetY_ = y;740return this;741}742743MockButton *SetSelectedButton(int *s) {744selectedButton_ = s;745return this;746}747748bool Selected() {749return selectedButton_ && *selectedButton_ == button_;750}751752int Button() {753return button_;754}755756void NotifyPressed() {757timeLastPressed_ = time_now_d();758}759760private:761int button_;762ImageID img_;763ImageID bgImg_;764float angle_;765float scaleX_ = 1.0f;766float scaleY_ = 1.0f;767float offsetX_ = 0.0f;768float offsetY_ = 0.0f;769bool flipHBG_ = false;770int *selectedButton_ = nullptr;771double timeLastPressed_ = -1.0;772};773774class MockPSP : public UI::AnchorLayout {775public:776static constexpr float SCALE = 1.4f;777778MockPSP(UI::LayoutParams *layoutParams = nullptr);779void SelectButton(int btn);780void FocusButton(int btn);781void NotifyPressed(int btn);782float GetPopupOffset();783784bool SubviewFocused(View *view) override;785786UI::Event ButtonClick;787788private:789UI::AnchorLayoutParams *LayoutAt(float l, float t, float r, float b);790UI::AnchorLayoutParams *LayoutSize(float w, float h, float l, float t);791MockButton *AddButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *lp);792793UI::EventReturn OnSelectButton(UI::EventParams &e);794795std::unordered_map<int, MockButton *> buttons_;796UI::TextView *labelView_ = nullptr;797int selectedButton_ = 0;798};799800MockPSP::MockPSP(UI::LayoutParams *layoutParams) : AnchorLayout(layoutParams) {801Add(new Backplate(SCALE));802Add(new MockScreen(LayoutAt(99.0f, 13.0f, 97.0f, 33.0f)));803804// Left side.805AddButton(VIRTKEY_AXIS_Y_MAX, ImageID("I_STICK_LINE"), ImageID("I_STICK_BG_LINE"), 0.0f, LayoutSize(34.0f, 34.0f, 35.0f, 133.0f));806AddButton(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);807AddButton(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);808AddButton(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);809AddButton(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);810811// Top.812AddButton(CTRL_LTRIGGER, ImageID("I_L"), ImageID("I_SHOULDER_LINE"), 0.0f, LayoutSize(50.0f, 16.0f, 29.0f, 0.0f));813AddButton(CTRL_RTRIGGER, ImageID("I_R"), ImageID("I_SHOULDER_LINE"), 0.0f, LayoutSize(50.0f, 16.0f, 397.0f, 0.0f))->SetFlipHBG(true);814815// Bottom.816AddButton(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);817AddButton(CTRL_SELECT, ImageID("I_SELECT"), ImageID("I_RECT_LINE"), 0.0f, LayoutSize(28.0f, 14.0f, 330.0f, 181.0f));818AddButton(CTRL_START, ImageID("I_START"), ImageID("I_RECT_LINE"), 0.0f, LayoutSize(28.0f, 14.0f, 361.0f, 181.0f));819820// Right side.821AddButton(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);822AddButton(CTRL_CIRCLE, ImageID("I_CIRCLE"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 446.0f, 74.0f))->SetScale(0.7f);823AddButton(CTRL_CROSS, ImageID("I_CROSS"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 419.0f, 102.0f))->SetScale(0.7f);824AddButton(CTRL_SQUARE, ImageID("I_SQUARE"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 392.0f, 74.0f))->SetScale(0.7f);825826labelView_ = Add(new UI::TextView(""));827labelView_->SetShadow(true);828labelView_->SetVisibility(UI::V_GONE);829}830831void MockPSP::SelectButton(int btn) {832selectedButton_ = btn;833}834835void MockPSP::FocusButton(int btn) {836MockButton *view = buttons_[btn];837if (view) {838view->SetFocus();839} else {840labelView_->SetVisibility(UI::V_GONE);841}842}843844void MockPSP::NotifyPressed(int btn) {845MockButton *view = buttons_[btn];846if (view)847view->NotifyPressed();848}849850bool MockPSP::SubviewFocused(View *view) {851for (auto it : buttons_) {852if (view == it.second) {853labelView_->SetVisibility(UI::V_VISIBLE);854labelView_->SetText(KeyMap::GetPspButtonName(it.first));855856const Bounds &pos = view->GetBounds().Offset(-GetBounds().x, -GetBounds().y);857labelView_->ReplaceLayoutParams(new UI::AnchorLayoutParams(pos.centerX(), pos.y2() + 5, UI::NONE, UI::NONE));858}859}860return AnchorLayout::SubviewFocused(view);861}862863float MockPSP::GetPopupOffset() {864MockButton *view = buttons_[selectedButton_];865if (!view)866return 0.0f;867868float ypos = view->GetBounds().centerY();869if (ypos > bounds_.centerY()) {870return -0.25f;871}872return 0.25f;873}874875UI::AnchorLayoutParams *MockPSP::LayoutAt(float l, float t, float r, float b) {876return new UI::AnchorLayoutParams(l * SCALE, t * SCALE, r * SCALE, b * SCALE);877}878UI::AnchorLayoutParams *MockPSP::LayoutSize(float w, float h, float l, float t) {879return new UI::AnchorLayoutParams(w * SCALE, h * SCALE, l * SCALE, t * SCALE, UI::NONE, UI::NONE);880}881882MockButton *MockPSP::AddButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *lp) {883MockButton *view = Add(new MockButton(button, img, bg, angle, lp));884view->OnClick.Handle(this, &MockPSP::OnSelectButton);885view->SetSelectedButton(&selectedButton_);886buttons_[button] = view;887return view;888}889890UI::EventReturn MockPSP::OnSelectButton(UI::EventParams &e) {891auto view = (MockButton *)e.v;892e.a = view->Button();893return ButtonClick.Dispatch(e);894}895896static std::vector<int> bindAllOrder{897CTRL_LTRIGGER,898CTRL_RTRIGGER,899CTRL_UP,900CTRL_DOWN,901CTRL_LEFT,902CTRL_RIGHT,903VIRTKEY_AXIS_Y_MAX,904VIRTKEY_AXIS_Y_MIN,905VIRTKEY_AXIS_X_MIN,906VIRTKEY_AXIS_X_MAX,907CTRL_HOME,908CTRL_SELECT,909CTRL_START,910CTRL_CROSS,911CTRL_CIRCLE,912CTRL_TRIANGLE,913CTRL_SQUARE,914};915916void VisualMappingScreen::CreateViews() {917using namespace UI;918919auto km = GetI18NCategory(I18NCat::KEYMAPPING);920921root_ = new LinearLayout(ORIENT_HORIZONTAL);922923constexpr float leftColumnWidth = 200.0f;924LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(leftColumnWidth, FILL_PARENT, Margins(10, 0, 0, 10)));925leftColumn->Add(new Choice(km->T("Bind All")))->OnClick.Handle(this, &VisualMappingScreen::OnBindAll);926leftColumn->Add(new CheckBox(&replace_, km->T("Replace"), ""));927928leftColumn->Add(new Spacer(new LinearLayoutParams(1.0f)));929AddStandardBack(leftColumn);930931Bounds bounds = screenManager()->getUIContext()->GetLayoutBounds();932// Account for left side.933bounds.w -= leftColumnWidth + 10.0f;934935AnchorLayout *rightColumn = new AnchorLayout(new LinearLayoutParams(bounds.w, FILL_PARENT, 1.0f));936psp_ = rightColumn->Add(new MockPSP(new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, true)));937psp_->ButtonClick.Handle(this, &VisualMappingScreen::OnMapButton);938939root_->Add(leftColumn);940root_->Add(rightColumn);941}942943bool VisualMappingScreen::key(const KeyInput &key) {944if (key.flags & KEY_DOWN) {945std::vector<int> pspKeys;946KeyMap::InputMappingToPspButton(InputMapping(key.deviceId, key.keyCode), &pspKeys);947for (int pspKey : pspKeys) {948switch (pspKey) {949case VIRTKEY_AXIS_X_MIN:950case VIRTKEY_AXIS_Y_MIN:951case VIRTKEY_AXIS_X_MAX:952case VIRTKEY_AXIS_Y_MAX:953psp_->NotifyPressed(VIRTKEY_AXIS_Y_MAX);954break;955default:956psp_->NotifyPressed(pspKey);957break;958}959}960}961return UIDialogScreenWithGameBackground::key(key);962}963964void VisualMappingScreen::axis(const AxisInput &axis) {965std::vector<int> results;966if (axis.value >= g_Config.fAnalogDeadzone * 0.7f)967KeyMap::InputMappingToPspButton(InputMapping(axis.deviceId, axis.axisId, 1), &results);968if (axis.value <= g_Config.fAnalogDeadzone * -0.7f)969KeyMap::InputMappingToPspButton(InputMapping(axis.deviceId, axis.axisId, -1), &results);970971for (int result : results) {972switch (result) {973case VIRTKEY_AXIS_X_MIN:974case VIRTKEY_AXIS_Y_MIN:975case VIRTKEY_AXIS_X_MAX:976case VIRTKEY_AXIS_Y_MAX:977psp_->NotifyPressed(VIRTKEY_AXIS_Y_MAX);978break;979default:980psp_->NotifyPressed(result);981break;982}983}984UIDialogScreenWithGameBackground::axis(axis);985}986987void VisualMappingScreen::resized() {988UIDialogScreenWithGameBackground::resized();989RecreateViews();990}991992UI::EventReturn VisualMappingScreen::OnMapButton(UI::EventParams &e) {993nextKey_ = e.a;994MapNext(false);995return UI::EVENT_DONE;996}997998UI::EventReturn VisualMappingScreen::OnBindAll(UI::EventParams &e) {999bindAll_ = 0;1000nextKey_ = bindAllOrder[bindAll_];1001MapNext(false);1002return UI::EVENT_DONE;1003}10041005void VisualMappingScreen::HandleKeyMapping(const KeyMap::MultiInputMapping &key) {1006KeyMap::SetInputMapping(nextKey_, key, replace_);1007KeyMap::UpdateNativeMenuKeys();10081009if (bindAll_ < 0) {1010// For analog, we do each direction in a row.1011if (nextKey_ == VIRTKEY_AXIS_Y_MAX)1012nextKey_ = VIRTKEY_AXIS_Y_MIN;1013else if (nextKey_ == VIRTKEY_AXIS_Y_MIN)1014nextKey_ = VIRTKEY_AXIS_X_MIN;1015else if (nextKey_ == VIRTKEY_AXIS_X_MIN)1016nextKey_ = VIRTKEY_AXIS_X_MAX;1017else {1018if (nextKey_ == VIRTKEY_AXIS_X_MAX)1019psp_->FocusButton(VIRTKEY_AXIS_Y_MAX);1020else1021psp_->FocusButton(nextKey_);1022nextKey_ = 0;1023}1024} else if ((size_t)bindAll_ + 1 < bindAllOrder.size()) {1025bindAll_++;1026nextKey_ = bindAllOrder[bindAll_];1027} else {1028bindAll_ = -1;1029nextKey_ = 0;1030}1031}10321033void VisualMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) {1034if (result == DR_YES && nextKey_ != 0) {1035MapNext(true);1036} else {1037// This means they canceled.1038if (nextKey_ != 0)1039psp_->FocusButton(nextKey_);1040nextKey_ = 0;1041bindAll_ = -1;1042psp_->SelectButton(0);1043}1044}10451046void VisualMappingScreen::MapNext(bool successive) {1047if (nextKey_ == VIRTKEY_AXIS_Y_MIN || nextKey_ == VIRTKEY_AXIS_X_MIN || nextKey_ == VIRTKEY_AXIS_X_MAX) {1048psp_->SelectButton(VIRTKEY_AXIS_Y_MAX);1049} else {1050psp_->SelectButton(nextKey_);1051}1052auto dialog = new KeyMappingNewKeyDialog(nextKey_, true, std::bind(&VisualMappingScreen::HandleKeyMapping, this, std::placeholders::_1), I18NCat::KEYMAPPING);10531054Bounds bounds = screenManager()->getUIContext()->GetLayoutBounds();1055dialog->SetPopupOffset(psp_->GetPopupOffset() * bounds.h);1056dialog->SetDelay(successive ? 0.5f : 0.1f);1057screenManager()->push(dialog);1058}105910601061