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