Skip to content

Commit 1ee395e

Browse files
committed
Improved touch controls
Touch button images are half-transparent and smaller, but touch button area is bigger. Touch buttons will not send mouse click events to the underlying widgets. Does not depend on touch device ID 0, on my phone the touchscreen has device ID 4.
1 parent 52c4f62 commit 1ee395e

File tree

3 files changed

+144
-64
lines changed

3 files changed

+144
-64
lines changed

src/control/mobile_controller.cpp

Lines changed: 115 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,6 @@
2929
#include "video/drawing_context.hpp"
3030
#include "video/surface.hpp"
3131

32-
// Util to automatically put rectangles in their corners
33-
static Rectf apply_corner(const Rectf& rect, int screen_width, int screen_height)
34-
{
35-
Rectf r = rect;
36-
37-
if (r.p1().x < 0)
38-
r.move(Vector(static_cast<float>(screen_width), 0));
39-
40-
if (r.p1().y < 0)
41-
r.move(Vector(0, static_cast<float>(screen_height)));
42-
43-
return r;
44-
}
45-
4632
MobileController::MobileController() :
4733
m_up(false),
4834
m_down(false),
@@ -62,12 +48,19 @@ MobileController::MobileController() :
6248
m_old_cheats(false),
6349
m_old_debug(false),
6450
m_old_escape(false),
51+
m_fingers(),
6552
m_rect_directions(16.f, -144.f, 144.f, -16.f),
6653
m_rect_jump(-160.f, -80.f, -96.f, -16.f),
6754
m_rect_action(-80.f, -80.f, -16.f, -16.f),
6855
m_rect_cheats(-160.f, 16.f, -96.f, 80.f),
6956
m_rect_debug(-80.f, 16.f, -16.f, 80.f),
7057
m_rect_escape(16.f, 16.f, 64.f, 64.f),
58+
m_draw_directions(16.f, -144.f, 144.f, -16.f),
59+
m_draw_jump(-160.f, -80.f, -96.f, -16.f),
60+
m_draw_action(-80.f, -80.f, -16.f, -16.f),
61+
m_draw_cheats(-160.f, 16.f, -96.f, 80.f),
62+
m_draw_debug(-80.f, 16.f, -16.f, 80.f),
63+
m_draw_escape(16.f, 16.f, 64.f, 64.f),
7164
m_tex_dirs(Surface::from_file("/images/engine/mobile/direction.png")),
7265
m_tex_btn(Surface::from_file("/images/engine/mobile/button.png")),
7366
m_tex_btn_press(Surface::from_file("/images/engine/mobile/button_press.png")),
@@ -91,36 +84,75 @@ MobileController::draw(DrawingContext& context)
9184
if (!g_config->mobile_controls)
9285
return;
9386

94-
m_screen_width = context.get_width();
95-
m_screen_height = context.get_height();
87+
if (m_screen_width != context.get_width() || m_screen_height != context.get_height())
88+
{
89+
m_screen_width = context.get_width();
90+
m_screen_height = context.get_height();
91+
float width = static_cast<float>(m_screen_width);
92+
float height = static_cast<float>(m_screen_height);
93+
// Buttons on Android are bigger, and direction buttons are extra wide
94+
// Use screen height to calculate button size, because 20:9 screen ratios are common
95+
#ifdef __ANDROID__
96+
constexpr float BUTTON_SCALE = 0.4f;
97+
#else
98+
constexpr float BUTTON_SCALE = 0.2f;
99+
#endif
100+
m_rect_directions.set_size(height * BUTTON_SCALE * 4 / 3, height * BUTTON_SCALE);
101+
m_rect_directions.set_pos(Vector(0, height - height * BUTTON_SCALE));
102+
m_draw_directions = Rectf::from_center(m_rect_directions.get_middle(),
103+
Sizef(m_rect_directions.get_height() / 2, m_rect_directions.get_height() / 2));
104+
105+
m_rect_jump.set_size(height * BUTTON_SCALE, height * BUTTON_SCALE);
106+
m_rect_jump.set_pos(Vector(width - height * BUTTON_SCALE, height - height * BUTTON_SCALE));
107+
m_draw_jump = m_rect_jump.grown(-m_rect_jump.get_height() * 3 / 8);
108+
109+
m_rect_action.set_size(height * BUTTON_SCALE, height * BUTTON_SCALE);
110+
m_rect_action.set_pos(Vector(width - 2 * height * BUTTON_SCALE, height - height * BUTTON_SCALE));
111+
m_draw_action = m_rect_action.grown(-m_rect_action.get_height() * 3 / 8);
112+
113+
m_rect_escape.set_size(height * BUTTON_SCALE / 2, height * BUTTON_SCALE / 2);
114+
m_rect_escape.set_pos(Vector(0, 0));
115+
m_draw_escape = m_rect_escape.grown(-m_rect_escape.get_height() / 4);
116+
117+
m_rect_cheats.set_size(height * BUTTON_SCALE / 2, height * BUTTON_SCALE / 2);
118+
m_rect_cheats.set_pos(Vector(width - 2 * height * BUTTON_SCALE / 2, 0));
119+
m_draw_cheats = m_rect_cheats.grown(-m_rect_cheats.get_height() / 4);
120+
121+
m_rect_debug.set_size(height * BUTTON_SCALE / 2, height * BUTTON_SCALE / 2);
122+
m_rect_debug.set_pos(Vector(width - height * BUTTON_SCALE / 2, 0));
123+
m_draw_debug = m_rect_debug.grown(-m_rect_debug.get_height() / 4);
124+
}
125+
126+
PaintStyle translucent;
127+
translucent.set_alpha(0.5f);
96128

97-
context.color().draw_surface_scaled(m_tex_dirs, apply_corner(m_rect_directions, m_screen_width, m_screen_height), LAYER_GUI + 99);
129+
context.color().draw_surface_scaled(m_tex_dirs, m_draw_directions, LAYER_GUI + 99, translucent);
98130

99131
if (m_up)
100-
context.color().draw_surface_scaled(m_tex_up, apply_corner(m_rect_directions, m_screen_width, m_screen_height), LAYER_GUI + 99);
132+
context.color().draw_surface_scaled(m_tex_up, m_draw_directions, LAYER_GUI + 99, translucent);
101133
if (m_down)
102-
context.color().draw_surface_scaled(m_tex_dwn, apply_corner(m_rect_directions, m_screen_width, m_screen_height), LAYER_GUI + 99);
134+
context.color().draw_surface_scaled(m_tex_dwn, m_draw_directions, LAYER_GUI + 99, translucent);
103135
if (m_left)
104-
context.color().draw_surface_scaled(m_tex_lft, apply_corner(m_rect_directions, m_screen_width, m_screen_height), LAYER_GUI + 99);
136+
context.color().draw_surface_scaled(m_tex_lft, m_draw_directions, LAYER_GUI + 99, translucent);
105137
if (m_right)
106-
context.color().draw_surface_scaled(m_tex_rgt, apply_corner(m_rect_directions, m_screen_width, m_screen_height), LAYER_GUI + 99);
138+
context.color().draw_surface_scaled(m_tex_rgt, m_draw_directions, LAYER_GUI + 99, translucent);
107139

108-
context.color().draw_surface_scaled(m_action ? m_tex_btn_press : m_tex_btn, apply_corner(m_rect_action, m_screen_width, m_screen_height), LAYER_GUI + 99);
109-
context.color().draw_surface_scaled(m_tex_action, apply_corner(m_rect_action, m_screen_width, m_screen_height), LAYER_GUI + 99);
140+
context.color().draw_surface_scaled(m_action ? m_tex_btn_press : m_tex_btn, m_draw_action, LAYER_GUI + 99, translucent);
141+
context.color().draw_surface_scaled(m_tex_action, m_draw_action, LAYER_GUI + 99, translucent);
110142

111-
context.color().draw_surface_scaled(m_jump ? m_tex_btn_press : m_tex_btn, apply_corner(m_rect_jump, m_screen_width, m_screen_height), LAYER_GUI + 99);
112-
context.color().draw_surface_scaled(m_tex_jump, apply_corner(m_rect_jump, m_screen_width, m_screen_height), LAYER_GUI + 99);
143+
context.color().draw_surface_scaled(m_jump ? m_tex_btn_press : m_tex_btn, m_draw_jump, LAYER_GUI + 99, translucent);
144+
context.color().draw_surface_scaled(m_tex_jump, m_draw_jump, LAYER_GUI + 99, translucent);
113145

114-
context.color().draw_surface_scaled(m_escape ? m_tex_btn_press : m_tex_btn, apply_corner(m_rect_escape, m_screen_width, m_screen_height), LAYER_GUI + 99);
115-
context.color().draw_surface_scaled(m_tex_pause, apply_corner(m_rect_escape, m_screen_width, m_screen_height).grown(-8.f), LAYER_GUI + 99);
146+
context.color().draw_surface_scaled(m_escape ? m_tex_btn_press : m_tex_btn, m_draw_escape, LAYER_GUI + 99, translucent);
147+
context.color().draw_surface_scaled(m_tex_pause, m_draw_escape.grown(-m_draw_escape.get_height() / 8), LAYER_GUI + 99, translucent);
116148

117149
if (g_config->developer_mode)
118150
{
119-
context.color().draw_surface_scaled(m_cheats ? m_tex_btn_press : m_tex_btn, apply_corner(m_rect_cheats, m_screen_width, m_screen_height), LAYER_GUI + 99);
120-
context.color().draw_surface_scaled(m_tex_cheats, apply_corner(m_rect_cheats, m_screen_width, m_screen_height), LAYER_GUI + 99);
151+
context.color().draw_surface_scaled(m_cheats ? m_tex_btn_press : m_tex_btn, m_draw_cheats, LAYER_GUI + 99, translucent);
152+
context.color().draw_surface_scaled(m_tex_cheats, m_draw_cheats, LAYER_GUI + 99, translucent);
121153

122-
context.color().draw_surface_scaled(m_debug ? m_tex_btn_press : m_tex_btn, apply_corner(m_rect_debug, m_screen_width, m_screen_height), LAYER_GUI + 99);
123-
context.color().draw_surface_scaled(m_tex_debug, apply_corner(m_rect_debug, m_screen_width, m_screen_height), LAYER_GUI + 99);
154+
context.color().draw_surface_scaled(m_debug ? m_tex_btn_press : m_tex_btn, m_draw_debug, LAYER_GUI + 99, translucent);
155+
context.color().draw_surface_scaled(m_tex_debug, m_draw_debug, LAYER_GUI + 99, translucent);
124156
}
125157
}
126158

@@ -150,26 +182,9 @@ MobileController::update()
150182
activate_widget_at_pos(static_cast<float>(x), static_cast<float>(y));
151183
}
152184

153-
// FIXME: This assumes that 1) there is only one touchscreen and 2) SuperTux
154-
// fills the whole screen
155-
if (SDL_GetNumTouchDevices() < 1)
156-
return;
157-
158-
SDL_TouchID device = SDL_GetTouchDevice(0);
159-
160-
if (device == 0)
161-
throw new std::runtime_error("Error getting touchscreen info: " + std::string(SDL_GetError()));
162-
163-
int num_touches = SDL_GetNumTouchFingers(device);
164-
165-
for (int i = 0; i < num_touches; i++)
185+
for (auto& i : m_fingers)
166186
{
167-
SDL_Finger* finger = SDL_GetTouchFinger(device, i);
168-
169-
if (!finger)
170-
continue;
171-
172-
activate_widget_at_pos(finger->x * float(m_screen_width), finger->y * float(m_screen_height));
187+
activate_widget_at_pos(i.second.x, i.second.y);
173188
}
174189
}
175190

@@ -199,6 +214,45 @@ MobileController::apply(Controller& controller) const
199214
controller.set_control(Control::ESCAPE, m_escape);
200215
}
201216

217+
bool
218+
MobileController::process_finger_down_event(const SDL_TouchFingerEvent& event)
219+
{
220+
Vector pos(event.x * float(m_screen_width), event.y * float(m_screen_height));
221+
m_fingers[event.fingerId] = pos;
222+
return m_rect_jump.contains(pos) ||
223+
m_rect_action.contains(pos) ||
224+
m_rect_escape.contains(pos) ||
225+
m_rect_directions.contains(pos) ||
226+
(g_config->developer_mode && m_rect_cheats.contains(pos)) ||
227+
(g_config->developer_mode && m_rect_debug.contains(pos));
228+
}
229+
230+
bool
231+
MobileController::process_finger_up_event(const SDL_TouchFingerEvent& event)
232+
{
233+
Vector pos(event.x * float(m_screen_width), event.y * float(m_screen_height));
234+
m_fingers.erase(event.fingerId);
235+
return m_rect_jump.contains(pos) ||
236+
m_rect_action.contains(pos) ||
237+
m_rect_escape.contains(pos) ||
238+
m_rect_directions.contains(pos) ||
239+
(g_config->developer_mode && m_rect_cheats.contains(pos)) ||
240+
(g_config->developer_mode && m_rect_debug.contains(pos));
241+
}
242+
243+
bool
244+
MobileController::process_finger_motion_event(const SDL_TouchFingerEvent& event)
245+
{
246+
Vector pos(event.x * float(m_screen_width), event.y * float(m_screen_height));
247+
m_fingers[event.fingerId] = pos;
248+
return m_rect_jump.contains(pos) ||
249+
m_rect_action.contains(pos) ||
250+
m_rect_escape.contains(pos) ||
251+
m_rect_directions.contains(pos) ||
252+
(g_config->developer_mode && m_rect_cheats.contains(pos)) ||
253+
(g_config->developer_mode && m_rect_debug.contains(pos));
254+
}
255+
202256
void
203257
MobileController::activate_widget_at_pos(float x, float y)
204258
{
@@ -207,42 +261,41 @@ MobileController::activate_widget_at_pos(float x, float y)
207261

208262
Vector pos(x, y);
209263

210-
if (apply_corner(m_rect_jump, m_screen_width, m_screen_height).contains(pos))
264+
if (m_rect_jump.contains(pos))
211265
m_jump = true;
212266

213-
if (apply_corner(m_rect_action, m_screen_width, m_screen_height).contains(pos))
267+
if (m_rect_action.contains(pos))
214268
m_action = true;
215269

216270
if (g_config->developer_mode)
217271
{
218-
if (apply_corner(m_rect_cheats, m_screen_width, m_screen_height).contains(pos))
272+
if (m_rect_cheats.contains(pos))
219273
m_cheats = true;
220274

221-
if (apply_corner(m_rect_debug, m_screen_width, m_screen_height).contains(pos))
275+
if (m_rect_debug.contains(pos))
222276
m_debug = true;
223277
}
224278

225-
if (apply_corner(m_rect_escape, m_screen_width, m_screen_height).contains(pos))
279+
if (m_rect_escape.contains(pos))
226280
m_escape = true;
227281

228-
Rectf applied = apply_corner(m_rect_directions, m_screen_width, m_screen_height);
229-
Rectf up = applied;
282+
Rectf up = m_rect_directions;
230283
up.set_bottom(up.get_bottom() - up.get_height() * 2.f / 3.f);
231284
if (up.contains(pos))
232285
m_up = true;
233286

234-
Rectf down = applied;
287+
Rectf down = m_rect_directions;
235288
down.set_top(down.get_top() + down.get_height() * 2.f / 3.f);
236289
if (down.contains(pos))
237290
m_down = true;
238291

239-
Rectf left = applied;
240-
left.set_right(left.get_right() - left.get_width() * 2.f / 3.f);
292+
Rectf left = m_rect_directions;
293+
left.set_right(left.get_right() - left.get_width() * 7.f / 12.f);
241294
if (left.contains(pos))
242295
m_left = true;
243296

244-
Rectf right = applied;
245-
right.set_left(right.get_left() + right.get_width() * 2.f / 3.f);
297+
Rectf right = m_rect_directions;
298+
right.set_left(right.get_left() + right.get_width() * 7.f / 12.f);
246299
if (right.contains(pos))
247300
m_right = true;
248301
}

src/control/mobile_controller.hpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@
1717
#ifndef HEADER_SUPERTUX_CONTROL_MOBILE_CONTROLLER_HPP
1818
#define HEADER_SUPERTUX_CONTROL_MOBILE_CONTROLLER_HPP
1919

20+
#include <SDL.h>
21+
#include <map>
22+
2023
#include "config.h"
2124

2225
#ifdef ENABLE_TOUCHSCREEN_SUPPORT
2326

2427
#include "math/rectf.hpp"
28+
#include "math/vector.hpp"
2529
#include "video/surface_ptr.hpp"
2630

2731
class Controller;
@@ -35,15 +39,26 @@ class MobileController final
3539
void apply(Controller& controller) const;
3640
void update();
3741

42+
/** returns true if the finger event was inside the screen button area */
43+
bool process_finger_down_event(const SDL_TouchFingerEvent& event);
44+
/** returns true if the finger event was inside the screen button area */
45+
bool process_finger_up_event(const SDL_TouchFingerEvent& event);
46+
/** returns true if the finger event was inside the screen button area */
47+
bool process_finger_motion_event(const SDL_TouchFingerEvent& event);
48+
3849
private:
3950
void activate_widget_at_pos(float x, float y);
4051

4152
private:
4253
bool m_up, m_down, m_left, m_right, m_jump, m_action, m_cheats, m_debug, m_escape;
4354
bool m_old_up, m_old_down, m_old_left, m_old_right, m_old_jump, m_old_action, m_old_cheats, m_old_debug, m_old_escape;
4455

45-
const Rectf m_rect_directions, m_rect_jump, m_rect_action, m_rect_cheats,
46-
m_rect_debug, m_rect_escape;
56+
std::map<SDL_FingerID, Vector> m_fingers;
57+
58+
Rectf m_rect_directions, m_rect_jump, m_rect_action, m_rect_cheats,
59+
m_rect_debug, m_rect_escape;
60+
Rectf m_draw_directions, m_draw_jump, m_draw_action, m_draw_cheats,
61+
m_draw_debug, m_draw_escape;
4762
const SurfacePtr m_tex_dirs, m_tex_btn, m_tex_btn_press, m_tex_pause,
4863
m_tex_up, m_tex_dwn, m_tex_lft, m_tex_rgt,
4964
m_tex_jump, m_tex_action, m_tex_cheats, m_tex_debug;

src/supertux/screen_manager.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,10 @@ ScreenManager::process_events()
341341
SDL_Event old_event = event;
342342

343343
SDL_Event event2;
344+
345+
if (m_mobile_controller.process_finger_down_event(event.tfinger))
346+
break; // Event was processed by touch controls, do not generate mouse event
347+
344348
event2.type = SDL_MOUSEBUTTONDOWN;
345349
event2.button.button = SDL_BUTTON_LEFT;
346350
event2.button.x = Sint32(old_event.tfinger.x * float(m_video_system.get_window_size().width));
@@ -358,13 +362,18 @@ ScreenManager::process_events()
358362
{
359363
SDL_Event old_event = event;
360364

365+
// Always generate mouse up event, because the finger can generate mouse click
366+
// and then move to the screen button, and the mouse button will stay pressed
361367
SDL_Event event2;
362368
event2.type = SDL_MOUSEBUTTONUP;
363369
event2.button.button = SDL_BUTTON_LEFT;
364370
event2.button.x = Sint32(old_event.tfinger.x * float(m_video_system.get_window_size().width));
365371
event2.button.y = Sint32(old_event.tfinger.y * float(m_video_system.get_window_size().height));
366372
SDL_PushEvent(&event2);
367373

374+
if (m_mobile_controller.process_finger_up_event(event.tfinger))
375+
break; // Event was processed by touch controls, do not generate mouse event
376+
368377
event.type = SDL_MOUSEMOTION;
369378
event.motion.x = event2.button.x;
370379
event.motion.y = event2.button.y;
@@ -375,6 +384,9 @@ ScreenManager::process_events()
375384
case SDL_FINGERMOTION:
376385
SDL_Event old_event = event;
377386

387+
if (m_mobile_controller.process_finger_motion_event(event.tfinger))
388+
break; // Event was processed by touch controls, do not generate mouse event
389+
378390
event.type = SDL_MOUSEMOTION;
379391
event.motion.x = Sint32(old_event.tfinger.x * float(m_video_system.get_window_size().width));
380392
event.motion.y = Sint32(old_event.tfinger.y * float(m_video_system.get_window_size().height));

0 commit comments

Comments
 (0)