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