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