CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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