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