Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/GamepadEmu.cpp
5665 views
1
// Copyright (c) 2012- 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 <algorithm>
19
20
#include "Common/Data/Color/RGBAUtil.h"
21
#include "Common/Data/Text/I18n.h"
22
#include "Common/System/Display.h"
23
#include "Common/System/System.h"
24
#include "Common/Render/TextureAtlas.h"
25
#include "Common/Math/math_util.h"
26
#include "Common/UI/Context.h"
27
28
#include "Common/Log.h"
29
#include "Common/TimeUtil.h"
30
#include "Core/Config.h"
31
#include "Core/Core.h"
32
#include "Core/System.h"
33
#include "Core/HLE/sceCtrl.h"
34
#include "Core/ControlMapper.h"
35
#include "UI/GamepadEmu.h"
36
37
const float TOUCH_SCALE_FACTOR = 1.5f;
38
39
static uint32_t usedPointerMask = 0;
40
static uint32_t analogPointerMask = 0;
41
42
static float g_gamepadOpacity;
43
static double g_lastTouch;
44
45
MultiTouchButton *primaryButton[TOUCH_MAX_POINTERS]{};
46
std::set<int> g_activeGesturePointers;
47
48
void GamepadUpdateOpacity(float force) {
49
if (force >= 0.0f) {
50
g_gamepadOpacity = force;
51
return;
52
}
53
if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_POWERDOWN || coreState == CORE_STEPPING_GE) {
54
// No need to show the controls.
55
g_gamepadOpacity = 0.0f;
56
return;
57
}
58
59
float fadeAfterSeconds = g_Config.iTouchButtonHideSeconds;
60
float fadeTransitionSeconds = std::min(fadeAfterSeconds, 0.5f);
61
float opacity = g_Config.iTouchButtonOpacity / 100.0f;
62
63
float multiplier = 1.0f;
64
float secondsWithoutTouch = time_now_d() - g_lastTouch;
65
if (secondsWithoutTouch >= fadeAfterSeconds && fadeAfterSeconds > 0.0f) {
66
if (secondsWithoutTouch >= fadeAfterSeconds + fadeTransitionSeconds) {
67
multiplier = 0.0f;
68
} else {
69
float secondsIntoFade = secondsWithoutTouch - fadeAfterSeconds;
70
multiplier = 1.0f - (secondsIntoFade / fadeTransitionSeconds);
71
}
72
}
73
74
g_gamepadOpacity = opacity * multiplier;
75
}
76
77
void GamepadResetTouch() {
78
g_lastTouch = 0.0f;
79
}
80
81
void GamepadTouch() {
82
g_lastTouch = time_now_d();
83
}
84
85
float GamepadGetOpacity() {
86
return g_gamepadOpacity;
87
}
88
89
static u32 GetButtonColor() {
90
return g_Config.iTouchButtonStyle != 0 ? 0xFFFFFF : 0xc0b080;
91
}
92
93
GamepadComponent::GamepadComponent(std::string_view key, UI::LayoutParams *layoutParams) : UI::View(layoutParams), key_(key) {}
94
95
std::string GamepadComponent::DescribeText() const {
96
return key_;
97
}
98
99
void MultiTouchButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
100
const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(bgImg_);
101
if (image) {
102
w = image->w * scale_;
103
h = image->h * scale_;
104
} else {
105
w = 0.0f;
106
h = 0.0f;
107
}
108
}
109
110
bool MultiTouchButton::CanGlide() const {
111
return g_Config.bTouchGliding;
112
}
113
114
bool MultiTouchButton::Touch(const TouchInput &input) {
115
_dbg_assert_(input.id >= 0 && input.id < TOUCH_MAX_POINTERS);
116
117
bool retval = GamepadComponent::Touch(input);
118
if ((input.flags & TouchInputFlags::DOWN) && bounds_.Contains(input.x, input.y)) {
119
pointerDownMask_ |= 1 << input.id;
120
usedPointerMask |= 1 << input.id;
121
if (CanGlide() && !primaryButton[input.id])
122
primaryButton[input.id] = this;
123
}
124
if (input.flags & TouchInputFlags::MOVE) {
125
if (!(input.flags & TouchInputFlags::MOUSE) || input.buttons) {
126
bool ignorePress = false;
127
if (g_activeGesturePointers.count(input.id)) {
128
// Don't start a button touch if dragging and this pointer is already being used for a gesture.
129
ignorePress = true;
130
}
131
if (bounds_.Contains(input.x, input.y) && !(analogPointerMask & (1 << input.id))) {
132
if (!ignorePress) {
133
if (CanGlide() && !primaryButton[input.id]) {
134
primaryButton[input.id] = this;
135
}
136
pointerDownMask_ |= 1 << input.id;
137
}
138
} else if (primaryButton[input.id] != this) {
139
pointerDownMask_ &= ~(1 << input.id);
140
}
141
}
142
}
143
if (input.flags & TouchInputFlags::UP) {
144
pointerDownMask_ &= ~(1 << input.id);
145
usedPointerMask &= ~(1 << input.id);
146
primaryButton[input.id] = nullptr;
147
}
148
if (input.flags & TouchInputFlags::RELEASE_ALL) {
149
pointerDownMask_ = 0;
150
usedPointerMask = 0;
151
}
152
return retval;
153
}
154
155
void MultiTouchButton::Draw(UIContext &dc) {
156
float opacity = std::max(g_gamepadOpacity, minimumAlpha_);
157
if (opacity <= 0.0f)
158
return;
159
160
float scale = scale_;
161
if (IsDown()) {
162
if (g_Config.iTouchButtonStyle == 2) {
163
opacity *= 1.35f;
164
} else {
165
scale *= TOUCH_SCALE_FACTOR;
166
opacity *= 1.15f;
167
}
168
}
169
170
uint32_t colorBg = colorAlpha(GetButtonColor(), opacity);
171
uint32_t downBg = colorAlpha(0xFFFFFF, opacity * 0.5f);
172
uint32_t color = colorAlpha(0xFFFFFF, opacity);
173
174
if (IsDown() && g_Config.iTouchButtonStyle == 2) {
175
if (bgImg_ != bgDownImg_)
176
dc.Draw()->DrawImageRotated(bgDownImg_, bounds_.centerX(), bounds_.centerY(), scale, bgAngle_ * (M_PI * 2 / 360.0f), downBg, flipImageH_);
177
}
178
179
dc.Draw()->DrawImageRotated(bgImg_, bounds_.centerX(), bounds_.centerY(), scale, bgAngle_ * (M_PI * 2 / 360.0f), colorBg, flipImageH_);
180
181
float x = bounds_.centerX();
182
float y = bounds_.centerY();
183
// Hack round the fact that the center of the rectangular picture the triangle is contained in
184
// is not at the "weight center" of the triangle. Same for fast-forward.
185
if (img_ == ImageID("I_TRIANGLE"))
186
y -= 2.8f * scale;
187
if (img_ == ImageID("I_FAST_FORWARD_LINE"))
188
x += 2.0f * scale;
189
dc.Draw()->DrawImageRotated(img_, x, y, scale, angle_ * (M_PI * 2 / 360.0f), color);
190
}
191
192
bool BoolButton::Touch(const TouchInput &input) {
193
bool lastDown = pointerDownMask_ != 0;
194
bool retval = MultiTouchButton::Touch(input);
195
bool down = pointerDownMask_ != 0;
196
197
if (down != lastDown) {
198
*value_ = down;
199
UI::EventParams params{ this };
200
params.a = down;
201
OnChange.Trigger(params);
202
}
203
return retval;
204
}
205
206
bool PSPButton::Touch(const TouchInput &input) {
207
bool lastDown = pointerDownMask_ != 0;
208
bool retval = MultiTouchButton::Touch(input);
209
bool down = pointerDownMask_ != 0;
210
if (down && !lastDown) {
211
if (g_Config.bHapticFeedback) {
212
System_Vibrate(HAPTIC_VIRTUAL_KEY);
213
}
214
__CtrlUpdateButtons(pspButtonBit_, 0);
215
} else if (lastDown && !down) {
216
__CtrlUpdateButtons(0, pspButtonBit_);
217
}
218
return retval;
219
}
220
221
bool CustomButton::IsDown() const {
222
return (toggle_ && on_) || (!toggle_ && pointerDownMask_ != 0);
223
}
224
225
bool CustomButton::IsDownForFadeoutCheck() const {
226
// This check is due to a stupid mistake, see the header.
227
#ifdef MOBILE_DEVICE
228
constexpr int bitNumber = 36;
229
#else
230
constexpr int bitNumber = 38;
231
#endif
232
const bool down = IsDown() && !(pspButtonBit_ & (1ULL << bitNumber)); // VIRTKEY_TOGGLE_TOUCH_CONTROLS from g_customKeyList
233
return down;
234
}
235
236
void CustomButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
237
MultiTouchButton::GetContentDimensions(dc, w, h);
238
if (invertedContentDimension_) {
239
float tmp = w;
240
w = h;
241
h = tmp;
242
}
243
}
244
245
bool CustomButton::Touch(const TouchInput &input) {
246
using namespace CustomKeyData;
247
bool lastDown = pointerDownMask_ != 0;
248
bool retval = MultiTouchButton::Touch(input);
249
bool down = pointerDownMask_ != 0;
250
251
if (down && !lastDown) {
252
if (g_Config.bHapticFeedback)
253
System_Vibrate(HAPTIC_VIRTUAL_KEY);
254
255
if (!repeat_) {
256
for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) {
257
if (pspButtonBit_ & (1ULL << i)) {
258
controlMapper_->PSPKey(DEVICE_ID_TOUCH, g_customKeyList[i].c, (on_ && toggle_) ? KeyInputFlags::UP : KeyInputFlags::DOWN);
259
}
260
}
261
}
262
on_ = toggle_ ? !on_ : true;
263
} else if (!toggle_ && lastDown && !down) {
264
if (!repeat_) {
265
for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) {
266
if (pspButtonBit_ & (1ULL << i)) {
267
controlMapper_->PSPKey(DEVICE_ID_TOUCH, g_customKeyList[i].c, KeyInputFlags::UP);
268
}
269
}
270
}
271
on_ = false;
272
}
273
return retval;
274
}
275
276
void CustomButton::Update() {
277
MultiTouchButton::Update();
278
using namespace CustomKeyData;
279
280
if (repeat_) {
281
// Give the game some time to process the input, frame based so it's faster when fast-forwarding.
282
static constexpr int DOWN_FRAME = 5;
283
284
if (pressedFrames_ == 2*DOWN_FRAME) {
285
pressedFrames_ = 0;
286
} else if (pressedFrames_ == DOWN_FRAME) {
287
for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) {
288
if (pspButtonBit_ & (1ULL << i)) {
289
controlMapper_->PSPKey(DEVICE_ID_TOUCH, g_customKeyList[i].c, KeyInputFlags::UP);
290
}
291
}
292
} else if (on_ && pressedFrames_ == 0) {
293
for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) {
294
if (pspButtonBit_ & (1ULL << i)) {
295
controlMapper_->PSPKey(DEVICE_ID_TOUCH, g_customKeyList[i].c, KeyInputFlags::DOWN);
296
}
297
}
298
pressedFrames_ = 1;
299
}
300
301
if (pressedFrames_ > 0)
302
pressedFrames_++;
303
}
304
}
305
306
bool PSPButton::IsDown() const {
307
return (__CtrlPeekButtonsVisual() & pspButtonBit_) != 0;
308
}
309
310
PSPDpad::PSPDpad(ImageID arrowIndex, std::string_view key, ImageID arrowDownIndex, ImageID overlayIndex, float scale, float spacing, UI::LayoutParams *layoutParams)
311
: GamepadComponent(key, layoutParams), arrowIndex_(arrowIndex), arrowDownIndex_(arrowDownIndex), overlayIndex_(overlayIndex),
312
scale_(scale), spacing_(spacing), dragPointerId_(-1), down_(0) {
313
}
314
315
void PSPDpad::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
316
const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(arrowIndex_);
317
if (image) {
318
w = 2.0f * D_pad_Radius * spacing_ + image->w * scale_;
319
h = w;
320
} else {
321
w = 0.0f;
322
h = 0.0f;
323
}
324
}
325
326
bool PSPDpad::Touch(const TouchInput &input) {
327
bool retval = GamepadComponent::Touch(input);
328
329
if (input.flags & TouchInputFlags::DOWN) {
330
if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y)) {
331
dragPointerId_ = input.id;
332
usedPointerMask |= 1 << input.id;
333
ProcessTouch(input.x, input.y, true, false);
334
}
335
}
336
if (input.flags & TouchInputFlags::MOVE) {
337
if (!(input.flags & TouchInputFlags::MOUSE) || input.buttons) {
338
bool ignorePress = false;
339
if (g_activeGesturePointers.count(input.id)) {
340
// Don't start a dpad touch if dragging and this pointer is already being used for a gesture.
341
ignorePress = true;
342
}
343
344
if (dragPointerId_ == -1 && bounds_.Contains(input.x, input.y) && !(analogPointerMask & (1 << input.id))) {
345
dragPointerId_ = input.id;
346
}
347
348
if (input.id == dragPointerId_) {
349
ProcessTouch(input.x, input.y, true, ignorePress);
350
}
351
}
352
}
353
if (input.flags & TouchInputFlags::UP) {
354
if (input.id == dragPointerId_) {
355
dragPointerId_ = -1;
356
usedPointerMask &= ~(1 << input.id);
357
ProcessTouch(input.x, input.y, false, false);
358
}
359
}
360
return retval;
361
}
362
363
void PSPDpad::ProcessTouch(float x, float y, bool down, bool ignorePress) {
364
float stick_size = bounds_.w;
365
float inv_stick_size = 1.0f / stick_size;
366
const float deadzone = 0.05f;
367
368
float dx = (x - bounds_.centerX()) * inv_stick_size;
369
float dy = (y - bounds_.centerY()) * inv_stick_size;
370
float rad = sqrtf(dx * dx + dy * dy);
371
if (!g_Config.bStickyTouchDPad && (rad < deadzone || fabs(dx) > 0.5f || fabs(dy) > 0.5))
372
down = false;
373
374
int ctrlMask = 0;
375
bool fourWay = g_Config.bDisableDpadDiagonals || rad < 0.2f;
376
if (down) {
377
if (fourWay) {
378
int direction = (int)(floorf((atan2f(dy, dx) / (2 * M_PI) * 4) + 0.5f)) & 3;
379
switch (direction) {
380
case 0: ctrlMask = CTRL_RIGHT; break;
381
case 1: ctrlMask = CTRL_DOWN; break;
382
case 2: ctrlMask = CTRL_LEFT; break;
383
case 3: ctrlMask = CTRL_UP; break;
384
}
385
// 4 way pad
386
} else {
387
// 8 way pad
388
int direction = (int)(floorf((atan2f(dy, dx) / (2 * M_PI) * 8) + 0.5f)) & 7;
389
switch (direction) {
390
case 0: ctrlMask = CTRL_RIGHT; break;
391
case 1: ctrlMask = CTRL_RIGHT | CTRL_DOWN; break;
392
case 2: ctrlMask = CTRL_DOWN; break;
393
case 3: ctrlMask = CTRL_DOWN | CTRL_LEFT; break;
394
case 4: ctrlMask = CTRL_LEFT; break;
395
case 5: ctrlMask = CTRL_UP | CTRL_LEFT; break;
396
case 6: ctrlMask = CTRL_UP; break;
397
case 7: ctrlMask = CTRL_UP | CTRL_RIGHT; break;
398
}
399
}
400
}
401
402
int lastDown = down_;
403
int pressed = ctrlMask & ~lastDown;
404
int released = (~ctrlMask) & lastDown;
405
down_ = ctrlMask;
406
bool vibrate = false;
407
static const int dir[4] = { CTRL_RIGHT, CTRL_DOWN, CTRL_LEFT, CTRL_UP };
408
for (int i = 0; i < 4; i++) {
409
if (!ignorePress && (pressed & dir[i])) {
410
vibrate = true;
411
__CtrlUpdateButtons(dir[i], 0);
412
}
413
if (released & dir[i]) {
414
__CtrlUpdateButtons(0, dir[i]);
415
}
416
}
417
if (vibrate && g_Config.bHapticFeedback) {
418
System_Vibrate(HAPTIC_VIRTUAL_KEY);
419
}
420
}
421
422
void PSPDpad::Draw(UIContext &dc) {
423
float opacity = g_gamepadOpacity;
424
if (opacity <= 0.0f)
425
return;
426
427
static const float xoff[4] = {1, 0, -1, 0};
428
static const float yoff[4] = {0, 1, 0, -1};
429
static const int dir[4] = {CTRL_RIGHT, CTRL_DOWN, CTRL_LEFT, CTRL_UP};
430
int buttons = __CtrlPeekButtons();
431
float r = D_pad_Radius * spacing_;
432
for (int i = 0; i < 4; i++) {
433
bool isDown = (buttons & dir[i]) != 0;
434
435
float x = bounds_.centerX() + xoff[i] * r;
436
float y = bounds_.centerY() + yoff[i] * r;
437
float x2 = bounds_.centerX() + xoff[i] * (r + 10.f * scale_);
438
float y2 = bounds_.centerY() + yoff[i] * (r + 10.f * scale_);
439
float angle = i * (M_PI / 2.0f);
440
float imgScale = isDown ? scale_ * TOUCH_SCALE_FACTOR : scale_;
441
float imgOpacity = opacity;
442
443
if (isDown && g_Config.iTouchButtonStyle == 2) {
444
imgScale = scale_;
445
imgOpacity *= 1.35f;
446
447
uint32_t downBg = colorAlpha(0x00FFFFFF, imgOpacity * 0.5f);
448
if (arrowIndex_ != arrowDownIndex_)
449
dc.Draw()->DrawImageRotated(arrowDownIndex_, x, y, imgScale, angle + PI, downBg, false);
450
}
451
452
uint32_t colorBg = colorAlpha(GetButtonColor(), imgOpacity);
453
uint32_t color = colorAlpha(0xFFFFFF, imgOpacity);
454
455
dc.Draw()->DrawImageRotated(arrowIndex_, x, y, imgScale, angle + PI, colorBg, false);
456
if (overlayIndex_.isValid())
457
dc.Draw()->DrawImageRotated(overlayIndex_, x2, y2, imgScale, angle + PI, color);
458
}
459
}
460
461
PSPStick::PSPStick(ImageID bgImg, std::string_view key, ImageID stickImg, ImageID stickDownImg, int stick, float scale, UI::LayoutParams *layoutParams)
462
: GamepadComponent(key, layoutParams), bgImg_(bgImg), stickImageIndex_(stickImg), stickDownImg_(stickDownImg), stick_(stick), scale_(scale) {
463
stick_size_ = 50;
464
}
465
466
void PSPStick::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
467
dc.Draw()->GetAtlas()->measureImage(bgImg_, &w, &h);
468
w *= scale_;
469
h *= scale_;
470
}
471
472
void PSPStick::Draw(UIContext &dc) {
473
float opacity = g_gamepadOpacity;
474
if (opacity <= 0.0f)
475
return;
476
477
if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2) {
478
opacity *= 1.35f;
479
}
480
481
uint32_t colorBg = colorAlpha(GetButtonColor(), opacity);
482
uint32_t downBg = colorAlpha(0x00FFFFFF, opacity * 0.5f);
483
484
if (centerX_ < 0.0f) {
485
centerX_ = bounds_.centerX();
486
centerY_ = bounds_.centerY();
487
}
488
489
float stickX = centerX_;
490
float stickY = centerY_;
491
492
float dx, dy;
493
__CtrlPeekAnalog(stick_, &dx, &dy);
494
495
const TouchControlConfig &config = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation());
496
497
if (!config.bHideStickBackground)
498
dc.Draw()->DrawImage(bgImg_, stickX, stickY, 1.0f * scale_, colorBg, ALIGN_CENTER);
499
float headScale = stick_ ? config.fRightStickHeadScale : config.fLeftStickHeadScale;
500
if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2 && stickDownImg_ != stickImageIndex_)
501
dc.Draw()->DrawImage(stickDownImg_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, downBg, ALIGN_CENTER);
502
dc.Draw()->DrawImage(stickImageIndex_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, colorBg, ALIGN_CENTER);
503
}
504
505
bool PSPStick::Touch(const TouchInput &input) {
506
bool retval = GamepadComponent::Touch(input);
507
if (input.flags & TouchInputFlags::RELEASE_ALL) {
508
dragPointerId_ = -1;
509
centerX_ = bounds_.centerX();
510
centerY_ = bounds_.centerY();
511
__CtrlSetAnalogXY(stick_, 0.0f, 0.0f);
512
usedPointerMask = 0;
513
analogPointerMask = 0;
514
return retval;
515
}
516
if (input.flags & TouchInputFlags::DOWN) {
517
const TouchControlConfig &config = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation());
518
float fac = 0.5f * (stick_ ? config.fRightStickHeadScale : config.fLeftStickHeadScale)-0.5f;
519
if (dragPointerId_ == -1 && bounds_.Expand(bounds_.w*fac, bounds_.h*fac).Contains(input.x, input.y)) {
520
if (g_Config.bAutoCenterTouchAnalog) {
521
centerX_ = input.x;
522
centerY_ = input.y;
523
} else {
524
centerX_ = bounds_.centerX();
525
centerY_ = bounds_.centerY();
526
}
527
dragPointerId_ = input.id;
528
usedPointerMask |= 1 << input.id;
529
analogPointerMask |= 1 << input.id;
530
ProcessTouch(input.x, input.y, true);
531
retval = true;
532
}
533
}
534
if (input.flags & TouchInputFlags::MOVE) {
535
if (input.id == dragPointerId_) {
536
ProcessTouch(input.x, input.y, true);
537
retval = true;
538
}
539
}
540
if (input.flags & TouchInputFlags::UP) {
541
if (input.id == dragPointerId_) {
542
dragPointerId_ = -1;
543
centerX_ = bounds_.centerX();
544
centerY_ = bounds_.centerY();
545
usedPointerMask &= ~(1 << input.id);
546
analogPointerMask &= ~(1 << input.id);
547
ProcessTouch(input.x, input.y, false);
548
retval = true;
549
}
550
}
551
return retval;
552
}
553
554
void PSPStick::ProcessTouch(float x, float y, bool down) {
555
if (down && centerX_ >= 0.0f) {
556
float inv_stick_size = 1.0f / (stick_size_ * scale_);
557
558
float dx = (x - centerX_) * inv_stick_size;
559
float dy = (y - centerY_) * inv_stick_size;
560
// Do not clamp to a circle! The PSP has nearly square range!
561
562
// Old code to clamp to a circle
563
// float len = sqrtf(dx * dx + dy * dy);
564
// if (len > 1.0f) {
565
// dx /= len;
566
// dy /= len;
567
//}
568
569
// Still need to clamp to a square
570
dx = std::min(1.0f, std::max(-1.0f, dx));
571
dy = std::min(1.0f, std::max(-1.0f, dy));
572
573
__CtrlSetAnalogXY(stick_, dx, -dy);
574
} else {
575
__CtrlSetAnalogXY(stick_, 0.0f, 0.0f);
576
}
577
}
578
579
PSPCustomStick::PSPCustomStick(ImageID bgImg, const char *key, ImageID stickImg, ImageID stickDownImg, int stick, float scale, UI::LayoutParams *layoutParams)
580
: PSPStick(bgImg, key, stickImg, stickDownImg, stick, scale, layoutParams) {
581
}
582
583
void PSPCustomStick::Draw(UIContext &dc) {
584
float opacity = g_gamepadOpacity;
585
if (opacity <= 0.0f)
586
return;
587
588
if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2) {
589
opacity *= 1.35f;
590
}
591
592
uint32_t colorBg = colorAlpha(GetButtonColor(), opacity);
593
uint32_t downBg = colorAlpha(0x00FFFFFF, opacity * 0.5f);
594
595
if (centerX_ < 0.0f) {
596
centerX_ = bounds_.centerX();
597
centerY_ = bounds_.centerY();
598
}
599
600
float stickX = centerX_;
601
float stickY = centerY_;
602
603
float dx, dy;
604
dx = posX_;
605
dy = -posY_;
606
607
const TouchControlConfig &config = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation());
608
const float headScale = config.fRightStickHeadScale;
609
if (!config.bHideStickBackground)
610
dc.Draw()->DrawImage(bgImg_, stickX, stickY, 1.0f * scale_, colorBg, ALIGN_CENTER);
611
if (dragPointerId_ != -1 && g_Config.iTouchButtonStyle == 2 && stickDownImg_ != stickImageIndex_)
612
dc.Draw()->DrawImage(stickDownImg_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, downBg, ALIGN_CENTER);
613
dc.Draw()->DrawImage(stickImageIndex_, stickX + dx * stick_size_ * scale_, stickY - dy * stick_size_ * scale_, 1.0f * scale_ * headScale, colorBg, ALIGN_CENTER);
614
}
615
616
bool PSPCustomStick::Touch(const TouchInput &input) {
617
bool retval = GamepadComponent::Touch(input);
618
if (input.flags & TouchInputFlags::RELEASE_ALL) {
619
dragPointerId_ = -1;
620
centerX_ = bounds_.centerX();
621
centerY_ = bounds_.centerY();
622
posX_ = 0.0f;
623
posY_ = 0.0f;
624
usedPointerMask = 0;
625
analogPointerMask = 0;
626
return false;
627
}
628
if (input.flags & TouchInputFlags::DOWN) {
629
const TouchControlConfig &config = g_Config.GetTouchControlsConfig(g_display.GetDeviceOrientation());
630
float fac = 0.5f * config.fRightStickHeadScale - 0.5f;
631
if (dragPointerId_ == -1 && bounds_.Expand(bounds_.w*fac, bounds_.h*fac).Contains(input.x, input.y)) {
632
if (g_Config.bAutoCenterTouchAnalog) {
633
centerX_ = input.x;
634
centerY_ = input.y;
635
} else {
636
centerX_ = bounds_.centerX();
637
centerY_ = bounds_.centerY();
638
}
639
dragPointerId_ = input.id;
640
usedPointerMask |= 1 << input.id;
641
analogPointerMask |= 1 << input.id;
642
ProcessTouch(input.x, input.y, true);
643
retval = true;
644
}
645
}
646
if (input.flags & TouchInputFlags::MOVE) {
647
if (input.id == dragPointerId_) {
648
ProcessTouch(input.x, input.y, true);
649
retval = true;
650
}
651
}
652
if (input.flags & TouchInputFlags::UP) {
653
if (input.id == dragPointerId_) {
654
dragPointerId_ = -1;
655
centerX_ = bounds_.centerX();
656
centerY_ = bounds_.centerY();
657
usedPointerMask &= ~(1 << input.id);
658
analogPointerMask &= ~(1 << input.id);
659
ProcessTouch(input.x, input.y, false);
660
retval = true;
661
}
662
}
663
return retval;
664
}
665
666
void PSPCustomStick::ProcessTouch(float x, float y, bool down) {
667
static const int buttons[] = {0, CTRL_LTRIGGER, CTRL_RTRIGGER, CTRL_SQUARE, CTRL_TRIANGLE, CTRL_CIRCLE, CTRL_CROSS, CTRL_UP, CTRL_DOWN, CTRL_LEFT, CTRL_RIGHT, CTRL_START, CTRL_SELECT};
668
static const int analogs[] = {VIRTKEY_AXIS_RIGHT_Y_MAX, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, VIRTKEY_AXIS_Y_MAX, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX};
669
u32 press = 0;
670
u32 release = 0;
671
672
auto toggle = [&](int config, bool simpleCheck, bool diagCheck = true) {
673
if (config <= 0 || (size_t)config >= ARRAY_SIZE(buttons))
674
return;
675
676
if (simpleCheck && (!g_Config.bRightAnalogDisableDiagonal || diagCheck))
677
press |= buttons[config];
678
else
679
release |= buttons[config];
680
};
681
682
auto analog = [&](float dx, float dy) {
683
if (g_Config.bRightAnalogDisableDiagonal) {
684
if (fabs(dx) > fabs(dy)) {
685
dy = 0.0f;
686
} else {
687
dx = 0.0f;
688
}
689
}
690
691
auto assign = [&](int config, float value) {
692
if (config < ARRAY_SIZE(buttons) || config >= ARRAY_SIZE(buttons) + ARRAY_SIZE(analogs)) {
693
return;
694
}
695
switch(analogs[config - ARRAY_SIZE(buttons)]) {
696
case VIRTKEY_AXIS_Y_MAX:
697
__CtrlSetAnalogY(0, value);
698
break;
699
case VIRTKEY_AXIS_Y_MIN:
700
__CtrlSetAnalogY(0, value * -1.0f);
701
break;
702
case VIRTKEY_AXIS_X_MIN:
703
__CtrlSetAnalogX(0, value * -1.0f);
704
break;
705
case VIRTKEY_AXIS_X_MAX:
706
__CtrlSetAnalogX(0, value);
707
break;
708
case VIRTKEY_AXIS_RIGHT_Y_MAX:
709
__CtrlSetAnalogY(1, value);
710
break;
711
case VIRTKEY_AXIS_RIGHT_Y_MIN:
712
__CtrlSetAnalogY(1, value * -1.0f);
713
break;
714
case VIRTKEY_AXIS_RIGHT_X_MIN:
715
__CtrlSetAnalogX(1, value * -1.0f);
716
break;
717
case VIRTKEY_AXIS_RIGHT_X_MAX:
718
__CtrlSetAnalogX(1, value);
719
break;
720
}
721
};
722
723
// if/when we ever get iLeftAnalog settings, check stick_ for the config to use
724
// let 0.0f through during centering
725
if (dy >= 0.0f) {
726
// down
727
assign(g_Config.iRightAnalogUp, 0.0f);
728
assign(g_Config.iRightAnalogDown, dy);
729
}
730
if (dy <= 0.0f) {
731
// up
732
assign(g_Config.iRightAnalogDown, 0.0f);
733
assign(g_Config.iRightAnalogUp, dy * -1.0f);
734
}
735
if (dx <= 0.0f) {
736
// left
737
assign(g_Config.iRightAnalogRight, 0.0f);
738
assign(g_Config.iRightAnalogLeft, dx * -1.0f);
739
}
740
if (dx >= 0.0f) {
741
// right
742
assign(g_Config.iRightAnalogLeft, 0.0f);
743
assign(g_Config.iRightAnalogRight, dx);
744
}
745
};
746
747
if (down && centerX_ >= 0.0f) {
748
float inv_stick_size = 1.0f / (stick_size_ * scale_);
749
750
float dx = (x - centerX_) * inv_stick_size;
751
float dy = (y - centerY_) * inv_stick_size;
752
753
dx = std::min(1.0f, std::max(-1.0f, dx));
754
dy = std::min(1.0f, std::max(-1.0f, dy));
755
756
toggle(g_Config.iRightAnalogRight, dx > 0.5f, fabs(dx) > fabs(dy));
757
toggle(g_Config.iRightAnalogLeft, dx < -0.5f, fabs(dx) > fabs(dy));
758
toggle(g_Config.iRightAnalogUp, dy < -0.5f, fabs(dx) <= fabs(dy));
759
toggle(g_Config.iRightAnalogDown, dy > 0.5f, fabs(dx) <= fabs(dy));
760
toggle(g_Config.iRightAnalogPress, true);
761
762
analog(dx, dy);
763
764
posX_ = dx;
765
posY_ = dy;
766
767
} else {
768
toggle(g_Config.iRightAnalogRight, false);
769
toggle(g_Config.iRightAnalogLeft, false);
770
toggle(g_Config.iRightAnalogUp, false);
771
toggle(g_Config.iRightAnalogDown, false);
772
toggle(g_Config.iRightAnalogPress, false);
773
774
analog(0.0f, 0.0f);
775
776
posX_ = 0.0f;
777
posY_ = 0.0f;
778
}
779
780
if (release || press) {
781
__CtrlUpdateButtons(press, release);
782
}
783
}
784
785
void InitPadLayout(TouchControlConfig *config, DeviceOrientation orientation, float xres, float yres, float globalScale) {
786
const float scale = globalScale;
787
const int halfW = xres / 2;
788
789
const float screenBottom = orientation == DeviceOrientation::Portrait ? (yres - yres * 0.13f) : yres;
790
791
auto initTouchPos = [=](ConfigTouchPos *touch, float x, float y, float extraScale = 1.0f) {
792
if (touch->x == -1.0f || touch->y == -1.0f) {
793
touch->x = x / xres;
794
touch->y = std::max(y, 20.0f * globalScale) / yres;
795
touch->scale = scale * extraScale;
796
}
797
};
798
799
// Pause button. Has some special handling, it MUST be visible on some platforms.
800
float Pause_button_center_X = halfW;
801
float Pause_button_center_Y = 28.0f;
802
803
if (!System_GetPropertyBool(SYSPROP_HAS_BACK_BUTTON)) {
804
// Make really sure the pause button will be visible. Setting to -1 ensures reinit.
805
if (config->touchPauseKey.x < 0.0f || config->touchPauseKey.x > 1.0f) {
806
config->touchPauseKey.x = -1;
807
}
808
if (config->touchPauseKey.y < 0.0f || config->touchPauseKey.y > 1.0f) {
809
config->touchPauseKey.y = -1;
810
}
811
config->touchPauseKey.show = true;
812
}
813
814
initTouchPos(&config->touchPauseKey, Pause_button_center_X, Pause_button_center_Y, 0.8f);
815
816
// PSP buttons (triangle, circle, square, cross)---------------------
817
// space between the PSP buttons (triangle, circle, square and cross)
818
if (config->fActionButtonSpacing < 0) {
819
config->fActionButtonSpacing = 1.0f;
820
}
821
822
// Position of the circle button (the PSP circle button). It is the farthest to the left
823
float Action_button_spacing = config->fActionButtonSpacing * baseActionButtonSpacing;
824
int Action_button_center_X = xres - Action_button_spacing * 2;
825
int Action_button_center_Y = screenBottom - Action_button_spacing * 2;
826
if (config->touchRightAnalogStick.show) {
827
Action_button_center_Y -= 150 * scale;
828
}
829
initTouchPos(&config->touchActionButtonCenter, Action_button_center_X, Action_button_center_Y);
830
831
//D-PAD (up down left right) (aka PSP cross)----------------------------
832
//radius to the D-pad
833
// TODO: Make configurable
834
835
int D_pad_X = 2.5 * D_pad_Radius * scale;
836
int D_pad_Y = screenBottom - D_pad_Radius * scale;
837
if (config->touchAnalogStick.show) {
838
D_pad_Y -= 200 * scale;
839
}
840
initTouchPos(&config->touchDpad, D_pad_X, D_pad_Y);
841
842
//analog stick-------------------------------------------------------
843
//keep the analog stick right below the D pad
844
int analog_stick_X = D_pad_X;
845
int analog_stick_Y = screenBottom - 80 * scale;
846
initTouchPos(&config->touchAnalogStick, analog_stick_X, analog_stick_Y);
847
848
//right analog stick-------------------------------------------------
849
//keep the right analog stick right below the face buttons
850
int right_analog_stick_X = Action_button_center_X;
851
int right_analog_stick_Y = screenBottom - 80 * scale;
852
initTouchPos(&config->touchRightAnalogStick, right_analog_stick_X, right_analog_stick_Y);
853
854
//select, start, throttle--------------------------------------------
855
//space between the bottom keys (space between select, start and un-throttle)
856
float bottom_key_spacing = 100;
857
if (g_display.dp_xres < 750) {
858
bottom_key_spacing *= 0.8f;
859
}
860
861
// On IOS, nudge the bottom button up a little to avoid the task switcher.
862
// Additionally, in portrait mode on any platform, move the defaults up by quite a bit - much
863
// more comfortable to reach.
864
#if PPSSPP_PLATFORM(IOS)
865
constexpr float bottom_button_Y = 80.0f;
866
#else
867
constexpr float bottom_button_Y = 60.0f;
868
#endif
869
if (orientation == DeviceOrientation::Portrait) {
870
int start_key_X = halfW;
871
int start_key_Y = screenBottom - bottom_button_Y * scale;
872
initTouchPos(&config->touchStartKey, start_key_X, start_key_Y);
873
874
int select_key_X = halfW;
875
int select_key_Y = screenBottom - bottom_button_Y * scale - 50;
876
initTouchPos(&config->touchSelectKey, select_key_X, select_key_Y);
877
878
int fast_forward_key_X = halfW;
879
int fast_forward_key_Y = screenBottom - bottom_button_Y * scale - 100;
880
initTouchPos(&config->touchFastForwardKey, fast_forward_key_X, fast_forward_key_Y);
881
} else {
882
int start_key_X = halfW + bottom_key_spacing * scale;
883
int start_key_Y = screenBottom - bottom_button_Y * scale;
884
initTouchPos(&config->touchStartKey, start_key_X, start_key_Y);
885
886
int select_key_X = halfW;
887
int select_key_Y = screenBottom - bottom_button_Y * scale;
888
initTouchPos(&config->touchSelectKey, select_key_X, select_key_Y);
889
890
int fast_forward_key_X = halfW - bottom_key_spacing * scale;
891
int fast_forward_key_Y = screenBottom - bottom_button_Y * scale;
892
initTouchPos(&config->touchFastForwardKey, fast_forward_key_X, fast_forward_key_Y);
893
}
894
895
// L and R------------------------------------------------------------
896
// Put them above the analog stick / above the buttons to the right.
897
// The corners were very hard to reach..
898
899
int l_key_X = 60 * scale;
900
int l_key_Y = screenBottom - 380 * scale;
901
initTouchPos(&config->touchLKey, l_key_X, l_key_Y);
902
903
int r_key_X = xres - 60 * scale;
904
int r_key_Y = l_key_Y;
905
initTouchPos(&config->touchRKey, r_key_X, r_key_Y);
906
907
struct { float x; float y; } customButtonPositions[10] = {
908
{ 1.2f, 0.5f },
909
{ 2.2f, 0.5f },
910
{ 3.2f, 0.5f },
911
{ 1.2f, 0.333f },
912
{ 2.2f, 0.333f },
913
{ -1.2f, 0.5f },
914
{ -2.2f, 0.5f },
915
{ -3.2f, 0.5f },
916
{ -1.2f, 0.333f },
917
{ -2.2f, 0.333f },
918
};
919
920
for (int i = 0; i < TouchControlConfig::CUSTOM_BUTTON_COUNT; i++) {
921
const float y_offset = (float)(i / 10) * 0.08333f;
922
923
int combo_key_X = halfW + bottom_key_spacing * scale * customButtonPositions[i % 10].x;
924
int combo_key_Y = screenBottom * (y_offset + customButtonPositions[i % 10].y);
925
926
initTouchPos(&config->touchCustom[i], combo_key_X, combo_key_Y);
927
}
928
}
929
930
UI::ViewGroup *CreatePadLayout(const TouchControlConfig &config, float xres, float yres, bool *pause, ControlMapper *controlMapper) {
931
using namespace UI;
932
return new GamepadEmuView(config, xres, yres, pause, controlMapper, new UI::LayoutParams(UI::FILL_PARENT, UI::FILL_PARENT));
933
}
934
935
GamepadEmuView::GamepadEmuView(const TouchControlConfig &config, float xres, float yres, bool *pause, ControlMapper *controlMapper, UI::LayoutParams *layoutParams)
936
: UI::AnchorLayout(layoutParams)
937
{
938
if (!g_Config.bShowTouchControls) {
939
return;
940
}
941
942
using namespace UI;
943
944
struct ButtonOffset {
945
float x;
946
float y;
947
};
948
auto buttonLayoutParams = [=](const ConfigTouchPos &touch, ButtonOffset off = { 0, 0 }) {
949
return new AnchorLayoutParams(touch.x * xres + off.x, touch.y * yres + off.y, NONE, NONE, Centering::Both);
950
};
951
952
// Space between the PSP buttons (triangle, circle, square and cross)
953
const float actionButtonSpacing = config.fActionButtonSpacing * baseActionButtonSpacing;
954
// Position of the circle button (the PSP circle button). It is the farthest to the right.
955
ButtonOffset circleOffset{ actionButtonSpacing, 0.0f };
956
ButtonOffset crossOffset{ 0.0f, actionButtonSpacing };
957
ButtonOffset triangleOffset{ 0.0f, -actionButtonSpacing };
958
ButtonOffset squareOffset{ -actionButtonSpacing, 0.0f };
959
960
const int halfW = xres / 2;
961
962
const ImageID roundImage = g_Config.iTouchButtonStyle ? ImageID("I_ROUND_LINE") : ImageID("I_ROUND");
963
const ImageID rectImage = g_Config.iTouchButtonStyle ? ImageID("I_RECT_LINE") : ImageID("I_RECT");
964
const ImageID shoulderImage = g_Config.iTouchButtonStyle ? ImageID("I_SHOULDER_LINE") : ImageID("I_SHOULDER");
965
const ImageID stickImage = g_Config.iTouchButtonStyle ? ImageID("I_STICK_LINE") : ImageID("I_STICK");
966
const ImageID stickBg = g_Config.iTouchButtonStyle ? ImageID("I_STICK_BG_LINE") : ImageID("I_STICK_BG");
967
968
auto addPSPButton = [this, buttonLayoutParams](int buttonBit, const char *key, ImageID bgImg, ImageID bgDownImg, ImageID img, const ConfigTouchPos &touch, ButtonOffset off = { 0, 0 }) -> PSPButton * {
969
if (touch.show) {
970
return Add(new PSPButton(buttonBit, key, bgImg, bgDownImg, img, touch.scale, buttonLayoutParams(touch, off)));
971
}
972
return nullptr;
973
};
974
auto addBoolButton = [this, buttonLayoutParams](bool *value, const char *key, ImageID bgImg, ImageID bgDownImg, ImageID img, const ConfigTouchPos &touch) -> BoolButton * {
975
if (touch.show) {
976
return Add(new BoolButton(value, key, bgImg, bgDownImg, img, touch.scale, buttonLayoutParams(touch)));
977
}
978
return nullptr;
979
};
980
auto addCustomButton = [this, buttonLayoutParams, controlMapper](const ConfigCustomButton& cfg, const char *key, const ConfigTouchPos &touch) -> CustomButton * {
981
using namespace CustomKeyData;
982
if (touch.show) {
983
_dbg_assert_(cfg.shape < ARRAY_SIZE(customKeyShapes));
984
_dbg_assert_(cfg.image < ARRAY_SIZE(customKeyImages));
985
986
// Note: cfg.shape and cfg.image are bounds-checked elsewhere.
987
auto aux = Add(new CustomButton(cfg.key, key, cfg.toggle, cfg.repeat, controlMapper,
988
g_Config.iTouchButtonStyle == 0 ? customKeyShapes[cfg.shape].i : customKeyShapes[cfg.shape].l, customKeyShapes[cfg.shape].i,
989
customKeyImages[cfg.image].i, touch.scale, customKeyShapes[cfg.shape].d, buttonLayoutParams(touch)));
990
aux->SetAngle(customKeyImages[cfg.image].r, customKeyShapes[cfg.shape].r);
991
aux->FlipImageH(customKeyShapes[cfg.shape].f);
992
return aux;
993
}
994
return nullptr;
995
};
996
997
if (config.touchPauseKey.show) {
998
auto button = addBoolButton(pause, "Pause button", roundImage, ImageID("I_ROUND"), ImageID("I_HAMBURGER"), config.touchPauseKey);
999
if (button) {
1000
// The user is not allowed to hide this completely on some platforms - it must be findable.
1001
button->SetMinimumAlpha(0.1f);
1002
}
1003
}
1004
1005
// touchActionButtonCenter.show will always be true, since that's the default.
1006
if (config.bShowTouchCircle)
1007
addPSPButton(CTRL_CIRCLE, "Circle button", roundImage, ImageID("I_ROUND"), ImageID("I_CIRCLE"), config.touchActionButtonCenter, circleOffset);
1008
if (config.bShowTouchCross)
1009
addPSPButton(CTRL_CROSS, "Cross button", roundImage, ImageID("I_ROUND"), ImageID("I_CROSS"), config.touchActionButtonCenter, crossOffset);
1010
if (config.bShowTouchTriangle)
1011
addPSPButton(CTRL_TRIANGLE, "Triangle button", roundImage, ImageID("I_ROUND"), ImageID("I_TRIANGLE"), config.touchActionButtonCenter, triangleOffset);
1012
if (config.bShowTouchSquare)
1013
addPSPButton(CTRL_SQUARE, "Square button", roundImage, ImageID("I_ROUND"), ImageID("I_SQUARE"), config.touchActionButtonCenter, squareOffset);
1014
1015
addPSPButton(CTRL_START, "Start button", rectImage, ImageID("I_RECT"), ImageID("I_START"), config.touchStartKey);
1016
addPSPButton(CTRL_SELECT, "Select button", rectImage, ImageID("I_RECT"), ImageID("I_SELECT"), config.touchSelectKey);
1017
1018
BoolButton *fastForward = addBoolButton(&PSP_CoreParameter().fastForward, "Fast-forward button", rectImage, ImageID("I_RECT"), ImageID("I_FAST_FORWARD_LINE"), config.touchFastForwardKey);
1019
if (fastForward) {
1020
fastForward->OnChange.Add([](UI::EventParams &e) {
1021
if (e.a && coreState == CORE_STEPPING_CPU) {
1022
Core_Resume();
1023
}
1024
});
1025
}
1026
1027
addPSPButton(CTRL_LTRIGGER, "Left shoulder button", shoulderImage, ImageID("I_SHOULDER"), ImageID("I_L"), config.touchLKey);
1028
PSPButton *rTrigger = addPSPButton(CTRL_RTRIGGER, "Right shoulder button", shoulderImage, ImageID("I_SHOULDER"), ImageID("I_R"), config.touchRKey);
1029
if (rTrigger)
1030
rTrigger->FlipImageH(true);
1031
1032
if (config.touchDpad.show) {
1033
const ImageID dirImage = g_Config.iTouchButtonStyle ? ImageID("I_DIR_LINE") : ImageID("I_DIR");
1034
Add(new PSPDpad(dirImage, "D-pad", ImageID("I_DIR"), ImageID("I_ARROW"), config.touchDpad.scale, config.fDpadSpacing, buttonLayoutParams(config.touchDpad)));
1035
}
1036
1037
if (config.touchAnalogStick.show)
1038
Add(new PSPStick(stickBg, "Left analog stick", stickImage, ImageID("I_STICK"), 0, config.touchAnalogStick.scale, buttonLayoutParams(config.touchAnalogStick)));
1039
1040
if (config.touchRightAnalogStick.show) {
1041
if (g_Config.bRightAnalogCustom)
1042
Add(new PSPCustomStick(stickBg, "Right analog stick", stickImage, ImageID("I_STICK"), 1, config.touchRightAnalogStick.scale, buttonLayoutParams(config.touchRightAnalogStick)));
1043
else
1044
Add(new PSPStick(stickBg, "Right analog stick", stickImage, ImageID("I_STICK"), 1, config.touchRightAnalogStick.scale, buttonLayoutParams(config.touchRightAnalogStick)));
1045
}
1046
1047
// Sanitize custom button images, while adding them.
1048
for (int i = 0; i < TouchControlConfig::CUSTOM_BUTTON_COUNT; i++) {
1049
if (g_Config.CustomButton[i].shape >= ARRAY_SIZE(CustomKeyData::customKeyShapes)) {
1050
g_Config.CustomButton[i].shape = 0;
1051
}
1052
if (g_Config.CustomButton[i].image >= ARRAY_SIZE(CustomKeyData::customKeyImages)) {
1053
g_Config.CustomButton[i].image = 0;
1054
}
1055
1056
char temp[64];
1057
snprintf(temp, sizeof(temp), "Custom %d button", i + 1);
1058
addCustomButton(g_Config.CustomButton[i], temp, config.touchCustom[i]);
1059
}
1060
1061
// Add the two gesture zones.
1062
for (int i = 0; i < 2; i++) {
1063
if (g_Config.gestureControls[i].bGestureControlEnabled) {
1064
// We have them both cover the whole surface, then limit in the touch handler.
1065
// This is because there's no easy way to do "half the screen" in AnchorLayout.
1066
// We can do more complex layout combinations, but meh.
1067
Add(new GestureGamepad(controlMapper, i, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, 0.0f, 0.0f, 0.0f, 0.0f)));
1068
}
1069
}
1070
}
1071
1072
void GamepadEmuView::Update() {
1073
AnchorLayout::Update();
1074
GamepadUpdateOpacity();
1075
1076
bool anyDown = false;
1077
for (auto view : views_) {
1078
GamepadComponent *component = dynamic_cast<GamepadComponent *>(view);
1079
if (component && component->IsDownForFadeoutCheck()) {
1080
// INFO_LOG(Log::System, "GamepadEmuView::Update: component is down for fadeout check: %s", component->DescribeText().c_str());
1081
anyDown = true;
1082
}
1083
}
1084
1085
if (anyDown) {
1086
// INFO_LOG(Log::System, "last touch in update");
1087
g_lastTouch = time_now_d();
1088
}
1089
}
1090
1091
const GestureControlConfig &GestureGamepad::GetZone() {
1092
// TODO: Add support for multiple zones.
1093
return g_Config.gestureControls[zoneIndex_];
1094
}
1095
1096
GestureGamepad::~GestureGamepad() {
1097
if (dragPointerId_ != -1) {
1098
g_activeGesturePointers.erase(dragPointerId_);
1099
}
1100
}
1101
1102
bool GestureGamepad::Touch(const TouchInput &input) {
1103
const GestureControlConfig &zone = GetZone();
1104
1105
if (usedPointerMask & (1 << input.id)) {
1106
if (input.id == dragPointerId_)
1107
dragPointerId_ = -1;
1108
return false;
1109
}
1110
1111
if (input.flags & TouchInputFlags::RELEASE_ALL) {
1112
dragPointerId_ = -1;
1113
g_activeGesturePointers.clear();
1114
return false;
1115
}
1116
1117
if (input.flags & TouchInputFlags::DOWN) {
1118
// Handle the zones here, easier than with AnchorLayout.
1119
const float minX = zoneIndex_ == 0 ? 0.0f : bounds_.w * 0.5f;
1120
const float maxX = zoneIndex_ == 0 ? bounds_.w * 0.5f : bounds_.w;
1121
1122
if (dragPointerId_ == -1 && input.x >= minX && input.x < maxX) {
1123
dragPointerId_ = input.id;
1124
lastX_ = input.x;
1125
lastY_ = input.y;
1126
downX_ = input.x;
1127
downY_ = input.y;
1128
1129
const float now = time_now_d();
1130
if (now - lastTapRelease_ < 0.3f && !haveDoubleTapped_) {
1131
if (zone.iDoubleTapGesture != 0 )
1132
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[zone.iDoubleTapGesture - 1], KeyInputFlags::DOWN);
1133
haveDoubleTapped_ = true;
1134
}
1135
1136
lastTouchDown_ = now;
1137
}
1138
}
1139
if (input.flags & TouchInputFlags::MOVE) {
1140
if (input.id == dragPointerId_) {
1141
deltaX_ += input.x - lastX_;
1142
deltaY_ += input.y - lastY_;
1143
lastX_ = input.x;
1144
lastY_ = input.y;
1145
1146
const float distance = sqrtf((input.x - downX_) * (input.x - downX_) + (input.y - downY_) * (input.y - downY_));
1147
// TODO: Configurable distance?
1148
if (distance > 50.0f * g_display.dpi_scale_x) {
1149
// The user has dragged some distance from the initial touch. Start ignoring button presses.
1150
g_activeGesturePointers.insert(input.id);
1151
} else {
1152
g_activeGesturePointers.erase(input.id);
1153
}
1154
1155
if (zone.bAnalogGesture) {
1156
const float k = zone.fAnalogGestureSensitivity * 0.02;
1157
float dx = (input.x - downX_) * g_display.dpi_scale_x * k;
1158
float dy = (input.y - downY_) * g_display.dpi_scale_y * k;
1159
dx = std::clamp(dx, -1.0f, 1.0f);
1160
dy = std::clamp(dy, -1.0f, 1.0f);
1161
__CtrlSetAnalogXY(0, dx, -dy);
1162
}
1163
}
1164
}
1165
if (input.flags & TouchInputFlags::UP) {
1166
g_activeGesturePointers.erase(input.id);
1167
if (input.id == dragPointerId_) {
1168
dragPointerId_ = -1;
1169
if (time_now_d() - lastTouchDown_ < 0.3f)
1170
lastTapRelease_ = time_now_d();
1171
1172
if (haveDoubleTapped_) {
1173
if (zone.iDoubleTapGesture != 0)
1174
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[zone.iDoubleTapGesture - 1], KeyInputFlags::UP);
1175
haveDoubleTapped_ = false;
1176
}
1177
1178
if (zone.bAnalogGesture)
1179
__CtrlSetAnalogXY(0, 0, 0);
1180
}
1181
}
1182
return true;
1183
}
1184
1185
void GestureGamepad::Draw(UIContext &dc) {
1186
float opacity = g_Config.iTouchButtonOpacity / 100.0;
1187
if (opacity <= 0.0f)
1188
return;
1189
1190
uint32_t colorBg = colorAlpha(GetButtonColor(), opacity);
1191
1192
if (GetZone().bAnalogGesture && dragPointerId_ != -1) {
1193
dc.Draw()->DrawImage(ImageID("I_CIRCLE"), downX_, downY_, 0.7f, colorBg, ALIGN_CENTER);
1194
}
1195
}
1196
1197
void GestureGamepad::Update() {
1198
const float th = 1.0f;
1199
float dx = deltaX_ * g_display.dpi_scale_x * GetZone().fSwipeSensitivity;
1200
float dy = deltaY_ * g_display.dpi_scale_y * GetZone().fSwipeSensitivity;
1201
if (GetZone().iSwipeRight != 0) {
1202
if (dx > th) {
1203
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeRight - 1], KeyInputFlags::DOWN);
1204
swipeRightReleased_ = false;
1205
} else if (!swipeRightReleased_) {
1206
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeRight -1], KeyInputFlags::UP);
1207
swipeRightReleased_ = true;
1208
}
1209
}
1210
if (GetZone().iSwipeLeft != 0) {
1211
if (dx < -th) {
1212
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeLeft - 1], KeyInputFlags::DOWN);
1213
swipeLeftReleased_ = false;
1214
} else if (!swipeLeftReleased_) {
1215
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeLeft - 1], KeyInputFlags::UP);
1216
swipeLeftReleased_ = true;
1217
}
1218
}
1219
if (GetZone().iSwipeUp != 0) {
1220
if (dy < -th) {
1221
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeUp - 1], KeyInputFlags::DOWN);
1222
swipeUpReleased_ = false;
1223
} else if (!swipeUpReleased_) {
1224
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeUp - 1], KeyInputFlags::UP);
1225
swipeUpReleased_ = true;
1226
}
1227
}
1228
if (GetZone().iSwipeDown != 0) {
1229
if (dy > th) {
1230
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeDown - 1], KeyInputFlags::DOWN);
1231
swipeDownReleased_ = false;
1232
} else if (!swipeDownReleased_) {
1233
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeDown - 1], KeyInputFlags::UP);
1234
swipeDownReleased_ = true;
1235
}
1236
}
1237
const float smoothing = GetZone().fSwipeSmoothing;
1238
deltaX_ *= smoothing;
1239
deltaY_ *= smoothing;
1240
}
1241
1242