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