acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

lv_roller.c (27972B)

      1 /**
      2  * @file lv_roller.c
      3  *
      4  */
      5 
      6 /*********************
      7  *      INCLUDES
      8  *********************/
      9 #include "lv_roller.h"
     10 #if LV_USE_ROLLER != 0
     11 
     12 #include "../misc/lv_assert.h"
     13 #include "../draw/lv_draw.h"
     14 #include "../core/lv_group.h"
     15 #include "../core/lv_indev.h"
     16 #include "../core/lv_indev_scroll.h"
     17 
     18 /*********************
     19  *      DEFINES
     20  *********************/
     21 #define MY_CLASS &lv_roller_class
     22 #define MY_CLASS_LABEL &lv_roller_label_class
     23 
     24 /**********************
     25  *      TYPEDEFS
     26  **********************/
     27 
     28 /**********************
     29  *  STATIC PROTOTYPES
     30  **********************/
     31 static void lv_roller_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
     32 static void lv_roller_event(const lv_obj_class_t * class_p, lv_event_t * e);
     33 static void lv_roller_label_event(const lv_obj_class_t * class_p, lv_event_t * e);
     34 static void draw_main(lv_event_t * e);
     35 static void draw_label(lv_event_t * e);
     36 static void get_sel_area(lv_obj_t * obj, lv_area_t * sel_area);
     37 static void refr_position(lv_obj_t * obj, lv_anim_enable_t animen);
     38 static lv_res_t release_handler(lv_obj_t * obj);
     39 static void inf_normalize(lv_obj_t * obj_scrl);
     40 static lv_obj_t * get_label(const lv_obj_t * obj);
     41 static lv_coord_t get_selected_label_width(const lv_obj_t * obj);
     42 static void scroll_anim_ready_cb(lv_anim_t * a);
     43 static void set_y_anim(void * obj, int32_t v);
     44 
     45 /**********************
     46  *  STATIC VARIABLES
     47  **********************/
     48 const lv_obj_class_t lv_roller_class = {
     49     .constructor_cb = lv_roller_constructor,
     50     .event_cb = lv_roller_event,
     51     .width_def = LV_SIZE_CONTENT,
     52     .height_def = LV_DPI_DEF,
     53     .instance_size = sizeof(lv_roller_t),
     54     .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
     55     .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
     56     .base_class = &lv_obj_class
     57 };
     58 
     59 const lv_obj_class_t lv_roller_label_class  = {
     60     .event_cb = lv_roller_label_event,
     61     .instance_size = sizeof(lv_label_t),
     62     .base_class = &lv_label_class
     63 };
     64 
     65 /**********************
     66  *      MACROS
     67  **********************/
     68 
     69 /**********************
     70  *   GLOBAL FUNCTIONS
     71  **********************/
     72 
     73 /**
     74  * Create a roller object
     75  * @param parent pointer to an object, it will be the parent of the new roller
     76  * @return pointer to the created roller
     77  */
     78 lv_obj_t * lv_roller_create(lv_obj_t * parent)
     79 {
     80     LV_LOG_INFO("begin");
     81     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
     82     lv_obj_class_init_obj(obj);
     83     return obj;
     84 }
     85 
     86 /*=====================
     87  * Setter functions
     88  *====================*/
     89 
     90 /**
     91  * Set the options on a roller
     92  * @param roller pointer to roller object
     93  * @param options a string with '\n' separated options. E.g. "One\nTwo\nThree"
     94  * @param mode `LV_ROLLER_MODE_NORMAL` or `LV_ROLLER_MODE_INFINITE`
     95  */
     96 void lv_roller_set_options(lv_obj_t * obj, const char * options, lv_roller_mode_t mode)
     97 {
     98     LV_ASSERT_OBJ(obj, MY_CLASS);
     99     LV_ASSERT_NULL(options);
    100 
    101     lv_roller_t * roller = (lv_roller_t *)obj;
    102     lv_obj_t * label = get_label(obj);
    103 
    104     roller->sel_opt_id     = 0;
    105     roller->sel_opt_id_ori = 0;
    106 
    107     /*Count the '\n'-s to determine the number of options*/
    108     roller->option_cnt = 0;
    109     uint32_t cnt;
    110     for(cnt = 0; options[cnt] != '\0'; cnt++) {
    111         if(options[cnt] == '\n') roller->option_cnt++;
    112     }
    113     roller->option_cnt++; /*Last option has no `\n`*/
    114 
    115     if(mode == LV_ROLLER_MODE_NORMAL) {
    116         roller->mode = LV_ROLLER_MODE_NORMAL;
    117         lv_label_set_text(label, options);
    118     }
    119     else {
    120         roller->mode = LV_ROLLER_MODE_INFINITE;
    121 
    122         size_t opt_len = strlen(options) + 1; /*+1 to add '\n' after option lists*/
    123         char * opt_extra = lv_mem_buf_get(opt_len * LV_ROLLER_INF_PAGES);
    124         uint8_t i;
    125         for(i = 0; i < LV_ROLLER_INF_PAGES; i++) {
    126             strcpy(&opt_extra[opt_len * i], options);
    127             opt_extra[opt_len * (i + 1) - 1] = '\n';
    128         }
    129         opt_extra[opt_len * LV_ROLLER_INF_PAGES - 1] = '\0';
    130         lv_label_set_text(label, opt_extra);
    131         lv_mem_buf_release(opt_extra);
    132 
    133         roller->sel_opt_id     = ((LV_ROLLER_INF_PAGES / 2) + 0) * roller->option_cnt;
    134 
    135         roller->option_cnt = roller->option_cnt * LV_ROLLER_INF_PAGES;
    136         inf_normalize(obj);
    137     }
    138 
    139     roller->sel_opt_id_ori = roller->sel_opt_id;
    140 
    141     /*If the selected text has larger font the label needs some extra draw padding to draw it.*/
    142     lv_obj_refresh_ext_draw_size(label);
    143 
    144 }
    145 
    146 /**
    147  * Set the selected option
    148  * @param roller pointer to a roller object
    149  * @param sel_opt id of the selected option (0 ... number of option - 1);
    150  * @param anim_en LV_ANIM_ON: set with animation; LV_ANOM_OFF set immediately
    151  */
    152 void lv_roller_set_selected(lv_obj_t * obj, uint16_t sel_opt, lv_anim_enable_t anim)
    153 {
    154     LV_ASSERT_OBJ(obj, MY_CLASS);
    155 
    156     /*Set the value even if it's the same as the current value because
    157      *if moving to the next option with an animation which was just deleted in the PRESS Call the ancestor's event handler
    158      *nothing will continue the animation.*/
    159 
    160     lv_roller_t * roller = (lv_roller_t *)obj;
    161 
    162     /*In infinite mode interpret the new ID relative to the currently visible "page"*/
    163     if(roller->mode == LV_ROLLER_MODE_INFINITE) {
    164         uint32_t real_option_cnt = roller->option_cnt / LV_ROLLER_INF_PAGES;
    165         uint16_t current_page = roller->sel_opt_id / real_option_cnt;
    166         /*Set by the user to e.g. 0, 1, 2, 3...
    167          *Upscale the value to the current page*/
    168         if(sel_opt < real_option_cnt) {
    169             uint16_t act_opt = roller->sel_opt_id - current_page * real_option_cnt;
    170             int32_t sel_opt_signed = sel_opt;
    171             /*Huge jump? Probably from last to first or first to last option.*/
    172             if(LV_ABS((int16_t)act_opt - sel_opt) > real_option_cnt / 2) {
    173                 if(act_opt > sel_opt) sel_opt_signed += real_option_cnt;
    174                 else sel_opt_signed -= real_option_cnt;
    175             }
    176             sel_opt = sel_opt_signed + real_option_cnt * current_page;
    177         }
    178     }
    179 
    180     roller->sel_opt_id     = sel_opt < roller->option_cnt ? sel_opt : roller->option_cnt - 1;
    181     roller->sel_opt_id_ori = roller->sel_opt_id;
    182 
    183     refr_position(obj, anim);
    184 }
    185 
    186 /**
    187  * Set the height to show the given number of rows (options)
    188  * @param roller pointer to a roller object
    189  * @param row_cnt number of desired visible rows
    190  */
    191 void lv_roller_set_visible_row_count(lv_obj_t * obj, uint8_t row_cnt)
    192 {
    193     LV_ASSERT_OBJ(obj, MY_CLASS);
    194 
    195     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
    196     lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
    197     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    198     lv_obj_set_height(obj, (lv_font_get_line_height(font) + line_space) * row_cnt + 2 * border_width);
    199 }
    200 
    201 /*=====================
    202  * Getter functions
    203  *====================*/
    204 
    205 /**
    206  * Get the id of the selected option
    207  * @param roller pointer to a roller object
    208  * @return id of the selected option (0 ... number of option - 1);
    209  */
    210 uint16_t lv_roller_get_selected(const lv_obj_t * obj)
    211 {
    212     LV_ASSERT_OBJ(obj, MY_CLASS);
    213 
    214     lv_roller_t * roller = (lv_roller_t *)obj;
    215     if(roller->mode == LV_ROLLER_MODE_INFINITE) {
    216         uint16_t real_id_cnt = roller->option_cnt / LV_ROLLER_INF_PAGES;
    217         return roller->sel_opt_id % real_id_cnt;
    218     }
    219     else {
    220         return roller->sel_opt_id;
    221     }
    222 }
    223 
    224 /**
    225  * Get the current selected option as a string
    226  * @param ddlist pointer to ddlist object
    227  * @param buf pointer to an array to store the string
    228  * @param buf_size size of `buf` in bytes. 0: to ignore it.
    229  */
    230 void lv_roller_get_selected_str(const lv_obj_t * obj, char * buf, uint32_t buf_size)
    231 {
    232     LV_ASSERT_OBJ(obj, MY_CLASS);
    233 
    234     lv_roller_t * roller = (lv_roller_t *)obj;
    235     lv_obj_t * label = get_label(obj);
    236     uint32_t i;
    237     uint16_t line        = 0;
    238     const char * opt_txt = lv_label_get_text(label);
    239     size_t txt_len     = strlen(opt_txt);
    240 
    241     for(i = 0; i < txt_len && line != roller->sel_opt_id; i++) {
    242         if(opt_txt[i] == '\n') line++;
    243     }
    244 
    245     uint32_t c;
    246     for(c = 0; i < txt_len && opt_txt[i] != '\n'; c++, i++) {
    247         if(buf_size && c >= buf_size - 1) {
    248             LV_LOG_WARN("lv_dropdown_get_selected_str: the buffer was too small");
    249             break;
    250         }
    251         buf[c] = opt_txt[i];
    252     }
    253 
    254     buf[c] = '\0';
    255 }
    256 
    257 
    258 /**
    259  * Get the options of a roller
    260  * @param roller pointer to roller object
    261  * @return the options separated by '\n'-s (E.g. "Option1\nOption2\nOption3")
    262  */
    263 const char * lv_roller_get_options(const lv_obj_t * obj)
    264 {
    265     LV_ASSERT_OBJ(obj, MY_CLASS);
    266 
    267     return lv_label_get_text(get_label(obj));
    268 }
    269 
    270 
    271 /**
    272  * Get the total number of options
    273  * @param roller pointer to a roller object
    274  * @return the total number of options
    275  */
    276 uint16_t lv_roller_get_option_cnt(const lv_obj_t * obj)
    277 {
    278     LV_ASSERT_OBJ(obj, MY_CLASS);
    279 
    280     lv_roller_t * roller = (lv_roller_t *)obj;
    281     if(roller->mode == LV_ROLLER_MODE_INFINITE) {
    282         return roller->option_cnt / LV_ROLLER_INF_PAGES;
    283     }
    284     else {
    285         return roller->option_cnt;
    286     }
    287 }
    288 
    289 /**********************
    290  *   STATIC FUNCTIONS
    291  **********************/
    292 
    293 
    294 static void lv_roller_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
    295 {
    296     LV_UNUSED(class_p);
    297     lv_roller_t * roller = (lv_roller_t *)obj;
    298 
    299     roller->mode = LV_ROLLER_MODE_NORMAL;
    300     roller->option_cnt = 0;
    301     roller->sel_opt_id = 0;
    302     roller->sel_opt_id_ori = 0;
    303 
    304     lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
    305     lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN_VER);
    306 
    307     LV_LOG_INFO("begin");
    308     lv_obj_t * label = lv_obj_class_create_obj(&lv_roller_label_class, obj);
    309     lv_obj_class_init_obj(label);
    310     lv_roller_set_options(obj, "Option 1\nOption 2\nOption 3\nOption 4\nOption 5", LV_ROLLER_MODE_NORMAL);
    311 
    312     LV_LOG_TRACE("finshed");
    313 }
    314 
    315 static void lv_roller_event(const lv_obj_class_t * class_p, lv_event_t * e)
    316 {
    317     LV_UNUSED(class_p);
    318 
    319     lv_res_t res;
    320 
    321     /*Call the ancestor's event handler*/
    322     res = lv_obj_event_base(MY_CLASS, e);
    323     if(res != LV_RES_OK) return;
    324 
    325     lv_event_code_t code = lv_event_get_code(e);
    326     lv_obj_t * obj = lv_event_get_target(e);
    327     lv_roller_t * roller = (lv_roller_t *)obj;
    328 
    329     if(code == LV_EVENT_GET_SELF_SIZE) {
    330         lv_point_t * p = lv_event_get_param(e);
    331         p->x = get_selected_label_width(obj);
    332     }
    333     else if(code == LV_EVENT_STYLE_CHANGED) {
    334         lv_obj_t * label = get_label(obj);
    335         /*Be sure the label's style is updated before processing the roller*/
    336         if(label) lv_event_send(label, LV_EVENT_STYLE_CHANGED, NULL);
    337         lv_obj_refresh_self_size(obj);
    338         refr_position(obj, LV_ANIM_OFF);
    339     }
    340     else if(code == LV_EVENT_SIZE_CHANGED) {
    341         refr_position(obj, LV_ANIM_OFF);
    342     }
    343     else if(code == LV_EVENT_PRESSED) {
    344         roller->moved = 0;
    345         lv_anim_del(get_label(obj), set_y_anim);
    346     }
    347     else if(code == LV_EVENT_PRESSING) {
    348         lv_indev_t * indev = lv_indev_get_act();
    349         lv_point_t p;
    350         lv_indev_get_vect(indev, &p);
    351         if(p.y) {
    352             lv_obj_t * label = get_label(obj);
    353             lv_obj_set_y(label, lv_obj_get_y(label) + p.y);
    354             roller->moved = 1;
    355         }
    356     }
    357     else if(code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
    358         release_handler(obj);
    359     }
    360     else if(code == LV_EVENT_FOCUSED) {
    361         lv_group_t * g             = lv_obj_get_group(obj);
    362         bool editing               = lv_group_get_editing(g);
    363         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
    364 
    365         /*Encoders need special handling*/
    366         if(indev_type == LV_INDEV_TYPE_ENCODER) {
    367             /*In navigate mode revert the original value*/
    368             if(!editing) {
    369                 if(roller->sel_opt_id != roller->sel_opt_id_ori) {
    370                     roller->sel_opt_id = roller->sel_opt_id_ori;
    371                     refr_position(obj, LV_ANIM_ON);
    372                 }
    373             }
    374             /*Save the current state when entered to edit mode*/
    375             else {
    376                 roller->sel_opt_id_ori = roller->sel_opt_id;
    377             }
    378         }
    379         else {
    380             roller->sel_opt_id_ori = roller->sel_opt_id; /*Save the current value. Used to revert this state if
    381                                                                     ENTER won't be pressed*/
    382         }
    383     }
    384     else if(code == LV_EVENT_DEFOCUSED) {
    385         /*Revert the original state*/
    386         if(roller->sel_opt_id != roller->sel_opt_id_ori) {
    387             roller->sel_opt_id = roller->sel_opt_id_ori;
    388             refr_position(obj, LV_ANIM_ON);
    389         }
    390     }
    391     else if(code == LV_EVENT_KEY) {
    392         char c = *((char *)lv_event_get_param(e));
    393         if(c == LV_KEY_RIGHT || c == LV_KEY_DOWN) {
    394             if(roller->sel_opt_id + 1 < roller->option_cnt) {
    395                 uint16_t ori_id = roller->sel_opt_id_ori; /*lv_roller_set_selected will overwrite this*/
    396                 lv_roller_set_selected(obj, roller->sel_opt_id + 1, LV_ANIM_ON);
    397                 roller->sel_opt_id_ori = ori_id;
    398             }
    399         }
    400         else if(c == LV_KEY_LEFT || c == LV_KEY_UP) {
    401             if(roller->sel_opt_id > 0) {
    402                 uint16_t ori_id = roller->sel_opt_id_ori; /*lv_roller_set_selected will overwrite this*/
    403 
    404                 lv_roller_set_selected(obj, roller->sel_opt_id - 1, LV_ANIM_ON);
    405                 roller->sel_opt_id_ori = ori_id;
    406             }
    407         }
    408     }
    409     else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
    410         lv_obj_t * label = get_label(obj);
    411         lv_obj_refresh_ext_draw_size(label);
    412     }
    413     else if(code == LV_EVENT_DRAW_MAIN || code == LV_EVENT_DRAW_POST) {
    414         draw_main(e);
    415     }
    416 }
    417 
    418 static void lv_roller_label_event(const lv_obj_class_t * class_p, lv_event_t * e)
    419 {
    420     LV_UNUSED(class_p);
    421 
    422     lv_res_t res;
    423 
    424     lv_event_code_t code = lv_event_get_code(e);
    425     /*LV_EVENT_DRAW_MAIN will be called in the draw function*/
    426     if(code != LV_EVENT_DRAW_MAIN) {
    427         /* Call the ancestor's event handler */
    428         res = lv_obj_event_base(MY_CLASS_LABEL, e);
    429         if(res != LV_RES_OK) return;
    430     }
    431 
    432     lv_obj_t * label = lv_event_get_target(e);
    433     if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
    434         /*If the selected text has a larger font it needs some extra space to draw it*/
    435         lv_coord_t * s = lv_event_get_param(e);
    436         lv_obj_t * obj = lv_obj_get_parent(label);
    437         lv_coord_t sel_w = get_selected_label_width(obj);
    438         lv_coord_t label_w = lv_obj_get_width(label);
    439         *s = LV_MAX(*s, sel_w - label_w);
    440     }
    441     else if(code == LV_EVENT_SIZE_CHANGED) {
    442         refr_position(lv_obj_get_parent(label), LV_ANIM_OFF);
    443     }
    444     else if(code == LV_EVENT_DRAW_MAIN) {
    445         draw_label(e);
    446     }
    447 }
    448 
    449 
    450 static void draw_main(lv_event_t * e)
    451 {
    452     lv_event_code_t code = lv_event_get_code(e);
    453     lv_obj_t * obj = lv_event_get_target(e);
    454     if(code == LV_EVENT_DRAW_MAIN) {
    455         /*Draw the selected rectangle*/
    456         lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
    457         lv_area_t sel_area;
    458         get_sel_area(obj, &sel_area);
    459         lv_draw_rect_dsc_t sel_dsc;
    460         lv_draw_rect_dsc_init(&sel_dsc);
    461         lv_obj_init_draw_rect_dsc(obj, LV_PART_SELECTED, &sel_dsc);
    462         lv_draw_rect(draw_ctx, &sel_dsc, &sel_area);
    463     }
    464     /*Post draw when the children are drawn*/
    465     else if(code == LV_EVENT_DRAW_POST) {
    466         lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
    467 
    468         lv_draw_label_dsc_t label_dsc;
    469         lv_draw_label_dsc_init(&label_dsc);
    470         lv_obj_init_draw_label_dsc(obj, LV_PART_SELECTED, &label_dsc);
    471 
    472         /*Redraw the text on the selected area*/
    473         lv_area_t sel_area;
    474         get_sel_area(obj, &sel_area);
    475         lv_area_t mask_sel;
    476         bool area_ok;
    477         area_ok = _lv_area_intersect(&mask_sel, draw_ctx->clip_area, &sel_area);
    478         if(area_ok) {
    479             lv_obj_t * label = get_label(obj);
    480 
    481             /*Get the size of the "selected text"*/
    482             lv_point_t res_p;
    483             lv_txt_get_size(&res_p, lv_label_get_text(label), label_dsc.font, label_dsc.letter_space, label_dsc.line_space,
    484                             lv_obj_get_width(obj), LV_TEXT_FLAG_EXPAND);
    485 
    486             /*Move the selected label proportionally with the background label*/
    487             lv_coord_t roller_h = lv_obj_get_height(obj);
    488             int32_t label_y_prop = label->coords.y1 - (roller_h / 2 +
    489                                                        obj->coords.y1); /*label offset from the middle line of the roller*/
    490             label_y_prop = (label_y_prop * 16384) / lv_obj_get_height(
    491                                label); /*Proportional position from the middle line (upscaled by << 14)*/
    492 
    493             /*Apply a correction with different line heights*/
    494             const lv_font_t * normal_label_font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
    495             lv_coord_t corr = (label_dsc.font->line_height - normal_label_font->line_height) / 2;
    496 
    497             /*Apply the proportional position to the selected text*/
    498             res_p.y -= corr;
    499             int32_t label_sel_y = roller_h / 2 + obj->coords.y1;
    500             label_sel_y += (label_y_prop * res_p.y) >> 14;
    501             label_sel_y -= corr;
    502 
    503             lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    504             lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    505             lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
    506 
    507             /*Draw the selected text*/
    508             lv_area_t label_sel_area;
    509             label_sel_area.x1 = obj->coords.x1 + pleft + bwidth;
    510             label_sel_area.y1 = label_sel_y;
    511             label_sel_area.x2 = obj->coords.x2 - pright - bwidth;
    512             label_sel_area.y2 = label_sel_area.y1 + res_p.y;
    513 
    514             label_dsc.flag |= LV_TEXT_FLAG_EXPAND;
    515             const lv_area_t * clip_area_ori = draw_ctx->clip_area;
    516             draw_ctx->clip_area = &mask_sel;
    517             lv_draw_label(draw_ctx, &label_dsc, &label_sel_area, lv_label_get_text(label), NULL);
    518             draw_ctx->clip_area = clip_area_ori;
    519         }
    520     }
    521 }
    522 
    523 static void draw_label(lv_event_t * e)
    524 {
    525     /* Split the drawing of the label into  an upper (above the selected area)
    526      * and a lower (below the selected area)*/
    527     lv_obj_t * label_obj = lv_event_get_target(e);
    528     lv_obj_t * roller = lv_obj_get_parent(label_obj);
    529     lv_draw_label_dsc_t label_draw_dsc;
    530     lv_draw_label_dsc_init(&label_draw_dsc);
    531     lv_obj_init_draw_label_dsc(roller, LV_PART_MAIN, &label_draw_dsc);
    532     lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
    533 
    534     /*If the roller has shadow or outline it has some ext. draw size
    535      *therefore the label can overflow the roller's boundaries.
    536      *To solve this limit the clip area to the "plain" roller.*/
    537     const lv_area_t * clip_area_ori = draw_ctx->clip_area;
    538     lv_area_t roller_clip_area;
    539     if(!_lv_area_intersect(&roller_clip_area, draw_ctx->clip_area, &roller->coords)) return;
    540     draw_ctx->clip_area = &roller_clip_area;
    541 
    542     lv_area_t sel_area;
    543     get_sel_area(roller, &sel_area);
    544 
    545     lv_area_t clip2;
    546     clip2.x1 = label_obj->coords.x1;
    547     clip2.y1 = label_obj->coords.y1;
    548     clip2.x2 = label_obj->coords.x2;
    549     clip2.y2 = sel_area.y1;
    550     if(_lv_area_intersect(&clip2, draw_ctx->clip_area, &clip2)) {
    551         const lv_area_t * clip_area_ori2 = draw_ctx->clip_area;
    552         draw_ctx->clip_area = &clip2;
    553         lv_draw_label(draw_ctx, &label_draw_dsc, &label_obj->coords, lv_label_get_text(label_obj), NULL);
    554         draw_ctx->clip_area = clip_area_ori2;
    555     }
    556 
    557     clip2.x1 = label_obj->coords.x1;
    558     clip2.y1 = sel_area.y2;
    559     clip2.x2 = label_obj->coords.x2;
    560     clip2.y2 = label_obj->coords.y2;
    561     if(_lv_area_intersect(&clip2, draw_ctx->clip_area, &clip2)) {
    562         const lv_area_t * clip_area_ori2 = draw_ctx->clip_area;
    563         draw_ctx->clip_area = &clip2;
    564         lv_draw_label(draw_ctx, &label_draw_dsc, &label_obj->coords, lv_label_get_text(label_obj), NULL);
    565         draw_ctx->clip_area = clip_area_ori2;
    566     }
    567 
    568     draw_ctx->clip_area = clip_area_ori;
    569 }
    570 
    571 static void get_sel_area(lv_obj_t * obj, lv_area_t * sel_area)
    572 {
    573 
    574     const lv_font_t * font_main = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
    575     const lv_font_t * font_sel = lv_obj_get_style_text_font(obj, LV_PART_SELECTED);
    576     lv_coord_t font_main_h        = lv_font_get_line_height(font_main);
    577     lv_coord_t font_sel_h        = lv_font_get_line_height(font_sel);
    578     lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
    579     lv_coord_t d = (font_sel_h + font_main_h) / 2 + line_space;
    580     sel_area->y1 = obj->coords.y1 + lv_obj_get_height(obj) / 2 - d / 2;
    581     sel_area->y2 = sel_area->y1 + d;
    582     lv_area_t roller_coords;
    583     lv_obj_get_coords(obj, &roller_coords);
    584 
    585     sel_area->x1 = roller_coords.x1;
    586     sel_area->x2 = roller_coords.x2;
    587 
    588 }
    589 
    590 /**
    591  * Refresh the position of the roller. It uses the id stored in: roller->ddlist.selected_option_id
    592  * @param roller pointer to a roller object
    593  * @param anim_en LV_ANIM_ON: refresh with animation; LV_ANOM_OFF: without animation
    594  */
    595 static void refr_position(lv_obj_t * obj, lv_anim_enable_t anim_en)
    596 {
    597     lv_obj_t * label = get_label(obj);
    598     if(label == NULL) return;
    599 
    600     lv_text_align_t align = lv_obj_calculate_style_text_align(label, LV_PART_MAIN, lv_label_get_text(label));
    601 
    602     switch(align) {
    603         case LV_TEXT_ALIGN_CENTER:
    604             lv_obj_set_x(label, (lv_obj_get_content_width(obj) - lv_obj_get_width(label)) / 2);
    605             break;
    606         case LV_TEXT_ALIGN_RIGHT:
    607             lv_obj_set_x(label, lv_obj_get_content_width(obj) - lv_obj_get_width(label));
    608             break;
    609         case LV_TEXT_ALIGN_LEFT:
    610             lv_obj_set_x(label, 0);
    611             break;
    612     }
    613 
    614     lv_roller_t * roller = (lv_roller_t *)obj;
    615     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
    616     lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
    617     lv_coord_t font_h              = lv_font_get_line_height(font);
    618     lv_coord_t h                   = lv_obj_get_content_height(obj);
    619     uint16_t anim_time             = lv_obj_get_style_anim_time(obj, LV_PART_MAIN);
    620 
    621     /*Normally the animation's `end_cb` sets correct position of the roller if infinite.
    622      *But without animations do it manually*/
    623     if(anim_en == LV_ANIM_OFF || anim_time == 0) {
    624         inf_normalize(obj);
    625     }
    626 
    627     int32_t id = roller->sel_opt_id;
    628     lv_coord_t sel_y1 = id * (font_h + line_space);
    629     lv_coord_t mid_y1 = h / 2 - font_h / 2;
    630 
    631     lv_coord_t new_y = mid_y1 - sel_y1;
    632 
    633     if(anim_en == LV_ANIM_OFF || anim_time == 0) {
    634         lv_anim_del(label, set_y_anim);
    635         lv_obj_set_y(label, new_y);
    636     }
    637     else {
    638         lv_anim_t a;
    639         lv_anim_init(&a);
    640         lv_anim_set_var(&a, label);
    641         lv_anim_set_exec_cb(&a, set_y_anim);
    642         lv_anim_set_values(&a, lv_obj_get_y(label), new_y);
    643         lv_anim_set_time(&a, anim_time);
    644         lv_anim_set_ready_cb(&a, scroll_anim_ready_cb);
    645         lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
    646         lv_anim_start(&a);
    647     }
    648 }
    649 
    650 static lv_res_t release_handler(lv_obj_t * obj)
    651 {
    652     lv_obj_t * label = get_label(obj);
    653     if(label == NULL) return LV_RES_OK;
    654 
    655     lv_indev_t * indev = lv_indev_get_act();
    656     lv_roller_t * roller = (lv_roller_t *)obj;
    657 
    658     /*Leave edit mode once a new option is selected*/
    659     lv_indev_type_t indev_type = lv_indev_get_type(indev);
    660     if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) {
    661         roller->sel_opt_id_ori = roller->sel_opt_id;
    662 
    663         if(indev_type == LV_INDEV_TYPE_ENCODER) {
    664             lv_group_t * g      = lv_obj_get_group(obj);
    665             if(lv_group_get_editing(g)) {
    666                 lv_group_set_editing(g, false);
    667             }
    668         }
    669     }
    670 
    671     if(lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON) {
    672         /*Search the clicked option (For KEYPAD and ENCODER the new value should be already set)*/
    673         int16_t new_opt  = -1;
    674         if(roller->moved == 0) {
    675             new_opt = 0;
    676             lv_point_t p;
    677             lv_indev_get_point(indev, &p);
    678             p.y -= label->coords.y1;
    679             p.x -= label->coords.x1;
    680             uint32_t letter_i;
    681             letter_i = lv_label_get_letter_on(label, &p);
    682 
    683             const char * txt  = lv_label_get_text(label);
    684             uint32_t i        = 0;
    685             uint32_t i_prev   = 0;
    686 
    687             uint32_t letter_cnt = 0;
    688             for(letter_cnt = 0; letter_cnt < letter_i; letter_cnt++) {
    689                 uint32_t letter = _lv_txt_encoded_next(txt, &i);
    690                 /*Count he lines to reach the clicked letter. But ignore the last '\n' because it
    691                  * still belongs to the clicked line*/
    692                 if(letter == '\n' && i_prev != letter_i) new_opt++;
    693                 i_prev = i;
    694             }
    695         }
    696         else {
    697             /*If dragged then align the list to have an element in the middle*/
    698             const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
    699             lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
    700             lv_coord_t font_h              = lv_font_get_line_height(font);
    701 
    702             lv_coord_t label_unit = font_h + line_space;
    703             lv_coord_t mid        = obj->coords.y1 + (obj->coords.y2 - obj->coords.y1) / 2;
    704             lv_coord_t label_y1 = label->coords.y1 + lv_indev_scroll_throw_predict(indev, LV_DIR_VER);
    705             int32_t id = (mid - label_y1) / label_unit;
    706 
    707             if(id < 0) id = 0;
    708             if(id >= roller->option_cnt) id = roller->option_cnt - 1;
    709 
    710             new_opt = id;
    711         }
    712 
    713         if(new_opt >= 0) {
    714             lv_roller_set_selected(obj, new_opt, LV_ANIM_ON);
    715         }
    716     }
    717 
    718     uint32_t id  = roller->sel_opt_id; /*Just to use uint32_t in event data*/
    719     lv_res_t res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &id);
    720     return res;
    721 }
    722 
    723 /**
    724  * Set the middle page for the roller if infinite is enabled
    725  * @param roller pointer to a roller object
    726  */
    727 static void inf_normalize(lv_obj_t * obj)
    728 {
    729     lv_roller_t * roller = (lv_roller_t *)obj;
    730 
    731     if(roller->mode == LV_ROLLER_MODE_INFINITE) {
    732         uint16_t real_id_cnt = roller->option_cnt / LV_ROLLER_INF_PAGES;
    733         roller->sel_opt_id = roller->sel_opt_id % real_id_cnt;
    734         roller->sel_opt_id += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
    735 
    736         roller->sel_opt_id_ori = roller->sel_opt_id % real_id_cnt;
    737         roller->sel_opt_id_ori += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
    738 
    739         /*Move to the new id*/
    740         const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
    741         lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
    742         lv_coord_t font_h              = lv_font_get_line_height(font);
    743         lv_coord_t h                   = lv_obj_get_content_height(obj);
    744 
    745         lv_obj_t * label = get_label(obj);
    746 
    747 
    748         lv_coord_t sel_y1 = roller->sel_opt_id * (font_h + line_space);
    749         lv_coord_t mid_y1 = h / 2 - font_h / 2;
    750         lv_coord_t new_y = mid_y1 - sel_y1;
    751         lv_obj_set_y(label, new_y);
    752     }
    753 }
    754 
    755 static lv_obj_t * get_label(const lv_obj_t * obj)
    756 {
    757     return lv_obj_get_child(obj, 0);
    758 }
    759 
    760 
    761 static lv_coord_t get_selected_label_width(const lv_obj_t * obj)
    762 {
    763     lv_obj_t * label = get_label(obj);
    764     if(label == NULL) return 0;
    765 
    766     const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_SELECTED);
    767     lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_SELECTED);
    768     const char * txt = lv_label_get_text(label);
    769     lv_point_t size;
    770     lv_txt_get_size(&size, txt, font, letter_space, 0, LV_COORD_MAX,  LV_TEXT_FLAG_NONE);
    771     return size.x;
    772 }
    773 
    774 static void scroll_anim_ready_cb(lv_anim_t * a)
    775 {
    776     lv_obj_t * obj = lv_obj_get_parent(a->var); /*The label is animated*/
    777     inf_normalize(obj);
    778 }
    779 
    780 
    781 static void set_y_anim(void * obj, int32_t v)
    782 {
    783     lv_obj_set_y(obj, v);
    784 }
    785 
    786 #endif