efl/src/lib/elementary/efl_ui_scroll_manager.c

2514 lines
78 KiB
C

#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"