summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJean-Philippe Andre <jp.andre@samsung.com>2015-09-16 16:40:56 +0900
committerJean-Philippe Andre <jp.andre@samsung.com>2015-09-18 17:28:44 +0900
commit1e7dd3cae9c6e012f55767b1d596340c1b7dc56f (patch)
treedf230a757143205fb3708ae8250dc4320d504f2e
parentcf3b9ba6fa9df3eae0f41f73e0ea1d44f8453624 (diff)
elm_gesture: Smoothen out x,y position when moving
Add smoothing and prediction algorithms to get smooth inputs for elm_gesture. Also, resample inputs at render frequency. Movement prediction is actually necessary as inputs and animators may not run in a synchronized manner. For instance if inputs happen at a frequency of 90Hz and the display is refreshed at 60fps, then each frame receives either 1 or 2 inputs. In that case, smoothing only can't prevent scroll jerkiness. @feature
-rw-r--r--src/lib/elm_gesture_layer.c342
1 files changed, 316 insertions, 26 deletions
diff --git a/src/lib/elm_gesture_layer.c b/src/lib/elm_gesture_layer.c
index 48f528553..be72fdfbd 100644
--- a/src/lib/elm_gesture_layer.c
+++ b/src/lib/elm_gesture_layer.c
@@ -80,6 +80,8 @@ _glayer_buf_dup(void *buf, size_t size)
80 if (!obj || !eo_isa(obj, MY_CLASS)) \ 80 if (!obj || !eo_isa(obj, MY_CLASS)) \
81 return 81 return
82 82
83#define POINTER_SMOOTH_CNT 63
84
83/** 85/**
84 * @internal 86 * @internal
85 * 87 *
@@ -91,10 +93,13 @@ _glayer_buf_dup(void *buf, size_t size)
91 */ 93 */
92struct _Pointer_Event 94struct _Pointer_Event
93{ 95{
94 Evas_Coord x, y; 96 Evas_Coord x, y; // smooth
95 unsigned int timestamp; 97 unsigned int timestamp;
96 int device; 98 int device;
97 Evas_Callback_Type event_type; 99 Evas_Callback_Type event_type;
100 Evas_Coord px[POINTER_SMOOTH_CNT + 1], py[POINTER_SMOOTH_CNT + 1]; // raw
101 unsigned int ptimestamp[POINTER_SMOOTH_CNT + 1];
102 int pcnt;
98}; 103};
99 104
100/** 105/**
@@ -388,6 +393,12 @@ struct _Elm_Gesture_Layer_Data
388 Ecore_Timer *gest_taps_timeout; /* When this expires, dbl 393 Ecore_Timer *gest_taps_timeout; /* When this expires, dbl
389 * click/taps ABORTed */ 394 * click/taps ABORTed */
390 395
396 Ecore_Animator *smooth_animator;
397 double timestamp_diff; // ms
398 double last_animator_tick; // ms
399 double last_move_timestamp; // ms
400 Eina_List *untouched; /* touched evices that just went UP */
401
391 Eina_Bool repeat_events : 1; 402 Eina_Bool repeat_events : 1;
392}; 403};
393 404
@@ -431,6 +442,237 @@ _touched_device_remove(Eina_List *list,
431 return list; 442 return list;
432} 443}
433 444
445static Pointer_Event *
446_touched_device_find(Elm_Gesture_Layer_Data *sd, const Pointer_Event *pe)
447{
448 return eina_list_search_unsorted(sd->touched, _device_compare, pe);
449}
450
451static void
452_smooth_move_record(Pointer_Event *p, const Pointer_Event *praw, Eina_Bool fake)
453{
454 /* praw is a MOVE event and p is either DOWN or MOVE */
455 //assert(p->device == praw->device);
456
457 if (!fake)
458 DBG("Gesture record_real [0/%d] %u %d %d\n", p->pcnt, praw->timestamp, praw->x, praw->y);
459 else
460 DBG("Gesture record_fake [0/%d] %u %d %d\n", p->pcnt, praw->timestamp, praw->x, praw->y);
461
462 if ((p->event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
463 (p->event_type == EVAS_CALLBACK_MULTI_DOWN))
464 {
465 /* 1st move, no smoothing yet */
466 p->pcnt = 1;
467 p->px[1] = p->x;
468 p->py[1] = p->y;
469 p->ptimestamp[1] = p->timestamp;
470 p->px[0] = praw->x;
471 p->py[0] = praw->y;
472 p->ptimestamp[0] = praw->timestamp;
473 p->event_type = praw->event_type;
474 p->x = praw->x;
475 p->y = praw->y;
476 p->timestamp = praw->timestamp;
477 return;
478 }
479
480 /* TODO */
481 if ((praw->event_type == EVAS_CALLBACK_MOUSE_UP) ||
482 (praw->event_type == EVAS_CALLBACK_MULTI_UP))
483 {
484 CRI("UP smoothing not implemented yet!");
485 return;
486 }
487
488 /* shift past records */
489 if (p->pcnt < POINTER_SMOOTH_CNT)
490 p->pcnt++;
491 for (int i = p->pcnt; i > 0; --i)
492 {
493 p->px[i] = p->px[i - 1];
494 p->py[i] = p->py[i - 1];
495 p->ptimestamp[i] = p->ptimestamp[i - 1];
496 }
497
498 /* insert new record */
499 p->px[0] = praw->x;
500 p->py[0] = praw->y;
501 p->ptimestamp[0] = praw->timestamp;
502}
503
504static double
505_smooth_move_velocity_weight(double factor)
506{
507 // TODO: test more weight functions
508 return 0.5 + 0.5 * cos(factor * M_PI);
509}
510
511static double
512_smooth_move_position_weight(double factor)
513{
514 /* y = (x-1)^2 */
515 double x = factor - 1;
516 return x * x;
517}
518
519#if 0
520static double
521_smooth_move_acceleration_weight(double factor)
522{
523 /* y = (2x-1)^4 */
524 if (factor > 0.5) return 0;
525 double x = factor - 1;
526 x *= x;
527 return x * x;
528}
529#endif
530
531static double
532_smooth_move_position_predict(double f, double avgx, double lastx, double v, double a EINA_UNUSED)
533{
534 /* TODO: predict velocity */
535 static double factor = -1.0;
536 double mixer = _elm_config->scroll_smooth_amount;
537 if (factor < 0.0)
538 {
539 const char *s = getenv("ELM_GESTURE_PREDICTION_AMOUNT");
540 factor = s ? atof(s) : 0.5;
541 if (factor < 0.0) factor = 0.0;
542 else if (factor > 1.0) factor = 1.0;
543 }
544 return (lastx * (1.0 - mixer)) + (avgx * mixer) + (v * f * factor);
545}
546
547static void
548_smooth_move_predict(Elm_Gesture_Layer_Data *sd, Pointer_Event *p)
549{
550 const double now = (ecore_loop_time_get() * 1000.0) + sd->timestamp_diff;
551 double x = 0, y = 0, vx = 0, vy = 0, accw_pos = 0, accw_velo = 0;
552 double w, f, ts, diffts, dts, mints, dt;
553 int i;
554
555 /* TODO: measure acceleration */
556
557 if (p->pcnt > 0)
558 {
559 //dts = now - p->ptimestamp[p->pcnt];
560 dts = _elm_config->scroll_smooth_time_window * 1000.0;
561 mints = now - dts;
562 for (i = 0; i < p->pcnt; i++)
563 {
564 ts = p->ptimestamp[i];
565 if (ts < mints) break;
566 if (ts > now) ts = now;
567
568 if (dts)
569 f = (now - ts) / dts;
570 else
571 f = 0.0;
572
573 /* position */
574 w = _smooth_move_position_weight(f);
575 x += w * p->px[i];
576 y += w * p->py[i];
577 accw_pos += w;
578
579 /* velocity */
580 if (i > 0)
581 {
582 dt = (double)p->ptimestamp[i - 1] - (double)p->ptimestamp[i];
583 if (dt > 0)
584 {
585 if (dts)
586 f = (p->ptimestamp[0] - ts) / dts;
587 else
588 f = 0.0;
589 w = _smooth_move_velocity_weight(f) / dt;
590 vx += w * (p->px[i - 1] - p->px[i]);
591 vy += w * (p->py[i - 1] - p->py[i]);
592 accw_velo += w;
593 }
594 }
595 }
596
597 if (accw_pos)
598 {
599 x /= accw_pos;
600 y /= accw_pos;
601 }
602
603 if (accw_velo)
604 {
605 vx /= accw_velo;
606 vy /= accw_velo;
607 }
608
609 diffts = (now - p->ptimestamp[0]) / dts;
610 }
611 else
612 {
613 x = p->px[0];
614 y = p->py[0];
615 diffts = 0.0;
616 }
617
618 p->timestamp = now;
619 p->x = _smooth_move_position_predict(diffts, x, p->px[0], vx, 0.0) + 0.5;
620 p->y = _smooth_move_position_predict(diffts, y, p->py[0], vy, 0.0) + 0.5;
621
622 DBG("Gesture prediction [0/%d] %u %d %d %d %d %d %d\n",
623 p->pcnt, p->timestamp, p->x, p->y, (int) x, (int) y, p->px[0], p->py[0]);
624}
625
626static Eina_Bool
627_smooth_animator_cb(void *data)
628{
629 Eina_Bool keep_anim = EINA_FALSE;
630 Pointer_Event *pe;
631 Eina_List *li;
632 double now, dts;
633
634 ELM_GESTURE_LAYER_DATA_GET(data, sd);
635
636 now = (ecore_loop_time_get() * 1000.0) + sd->timestamp_diff;
637 dts = _elm_config->scroll_smooth_time_window * 1000.0;
638
639 EINA_LIST_FOREACH(sd->touched, li, pe)
640 {
641 /* no physical move since last animator */
642 if (pe->ptimestamp[0] < sd->last_animator_tick)
643 {
644 Pointer_Event fake_pe = *pe;
645 fake_pe.timestamp = now;
646 fake_pe.x = pe->px[0];
647 fake_pe.y = pe->py[0];
648 _smooth_move_record(pe, &fake_pe, EINA_TRUE);
649 }
650
651 _smooth_move_predict(sd, pe);
652
653 if ((sd->last_move_timestamp + dts > now) &&
654 ((pe->x != pe->px[0]) || (pe->y != pe->py[0])))
655 {
656 /* still moving */
657 keep_anim = EINA_TRUE;
658 }
659
660 /* FIXME: +1 because of the mistake in the enum. */
661 Gesture_Info **gitr = sd->gesture + 1;
662 Tests_Array_Funcs *fitr = _glayer_tests_array + 1;
663 for (; fitr->test; fitr++, gitr++)
664 {
665 if (IS_TESTED_GESTURE(*gitr))
666 fitr->test(data, pe, NULL /* event_info */, pe->event_type, (*gitr)->g_type);
667 }
668 }
669
670 sd->last_animator_tick = now;
671 if (!keep_anim)
672 sd->smooth_animator = NULL;
673 return keep_anim;
674}
675
434/** 676/**
435 * @internal 677 * @internal
436 * 678 *
@@ -443,14 +685,16 @@ _touched_device_remove(Eina_List *list,
443 * @ingroup Elm_Gesture_Layer 685 * @ingroup Elm_Gesture_Layer
444 */ 686 */
445static Eina_List * 687static Eina_List *
446_touched_device_add(Eina_List *list, 688_touched_device_add(void *data,
689 Eina_List *list,
447 Pointer_Event *pe) 690 Pointer_Event *pe)
448{ 691{
449 Pointer_Event *p = eina_list_search_unsorted(list, _device_compare, pe); 692 Pointer_Event *p = eina_list_search_unsorted(list, _device_compare, pe);
450 693
451 if (p) /* We like to track device touch-position, overwrite info */ 694 if (p) /* We like to track device touch-position, overwrite info */
452 { 695 {
453 memcpy(p, pe, sizeof(Pointer_Event)); 696 memcpy(p, pe, sizeof(*p));
697 p->pcnt = 0;
454 return list; 698 return list;
455 } 699 }
456 700
@@ -459,9 +703,13 @@ _touched_device_add(Eina_List *list,
459 * device on DOWN 703 * device on DOWN
460 * event only */ 704 * event only */
461 { 705 {
706 ELM_GESTURE_LAYER_DATA_GET(data, sd);
462 p = malloc(sizeof(Pointer_Event)); 707 p = malloc(sizeof(Pointer_Event));
463 /* Freed in _touched_device_remove() */ 708 /* Freed in _touched_device_remove() */
464 memcpy(p, pe, sizeof(Pointer_Event)); 709 memcpy(p, pe, sizeof(Pointer_Event));
710 p->pcnt = 0;
711 sd->timestamp_diff =
712 (ecore_loop_time_get() * 1000.0) - ((double)pe->timestamp);
465 return eina_list_append(list, p); 713 return eina_list_append(list, p);
466 } 714 }
467 715
@@ -1342,6 +1590,7 @@ _event_process(void *data,
1342{ 1590{
1343 Pointer_Event _pe; 1591 Pointer_Event _pe;
1344 Pointer_Event *pe = NULL; 1592 Pointer_Event *pe = NULL;
1593 Pointer_Event *pe_down = NULL;
1345 1594
1346 ELM_GESTURE_LAYER_DATA_GET(data, sd); 1595 ELM_GESTURE_LAYER_DATA_GET(data, sd);
1347 1596
@@ -1349,34 +1598,75 @@ _event_process(void *data,
1349 if (_pointer_event_make(data, event_info, event_type, &_pe)) 1598 if (_pointer_event_make(data, event_info, event_type, &_pe))
1350 pe = &_pe; 1599 pe = &_pe;
1351 1600
1601 /* Smoothing and predicting inputs, resampling at animator rate */
1602 /* FIXME: Use specific variables for gestures (?) */
1603 if (_elm_config->scroll_smooth_start_enable &&
1604 _elm_config->scroll_smooth_amount &&
1605 _elm_config->scroll_smooth_time_window)
1606 {
1607 /* smooth input */
1608 switch (event_type)
1609 {
1610 case EVAS_CALLBACK_MOUSE_DOWN:
1611 case EVAS_CALLBACK_MULTI_DOWN:
1612 /* nothing to smoothen yet */
1613 break;
1614 case EVAS_CALLBACK_MOUSE_MOVE:
1615 case EVAS_CALLBACK_MULTI_MOVE:
1616 /* find if device is down */
1617 pe_down = _touched_device_find(sd, pe);
1618 if (!pe_down) break;
1619 /* queue move event with exact timestamp and position */
1620 _smooth_move_record(pe_down, pe, EINA_FALSE);
1621 sd->last_move_timestamp = (double) pe->timestamp;
1622 if (!sd->smooth_animator)
1623 sd->smooth_animator = ecore_animator_add(_smooth_animator_cb, data);
1624 break;
1625 case EVAS_CALLBACK_MOUSE_UP:
1626 case EVAS_CALLBACK_MULTI_UP:
1627 /* for now, send UP immediately, even if smooth move is late */
1628 break;
1629 default: break;
1630 }
1631 }
1632
1352 /* Test all the gestures */ 1633 /* Test all the gestures */
1353 { 1634 if (!pe_down)
1354 /* FIXME: +1 because of the mistake in the enum. */ 1635 {
1355 Gesture_Info **gitr = sd->gesture + 1; 1636 /* FIXME: +1 because of the mistake in the enum. */
1356 Tests_Array_Funcs *fitr = _glayer_tests_array + 1; 1637 Gesture_Info **gitr = sd->gesture + 1;
1357 for (; fitr->test; fitr++, gitr++) 1638 Tests_Array_Funcs *fitr = _glayer_tests_array + 1;
1358 { 1639
1359 if (IS_TESTED_GESTURE(*gitr)) 1640 for (; fitr->test; fitr++, gitr++)
1360 fitr->test(data, pe, event_info, event_type, (*gitr)->g_type); 1641 {
1361 } 1642 if (IS_TESTED_GESTURE(*gitr))
1362 } 1643 fitr->test(data, pe, event_info, event_type, (*gitr)->g_type);
1644 }
1645 }
1363 1646
1364 if (_event_flag_get(event_info, event_type) & EVAS_EVENT_FLAG_ON_HOLD) 1647 if (_event_flag_get(event_info, event_type) & EVAS_EVENT_FLAG_ON_HOLD)
1365 _event_history_add(data, event_info, event_type); 1648 _event_history_add(data, event_info, event_type);
1366 1649
1367 /* we maintain list of touched devices */ 1650 if (!pe_down)
1368 /* We also use move to track current device x.y pos */
1369 if ((event_type == EVAS_CALLBACK_MOUSE_DOWN) ||
1370 (event_type == EVAS_CALLBACK_MULTI_DOWN) ||
1371 (event_type == EVAS_CALLBACK_MOUSE_MOVE) ||
1372 (event_type == EVAS_CALLBACK_MULTI_MOVE))
1373 { 1651 {
1374 sd->touched = _touched_device_add(sd->touched, pe); 1652 /* we maintain list of touched devices */
1375 } 1653 /* We also use move to track current device x.y pos */
1376 else if ((event_type == EVAS_CALLBACK_MOUSE_UP) || 1654 switch (event_type)
1377 (event_type == EVAS_CALLBACK_MULTI_UP)) 1655 {
1378 { 1656 case EVAS_CALLBACK_MOUSE_DOWN:
1379 sd->touched = _touched_device_remove(sd->touched, pe); 1657 case EVAS_CALLBACK_MULTI_DOWN:
1658 case EVAS_CALLBACK_MOUSE_MOVE:
1659 case EVAS_CALLBACK_MULTI_MOVE:
1660 sd->touched = _touched_device_add(data, sd->touched, pe);
1661 break;
1662 case EVAS_CALLBACK_MOUSE_UP:
1663 case EVAS_CALLBACK_MULTI_UP:
1664 sd->touched = _touched_device_remove(sd->touched, pe);
1665 if (!sd->touched)
1666 ELM_SAFE_DEL(sd->smooth_animator);
1667 break;
1668 default: break;
1669 }
1380 } 1670 }
1381 1671
1382 /* Report current states and clear history if needed */ 1672 /* Report current states and clear history if needed */
@@ -2102,7 +2392,7 @@ _n_long_tap_test(Evas_Object *obj,
2102 { 2392 {
2103 case EVAS_CALLBACK_MULTI_DOWN: 2393 case EVAS_CALLBACK_MULTI_DOWN:
2104 case EVAS_CALLBACK_MOUSE_DOWN: 2394 case EVAS_CALLBACK_MOUSE_DOWN:
2105 st->touched = _touched_device_add(st->touched, pe); 2395 st->touched = _touched_device_add(obj, st->touched, pe);
2106 st->info.n = eina_list_count(st->touched); 2396 st->info.n = eina_list_count(st->touched);
2107 2397
2108 _event_consume(sd, event_info, event_type, ev_flag); 2398 _event_consume(sd, event_info, event_type, ev_flag);