1 #include "input/touch.h"
2
3 #include "game/system.h"
4 #include "input/mouse.h"
5
6 #include <stdlib.h>
7
8 #define CLICK_TIME 300
9 #define NOT_MOVING_RANGE 5
10 #define SCROLL_FINGER_RADIUS 25
11 #define MILLIS_TOLERANCE_BETWEEN_LAST_MOVE_AND_TOUCH_END 60
12
13 typedef enum {
14 EMULATED_MOUSE_CLICK_NONE = 0,
15 EMULATED_MOUSE_CLICK_LEFT = 1,
16 EMULATED_MOUSE_CLICK_RIGHT = 2
17 } emulated_mouse_click;
18
19 static struct {
20 touch finger[MAX_ACTIVE_TOUCHES + 1];
21 touch old_touch;
22 int last_scroll_position;
23 touch_mode mode;
24 emulated_mouse_click touchpad_mode_click_type;
25 } data;
26
start_delayed(const touch * t)27 static int start_delayed(const touch *t)
28 {
29 return t->has_started && !t->has_moved && !t->has_ended
30 && ((time_get_millis() - t->start_time) < (time_millis) (CLICK_TIME / 2));
31 }
32
touch_get_earliest(void)33 const touch *touch_get_earliest(void)
34 {
35 time_millis timestamp = -1;
36 int touch_index = MAX_ACTIVE_TOUCHES;
37 for (int i = 0; i < MAX_ACTIVE_TOUCHES; ++i) {
38 if (data.finger[i].in_use && !start_delayed(&data.finger[i]) && data.finger[i].start_time < timestamp) {
39 timestamp = data.finger[i].start_time;
40 touch_index = i;
41 }
42 }
43 return &data.finger[touch_index];
44 }
45
touch_get_latest(void)46 const touch *touch_get_latest(void)
47 {
48 int active_touches = 0;
49 time_millis timestamp = 0;
50 int touch_index = MAX_ACTIVE_TOUCHES;
51 for (int i = 0; i < MAX_ACTIVE_TOUCHES; ++i) {
52 if (data.finger[i].in_use && !start_delayed(&data.finger[i])) {
53 ++active_touches;
54 if (data.finger[i].start_time > timestamp) {
55 timestamp = data.finger[i].start_time;
56 touch_index = i;
57 }
58 }
59 }
60 return (active_touches > 1) ? &data.finger[touch_index] : &data.finger[MAX_ACTIVE_TOUCHES];
61 }
62
get_total_active_touches(void)63 static int get_total_active_touches(void)
64 {
65 int active_touches = 0;
66 for (int i = 0; i < MAX_ACTIVE_TOUCHES; ++i) {
67 if (data.finger[i].in_use) {
68 ++active_touches;
69 }
70 }
71 return active_touches;
72 }
73
touch_not_click(const touch * t)74 int touch_not_click(const touch *t)
75 {
76 return (t->has_moved || (!t->has_ended && (time_get_millis() - t->start_time) >= CLICK_TIME) ||
77 (t->has_ended && (t->last_change_time - t->start_time) >= CLICK_TIME));
78 }
79
touch_was_click(const touch * t)80 int touch_was_click(const touch *t)
81 {
82 return (t->has_ended && !t->has_moved && (t->last_change_time - t->start_time) < CLICK_TIME);
83 }
84
touch_was_double_click(const touch * t)85 int touch_was_double_click(const touch *t)
86 {
87 return (touch_was_click(t) && touch_was_click(&data.old_touch) &&
88 (t->start_time > data.old_touch.last_change_time) &&
89 (t->start_time - data.old_touch.last_change_time) < CLICK_TIME);
90 }
91
touch_is_scroll(void)92 int touch_is_scroll(void)
93 {
94 int num_touches = get_total_active_touches();
95 if (num_touches > 2) {
96 return 0;
97 }
98 const touch *first = touch_get_earliest();
99 if (num_touches == 2) {
100 const touch *last = touch_get_latest();
101 return ((last->start_time - first->start_time) < CLICK_TIME) && !touch_was_click(last);
102 }
103 return first->in_use && start_delayed(first);
104 }
105
touch_get_scroll(void)106 int touch_get_scroll(void)
107 {
108 const touch *first = touch_get_earliest();
109 const touch *last = touch_get_latest();
110 if (!touch_is_scroll() || !first->has_moved || !last->has_moved) {
111 return SCROLL_NONE;
112 }
113 if (!data.last_scroll_position) {
114 data.last_scroll_position = first->start_point.y;
115 }
116 int delta_x = abs((first->current_point.x - first->start_point.x) - (last->current_point.x - last->start_point.x));
117 int delta_y = abs((first->current_point.y - first->start_point.y) - (last->current_point.y - last->start_point.y));
118 if (delta_x > SCROLL_FINGER_RADIUS || delta_y > SCROLL_FINGER_RADIUS) {
119 return SCROLL_NONE;
120 }
121 int delta = first->current_point.y - data.last_scroll_position;
122 if (abs(delta) < NOT_MOVING_RANGE * 2) {
123 return SCROLL_NONE;
124 }
125 data.last_scroll_position = first->current_point.y;
126 return (delta > 0) ? SCROLL_UP : SCROLL_DOWN;
127 }
128
get_unused_touch_index(void)129 static int get_unused_touch_index(void)
130 {
131 int i = 0;
132 while (i < MAX_ACTIVE_TOUCHES) {
133 if (!data.finger[i].in_use) {
134 break;
135 }
136 ++i;
137 }
138 return i;
139 }
140
touch_create(touch_coords start_coords,time_millis start_time)141 int touch_create(touch_coords start_coords, time_millis start_time)
142 {
143 int index = get_unused_touch_index();
144 if (index != MAX_ACTIVE_TOUCHES) {
145 touch *t = &data.finger[index];
146 t->in_use = 1;
147 t->has_started = 1;
148 t->has_ended = 0;
149 t->start_point = start_coords;
150 t->current_point = start_coords;
151 t->previous_frame_point = start_coords;
152 t->frame_movement.x = 0;
153 t->frame_movement.y = 0;
154 t->last_movement = t->frame_movement;
155 t->start_time = start_time;
156 t->last_change_time = start_time;
157 }
158 return index;
159 }
160
touch_in_use(int index)161 int touch_in_use(int index)
162 {
163 return index >= 0 && index < MAX_ACTIVE_TOUCHES && data.finger[index].in_use;
164 }
165
touch_move(int index,touch_coords current_coords,time_millis current_time)166 void touch_move(int index, touch_coords current_coords, time_millis current_time)
167 {
168 if (index < 0 || index >= MAX_ACTIVE_TOUCHES || !data.finger[index].in_use) {
169 return;
170 }
171 touch *t = &data.finger[index];
172 t->last_change_time = current_time;
173 t->current_point = current_coords;
174 if ((abs(current_coords.x - t->start_point.x) > NOT_MOVING_RANGE)
175 || (abs(current_coords.y - t->start_point.y) > NOT_MOVING_RANGE)) {
176 t->has_moved = 1;
177 }
178 }
179
touch_end(int index,time_millis current_time)180 void touch_end(int index, time_millis current_time)
181 {
182 if (index < 0 || index >= MAX_ACTIVE_TOUCHES || !data.finger[index].in_use) {
183 return;
184 }
185 touch *t = &data.finger[index];
186 t->has_ended = 1;
187 // This is needed because sometimes SDL waits for up to three frames to register the touch end
188 if (current_time - t->last_change_time < MILLIS_TOLERANCE_BETWEEN_LAST_MOVE_AND_TOUCH_END) {
189 t->frame_movement = t->last_movement;
190 }
191 }
192
reset_touches(int reset_old_touch)193 void reset_touches(int reset_old_touch)
194 {
195 for (int i = 0; i < MAX_ACTIVE_TOUCHES; ++i) {
196 touch *t = &data.finger[i];
197 if (!t->in_use) {
198 continue;
199 }
200 if (t->has_ended) {
201 t->in_use = 0;
202 if (!reset_old_touch) {
203 data.old_touch = *t;
204 } else {
205 data.old_touch.last_change_time = 0;
206 }
207 t->has_started = 0;
208 t->has_moved = 0;
209 t->has_ended = 0;
210 data.last_scroll_position = 0;
211 } else {
212 t->frame_movement.x = t->current_point.x - t->previous_frame_point.x;
213 t->frame_movement.y = t->current_point.y - t->previous_frame_point.y;
214 if (t->frame_movement.x != 0 || t->frame_movement.y != 0) {
215 t->last_movement = t->frame_movement;
216 }
217 t->previous_frame_point = t->current_point;
218 t->has_started = start_delayed(t);
219 }
220 }
221 }
222
any_touch_went_up(void)223 static int any_touch_went_up(void)
224 {
225 for (int i = 0; i < MAX_ACTIVE_TOUCHES; ++i) {
226 if (data.finger[i].has_ended) {
227 return 1;
228 }
229 }
230 return 0;
231 }
232
handle_emulated_mouse_clicks(void)233 static int handle_emulated_mouse_clicks(void)
234 {
235 mouse_reset_scroll();
236 switch (data.touchpad_mode_click_type) {
237 case EMULATED_MOUSE_CLICK_LEFT:
238 mouse_set_left_down(0);
239 break;
240 case EMULATED_MOUSE_CLICK_RIGHT:
241 mouse_set_right_down(0);
242 break;
243 default:
244 mouse_reset_button_state();
245 return 0;
246 }
247 mouse_determine_button_state();
248 data.touchpad_mode_click_type = EMULATED_MOUSE_CLICK_NONE;
249 return 1;
250 }
251
handle_mouse_touchpad(void)252 static void handle_mouse_touchpad(void)
253 {
254 if (handle_emulated_mouse_clicks()) {
255 return;
256 }
257
258 int num_fingers = get_total_active_touches();
259
260 if (!num_fingers) {
261 return;
262 }
263
264 if (any_touch_went_up()) {
265 if (num_fingers == 1 && touch_was_click(touch_get_earliest())) {
266 mouse_set_left_down(1);
267 mouse_determine_button_state();
268 data.touchpad_mode_click_type = EMULATED_MOUSE_CLICK_LEFT;
269 } else if (num_fingers == 2 &&
270 (touch_was_click(touch_get_earliest()) || touch_was_click(touch_get_latest()))) {
271 mouse_set_right_down(1);
272 mouse_determine_button_state();
273 data.touchpad_mode_click_type = EMULATED_MOUSE_CLICK_RIGHT;
274 }
275 } else {
276 const touch *t = touch_get_earliest();
277 if (!t->has_moved) {
278 return;
279 }
280 system_move_mouse_cursor(t->frame_movement.x, t->frame_movement.y);
281 }
282 }
283
handle_mouse_direct(void)284 static void handle_mouse_direct(void)
285 {
286 mouse_reset_scroll();
287 mouse_reset_button_state();
288
289 const touch *first = touch_get_earliest();
290 int x = first->current_point.x;
291 int y = first->current_point.y;
292 system_set_mouse_position(&x, &y);
293 mouse_set_position(x, y);
294 }
295
touch_to_mouse(void)296 int touch_to_mouse(void)
297 {
298 const touch *first = touch_get_earliest();
299 if (!first->in_use) {
300 if (mouse_get()->is_touch) {
301 mouse_reset_scroll();
302 mouse_reset_button_state();
303 return 1;
304 } else if (data.touchpad_mode_click_type != EMULATED_MOUSE_CLICK_NONE) {
305 return handle_emulated_mouse_clicks();
306 }
307 return 0;
308 }
309 switch (data.mode) {
310 case TOUCH_MODE_TOUCHPAD:
311 handle_mouse_touchpad();
312 break;
313 case TOUCH_MODE_DIRECT:
314 handle_mouse_direct();
315 break;
316 default:
317 mouse_set_from_touch(first, touch_get_latest());
318 break;
319 }
320 return 1;
321 }
322
touch_set_mode(touch_mode mode)323 void touch_set_mode(touch_mode mode)
324 {
325 data.mode = mode;
326 }
327
touch_cycle_mode(void)328 void touch_cycle_mode(void)
329 {
330 data.mode = (data.mode + 1) % TOUCH_MODE_MAX;
331 }
332