1 // Copyright (c) 2013- PPSSPP Project.
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.
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.
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
20 #include "base/colorutil.h"
21 #include "gfx_es2/draw_buffer.h"
22 #include "i18n/i18n.h"
23 #include "ui/ui_context.h"
26 #include "TouchControlLayoutScreen.h"
27 #include "TouchControlVisibilityScreen.h"
28 #include "Core/Config.h"
29 #include "Core/System.h"
30 #include "GamepadEmu.h"
32 static const int leftColumnWidth
= 140;
34 // Ugly hackery, need to rework some stuff to get around this
35 static float local_dp_xres
;
36 static float local_dp_yres
;
38 static u32
GetButtonColor() {
39 return g_Config
.iTouchButtonStyle
== 1 ? 0xFFFFFF : 0xc0b080;
42 class DragDropButton
: public MultiTouchButton
{
44 DragDropButton(float &x
, float &y
, int bgImg
, int img
, float &scale
)
45 : MultiTouchButton(bgImg
, img
, scale
, new UI::AnchorLayoutParams(fromFullscreenCoord(x
), y
*local_dp_yres
, UI::NONE
, UI::NONE
, true)),
46 x_(x
), y_(y
), theScale_(scale
) {
50 virtual bool IsDown() {
51 // Don't want the button to enlarge and throw the user's perspective
52 // of button size off whack.
56 virtual void SavePosition() {
57 x_
= toFullscreenCoord(bounds_
.centerX());
58 y_
= bounds_
.centerY() / local_dp_yres
;
62 virtual float GetScale() const { return theScale_
; }
63 virtual void SetScale(float s
) { theScale_
= s
; scale_
= s
; }
65 virtual float GetSpacing() const { return 1.0f
; }
66 virtual void SetSpacing(float s
) { }
69 // convert from screen coordinates (leftColumnWidth to dp_xres) to actual fullscreen coordinates (0 to 1.0)
70 inline float toFullscreenCoord(int screenx
) {
71 return (float)(screenx
- leftColumnWidth
) / (local_dp_xres
- leftColumnWidth
);
74 // convert from external fullscreen coordinates(0 to 1.0) to the current partial coordinates (leftColumnWidth to dp_xres)
75 inline int fromFullscreenCoord(float controllerX
) {
76 return leftColumnWidth
+ (local_dp_xres
- leftColumnWidth
) * controllerX
;
83 class PSPActionButtons
: public DragDropButton
{
85 PSPActionButtons(float &x
, float &y
, float &scale
, float &spacing
)
86 : DragDropButton(x
, y
, -1, -1, scale
), spacing_(spacing
) {
88 roundId_
= g_Config
.iTouchButtonStyle
? I_ROUND_LINE
: I_ROUND
;
92 triangleId_
= I_TRIANGLE
;
95 circleVisible_
= triangleVisible_
= squareVisible_
= crossVisible_
= true;
98 void setCircleVisibility(bool visible
){
99 circleVisible_
= visible
;
102 void setCrossVisibility(bool visible
){
103 crossVisible_
= visible
;
106 void setTriangleVisibility(bool visible
){
107 triangleVisible_
= visible
;
110 void setSquareVisibility(bool visible
){
111 squareVisible_
= visible
;
114 void Draw(UIContext
&dc
) {
115 float opacity
= g_Config
.iTouchButtonOpacity
/ 100.0f
;
117 uint32_t colorBg
= colorAlpha(GetButtonColor(), opacity
);
118 uint32_t color
= colorAlpha(0xFFFFFF, opacity
);
120 int centerX
= bounds_
.centerX();
121 int centerY
= bounds_
.centerY();
123 float spacing
= spacing_
* baseActionButtonSpacing
;
124 if (circleVisible_
) {
125 dc
.Draw()->DrawImageRotated(roundId_
, centerX
+ spacing
, centerY
, scale_
, 0, colorBg
, false);
126 dc
.Draw()->DrawImageRotated(circleId_
, centerX
+ spacing
, centerY
, scale_
, 0, color
, false);
130 dc
.Draw()->DrawImageRotated(roundId_
, centerX
, centerY
+ spacing
, scale_
, 0, colorBg
, false);
131 dc
.Draw()->DrawImageRotated(crossId_
, centerX
, centerY
+ spacing
, scale_
, 0, color
, false);
134 if (triangleVisible_
) {
135 float y
= centerY
- spacing
;
137 dc
.Draw()->DrawImageRotated(roundId_
, centerX
, centerY
- spacing
, scale_
, 0, colorBg
, false);
138 dc
.Draw()->DrawImageRotated(triangleId_
, centerX
, y
, scale_
, 0, color
, false);
141 if (squareVisible_
) {
142 dc
.Draw()->DrawImageRotated(roundId_
, centerX
- spacing
, centerY
, scale_
, 0, colorBg
, false);
143 dc
.Draw()->DrawImageRotated(squareId_
, centerX
- spacing
, centerY
, scale_
, 0, color
, false);
147 void GetContentDimensions(const UIContext
&dc
, float &w
, float &h
) const{
148 const AtlasImage
&image
= dc
.Draw()->GetAtlas()->images
[roundId_
];
150 w
= (2 * baseActionButtonSpacing
* spacing_
) + image
.w
* scale_
;
151 h
= (2 * baseActionButtonSpacing
* spacing_
) + image
.h
* scale_
;
154 virtual float GetSpacing() const { return spacing_
; }
155 virtual void SetSpacing(float s
) { spacing_
= s
; }
157 virtual void SavePosition() {
158 DragDropButton::SavePosition();
162 bool circleVisible_
, crossVisible_
, triangleVisible_
, squareVisible_
;
165 int circleId_
, crossId_
, triangleId_
, squareId_
;
170 class PSPDPadButtons
: public DragDropButton
{
172 PSPDPadButtons(float &x
, float &y
, float &scale
, float &spacing
)
173 : DragDropButton(x
, y
, -1, -1, scale
), spacing_(spacing
) {
176 void Draw(UIContext
&dc
) {
177 float opacity
= g_Config
.iTouchButtonOpacity
/ 100.0f
;
179 uint32_t colorBg
= colorAlpha(GetButtonColor(), opacity
);
180 uint32_t color
= colorAlpha(0xFFFFFF, opacity
);
182 static const float xoff
[4] = {1, 0, -1, 0};
183 static const float yoff
[4] = {0, 1, 0, -1};
185 int dirImage
= g_Config
.iTouchButtonStyle
? I_DIR_LINE
: I_DIR
;
187 for (int i
= 0; i
< 4; i
++) {
188 float r
= D_pad_Radius
* spacing_
;
189 float x
= bounds_
.centerX() + xoff
[i
] * r
;
190 float y
= bounds_
.centerY() + yoff
[i
] * r
;
191 float x2
= bounds_
.centerX() + xoff
[i
] * (r
+ 10.f
* scale_
);
192 float y2
= bounds_
.centerY() + yoff
[i
] * (r
+ 10.f
* scale_
);
193 float angle
= i
* M_PI
/ 2;
195 dc
.Draw()->DrawImageRotated(dirImage
, x
, y
, scale_
, angle
+ PI
, colorBg
, false);
196 dc
.Draw()->DrawImageRotated(I_ARROW
, x2
, y2
, scale_
, angle
+ PI
, color
);
200 void GetContentDimensions(const UIContext
&dc
, float &w
, float &h
) const{
201 const AtlasImage
&image
= dc
.Draw()->GetAtlas()->images
[I_DIR
];
202 w
= 2 * D_pad_Radius
* spacing_
+ image
.w
* scale_
;
203 h
= 2 * D_pad_Radius
* spacing_
+ image
.h
* scale_
;
206 float GetSpacing() const { return spacing_
; }
207 virtual void SetSpacing(float s
) { spacing_
= s
; }
213 TouchControlLayoutScreen::TouchControlLayoutScreen() {
217 bool TouchControlLayoutScreen::touch(const TouchInput
&touch
) {
218 UIScreen::touch(touch
);
222 int mode
= mode_
->GetSelection();
224 const Bounds
&screen_bounds
= screenManager()->getUIContext()->GetBounds();
226 if ((touch
.flags
& TOUCH_MOVE
) && pickedControl_
!= 0) {
228 const Bounds
&bounds
= pickedControl_
->GetBounds();
230 int mintouchX
= leftColumnWidth
+ bounds
.w
* 0.5;
231 int maxTouchX
= screen_bounds
.w
- bounds
.w
* 0.5;
233 int minTouchY
= bounds
.h
* 0.5;
234 int maxTouchY
= screen_bounds
.h
- bounds
.h
* 0.5;
236 int newX
= bounds
.centerX(), newY
= bounds
.centerY();
238 // we have to handle x and y separately since even if x is blocked, y may not be.
239 if (touch
.x
> mintouchX
&& touch
.x
< maxTouchX
) {
240 // if the leftmost point of the control is ahead of the margin,
241 // move it. Otherwise, don't.
244 if (touch
.y
> minTouchY
&& touch
.y
< maxTouchY
) {
247 pickedControl_
->ReplaceLayoutParams(new UI::AnchorLayoutParams(newX
, newY
, NONE
, NONE
, true));
248 } else if (mode
== 1) {
249 // Resize. Vertical = scaling, horizontal = spacing;
250 // Up should be bigger so let's negate in that direction
251 float diffX
= (touch
.x
- startX_
);
252 float diffY
= -(touch
.y
- startY_
);
254 float movementScale
= 0.02f
;
255 float newScale
= startScale_
+ diffY
* movementScale
;
256 float newSpacing
= startSpacing_
+ diffX
* movementScale
;
257 if (newScale
> 3.0f
) newScale
= 3.0f
;
258 if (newScale
< 0.5f
) newScale
= 0.5f
;
259 if (newSpacing
> 3.0f
) newSpacing
= 3.0f
;
260 if (newSpacing
< 0.5f
) newSpacing
= 0.5f
;
261 pickedControl_
->SetSpacing(newSpacing
);
262 pickedControl_
->SetScale(newScale
);
265 if ((touch
.flags
& TOUCH_DOWN
) && pickedControl_
== 0) {
266 pickedControl_
= getPickedControl(touch
.x
, touch
.y
);
267 if (pickedControl_
) {
270 startSpacing_
= pickedControl_
->GetSpacing();
271 startScale_
= pickedControl_
->GetScale();
274 if ((touch
.flags
& TOUCH_UP
) && pickedControl_
!= 0) {
275 pickedControl_
->SavePosition();
281 void TouchControlLayoutScreen::onFinish(DialogResult reason
) {
285 UI::EventReturn
TouchControlLayoutScreen::OnVisibility(UI::EventParams
&e
) {
286 screenManager()->push(new TouchControlVisibilityScreen());
287 return UI::EVENT_DONE
;
290 UI::EventReturn
TouchControlLayoutScreen::OnReset(UI::EventParams
&e
) {
291 ILOG("Resetting touch control layout");
292 g_Config
.ResetControlLayout();
293 const Bounds
&bounds
= screenManager()->getUIContext()->GetBounds();
294 InitPadLayout(bounds
.w
, bounds
.h
);
296 return UI::EVENT_DONE
;
299 void TouchControlLayoutScreen::dialogFinished(const Screen
*dialog
, DialogResult result
) {
303 void TouchControlLayoutScreen::CreateViews() {
304 // setup g_Config for button layout
305 const Bounds
&bounds
= screenManager()->getUIContext()->GetBounds();
306 InitPadLayout(bounds
.w
, bounds
.h
);
308 local_dp_xres
= bounds
.w
;
309 local_dp_yres
= bounds
.h
;
313 I18NCategory
*co
= GetI18NCategory("Controls");
314 I18NCategory
*di
= GetI18NCategory("Dialog");
316 root_
= new AnchorLayout(new LayoutParams(FILL_PARENT
, FILL_PARENT
));
318 Choice
*reset
= new Choice(di
->T("Reset"), "", false, new AnchorLayoutParams(leftColumnWidth
, WRAP_CONTENT
, 10, NONE
, NONE
, 84));
319 Choice
*back
= new Choice(di
->T("Back"), "", false, new AnchorLayoutParams(leftColumnWidth
, WRAP_CONTENT
, 10, NONE
, NONE
, 10));
320 Choice
*visibility
= new Choice(co
->T("Visibility"), "", false, new AnchorLayoutParams(leftColumnWidth
, WRAP_CONTENT
, 10, NONE
, NONE
, 158));
321 // controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fButtonScale, 0.80, 2.0, co->T("Button Scaling"), screenManager()))
322 // ->OnChange.Handle(this, &GameSettingsScreen::OnChangeControlScaling);
324 mode_
= new ChoiceStrip(ORIENT_VERTICAL
, new AnchorLayoutParams(leftColumnWidth
, WRAP_CONTENT
, 10, NONE
, NONE
, 158 + 64 + 10));
325 mode_
->AddChoice(di
->T("Move"));
326 mode_
->AddChoice(di
->T("Resize"));
327 mode_
->SetSelection(0);
329 reset
->OnClick
.Handle(this, &TouchControlLayoutScreen::OnReset
);
330 back
->OnClick
.Handle
<UIScreen
>(this, &UIScreen::OnBack
);
331 visibility
->OnClick
.Handle(this, &TouchControlLayoutScreen::OnVisibility
);
333 root_
->Add(visibility
);
337 TabHolder
*tabHolder
= new TabHolder(ORIENT_VERTICAL
, leftColumnWidth
, new AnchorLayoutParams(10, 0, 10, 0, false));
338 root_
->Add(tabHolder
);
340 // this is more for show than anything else. It's used to provide a boundary
341 // so that buttons like back can be placed within the boundary.
342 // serves no other purpose.
343 AnchorLayout
*controlsHolder
= new AnchorLayout(new LayoutParams(FILL_PARENT
, FILL_PARENT
));
345 I18NCategory
*ms
= GetI18NCategory("MainSettings");
347 tabHolder
->AddTab(ms
->T("Controls"), controlsHolder
);
349 if (!g_Config
.bShowTouchControls
) {
350 // Shouldn't even be able to get here as the way into this dialog should be closed.
356 PSPActionButtons
*actionButtons
= new PSPActionButtons(g_Config
.fActionButtonCenterX
, g_Config
.fActionButtonCenterY
, g_Config
.fActionButtonScale
, g_Config
.fActionButtonSpacing
);
357 actionButtons
->setCircleVisibility(g_Config
.bShowTouchCircle
);
358 actionButtons
->setCrossVisibility(g_Config
.bShowTouchCross
);
359 actionButtons
->setTriangleVisibility(g_Config
.bShowTouchTriangle
);
360 actionButtons
->setSquareVisibility(g_Config
.bShowTouchSquare
);
362 controls_
.push_back(actionButtons
);
364 int rectImage
= g_Config
.iTouchButtonStyle
? I_RECT_LINE
: I_RECT
;
365 int shoulderImage
= g_Config
.iTouchButtonStyle
? I_SHOULDER_LINE
: I_SHOULDER
;
366 int dirImage
= g_Config
.iTouchButtonStyle
? I_DIR_LINE
: I_DIR
;
367 int stickImage
= g_Config
.iTouchButtonStyle
? I_STICK_LINE
: I_STICK
;
368 int stickBg
= g_Config
.iTouchButtonStyle
? I_STICK_BG_LINE
: I_STICK_BG
;
370 if (g_Config
.bShowTouchDpad
) {
371 controls_
.push_back(new PSPDPadButtons(g_Config
.fDpadX
, g_Config
.fDpadY
, g_Config
.fDpadScale
, g_Config
.fDpadSpacing
));
374 if (g_Config
.bShowTouchSelect
) {
375 controls_
.push_back(new DragDropButton(g_Config
.fSelectKeyX
, g_Config
.fSelectKeyY
, rectImage
, I_SELECT
, g_Config
.fSelectKeyScale
));
378 if (g_Config
.bShowTouchStart
) {
379 controls_
.push_back(new DragDropButton(g_Config
.fStartKeyX
, g_Config
.fStartKeyY
, rectImage
, I_START
, g_Config
.fStartKeyScale
));
382 if (g_Config
.bShowTouchUnthrottle
) {
383 DragDropButton
*unthrottle
= new DragDropButton(g_Config
.fUnthrottleKeyX
, g_Config
.fUnthrottleKeyY
, rectImage
, I_ARROW
, g_Config
.fUnthrottleKeyScale
);
384 unthrottle
->SetAngle(180.0f
);
385 controls_
.push_back(unthrottle
);
388 if (g_Config
.bShowTouchLTrigger
) {
389 controls_
.push_back(new DragDropButton(g_Config
.fLKeyX
, g_Config
.fLKeyY
, shoulderImage
, I_L
, g_Config
.fLKeyScale
));
392 if (g_Config
.bShowTouchRTrigger
) {
393 DragDropButton
*rbutton
= new DragDropButton(g_Config
.fRKeyX
, g_Config
.fRKeyY
, shoulderImage
, I_R
, g_Config
.fRKeyScale
);
394 rbutton
->FlipImageH(true);
395 controls_
.push_back(rbutton
);
398 if (g_Config
.bShowTouchAnalogStick
) {
399 controls_
.push_back(new DragDropButton(g_Config
.fAnalogStickX
, g_Config
.fAnalogStickY
, stickBg
, stickImage
, g_Config
.fAnalogStickScale
));
402 for (size_t i
= 0; i
< controls_
.size(); i
++) {
403 root_
->Add(controls_
[i
]);
407 // return the control which was picked up by the touchEvent. If a control
408 // was already picked up, then it's being dragged around, so just return that instead
409 DragDropButton
*TouchControlLayoutScreen::getPickedControl(const int x
, const int y
) {
410 if (pickedControl_
!= 0) {
411 return pickedControl_
;
414 for (size_t i
= 0; i
< controls_
.size(); i
++) {
415 DragDropButton
*control
= controls_
[i
];
416 const Bounds
&bounds
= control
->GetBounds();
417 const float thresholdFactor
= 1.5f
;
419 Bounds
tolerantBounds(bounds
.x
, bounds
.y
, bounds
.w
* thresholdFactor
, bounds
.h
* thresholdFactor
);
420 if (tolerantBounds
.Contains(x
, y
)) {