diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-04-09 16:39:39 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-04-10 06:29:31 +0300 |
commit | 6d36a826ce502737bd1b6ff2b9fe47c79338e58e (patch) | |
tree | e4f2938afb74b73cf675507aa6a9ff12e3b9704c | |
parent | c09d4a5593c4d2ce6814dfd96b5da3368ec9b198 (diff) |
Touch: Multitouch pinch events
-rw-r--r-- | src/ui/touch.c | 226 |
1 files changed, 165 insertions, 61 deletions
diff --git a/src/ui/touch.c b/src/ui/touch.c index 88746bed..68c493dc 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -32,9 +32,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | # include "../ios.h" | 32 | # include "../ios.h" |
33 | #endif | 33 | #endif |
34 | 34 | ||
35 | iDeclareType(Momentum) | ||
36 | iDeclareType(Pinch) | ||
35 | iDeclareType(Touch) | 37 | iDeclareType(Touch) |
36 | iDeclareType(TouchState) | 38 | iDeclareType(TouchState) |
37 | iDeclareType(Momentum) | ||
38 | 39 | ||
39 | #define numHistory_Touch_ 5 | 40 | #define numHistory_Touch_ 5 |
40 | #define lastIndex_Touch_ (numHistory_Touch_ - 1) | 41 | #define lastIndex_Touch_ (numHistory_Touch_ - 1) |
@@ -59,6 +60,7 @@ struct Impl_Touch { | |||
59 | iBool isTapBegun; | 60 | iBool isTapBegun; |
60 | iBool isTouchDrag; | 61 | iBool isTouchDrag; |
61 | iBool isTapAndHold; | 62 | iBool isTapAndHold; |
63 | int pinchId; | ||
62 | enum iTouchEdge edge; | 64 | enum iTouchEdge edge; |
63 | uint32_t startTime; | 65 | uint32_t startTime; |
64 | iFloat3 startPos; | 66 | iFloat3 startPos; |
@@ -85,8 +87,15 @@ struct Impl_Momentum { | |||
85 | iFloat3 accum; | 87 | iFloat3 accum; |
86 | }; | 88 | }; |
87 | 89 | ||
90 | struct Impl_Pinch { | ||
91 | int id; | ||
92 | SDL_FingerID touchIds[2]; | ||
93 | iWidget *affinity; | ||
94 | }; | ||
95 | |||
88 | struct Impl_TouchState { | 96 | struct Impl_TouchState { |
89 | iArray *touches; | 97 | iArray *touches; |
98 | iArray *pinches; | ||
90 | iArray *moms; | 99 | iArray *moms; |
91 | double lastMomTime; | 100 | double lastMomTime; |
92 | }; | 101 | }; |
@@ -95,8 +104,9 @@ static iTouchState *touchState_(void) { | |||
95 | static iTouchState state_; | 104 | static iTouchState state_; |
96 | iTouchState *d = &state_; | 105 | iTouchState *d = &state_; |
97 | if (!d->touches) { | 106 | if (!d->touches) { |
98 | d->touches = new_Array(sizeof(iTouch)); | 107 | d->touches = new_Array(sizeof(iTouch)); |
99 | d->moms = new_Array(sizeof(iMomentum)); | 108 | d->pinches = new_Array(sizeof(iPinch)); |
109 | d->moms = new_Array(sizeof(iMomentum)); | ||
100 | d->lastMomTime = SDL_GetTicks(); | 110 | d->lastMomTime = SDL_GetTicks(); |
101 | } | 111 | } |
102 | return d; | 112 | return d; |
@@ -127,6 +137,16 @@ static iBool isStationary_Touch_(const iTouch *d) { | |||
127 | return isStationaryDistance_Touch_(d, tapRadiusPt_); | 137 | return isStationaryDistance_Touch_(d, tapRadiusPt_); |
128 | } | 138 | } |
129 | 139 | ||
140 | static void clearWidgetMomentum_TouchState_(iTouchState *d, iWidget *widget) { | ||
141 | if (!widget) return; | ||
142 | iForEach(Array, m, d->moms) { | ||
143 | iMomentum *mom = m.value; | ||
144 | if (mom->affinity == widget) { | ||
145 | remove_ArrayIterator(&m); | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | |||
130 | static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) { | 150 | static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) { |
131 | dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseMotionEvent){ | 151 | dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseMotionEvent){ |
132 | .type = SDL_MOUSEMOTION, | 152 | .type = SDL_MOUSEMOTION, |
@@ -163,14 +183,30 @@ static iBool dispatchClick_Touch_(const iTouch *d, int button) { | |||
163 | return wasUsed; | 183 | return wasUsed; |
164 | } | 184 | } |
165 | 185 | ||
166 | static void clearWidgetMomentum_TouchState_(iTouchState *d, iWidget *widget) { | 186 | static void dispatchButtonDown_Touch_(iFloat3 pos) { |
167 | if (!widget) return; | 187 | dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseButtonEvent){ |
168 | iForEach(Array, m, d->moms) { | 188 | .type = SDL_MOUSEBUTTONDOWN, |
169 | iMomentum *mom = m.value; | 189 | .timestamp = SDL_GetTicks(), |
170 | if (mom->affinity == widget) { | 190 | .clicks = 1, |
171 | remove_ArrayIterator(&m); | 191 | .state = SDL_PRESSED, |
172 | } | 192 | .which = SDL_TOUCH_MOUSEID, |
173 | } | 193 | .button = SDL_BUTTON_LEFT, |
194 | .x = x_F3(pos), | ||
195 | .y = y_F3(pos) | ||
196 | }); | ||
197 | } | ||
198 | |||
199 | static void dispatchButtonUp_Touch_(iFloat3 pos) { | ||
200 | dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseButtonEvent){ | ||
201 | .type = SDL_MOUSEBUTTONUP, | ||
202 | .timestamp = SDL_GetTicks(), | ||
203 | .clicks = 1, | ||
204 | .state = SDL_RELEASED, | ||
205 | .which = SDL_TOUCH_MOUSEID, | ||
206 | .button = SDL_BUTTON_LEFT, | ||
207 | .x = x_F3(pos), | ||
208 | .y = y_F3(pos) | ||
209 | }); | ||
174 | } | 210 | } |
175 | 211 | ||
176 | static void update_TouchState_(void *ptr) { | 212 | static void update_TouchState_(void *ptr) { |
@@ -179,6 +215,9 @@ static void update_TouchState_(void *ptr) { | |||
179 | /* Check for long presses to simulate right clicks. */ | 215 | /* Check for long presses to simulate right clicks. */ |
180 | iForEach(Array, i, d->touches) { | 216 | iForEach(Array, i, d->touches) { |
181 | iTouch *touch = i.value; | 217 | iTouch *touch = i.value; |
218 | if (touch->pinchId) { | ||
219 | continue; | ||
220 | } | ||
182 | /* Holding a touch will reset previous momentum for this widget. */ | 221 | /* Holding a touch will reset previous momentum for this widget. */ |
183 | if (isStationary_Touch_(touch)) { | 222 | if (isStationary_Touch_(touch)) { |
184 | const int elapsed = nowTime - touch->startTime; | 223 | const int elapsed = nowTime - touch->startTime; |
@@ -211,8 +250,8 @@ static void update_TouchState_(void *ptr) { | |||
211 | int numSteps = (int) (momAvailMs / stepDurationMs); | 250 | int numSteps = (int) (momAvailMs / stepDurationMs); |
212 | d->lastMomTime += numSteps * stepDurationMs; | 251 | d->lastMomTime += numSteps * stepDurationMs; |
213 | numSteps = iMin(numSteps, 10); /* don't spend too much time here */ | 252 | numSteps = iMin(numSteps, 10); /* don't spend too much time here */ |
214 | // printf("mom steps:%d\n", numSteps); | 253 | // printf("mom steps:%d\n", numSteps); |
215 | iWindow *window = get_Window(); | 254 | // iWindow *window = get_Window(); |
216 | iForEach(Array, m, d->moms) { | 255 | iForEach(Array, m, d->moms) { |
217 | if (numSteps == 0) break; | 256 | if (numSteps == 0) break; |
218 | iMomentum *mom = m.value; | 257 | iMomentum *mom = m.value; |
@@ -229,12 +268,12 @@ static void update_TouchState_(void *ptr) { | |||
229 | subv_F3(&mom->accum, initI2_F3(pixels)); | 268 | subv_F3(&mom->accum, initI2_F3(pixels)); |
230 | dispatchMotion_Touch_(mom->pos, 0); | 269 | dispatchMotion_Touch_(mom->pos, 0); |
231 | dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ | 270 | dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ |
232 | .type = SDL_MOUSEWHEEL, | 271 | .type = SDL_MOUSEWHEEL, |
233 | .timestamp = nowTime, | 272 | .timestamp = nowTime, |
234 | .x = pixels.x, | 273 | .x = pixels.x, |
235 | .y = pixels.y, | 274 | .y = pixels.y, |
236 | .direction = perPixel_MouseWheelFlag | 275 | .direction = perPixel_MouseWheelFlag |
237 | }); | 276 | }); |
238 | } | 277 | } |
239 | if (length_F3(mom->velocity) < minSpeed) { | 278 | if (length_F3(mom->velocity) < minSpeed) { |
240 | setHover_Widget(NULL); | 279 | setHover_Widget(NULL); |
@@ -248,48 +287,6 @@ static void update_TouchState_(void *ptr) { | |||
248 | } | 287 | } |
249 | } | 288 | } |
250 | 289 | ||
251 | void widgetDestroyed_Touch(iWidget *widget) { | ||
252 | iTouchState *d = touchState_(); | ||
253 | iForEach(Array, i, d->touches) { | ||
254 | iTouch *touch = i.value; | ||
255 | if (touch->affinity == widget) { | ||
256 | remove_ArrayIterator(&i); | ||
257 | } | ||
258 | } | ||
259 | iForEach(Array, m, d->moms) { | ||
260 | iMomentum *mom = m.value; | ||
261 | if (mom->affinity == widget) { | ||
262 | remove_ArrayIterator(&m); | ||
263 | } | ||
264 | } | ||
265 | } | ||
266 | |||
267 | static void dispatchButtonDown_Touch_(iFloat3 pos) { | ||
268 | dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseButtonEvent){ | ||
269 | .type = SDL_MOUSEBUTTONDOWN, | ||
270 | .timestamp = SDL_GetTicks(), | ||
271 | .clicks = 1, | ||
272 | .state = SDL_PRESSED, | ||
273 | .which = SDL_TOUCH_MOUSEID, | ||
274 | .button = SDL_BUTTON_LEFT, | ||
275 | .x = x_F3(pos), | ||
276 | .y = y_F3(pos) | ||
277 | }); | ||
278 | } | ||
279 | |||
280 | static void dispatchButtonUp_Touch_(iFloat3 pos) { | ||
281 | dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseButtonEvent){ | ||
282 | .type = SDL_MOUSEBUTTONUP, | ||
283 | .timestamp = SDL_GetTicks(), | ||
284 | .clicks = 1, | ||
285 | .state = SDL_RELEASED, | ||
286 | .which = SDL_TOUCH_MOUSEID, | ||
287 | .button = SDL_BUTTON_LEFT, | ||
288 | .x = x_F3(pos), | ||
289 | .y = y_F3(pos) | ||
290 | }); | ||
291 | } | ||
292 | |||
293 | static iWidget *findOverflowScrollable_Widget_(iWidget *d) { | 290 | static iWidget *findOverflowScrollable_Widget_(iWidget *d) { |
294 | const iInt2 rootSize = rootSize_Window(get_Window()); | 291 | const iInt2 rootSize = rootSize_Window(get_Window()); |
295 | for (iWidget *w = d; w; w = parent_Widget(w)) { | 292 | for (iWidget *w = d; w; w = parent_Widget(w)) { |
@@ -312,6 +309,81 @@ static iWidget *findSlidePanel_Widget_(iWidget *d) { | |||
312 | return NULL; | 309 | return NULL; |
313 | } | 310 | } |
314 | 311 | ||
312 | static void checkNewPinch_TouchState_(iTouchState *d, iTouch *newTouch) { | ||
313 | iWidget *affinity = newTouch->affinity; | ||
314 | if (!affinity) { | ||
315 | return; | ||
316 | } | ||
317 | iForEach(Array, i, d->touches) { | ||
318 | iTouch *other = i.value; | ||
319 | if (other->id == newTouch->id || other->pinchId || other->affinity != affinity) { | ||
320 | continue; | ||
321 | } | ||
322 | /* A second finger on the same widget. */ | ||
323 | iPinch pinch = { .affinity = affinity, .id = SDL_GetTicks() }; | ||
324 | pinch.touchIds[0] = newTouch->id; | ||
325 | pinch.touchIds[1] = other->id; | ||
326 | newTouch->pinchId = other->pinchId = pinch.id; | ||
327 | clearWidgetMomentum_TouchState_(d, affinity); | ||
328 | /* Remember current positions to determine pinch amount. */ | ||
329 | newTouch->startPos = newTouch->pos[0]; | ||
330 | other->startPos = other->pos[0]; | ||
331 | pushBack_Array(d->pinches, &pinch); | ||
332 | /*printf("[Touch] pinch %d starts with fingers %lld and %lld\n", pinch.id, | ||
333 | newTouch->id, other->id);*/ | ||
334 | postCommandf_App("pinch.began ptr:%p", affinity); | ||
335 | break; | ||
336 | } | ||
337 | } | ||
338 | |||
339 | static iPinch *findPinch_TouchState_(iTouchState *d, int pinchId) { | ||
340 | iForEach(Array, i, d->pinches) { | ||
341 | iPinch *pinch = i.value; | ||
342 | if (pinch->id == pinchId) { | ||
343 | return pinch; | ||
344 | } | ||
345 | } | ||
346 | return NULL; | ||
347 | } | ||
348 | |||
349 | static void pinchMotion_TouchState_(iTouchState *d, int pinchId) { | ||
350 | const iPinch *pinch = findPinch_TouchState_(d, pinchId); | ||
351 | iAssert(pinch != NULL); | ||
352 | if (!pinch) return; | ||
353 | const iTouch *touch[2] = { | ||
354 | find_TouchState_(d, pinch->touchIds[0]), | ||
355 | find_TouchState_(d, pinch->touchIds[1]) | ||
356 | }; | ||
357 | iAssert(pinch->affinity == touch[0]->affinity); | ||
358 | iAssert(pinch->affinity == touch[1]->affinity); | ||
359 | const float startDist = length_F3(sub_F3(touch[1]->startPos, touch[0]->startPos)); | ||
360 | if (startDist < gap_UI) { | ||
361 | return; | ||
362 | } | ||
363 | const float dist = length_F3(sub_F3(touch[1]->pos[0], touch[0]->pos[0])); | ||
364 | // printf("[Touch] pinch %d motion: relative %f\n", pinchId, dist / startDist); | ||
365 | postCommandf_App("pinch.moved arg:%f ptr:%p", dist / startDist, pinch->affinity); | ||
366 | } | ||
367 | |||
368 | static void endPinch_TouchState_(iTouchState *d, int pinchId) { | ||
369 | //printf("[Touch] pinch %d ends\n", pinchId); | ||
370 | iForEach(Array, i, d->pinches) { | ||
371 | iPinch *pinch = i.value; | ||
372 | if (pinch->id == pinchId) { | ||
373 | postCommandf_App("pinch.ended ptr:%p", pinch->affinity); | ||
374 | /* Cancel both touches. */ | ||
375 | iForEach(Array, j, d->touches) { | ||
376 | iTouch *touch = j.value; | ||
377 | if (touch->id == pinch->touchIds[0] || touch->id == pinch->touchIds[1]) { | ||
378 | remove_ArrayIterator(&j); | ||
379 | } | ||
380 | } | ||
381 | remove_ArrayIterator(&i); | ||
382 | break; | ||
383 | } | ||
384 | } | ||
385 | } | ||
386 | |||
315 | iBool processEvent_Touch(const SDL_Event *ev) { | 387 | iBool processEvent_Touch(const SDL_Event *ev) { |
316 | /* We only handle finger events here. */ | 388 | /* We only handle finger events here. */ |
317 | if (ev->type != SDL_FINGERDOWN && ev->type != SDL_FINGERMOTION && ev->type != SDL_FINGERUP) { | 389 | if (ev->type != SDL_FINGERDOWN && ev->type != SDL_FINGERMOTION && ev->type != SDL_FINGERUP) { |
@@ -367,6 +439,8 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
367 | if (flags_Widget(aff) & hover_WidgetFlag && ~flags_Widget(aff) & touchDrag_WidgetFlag) { | 439 | if (flags_Widget(aff) & hover_WidgetFlag && ~flags_Widget(aff) & touchDrag_WidgetFlag) { |
368 | setHover_Widget(aff); | 440 | setHover_Widget(aff); |
369 | } | 441 | } |
442 | /* This may begin a pinch. */ | ||
443 | checkNewPinch_TouchState_(d, back_Array(d->touches)); | ||
370 | addTicker_App(update_TouchState_, d); | 444 | addTicker_App(update_TouchState_, d); |
371 | } | 445 | } |
372 | else if (ev->type == SDL_FINGERMOTION) { | 446 | else if (ev->type == SDL_FINGERMOTION) { |
@@ -388,6 +462,10 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
388 | } | 462 | } |
389 | /* Update touch position. */ | 463 | /* Update touch position. */ |
390 | pushPos_Touch_(touch, pos, nowTime); | 464 | pushPos_Touch_(touch, pos, nowTime); |
465 | if (touch->pinchId) { | ||
466 | pinchMotion_TouchState_(d, touch->pinchId); | ||
467 | return iTrue; | ||
468 | } | ||
391 | if (!touch->isTouchDrag && !isStationary_Touch_(touch) && | 469 | if (!touch->isTouchDrag && !isStationary_Touch_(touch) && |
392 | flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { | 470 | flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { |
393 | touch->hasMoved = iTrue; | 471 | touch->hasMoved = iTrue; |
@@ -496,6 +574,10 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
496 | if (touch->id != fing->fingerId) { | 574 | if (touch->id != fing->fingerId) { |
497 | continue; | 575 | continue; |
498 | } | 576 | } |
577 | if (touch->pinchId) { | ||
578 | endPinch_TouchState_(d, touch->pinchId); | ||
579 | break; | ||
580 | } | ||
499 | if (touch->edgeDragging) { | 581 | if (touch->edgeDragging) { |
500 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); | 582 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); |
501 | } | 583 | } |
@@ -574,6 +656,28 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
574 | return iTrue; | 656 | return iTrue; |
575 | } | 657 | } |
576 | 658 | ||
659 | void widgetDestroyed_Touch(iWidget *widget) { | ||
660 | iTouchState *d = touchState_(); | ||
661 | iForEach(Array, i, d->touches) { | ||
662 | iTouch *touch = i.value; | ||
663 | if (touch->affinity == widget) { | ||
664 | remove_ArrayIterator(&i); | ||
665 | } | ||
666 | } | ||
667 | iForEach(Array, p, d->pinches) { | ||
668 | iPinch *pinch = i.value; | ||
669 | if (pinch->affinity == widget) { | ||
670 | remove_ArrayIterator(&p); | ||
671 | } | ||
672 | } | ||
673 | iForEach(Array, m, d->moms) { | ||
674 | iMomentum *mom = m.value; | ||
675 | if (mom->affinity == widget) { | ||
676 | remove_ArrayIterator(&m); | ||
677 | } | ||
678 | } | ||
679 | } | ||
680 | |||
577 | size_t numFingers_Touch(void) { | 681 | size_t numFingers_Touch(void) { |
578 | return size_Array(touchState_()->touches); | 682 | return size_Array(touchState_()->touches); |
579 | } | 683 | } |