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