1 /**
2  * @file lv_indev_scroll.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_indev.h"
10 #include "lv_indev_scroll.h"
11 
12 /*********************
13  *      DEFINES
14  *********************/
15 #define ELASTIC_SLOWNESS_FACTOR 4   /*Scrolling on elastic parts are slower by this factor*/
16 
17 /**********************
18  *      TYPEDEFS
19  **********************/
20 
21 /**********************
22  *  STATIC PROTOTYPES
23  **********************/
24 static lv_obj_t * find_scroll_obj(_lv_indev_proc_t * proc);
25 static void init_scroll_limits(_lv_indev_proc_t * proc);
26 static lv_coord_t find_snap_point_x(const lv_obj_t * obj, lv_coord_t min, lv_coord_t max, lv_coord_t ofs);
27 static lv_coord_t find_snap_point_y(const lv_obj_t * obj, lv_coord_t min, lv_coord_t max, lv_coord_t ofs);
28 static void scroll_limit_diff(_lv_indev_proc_t * proc, lv_coord_t * diff_x, lv_coord_t * diff_y);
29 static lv_coord_t scroll_throw_predict_y(_lv_indev_proc_t * proc);
30 static lv_coord_t scroll_throw_predict_x(_lv_indev_proc_t * proc);
31 static lv_coord_t elastic_diff(lv_obj_t * scroll_obj, lv_coord_t diff, lv_coord_t scroll_start, lv_coord_t scroll_end,
32                                lv_dir_t dir);
33 
34 /**********************
35  *  STATIC VARIABLES
36  **********************/
37 
38 /**********************
39  *      MACROS
40  **********************/
41 
42 /**********************
43  *   GLOBAL FUNCTIONS
44  **********************/
45 
_lv_indev_scroll_handler(_lv_indev_proc_t * proc)46 void _lv_indev_scroll_handler(_lv_indev_proc_t * proc)
47 {
48     if(proc->types.pointer.vect.x == 0 && proc->types.pointer.vect.y == 0) {
49         return;
50     }
51 
52     lv_obj_t * scroll_obj = proc->types.pointer.scroll_obj;
53     /*If there is no scroll object yet try to find one*/
54     if(scroll_obj == NULL) {
55         scroll_obj = find_scroll_obj(proc);
56         if(scroll_obj == NULL) return;
57 
58         init_scroll_limits(proc);
59 
60         lv_event_send(scroll_obj, LV_EVENT_SCROLL_BEGIN, NULL);
61         if(proc->reset_query) return;
62     }
63 
64     /*Set new position or scroll if the vector is not zero*/
65     int16_t angle = 0;
66     int16_t zoom = 256;
67     lv_obj_t * parent = scroll_obj;
68     while(parent) {
69         angle += lv_obj_get_style_transform_angle(parent, 0);
70         zoom *= (lv_obj_get_style_transform_zoom(parent, 0) / 256);
71         parent = lv_obj_get_parent(parent);
72     }
73 
74     if(angle != 0 || zoom != LV_IMG_ZOOM_NONE) {
75         angle = -angle;
76         zoom = (256 * 256) / zoom;
77         lv_point_t pivot = { 0, 0 };
78         lv_point_transform(&proc->types.pointer.vect, angle, zoom, &pivot);
79     }
80 
81     lv_coord_t diff_x = 0;
82     lv_coord_t diff_y = 0;
83     if(proc->types.pointer.scroll_dir == LV_DIR_HOR) {
84         lv_coord_t sr = lv_obj_get_scroll_right(scroll_obj);
85         lv_coord_t sl = lv_obj_get_scroll_left(scroll_obj);
86         diff_x = elastic_diff(scroll_obj, proc->types.pointer.vect.x, sl, sr, LV_DIR_HOR);
87     }
88     else {
89         lv_coord_t st = lv_obj_get_scroll_top(scroll_obj);
90         lv_coord_t sb = lv_obj_get_scroll_bottom(scroll_obj);
91         diff_y = elastic_diff(scroll_obj, proc->types.pointer.vect.y, st, sb, LV_DIR_VER);
92     }
93 
94     lv_dir_t scroll_dir = lv_obj_get_scroll_dir(scroll_obj);
95     if((scroll_dir & LV_DIR_LEFT)   == 0 && diff_x > 0) diff_x = 0;
96     if((scroll_dir & LV_DIR_RIGHT)  == 0 && diff_x < 0) diff_x = 0;
97     if((scroll_dir & LV_DIR_TOP)    == 0 && diff_y > 0) diff_y = 0;
98     if((scroll_dir & LV_DIR_BOTTOM) == 0 && diff_y < 0) diff_y = 0;
99 
100     /*Respect the scroll limit area*/
101     scroll_limit_diff(proc, &diff_x, &diff_y);
102 
103     _lv_obj_scroll_by_raw(scroll_obj, diff_x, diff_y);
104     if(proc->reset_query) return;
105     proc->types.pointer.scroll_sum.x += diff_x;
106     proc->types.pointer.scroll_sum.y += diff_y;
107 }
108 
_lv_indev_scroll_throw_handler(_lv_indev_proc_t * proc)109 void _lv_indev_scroll_throw_handler(_lv_indev_proc_t * proc)
110 {
111     lv_obj_t * scroll_obj = proc->types.pointer.scroll_obj;
112     if(scroll_obj == NULL) return;
113     if(proc->types.pointer.scroll_dir == LV_DIR_NONE) return;
114 
115     lv_indev_t * indev_act = lv_indev_get_act();
116     lv_coord_t scroll_throw = indev_act->driver->scroll_throw;
117 
118     if(lv_obj_has_flag(scroll_obj, LV_OBJ_FLAG_SCROLL_MOMENTUM) == false) {
119         proc->types.pointer.scroll_throw_vect.y = 0;
120         proc->types.pointer.scroll_throw_vect.x = 0;
121     }
122 
123     lv_scroll_snap_t align_x = lv_obj_get_scroll_snap_x(scroll_obj);
124     lv_scroll_snap_t align_y = lv_obj_get_scroll_snap_y(scroll_obj);
125 
126     if(proc->types.pointer.scroll_dir == LV_DIR_VER) {
127         proc->types.pointer.scroll_throw_vect.x = 0;
128         /*If no snapping "throw"*/
129         if(align_y == LV_SCROLL_SNAP_NONE) {
130             proc->types.pointer.scroll_throw_vect.y =
131                 proc->types.pointer.scroll_throw_vect.y * (100 - scroll_throw) / 100;
132 
133             lv_coord_t sb = lv_obj_get_scroll_bottom(scroll_obj);
134             lv_coord_t st = lv_obj_get_scroll_top(scroll_obj);
135 
136             proc->types.pointer.scroll_throw_vect.y = elastic_diff(scroll_obj, proc->types.pointer.scroll_throw_vect.y, st, sb,
137                                                                    LV_DIR_VER);
138 
139             lv_obj_scroll_by(scroll_obj, 0, proc->types.pointer.scroll_throw_vect.y, LV_ANIM_OFF);
140         }
141         /*With snapping find the nearest snap point and scroll there*/
142         else {
143             lv_coord_t diff_y = scroll_throw_predict_y(proc);
144             proc->types.pointer.scroll_throw_vect.y = 0;
145             scroll_limit_diff(proc, NULL, &diff_y);
146             lv_coord_t y = find_snap_point_y(scroll_obj, LV_COORD_MIN, LV_COORD_MAX, diff_y);
147             lv_obj_scroll_by(scroll_obj, 0, diff_y + y, LV_ANIM_ON);
148         }
149     }
150     else if(proc->types.pointer.scroll_dir == LV_DIR_HOR) {
151         proc->types.pointer.scroll_throw_vect.y = 0;
152         /*If no snapping "throw"*/
153         if(align_x == LV_SCROLL_SNAP_NONE) {
154             proc->types.pointer.scroll_throw_vect.x =
155                 proc->types.pointer.scroll_throw_vect.x * (100 - scroll_throw) / 100;
156 
157             lv_coord_t sl = lv_obj_get_scroll_left(scroll_obj);
158             lv_coord_t sr = lv_obj_get_scroll_right(scroll_obj);
159 
160             proc->types.pointer.scroll_throw_vect.x = elastic_diff(scroll_obj, proc->types.pointer.scroll_throw_vect.x, sl, sr,
161                                                                    LV_DIR_HOR);
162 
163             lv_obj_scroll_by(scroll_obj, proc->types.pointer.scroll_throw_vect.x, 0, LV_ANIM_OFF);
164         }
165         /*With snapping find the nearest snap point and scroll there*/
166         else {
167             lv_coord_t diff_x = scroll_throw_predict_x(proc);
168             proc->types.pointer.scroll_throw_vect.x = 0;
169             scroll_limit_diff(proc, &diff_x, NULL);
170             lv_coord_t x = find_snap_point_x(scroll_obj, LV_COORD_MIN, LV_COORD_MAX, diff_x);
171             lv_obj_scroll_by(scroll_obj, x + diff_x, 0, LV_ANIM_ON);
172         }
173     }
174 
175     /*Check if the scroll has finished*/
176     if(proc->types.pointer.scroll_throw_vect.x == 0 && proc->types.pointer.scroll_throw_vect.y == 0) {
177         /*Revert if scrolled in*/
178         /*If vertically scrollable and not controlled by snap*/
179         if(align_y == LV_SCROLL_SNAP_NONE) {
180             lv_coord_t st = lv_obj_get_scroll_top(scroll_obj);
181             lv_coord_t sb = lv_obj_get_scroll_bottom(scroll_obj);
182             if(st > 0 || sb > 0) {
183                 if(st < 0) {
184                     lv_obj_scroll_by(scroll_obj, 0, st, LV_ANIM_ON);
185                 }
186                 else if(sb < 0) {
187                     lv_obj_scroll_by(scroll_obj, 0, -sb, LV_ANIM_ON);
188                 }
189             }
190         }
191 
192         /*If horizontally scrollable and not controlled by snap*/
193         if(align_x == LV_SCROLL_SNAP_NONE) {
194             lv_coord_t sl = lv_obj_get_scroll_left(scroll_obj);
195             lv_coord_t sr = lv_obj_get_scroll_right(scroll_obj);
196             if(sl > 0 || sr > 0) {
197                 if(sl < 0) {
198                     lv_obj_scroll_by(scroll_obj, sl, 0, LV_ANIM_ON);
199                 }
200                 else if(sr < 0) {
201                     lv_obj_scroll_by(scroll_obj, -sr, 0, LV_ANIM_ON);
202                 }
203             }
204         }
205 
206         lv_event_send(scroll_obj, LV_EVENT_SCROLL_END, indev_act);
207         if(proc->reset_query) return;
208 
209         proc->types.pointer.scroll_dir = LV_DIR_NONE;
210         proc->types.pointer.scroll_obj = NULL;
211     }
212 }
213 
214 /**
215  * Predict where would a scroll throw end
216  * @param indev pointer to an input device
217  * @param dir `LV_DIR_VER` or `LV_DIR_HOR`
218  * @return the difference compared to the current position when the throw would be finished
219  */
lv_indev_scroll_throw_predict(lv_indev_t * indev,lv_dir_t dir)220 lv_coord_t lv_indev_scroll_throw_predict(lv_indev_t * indev, lv_dir_t dir)
221 {
222     if(indev == NULL) return 0;
223     lv_coord_t v;
224     switch(dir) {
225         case LV_DIR_VER:
226             v = indev->proc.types.pointer.scroll_throw_vect_ori.y;
227             break;
228         case LV_DIR_HOR:
229             v = indev->proc.types.pointer.scroll_throw_vect_ori.x;
230             break;
231         default:
232             return 0;
233     }
234 
235     lv_coord_t scroll_throw = indev->driver->scroll_throw;
236     lv_coord_t sum = 0;
237     while(v) {
238         sum += v;
239         v = v * (100 - scroll_throw) / 100;
240     }
241 
242     return sum;
243 }
244 
lv_indev_scroll_get_snap_dist(lv_obj_t * obj,lv_point_t * p)245 void lv_indev_scroll_get_snap_dist(lv_obj_t * obj, lv_point_t * p)
246 {
247     p->x = find_snap_point_x(obj, obj->coords.x1, obj->coords.x2, 0);
248     p->y = find_snap_point_y(obj, obj->coords.y1, obj->coords.y2, 0);
249 }
250 
251 /**********************
252  *   STATIC FUNCTIONS
253  **********************/
254 
find_scroll_obj(_lv_indev_proc_t * proc)255 static lv_obj_t * find_scroll_obj(_lv_indev_proc_t * proc)
256 {
257     lv_obj_t * obj_candidate = NULL;
258     lv_dir_t dir_candidate = LV_DIR_NONE;
259     lv_indev_t * indev_act = lv_indev_get_act();
260     lv_coord_t scroll_limit = indev_act->driver->scroll_limit;
261 
262     /*Go until find a scrollable object in the current direction
263      *More precisely:
264      * 1. Check the pressed object and all of its ancestors and try to find an object which is scrollable
265      * 2. Scrollable means it has some content out of its area
266      * 3. If an object can be scrolled into the current direction then use it ("real match"")
267      * 4. If can be scrolled on the current axis (hor/ver) save it as candidate (at least show an elastic scroll effect)
268      * 5. Use the last candidate. Always the "deepest" parent or the object from point 3*/
269     lv_obj_t * obj_act = proc->types.pointer.act_obj;
270 
271     /*Decide if it's a horizontal or vertical scroll*/
272     bool hor_en = false;
273     bool ver_en = false;
274 
275     proc->types.pointer.scroll_sum.x += proc->types.pointer.vect.x;
276     proc->types.pointer.scroll_sum.y += proc->types.pointer.vect.y;
277 
278     while(obj_act) {
279         /*Get the transformed scroll_sum with this object*/
280         int16_t angle = 0;
281         int32_t zoom = 256;
282         lv_point_t pivot = { 0, 0 };
283         lv_obj_t * parent = obj_act;
284         while(parent) {
285             angle += lv_obj_get_style_transform_angle(parent, 0);
286             int32_t zoom_act = lv_obj_get_style_transform_zoom(parent, 0);
287             zoom = (zoom * zoom_act) >> 8;
288             parent = lv_obj_get_parent(parent);
289         }
290 
291         lv_point_t obj_scroll_sum = proc->types.pointer.scroll_sum;
292         if(angle != 0 || zoom != LV_IMG_ZOOM_NONE) {
293             angle = -angle;
294             zoom = (256 * 256) / zoom;
295             lv_point_transform(&obj_scroll_sum, angle, zoom, &pivot);
296         }
297 
298         if(LV_ABS(obj_scroll_sum.x) > LV_ABS(obj_scroll_sum.y)) {
299             hor_en = true;
300         }
301         else {
302             ver_en = true;
303         }
304 
305         if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLLABLE) == false) {
306             /*If this object don't want to chain the scroll to the parent stop searching*/
307             if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLL_CHAIN_HOR) == false && hor_en) break;
308             if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLL_CHAIN_VER) == false && ver_en) break;
309 
310             obj_act = lv_obj_get_parent(obj_act);
311             continue;
312         }
313 
314         /*Consider both up-down or left/right scrollable according to the current direction*/
315         bool up_en = ver_en;
316         bool down_en = ver_en;
317         bool left_en = hor_en;
318         bool right_en = hor_en;
319 
320         /*The object might have disabled some directions.*/
321         lv_dir_t scroll_dir = lv_obj_get_scroll_dir(obj_act);
322         if((scroll_dir & LV_DIR_LEFT) == 0) left_en = false;
323         if((scroll_dir & LV_DIR_RIGHT) == 0) right_en = false;
324         if((scroll_dir & LV_DIR_TOP) == 0) up_en = false;
325         if((scroll_dir & LV_DIR_BOTTOM) == 0) down_en = false;
326 
327         /*The object is scrollable to a direction if its content overflow in that direction.*/
328         lv_coord_t st = lv_obj_get_scroll_top(obj_act);
329         lv_coord_t sb = lv_obj_get_scroll_bottom(obj_act);
330         lv_coord_t sl = lv_obj_get_scroll_left(obj_act);
331         lv_coord_t sr = lv_obj_get_scroll_right(obj_act);
332 
333         /*If this object is scrollable into the current scroll direction then save it as a candidate.
334          *It's important only to be scrollable on the current axis (hor/ver) because if the scroll
335          *is propagated to this object it can show at least elastic scroll effect.
336          *But if not hor/ver scrollable do not scroll it at all (so it's not a good candidate)*/
337         if((st > 0 || sb > 0)  &&
338            ((up_en    && obj_scroll_sum.y >=   scroll_limit) ||
339             (down_en  && obj_scroll_sum.y <= - scroll_limit))) {
340             obj_candidate = obj_act;
341             dir_candidate = LV_DIR_VER;
342         }
343 
344         if((sl > 0 || sr > 0)  &&
345            ((left_en   && obj_scroll_sum.x >=   scroll_limit) ||
346             (right_en  && obj_scroll_sum.x <= - scroll_limit))) {
347             obj_candidate = obj_act;
348             dir_candidate = LV_DIR_HOR;
349         }
350 
351         if(st <= 0) up_en = false;
352         if(sb <= 0) down_en = false;
353         if(sl <= 0) left_en = false;
354         if(sr <= 0) right_en = false;
355 
356         /*If the object really can be scrolled into the current direction then use it.*/
357         if((left_en  && obj_scroll_sum.x >=   scroll_limit) ||
358            (right_en && obj_scroll_sum.x <= - scroll_limit) ||
359            (up_en    && obj_scroll_sum.y >=   scroll_limit) ||
360            (down_en  && obj_scroll_sum.y <= - scroll_limit)) {
361             proc->types.pointer.scroll_dir = hor_en ? LV_DIR_HOR : LV_DIR_VER;
362             break;
363         }
364 
365         /*If this object don't want to chain the scroll to the parent stop searching*/
366         if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLL_CHAIN_HOR) == false && hor_en) break;
367         if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLL_CHAIN_VER) == false && ver_en) break;
368 
369         /*Try the parent*/
370         obj_act = lv_obj_get_parent(obj_act);
371     }
372 
373     /*Use the last candidate*/
374     if(obj_candidate) {
375         proc->types.pointer.scroll_dir = dir_candidate;
376         proc->types.pointer.scroll_obj = obj_candidate;
377         proc->types.pointer.scroll_sum.x = 0;
378         proc->types.pointer.scroll_sum.y = 0;
379     }
380 
381     return obj_candidate;
382 }
383 
init_scroll_limits(_lv_indev_proc_t * proc)384 static void init_scroll_limits(_lv_indev_proc_t * proc)
385 {
386     lv_obj_t * obj = proc->types.pointer.scroll_obj;
387     /*If there no STOP allow scrolling anywhere*/
388     if(lv_obj_has_flag(obj, LV_OBJ_FLAG_SCROLL_ONE) == false) {
389         lv_area_set(&proc->types.pointer.scroll_area, LV_COORD_MIN, LV_COORD_MIN, LV_COORD_MAX, LV_COORD_MAX);
390     }
391     /*With STOP limit the scrolling to the perv and next snap point*/
392     else {
393         switch(lv_obj_get_scroll_snap_y(obj)) {
394             case LV_SCROLL_SNAP_START:
395                 proc->types.pointer.scroll_area.y1 = find_snap_point_y(obj, obj->coords.y1 + 1, LV_COORD_MAX, 0);
396                 proc->types.pointer.scroll_area.y2 = find_snap_point_y(obj, LV_COORD_MIN, obj->coords.y1 - 1, 0);
397                 break;
398             case LV_SCROLL_SNAP_END:
399                 proc->types.pointer.scroll_area.y1 = find_snap_point_y(obj, obj->coords.y2, LV_COORD_MAX, 0);
400                 proc->types.pointer.scroll_area.y2 = find_snap_point_y(obj, LV_COORD_MIN, obj->coords.y2, 0);
401                 break;
402             case LV_SCROLL_SNAP_CENTER: {
403                     lv_coord_t y_mid = obj->coords.y1 + lv_area_get_height(&obj->coords) / 2;
404                     proc->types.pointer.scroll_area.y1 = find_snap_point_y(obj, y_mid + 1, LV_COORD_MAX, 0);
405                     proc->types.pointer.scroll_area.y2 = find_snap_point_y(obj, LV_COORD_MIN, y_mid - 1, 0);
406                     break;
407                 }
408             default:
409                 proc->types.pointer.scroll_area.y1 = LV_COORD_MIN;
410                 proc->types.pointer.scroll_area.y2 = LV_COORD_MAX;
411                 break;
412         }
413 
414         switch(lv_obj_get_scroll_snap_x(obj)) {
415             case LV_SCROLL_SNAP_START:
416                 proc->types.pointer.scroll_area.x1 = find_snap_point_x(obj, obj->coords.x1, LV_COORD_MAX, 0);
417                 proc->types.pointer.scroll_area.x2 = find_snap_point_x(obj, LV_COORD_MIN, obj->coords.x1, 0);
418                 break;
419             case LV_SCROLL_SNAP_END:
420                 proc->types.pointer.scroll_area.x1 = find_snap_point_x(obj, obj->coords.x2, LV_COORD_MAX, 0);
421                 proc->types.pointer.scroll_area.x2 = find_snap_point_x(obj, LV_COORD_MIN, obj->coords.x2, 0);
422                 break;
423             case LV_SCROLL_SNAP_CENTER: {
424                     lv_coord_t x_mid = obj->coords.x1 + lv_area_get_width(&obj->coords) / 2;
425                     proc->types.pointer.scroll_area.x1 = find_snap_point_x(obj, x_mid + 1, LV_COORD_MAX, 0);
426                     proc->types.pointer.scroll_area.x2 = find_snap_point_x(obj, LV_COORD_MIN, x_mid - 1, 0);
427                     break;
428                 }
429             default:
430                 proc->types.pointer.scroll_area.x1 = LV_COORD_MIN;
431                 proc->types.pointer.scroll_area.x2 = LV_COORD_MAX;
432                 break;
433         }
434     }
435 
436     /*Allow scrolling on the edges. It will be reverted to the edge due to snapping anyway*/
437     if(proc->types.pointer.scroll_area.x1 == 0) proc->types.pointer.scroll_area.x1 = LV_COORD_MIN;
438     if(proc->types.pointer.scroll_area.x2 == 0) proc->types.pointer.scroll_area.x2 = LV_COORD_MAX;
439     if(proc->types.pointer.scroll_area.y1 == 0) proc->types.pointer.scroll_area.y1 = LV_COORD_MIN;
440     if(proc->types.pointer.scroll_area.y2 == 0) proc->types.pointer.scroll_area.y2 = LV_COORD_MAX;
441 }
442 
443 /**
444  * Search for snap point in the `min` - `max` range.
445  * @param obj the object on which snap point should be found
446  * @param min ignore snap points smaller than this. (Absolute coordinate)
447  * @param max ignore snap points greater than this. (Absolute coordinate)
448  * @param ofs offset to snap points. Useful the get a snap point in an imagined case
449  *            what if children are already moved by this value
450  * @return the distance of the snap point.
451  */
find_snap_point_x(const lv_obj_t * obj,lv_coord_t min,lv_coord_t max,lv_coord_t ofs)452 static lv_coord_t find_snap_point_x(const lv_obj_t * obj, lv_coord_t min, lv_coord_t max, lv_coord_t ofs)
453 {
454     lv_scroll_snap_t align = lv_obj_get_scroll_snap_x(obj);
455     if(align == LV_SCROLL_SNAP_NONE) return 0;
456 
457     lv_coord_t dist = LV_COORD_MAX;
458 
459     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
460     lv_coord_t pad_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
461 
462     uint32_t i;
463     uint32_t child_cnt = lv_obj_get_child_cnt(obj);
464     for(i = 0; i < child_cnt; i++) {
465         lv_obj_t * child = obj->spec_attr->children[i];
466         if(lv_obj_has_flag_any(child, LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue;
467         if(lv_obj_has_flag(child, LV_OBJ_FLAG_SNAPPABLE)) {
468             lv_coord_t x_child = 0;
469             lv_coord_t x_parent = 0;
470             switch(align) {
471                 case LV_SCROLL_SNAP_START:
472                     x_child = child->coords.x1;
473                     x_parent = obj->coords.x1 + pad_left;
474                     break;
475                 case LV_SCROLL_SNAP_END:
476                     x_child = child->coords.x2;
477                     x_parent = obj->coords.x2 - pad_right;
478                     break;
479                 case LV_SCROLL_SNAP_CENTER:
480                     x_child = child->coords.x1 + lv_area_get_width(&child->coords) / 2;
481                     x_parent = obj->coords.x1 + pad_left + (lv_area_get_width(&obj->coords) - pad_left - pad_right) / 2;
482                     break;
483                 default:
484                     continue;
485             }
486 
487             x_child += ofs;
488             if(x_child >= min && x_child <= max) {
489                 lv_coord_t x = x_child -  x_parent;
490                 if(LV_ABS(x) < LV_ABS(dist)) dist = x;
491             }
492         }
493     }
494 
495     return dist == LV_COORD_MAX ? 0 : -dist;
496 }
497 
498 /**
499  * Search for snap point in the `min` - `max` range.
500  * @param obj the object on which snap point should be found
501  * @param min ignore snap points smaller than this. (Absolute coordinate)
502  * @param max ignore snap points greater than this. (Absolute coordinate)
503  * @param ofs offset to snap points. Useful to get a snap point in an imagined case
504  *            what if children are already moved by this value
505  * @return the distance of the snap point.
506  */
find_snap_point_y(const lv_obj_t * obj,lv_coord_t min,lv_coord_t max,lv_coord_t ofs)507 static lv_coord_t find_snap_point_y(const lv_obj_t * obj, lv_coord_t min, lv_coord_t max, lv_coord_t ofs)
508 {
509     lv_scroll_snap_t align = lv_obj_get_scroll_snap_y(obj);
510     if(align == LV_SCROLL_SNAP_NONE) return 0;
511 
512     lv_coord_t dist = LV_COORD_MAX;
513 
514     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
515     lv_coord_t pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
516 
517     uint32_t i;
518     uint32_t child_cnt = lv_obj_get_child_cnt(obj);
519     for(i = 0; i < child_cnt; i++) {
520         lv_obj_t * child = obj->spec_attr->children[i];
521         if(lv_obj_has_flag_any(child, LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue;
522         if(lv_obj_has_flag(child, LV_OBJ_FLAG_SNAPPABLE)) {
523             lv_coord_t y_child = 0;
524             lv_coord_t y_parent = 0;
525             switch(align) {
526                 case LV_SCROLL_SNAP_START:
527                     y_child = child->coords.y1;
528                     y_parent = obj->coords.y1 + pad_top;
529                     break;
530                 case LV_SCROLL_SNAP_END:
531                     y_child = child->coords.y2;
532                     y_parent = obj->coords.y2 - pad_bottom;
533                     break;
534                 case LV_SCROLL_SNAP_CENTER:
535                     y_child = child->coords.y1 + lv_area_get_height(&child->coords) / 2;
536                     y_parent = obj->coords.y1 + pad_top + (lv_area_get_height(&obj->coords) - pad_top - pad_bottom) / 2;
537                     break;
538                 default:
539                     continue;
540             }
541 
542             y_child += ofs;
543             if(y_child >= min && y_child <= max) {
544                 lv_coord_t y = y_child -  y_parent;
545                 if(LV_ABS(y) < LV_ABS(dist)) dist = y;
546             }
547         }
548     }
549 
550     return dist == LV_COORD_MAX ? 0 : -dist;
551 }
552 
scroll_limit_diff(_lv_indev_proc_t * proc,lv_coord_t * diff_x,lv_coord_t * diff_y)553 static void scroll_limit_diff(_lv_indev_proc_t * proc, lv_coord_t * diff_x, lv_coord_t * diff_y)
554 {
555     if(diff_y) {
556         if(proc->types.pointer.scroll_sum.y + *diff_y < proc->types.pointer.scroll_area.y1) {
557             *diff_y = proc->types.pointer.scroll_area.y1 - proc->types.pointer.scroll_sum.y;
558         }
559 
560         if(proc->types.pointer.scroll_sum.y + *diff_y > proc->types.pointer.scroll_area.y2) {
561             *diff_y = proc->types.pointer.scroll_area.y2 - proc->types.pointer.scroll_sum.y;
562         }
563     }
564 
565     if(diff_x) {
566         if(proc->types.pointer.scroll_sum.x + *diff_x < proc->types.pointer.scroll_area.x1) {
567             *diff_x = proc->types.pointer.scroll_area.x1 - proc->types.pointer.scroll_sum.x;
568         }
569 
570         if(proc->types.pointer.scroll_sum.x + *diff_x > proc->types.pointer.scroll_area.x2) {
571             *diff_x = proc->types.pointer.scroll_area.x2 - proc->types.pointer.scroll_sum.x;
572         }
573     }
574 }
575 
scroll_throw_predict_y(_lv_indev_proc_t * proc)576 static lv_coord_t scroll_throw_predict_y(_lv_indev_proc_t * proc)
577 {
578     lv_coord_t y = proc->types.pointer.scroll_throw_vect.y;
579     lv_coord_t move = 0;
580 
581     lv_indev_t * indev_act = lv_indev_get_act();
582     lv_coord_t scroll_throw = indev_act->driver->scroll_throw;
583 
584     while(y) {
585         move += y;
586         y = y * (100 - scroll_throw) / 100;
587     }
588     return move;
589 }
590 
scroll_throw_predict_x(_lv_indev_proc_t * proc)591 static lv_coord_t scroll_throw_predict_x(_lv_indev_proc_t * proc)
592 {
593     lv_coord_t x = proc->types.pointer.scroll_throw_vect.x;
594     lv_coord_t move = 0;
595 
596     lv_indev_t * indev_act = lv_indev_get_act();
597     lv_coord_t scroll_throw = indev_act->driver->scroll_throw;
598 
599     while(x) {
600         move += x;
601         x = x * (100 - scroll_throw) / 100;
602     }
603     return move;
604 }
605 
elastic_diff(lv_obj_t * scroll_obj,lv_coord_t diff,lv_coord_t scroll_start,lv_coord_t scroll_end,lv_dir_t dir)606 static lv_coord_t elastic_diff(lv_obj_t * scroll_obj, lv_coord_t diff, lv_coord_t scroll_start, lv_coord_t scroll_end,
607                                lv_dir_t dir)
608 {
609     if(lv_obj_has_flag(scroll_obj, LV_OBJ_FLAG_SCROLL_ELASTIC)) {
610         /*If there is snapping in the current direction don't use the elastic factor because
611          *it's natural that the first and last items are scrolled (snapped) in.*/
612         lv_scroll_snap_t snap;
613         snap = dir == LV_DIR_HOR ? lv_obj_get_scroll_snap_x(scroll_obj) : lv_obj_get_scroll_snap_y(scroll_obj);
614 
615         lv_obj_t * act_obj = lv_indev_get_obj_act();
616         lv_coord_t snap_point = 0;
617         lv_coord_t act_obj_point = 0;
618 
619         if(dir == LV_DIR_HOR) {
620             lv_coord_t pad_left = lv_obj_get_style_pad_left(scroll_obj, LV_PART_MAIN);
621             lv_coord_t pad_right = lv_obj_get_style_pad_right(scroll_obj, LV_PART_MAIN);
622 
623             switch(snap) {
624                 case LV_SCROLL_SNAP_CENTER:
625                     snap_point = pad_left + (lv_area_get_width(&scroll_obj->coords) - pad_left - pad_right) / 2 + scroll_obj->coords.x1;
626                     act_obj_point = lv_area_get_width(&act_obj->coords) / 2 + act_obj->coords.x1;
627                     break;
628                 case LV_SCROLL_SNAP_START:
629                     snap_point = scroll_obj->coords.x1 + pad_left;
630                     act_obj_point = act_obj->coords.x1;
631                     break;
632                 case LV_SCROLL_SNAP_END:
633                     snap_point = scroll_obj->coords.x2 - pad_right;
634                     act_obj_point = act_obj->coords.x2;
635                     break;
636             }
637         }
638         else {
639             lv_coord_t pad_top = lv_obj_get_style_pad_top(scroll_obj, LV_PART_MAIN);
640             lv_coord_t pad_bottom = lv_obj_get_style_pad_bottom(scroll_obj, LV_PART_MAIN);
641 
642             switch(snap) {
643                 case LV_SCROLL_SNAP_CENTER:
644                     snap_point = pad_top + (lv_area_get_height(&scroll_obj->coords) - pad_top - pad_bottom) / 2 + scroll_obj->coords.y1;
645                     act_obj_point = lv_area_get_height(&act_obj->coords) / 2 + act_obj->coords.y1;
646                     break;
647                 case LV_SCROLL_SNAP_START:
648                     snap_point = scroll_obj->coords.y1 + pad_top;
649                     act_obj_point = act_obj->coords.y1;
650                     break;
651                 case LV_SCROLL_SNAP_END:
652                     snap_point = scroll_obj->coords.y2 - pad_bottom;
653                     act_obj_point = act_obj->coords.y2;
654                     break;
655             }
656         }
657 
658         if(scroll_end < 0) {
659             if(snap != LV_SCROLL_SNAP_NONE && act_obj_point > snap_point) return diff;
660 
661             /*Rounding*/
662             if(diff < 0) diff -= ELASTIC_SLOWNESS_FACTOR / 2;
663             if(diff > 0) diff += ELASTIC_SLOWNESS_FACTOR / 2;
664             return diff / ELASTIC_SLOWNESS_FACTOR;
665         }
666         else if(scroll_start < 0) {
667             if(snap != LV_SCROLL_SNAP_NONE && act_obj_point < snap_point) return diff;
668 
669             /*Rounding*/
670             if(diff < 0) diff -= ELASTIC_SLOWNESS_FACTOR / 2;
671             if(diff > 0) diff += ELASTIC_SLOWNESS_FACTOR / 2;
672             return diff / ELASTIC_SLOWNESS_FACTOR;
673         }
674     }
675     else {
676         /*Scroll back to the boundary if required*/
677         if(scroll_end + diff < 0) diff = - scroll_end;
678         if(scroll_start - diff < 0) diff = scroll_start;
679     }
680 
681     return diff;
682 }
683