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