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