1 //  SuperTux
2 //  Copyright (C) 2021 A. Semphris <semphris@protonmail.com>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 #include "control/mobile_controller.hpp"
18 
19 #ifdef ENABLE_TOUCHSCREEN_SUPPORT
20 
21 #include <string>
22 
23 #include "SDL.h"
24 
25 #include "control/controller.hpp"
26 #include "math/vector.hpp"
27 #include "supertux/globals.hpp"
28 #include "supertux/gameconfig.hpp"
29 #include "video/drawing_context.hpp"
30 #include "video/surface.hpp"
31 
32 // Util to automatically put rectangles in their corners
apply_corner(const Rectf & rect,int screen_width,int screen_height)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 
MobileController()46 MobileController::MobileController() :
47   m_up(false),
48   m_down(false),
49   m_left(false),
50   m_right(false),
51   m_jump(false),
52   m_action(false),
53   m_escape(false),
54   m_bak_escape(false),
55   m_old_up(false),
56   m_old_down(false),
57   m_old_left(false),
58   m_old_right(false),
59   m_old_jump(false),
60   m_old_action(false),
61   m_old_escape(false),
62   m_rect_directions(16.f, -144.f, 144.f, -16.f),
63   m_rect_jump(-160.f, -80.f, -96.f, -16.f),
64   m_rect_action(-80.f, -80.f, -16.f, -16.f),
65   m_rect_escape(16.f, 16.f, 64.f, 64.f),
66   m_tex_dirs(Surface::from_file("/images/engine/mobile/direction.png")),
67   m_tex_btn(Surface::from_file("/images/engine/mobile/button.png")),
68   m_tex_btn_press(Surface::from_file("/images/engine/mobile/button_press.png")),
69   m_tex_pause(Surface::from_file("/images/engine/mobile/pause.png")),
70   m_tex_up(Surface::from_file("/images/engine/mobile/direction_hightlight_up.png")),
71   m_tex_dwn(Surface::from_file("/images/engine/mobile/direction_hightlight_down.png")),
72   m_tex_lft(Surface::from_file("/images/engine/mobile/direction_hightlight_left.png")),
73   m_tex_rgt(Surface::from_file("/images/engine/mobile/direction_hightlight_right.png")),
74   m_tex_jump(Surface::from_file("/images/engine/mobile/jump.png")),
75   m_tex_action(Surface::from_file("/images/engine/mobile/action.png")),
76   m_screen_width(),
77   m_screen_height()
78 {
79 }
80 
81 void
draw(DrawingContext & context)82 MobileController::draw(DrawingContext& context)
83 {
84   if (!g_config->mobile_controls)
85     return;
86 
87   m_screen_width = context.get_width();
88   m_screen_height = context.get_height();
89 
90   context.color().draw_surface_scaled(m_tex_dirs, apply_corner(m_rect_directions, m_screen_width, m_screen_height), 1650);
91 
92   if (m_up)
93     context.color().draw_surface_scaled(m_tex_up, apply_corner(m_rect_directions, m_screen_width, m_screen_height), 1651);
94   if (m_down)
95     context.color().draw_surface_scaled(m_tex_dwn, apply_corner(m_rect_directions, m_screen_width, m_screen_height), 1651);
96   if (m_left)
97     context.color().draw_surface_scaled(m_tex_lft, apply_corner(m_rect_directions, m_screen_width, m_screen_height), 1651);
98   if (m_right)
99     context.color().draw_surface_scaled(m_tex_rgt, apply_corner(m_rect_directions, m_screen_width, m_screen_height), 1651);
100 
101   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), 1650);
102   context.color().draw_surface_scaled(m_tex_action, apply_corner(m_rect_action, m_screen_width, m_screen_height), 1651);
103 
104   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), 1650);
105   context.color().draw_surface_scaled(m_tex_jump, apply_corner(m_rect_jump, m_screen_width, m_screen_height), 1651);
106 
107   context.color().draw_surface_scaled(m_bak_escape ? m_tex_btn_press : m_tex_btn, apply_corner(m_rect_escape, m_screen_width, m_screen_height), 1650);
108   context.color().draw_surface_scaled(m_tex_pause, apply_corner(m_rect_escape, m_screen_width, m_screen_height).grown(-8.f), 1650);
109 }
110 
111 void
update()112 MobileController::update()
113 {
114   if (!g_config->mobile_controls)
115     return;
116 
117   m_old_up = m_up;
118   m_old_down = m_down;
119   m_old_left = m_left;
120   m_old_right = m_right;
121   m_old_jump = m_jump;
122   m_old_action = m_action;
123   m_old_escape = m_escape;
124 
125   m_up = m_down = m_left = m_right = m_jump = m_action = m_escape = false;
126 
127   // FIXME: This assumes that 1) there is only one touchscreen and 2) SuperTux
128   // fills the whole screen
129   if (SDL_GetNumTouchDevices() < 1)
130     return;
131 
132   SDL_TouchID device = SDL_GetTouchDevice(0);
133 
134   if (device == 0)
135     throw new std::runtime_error("Error getting touchscreen info: " + std::string(SDL_GetError()));
136 
137   int num_touches = SDL_GetNumTouchFingers(device);
138 
139   // FIXME: There's some weird problem with the escape button specifically, which
140   // I had to patch a weird way. If someone in the future finds a fix to handle
141   // escaping on mobile properly, don't forget to remove those lines.
142   if (num_touches == 0)
143     m_bak_escape = false;
144 
145   for (int i = 0; i < num_touches; i++)
146   {
147     SDL_Finger* finger = SDL_GetTouchFinger(device, i);
148 
149     if (!finger)
150       continue;
151 
152     activate_widget_at_pos(finger->x * float(m_screen_width), finger->y * float(m_screen_height));
153   }
154 }
155 
156 void
apply(Controller & controller) const157 MobileController::apply(Controller& controller) const
158 {
159   if (!g_config->mobile_controls)
160     return;
161 
162   controller.set_control(Control::UP,     m_up     || (!m_old_up     && controller.hold(Control::UP)));
163   controller.set_control(Control::DOWN,   m_down   || (!m_old_down   && controller.hold(Control::DOWN)));
164   controller.set_control(Control::LEFT,   m_left   || (!m_old_left   && controller.hold(Control::LEFT)));
165   controller.set_control(Control::RIGHT,  m_right  || (!m_old_right  && controller.hold(Control::RIGHT)));
166   controller.set_control(Control::JUMP,   m_jump   || (!m_old_jump   && controller.hold(Control::JUMP)));
167   controller.set_control(Control::ACTION, m_action || (!m_old_action && controller.hold(Control::ACTION)));
168   controller.set_control(Control::ESCAPE, m_escape || (!m_old_escape && controller.hold(Control::ESCAPE)));
169 }
170 
171 void
activate_widget_at_pos(float x,float y)172 MobileController::activate_widget_at_pos(float x, float y)
173 {
174   if (!g_config->mobile_controls)
175     return;
176 
177   Vector pos(x, y);
178 
179   if (apply_corner(m_rect_jump, m_screen_width, m_screen_height).contains(pos))
180     m_jump = true;
181 
182   if (apply_corner(m_rect_action, m_screen_width, m_screen_height).contains(pos))
183     m_action = true;
184 
185   // FIXME: Why do I need an extra variable (m_bak_escape) just for this one?
186   // Without it, pressing escape will toggle pressed() (not hold(), pressed())
187   // every single frame, apparently
188   if (apply_corner(m_rect_escape, m_screen_width, m_screen_height).contains(pos))
189   {
190     if (!m_bak_escape)
191       m_escape = true;
192     m_bak_escape = true;
193   }
194   else
195   {
196     m_bak_escape = false;
197   }
198 
199   Rectf applied = apply_corner(m_rect_directions, m_screen_width, m_screen_height);
200   Rectf up = applied;
201   up.set_bottom(up.get_bottom() - up.get_height() * 2.f / 3.f);
202   if (up.contains(pos))
203     m_up = true;
204 
205   Rectf down = applied;
206   down.set_top(down.get_top() + down.get_height() * 2.f / 3.f);
207   if (down.contains(pos))
208     m_down = true;
209 
210   Rectf left = applied;
211   left.set_right(left.get_right() - left.get_width() * 2.f / 3.f);
212   if (left.contains(pos))
213     m_left = true;
214 
215   Rectf right = applied;
216   right.set_left(right.get_left() + right.get_width() * 2.f / 3.f);
217   if (right.contains(pos))
218     m_right = true;
219 }
220 
221 #endif
222 
223 /* EOF */
224