#ifdef HAVE_CONFIG_H # include "elementary_config.h" #endif #define EFL_UI_SCROLL_MANAGER_PROTECTED #define EFL_UI_SCROLLBAR_PROTECTED #include #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"