Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/ControlMappingScreen.cpp
5654 views
1
// Copyright (c) 2013- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
#include <algorithm>
20
#include <deque>
21
#include <mutex>
22
#include <unordered_map>
23
24
#include "Common/Render/TextureAtlas.h"
25
#include "Common/UI/Context.h"
26
#include "Common/UI/View.h"
27
#include "Common/UI/ViewGroup.h"
28
#include "Common/UI/Notice.h"
29
#include "Common/VR/PPSSPPVR.h"
30
#include "Common/Log.h"
31
#include "Common/Data/Color/RGBAUtil.h"
32
#include "Common/Data/Text/I18n.h"
33
#include "Common/Input/KeyCodes.h"
34
#include "Common/Input/InputState.h"
35
#include "Common/StringUtils.h"
36
#include "Common/System/System.h"
37
#include "Common/System/Request.h"
38
#include "Common/TimeUtil.h"
39
#include "Core/KeyMap.h"
40
#include "Core/HLE/sceCtrl.h"
41
#include "Core/Config.h"
42
#include "UI/ControlMappingScreen.h"
43
#include "UI/PopupScreens.h"
44
#include "UI/JoystickHistoryView.h"
45
46
#if PPSSPP_PLATFORM(ANDROID)
47
#include "android/jni/app-android.h"
48
#endif
49
50
using KeyMap::MultiInputMapping;
51
52
class SingleControlMapper : public UI::LinearLayout {
53
public:
54
SingleControlMapper(int pspKey, std::string_view keyName, bool portrait, ScreenManager *scrm, UI::LinearLayoutParams *layoutParams = nullptr)
55
: UI::LinearLayout(ORIENT_VERTICAL, layoutParams), pspKey_(pspKey), keyName_(keyName), scrm_(scrm), portrait_(portrait) {
56
Refresh();
57
}
58
~SingleControlMapper() {
59
g_IsMappingMouseInput = false;
60
}
61
int GetPspKey() const { return pspKey_; }
62
63
private:
64
void Refresh();
65
66
void OnAdd(UI::EventParams &params);
67
void OnAddMouse(UI::EventParams &params);
68
void OnDelete(UI::EventParams &params);
69
void OnReplace(UI::EventParams &params);
70
void OnReplaceAll(UI::EventParams &params);
71
72
int pspKey_;
73
74
UI::Choice *addButton_ = nullptr;
75
UI::Choice *replaceAllButton_ = nullptr;
76
std::vector<UI::View *> rows_;
77
std::string keyName_;
78
ScreenManager *scrm_;
79
bool portrait_;
80
};
81
82
void SingleControlMapper::Refresh() {
83
Clear();
84
auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);
85
86
std::map<std::string, ImageID> keyImages = {
87
{ "Circle", ImageID("I_CIRCLE") },
88
{ "Cross", ImageID("I_CROSS") },
89
{ "Square", ImageID("I_SQUARE") },
90
{ "Triangle", ImageID("I_TRIANGLE") },
91
{ "Start", ImageID("I_START") },
92
{ "Select", ImageID("I_SELECT") },
93
{ "L", ImageID("I_L") },
94
{ "R", ImageID("I_R") }
95
};
96
97
using namespace UI;
98
99
float itemH = 55.0f;
100
101
float leftColumnWidth = 200;
102
float rightColumnWidth = 350;
103
104
LinearLayout *root = Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
105
root->SetSpacing(3.0f);
106
107
auto iter = keyImages.find(keyName_);
108
// First, look among images.
109
if (iter != keyImages.end()) {
110
replaceAllButton_ = new Choice(iter->second, new LinearLayoutParams(leftColumnWidth, itemH));
111
} else {
112
// No image? Let's translate.
113
replaceAllButton_ = new Choice(mc->T(keyName_), new LinearLayoutParams(leftColumnWidth, itemH));
114
replaceAllButton_->SetCentered(true);
115
}
116
root->Add(replaceAllButton_)->OnClick.Handle(this, &SingleControlMapper::OnReplaceAll);
117
118
addButton_ = root->Add(new Choice(" + ", new LayoutParams(WRAP_CONTENT, itemH)));
119
addButton_->OnClick.Handle(this, &SingleControlMapper::OnAdd);
120
if (g_Config.bMouseControl) {
121
Choice *p = root->Add(new Choice("M", new LayoutParams(WRAP_CONTENT, itemH)));
122
p->OnClick.Handle(this, &SingleControlMapper::OnAddMouse);
123
}
124
125
LinearLayout *rightColumn = root->Add(new LinearLayout(ORIENT_VERTICAL, portrait_ ? new LinearLayoutParams(1.0f) : new LinearLayoutParams(rightColumnWidth, WRAP_CONTENT)));
126
rightColumn->SetSpacing(2.0f);
127
std::vector<MultiInputMapping> mappings;
128
KeyMap::InputMappingsFromPspButton(pspKey_, &mappings, false);
129
130
rows_.clear();
131
for (size_t i = 0; i < mappings.size(); i++) {
132
std::string multiMappingString = mappings[i].ToVisualString();
133
LinearLayout *row = rightColumn->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
134
row->SetSpacing(2.0f);
135
rows_.push_back(row);
136
137
Choice *c = row->Add(new Choice(multiMappingString, new LinearLayoutParams(FILL_PARENT, itemH, 1.0f)));
138
c->SetTag(StringFromFormat("%d_Change%d", (int)i, pspKey_));
139
c->OnClick.Handle(this, &SingleControlMapper::OnReplace);
140
141
Choice *d = row->Add(new Choice(ImageID("I_TRASHCAN"), new LayoutParams(WRAP_CONTENT, itemH)));
142
d->SetTag(StringFromFormat("%d_Del%d", (int)i, pspKey_));
143
d->OnClick.Handle(this, &SingleControlMapper::OnDelete);
144
}
145
146
if (mappings.empty()) {
147
// look like an empty line
148
Choice *c = rightColumn->Add(new Choice("", new LinearLayoutParams(FILL_PARENT, itemH)));
149
c->OnClick.Handle(this, &SingleControlMapper::OnAdd);
150
}
151
}
152
153
void SingleControlMapper::OnReplace(UI::EventParams &params) {
154
const int index = atoi(params.v->Tag().c_str());
155
scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, [this, index](KeyMap::MultiInputMapping mapping) {
156
if (mapping.empty())
157
return;
158
bool success = KeyMap::ReplaceSingleKeyMapping(pspKey_, index, mapping);
159
if (!success) {
160
replaceAllButton_->SetFocus(); // Last got removed as a duplicate
161
} else if (index < (int)rows_.size()) {
162
rows_[index]->SetFocus();
163
} else {
164
SetFocus();
165
}
166
KeyMap::UpdateNativeMenuKeys();
167
g_IsMappingMouseInput = false;
168
}, I18NCat::KEYMAPPING));
169
}
170
171
void SingleControlMapper::OnReplaceAll(UI::EventParams &params) {
172
scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, [this](KeyMap::MultiInputMapping mapping) {
173
if (mapping.empty())
174
return;
175
KeyMap::SetInputMapping(pspKey_, mapping, true);
176
replaceAllButton_->SetFocus();
177
KeyMap::UpdateNativeMenuKeys();
178
g_IsMappingMouseInput = false;
179
}, I18NCat::KEYMAPPING));
180
}
181
182
void SingleControlMapper::OnAdd(UI::EventParams &params) {
183
scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, [this](KeyMap::MultiInputMapping mapping) {
184
if (mapping.empty())
185
return;
186
KeyMap::SetInputMapping(pspKey_, mapping, false);
187
addButton_->SetFocus();
188
KeyMap::UpdateNativeMenuKeys();
189
g_IsMappingMouseInput = false;
190
}, I18NCat::KEYMAPPING));
191
}
192
193
void SingleControlMapper::OnAddMouse(UI::EventParams &params) {
194
g_IsMappingMouseInput = true;
195
scrm_->push(new KeyMappingNewMouseKeyDialog(pspKey_, true, [this](KeyMap::MultiInputMapping mapping) {
196
if (mapping.empty())
197
return;
198
KeyMap::SetInputMapping(pspKey_, mapping, false);
199
addButton_->SetFocus();
200
KeyMap::UpdateNativeMenuKeys();
201
g_IsMappingMouseInput = false;
202
}, I18NCat::KEYMAPPING));
203
}
204
205
void SingleControlMapper::OnDelete(UI::EventParams &params) {
206
int index = atoi(params.v->Tag().c_str());
207
KeyMap::DeleteNthMapping(pspKey_, index);
208
if (index + 1 < (int)rows_.size())
209
rows_[index]->SetFocus();
210
else
211
SetFocus();
212
}
213
214
struct BindingCategory {
215
const char *catName;
216
int firstKey;
217
};
218
219
// Category name, first input from psp_button_names.
220
static const BindingCategory cats[] = {
221
{"Standard PSP controls", CTRL_UP},
222
{"Control modifiers", VIRTKEY_ANALOG_ROTATE_CW},
223
{"Emulator controls", VIRTKEY_FASTFORWARD},
224
{"Extended PSP controls", VIRTKEY_AXIS_RIGHT_Y_MAX},
225
{}, // sentinel
226
};
227
228
229
void ControlMappingScreen::CreateSettingsViews(UI::ViewGroup *parent) {
230
using namespace UI;
231
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
232
parent->Add(new Choice(km->T("Clear All")))->OnClick.Add([](UI::EventParams &) {
233
KeyMap::ClearAllMappings();
234
});
235
parent->Add(new Choice(km->T("Default All")))->OnClick.Add([](UI::EventParams &) {
236
KeyMap::RestoreDefault();
237
});
238
std::string sysName = System_GetProperty(SYSPROP_NAME);
239
// If there's a builtin controller, restore to default should suffice. No need to conf the controller on top.
240
if (!KeyMap::HasBuiltinController(sysName) && KeyMap::GetSeenPads().size()) {
241
parent->Add(new Choice(km->T("Autoconfigure")))->OnClick.Handle(this, &ControlMappingScreen::OnAutoConfigure);
242
}
243
parent->Add(new CheckBox(&g_Config.bAllowMappingCombos, km->T("Allow combo mappings")));
244
parent->Add(new CheckBox(&g_Config.bStrictComboOrder, km->T("Strict combo input order")));
245
}
246
247
std::string_view ControlMappingScreen::GetTitle() const {
248
auto co = GetI18NCategory(I18NCat::CONTROLS);
249
return co->T("Control mapping");
250
}
251
252
void ControlMappingScreen::CreateContentViews(UI::ViewGroup *parent) {
253
using namespace UI;
254
255
LinearLayout *rootLayout = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
256
mappers_.clear();
257
258
size_t numMappableKeys = 0;
259
const KeyMap::KeyMap_IntStrPair *mappableKeys = KeyMap::GetMappableKeys(&numMappableKeys);
260
261
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
262
263
bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait;
264
265
int curCat = -1;
266
CollapsibleSection *curSection = nullptr;
267
for (size_t i = 0; i < numMappableKeys; i++) {
268
if (curCat < (int)ARRAY_SIZE(cats) && mappableKeys[i].key == cats[curCat + 1].firstKey) {
269
if (curCat >= 0) {
270
curSection->SetOpenPtr(&categoryToggles_[curCat]);
271
}
272
curCat++;
273
curSection = rootLayout->Add(new CollapsibleSection(km->T(cats[curCat].catName)));
274
curSection->SetSpacing(6.0f);
275
}
276
SingleControlMapper *mapper = curSection->Add(
277
new SingleControlMapper(mappableKeys[i].key, mappableKeys[i].name, portrait, screenManager()));
278
mapper->SetTag(StringFromFormat("KeyMap%s", mappableKeys[i].name));
279
mappers_.push_back(mapper);
280
}
281
if (curCat >= 0 && curSection) {
282
curSection->SetOpenPtr(&categoryToggles_[curCat]);
283
}
284
_dbg_assert_(curCat == ARRAY_SIZE(cats) - 2); // count the sentinel
285
286
keyMapGeneration_ = KeyMap::g_controllerMapGeneration;
287
}
288
289
void ControlMappingScreen::update() {
290
if (KeyMap::HasChanged(keyMapGeneration_)) {
291
RecreateViews();
292
}
293
294
UIBaseDialogScreen::update();
295
SetVRAppMode(VRAppMode::VR_MENU_MODE);
296
}
297
298
void ControlMappingScreen::OnAutoConfigure(UI::EventParams &params) {
299
std::vector<std::string> items;
300
const auto seenPads = KeyMap::GetSeenPads();
301
for (auto s = seenPads.begin(), end = seenPads.end(); s != end; ++s) {
302
items.push_back(*s);
303
}
304
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
305
auto di = GetI18NCategory(I18NCat::DIALOG);
306
UI::ListPopupScreen *autoConfList = new UI::ListPopupScreen(km->T("Autoconfigure for device"), items, -1);
307
autoConfList->SetNotification(NoticeLevel::WARN, di->T("This will overwrite the existing configuration"));
308
if (params.v)
309
autoConfList->SetPopupOrigin(params.v);
310
screenManager()->push(autoConfList);
311
}
312
313
void ControlMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) {
314
if (result == DR_OK && !strcmp(dialog->tag(), "listpopup")) {
315
UI::ListPopupScreen *popup = (UI::ListPopupScreen *)dialog;
316
KeyMap::AutoConfForPad(popup->GetChoiceString());
317
}
318
}
319
320
void KeyMappingNewKeyDialog::CreatePopupContents(UI::ViewGroup *parent) {
321
using namespace UI;
322
323
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
324
auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);
325
326
std::string pspButtonName = KeyMap::GetPspButtonName(this->pspBtn_);
327
328
parent->Add(new TextView(std::string(km->T("Map a new key for")) + " " + std::string(mc->T(pspButtonName)), new LinearLayoutParams(Margins(10, 0))));
329
parent->Add(new TextView(mapping_.ToVisualString(), new LinearLayoutParams(Margins(10, 0))));
330
331
comboMappingsNotEnabled_ = parent->Add(new NoticeView(NoticeLevel::WARN, km->T("Combo mappings are not enabled"), "", new LinearLayoutParams(Margins(10, 0))));
332
comboMappingsNotEnabled_->SetVisibility(UI::V_GONE);
333
334
SetVRAppMode(VRAppMode::VR_CONTROLLER_MAPPING_MODE);
335
}
336
337
bool KeyMappingNewKeyDialog::key(const KeyInput &key) {
338
if (ignoreInput_)
339
return true;
340
if (time_now_d() < delayUntil_)
341
return true;
342
343
if (key.flags & KeyInputFlags::DOWN) {
344
if (key.keyCode == NKCODE_EXT_MOUSEBUTTON_1) {
345
// Don't map
346
return true;
347
}
348
349
if (pspBtn_ == VIRTKEY_SPEED_ANALOG && !UI::IsEscapeKey(key)) {
350
// Only map analog values to this mapping.
351
return true;
352
}
353
354
InputMapping newMapping(key.deviceId, key.keyCode);
355
356
if (!(key.flags & KeyInputFlags::IS_REPEAT)) {
357
if (!g_Config.bAllowMappingCombos && !mapping_.mappings.empty()) {
358
comboMappingsNotEnabled_->SetVisibility(UI::V_VISIBLE);
359
} else if (!mapping_.mappings.contains(newMapping)) {
360
mapping_.mappings.push_back(newMapping);
361
RecreateViews();
362
}
363
}
364
}
365
if (key.flags & KeyInputFlags::UP) {
366
// If the key released wasn't part of the mapping, ignore it here. Some device can cause
367
// stray key-up events.
368
InputMapping upMapping(key.deviceId, key.keyCode);
369
if (!mapping_.mappings.contains(upMapping)) {
370
return true;
371
}
372
373
if (callback_)
374
callback_(mapping_);
375
TriggerFinish(DR_YES);
376
}
377
return true;
378
}
379
380
void KeyMappingNewKeyDialog::SetDelay(float t) {
381
delayUntil_ = time_now_d() + t;
382
}
383
384
void KeyMappingNewMouseKeyDialog::CreatePopupContents(UI::ViewGroup *parent) {
385
using namespace UI;
386
387
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
388
389
parent->Add(new TextView(std::string(km->T("You can press ESC to cancel.")), new LinearLayoutParams(Margins(10, 0))));
390
SetVRAppMode(VRAppMode::VR_CONTROLLER_MAPPING_MODE);
391
}
392
393
bool KeyMappingNewMouseKeyDialog::key(const KeyInput &key) {
394
if (mapped_)
395
return false;
396
if (ignoreInput_)
397
return true;
398
if (key.flags & KeyInputFlags::DOWN) {
399
if (key.keyCode == NKCODE_ESCAPE) {
400
TriggerFinish(DR_OK);
401
g_IsMappingMouseInput = false;
402
return false;
403
}
404
405
mapped_ = true;
406
407
TriggerFinish(DR_YES);
408
g_IsMappingMouseInput = false;
409
if (callback_) {
410
MultiInputMapping kdf(InputMapping(key.deviceId, key.keyCode));
411
callback_(kdf);
412
}
413
}
414
return true;
415
}
416
417
// Only used during the bind process. In other places, it's configurable for some types of axis, like trigger.
418
const float AXIS_BIND_THRESHOLD = 0.75f;
419
const float AXIS_BIND_RELEASE_THRESHOLD = 0.35f; // Used during mapping only to detect a "key-up" reliably.
420
421
void KeyMappingNewKeyDialog::axis(const AxisInput &axis) {
422
if (time_now_d() < delayUntil_)
423
return;
424
if (ignoreInput_)
425
return;
426
427
if (axis.value > AXIS_BIND_THRESHOLD) {
428
InputMapping mapping(axis.deviceId, axis.axisId, 1);
429
triggeredAxes_.insert(mapping);
430
if (!g_Config.bAllowMappingCombos && !mapping_.mappings.empty()) {
431
if (mapping_.mappings.size() == 1 && mapping != mapping_.mappings[0])
432
comboMappingsNotEnabled_->SetVisibility(UI::V_VISIBLE);
433
} else if (!mapping_.mappings.contains(mapping)) {
434
mapping_.mappings.push_back(mapping);
435
RecreateViews();
436
}
437
} else if (axis.value < -AXIS_BIND_THRESHOLD) {
438
InputMapping mapping(axis.deviceId, axis.axisId, -1);
439
triggeredAxes_.insert(mapping);
440
if (!g_Config.bAllowMappingCombos && !mapping_.mappings.empty()) {
441
if (mapping_.mappings.size() == 1 && mapping != mapping_.mappings[0])
442
comboMappingsNotEnabled_->SetVisibility(UI::V_VISIBLE);
443
} else if (!mapping_.mappings.contains(mapping)) {
444
mapping_.mappings.push_back(mapping);
445
RecreateViews();
446
}
447
} else if (fabsf(axis.value) < AXIS_BIND_RELEASE_THRESHOLD) {
448
InputMapping neg(axis.deviceId, axis.axisId, -1);
449
InputMapping pos(axis.deviceId, axis.axisId, 1);
450
if (triggeredAxes_.find(neg) != triggeredAxes_.end() || triggeredAxes_.find(pos) != triggeredAxes_.end()) {
451
// "Key-up" the axis.
452
TriggerFinish(DR_YES);
453
if (callback_)
454
callback_(mapping_);
455
}
456
}
457
}
458
459
void KeyMappingNewMouseKeyDialog::axis(const AxisInput &axis) {
460
if (mapped_)
461
return;
462
463
if (axis.value > AXIS_BIND_THRESHOLD) {
464
mapped_ = true;
465
TriggerFinish(DR_YES);
466
if (callback_) {
467
MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, 1));
468
callback_(kdf);
469
}
470
}
471
472
if (axis.value < -AXIS_BIND_THRESHOLD) {
473
mapped_ = true;
474
TriggerFinish(DR_YES);
475
if (callback_) {
476
MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, -1));
477
callback_(kdf);
478
}
479
}
480
}
481
482
AnalogCalibrationScreen::AnalogCalibrationScreen(const Path &gamePath) : UITwoPaneBaseDialogScreen(gamePath, TwoPaneFlags::SettingsCanScroll) {
483
mapper_.SetCallbacks(
484
[](int vkey, bool down) {},
485
[](int vkey, float analogValue) {},
486
[&](uint32_t bitsToSet, uint32_t bitsToClear) {},
487
[&](int iInternalRotation, int stick, float x, float y) {
488
analogX_[stick] = x;
489
analogY_[stick] = y;
490
},
491
[&](int stick, float x, float y) {
492
rawX_[stick] = x;
493
rawY_[stick] = y;
494
});
495
}
496
497
void AnalogCalibrationScreen::update() {
498
mapper_.Update(g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()), time_now_d());
499
// We ignore the secondary stick for now and just use the two views
500
// for raw and psp input.
501
if (stickView_[0]) {
502
stickView_[0]->SetXY(analogX_[0], analogY_[0]);
503
}
504
if (stickView_[1]) {
505
stickView_[1]->SetXY(rawX_[0], rawY_[0]);
506
}
507
UIScreen::update();
508
}
509
510
bool AnalogCalibrationScreen::key(const KeyInput &key) {
511
bool retval = UIScreen::key(key);
512
513
// Allow testing auto-rotation. If it collides with UI keys, too bad.
514
bool pauseTrigger = false;
515
mapper_.Key(key, &pauseTrigger);
516
517
if (UI::IsEscapeKey(key)) {
518
TriggerFinish(DR_BACK);
519
return retval;
520
}
521
return retval;
522
}
523
524
void AnalogCalibrationScreen::axis(const AxisInput &axis) {
525
// We DON'T call UIScreen::Axis here! Otherwise it'll try to move the UI focus around.
526
// UIScreen::axis(axis);
527
528
// Instead we just send the input directly to the mapper, that we'll visualize.
529
mapper_.Axis(&axis, 1);
530
}
531
532
std::string_view AnalogCalibrationScreen::GetTitle() const {
533
auto co = GetI18NCategory(I18NCat::CONTROLS);
534
return co->T("Calibrate analog stick");
535
}
536
537
void AnalogCalibrationScreen::CreateSettingsViews(UI::ViewGroup *scrollContents) {
538
using namespace UI;
539
auto co = GetI18NCategory(I18NCat::CONTROLS);
540
541
scrollContents->Add(new ItemHeader(co->T("Analog Settings")));
542
543
// TODO: Would be nicer if these didn't pop up...
544
scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogDeadzone, 0.0f, 0.5f, 0.15f, co->T("Deadzone radius"), 0.01f, screenManager(), "/ 1.0"));
545
scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogInverseDeadzone, 0.0f, 1.0f, 0.0f, co->T("Low end radius"), 0.01f, screenManager(), "/ 1.0"));
546
scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogSensitivity, 0.0f, 2.0f, 1.1f, co->T("Sensitivity (scale)", "Sensitivity"), 0.01f, screenManager(), "x"));
547
// TODO: This should probably be a slider.
548
scrollContents->Add(new CheckBox(&g_Config.bAnalogIsCircular, co->T("Circular stick input")));
549
scrollContents->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogAutoRotSpeed, 0.1f, 20.0f, 8.0f, co->T("Auto-rotation speed"), 1.0f, screenManager()));
550
scrollContents->Add(new Choice(co->T("Reset to defaults")))->OnClick.Handle(this, &AnalogCalibrationScreen::OnResetToDefaults);
551
}
552
553
void AnalogCalibrationScreen::CreateContentViews(UI::ViewGroup *parent) {
554
using namespace UI;
555
auto co = GetI18NCategory(I18NCat::CONTROLS);
556
557
// Two joystick views, one for calibrated output, one for raw input.
558
LinearLayout *theTwo = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(1.0f));
559
560
stickView_[0] = theTwo->Add(new JoystickHistoryView(StickHistoryViewType::OUTPUT, co->T("Calibrated"), new LinearLayoutParams(1.0f)));
561
stickView_[1] = theTwo->Add(new JoystickHistoryView(StickHistoryViewType::INPUT, co->T("Raw input"), new LinearLayoutParams(1.0f)));
562
563
parent->Add(theTwo);
564
}
565
566
void AnalogCalibrationScreen::OnResetToDefaults(UI::EventParams &e) {
567
g_Config.fAnalogDeadzone = 0.15f;
568
g_Config.fAnalogInverseDeadzone = 0.0f;
569
g_Config.fAnalogSensitivity = 1.1f;
570
g_Config.bAnalogIsCircular = false;
571
g_Config.fAnalogAutoRotSpeed = 8.0f;
572
}
573
574
class Backplate : public UI::InertView {
575
public:
576
explicit Backplate(float scale, UI::LayoutParams *layoutParams = nullptr) : InertView(layoutParams), scale_(scale) {}
577
578
void Draw(UIContext &dc) override {
579
for (float dy = 0.0f; dy <= 4.0f; dy += 1.0f) {
580
for (float dx = 0.0f; dx <= 4.0f; dx += 1.0f) {
581
if (dx == 2.0f && dy == 2.0f)
582
continue;
583
DrawPSP(dc, dx, dy, 0x06C1B6B6);
584
}
585
}
586
DrawPSP(dc, 2.0f, 2.0f, 0xC01C1818);
587
}
588
589
void DrawPSP(UIContext &dc, float xoff, float yoff, uint32_t color) {
590
using namespace UI;
591
592
const AtlasImage *whiteImage = dc.Draw()->GetAtlas()->getImage(dc.GetTheme().whiteImage);
593
float centerU = (whiteImage->u1 + whiteImage->u2) * 0.5f;
594
float centerV = (whiteImage->v1 + whiteImage->v2) * 0.5f;
595
596
auto V = [&](float x, float y) {
597
dc.Draw()->V(bounds_.x + (x + xoff) * scale_, bounds_.y + (y + yoff) * scale_, color, centerU, centerV);
598
};
599
auto R = [&](float x1, float y1, float x2, float y2) {
600
V(x1, y1); V(x2, y1); V(x2, y2);
601
V(x1, y1); V(x2, y2); V(x1, y2);
602
};
603
604
// Curved left side.
605
V(12.0f, 44.0f); V(30.0f, 16.0f); V(30.0f, 44.0f);
606
V(0.0f, 80.0f); V(12.0f, 44.0f); V(12.0f, 80.0f);
607
R(12.0f, 44.0f, 30.0f, 80.0f);
608
R(0.0f, 80.0f, 30.0f, 114.0f);
609
V(0.0f, 114.0f); V(12.0f, 114.0f); V(12.0f, 154.0f);
610
R(12.0f, 114.0f, 30.0f, 154.0f);
611
V(12.0f, 154.0f); V(30.0f, 154.0f); V(30.0f, 180.0f);
612
// Left side.
613
V(30.0f, 16.0f); V(64.0f, 13.0f); V(64.0f, 184.0f);
614
V(30.0f, 16.0f); V(64.0f, 184.0f); V(30.0f, 180.0f);
615
V(64.0f, 13.0f); V(76.0f, 0.0f); V(76.0f, 13.0f);
616
V(64.0f, 184.0f); V(76.0f, 200.0f); V(76.0f, 184.0f);
617
R(64.0f, 13.0f, 76.0f, 184.0f);
618
// Center.
619
R(76.0f, 0.0f, 400.0f, 13.0f);
620
R(76.0f, 167.0f, 400.0f, 200.0f);
621
R(76.0f, 13.0f, 99.0f, 167.0f);
622
R(377.0f, 13.0f, 400.0f, 167.0f);
623
// Right side.
624
V(400.0f, 0.0f); V(412.0f, 13.0f); V(400.0f, 13.0f);
625
V(400.0f, 184.0f); V(412.0f, 184.0f); V(400.0f, 200.0f);
626
R(400.0f, 13.0f, 412.0f, 184.0f);
627
V(412.0f, 13.0f); V(446.0f, 16.0f); V(446.0f, 180.0f);
628
V(412.0f, 13.0f); V(446.0f, 180.0f); V(412.0f, 184.0f);
629
// Curved right side.
630
V(446.0f, 16.0f); V(462.0f, 44.0f); V(446.0f, 44.0f);
631
V(462.0f, 44.0f); V(474.0f, 80.0f); V(462.0f, 80.0f);
632
R(446.0f, 44.0f, 462.0f, 80.0f);
633
R(446.0f, 80.0f, 474.0f, 114.0f);
634
V(462.0f, 114.0f); V(474.0f, 114.0f); V(462.0f, 154.0f);
635
R(446.0f, 114.0f, 462.0f, 154.0f);
636
V(446.0f, 154.0f); V(462.0f, 154.0f); V(446.0f, 180.0f);
637
}
638
639
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
640
w = 478.0f * scale_;
641
h = 204.0f * scale_;
642
}
643
644
protected:
645
float scale_ = 1.0f;
646
};
647
648
class MockScreen : public UI::InertView {
649
public:
650
explicit MockScreen(UI::LayoutParams *layoutParams = nullptr) : InertView(layoutParams) {
651
}
652
653
void Draw(UIContext &dc) override {
654
ImageID bg = ImageID("I_PSP_DISPLAY");
655
dc.Draw()->DrawImageStretch(bg, bounds_, 0x7FFFFFFF);
656
}
657
};
658
659
class MockButton : public UI::Clickable {
660
public:
661
MockButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *layoutParams = nullptr)
662
: Clickable(layoutParams), button_(button), img_(img), bgImg_(bg), angle_(angle) {
663
}
664
665
void Draw(UIContext &dc) override {
666
uint32_t c = 0xFFFFFFFF;
667
if (HasFocus() || Selected())
668
c = dc.GetTheme().itemFocusedStyle.background.color;
669
670
float scales[2]{};
671
if (bgImg_.isValid())
672
dc.Draw()->DrawImageRotatedStretch(bgImg_, bounds_, scales, angle_, c, flipHBG_);
673
if (img_.isValid()) {
674
scales[0] *= scaleX_;
675
scales[1] *= scaleY_;
676
if (timeLastPressed_ >= 0.0) {
677
double sincePress = time_now_d() - timeLastPressed_;
678
if (sincePress < 1.0) {
679
c = colorBlend(c, dc.GetTheme().itemDownStyle.background.color, (float)sincePress);
680
}
681
}
682
dc.Draw()->DrawImageRotatedStretch(img_, bounds_.Offset(offsetX_, offsetY_), scales, angle_, c);
683
}
684
}
685
686
MockButton *SetScale(float s) {
687
scaleX_ = s;
688
scaleY_ = s;
689
return this;
690
}
691
692
MockButton *SetScale(float x, float y) {
693
scaleX_ = x;
694
scaleY_ = y;
695
return this;
696
}
697
698
MockButton *SetFlipHBG(bool b) {
699
flipHBG_ = b;
700
return this;
701
}
702
703
MockButton *SetOffset(float x, float y) {
704
offsetX_ = x;
705
offsetY_ = y;
706
return this;
707
}
708
709
MockButton *SetSelectedButton(int *s) {
710
selectedButton_ = s;
711
return this;
712
}
713
714
bool Selected() {
715
return selectedButton_ && *selectedButton_ == button_;
716
}
717
718
int Button() const {
719
return button_;
720
}
721
722
void NotifyPressed() {
723
timeLastPressed_ = time_now_d();
724
}
725
726
private:
727
int button_;
728
ImageID img_;
729
ImageID bgImg_;
730
float angle_;
731
float scaleX_ = 1.0f;
732
float scaleY_ = 1.0f;
733
float offsetX_ = 0.0f;
734
float offsetY_ = 0.0f;
735
bool flipHBG_ = false;
736
int *selectedButton_ = nullptr;
737
double timeLastPressed_ = -1.0;
738
};
739
740
class MockPSP : public UI::AnchorLayout {
741
public:
742
static constexpr float SCALE = 1.4f;
743
744
explicit MockPSP(UI::LayoutParams *layoutParams = nullptr);
745
void SelectButton(int btn);
746
void FocusButton(int btn);
747
void NotifyPressed(int btn);
748
float GetPopupOffset();
749
750
bool SubviewFocused(View *view) override;
751
752
UI::Event ButtonClick;
753
754
private:
755
UI::AnchorLayoutParams *LayoutAt(float l, float t, float r, float b);
756
UI::AnchorLayoutParams *LayoutSize(float w, float h, float l, float t);
757
MockButton *AddButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *lp);
758
759
void OnSelectButton(UI::EventParams &e);
760
761
std::unordered_map<int, MockButton *> buttons_;
762
UI::TextView *labelView_ = nullptr;
763
int selectedButton_ = 0;
764
};
765
766
MockPSP::MockPSP(UI::LayoutParams *layoutParams) : AnchorLayout(layoutParams) {
767
Add(new Backplate(SCALE));
768
Add(new MockScreen(LayoutAt(99.0f, 13.0f, 97.0f, 33.0f)));
769
770
// Left side.
771
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));
772
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);
773
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);
774
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);
775
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);
776
777
// Top.
778
AddButton(CTRL_LTRIGGER, ImageID("I_L"), ImageID("I_SHOULDER_LINE"), 0.0f, LayoutSize(50.0f, 16.0f, 29.0f, 0.0f));
779
AddButton(CTRL_RTRIGGER, ImageID("I_R"), ImageID("I_SHOULDER_LINE"), 0.0f, LayoutSize(50.0f, 16.0f, 397.0f, 0.0f))->SetFlipHBG(true);
780
781
// Bottom.
782
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);
783
AddButton(CTRL_SELECT, ImageID("I_SELECT"), ImageID("I_RECT_LINE"), 0.0f, LayoutSize(28.0f, 14.0f, 330.0f, 181.0f));
784
AddButton(CTRL_START, ImageID("I_START"), ImageID("I_RECT_LINE"), 0.0f, LayoutSize(28.0f, 14.0f, 361.0f, 181.0f));
785
786
// Right side.
787
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);
788
AddButton(CTRL_CIRCLE, ImageID("I_CIRCLE"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 446.0f, 74.0f))->SetScale(0.7f);
789
AddButton(CTRL_CROSS, ImageID("I_CROSS"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 419.0f, 102.0f))->SetScale(0.7f);
790
AddButton(CTRL_SQUARE, ImageID("I_SQUARE"), ImageID("I_ROUND_LINE"), 0.0f, LayoutSize(23.0f, 23.0f, 392.0f, 74.0f))->SetScale(0.7f);
791
792
labelView_ = Add(new UI::TextView(""));
793
labelView_->SetShadow(true);
794
labelView_->SetVisibility(UI::V_GONE);
795
}
796
797
void MockPSP::SelectButton(int btn) {
798
selectedButton_ = btn;
799
}
800
801
void MockPSP::FocusButton(int btn) {
802
MockButton *view = buttons_[btn];
803
if (view) {
804
view->SetFocus();
805
} else {
806
labelView_->SetVisibility(UI::V_GONE);
807
}
808
}
809
810
void MockPSP::NotifyPressed(int btn) {
811
MockButton *view = buttons_[btn];
812
if (view)
813
view->NotifyPressed();
814
}
815
816
bool MockPSP::SubviewFocused(View *view) {
817
for (auto it : buttons_) {
818
if (view == it.second) {
819
labelView_->SetVisibility(UI::V_VISIBLE);
820
labelView_->SetText(KeyMap::GetPspButtonName(it.first));
821
822
const Bounds &pos = view->GetBounds().Offset(-GetBounds().x, -GetBounds().y);
823
labelView_->ReplaceLayoutParams(new UI::AnchorLayoutParams(pos.centerX(), pos.y2() + 5, UI::NONE, UI::NONE));
824
}
825
}
826
return AnchorLayout::SubviewFocused(view);
827
}
828
829
float MockPSP::GetPopupOffset() {
830
MockButton *view = buttons_[selectedButton_];
831
if (!view)
832
return 0.0f;
833
834
float ypos = view->GetBounds().centerY();
835
if (ypos > bounds_.centerY()) {
836
return -0.25f;
837
}
838
return 0.25f;
839
}
840
841
UI::AnchorLayoutParams *MockPSP::LayoutAt(float l, float t, float r, float b) {
842
return new UI::AnchorLayoutParams(l * SCALE, t * SCALE, r * SCALE, b * SCALE);
843
}
844
UI::AnchorLayoutParams *MockPSP::LayoutSize(float w, float h, float l, float t) {
845
return new UI::AnchorLayoutParams(w * SCALE, h * SCALE, l * SCALE, t * SCALE, UI::NONE, UI::NONE);
846
}
847
848
MockButton *MockPSP::AddButton(int button, ImageID img, ImageID bg, float angle, UI::LayoutParams *lp) {
849
MockButton *view = Add(new MockButton(button, img, bg, angle, lp));
850
view->OnClick.Handle(this, &MockPSP::OnSelectButton);
851
view->SetSelectedButton(&selectedButton_);
852
buttons_[button] = view;
853
return view;
854
}
855
856
void MockPSP::OnSelectButton(UI::EventParams &e) {
857
auto view = (MockButton *)e.v;
858
e.a = view->Button();
859
return ButtonClick.Dispatch(e);
860
}
861
862
static std::vector<int> bindAllOrder{
863
CTRL_LTRIGGER,
864
CTRL_RTRIGGER,
865
CTRL_UP,
866
CTRL_DOWN,
867
CTRL_LEFT,
868
CTRL_RIGHT,
869
VIRTKEY_AXIS_Y_MAX,
870
VIRTKEY_AXIS_Y_MIN,
871
VIRTKEY_AXIS_X_MIN,
872
VIRTKEY_AXIS_X_MAX,
873
CTRL_HOME,
874
CTRL_SELECT,
875
CTRL_START,
876
CTRL_CROSS,
877
CTRL_CIRCLE,
878
CTRL_TRIANGLE,
879
CTRL_SQUARE,
880
};
881
882
void VisualMappingScreen::CreateViews() {
883
using namespace UI;
884
885
auto km = GetI18NCategory(I18NCat::KEYMAPPING);
886
887
root_ = new LinearLayout(ORIENT_HORIZONTAL);
888
889
constexpr float leftColumnWidth = 200.0f;
890
LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(leftColumnWidth, FILL_PARENT, Margins(10, 0, 0, 10)));
891
leftColumn->Add(new Choice(km->T("Bind All")))->OnClick.Handle(this, &VisualMappingScreen::OnBindAll);
892
leftColumn->Add(new CheckBox(&replace_, km->T("Replace"), ""));
893
894
leftColumn->Add(new Spacer(new LinearLayoutParams(1.0f)));
895
AddStandardBack(leftColumn);
896
897
Bounds bounds = screenManager()->getUIContext()->GetLayoutBounds();
898
// Account for left side.
899
bounds.w -= leftColumnWidth + 10.0f;
900
901
AnchorLayout *rightColumn = new AnchorLayout(new LinearLayoutParams(bounds.w, FILL_PARENT, 1.0f));
902
psp_ = rightColumn->Add(new MockPSP(new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, Centering::Both)));
903
psp_->ButtonClick.Handle(this, &VisualMappingScreen::OnMapButton);
904
905
root_->Add(leftColumn);
906
root_->Add(rightColumn);
907
}
908
909
bool VisualMappingScreen::key(const KeyInput &key) {
910
if (key.flags & KeyInputFlags::DOWN) {
911
std::vector<int> pspKeys;
912
KeyMap::InputMappingToPspButton(InputMapping(key.deviceId, key.keyCode), &pspKeys);
913
for (int pspKey : pspKeys) {
914
switch (pspKey) {
915
case VIRTKEY_AXIS_X_MIN:
916
case VIRTKEY_AXIS_Y_MIN:
917
case VIRTKEY_AXIS_X_MAX:
918
case VIRTKEY_AXIS_Y_MAX:
919
psp_->NotifyPressed(VIRTKEY_AXIS_Y_MAX);
920
break;
921
default:
922
psp_->NotifyPressed(pspKey);
923
break;
924
}
925
}
926
}
927
return UIBaseDialogScreen::key(key);
928
}
929
930
void VisualMappingScreen::axis(const AxisInput &axis) {
931
std::vector<int> results;
932
if (axis.value >= g_Config.fAnalogDeadzone * 0.7f)
933
KeyMap::InputMappingToPspButton(InputMapping(axis.deviceId, axis.axisId, 1), &results);
934
if (axis.value <= g_Config.fAnalogDeadzone * -0.7f)
935
KeyMap::InputMappingToPspButton(InputMapping(axis.deviceId, axis.axisId, -1), &results);
936
937
for (int result : results) {
938
switch (result) {
939
case VIRTKEY_AXIS_X_MIN:
940
case VIRTKEY_AXIS_Y_MIN:
941
case VIRTKEY_AXIS_X_MAX:
942
case VIRTKEY_AXIS_Y_MAX:
943
psp_->NotifyPressed(VIRTKEY_AXIS_Y_MAX);
944
break;
945
default:
946
psp_->NotifyPressed(result);
947
break;
948
}
949
}
950
UIBaseDialogScreen::axis(axis);
951
}
952
953
void VisualMappingScreen::resized() {
954
UIBaseDialogScreen::resized();
955
RecreateViews();
956
}
957
958
void VisualMappingScreen::OnMapButton(UI::EventParams &e) {
959
nextKey_ = e.a;
960
MapNext(false);
961
}
962
963
void VisualMappingScreen::OnBindAll(UI::EventParams &e) {
964
bindAll_ = 0;
965
nextKey_ = bindAllOrder[bindAll_];
966
MapNext(false);
967
}
968
969
void VisualMappingScreen::HandleKeyMapping(const KeyMap::MultiInputMapping &key) {
970
KeyMap::SetInputMapping(nextKey_, key, replace_);
971
KeyMap::UpdateNativeMenuKeys();
972
973
if (bindAll_ < 0) {
974
// For analog, we do each direction in a row.
975
if (nextKey_ == VIRTKEY_AXIS_Y_MAX)
976
nextKey_ = VIRTKEY_AXIS_Y_MIN;
977
else if (nextKey_ == VIRTKEY_AXIS_Y_MIN)
978
nextKey_ = VIRTKEY_AXIS_X_MIN;
979
else if (nextKey_ == VIRTKEY_AXIS_X_MIN)
980
nextKey_ = VIRTKEY_AXIS_X_MAX;
981
else {
982
if (nextKey_ == VIRTKEY_AXIS_X_MAX)
983
psp_->FocusButton(VIRTKEY_AXIS_Y_MAX);
984
else
985
psp_->FocusButton(nextKey_);
986
nextKey_ = 0;
987
}
988
} else if ((size_t)bindAll_ + 1 < bindAllOrder.size()) {
989
bindAll_++;
990
nextKey_ = bindAllOrder[bindAll_];
991
} else {
992
bindAll_ = -1;
993
nextKey_ = 0;
994
}
995
}
996
997
void VisualMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) {
998
if (result == DR_YES && nextKey_ != 0) {
999
MapNext(true);
1000
} else {
1001
// This means they canceled.
1002
if (nextKey_ != 0)
1003
psp_->FocusButton(nextKey_);
1004
nextKey_ = 0;
1005
bindAll_ = -1;
1006
psp_->SelectButton(0);
1007
}
1008
}
1009
1010
void VisualMappingScreen::MapNext(bool successive) {
1011
if (nextKey_ == VIRTKEY_AXIS_Y_MIN || nextKey_ == VIRTKEY_AXIS_X_MIN || nextKey_ == VIRTKEY_AXIS_X_MAX) {
1012
psp_->SelectButton(VIRTKEY_AXIS_Y_MAX);
1013
} else {
1014
psp_->SelectButton(nextKey_);
1015
}
1016
1017
auto dialog = new KeyMappingNewKeyDialog(nextKey_, true, [this](KeyMap::MultiInputMapping mapping) {
1018
HandleKeyMapping(mapping);
1019
}, I18NCat::KEYMAPPING);
1020
1021
Bounds bounds = screenManager()->getUIContext()->GetLayoutBounds();
1022
dialog->SetPopupOffset(psp_->GetPopupOffset() * bounds.h);
1023
dialog->SetDelay(successive ? 0.5f : 0.1f);
1024
screenManager()->push(dialog);
1025
}
1026
1027