1 /**
2  * @file lv_obj_scroll.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_obj_scroll.h"
10 #include "lv_obj.h"
11 #include "lv_indev.h"
12 #include "lv_disp.h"
13 #include "lv_indev_scroll.h"
14 
15 /*********************
16  *      DEFINES
17  *********************/
18 #define MY_CLASS &lv_obj_class
19 #define SCROLL_ANIM_TIME_MIN    200    /*ms*/
20 #define SCROLL_ANIM_TIME_MAX    400    /*ms*/
21 #define SCROLLBAR_MIN_SIZE      (LV_DPX(10))
22 
23 /**********************
24  *      TYPEDEFS
25  **********************/
26 
27 /**********************
28  *  GLOBAL PROTOTYPES
29  **********************/
30 
31 /**********************
32  *  STATIC PROTOTYPES
33  **********************/
34 static void scroll_by_raw(lv_obj_t * obj, lv_coord_t x, lv_coord_t y);
35 static void scroll_x_anim(void * obj, int32_t v);
36 static void scroll_y_anim(void * obj, int32_t v);
37 static void scroll_anim_ready_cb(lv_anim_t * a);
38 static void scroll_area_into_view(const lv_area_t * area, lv_obj_t * child, lv_point_t * scroll_value,
39                                   lv_anim_enable_t anim_en);
40 
41 /**********************
42  *  STATIC VARIABLES
43  **********************/
44 
45 /**********************
46  *      MACROS
47  **********************/
48 
49 /**********************
50  *   GLOBAL FUNCTIONS
51  **********************/
52 
53 /*=====================
54  * Setter functions
55  *====================*/
56 
lv_obj_set_scrollbar_mode(lv_obj_t * obj,lv_scrollbar_mode_t mode)57 void lv_obj_set_scrollbar_mode(lv_obj_t * obj, lv_scrollbar_mode_t mode)
58 {
59     LV_ASSERT_OBJ(obj, MY_CLASS);
60 
61     lv_obj_allocate_spec_attr(obj);
62 
63     if(obj->spec_attr->scrollbar_mode == mode) return;
64     obj->spec_attr->scrollbar_mode = mode;
65     lv_obj_invalidate(obj);
66 }
67 
lv_obj_set_scroll_dir(lv_obj_t * obj,lv_dir_t dir)68 void lv_obj_set_scroll_dir(lv_obj_t * obj, lv_dir_t dir)
69 {
70     lv_obj_allocate_spec_attr(obj);
71 
72     if(dir != obj->spec_attr->scroll_dir) {
73         obj->spec_attr->scroll_dir = dir;
74     }
75 }
76 
lv_obj_set_scroll_snap_x(lv_obj_t * obj,lv_scroll_snap_t align)77 void lv_obj_set_scroll_snap_x(lv_obj_t * obj, lv_scroll_snap_t align)
78 {
79     lv_obj_allocate_spec_attr(obj);
80     obj->spec_attr->scroll_snap_x = align;
81 }
82 
lv_obj_set_scroll_snap_y(lv_obj_t * obj,lv_scroll_snap_t align)83 void lv_obj_set_scroll_snap_y(lv_obj_t * obj, lv_scroll_snap_t align)
84 {
85     lv_obj_allocate_spec_attr(obj);
86     obj->spec_attr->scroll_snap_y = align;
87 }
88 
89 /*=====================
90  * Getter functions
91  *====================*/
92 
lv_obj_get_scrollbar_mode(const lv_obj_t * obj)93 lv_scrollbar_mode_t lv_obj_get_scrollbar_mode(const lv_obj_t * obj)
94 {
95     if(obj->spec_attr) return obj->spec_attr->scrollbar_mode;
96     else return LV_SCROLLBAR_MODE_AUTO;
97 }
98 
lv_obj_get_scroll_dir(const lv_obj_t * obj)99 lv_dir_t lv_obj_get_scroll_dir(const lv_obj_t * obj)
100 {
101     if(obj->spec_attr) return obj->spec_attr->scroll_dir;
102     else return LV_DIR_ALL;
103 }
104 
lv_obj_get_scroll_snap_x(const lv_obj_t * obj)105 lv_scroll_snap_t lv_obj_get_scroll_snap_x(const lv_obj_t * obj)
106 {
107     if(obj->spec_attr) return obj->spec_attr->scroll_snap_x;
108     else return LV_SCROLL_SNAP_NONE;
109 }
110 
lv_obj_get_scroll_snap_y(const lv_obj_t * obj)111 lv_scroll_snap_t lv_obj_get_scroll_snap_y(const lv_obj_t * obj)
112 {
113     if(obj->spec_attr) return obj->spec_attr->scroll_snap_y;
114     else return LV_SCROLL_SNAP_NONE;
115 }
116 
lv_obj_get_scroll_x(const lv_obj_t * obj)117 lv_coord_t lv_obj_get_scroll_x(const lv_obj_t * obj)
118 {
119     if(obj->spec_attr == NULL) return 0;
120     return -obj->spec_attr->scroll.x;
121 }
122 
lv_obj_get_scroll_y(const lv_obj_t * obj)123 lv_coord_t lv_obj_get_scroll_y(const lv_obj_t * obj)
124 {
125     if(obj->spec_attr == NULL) return 0;
126     return -obj->spec_attr->scroll.y;
127 }
128 
lv_obj_get_scroll_top(lv_obj_t * obj)129 lv_coord_t lv_obj_get_scroll_top(lv_obj_t * obj)
130 {
131     if(obj->spec_attr == NULL) return 0;
132     return -obj->spec_attr->scroll.y;
133 }
134 
lv_obj_get_scroll_bottom(lv_obj_t * obj)135 lv_coord_t lv_obj_get_scroll_bottom(lv_obj_t * obj)
136 {
137     LV_ASSERT_OBJ(obj, MY_CLASS);
138 
139     lv_coord_t child_res = LV_COORD_MIN;
140     uint32_t i;
141     uint32_t child_cnt = lv_obj_get_child_cnt(obj);
142     for(i = 0; i < child_cnt; i++) {
143         lv_obj_t * child = obj->spec_attr->children[i];
144         if(lv_obj_has_flag_any(child,  LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue;
145         child_res = LV_MAX(child_res, child->coords.y2);
146     }
147 
148     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
149     lv_coord_t pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
150     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
151 
152     if(child_res != LV_COORD_MIN) {
153         child_res -= (obj->coords.y2 - pad_bottom - border_width);
154     }
155 
156     lv_coord_t self_h = lv_obj_get_self_height(obj);
157     self_h = self_h - (lv_obj_get_height(obj) - pad_top - pad_bottom - 2 * border_width);
158     self_h -= lv_obj_get_scroll_y(obj);
159     return LV_MAX(child_res, self_h);
160 }
161 
lv_obj_get_scroll_left(lv_obj_t * obj)162 lv_coord_t lv_obj_get_scroll_left(lv_obj_t * obj)
163 {
164     LV_ASSERT_OBJ(obj, MY_CLASS);
165 
166     /*Normally can't scroll the object out on the left.
167      *So simply use the current scroll position as "left size"*/
168     if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL) {
169         if(obj->spec_attr == NULL) return 0;
170         return -obj->spec_attr->scroll.x;
171     }
172 
173     /*With RTL base direction scrolling the left is normal so find the left most coordinate*/
174     lv_coord_t pad_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
175     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
176     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
177 
178     lv_coord_t child_res = 0;
179 
180     uint32_t i;
181     lv_coord_t x1 = LV_COORD_MAX;
182     uint32_t child_cnt = lv_obj_get_child_cnt(obj);
183     for(i = 0; i < child_cnt; i++) {
184         lv_obj_t * child = obj->spec_attr->children[i];
185         if(lv_obj_has_flag_any(child,  LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue;
186         x1 = LV_MIN(x1, child->coords.x1);
187 
188     }
189 
190     if(x1 != LV_COORD_MAX) {
191         child_res = x1;
192         child_res = (obj->coords.x1 + pad_left + border_width) - child_res;
193     }
194     else {
195         child_res = LV_COORD_MIN;
196     }
197 
198     lv_coord_t self_w = lv_obj_get_self_width(obj);
199     self_w = self_w - (lv_obj_get_width(obj) - pad_right - pad_left - 2 * border_width);
200     self_w += lv_obj_get_scroll_x(obj);
201 
202     return LV_MAX(child_res, self_w);
203 }
204 
lv_obj_get_scroll_right(lv_obj_t * obj)205 lv_coord_t lv_obj_get_scroll_right(lv_obj_t * obj)
206 {
207     LV_ASSERT_OBJ(obj, MY_CLASS);
208 
209     /*With RTL base dir can't scroll to the object out on the right.
210      *So simply use the current scroll position as "right size"*/
211     if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL) {
212         if(obj->spec_attr == NULL) return 0;
213         return obj->spec_attr->scroll.x;
214     }
215 
216     /*With other base direction (LTR) scrolling to the right is normal so find the right most coordinate*/
217     lv_coord_t child_res = LV_COORD_MIN;
218     uint32_t i;
219     uint32_t child_cnt = lv_obj_get_child_cnt(obj);
220     for(i = 0; i < child_cnt; i++) {
221         lv_obj_t * child = obj->spec_attr->children[i];
222         if(lv_obj_has_flag_any(child,  LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue;
223         child_res = LV_MAX(child_res, child->coords.x2);
224     }
225 
226     lv_coord_t pad_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
227     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
228     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
229 
230     if(child_res != LV_COORD_MIN) {
231         child_res -= (obj->coords.x2 - pad_right - border_width);
232     }
233 
234     lv_coord_t self_w;
235     self_w = lv_obj_get_self_width(obj);
236     self_w = self_w - (lv_obj_get_width(obj) - pad_right - pad_left - 2 * border_width);
237     self_w -= lv_obj_get_scroll_x(obj);
238     return LV_MAX(child_res, self_w);
239 }
240 
lv_obj_get_scroll_end(struct _lv_obj_t * obj,lv_point_t * end)241 void lv_obj_get_scroll_end(struct _lv_obj_t  * obj, lv_point_t * end)
242 {
243     lv_anim_t * a;
244     a = lv_anim_get(obj, scroll_x_anim);
245     end->x = a ? -a->end_value : lv_obj_get_scroll_x(obj);
246 
247     a = lv_anim_get(obj, scroll_y_anim);
248     end->y = a ? -a->end_value : lv_obj_get_scroll_y(obj);
249 }
250 
251 /*=====================
252  * Other functions
253  *====================*/
254 
lv_obj_scroll_by_bounded(lv_obj_t * obj,lv_coord_t dx,lv_coord_t dy,lv_anim_enable_t anim_en)255 void lv_obj_scroll_by_bounded(lv_obj_t * obj, lv_coord_t dx, lv_coord_t dy, lv_anim_enable_t anim_en)
256 {
257     if(dx == 0 && dy == 0) return;
258 
259     /*We need to know the final sizes for bound check*/
260     lv_obj_update_layout(obj);
261 
262     /*Don't let scroll more then naturally possible by the size of the content*/
263     lv_coord_t x_current = -lv_obj_get_scroll_x(obj);
264     lv_coord_t x_bounded = x_current + dx;
265 
266     if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL) {
267         if(x_bounded > 0) x_bounded = 0;
268         if(x_bounded < 0) {
269             lv_coord_t  scroll_max = lv_obj_get_scroll_left(obj) + lv_obj_get_scroll_right(obj);
270             if(scroll_max < 0) scroll_max = 0;
271 
272             if(x_bounded < -scroll_max) x_bounded = -scroll_max;
273         }
274     }
275     else {
276         if(x_bounded < 0) x_bounded = 0;
277         if(x_bounded > 0) {
278             lv_coord_t  scroll_max = lv_obj_get_scroll_left(obj) + lv_obj_get_scroll_right(obj);
279             if(scroll_max < 0) scroll_max = 0;
280 
281             if(x_bounded > scroll_max) x_bounded = scroll_max;
282         }
283     }
284 
285     /*Don't let scroll more then naturally possible by the size of the content*/
286     lv_coord_t y_current = -lv_obj_get_scroll_y(obj);
287     lv_coord_t y_bounded = y_current + dy;
288 
289     if(y_bounded > 0) y_bounded = 0;
290     if(y_bounded < 0) {
291         lv_coord_t  scroll_max = lv_obj_get_scroll_top(obj) + lv_obj_get_scroll_bottom(obj);
292         if(scroll_max < 0) scroll_max = 0;
293         if(y_bounded < -scroll_max) y_bounded = -scroll_max;
294     }
295 
296     dx = x_bounded - x_current;
297     dy = y_bounded - y_current;
298     if(dx || dy) {
299         lv_obj_scroll_by(obj, dx, dy, anim_en);
300     }
301 }
302 
303 
lv_obj_scroll_by(lv_obj_t * obj,lv_coord_t dx,lv_coord_t dy,lv_anim_enable_t anim_en)304 void lv_obj_scroll_by(lv_obj_t * obj, lv_coord_t dx, lv_coord_t dy, lv_anim_enable_t anim_en)
305 {
306     if(dx == 0 && dy == 0) return;
307     if(anim_en == LV_ANIM_ON) {
308         lv_disp_t * d = lv_obj_get_disp(obj);
309         lv_anim_t a;
310         lv_anim_init(&a);
311         lv_anim_set_var(&a, obj);
312         lv_anim_set_ready_cb(&a, scroll_anim_ready_cb);
313 
314         if(dx) {
315             uint32_t t = lv_anim_speed_to_time((lv_disp_get_hor_res(d) * 2) >> 2, 0, dx);
316             if(t < SCROLL_ANIM_TIME_MIN) t = SCROLL_ANIM_TIME_MIN;
317             if(t > SCROLL_ANIM_TIME_MAX) t = SCROLL_ANIM_TIME_MAX;
318             lv_anim_set_time(&a, t);
319             lv_coord_t sx = lv_obj_get_scroll_x(obj);
320             lv_anim_set_values(&a, -sx, -sx + dx);
321             lv_anim_set_exec_cb(&a, scroll_x_anim);
322             lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
323 
324             lv_res_t res;
325             res = lv_event_send(obj, LV_EVENT_SCROLL_BEGIN, &a);
326             if(res != LV_RES_OK) return;
327             lv_anim_start(&a);
328         }
329 
330         if(dy) {
331             uint32_t t = lv_anim_speed_to_time((lv_disp_get_ver_res(d) * 2) >> 2, 0, dy);
332             if(t < SCROLL_ANIM_TIME_MIN) t = SCROLL_ANIM_TIME_MIN;
333             if(t > SCROLL_ANIM_TIME_MAX) t = SCROLL_ANIM_TIME_MAX;
334             lv_anim_set_time(&a, t);
335             lv_coord_t sy = lv_obj_get_scroll_y(obj);
336             lv_anim_set_values(&a, -sy, -sy + dy);
337             lv_anim_set_exec_cb(&a,  scroll_y_anim);
338             lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
339 
340             lv_res_t res;
341             res = lv_event_send(obj, LV_EVENT_SCROLL_BEGIN, &a);
342             if(res != LV_RES_OK) return;
343             lv_anim_start(&a);
344         }
345     }
346     else {
347         /*Remove pending animations*/
348         bool y_del = lv_anim_del(obj, scroll_y_anim);
349         bool x_del = lv_anim_del(obj, scroll_x_anim);
350         scroll_by_raw(obj, dx, dy);
351         if(y_del || x_del) {
352             lv_res_t res;
353             res = lv_event_send(obj, LV_EVENT_SCROLL_END, NULL);
354             if(res != LV_RES_OK) return;
355         }
356     }
357 }
358 
lv_obj_scroll_to(lv_obj_t * obj,lv_coord_t x,lv_coord_t y,lv_anim_enable_t anim_en)359 void lv_obj_scroll_to(lv_obj_t * obj, lv_coord_t x, lv_coord_t y, lv_anim_enable_t anim_en)
360 {
361     lv_obj_scroll_to_x(obj, x, anim_en);
362     lv_obj_scroll_to_y(obj, y, anim_en);
363 }
364 
lv_obj_scroll_to_x(lv_obj_t * obj,lv_coord_t x,lv_anim_enable_t anim_en)365 void lv_obj_scroll_to_x(lv_obj_t * obj, lv_coord_t x, lv_anim_enable_t anim_en)
366 {
367     lv_anim_del(obj, scroll_x_anim);
368 
369     lv_coord_t scroll_x = lv_obj_get_scroll_x(obj);
370     lv_coord_t diff = -x + scroll_x;
371 
372     lv_obj_scroll_by_bounded(obj, diff, 0, anim_en);
373 }
374 
lv_obj_scroll_to_y(lv_obj_t * obj,lv_coord_t y,lv_anim_enable_t anim_en)375 void lv_obj_scroll_to_y(lv_obj_t * obj, lv_coord_t y, lv_anim_enable_t anim_en)
376 {
377     lv_anim_del(obj, scroll_y_anim);
378 
379     lv_coord_t scroll_y = lv_obj_get_scroll_y(obj);
380     lv_coord_t diff = -y + scroll_y;
381 
382     lv_obj_scroll_by_bounded(obj, 0, diff, anim_en);
383 }
384 
lv_obj_scroll_to_view(lv_obj_t * obj,lv_anim_enable_t anim_en)385 void lv_obj_scroll_to_view(lv_obj_t * obj, lv_anim_enable_t anim_en)
386 {
387     /*Be sure the screens layout is correct*/
388     lv_obj_update_layout(obj);
389 
390     lv_point_t p = {0, 0};
391     scroll_area_into_view(&obj->coords, obj, &p, anim_en);
392 }
393 
lv_obj_scroll_to_view_recursive(lv_obj_t * obj,lv_anim_enable_t anim_en)394 void lv_obj_scroll_to_view_recursive(lv_obj_t * obj, lv_anim_enable_t anim_en)
395 {
396     /*Be sure the screens layout is correct*/
397     lv_obj_update_layout(obj);
398 
399     lv_point_t p = {0, 0};
400     lv_obj_t * child = obj;
401     lv_obj_t * parent = lv_obj_get_parent(child);
402     while(parent) {
403         scroll_area_into_view(&obj->coords, child, &p, anim_en);
404         child = parent;
405         parent = lv_obj_get_parent(parent);
406     }
407 }
408 
lv_obj_is_scrolling(const lv_obj_t * obj)409 bool lv_obj_is_scrolling(const lv_obj_t * obj)
410 {
411     lv_indev_t * indev = lv_indev_get_next(NULL);
412     while(indev) {
413         if(lv_indev_get_scroll_obj(indev) == obj) return true;
414         indev = lv_indev_get_next(indev);
415     }
416 
417     return false;
418 }
419 
lv_obj_update_snap(lv_obj_t * obj,lv_anim_enable_t anim_en)420 void lv_obj_update_snap(lv_obj_t * obj, lv_anim_enable_t anim_en)
421 {
422     lv_obj_update_layout(obj);
423     lv_point_t p;
424     lv_indev_scroll_get_snap_dist(obj, &p);
425     lv_obj_scroll_by(obj, p.x, p.y, anim_en);
426 }
427 
lv_obj_get_scrollbar_area(lv_obj_t * obj,lv_area_t * hor_area,lv_area_t * ver_area)428 void lv_obj_get_scrollbar_area(lv_obj_t * obj, lv_area_t * hor_area, lv_area_t * ver_area)
429 {
430     lv_area_set(hor_area, 0, 0, -1, -1);
431     lv_area_set(ver_area, 0, 0, -1, -1);
432 
433     if(lv_obj_has_flag(obj, LV_OBJ_FLAG_SCROLLABLE) == false) return;
434 
435     lv_dir_t sm = lv_obj_get_scrollbar_mode(obj);
436     if(sm == LV_SCROLLBAR_MODE_OFF)  return;
437 
438     /*If there is no indev scrolling this object but `mode==active` return*/
439     lv_indev_t * indev = lv_indev_get_next(NULL);
440     if(sm == LV_SCROLLBAR_MODE_ACTIVE) {
441         while(indev) {
442             if(lv_indev_get_scroll_obj(indev) == obj) break;
443             indev = lv_indev_get_next(indev);
444         }
445         if(indev == NULL)  return;
446     }
447 
448     lv_coord_t st = lv_obj_get_scroll_top(obj);
449     lv_coord_t sb = lv_obj_get_scroll_bottom(obj);
450     lv_coord_t sl = lv_obj_get_scroll_left(obj);
451     lv_coord_t sr = lv_obj_get_scroll_right(obj);
452 
453     lv_dir_t dir = lv_obj_get_scroll_dir(obj);
454 
455     bool ver_draw = false;
456     if((dir & LV_DIR_VER) &&
457        ((sm == LV_SCROLLBAR_MODE_ON) ||
458         (sm == LV_SCROLLBAR_MODE_AUTO && (st > 0 || sb > 0)) ||
459         (sm == LV_SCROLLBAR_MODE_ACTIVE && lv_indev_get_scroll_dir(indev) == LV_DIR_VER))) {
460         ver_draw = true;
461     }
462 
463 
464     bool hor_draw = false;
465     if((dir & LV_DIR_HOR) &&
466        ((sm == LV_SCROLLBAR_MODE_ON) ||
467         (sm == LV_SCROLLBAR_MODE_AUTO && (sl > 0 || sr > 0)) ||
468         (sm == LV_SCROLLBAR_MODE_ACTIVE && lv_indev_get_scroll_dir(indev) == LV_DIR_HOR))) {
469         hor_draw = true;
470     }
471 
472     if(!hor_draw && !ver_draw) return;
473 
474     bool rtl = lv_obj_get_style_base_dir(obj, LV_PART_SCROLLBAR) == LV_BASE_DIR_RTL ? true : false;
475 
476     lv_coord_t top_space = lv_obj_get_style_pad_top(obj, LV_PART_SCROLLBAR);
477     lv_coord_t bottom_space = lv_obj_get_style_pad_bottom(obj, LV_PART_SCROLLBAR);
478     lv_coord_t left_space = lv_obj_get_style_pad_left(obj, LV_PART_SCROLLBAR);
479     lv_coord_t right_space = lv_obj_get_style_pad_right(obj, LV_PART_SCROLLBAR);
480     lv_coord_t tickness = lv_obj_get_style_width(obj, LV_PART_SCROLLBAR);
481 
482     lv_coord_t obj_h = lv_obj_get_height(obj);
483     lv_coord_t obj_w = lv_obj_get_width(obj);
484 
485     /*Space required for the vertical and horizontal scrollbars*/
486     lv_coord_t ver_reg_space = ver_draw ? tickness : 0;
487     lv_coord_t hor_req_space = hor_draw ? tickness : 0;
488     lv_coord_t rem;
489 
490     if(lv_obj_get_style_bg_opa(obj, LV_PART_SCROLLBAR) < LV_OPA_MIN &&
491        lv_obj_get_style_border_opa(obj, LV_PART_SCROLLBAR) < LV_OPA_MIN) {
492         return;
493     }
494 
495     /*Draw vertical scrollbar if the mode is ON or can be scrolled in this direction*/
496     lv_coord_t content_h = obj_h + st + sb;
497     if(ver_draw && content_h) {
498         ver_area->y1 = obj->coords.y1;
499         ver_area->y2 = obj->coords.y2;
500         if(rtl) {
501             ver_area->x1 = obj->coords.x1 + left_space;
502             ver_area->x2 = ver_area->x1 + tickness - 1;
503         }
504         else {
505             ver_area->x2 = obj->coords.x2 - right_space;
506             ver_area->x1 = ver_area->x2 - tickness + 1;
507         }
508 
509         lv_coord_t sb_h = ((obj_h - top_space - bottom_space - hor_req_space) * obj_h) / content_h;
510         sb_h = LV_MAX(sb_h, SCROLLBAR_MIN_SIZE);
511         rem = (obj_h - top_space - bottom_space - hor_req_space) -
512               sb_h;  /*Remaining size from the scrollbar track that is not the scrollbar itself*/
513         lv_coord_t scroll_h = content_h - obj_h; /*The size of the content which can be really scrolled*/
514         if(scroll_h <= 0) {
515             ver_area->y1 = obj->coords.y1 + top_space;
516             ver_area->y2 = obj->coords.y2 - bottom_space - hor_req_space - 1;
517         }
518         else {
519             lv_coord_t sb_y = (rem * sb) / scroll_h;
520             sb_y = rem - sb_y;
521 
522             ver_area->y1 = obj->coords.y1 + sb_y + top_space;
523             ver_area->y2 = ver_area->y1 + sb_h - 1;
524             if(ver_area->y1 < obj->coords.y1 + top_space) {
525                 ver_area->y1 = obj->coords.y1 + top_space;
526                 if(ver_area->y1 + SCROLLBAR_MIN_SIZE > ver_area->y2) {
527                     ver_area->y2 = ver_area->y1 + SCROLLBAR_MIN_SIZE;
528                 }
529             }
530             if(ver_area->y2 > obj->coords.y2 - hor_req_space - bottom_space) {
531                 ver_area->y2 = obj->coords.y2 - hor_req_space - bottom_space;
532                 if(ver_area->y2 - SCROLLBAR_MIN_SIZE < ver_area->y1) {
533                     ver_area->y1 = ver_area->y2 - SCROLLBAR_MIN_SIZE;
534                 }
535             }
536         }
537     }
538 
539     /*Draw horizontal scrollbar if the mode is ON or can be scrolled in this direction*/
540     lv_coord_t content_w = obj_w + sl + sr;
541     if(hor_draw && content_w) {
542         hor_area->y2 = obj->coords.y2 - bottom_space;
543         hor_area->y1 = hor_area->y2 - tickness + 1;
544         hor_area->x1 = obj->coords.x1;
545         hor_area->x2 = obj->coords.x2;
546 
547         lv_coord_t sb_w = ((obj_w - left_space - right_space - ver_reg_space) * obj_w) / content_w;
548         sb_w = LV_MAX(sb_w, SCROLLBAR_MIN_SIZE);
549         rem = (obj_w - left_space - right_space - ver_reg_space) -
550               sb_w;  /*Remaining size from the scrollbar track that is not the scrollbar itself*/
551         lv_coord_t scroll_w = content_w - obj_w; /*The size of the content which can be really scrolled*/
552         if(scroll_w <= 0) {
553             if(rtl) {
554                 hor_area->x1 = obj->coords.x1 + left_space + ver_reg_space - 1;
555                 hor_area->x2 = obj->coords.x2 - right_space;
556             }
557             else {
558                 hor_area->x1 = obj->coords.x1 + left_space;
559                 hor_area->x2 = obj->coords.x2 - right_space - ver_reg_space - 1;
560             }
561         }
562         else {
563             lv_coord_t sb_x = (rem * sr) / scroll_w;
564             sb_x = rem - sb_x;
565 
566             if(rtl) {
567                 hor_area->x1 = obj->coords.x1 + sb_x + left_space + ver_reg_space;
568                 hor_area->x2 = hor_area->x1 + sb_w - 1;
569                 if(hor_area->x1 < obj->coords.x1 + left_space + ver_reg_space) {
570                     hor_area->x1 = obj->coords.x1 + left_space + ver_reg_space;
571                     if(hor_area->x1 + SCROLLBAR_MIN_SIZE > hor_area->x2) {
572                         hor_area->x2 = hor_area->x1 + SCROLLBAR_MIN_SIZE;
573                     }
574                 }
575                 if(hor_area->x2 > obj->coords.x2 - right_space) {
576                     hor_area->x2 = obj->coords.x2 - right_space;
577                     if(hor_area->x2 - SCROLLBAR_MIN_SIZE < hor_area->x1) {
578                         hor_area->x1 = hor_area->x2 - SCROLLBAR_MIN_SIZE;
579                     }
580                 }
581             }
582             else {
583                 hor_area->x1 = obj->coords.x1 + sb_x + left_space;
584                 hor_area->x2 = hor_area->x1 + sb_w - 1;
585                 if(hor_area->x1 < obj->coords.x1 + left_space) {
586                     hor_area->x1 = obj->coords.x1 + left_space;
587                     if(hor_area->x1 + SCROLLBAR_MIN_SIZE > hor_area->x2) {
588                         hor_area->x2 = hor_area->x1 + SCROLLBAR_MIN_SIZE;
589                     }
590                 }
591                 if(hor_area->x2 > obj->coords.x2 - ver_reg_space - right_space) {
592                     hor_area->x2 = obj->coords.x2 - ver_reg_space - right_space;
593                     if(hor_area->x2 - SCROLLBAR_MIN_SIZE < hor_area->x1) {
594                         hor_area->x1 = hor_area->x2 - SCROLLBAR_MIN_SIZE;
595                     }
596                 }
597             }
598         }
599     }
600 }
601 
lv_obj_scrollbar_invalidate(lv_obj_t * obj)602 void lv_obj_scrollbar_invalidate(lv_obj_t * obj)
603 {
604     lv_area_t hor_area;
605     lv_area_t ver_area;
606     lv_obj_get_scrollbar_area(obj, &hor_area, &ver_area);
607 
608     if(lv_area_get_size(&hor_area) <= 0 && lv_area_get_size(&ver_area) <= 0) return;
609 
610     if(lv_area_get_size(&hor_area) > 0) lv_obj_invalidate_area(obj, &hor_area);
611     if(lv_area_get_size(&ver_area) > 0) lv_obj_invalidate_area(obj, &ver_area);
612 }
613 
lv_obj_readjust_scroll(lv_obj_t * obj,lv_anim_enable_t anim_en)614 void lv_obj_readjust_scroll(lv_obj_t * obj, lv_anim_enable_t anim_en)
615 {
616     /*Be sure the bottom side is not remains scrolled in*/
617     /*With snapping the content can't be scrolled in*/
618     if(lv_obj_get_scroll_snap_y(obj) == LV_SCROLL_SNAP_NONE) {
619         lv_coord_t st = lv_obj_get_scroll_top(obj);
620         lv_coord_t sb = lv_obj_get_scroll_bottom(obj);
621         if(sb < 0 && st > 0) {
622             sb = LV_MIN(st, -sb);
623             lv_obj_scroll_by(obj, 0, sb, anim_en);
624         }
625     }
626 
627     if(lv_obj_get_scroll_snap_x(obj) == LV_SCROLL_SNAP_NONE) {
628         lv_coord_t sl = lv_obj_get_scroll_left(obj);
629         lv_coord_t sr = lv_obj_get_scroll_right(obj);
630         if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL) {
631             /*Be sure the left side is not remains scrolled in*/
632             if(sr < 0 && sl > 0) {
633                 sr = LV_MIN(sl, -sr);
634                 lv_obj_scroll_by(obj, sr, 0, anim_en);
635             }
636         }
637         else {
638             /*Be sure the right side is not remains scrolled in*/
639             if(sl < 0 && sr > 0) {
640                 sr = LV_MIN(sr, -sl);
641                 lv_obj_scroll_by(obj, sl, 0, anim_en);
642             }
643         }
644     }
645 }
646 
647 /**********************
648  *   STATIC FUNCTIONS
649  **********************/
650 
scroll_by_raw(lv_obj_t * obj,lv_coord_t x,lv_coord_t y)651 static void scroll_by_raw(lv_obj_t * obj, lv_coord_t x, lv_coord_t y)
652 {
653     if(x == 0 && y == 0) return;
654 
655     lv_obj_allocate_spec_attr(obj);
656 
657     obj->spec_attr->scroll.x += x;
658     obj->spec_attr->scroll.y += y;
659 
660     lv_obj_move_children_by(obj, x, y, true);
661     lv_res_t res = lv_event_send(obj, LV_EVENT_SCROLL, NULL);
662     if(res != LV_RES_OK) return;
663     lv_obj_invalidate(obj);
664 }
665 
scroll_x_anim(void * obj,int32_t v)666 static void scroll_x_anim(void * obj, int32_t v)
667 {
668     scroll_by_raw(obj, v + lv_obj_get_scroll_x(obj), 0);
669 }
670 
scroll_y_anim(void * obj,int32_t v)671 static void scroll_y_anim(void * obj, int32_t v)
672 {
673     scroll_by_raw(obj, 0, v + lv_obj_get_scroll_y(obj));
674 }
675 
scroll_anim_ready_cb(lv_anim_t * a)676 static void scroll_anim_ready_cb(lv_anim_t * a)
677 {
678     lv_event_send(a->var, LV_EVENT_SCROLL_END, NULL);
679 }
680 
scroll_area_into_view(const lv_area_t * area,lv_obj_t * child,lv_point_t * scroll_value,lv_anim_enable_t anim_en)681 static void scroll_area_into_view(const lv_area_t * area, lv_obj_t * child, lv_point_t * scroll_value,
682                                   lv_anim_enable_t anim_en)
683 {
684     lv_obj_t * parent = lv_obj_get_parent(child);
685     if(!lv_obj_has_flag(parent, LV_OBJ_FLAG_SCROLLABLE)) return;
686 
687     lv_dir_t scroll_dir = lv_obj_get_scroll_dir(parent);
688     lv_coord_t snap_goal = 0;
689     lv_coord_t act = 0;
690     const lv_area_t * area_tmp;
691 
692     lv_coord_t y_scroll = 0;
693     lv_scroll_snap_t snap_y = lv_obj_get_scroll_snap_y(parent);
694     if(snap_y != LV_SCROLL_SNAP_NONE) area_tmp = &child->coords;
695     else area_tmp = area;
696 
697     lv_coord_t border_width = lv_obj_get_style_border_width(parent, LV_PART_MAIN);
698     lv_coord_t ptop = lv_obj_get_style_pad_top(parent, LV_PART_MAIN) + border_width;
699     lv_coord_t pbottom = lv_obj_get_style_pad_bottom(parent, LV_PART_MAIN) + border_width;
700     lv_coord_t top_diff = parent->coords.y1 + ptop - area_tmp->y1 - scroll_value->y;
701     lv_coord_t bottom_diff = -(parent->coords.y2 - pbottom - area_tmp->y2 - scroll_value->y);
702     lv_coord_t parent_h = lv_obj_get_height(parent) - ptop - pbottom;
703     if((top_diff >= 0 && bottom_diff >= 0)) y_scroll = 0;
704     else if(top_diff > 0) {
705         y_scroll = top_diff;
706         /*Do not let scrolling in*/
707         lv_coord_t st = lv_obj_get_scroll_top(parent);
708         if(st - y_scroll < 0) y_scroll = 0;
709     }
710     else if(bottom_diff > 0) {
711         y_scroll = -bottom_diff;
712         /*Do not let scrolling in*/
713         lv_coord_t sb = lv_obj_get_scroll_bottom(parent);
714         if(sb + y_scroll < 0) y_scroll = 0;
715     }
716 
717     switch(snap_y) {
718         case LV_SCROLL_SNAP_START:
719             snap_goal = parent->coords.y1 + ptop;
720             act = area_tmp->y1 + y_scroll;
721             y_scroll += snap_goal - act;
722             break;
723         case LV_SCROLL_SNAP_END:
724             snap_goal = parent->coords.y2 - pbottom;
725             act = area_tmp->y2 + y_scroll;
726             y_scroll += snap_goal - act;
727             break;
728         case LV_SCROLL_SNAP_CENTER:
729             snap_goal = parent->coords.y1 + ptop + parent_h / 2;
730             act = lv_area_get_height(area_tmp) / 2 + area_tmp->y1 + y_scroll;
731             y_scroll += snap_goal - act;
732             break;
733     }
734 
735     lv_coord_t x_scroll = 0;
736     lv_scroll_snap_t snap_x = lv_obj_get_scroll_snap_x(parent);
737     if(snap_x != LV_SCROLL_SNAP_NONE) area_tmp = &child->coords;
738     else area_tmp = area;
739 
740     lv_coord_t pleft = lv_obj_get_style_pad_left(parent, LV_PART_MAIN) + border_width;
741     lv_coord_t pright = lv_obj_get_style_pad_right(parent, LV_PART_MAIN) + border_width;
742     lv_coord_t left_diff = parent->coords.x1 + pleft - area_tmp->x1 - scroll_value->x;
743     lv_coord_t right_diff = -(parent->coords.x2 - pright - area_tmp->x2 - scroll_value->x);
744     if((left_diff >= 0 && right_diff >= 0)) x_scroll = 0;
745     else if(left_diff > 0) {
746         x_scroll = left_diff;
747         /*Do not let scrolling in*/
748         lv_coord_t sl = lv_obj_get_scroll_left(parent);
749         if(sl + x_scroll > 0) x_scroll = 0;
750     }
751     else if(right_diff > 0) {
752         x_scroll = -right_diff;
753         /*Do not let scrolling in*/
754         lv_coord_t sr = lv_obj_get_scroll_right(parent);
755         if(sr + x_scroll < 0) x_scroll = 0;
756     }
757 
758     lv_coord_t parent_w = lv_obj_get_width(parent) - pleft - pright;
759     switch(snap_x) {
760         case LV_SCROLL_SNAP_START:
761             snap_goal = parent->coords.x1 + pleft;
762             act = area_tmp->x1 + x_scroll;
763             x_scroll += snap_goal - act;
764             break;
765         case LV_SCROLL_SNAP_END:
766             snap_goal = parent->coords.x2 - pright;
767             act = area_tmp->x2 + x_scroll;
768             x_scroll += snap_goal - act;
769             break;
770         case LV_SCROLL_SNAP_CENTER:
771             snap_goal = parent->coords.x1 + pleft + parent_w / 2;
772             act = lv_area_get_width(area_tmp) / 2 + area_tmp->x1 + x_scroll;
773             x_scroll += snap_goal - act;
774             break;
775     }
776 
777     /*Remove any pending scroll animations.*/
778     bool y_del = lv_anim_del(parent, scroll_y_anim);
779     bool x_del = lv_anim_del(parent, scroll_x_anim);
780     if(y_del || x_del) {
781         lv_res_t res;
782         res = lv_event_send(parent, LV_EVENT_SCROLL_END, NULL);
783         if(res != LV_RES_OK) return;
784     }
785 
786     if((scroll_dir & LV_DIR_LEFT) == 0 && x_scroll < 0) x_scroll = 0;
787     if((scroll_dir & LV_DIR_RIGHT) == 0 && x_scroll > 0) x_scroll = 0;
788     if((scroll_dir & LV_DIR_TOP) == 0 && y_scroll < 0) y_scroll = 0;
789     if((scroll_dir & LV_DIR_BOTTOM) == 0 && y_scroll > 0) y_scroll = 0;
790 
791     scroll_value->x += anim_en == LV_ANIM_OFF ? 0 : x_scroll;
792     scroll_value->y += anim_en == LV_ANIM_OFF ? 0 : y_scroll;
793     lv_obj_scroll_by(parent, x_scroll, y_scroll, anim_en);
794 }
795