#ifdef HAVE_CONFIG_H
# include "elementary_config.h"
#endif

#define EFL_UI_SCROLL_MANAGER_PROTECTED
#define EFL_UI_SCROLLBAR_PROTECTED

#include <Elementary.h>
#include "elm_priv.h"
#include "efl_ui_widget_scroll_manager.h"

#define MY_CLASS EFL_UI_SCROLL_MANAGER_CLASS
#define MY_CLASS_NAME "Efl.Ui.Scroll.Manager"

#define ELM_ANIMATOR_CONNECT(Obj, Bool, Callback, Data)                 \
  efl_event_callback_del(Obj, EFL_CANVAS_OBJECT_EVENT_ANIMATOR_TICK, Callback, Data); \
  efl_event_callback_add(Obj, EFL_CANVAS_OBJECT_EVENT_ANIMATOR_TICK, Callback, Data); \
  Bool = 1;

#define ELM_ANIMATOR_DISCONNECT(Obj, Bool, Callback, Data)              \
  efl_event_callback_del(Obj, EFL_CANVAS_OBJECT_EVENT_ANIMATOR_TICK, Callback, Data); \
  Bool = 0;


static double
_scroll_manager_linear_interp(void *data EINA_UNUSED, double progress)
{
   return progress;
}
static double
_scroll_manager_accel_interp(void *data EINA_UNUSED, double progress)
{
   return progress * progress;
}
static double
_scroll_manager_decel_interp(void *data EINA_UNUSED, double progress)
{
   return (1.0 - (1.0 - progress) * (1.0 - progress));
}

static Interpolator
_scroll_manager_interp_get(InterpType interp)
{
   if (interp == INTERP_ACCEL)
     return _scroll_manager_accel_interp;
   else if (interp == INTERP_DECEL)
     return _scroll_manager_decel_interp;
   return _scroll_manager_linear_interp;
}

// Prototypes --- //

// ANIMATORS - tick function
static void _efl_ui_scroll_manager_hold_animator(void *data, const Efl_Event *event);
static void _efl_ui_scroll_manager_on_hold_animator(void *data, const Efl_Event *event);
static void _efl_ui_scroll_manager_scroll_to_y_animator(void *data, const Efl_Event *event);
static void _efl_ui_scroll_manager_scroll_to_x_animator(void *data, const Efl_Event *event);
static void _efl_ui_scroll_manager_bounce_y_animator(void *data, const Efl_Event *event);
static void _efl_ui_scroll_manager_bounce_x_animator(void *data, const Efl_Event *event);

// ANIMATORS - manipulate function
static void _scroll_manager_hold_animator_add(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord x, Evas_Coord y);
static Eina_Bool _scroll_manager_hold_animator_del(Efl_Ui_Scroll_Manager_Data *sd);

static void _scroll_manager_on_hold_animator_add(Efl_Ui_Scroll_Manager_Data *sd, double vx, double vy);
static Eina_Bool _scroll_manager_on_hold_animator_del(Efl_Ui_Scroll_Manager_Data *sd);

/// Constant scrolling
static void _scroll_manager_scrollto_animator_add(Efl_Ui_Scroll_Manager_Data *sd,  Evas_Coord cx, Evas_Coord cy, Evas_Coord x, Evas_Coord y, double tx, double ty, InterpType interp);
static Eina_Bool _scroll_manager_scrollto_animator_del(Efl_Ui_Scroll_Manager_Data *sd);
static void _scroll_manager_scrollto_x_animator_add(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord cx, Evas_Coord x, double t, InterpType interp);
static Eina_Bool _scroll_manager_scrollto_x_animator_del(Efl_Ui_Scroll_Manager_Data *sd);
static void _scroll_manager_scrollto_y_animator_add(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord cy, Evas_Coord y, double t, InterpType interp);
static Eina_Bool _scroll_manager_scrollto_y_animator_del(Efl_Ui_Scroll_Manager_Data *sd);

/// Flicking
static void _scroll_manager_momentum_animator_add(Efl_Ui_Scroll_Manager_Data *sd, double vx, double vy);

// Bounce
static void _scroll_manager_bounce_x_animator_add(Efl_Ui_Scroll_Manager_Data *sd, double vx);
static Eina_Bool _scroll_manager_bounce_x_animator_del(Efl_Ui_Scroll_Manager_Data *sd);
static void _scroll_manager_bounce_y_animator_add(Efl_Ui_Scroll_Manager_Data *sd, double vy);
static Eina_Bool _scroll_manager_bounce_y_animator_del(Efl_Ui_Scroll_Manager_Data *sd);

// Util
static void _scroll_manager_scrollto(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord x, Evas_Coord y);
static void _scroll_manager_animators_drop(Evas_Object *obj);

// ETC
static void _efl_ui_scroll_manager_wanted_coordinates_update(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord x,Evas_Coord y);
// --- Prototypes //

static inline double
_round(double value, int pos)
{
   double temp;

   temp = value * pow( 10, pos );
   temp = floor( temp + 0.5 );
   temp *= pow( 10, -pos );

   return temp;
}

#define EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(o, ptr)     \
  Efl_Ui_Scroll_Manager_Data *ptr =            \
    (!efl_isa(o, MY_CLASS) ? NULL :       \
     efl_data_scope_safe_get(o, MY_CLASS));    \
  if (!ptr)                                             \
    {                                                   \
       CRI("No interface data for object %p (%s)", \
                o, evas_object_type_get(o));            \
       return;                                          \
    }

#define EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN_VAL(o, ptr, val) \
  Efl_Ui_Scroll_Manager_Data *ptr =                 \
    (!efl_isa(o, MY_CLASS) ? NULL :            \
     efl_data_scope_safe_get(o, MY_CLASS));         \
  if (!ptr)                                                  \
    {                                                        \
       CRI("No interface data for object %p (%s)",      \
                o, evas_object_type_get(o));                 \
       return val;                                           \
    }

static void _efl_ui_scroll_manager_wanted_region_set(Evas_Object *obj);

#define LEFT               0
#define RIGHT              1
#define UP                 2
#define DOWN               3
//#define SCROLLDBG 1
/* smoothness debug calls - for debugging how much smooth your app is */

static inline Eina_Bool
_scroll_manager_thumb_scrollable_get(Efl_Ui_Scroll_Manager_Data *sd)
{
   if (!sd) return EINA_FALSE;
   if ((sd->block & EFL_UI_LAYOUT_ORIENTATION_VERTICAL) &&
       (sd->block & EFL_UI_LAYOUT_ORIENTATION_HORIZONTAL))
     return EINA_FALSE;

   if (!_elm_config->thumbscroll_enable) return EINA_FALSE;

   return EINA_TRUE;
}

static inline Eina_Bool
_scroll_manager_animating_get(Efl_Ui_Scroll_Manager_Data *sd)
{
   if (!sd) return EINA_FALSE;
   return ((sd->bounce.x.animator) || (sd->bounce.y.animator) ||
           (sd->scrollto.x.animator) || (sd->scrollto.y.animator));
}

static void
_efl_ui_scroll_manager_scroll_start(Efl_Ui_Scroll_Manager_Data *sd)
{
   sd->scrolling = EINA_TRUE;
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_STARTED, NULL);
}

static void
_efl_ui_scroll_manager_scroll_stop(Efl_Ui_Scroll_Manager_Data *sd)
{
   sd->scrolling = EINA_FALSE;
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_FINISHED, NULL);
}

static void
_efl_ui_scroll_manager_drag_start(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_DRAG_STARTED, NULL);
   if (!sd->scrolling)
     _efl_ui_scroll_manager_scroll_start(sd);
}

static void
_efl_ui_scroll_manager_drag_stop(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_DRAG_FINISHED, NULL);
}

static void
_efl_ui_scroll_manager_anim_start(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_ANIM_STARTED, NULL);
   if (!sd->scrolling)
     _efl_ui_scroll_manager_scroll_start(sd);
}

static void
_efl_ui_scroll_manager_anim_stop(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_ANIM_FINISHED, NULL);
   if (sd->scrolling)
     _efl_ui_scroll_manager_scroll_stop(sd);
}

static void
_efl_ui_scroll_manager_scroll(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_CHANGED, NULL);
}

static void
_efl_ui_scroll_manager_scroll_up(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_UP, NULL);
}

static void
_efl_ui_scroll_manager_scroll_down(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_DOWN, NULL);
}

static void
_efl_ui_scroll_manager_scroll_left(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_LEFT, NULL);
}

static void
_efl_ui_scroll_manager_scroll_right(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_SCROLL_RIGHT, NULL);
}

static void
_efl_ui_scroll_manager_edge_up(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_EDGE_UP, NULL);
}

static void
_efl_ui_scroll_manager_edge_down(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_EDGE_DOWN, NULL);
}

static void
_efl_ui_scroll_manager_edge_left(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_EDGE_LEFT, NULL);
}

static void
_efl_ui_scroll_manager_edge_right(Efl_Ui_Scroll_Manager_Data *sd)
{
   efl_event_callback_call(sd->parent, EFL_UI_EVENT_EDGE_RIGHT, NULL);
}

EOLIAN static Eina_Size2D
_efl_ui_scroll_manager_efl_ui_scrollable_content_size_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd)
{
   return efl_ui_pan_content_size_get(sd->pan_obj);
}

EOLIAN static Eina_Rect
_efl_ui_scroll_manager_efl_ui_scrollable_viewport_geometry_get(const Eo *obj EINA_UNUSED,
                                                                       Efl_Ui_Scroll_Manager_Data *sd)
{
   if (!sd->pan_obj) return EINA_RECT(0, 0, 0, 0);

   return efl_gfx_entity_geometry_get(sd->pan_obj);
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_match_content_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool w, Eina_Bool h)
{
   sd->match_content_w = !!w;
   sd->match_content_h = !!h;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_step_size_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Position2D step)
{
   sd->step.x = step.x * elm_config_scale_get();
   sd->step.y = step.y * elm_config_scale_get();
}

EOLIAN static Eina_Position2D
_efl_ui_scroll_manager_efl_ui_scrollable_step_size_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd)
{
   return EINA_POSITION2D(sd->step.x, sd->step.y);
}

static Evas_Coord
_efl_ui_scroll_manager_x_mirrored_get(const Evas_Object *obj,
                           Evas_Coord x)
{
   Evas_Coord ret;
   Eina_Position2D min = {0, 0}, max = {0, 0};

   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN_VAL(obj, sd, x);

   if (!sd->pan_obj) return 0;

   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);
   ret = max.x - (x - min.x);

   return (ret >= min.x) ? ret : min.x;
}

/* Update the wanted coordinates according to the x, y passed
 * widget directionality, content size and etc. */
static void
_efl_ui_scroll_manager_wanted_coordinates_update(Efl_Ui_Scroll_Manager_Data *sd,
                                      Evas_Coord x,
                                      Evas_Coord y)
{
   Eina_Position2D min = {0, 0}, max = {0, 0};

   if (!sd->pan_obj) return;

   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);

   /* Update wx/y/w/h - and if the requested positions aren't legal
    * adjust a bit. */
   Eina_Rect r = efl_ui_scrollable_viewport_geometry_get(sd->obj);
   sd->ww = r.w;
   sd->wh = r.h;

   if (x < min.x && !sd->is_mirrored)
     {
        if (!sd->loop_h) sd->wx = min.x;
        else sd->wx = max.x;
     }
   else if (sd->is_mirrored)
     sd->wx = _efl_ui_scroll_manager_x_mirrored_get(sd->obj, x);
   else if (!sd->loop_h && (x > max.x)) sd->wx = max.x;
   else if (sd->loop_h && x >= (sd->ww + max.x)) sd->wx = min.x;
   else sd->wx = x;

   if (y < min.y)
     {
        if (!sd->loop_v) sd->wy = min.y;
        else sd->wy = max.y;
     }
   else if (!sd->loop_v && (y > max.y)) sd->wy = max.y;
   else if (sd->loop_v && y >= (sd->wh + max.y)) sd->wy = min.y;
   else sd->wy = y;
}

static void
_scroll_manager_animator_velocity_get(Efl_Ui_Scroll_Manager_Data *sd, double *velx, double *vely)
{
   Evas_Coord dx = 0, dy = 0;
   double vx = 0.0, vy = 0.0;
   double t = ecore_loop_time_get();
   Eina_Position2D cur = efl_ui_pan_position_get(sd->pan_obj);

   if (t < sd->scrollto.x.start_t + sd->scrollto.x.dur)
     {
        dx = sd->scrollto.x.end - cur.x;
        vx = (double)(dx /((sd->scrollto.x.start_t + sd->scrollto.x.dur) - t));

     if (sd->scrollto.x.interp)
         vx = sd->scrollto.x.interp(NULL, t/(sd->scrollto.x.start_t + sd->scrollto.x.dur)) * vx;
     }
   if (t < sd->scrollto.y.start_t + sd->scrollto.y.dur)
     {
        dy = sd->scrollto.y.end - cur.y;
        vy = (double)(dy /((sd->scrollto.y.start_t + sd->scrollto.y.dur) - t));

        if (sd->scrollto.y.interp)
         vy = sd->scrollto.y.interp(NULL, t/(sd->scrollto.y.start_t + sd->scrollto.y.dur)) * vy;
     }

   if (velx) *velx = vx;
   if (vely) *vely = vy;
}

static void
_efl_ui_scroll_manager_bounce_eval(Efl_Ui_Scroll_Manager_Data *sd)
{
   double vx = 0.0, vy = 0.0;
   if (!sd->pan_obj) return;

   if (sd->freeze) return;
   if ((!sd->bouncemex) && (!sd->bouncemey)) return;
   if (sd->down.now) return;  // down bounce while still held down

   _scroll_manager_on_hold_animator_del(sd);
   _scroll_manager_hold_animator_del(sd);

   _scroll_manager_animator_velocity_get(sd, &vx, &vy);
   if (!sd->bounce.x.animator)
     {
        if (sd->bouncemex)
          {
             _scroll_manager_scrollto_x_animator_del(sd);
             _scroll_manager_bounce_x_animator_add(sd,vx);
          }
     }
   if (!sd->bounce.y.animator)
     {
        if (sd->bouncemey)
          {
             _scroll_manager_scrollto_y_animator_del(sd);
             _scroll_manager_bounce_y_animator_add(sd,vy);
          }
     }
}

EOLIAN static Eina_Position2D
_efl_ui_scroll_manager_efl_ui_scrollable_content_pos_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd)
{
   if (!sd->pan_obj) return EINA_POSITION2D(0, 0);

   return efl_ui_pan_position_get(sd->pan_obj);
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_content_pos_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Position2D pos)
{
   Evas_Coord x = pos.x, y = pos.y;
   Eina_Position2D min = {0, 0}, max = {0, 0}, cur = {0, 0};
   Eina_Size2D content = {0, 0};

   if (!sd->pan_obj) return;

   // FIXME: allow for bounce outsde of range
   max = efl_ui_pan_position_max_get(sd->pan_obj);
   min = efl_ui_pan_position_min_get(sd->pan_obj);
   content = efl_ui_pan_content_size_get(sd->pan_obj);
   cur = efl_ui_pan_position_get(sd->pan_obj);

   if (sd->loop_h && content.w > 0)
     {
        if (x < 0) x = content.w + (x % content.w);
        else if (x >= content.w) x = (x % content.w);
     }
   if (sd->loop_v && content.h > 0)
     {
        if (y < 0) y = content.h + (y % content.h);
        else if (y >= content.h) y = (y % content.h);
     }

   if (!_elm_config->thumbscroll_bounce_enable)
     {

        if (x < min.x) x = min.x;
        if (!sd->loop_h && (x - min.x) > max.x) x = max.x + min.x;
        if (y < min.y) y = min.y;
        if (!sd->loop_v && (y - min.y) > max.y) y = max.y + min.y;
     }

   if (!sd->bounce_horiz)
     {
        if (x < min.x) x = min.x;
        if (!sd->loop_h && (x - min.x) > max.x) x = max.x + min.x;
     }
   if (!sd->bounce_vert)
     {
        if (y < min.y) y = min.y;
        if (!sd->loop_v && (y - min.y) > max.y) y = max.y + min.y;
     }

   efl_ui_pan_position_set(sd->pan_obj, EINA_POSITION2D(x, y));

   if (!sd->loop_h && !sd->bounce.x.animator)
     {
        if ((x < min.x) ||(x > max.x + min.x))
          {
             sd->bouncemex = EINA_TRUE;
             _efl_ui_scroll_manager_bounce_eval(sd);
          }
        else
          sd->bouncemex = EINA_FALSE;
     }
   if (!sd->loop_v && !sd->bounce.y.animator)
     {
        if ((y < min.y) ||(y > max.y + min.y))
          {
             sd->bouncemey = EINA_TRUE;
             _efl_ui_scroll_manager_bounce_eval(sd);
          }
        else
          sd->bouncemey = EINA_FALSE;
     }

     {
        if ((x != cur.x) || (y != cur.y))
          {
             _efl_ui_scroll_manager_scroll(sd);
             if (x < cur.x)
               {
                  _efl_ui_scroll_manager_scroll_left(sd);
               }
             if (x > cur.x)
               {
                  _efl_ui_scroll_manager_scroll_right(sd);
               }
             if (y < cur.y)
               {
                  _efl_ui_scroll_manager_scroll_up(sd);
               }
             if (y > cur.y)
               {
                  _efl_ui_scroll_manager_scroll_down(sd);
               }
          }
        if (x != cur.x)
          {
             if (x == min.x)
               {
                  _efl_ui_scroll_manager_edge_left(sd);
               }
             if (x == (max.x + min.x))
               {
                  _efl_ui_scroll_manager_edge_right(sd);
               }
          }
        if (y != cur.y)
          {
             if (y == min.y)
               {
                  _efl_ui_scroll_manager_edge_up(sd);
               }
             if (y == max.y + min.y)
               {
                  _efl_ui_scroll_manager_edge_down(sd);
               }
          }
     }
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_i18n_mirrored_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool mirrored)
{
   Evas_Coord wx;

   mirrored = !!mirrored;

   if (sd->is_mirrored == mirrored)
     return;

   sd->is_mirrored = mirrored;

   if (sd->is_mirrored)
     wx = _efl_ui_scroll_manager_x_mirrored_get(sd->obj, sd->wx);
   else
     wx = sd->wx;

   efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(wx, sd->wy));
}

static void
_scroll_manager_animators_drop(Evas_Object *obj)
{
   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(obj, sd);
   if ((sd->bounce.x.animator) || (sd->bounce.y.animator) ||
       (sd->scrollto.x.animator) || (sd->scrollto.y.animator))
     {
        if (_scroll_manager_scrollto_x_animator_del(sd))
          {
          }
        if (_scroll_manager_scrollto_y_animator_del(sd))
          {
          }
        if (_scroll_manager_bounce_x_animator_del(sd))
          {
             sd->bouncemex = EINA_FALSE;
             if (sd->content_info.resized)
               _efl_ui_scroll_manager_wanted_region_set(sd->obj);
          }
        if (_scroll_manager_bounce_y_animator_del(sd))
         {
            sd->bouncemey = EINA_FALSE;
            if (sd->content_info.resized)
              _efl_ui_scroll_manager_wanted_region_set(sd->obj);
         }
        _efl_ui_scroll_manager_anim_stop(sd);
    }
  if (_scroll_manager_hold_animator_del(sd))
    {
        _efl_ui_scroll_manager_drag_stop(sd);
        if (sd->content_info.resized)
          _efl_ui_scroll_manager_wanted_region_set(sd->obj);
    }
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollbar_bar_mode_set(Eo *obj EINA_UNUSED,
                                                     Efl_Ui_Scroll_Manager_Data *sd,
                                                     Efl_Ui_Scrollbar_Mode hmode,
                                                     Efl_Ui_Scrollbar_Mode vmode)
{
   sd->hbar_mode = hmode;
   sd->vbar_mode = vmode;

   if (sd->hbar_timer &&
       hmode == EFL_UI_SCROLLBAR_MODE_ON)
     ELM_SAFE_FREE(sd->hbar_timer, ecore_timer_del);
   if (sd->vbar_timer &&
       vmode == EFL_UI_SCROLLBAR_MODE_ON)
     ELM_SAFE_FREE(sd->vbar_timer, ecore_timer_del);

   efl_ui_scrollbar_bar_visibility_update(sd->obj);
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollbar_bar_mode_get(const Eo *obj EINA_UNUSED,
                                                     Efl_Ui_Scroll_Manager_Data *sd,
                                                     Efl_Ui_Scrollbar_Mode *hmode,
                                                     Efl_Ui_Scrollbar_Mode *vmode)
{
    if (hmode) *hmode = sd->hbar_mode;
    if (vmode) *vmode = sd->vbar_mode;
}

/* returns TRUE when we need to move the scroller, FALSE otherwise.
 * Updates w and h either way, so save them if you need them. */
static Eina_Bool
_efl_ui_scroll_manager_content_region_show_internal(Evas_Object *obj,
                                                    Evas_Coord *_x,
                                                    Evas_Coord *_y,
                                                    Evas_Coord w,
                                                    Evas_Coord h)
{
   Evas_Coord  nx, ny, x = *_x, y = *_y;
   Eina_Position2D min = {0, 0}, max = {0, 0}, cur = {0, 0};
   Eina_Size2D  pan = {0, 0};

   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN_VAL(obj, sd, EINA_FALSE);

   if (!sd->pan_obj) return EINA_FALSE;

   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);
   cur = efl_ui_pan_position_get(sd->pan_obj);
   pan = efl_gfx_entity_size_get(sd->pan_obj);

   nx = x;
   if ((x > cur.x) && (w < pan.w))
     {
        if ((cur.x + pan.w) < (x + w)) nx = x - pan.w + w;
        else nx = cur.x;
     }
   ny = y;
   if ((y > cur.y) && (h < pan.h))
     {
        if ((cur.y + pan.h) < (y + h)) ny = y - pan.h + h;
        else ny = cur.y;
     }

   x = nx;
   y = ny;

   if (!sd->loop_h)
     {
        if (x > max.x) x = max.x;
        if (x < min.x) x = min.x;
     }
   if (!sd->loop_v)
     {
        if (y > max.y) y = max.y;
        if (y < min.y) y = min.y;
     }

   if ((x == cur.x) && (y == cur.y)) return EINA_FALSE;
   *_x = x;
   *_y = y;
   return EINA_TRUE;
}

static void
_efl_ui_scroll_manager_content_region_set(Eo *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(obj, sd);

   _scroll_manager_animators_drop(obj);
   if (_efl_ui_scroll_manager_content_region_show_internal(obj, &x, &y, w, h))
     {
        efl_ui_scrollable_content_pos_set(obj, EINA_POSITION2D(x, y));
        sd->down.sx = x;
        sd->down.sy = y;
        sd->down.x = sd->down.history[0].x;
        sd->down.y = sd->down.history[0].y;
     }
}

static void
_efl_ui_scroll_manager_wanted_region_set(Evas_Object *obj)
{
   Evas_Coord ww, wh, wx;
   Eina_Position2D max = {0, 0};

   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(obj, sd);

   wx = sd->wx;

   if (_scroll_manager_animating_get(sd) || sd->down.now ||
       sd->down.hold_animator || sd->down.onhold_animator) return;

   sd->content_info.resized = EINA_FALSE;

   /* Flip to RTL cords only if init in RTL mode */
   if (sd->is_mirrored)
     wx = _efl_ui_scroll_manager_x_mirrored_get(obj, sd->wx);

   if (sd->ww == -1)
     {
        Eina_Rect r = efl_ui_scrollable_viewport_geometry_get(sd->obj);
        ww = r.w;
        wh = r.h;
     }
   else
     {
        ww = sd->ww;
        wh = sd->wh;
     }

   max = efl_ui_pan_position_max_get(sd->pan_obj);

   wx += (max.x - sd->prev_cw) * sd->gravity_x;
   sd->wy += (max.y - sd->prev_ch) * sd->gravity_y;

   sd->prev_cw = max.x;
   sd->prev_ch = max.y;

   _efl_ui_scroll_manager_content_region_set(obj, wx, sd->wy, ww, wh);
}

static Eina_Value
_scroll_wheel_post_event_job(void *data, const Eina_Value v,
                             const Eina_Future *ev EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;

   // Animations are disabled if we are here
   efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(sd->wx, sd->wy));

   return v;
}

static inline void
_scroll_wheel_post_event_go(Efl_Ui_Scroll_Manager_Data *sd, int x, int y)
{
   Eina_Position2D cur;
   if (sd->hold || sd->freeze) return;
   _efl_ui_scroll_manager_wanted_coordinates_update(sd, x, y);
   if (_elm_config->scroll_animation_disable)
     {
        Eina_Future *f;

        f = eina_future_then(efl_loop_job(efl_loop_get(sd->obj)),
                             _scroll_wheel_post_event_job, sd, NULL);
        efl_future_then(sd->obj, f);
     }
   else
     {
        cur =  efl_ui_pan_position_get(sd->pan_obj);
        _scroll_manager_scrollto_animator_add(sd, cur.x, cur.y, x, y,
          _elm_config->bring_in_scroll_friction,
          _elm_config->bring_in_scroll_friction, INTERP_DECEL);
     }
}

static Eina_Bool
_scroll_wheel_post_event_cb(void *data, Evas *e EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Evas_Event_Mouse_Wheel *ev = sd->event_info;

   Eina_Position2D min = {0, 0}, max = {0, 0}, cur = {0, 0};
   Eina_Size2D content = {0, 0};
   Evas_Coord x = 0, y = 0, vw = 0, vh = 0;
   Eina_Bool hold = EINA_FALSE;
   Evas_Coord pwx, pwy;
   double t;
   int direction;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ev, EINA_TRUE);

   sd->event_info = NULL;
   direction = ev->direction;

   pwx = sd->wx;
   pwy = sd->wy;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
   if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
     direction = !direction;

   cur = efl_ui_pan_position_get(sd->pan_obj);
   x = cur.x;
   y = cur.y;
   if (sd->scrollto.x.animator)
     {
        if (((ev->z > 0) && (sd->scrollto.x.end > x)) || ((ev->z < 0) && (sd->scrollto.x.end < x)))
          x = sd->scrollto.x.end;
     }
   if (sd->scrollto.y.animator)
     {
        if (((ev->z > 0) && (sd->scrollto.y.end > y)) || ((ev->z < 0) && (sd->scrollto.y.end < y)))
          y = sd->scrollto.y.end;
     }
   max = efl_ui_pan_position_max_get(sd->pan_obj);
   min = efl_ui_pan_position_min_get(sd->pan_obj);
   if (x < min.x) x = min.x;
   if (x > max.x) x = max.x;
   if (y < min.y) y = min.y;
   if (y > max.y) y = max.y;

   t = ecore_loop_time_get();

   _scroll_manager_animators_drop(sd->obj);

   Eina_Rect r = efl_ui_scrollable_viewport_geometry_get(sd->obj);
   vw = r.w;
   vh = r.h;

   if (sd->pan_obj)
     content = efl_ui_pan_content_size_get(sd->pan_obj);

   int d = ev->z;
   double delta_t = (double)(ev->timestamp - sd->last_wheel) / 1000.0;
   double mul;

   if (delta_t > 0.2) sd->last_wheel_mul = 0.0;
   if (delta_t > 0.2) delta_t = 0.2;
   mul = 1.0 + (_elm_config->scroll_accel_factor * ((0.2 - delta_t) / 0.2));
   mul = mul * (1.0 + (0.15 * sd->last_wheel_mul));
   d *= mul;
   sd->last_wheel = ev->timestamp;
   sd->last_wheel_mul = mul;

   if (!direction)
     {
        if ((content.h > vh) || (content.w <= vw))
          y += d * sd->step.y;
        else
          {
             x += d * sd->step.x;
             direction = 1;
          }
     }
   else
     {
        if ((content.w > vw) || (content.h <= vh))
          x += d * sd->step.x;
        else
          {
             y += d * sd->step.y;
             direction = 0;
          }
     }
   _scroll_wheel_post_event_go(sd, x, y);

   if (direction)
     {
        if ((sd->bounce_horiz) ||
            (pwx != sd->wx) ||
            (((t - sd->down.last_time_x_wheel) < 0.5) &&
             (sd->down.last_hold_x_wheel)))
          {
             sd->down.last_hold_x_wheel = EINA_TRUE;
             hold = EINA_TRUE;
          }
        else sd->down.last_hold_x_wheel = EINA_FALSE;
        sd->down.last_time_x_wheel = t;
     }
   else
     {
        if ((sd->bounce_vert) ||
            (pwy != sd->wy) ||
            (((t - sd->down.last_time_y_wheel) < 0.5) &&
             (sd->down.last_hold_y_wheel)))
          {
             sd->down.last_hold_y_wheel = EINA_TRUE;
             hold = EINA_TRUE;
          }
        else sd->down.last_hold_y_wheel = EINA_FALSE;
        sd->down.last_time_y_wheel = t;
     }
   return !hold;
}

static void
_efl_ui_scroll_manager_wheel_event_cb(void *data,
                           Evas *e,
                           Evas_Object *obj EINA_UNUSED,
                           void *event_info)
{
   Efl_Ui_Scroll_Manager_Data *sd;
   Evas_Event_Mouse_Wheel *ev;
   int direction;

   sd = data;
   ev = event_info;
   sd->event_info = event_info;
   direction = ev->direction;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   if ((evas_key_modifier_is_set(ev->modifiers, "Control")) ||
       (evas_key_modifier_is_set(ev->modifiers, "Alt")) ||
       (evas_key_modifier_is_set(ev->modifiers, "Meta")) ||
       (evas_key_modifier_is_set(ev->modifiers, "Hyper")) ||
       (evas_key_modifier_is_set(ev->modifiers, "Super")))
     return;
   if (direction)
     {
        if (sd->block & EFL_UI_LAYOUT_ORIENTATION_HORIZONTAL) return;
     }
   else
     {
        if (sd->block & EFL_UI_LAYOUT_ORIENTATION_VERTICAL) return;
     }

   evas_post_event_callback_push(e, _scroll_wheel_post_event_cb, sd);
}

static void
_efl_ui_scroll_manager_scroll_to_x_animator(void *data, const Efl_Event *event EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Eina_Position2D min = {0, 0}, max = {0, 0};
   Evas_Coord nx = 0;
   double t = 0.0, dt = 0.0, progx = 0.0, rx = 0.0;
   Interpolator interp = NULL;
   Eina_Bool no_bounce_x_end = EINA_FALSE;

   t = ecore_loop_time_get();
   dt = t - sd->scrollto.x.start_t;

   if ( dt > sd->scrollto.x.dur) progx = 1.0;
   else progx = dt / sd->scrollto.x.dur;

   if (sd->scrollto.x.interp) interp = sd->scrollto.x.interp;
   else interp = _scroll_manager_interp_get(INTERP_LINEAR);

   rx = interp(NULL, progx);
   nx = sd->scrollto.x.start + (sd->scrollto.x.end - sd->scrollto.x.start) * rx;

   Eina_Position2D cur = efl_ui_scrollable_content_pos_get(sd->obj);
   efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(nx, cur.y));
   _efl_ui_scroll_manager_wanted_coordinates_update(sd, nx, cur.y);

   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);

   if (!_elm_config->thumbscroll_bounce_enable || !sd->bounce_horiz)
     {
        if (nx < min.x) no_bounce_x_end = EINA_TRUE;
        if (!sd->loop_h && (nx - min.x) > max.x) no_bounce_x_end = EINA_TRUE;
     }
   if (dt >= sd->scrollto.x.dur || no_bounce_x_end)
   {
      if ((!sd->scrollto.y.animator) &&
          (!sd->bounce.y.animator))
        _efl_ui_scroll_manager_anim_stop(sd);
      ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->scrollto.x.animator, _efl_ui_scroll_manager_scroll_to_x_animator, sd);
   }
}

static void
_efl_ui_scroll_manager_scroll_to_y_animator(void *data, const Efl_Event *event EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Eina_Position2D min = {0, 0}, max = {0, 0};
   Evas_Coord ny = 0;
   double t = 0.0, dt = 0.0, progy = 0.0, ry = 0.0;
   Interpolator interp = NULL;
   Eina_Bool no_bounce_y_end = EINA_FALSE;

   t = ecore_loop_time_get();
   dt = t - sd->scrollto.y.start_t;

   if ( dt > sd->scrollto.y.dur) progy = 1.0;
   else progy = dt / sd->scrollto.y.dur;

   if (sd->scrollto.y.interp) interp = sd->scrollto.y.interp;
   else interp = _scroll_manager_interp_get(INTERP_LINEAR);

   ry = interp(NULL, progy);
   ny = sd->scrollto.y.start + (sd->scrollto.y.end - sd->scrollto.y.start) * ry;

   Eina_Position2D cur = efl_ui_scrollable_content_pos_get(sd->obj);
   efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(cur.x, ny));
   _efl_ui_scroll_manager_wanted_coordinates_update(sd, cur.x, ny);

   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);

   if (!_elm_config->thumbscroll_bounce_enable || !sd->bounce_vert)
     {
        if (ny < min.y) no_bounce_y_end = EINA_TRUE;
        if (!sd->loop_v && (ny - min.y) > max.y) no_bounce_y_end = EINA_TRUE;
     }
   if (dt >= sd->scrollto.y.dur || no_bounce_y_end)
   {
      if ((!sd->scrollto.x.animator) &&
          (!sd->bounce.x.animator))
          _efl_ui_scroll_manager_anim_stop(sd);
      ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->scrollto.y.animator, _efl_ui_scroll_manager_scroll_to_y_animator, sd);
   }
}

static void
_efl_ui_scroll_manager_mouse_up_event_smooth(Efl_Ui_Scroll_Manager_Data *sd, double t, Evas_Coord *ox, Evas_Coord *oy, double *ot)
{
   static const unsigned int HISTORY_MAX = 60;
   unsigned int i = 0;
   double dt = 0, at = 0;
   Evas_Coord ax = 0, ay = 0;

   for (i = 0; i < HISTORY_MAX; i++)
     {
        dt = t - sd->down.history[i].timestamp;
        if (dt > 0.2) break;
#ifdef SCROLLDBG
        DBG("H: %i %i @ %1.3f\n",
        sd->down.history[i].x,
        sd->down.history[i].y, dt);
#endif
        ax = sd->down.history[i].x;
        ay = sd->down.history[i].y;
        at = sd->down.history[i].timestamp;
     }
   if (ox) *ox = ax;
   if (oy) *oy = ay;
   if (ot) *ot = t - at;
}

static void
_efl_ui_scroll_manager_mouse_up_event_momentum_eval(Efl_Ui_Scroll_Manager_Data *sd, Evas_Event_Mouse_Up *ev)
{
   double t, at;
   Evas_Coord dx, dy, ax, ay, vel;
   signed char sdx, sdy;

   t = ev->timestamp / 1000.0;

   ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
   ax = ev->canvas.x;
   ay = ev->canvas.y;
   at = 0.0;
#ifdef SCROLLDBG
   DBG("------ %i %i\n", ev->canvas.x, ev->canvas.y);
#endif
   _efl_ui_scroll_manager_mouse_up_event_smooth(sd, t, &ax, &ay, &at);
   dx = ev->canvas.x - ax;
   dy = ev->canvas.y - ay;

   sdx = (dx > 0) - (dx < 0);
   sdy = (dy > 0) - (dy < 0);

   dx = abs(dx);
   dy = abs(dy);
   if (at > 0)
     {
        vel = (Evas_Coord)(sqrt((dx * dx) + (dy * dy)) / at);
        if ((_elm_config->thumbscroll_friction > 0.0) &&
            (vel > _elm_config->thumbscroll_momentum_threshold))
          {
             _scroll_manager_momentum_animator_add(sd, -sdx*dx/at, -sdy*dy/at);
          }
        else if (!sd->bouncemex && !sd->bouncemey)
          {
             _efl_ui_scroll_manager_scroll_stop(sd);
          }
     }
}

static void
_efl_ui_scroll_manager_mouse_up_event_cb(void *data,
                              Evas *e,
                              Evas_Object *obj EINA_UNUSED,
                              void *event_info)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Evas_Event_Mouse_Up *ev;

   if (!sd->pan_obj) return;
   if (!_scroll_manager_thumb_scrollable_get(sd)) return;

   ev = event_info;

   if (ev->button == 1)
     {
        _scroll_manager_on_hold_animator_del(sd);

        if (sd->down.dragged)
          {
             _efl_ui_scroll_manager_drag_stop(sd);
             if ((!sd->hold) && (!sd->freeze))
               {
                  _efl_ui_scroll_manager_mouse_up_event_momentum_eval(sd, ev);
               }
             evas_event_feed_hold(e, 0, ev->timestamp, ev->data);
          }

        _scroll_manager_hold_animator_del(sd);

        if (sd->down.scroll)
          {
             ev->event_flags |= EVAS_EVENT_FLAG_ON_SCROLL;
             sd->down.scroll = EINA_FALSE;
          }
        if (sd->down.hold)
          {
             ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
             sd->down.hold = EINA_FALSE;
          }

        sd->down.dragged_began = EINA_FALSE;
        sd->down.dir_x = EINA_FALSE;
        sd->down.dir_y = EINA_FALSE;
        sd->down.dragged = EINA_FALSE;
        sd->down.now = EINA_FALSE;

        Eina_Position2D cur = efl_ui_scrollable_content_pos_get(sd->obj);
        efl_ui_scrollable_content_pos_set(sd->obj, cur);
        _efl_ui_scroll_manager_wanted_coordinates_update(sd, cur.x, cur.y);

        if (sd->content_info.resized)
          _efl_ui_scroll_manager_wanted_region_set(sd->obj);
     }
}

static void
_efl_ui_scroll_manager_mouse_down_event_cb(void *data,
                                Evas *e EINA_UNUSED,
                                Evas_Object *obj EINA_UNUSED,
                                void *event_info)
{
   Efl_Ui_Scroll_Manager_Data *sd;
   Evas_Event_Mouse_Down *ev;
   Eina_Position2D cur = {0, 0};

   sd = data;
   ev = event_info;

   if (!_scroll_manager_thumb_scrollable_get(sd)) return;

   sd->down.hold = EINA_FALSE;
   if (_scroll_manager_animating_get(sd))
     {
        ev->event_flags |= EVAS_EVENT_FLAG_ON_SCROLL |
          EVAS_EVENT_FLAG_ON_HOLD;
        sd->down.scroll = EINA_TRUE;
        sd->down.hold = EINA_TRUE;

        _scroll_manager_animators_drop(sd->obj);
     }

   if (ev->button == 1)
     {
        sd->down.est_timestamp_diff =
          ecore_loop_time_get() - ((double)ev->timestamp / 1000.0);
        sd->down.now = EINA_TRUE;
        sd->down.dragged = EINA_FALSE;
        sd->down.dir_x = EINA_FALSE;
        sd->down.dir_y = EINA_FALSE;
        sd->down.x = ev->canvas.x;
        sd->down.y = ev->canvas.y;
        cur = efl_ui_scrollable_content_pos_get(sd->obj);
        sd->down.sx = cur.x;
        sd->down.sy = cur.y;
        memset(&(sd->down.history[0]), 0,
               sizeof(sd->down.history[0]) * 60);
        sd->down.history[0].timestamp = ev->timestamp / 1000.0;
        sd->down.dragged_began_timestamp = sd->down.history[0].timestamp;
        sd->down.history[0].x = ev->canvas.x;
        sd->down.history[0].y = ev->canvas.y;
     }
   sd->down.dragged_began = EINA_FALSE;
   if (sd->hold || sd->freeze)
     sd->down.want_reset = EINA_TRUE;
   else
     sd->down.want_reset = EINA_FALSE;
}

static Eina_Bool
_efl_ui_scroll_manager_can_scroll(Efl_Ui_Scroll_Manager_Data *sd,
                       int dir)
{
   Eina_Position2D min = {0, 0}, max = {0, 0}, cur = {0, 0};

   if (!sd->pan_obj) return EINA_FALSE;

   max = efl_ui_pan_position_max_get(sd->pan_obj);
   min = efl_ui_pan_position_min_get(sd->pan_obj);
   cur = efl_ui_pan_position_get(sd->pan_obj);
   switch (dir)
     {
      case LEFT:
        if (cur.x > min.x) return EINA_TRUE;
        break;

      case RIGHT:
        if ((cur.x - min.x) < max.x) return EINA_TRUE;
        break;

      case UP:
        if (cur.y > min.y) return EINA_TRUE;
        break;

      case DOWN:
        if ((cur.y - min.y) < max.y) return EINA_TRUE;
        break;

      default:
        break;
     }
   return EINA_FALSE;
}

static void
_efl_ui_scroll_manager_bounce_weight_apply(Efl_Ui_Scroll_Manager_Data *sd,
                                           Evas_Coord *x,
                                           Evas_Coord *y)
{
   Eina_Position2D min = {0, 0}, max = {0, 0};
   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);

   if (!sd->loop_h && *x < min.x)
     *x += (min.x - *x) * _elm_config->thumbscroll_border_friction;
   else if (!sd->loop_h && max.x <= 0)
     *x += (sd->down.sx - *x) * _elm_config->thumbscroll_border_friction;
   else if (!sd->loop_h && (max.x + min.x) < *x)
     *x += (max.x + min.x - *x) *
       _elm_config->thumbscroll_border_friction;

   if (!sd->loop_v && *y < min.y)
     *y += (min.y - *y) * _elm_config->thumbscroll_border_friction;
   else if (!sd->loop_v && max.y <= 0)
     *y += (sd->down.sy - *y) * _elm_config->thumbscroll_border_friction;
   else if (!sd->loop_v && (max.y + min.y) < *y)
     *y += (max.y + min.y - *y) *
       _elm_config->thumbscroll_border_friction;
}

static inline double
_scroll_manager_animation_duration_get(Evas_Coord dx, Evas_Coord dy)
{
  double dist = 0.0, vel = 0.0, dur = 0.0;
  uint64_t x = abs(dx), y = abs(dy);
  dist = sqrt(x * x + y * y);
  vel = _elm_config->thumbscroll_friction_standard / _elm_config->thumbscroll_friction;
  dur = dist / vel;
  dur = (dur > _elm_config->thumbscroll_friction) ? _elm_config->thumbscroll_friction : dur;
  return dur;
}

static Eina_Bool
_scroll_manager_on_hold_animator_del(Efl_Ui_Scroll_Manager_Data *sd)
{
  if (sd->down.onhold_animator)
    {
      ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->down.onhold_animator, _efl_ui_scroll_manager_on_hold_animator, sd);
      if (sd->content_info.resized)
        _efl_ui_scroll_manager_wanted_region_set(sd->obj);
      return EINA_TRUE;
    }
  return EINA_FALSE;
}

static void
_scroll_manager_on_hold_animator_add(Efl_Ui_Scroll_Manager_Data *sd, double vx, double vy)
{
  sd->down.onhold_vx = vx;
  sd->down.onhold_vy = vy;
  if (!sd->down.onhold_animator)
    {
      sd->down.onhold_vxe = 0.0;
      sd->down.onhold_vye = 0.0;
      sd->down.onhold_tlast = 0.0;

      ELM_ANIMATOR_CONNECT(sd->event_rect, sd->down.onhold_animator, _efl_ui_scroll_manager_on_hold_animator, sd);
    }
}

static void
_scroll_manager_hold_animator_add(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord x, Evas_Coord y)
{
   sd->down.hold_x = x;
   sd->down.hold_y = y;
   ELM_ANIMATOR_CONNECT(sd->event_rect, sd->down.hold_animator, _efl_ui_scroll_manager_hold_animator, sd);
}

static Eina_Bool
_scroll_manager_hold_animator_del(Efl_Ui_Scroll_Manager_Data *sd)
{
   if (sd->down.hold_animator || sd->down.hold_enterer)
     {
        ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->down.hold_animator, _efl_ui_scroll_manager_hold_animator, sd);
        ELM_SAFE_FREE(sd->down.hold_enterer, ecore_idle_enterer_del);
        return EINA_TRUE;
     }
   return EINA_FALSE;
}

static void
_scroll_manager_momentum_animator_add(Efl_Ui_Scroll_Manager_Data *sd, double vx, double vy)
{
#define FRICTION 5000
#define INVERSE_MASS 1
#define ACCEL (FRICTION * INVERSE_MASS)
   double dur = 0.0;
   signed char sdx = 0, sdy = 0;
   Evas_Coord dstx = 0, dsty = 0;

/*
   if (_scroll_manager_scrollto_animator_del(sd))
     {
        restore current veolocity
        add to vx/vy
     }
*/
   Eina_Position2D cur =  efl_ui_pan_position_get(sd->pan_obj);

   sdx = (vx > 0) - (vx < 0);
   sdy = (vy > 0) - (vy < 0);

   dstx = cur.x + ((sdx * vx * vx) / (double)(2 * ACCEL));
   dsty = cur.y + ((sdy * vy * vy) / (double)(2 * ACCEL));

   dur = sqrt((vx * vx) + (vy * vy)) / (double)ACCEL;

   _scroll_manager_scrollto_animator_add(sd, cur.x, cur.y, dstx, dsty,
                                         dur, dur, INTERP_DECEL);
}

static void
_efl_ui_scroll_manager_bounce_y_animator(void *data, const Efl_Event *event EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Evas_Coord ny = 0;
   Eina_Position2D cur = {0, 0};
   double t = 0.0, dt = 0.0, r = 0.0;

   t = ecore_loop_time_get();
   if (sd->bounce.y.start_t + sd->bounce.y.t01 >= t)
     {
        dt = sd->bounce.y.start_t + sd->bounce.y.t01 - t;
        r = 1.0 - (dt / sd->bounce.y.t01);
        r = _scroll_manager_decel_interp(NULL, r);
        ny = sd->bounce.y.p0 + (sd->bounce.y.p1 - sd->bounce.y.p0) * r;

        cur = efl_ui_scrollable_content_pos_get(sd->obj);
        efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(cur.x, ny));
     }
   else if (sd->bounce.y.start_t + sd->bounce.y.t01 + sd->bounce.y.t12 >= t)
     {
        dt = sd->bounce.y.start_t + sd->bounce.y.t01  + sd->bounce.y.t12 - t;
        r = 1.0 - (dt / sd->bounce.y.t12);
        r = _scroll_manager_decel_interp(NULL, r);
        ny = sd->bounce.y.p1 + (sd->bounce.y.p2 - sd->bounce.y.p1) * r;

        cur = efl_ui_scrollable_content_pos_get(sd->obj);
        efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(cur.x, ny));
     }
   else
     {
        cur = efl_ui_scrollable_content_pos_get(sd->obj);
        efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(cur.x, sd->bounce.y.p2));
        if ((!sd->scrollto.x.animator) &&
            (!sd->bounce.x.animator))
          _efl_ui_scroll_manager_anim_stop(sd);
        _scroll_manager_bounce_y_animator_del(sd);
     }
}

static void
_efl_ui_scroll_manager_bounce_x_animator(void *data, const Efl_Event *event EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Evas_Coord nx;
   Eina_Position2D cur = {0, 0};
   double t = 0.0, dt = 0.0, r = 0.0;

   t = ecore_loop_time_get();

   if (sd->bounce.x.start_t + sd->bounce.x.t01 >= t)
     {
        dt = sd->bounce.x.start_t + sd->bounce.x.t01 - t;
        r = 1.0 - (dt / sd->bounce.x.t01);
        r = _scroll_manager_decel_interp(NULL, r);
        nx = sd->bounce.x.p0 + (sd->bounce.x.p1 - sd->bounce.x.p0) * r;

        cur = efl_ui_scrollable_content_pos_get(sd->obj);
        efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(nx, cur.y));
     }
   else if (sd->bounce.x.start_t + sd->bounce.x.t01 + sd->bounce.x.t12 >= t)
     {
        dt = sd->bounce.x.start_t + sd->bounce.x.t01  + sd->bounce.x.t12 - t;
        r = 1.0 - (dt / sd->bounce.x.t12);
        r = _scroll_manager_decel_interp(NULL, r);
        nx = sd->bounce.x.p1 + (sd->bounce.x.p2 - sd->bounce.x.p1) * r;

        cur = efl_ui_scrollable_content_pos_get(sd->obj);
        efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(nx, cur.y));
     }
   else
     {
        cur = efl_ui_scrollable_content_pos_get(sd->obj);
        efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(sd->bounce.x.p2, cur.y));
        if ((!sd->scrollto.y.animator) &&
            (!sd->bounce.y.animator))
          _efl_ui_scroll_manager_anim_stop(sd);
        _scroll_manager_bounce_x_animator_del(sd);
     }
}

static void _scroll_manager_bounce_x_animator_add(Efl_Ui_Scroll_Manager_Data *sd, double vx)
{
   static const double spring_k = 1000;
   static const double mass = 1;
   char sign = (vx > 0) - (vx < 0);
   Eina_Position2D min = {0, 0}, max = {0, 0}, cur = {0, 0};

   _scroll_manager_bounce_x_animator_del(sd);

   cur = efl_ui_pan_position_get(sd->pan_obj);
   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);

   double max_x = sqrt((mass * vx * vx) / spring_k);
   sd->bounce.x.start_t = ecore_loop_time_get();
   sd->bounce.x.vel = vx;
   sd->bounce.x.p0 = cur.x;
   if (fabs(vx) > 0.0)
    sd->bounce.x.t01 = 0.2;
   else
    sd->bounce.x.t01 = 0.0;
   sd->bounce.x.p1 = cur.x + sign * max_x;;
   sd->bounce.x.t12 = 0.2;
   if ( cur.x < min.x )
   {
      sd->bounce.x.p2 = min.x;
   }
   else if ( cur.x > max.x)
   {
      sd->bounce.x.p2 = max.x;
   }

   if ((!sd->bounce.y.animator) &&
       (!sd->scrollto.y.animator))
     _efl_ui_scroll_manager_anim_start(sd);
   ELM_ANIMATOR_CONNECT(sd->event_rect, sd->bounce.x.animator, _efl_ui_scroll_manager_bounce_x_animator, sd);
}

static Eina_Bool _scroll_manager_bounce_x_animator_del(Efl_Ui_Scroll_Manager_Data *sd)
{
   if (sd->bounce.x.animator)
     {
        ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->bounce.x.animator, _efl_ui_scroll_manager_bounce_x_animator, sd);
        return EINA_TRUE;
     }
   return EINA_FALSE;
}

static void _scroll_manager_bounce_y_animator_add(Efl_Ui_Scroll_Manager_Data *sd, double vy)
{
   static const double spring_k = 1000;
   static const double mass = 1;
   char sign = (vy > 0) - (vy < 0);
   Eina_Position2D min = {0, 0}, max = {0, 0}, cur = {0, 0};

   _scroll_manager_bounce_y_animator_del(sd);

   cur = efl_ui_pan_position_get(sd->pan_obj);
   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);

   double max_y = sqrt((mass * vy * vy) / spring_k);
   sd->bounce.y.start_t = ecore_loop_time_get();
   sd->bounce.y.vel = vy;
   sd->bounce.y.p0 = cur.y;
   if (fabs(vy) > 0.0)
    sd->bounce.y.t01 = 0.2;
   else
    sd->bounce.y.t01 = 0.0;

   sd->bounce.y.p1 = cur.y + sign * max_y;
   sd->bounce.y.t12 = 0.2;
   if ( cur.y < min.y )
   {
      sd->bounce.y.p2 = min.y;
   }
   else if ( cur.y > max.y)
   {
      sd->bounce.y.p2 = max.y;
   }

   if ((!sd->bounce.x.animator) &&
       (!sd->scrollto.x.animator))
     _efl_ui_scroll_manager_anim_start(sd);
   ELM_ANIMATOR_CONNECT(sd->event_rect, sd->bounce.y.animator, _efl_ui_scroll_manager_bounce_y_animator, sd);
}

static Eina_Bool _scroll_manager_bounce_y_animator_del(Efl_Ui_Scroll_Manager_Data *sd)
{
   if (sd->bounce.y.animator)
     {
        ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->bounce.y.animator, _efl_ui_scroll_manager_bounce_y_animator, sd);
        return EINA_TRUE;
     }
   return EINA_FALSE;
}

static void
_scroll_manager_scrollto_x_animator_add(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord sx, Evas_Coord ex, double t, InterpType interp)
{
   sd->scrollto.x.start_t = ecore_loop_time_get();
   sd->scrollto.x.dur = t;

   sd->scrollto.x.start = sx;
   sd->scrollto.x.end = ex;

   sd->scrollto.x.interp = _scroll_manager_interp_get(interp);
    if (!sd->scrollto.x.animator)
      {
         ELM_ANIMATOR_CONNECT(sd->event_rect, sd->scrollto.x.animator, _efl_ui_scroll_manager_scroll_to_x_animator, sd);
         if (!sd->scrollto.y.animator) _efl_ui_scroll_manager_anim_start(sd);
      }
}

static Eina_Bool
_scroll_manager_scrollto_x_animator_del(Efl_Ui_Scroll_Manager_Data *sd)
{
 if (sd->scrollto.x.animator)
     {
        ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->scrollto.x.animator, _efl_ui_scroll_manager_scroll_to_x_animator, sd);
        return EINA_TRUE;
     }
     return EINA_FALSE;
}

static void
_scroll_manager_scrollto_y_animator_add(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord sy, Evas_Coord ey, double t, InterpType interp)
{
   sd->scrollto.y.start_t = ecore_loop_time_get();
   sd->scrollto.y.dur = t;

   sd->scrollto.y.start = sy;
   sd->scrollto.y.end = ey;

   sd->scrollto.y.interp = _scroll_manager_interp_get(interp);
    if (!sd->scrollto.y.animator)
      {
         ELM_ANIMATOR_CONNECT(sd->event_rect, sd->scrollto.y.animator, _efl_ui_scroll_manager_scroll_to_y_animator, sd);
         if (!sd->scrollto.x.animator) _efl_ui_scroll_manager_anim_start(sd);
      }
}

static Eina_Bool
_scroll_manager_scrollto_y_animator_del(Efl_Ui_Scroll_Manager_Data *sd)
{
 if (sd->scrollto.y.animator)
     {
        ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->scrollto.y.animator, _efl_ui_scroll_manager_scroll_to_y_animator, sd);
        return EINA_TRUE;
     }
     return EINA_FALSE;
}

static void
_scroll_manager_scrollto_animator_add(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord sx, Evas_Coord sy, Evas_Coord x, Evas_Coord y, double tx, double ty, InterpType interp)
{
   if (!sd->pan_obj || sd->freeze)
     {
        _scroll_manager_scrollto_animator_del(sd);
        return;
     }
   _scroll_manager_scrollto_x_animator_add(sd, sx, x, tx, interp);
   _scroll_manager_scrollto_y_animator_add(sd, sy, y, ty, interp);
}

static Eina_Bool
_scroll_manager_scrollto_animator_del(Efl_Ui_Scroll_Manager_Data *sd)
{
 if ((sd->scrollto.x.animator) || (sd->scrollto.y.animator))
     {
        ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->scrollto.x.animator, _efl_ui_scroll_manager_scroll_to_x_animator, sd);
        ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->scrollto.y.animator, _efl_ui_scroll_manager_scroll_to_y_animator, sd);
        return EINA_TRUE;
     }
     return EINA_FALSE;
}

static void
_scroll_manager_scrollto(Efl_Ui_Scroll_Manager_Data *sd, Evas_Coord x, Evas_Coord y)
{
   double dur = 0.0;
   Eina_Position2D cur = efl_ui_scrollable_content_pos_get(sd->obj);
   dur = _scroll_manager_animation_duration_get(x - cur.x, y - cur.y);
   _scroll_manager_scrollto_animator_add(sd, cur.x, cur.y, x, y, dur, dur, INTERP_LINEAR);
}

static void
_efl_ui_scroll_manager_post_event_move_on_hold_eval(Efl_Ui_Scroll_Manager_Data *sd, Evas_Event_Mouse_Move *ev)
{
   Evas_Coord x = 0, y = 0;
   double vx = 0.0, vy = 0.0;
   char sx = 0, sy = 0;

   Eina_Rect r = efl_ui_scrollable_viewport_geometry_get(sd->obj);

   x = (r.x - ev->cur.canvas.x) > 0 ? (r.x - ev->cur.canvas.x) : 0;
   y = (r.y - ev->cur.canvas.y) > 0 ? (r.y - ev->cur.canvas.y) : 0;
   x = (ev->cur.canvas.x - (r.x + r.w)) > 0 ? (ev->cur.canvas.x - (r.x + r.w)) : x;
   y = (ev->cur.canvas.y - (r.y + r.h)) > 0 ? (ev->cur.canvas.y - (r.y + r.h)) : y;
   sx = r.x - ev->cur.canvas.x > 0 ? -1 : 1;
   sy = r.y - ev->cur.canvas.y > 0 ? -1 : 1;

   if (x > _elm_config->thumbscroll_hold_threshold)
     {
        vx = 1.0;
        if (_elm_config->thumbscroll_hold_threshold > 0.0)
          vx = (double)(x - _elm_config->thumbscroll_hold_threshold) /
               _elm_config->thumbscroll_hold_threshold;
     }

   if (y > _elm_config->thumbscroll_hold_threshold)
     {
        vy = 1.0;
        if (_elm_config->thumbscroll_hold_threshold > 0.0)
          vy = (double)(y - _elm_config->thumbscroll_hold_threshold) /
               _elm_config->thumbscroll_hold_threshold;
     }

   if (EINA_DBL_NONZERO(vx) || EINA_DBL_NONZERO(vy)) _scroll_manager_on_hold_animator_add(sd, vx*sx, vy*sy);
   else _scroll_manager_on_hold_animator_del(sd);
}


static Eina_Bool
_efl_ui_scroll_manager_post_event_move_direction_restrict_eval(Efl_Ui_Scroll_Manager_Data *sd, Evas_Event_Mouse_Move *ev EINA_UNUSED,
                                                                Evas_Coord dx, Evas_Coord dy)
{
   if (sd->down.dragged) return EINA_FALSE;

   sd->down.hdir = -1;
   sd->down.vdir = -1;

   if (dx > 0) sd->down.hdir = LEFT;
   else if (dx < 0)
      sd->down.hdir = RIGHT;
   if (dy > 0) sd->down.vdir = UP;
   else if (dy < 0)
      sd->down.vdir = DOWN;

   if (!(sd->block & EFL_UI_LAYOUT_ORIENTATION_HORIZONTAL))
     sd->down.dir_x = EINA_TRUE;

   if (!(sd->block & EFL_UI_LAYOUT_ORIENTATION_VERTICAL))
     sd->down.dir_y = EINA_TRUE;

   return EINA_TRUE;
}

static Eina_Bool
_efl_ui_scroll_manager_post_event_move_hold_eval(Efl_Ui_Scroll_Manager_Data *sd, Evas_Event_Mouse_Move *ev,
                                                 Evas_Coord dx, Evas_Coord dy)
{
  if (!sd->down.dragged)
    {
       if(((dx * dx) + (dy * dy)) >
          (_elm_config->thumbscroll_threshold * _elm_config->thumbscroll_threshold))
         {
            if (_elm_config->scroll_smooth_start_enable)
              {
                 sd->down.x = ev->cur.canvas.x;
                 sd->down.y = ev->cur.canvas.y;
                 sd->down.dragged_began_timestamp = ev->timestamp / 1000.0;
              }
            // TODO 다른조건들도 can_scroll 안쪽으로 넣는다?
            if ((((_efl_ui_scroll_manager_can_scroll(sd, sd->down.hdir) || sd->bounce_horiz) && sd->down.dir_x) ||
                ((_efl_ui_scroll_manager_can_scroll(sd, sd->down.vdir) || sd->bounce_vert) && sd->down.dir_y)))
              {
                 _efl_ui_scroll_manager_drag_start(sd);
                 sd->down.dragged = EINA_TRUE;
                 ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
              }
            else if (!sd->down.dragged)
              return EINA_FALSE;
         }
      return EINA_TRUE;
    }

    if (sd->down.want_reset)
      {
         sd->down.x = ev->cur.canvas.x;
         sd->down.y = ev->cur.canvas.y;
         sd->down.want_reset = EINA_FALSE;
      }

    if (sd->down.dir_x) dx = sd->down.sx - (ev->cur.canvas.x - sd->down.x);
    else dx = sd->down.sx;
    if (sd->down.dir_y) dy = sd->down.sy - (ev->cur.canvas.y - sd->down.y);
    else dy = sd->down.sy;

    _efl_ui_scroll_manager_bounce_weight_apply(sd, &dx, &dy);
    _scroll_manager_hold_animator_add(sd, dx, dy);

    return EINA_TRUE;
}


static Eina_Bool
_efl_ui_scroll_manager_post_event_move(void *data,
                            Evas *e EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Evas_Event_Mouse_Move *ev = sd->event_info;
   sd->event_info = NULL;
   Evas_Coord dx, dy;

   dx = ev->cur.canvas.x - sd->down.x;
   dy = ev->cur.canvas.y - sd->down.y;

   _efl_ui_scroll_manager_post_event_move_direction_restrict_eval(sd, ev, dx, dy);

   if (!sd->freeze)
     {
        if (!sd->hold)
          {
             if (!_efl_ui_scroll_manager_post_event_move_hold_eval(sd, ev, dx, dy))
               return EINA_TRUE;
          }
        else
          {
             _efl_ui_scroll_manager_post_event_move_on_hold_eval(sd, ev);
          }
     }

   return EINA_FALSE;
}

static void
_efl_ui_scroll_manager_down_coord_eval(Efl_Ui_Scroll_Manager_Data *sd,
                            Evas_Coord *x,
                            Evas_Coord *y)
{
   if (!sd->pan_obj) return;

   if (sd->down.dir_x) *x = sd->down.sx - (*x - sd->down.x);
   else *x = sd->down.sx;
   if (sd->down.dir_y) *y = sd->down.sy - (*y - sd->down.y);
   else *y = sd->down.sy;

   _efl_ui_scroll_manager_bounce_weight_apply(sd, x, y);
}

static Eina_Bool
_efl_ui_scroll_manager_hold_enterer(void *data)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Evas_Coord fx = 0, fy = 0;

   sd->down.hold_enterer = NULL;
   fx = sd->down.hold_x;
   fy = sd->down.hold_y;

   if ((_elm_config->scroll_smooth_amount > 0.0) &&
       (_elm_config->scroll_smooth_time_window > 0.0))
     {
        int i, count = 0;
        Evas_Coord basex = 0, basey = 0, x, y;
        double dt, tdiff, tnow, twin, ttot;
        double xx, yy, tot;
        struct
          {
             Evas_Coord x, y;
             double t;
          } pos[100];

        tdiff = sd->down.est_timestamp_diff;
        tnow = ecore_loop_time_get();
        twin = _elm_config->scroll_smooth_time_window;
        for (i = 0; i < 60; i++)
          {
             if ((sd->down.history[i].timestamp - tdiff) > tnow)
               continue;
             if ((sd->down.history[i].timestamp >
                 sd->down.dragged_began_timestamp) || (count == 0))
               {
                  x = sd->down.history[i].x;
                  y = sd->down.history[i].y;
                  _efl_ui_scroll_manager_down_coord_eval(sd, &x, &y);
                  if (count == 0)
                    {
                       basex = x;
                       basey = y;
                    }
                  dt = (tnow + tdiff) - sd->down.history[i].timestamp;
                  if ((dt > twin) && (count > 0)) break;
                  if ((dt > 0.0) && (count == 0))
                    {
                       pos[count].x = x - basex;
                       pos[count].y = y - basey;
                       pos[count].t = 0.0;
                       count++;
                    }
                  pos[count].x = x - basex;
                  pos[count].y = y - basey;
                  pos[count].t = dt;
                  count++;
               }
          }
        if (count > 0)
          {
             xx = 0.0;
             yy = 0.0;
             tot = 0.0;
             ttot = pos[count - 1].t;
             for (i = 0; i < count; i++)
               {
                  double wt;

                  if (ttot > 0.0)
                    {
                       if (i < (count - 1))
                         wt = (ttot - pos[i].t) * (pos[i + 1].t - pos[i].t);
                       else
                         wt = 0.0;
                    }
                  else wt = 1.0;

                  xx += ((double)(pos[i].x)) * wt;
                  yy += ((double)(pos[i].y)) * wt;
                  tot += wt;
               }
             if (tot > 0.0)
               {
                  xx = basex + (xx / tot);
                  yy = basey + (yy / tot);
                  fx =
                    (_elm_config->scroll_smooth_amount * xx) +
                    ((1.0 - _elm_config->scroll_smooth_amount) * fx);
                  fy =
                    (_elm_config->scroll_smooth_amount * yy) +
                    ((1.0 - _elm_config->scroll_smooth_amount) * fy);
               }
          }
     }

   Eina_Position2D cur = efl_ui_scrollable_content_pos_get(sd->obj);
   if (sd->down.dir_x)
     cur.x = fx;
   if (sd->down.dir_y)
     cur.y = fy;

   efl_ui_scrollable_content_pos_set(sd->obj, cur);

   return EINA_FALSE;
}

static void
_efl_ui_scroll_manager_hold_animator(void *data, const Efl_Event *event EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;

   ecore_idle_enterer_del(sd->down.hold_enterer);
   sd->down.hold_enterer =
     ecore_idle_enterer_before_add(_efl_ui_scroll_manager_hold_enterer, sd);
}

static void
_efl_ui_scroll_manager_on_hold_animator(void *data, const Efl_Event *event EINA_UNUSED)
{
   double t, td;
   double vx, vy;
   Evas_Coord x, y;
   Eina_Position2D cur = {0, 0};
   Efl_Ui_Scroll_Manager_Data *sd;

   sd = data;
   t = ecore_loop_time_get();
   if (sd->down.onhold_tlast > 0.0)
     {
        td = t - sd->down.onhold_tlast;
        vx = sd->down.onhold_vx * td *
          (double)_elm_config->thumbscroll_hold_threshold * 2.0;
        vy = sd->down.onhold_vy * td *
          (double)_elm_config->thumbscroll_hold_threshold * 2.0;
        cur = efl_ui_scrollable_content_pos_get(sd->obj);
        x = cur.x;
        y = cur.y;

        if (sd->down.dir_x)
          {
             sd->down.onhold_vxe += vx;
             x = cur.x + (int)sd->down.onhold_vxe;
             sd->down.onhold_vxe -= (int)sd->down.onhold_vxe;
          }

        if (sd->down.dir_y)
          {
             sd->down.onhold_vye += vy;
             y = cur.y + (int)sd->down.onhold_vye;
             sd->down.onhold_vye -= (int)sd->down.onhold_vye;
          }

        efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(x, y));
     }
   sd->down.onhold_tlast = t;
}

static void
_efl_ui_scroll_manager_mouse_move_event_cb(void *data,
                                Evas *e,
                                Evas_Object *obj EINA_UNUSED,
                                void *event_info)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Evas_Event_Mouse_Move *ev;
   Eina_Position2D cur = {0, 0};

   if (!sd->pan_obj) return;
   if (!_scroll_manager_thumb_scrollable_get(sd)) return;
   if (!sd->down.now) return;

   ev = event_info;

   if ((!sd->hold) && (!sd->freeze))
     {
        if (_scroll_manager_animating_get(sd))
          {
            _scroll_manager_animators_drop(sd->obj);
            cur = efl_ui_pan_position_get(sd->pan_obj);
            sd->down.sx = cur.x;
            sd->down.sy = cur.y;
            sd->down.x = sd->down.history[0].x;
            sd->down.y = sd->down.history[0].y;
          }
   }

#ifdef SCROLLDBG
   DBG("::: %i %i\n", ev->cur.canvas.x, ev->cur.canvas.y);
#endif
   memmove(&(sd->down.history[1]), &(sd->down.history[0]),
           sizeof(sd->down.history[0]) * (60 - 1));
   sd->down.history[0].timestamp = ev->timestamp / 1000.0;
   sd->down.history[0].x = ev->cur.canvas.x;
   sd->down.history[0].y = ev->cur.canvas.y;
   sd->event_info = event_info;

   if (!(ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD))
     evas_post_event_callback_push(e, _efl_ui_scroll_manager_post_event_move, sd);

   if (sd->down.dragged)
     ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
}

static void
_scroll_event_object_attach(Evas_Object *obj)
{
   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(obj, sd);

   evas_object_event_callback_add
     (sd->event_rect, EVAS_CALLBACK_MOUSE_WHEEL, _efl_ui_scroll_manager_wheel_event_cb,
     sd);
   evas_object_event_callback_add
     (sd->event_rect, EVAS_CALLBACK_MOUSE_DOWN,
     _efl_ui_scroll_manager_mouse_down_event_cb, sd);
   evas_object_event_callback_add
     (sd->event_rect, EVAS_CALLBACK_MOUSE_UP,
     _efl_ui_scroll_manager_mouse_up_event_cb, sd);
   evas_object_event_callback_add
     (sd->event_rect, EVAS_CALLBACK_MOUSE_MOVE,
     _efl_ui_scroll_manager_mouse_move_event_cb, sd);
}

static void
_efl_ui_scroll_manager_content_resized(Efl_Ui_Scroll_Manager_Data *sd, Eina_Size2D content)
{
   sd->content_info.w = content.w;
   sd->content_info.h = content.h;

   sd->content_info.resized = EINA_TRUE;
   efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_SIZE_CHANGED, NULL);
   efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_POS_CHANGED, NULL);
   efl_ui_scrollbar_bar_visibility_update(sd->obj);
   _efl_ui_scroll_manager_wanted_region_set(sd->obj);
}

static void
_efl_ui_scroll_manager_pan_content_size_changed_cb(void *data, const Efl_Event *event)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;
   Eina_Size2D *content = event->info;

   _efl_ui_scroll_manager_content_resized(sd, *content);
}

static void
_efl_ui_scroll_manager_pan_content_changed(Efl_Ui_Scroll_Manager_Data *sd)
{
   Eina_Size2D sz = {0, 0};

   if (sd->pan_obj) sz = efl_ui_pan_content_size_get(sd->pan_obj);
   _efl_ui_scroll_manager_content_resized(sd, sz);
}

static void
_efl_ui_scroll_manager_pan_content_changed_cb(void *data, const Efl_Event *event EINA_UNUSED)
{
   _efl_ui_scroll_manager_pan_content_changed(data);
}

static void
_efl_ui_scroll_manager_pan_viewport_changed_cb(void *data, const Efl_Event *event EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;

   if (!sd->pan_obj) return;

   efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_SIZE_CHANGED, NULL);
   efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_POS_CHANGED, NULL);
   efl_ui_scrollbar_bar_visibility_update(sd->obj);
   _efl_ui_scroll_manager_wanted_region_set(sd->obj);
}

static void
_efl_ui_scroll_manager_pan_position_changed_cb(void *data, const Efl_Event *event EINA_UNUSED)
{
   Efl_Ui_Scroll_Manager_Data *sd = data;

   if (!sd->pan_obj) return;

   efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_POS_CHANGED, NULL);
   efl_ui_scrollbar_bar_visibility_update(sd->obj);
}

static void
_efl_ui_scroll_manager_pan_resized_cb(void *data,
                                      Evas *e EINA_UNUSED,
                                      Evas_Object *obj EINA_UNUSED,
                                      void *event_info EINA_UNUSED)
{
   Eo *manager = data;
   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(manager, sd);

   efl_gfx_entity_size_set(sd->event_rect, efl_gfx_entity_size_get(obj));
}

static void
_efl_ui_scroll_manager_pan_moved_cb(void *data,
                                      Evas *e EINA_UNUSED,
                                      Evas_Object *obj EINA_UNUSED,
                                      void *event_info EINA_UNUSED)
{
   Eo *manager = data;
   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(manager, sd);

   efl_gfx_entity_position_set(sd->event_rect, efl_gfx_entity_position_get(obj));
}

static void
_efl_ui_scroll_manager_scrollbar_h_visibility_apply(Efl_Ui_Scroll_Manager_Data *sd)
{
   if (sd->hbar_visible)
     {
        Efl_Ui_Layout_Orientation type = EFL_UI_LAYOUT_ORIENTATION_HORIZONTAL;
        efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_SHOW, &type);
     }
   else
     {
        Efl_Ui_Layout_Orientation type = EFL_UI_LAYOUT_ORIENTATION_HORIZONTAL;
        efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_HIDE, &type);
     }
}

static void
_efl_ui_scroll_manager_scrollbar_v_visibility_apply(Efl_Ui_Scroll_Manager_Data *sd)
{
   if (sd->vbar_visible)
     {
        Efl_Ui_Layout_Orientation type = EFL_UI_LAYOUT_ORIENTATION_VERTICAL;
        efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_SHOW, &type);
     }
   else
     {
        Efl_Ui_Layout_Orientation type = EFL_UI_LAYOUT_ORIENTATION_VERTICAL;
        efl_event_callback_call(sd->parent, EFL_UI_SCROLLBAR_EVENT_BAR_HIDE, &type);
     }
}

static void
_efl_ui_scrollbar_h_visibility_adjust(Eo *obj)
{
   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(obj, sd);
   int scroll_h_vis_change = 0;
   Evas_Coord w;

   w = sd->content_info.w;
   Eina_Rect view = efl_ui_scrollable_viewport_geometry_get(sd->obj);

   switch (sd->hbar_mode)
     {
      case EFL_UI_SCROLLBAR_MODE_AUTO:
         if (sd->hbar_visible)
           {
              if (w <= view.w)
                {
                   scroll_h_vis_change = 1;
                   sd->hbar_visible = EINA_FALSE;
                }
           }
         else
           {
              if (w > view.w)
                {
                   scroll_h_vis_change = 1;
                   sd->hbar_visible = EINA_TRUE;
                }
           }
         break;
      case EFL_UI_SCROLLBAR_MODE_ON:
         if (!sd->hbar_visible)
           {
              scroll_h_vis_change = 1;
              sd->hbar_visible = EINA_TRUE;
           }
         break;
      case EFL_UI_SCROLLBAR_MODE_OFF:
         if (sd->hbar_visible)
           {
              scroll_h_vis_change = 1;
              sd->hbar_visible = EINA_FALSE;
           }
         break;
      default:
         break;
     }

   if (scroll_h_vis_change) _efl_ui_scroll_manager_scrollbar_h_visibility_apply(sd);
}

static void
_efl_ui_scrollbar_v_visibility_adjust(Eo *obj)
{
   EFL_UI_SCROLL_MANAGER_DATA_GET_OR_RETURN(obj, sd);
   int scroll_v_vis_change = 0;
   Evas_Coord h;

   h = sd->content_info.h;
   Eina_Rect view = efl_ui_scrollable_viewport_geometry_get(sd->obj);

   switch (sd->vbar_mode)
     {
      case EFL_UI_SCROLLBAR_MODE_AUTO:
         if (sd->vbar_visible)
           {
              if (h <= view.h)
                {
                   scroll_v_vis_change = 1;
                   sd->vbar_visible = EINA_FALSE;
                }
           }
         else
           {
              if (h > view.h)
                {
                   scroll_v_vis_change = 1;
                   sd->vbar_visible = EINA_TRUE;
                }
           }
         break;
      case EFL_UI_SCROLLBAR_MODE_ON:
         if (!sd->vbar_visible)
           {
              scroll_v_vis_change = 1;
              sd->vbar_visible = EINA_TRUE;
           }
         break;
      case EFL_UI_SCROLLBAR_MODE_OFF:
         if (sd->vbar_visible)
           {
              scroll_v_vis_change = 1;
              sd->vbar_visible = EINA_FALSE;
           }
         break;
      default:
         break;
     }
   if (scroll_v_vis_change) _efl_ui_scroll_manager_scrollbar_v_visibility_apply(sd);
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollbar_bar_visibility_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool *hbar, Eina_Bool *vbar)
{
   if (hbar) *hbar = sd->hbar_visible;
   if (vbar) *vbar = sd->vbar_visible;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollbar_bar_visibility_update(Eo *obj, Efl_Ui_Scroll_Manager_Data *sd EINA_UNUSED)
{
   _efl_ui_scrollbar_h_visibility_adjust(obj);
   _efl_ui_scrollbar_v_visibility_adjust(obj);
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollbar_bar_position_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, double posx, double posy)
{
   Evas_Coord x, y;
   Eina_Position2D min = {0, 0}, max = {0, 0};

   if (sd->down.dragged || _scroll_manager_animating_get(sd)) return;

   max = efl_ui_pan_position_max_get(sd->pan_obj);
   min = efl_ui_pan_position_min_get(sd->pan_obj);
   x = _round(posx * (double)max.x + min.x, 1);
   y = _round(posy * (double)max.y + min.y, 1);
   efl_ui_scrollable_content_pos_set(sd->obj, EINA_POSITION2D(x, y));
   _efl_ui_scroll_manager_wanted_coordinates_update(sd, x, y);
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollbar_bar_position_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, double *posx, double *posy)
{
   if (!sd->pan_obj) return;

   double vx = 0, vy = 0;
   Eina_Position2D min = {0, 0}, max = {0, 0}, cur = {0, 0};

   min = efl_ui_pan_position_min_get(sd->pan_obj);
   max = efl_ui_pan_position_max_get(sd->pan_obj);
   cur = efl_ui_pan_position_get(sd->pan_obj);

   if (max.x > 0) vx = (double)(cur.x - min.x) / (double)max.x;
   else vx = 0.0;

   if (vx < 0.0) vx = 0.0;
   else if (vx > 1.0)
     vx = 1.0;

   if (posx) *posx = vx;

   if (max.y > 0) vy = (double)(cur.y - min.y) / (double)max.y;
   else vy = 0.0;

   if (vy < 0.0) vy = 0.0;
   else if (vy > 1.0)
     vy = 1.0;

   if (posy) *posy = vy;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollbar_bar_size_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, double *width, double *height)
{
   if (!sd->pan_obj) return;

   Evas_Coord w = 0, h = 0, vw = 0, vh = 0;
   double size = 0.0;

   Eina_Rect r = efl_ui_scrollable_viewport_geometry_get(sd->obj);
   vw = r.w;
   vh = r.h;

   w = sd->content_info.w;
   if (w < 1) w = 1;
   size = (double)vw / (double)w;
   if (size > 1.0) size = 1.0;

   if (width) *width = size;

   h = sd->content_info.h;
   if (h < 1) h = 1;
   size = (double)vh / (double)h;
   if (size > 1.0) size = 1.0;

   if (height) *height = size;
}

EOLIAN static void
_efl_ui_scroll_manager_pan_set(Eo *obj, Efl_Ui_Scroll_Manager_Data *sd, Eo *pan)
{
   if (sd->pan_obj == pan) return;

   if (sd->pan_obj)
     {
        efl_event_callback_del
           (sd->pan_obj, EFL_CONTENT_EVENT_CONTENT_CHANGED, _efl_ui_scroll_manager_pan_content_changed_cb, sd);
        efl_event_callback_del
           (sd->pan_obj, EFL_GFX_ENTITY_EVENT_SIZE_CHANGED, _efl_ui_scroll_manager_pan_viewport_changed_cb, sd);
        efl_event_callback_del
           (sd->pan_obj, EFL_GFX_ENTITY_EVENT_POSITION_CHANGED, _efl_ui_scroll_manager_pan_viewport_changed_cb, sd);
        efl_event_callback_del
           (sd->pan_obj, EFL_UI_PAN_EVENT_PAN_CONTENT_POSITION_CHANGED, _efl_ui_scroll_manager_pan_position_changed_cb, sd);
        efl_event_callback_del
           (sd->pan_obj, EFL_UI_PAN_EVENT_PAN_CONTENT_SIZE_CHANGED, _efl_ui_scroll_manager_pan_content_size_changed_cb, sd);
     }

   sd->pan_obj = pan;
   _efl_ui_scroll_manager_pan_content_changed(sd);
   if (!pan)
     return;

   efl_event_callback_add
     (sd->pan_obj, EFL_CONTENT_EVENT_CONTENT_CHANGED, _efl_ui_scroll_manager_pan_content_changed_cb, sd);
   efl_event_callback_add
     (sd->pan_obj, EFL_GFX_ENTITY_EVENT_SIZE_CHANGED, _efl_ui_scroll_manager_pan_viewport_changed_cb, sd);
   efl_event_callback_add
     (sd->pan_obj, EFL_GFX_ENTITY_EVENT_POSITION_CHANGED, _efl_ui_scroll_manager_pan_viewport_changed_cb, sd);
   efl_event_callback_add
     (sd->pan_obj, EFL_UI_PAN_EVENT_PAN_CONTENT_POSITION_CHANGED, _efl_ui_scroll_manager_pan_position_changed_cb, sd);
   efl_event_callback_add
     (sd->pan_obj, EFL_UI_PAN_EVENT_PAN_CONTENT_SIZE_CHANGED, _efl_ui_scroll_manager_pan_content_size_changed_cb, sd);
   evas_object_event_callback_add(sd->pan_obj, EVAS_CALLBACK_RESIZE,
                                       _efl_ui_scroll_manager_pan_resized_cb, obj);
   evas_object_event_callback_add(sd->pan_obj, EVAS_CALLBACK_MOVE,
                                       _efl_ui_scroll_manager_pan_moved_cb, obj);
}

EOLIAN static Eina_Bool
_efl_ui_scroll_manager_efl_ui_scrollable_scroll_hold_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd)
{
   return sd->hold;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_scroll_hold_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool hold)
{
   sd->hold = hold;
}

EOLIAN static Eina_Bool
_efl_ui_scroll_manager_efl_ui_scrollable_scroll_freeze_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd)
{
   return sd->freeze;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_scroll_freeze_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool freeze)
{
   sd->freeze = freeze;
   if (sd->freeze)
     {
        _scroll_manager_on_hold_animator_del(sd);
     }
   else
     _efl_ui_scroll_manager_bounce_eval(sd);
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_bounce_enabled_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool horiz, Eina_Bool vert)
{
   sd->bounce_horiz = !!horiz;
   sd->bounce_vert = !!vert;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_bounce_enabled_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool *horiz, Eina_Bool *vert)
{
   if (horiz) *horiz = sd->bounce_horiz;
   if (vert) *vert = sd->bounce_vert;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_scroll(Eo *obj, Efl_Ui_Scroll_Manager_Data *sd, Eina_Rect rect, Eina_Bool animation)
{
   _scroll_manager_animators_drop(obj);
   if (animation)
     {
        if (_efl_ui_scroll_manager_content_region_show_internal(obj, &(rect.x), &(rect.y), rect.w, rect.h))
          {
             _scroll_manager_scrollto(sd, rect.x, rect.y);
          }
     }
   else
     {
        sd->wx = (sd->is_mirrored ? _efl_ui_scroll_manager_x_mirrored_get(sd->obj, rect.x) : rect.x);
        sd->wy = rect.y;
        sd->ww = rect.w;
        sd->wh = rect.h;
        if (_efl_ui_scroll_manager_content_region_show_internal(obj, &(rect.x), &(rect.y), rect.w, rect.h))
          {
             efl_ui_scrollable_content_pos_set(obj, EINA_POSITION2D(rect.x, rect.y));
             sd->down.sx = rect.x;
             sd->down.sy = rect.y;
             sd->down.x = sd->down.history[0].x;
             sd->down.y = sd->down.history[0].y;
          }
     }
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_gravity_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, double x, double y)
{
   sd->gravity_x = x;
   sd->gravity_y = y;
   Eina_Position2D max = efl_ui_pan_position_max_get(sd->pan_obj);
   sd->prev_cw = max.x;
   sd->prev_ch = max.y;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_gravity_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, double *x, double *y)
{
   if (x) *x = sd->gravity_x;
   if (y) *y = sd->gravity_y;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_movement_block_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Efl_Ui_Layout_Orientation block)
{
   sd->block = block;
}

EOLIAN static Efl_Ui_Layout_Orientation
_efl_ui_scroll_manager_efl_ui_scrollable_movement_block_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd)
{
   return sd->block;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_looping_set(Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool loop_h, Eina_Bool loop_v)
{
   if (sd->loop_h == loop_h && sd->loop_v == loop_v) return;

   sd->loop_h = loop_h;
   sd->loop_v = loop_v;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_ui_scrollable_looping_get(const Eo *obj EINA_UNUSED, Efl_Ui_Scroll_Manager_Data *sd, Eina_Bool *loop_h, Eina_Bool *loop_v)
{
   *loop_h = sd->loop_h;
   *loop_v = sd->loop_v;
}

EOLIAN static Eo *
_efl_ui_scroll_manager_efl_object_constructor(Eo *obj, Efl_Ui_Scroll_Manager_Data *sd)
{
   obj = efl_constructor(efl_super(obj, MY_CLASS));

   memset(sd, 0, sizeof(*sd));

   sd->parent = efl_parent_get(obj);

   sd->obj = obj;

   sd->step.x = 32 * elm_config_scale_get();
   sd->step.y = 32 * elm_config_scale_get();
   sd->page.x = -50;
   sd->page.y = -50;
   sd->loop_h = EINA_FALSE;
   sd->loop_v = EINA_FALSE;

   sd->hbar_mode = EFL_UI_SCROLLBAR_MODE_AUTO;
   sd->vbar_mode = EFL_UI_SCROLLBAR_MODE_AUTO;
   sd->hbar_visible = EINA_TRUE;
   sd->vbar_visible = EINA_TRUE;

   sd->bounce_horiz = _elm_config->thumbscroll_bounce_enable;
   sd->bounce_vert = _elm_config->thumbscroll_bounce_enable;

   sd->block = EFL_UI_LAYOUT_ORIENTATION_DEFAULT;
   sd->scrolling = EINA_FALSE;

   sd->event_rect = evas_object_rectangle_add(evas_object_evas_get(sd->parent));
   efl_key_data_set(sd->event_rect, "_elm_leaveme", obj);
   efl_canvas_group_member_add(sd->parent, sd->event_rect);
   efl_ui_widget_sub_object_add(sd->parent, sd->event_rect);

   efl_gfx_color_set(sd->event_rect, 0, 0, 0, 0);
   efl_gfx_entity_visible_set(sd->event_rect, EINA_TRUE);
   efl_canvas_object_repeat_events_set(sd->event_rect, EINA_TRUE);

   _scroll_event_object_attach(obj);

   //FIXME : mostly bar-related callback is added after scroll manager creation,
   // so when constructor of manager is called, callback is not registered.
   //efl_ui_scrollbar_bar_visibility_update(sd->obj);

   return obj;
}

EOLIAN static void
_efl_ui_scroll_manager_efl_object_destructor(Eo *obj, Efl_Ui_Scroll_Manager_Data *sd)
{
   ecore_idle_enterer_del(sd->down.hold_enterer);
   ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->down.hold_animator, _efl_ui_scroll_manager_hold_animator, sd);
   ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->down.onhold_animator, _efl_ui_scroll_manager_on_hold_animator, sd);
   ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->bounce.x.animator, _efl_ui_scroll_manager_bounce_x_animator, sd);
   ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->bounce.y.animator, _efl_ui_scroll_manager_bounce_y_animator, sd);
   ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->scrollto.x.animator, _efl_ui_scroll_manager_scroll_to_x_animator, sd);
   ELM_ANIMATOR_DISCONNECT(sd->event_rect, sd->scrollto.y.animator, _efl_ui_scroll_manager_scroll_to_y_animator, sd);

   if (!efl_invalidating_get(sd->pan_obj))
     {
        evas_object_event_callback_del_full(sd->pan_obj, EVAS_CALLBACK_RESIZE,
                                            _efl_ui_scroll_manager_pan_resized_cb, obj);
        evas_object_event_callback_del_full(sd->pan_obj, EVAS_CALLBACK_MOVE,
                                            _efl_ui_scroll_manager_pan_moved_cb, obj);
        efl_event_callback_del
           (sd->pan_obj, EFL_CONTENT_EVENT_CONTENT_CHANGED, _efl_ui_scroll_manager_pan_content_changed_cb, sd);
        efl_event_callback_del
           (sd->pan_obj, EFL_GFX_ENTITY_EVENT_SIZE_CHANGED, _efl_ui_scroll_manager_pan_viewport_changed_cb, sd);
        efl_event_callback_del
           (sd->pan_obj, EFL_GFX_ENTITY_EVENT_POSITION_CHANGED, _efl_ui_scroll_manager_pan_viewport_changed_cb, sd);
        efl_event_callback_del
           (sd->pan_obj, EFL_UI_PAN_EVENT_PAN_CONTENT_POSITION_CHANGED, _efl_ui_scroll_manager_pan_position_changed_cb, sd);
        efl_event_callback_del
           (sd->pan_obj, EFL_UI_PAN_EVENT_PAN_CONTENT_SIZE_CHANGED, _efl_ui_scroll_manager_pan_content_size_changed_cb, sd);
     }
   efl_destructor(efl_super(obj, MY_CLASS));
}

#include "efl_ui_scroll_manager.eo.c"
