美文网首页RTOS和GUI_基于英飞凌tc2x及stm32开发板
littlevgl_7.11源码分析(4)--Apple的学习笔

littlevgl_7.11源码分析(4)--Apple的学习笔

作者: applecai | 来源:发表于2021-05-20 21:11 被阅读0次

    一,前言

    继续分析按钮的创建及显示的过程。昨天很大的篇幅都是在研究lv_style_t结构体的初始化填充,简单理解就是主题默认样式的创建,它包括很多数据,都保存在*map指向的内存中。

    二,应用主题源码分析

    关于apply_theme中调用theme_apply函数,那么就很容易理解了。就是把lv_style_t加入到lv_style_list_t结构体的lv_style_t指针成员中。lv_style_list_t中style_cnt是6个bit长度的,说明这个list可以保存很多lv_style_t样式成员。最后还对调用lv_obj_refresh_style刷新主题,此函数在这里不展开,后面会分析

            case LV_THEME_BTN:
                list = lv_obj_get_style_list(obj, LV_BTN_PART_MAIN);
                _lv_style_list_add_style(list, &styles->btn);
                break;
            ......
            lv_obj_refresh_style();
    

    然后lv_theme_apply算是分析完了,又从最开始的函数btn创建开始分析,lv_btn_create先调用创建容器,然后创建容器调用创建基类对象。这个创建对象的过程真的和c++构造子类对象一样呀!学习到了,原来还可以这样设计。然后他们个初始化对象格式雷同,都是设置回调函数,然后设置对应的属性后进行主题应用。不过我个人认为,还可以再抽象些不同的内容打包为回调函数,然后用向父对象归递的方式实现lv_btn_create。因为这些调用代码展开后看上去就像是归递函数在运行呢~

    lv_obj_t * lv_btn_create(lv_obj_t * par, const lv_obj_t * copy)
    {
        lv_obj_t * btn;
        // 创建容器
        btn = lv_cont_create(par, copy);
        LV_ASSERT_MEM(btn);
        if(btn == NULL) return NULL;
    ......
        lv_obj_set_signal_cb(btn, lv_btn_signal);
        lv_obj_set_design_cb(btn, lv_btn_design);
    
        /*If no copy do the basic initialization*/
        if(copy == NULL) {
            /*Set layout if the button is not a screen*/
            if(par) {
                lv_obj_set_size(btn, LV_DPI, LV_DPI / 3);
                lv_btn_set_layout(btn, LV_LAYOUT_CENTER);
            }
    
            lv_obj_set_click(btn, true); /*Be sure the button is clickable*/
    
            lv_theme_apply(btn, LV_THEME_BTN);
        }
    }
    lv_obj_t * lv_cont_create(lv_obj_t * par, const lv_obj_t * copy)
    {
        // 创建基类对象
        lv_obj_t * cont = lv_obj_create(par, copy);
        LV_ASSERT_MEM(cont);
        if(cont == NULL) return NULL;
    ......
        lv_obj_set_signal_cb(cont, lv_cont_signal);
    
        /*Init the new container*/
        if(copy == NULL) {
            /*Set the default styles if it's not screen*/
            if(par != NULL) {
                lv_theme_apply(cont, LV_THEME_CONT);
            }
    
        }
    }
    

    这里比较关键的就是signal_cb回调函数,什么时候去调用呢!就是在lv_obj_refresh_style中会调用,一开始分析apply_theme就提及了此函数。用signal_cb来设置obj的一些属性,比如lv_cont_signal->lv_cont_refr_layout->lv_cont_layout_col->lv_obj_align一路就是根据style设置列的布局,最后lv_obj_align中会修改obj的x,y位置。这里面等于也说到了style的使用。包括用到了obj的ext_attr属性,ext属性是属于每个obj的特色属性。ext_attr是void *,属于万能的地址链接。比如如下,不同对象申请的空间类型都不同。

    lv_btn_ext_t * ext = lv_obj_allocate_ext_attr(btn, sizeof(lv_btn_ext_t));
    lv_calendar_ext_t * ext = lv_obj_allocate_ext_attr(calendar, sizeof(lv_calendar_ext_t));
    lv_bar_ext_t * ext = lv_obj_allocate_ext_attr(bar, sizeof(lv_bar_ext_t));
    lv_canvas_ext_t * ext = lv_obj_allocate_ext_attr(new_canvas, sizeof(lv_canvas_ext_t));
    

    代码看到现在,基本上对lv_obj_t基类结构体中的成员含义大多数能能理解了。lv_layout_t就是个uint8的枚举,因为控件对齐功能属于常用的,所以我就记录下,注释如下。其实这个signal_cb我现在立即就是一个个小的功能函数用switch case集成在一起,要使用哪个case的小功能就用signal_cb传入不同的参数。

    void lv_cont_set_layout(lv_obj_t * cont, lv_layout_t layout)
    {
        LV_ASSERT_OBJ(cont, LV_OBJX_NAME);
        // 获取当前对齐属性
        lv_cont_ext_t * ext = lv_obj_get_ext_attr(cont);
        if(ext->layout == layout) return;
        // 若设置属性与当前不同,则使用新的对齐属性
        ext->layout = layout;
        // 发送信号直接处理对齐算法
        /*Send a signal to refresh the layout*/
        cont->signal_cb(cont, LV_SIGNAL_CHILD_CHG, NULL);
    }
    

    然后说下lv_obj_refresh_style中先调用invalidate_style_cache(obj, part, prop);里面是设置此obj的所有属性及其子obj的所有属性为无效,这个用for循环不太好吧!干嘛要设置所有的都无效,某些要改动的设置为无效就好了呀?结果看到代码备注上已经写了,将来待优化,哈哈~
    接着继续看btn创建中lv_obj_set_size设置。里面为什么要调用2次lv_obj_invalidate,我理解就是设置obj区域无效,obj怎么还分原来的obj和新的obj,再仔细想想是有道理的,因为这是设置区域,无效区域是要重绘的,删除老的大小的区域要重绘,设置完新的size的区域也要重绘。

    void lv_obj_set_size(lv_obj_t * obj, lv_coord_t w, lv_coord_t h)
    {
        LV_ASSERT_OBJ(obj, LV_OBJX_NAME);
    
        /* Do nothing if the size is not changed */
        /* It is very important else recursive resizing can
         * occur without size change*/
        if(lv_obj_get_width(obj) == w && lv_obj_get_height(obj) == h) {
            return;
        }
    
        /*Invalidate the original area*/
        lv_obj_invalidate(obj);
    
        /*Save the original coordinates*/
        lv_area_t ori;
        lv_obj_get_coords(obj, &ori);
    
        /*Set the length and height*/
        obj->coords.y2 = obj->coords.y1 + h - 1;
        if(lv_obj_get_base_dir(obj) == LV_BIDI_DIR_RTL) {
            obj->coords.x1 = obj->coords.x2 - w + 1;
        }
        else {
            obj->coords.x2 = obj->coords.x1 + w - 1;
        }
    
        /*Send a signal to the object with its new coordinates*/
        obj->signal_cb(obj, LV_SIGNAL_COORD_CHG, &ori);
    
        /*Send a signal to the parent too*/
        lv_obj_t * par = lv_obj_get_parent(obj);
        if(par != NULL) par->signal_cb(par, LV_SIGNAL_CHILD_CHG, obj);
    
        /*Tell the children the parent's size has changed*/
        lv_obj_t * i;
        _LV_LL_READ(obj->child_ll, i) {
            i->signal_cb(i, LV_SIGNAL_PARENT_SIZE_CHG,  &ori);
        }
    
        /*Invalidate the new area*/
        lv_obj_invalidate(obj);
    
        /*Automatically realign the object if required*/
    #if LV_USE_OBJ_REALIGN
        if(obj->realign.auto_realign) lv_obj_realign(obj);
    #endif
    }
    

    接下来貌似初始化赋值完成了,并没有直接调用渲染绘图,渲染绘图是在周期task中统一调度的,所以我第一篇理解的说设置一个task为中优先级才会启动绘图渲染的理解是错误的,它不是触发式的,就是周期扫描,有无效区则渲染。然后就看看渲染绘图函数吧!调用关系如下


    image.png

    lv_refr_obj_and_children->lv_refr_obj会调用obj->design_cb,我说过2个callback是比较重要的,关于填充color值就是lv_obj_design函数。关键的渲染绘图函数如下,初始化矩形,获取初始化时候的style,然后画矩形。

            lv_draw_rect_dsc_t draw_dsc;
            lv_draw_rect_dsc_init(&draw_dsc);
            /*If the border is drawn later disable loading its properties*/
            if(lv_obj_get_style_border_post(obj, LV_OBJ_PART_MAIN)) {
                draw_dsc.border_post = 1;
            }
    
            lv_obj_init_draw_rect_dsc(obj, LV_OBJ_PART_MAIN, &draw_dsc);
    
            lv_coord_t w = lv_obj_get_style_transform_width(obj, LV_OBJ_PART_MAIN);
            lv_coord_t h = lv_obj_get_style_transform_height(obj, LV_OBJ_PART_MAIN);
            lv_area_t coords;
            lv_area_copy(&coords, &obj->coords);
            coords.x1 -= w;
            coords.x2 += w;
            coords.y1 -= h;
            coords.y2 += h;
            // 进行画布填充绘制
            lv_draw_rect(&coords, clip_area, &draw_dsc);
    

    lv_obj_init_draw_rect_dsc中就是为lv_draw_rect_dsc_t对象赋值,这里用到了很多获取style,这些style就是在初始化的时候赋值的。

    void lv_obj_init_draw_rect_dsc(lv_obj_t * obj, uint8_t part, lv_draw_rect_dsc_t * draw_dsc)
    {
        draw_dsc->radius = lv_obj_get_style_radius(obj, part);
    
        if(draw_dsc->bg_opa != LV_OPA_TRANSP) {
            draw_dsc->bg_opa = lv_obj_get_style_bg_opa(obj, part);
            if(draw_dsc->bg_opa > LV_OPA_MIN) {
                draw_dsc->bg_color = lv_obj_get_style_bg_color(obj, part);
                draw_dsc->bg_grad_dir =  lv_obj_get_style_bg_grad_dir(obj, part);
                if(draw_dsc->bg_grad_dir != LV_GRAD_DIR_NONE) {
                    draw_dsc->bg_grad_color = lv_obj_get_style_bg_grad_color(obj, part);
                    draw_dsc->bg_main_color_stop =  lv_obj_get_style_bg_main_stop(obj, part);
                    draw_dsc->bg_grad_color_stop =  lv_obj_get_style_bg_grad_stop(obj, part);
                }
        ......
        }
    

    接着的问题就是每个像素的color在哪个函数中绘制的,继续看code,如下图


    image.png

    lv_draw_rect函数进入后就会涉及到framebuffer对象的填充。
    draw_bg主要就是绘制矩形的背景,然后还有绘制边框的等等。为了美观一般矩形绘制都是带圆角的,这里用了mask方法来绘制矩形,最终就是带圆角的。

                /*In not corner areas apply the mask only if required*/
                if(y > coords_bg.y1 + rout + 1 &&
                   y < coords_bg.y2 - rout - 1) {
                    mask_res = LV_DRAW_MASK_RES_FULL_COVER;
                    if(simple_mode == false) {
                        _lv_memset(mask_buf, opa, draw_area_w);
                        mask_res = lv_draw_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w);
                    }
                }
                /*In corner areas apply the mask anyway*/
                else {
                    _lv_memset(mask_buf, opa, draw_area_w);
                    mask_res = lv_draw_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w);
                }
    

    lv_draw_mask_apply->lv_draw_mask_radius函数。里面有圆角mask的算法。
    fill_normal函数就是填充color的,这里是绘制矩形,不是划线或者画文字的填充方法。如下是软件渲染的方法一次循环赋值。

    LV_ATTRIBUTE_FAST_MEM static void fill_normal(const lv_area_t * disp_area, lv_color_t * disp_buf,
                                                  const lv_area_t * draw_area,
                                                  lv_color_t color, lv_opa_t opa,
                                                  const lv_opa_t * mask, lv_draw_mask_res_t mask_res)
    {
        /*Get the width of the `disp_area` it will be used to go to the next line*/
        int32_t disp_w = lv_area_get_width(disp_area);
    
        int32_t draw_area_w = lv_area_get_width(draw_area);
        int32_t draw_area_h = lv_area_get_height(draw_area);
    
        // 获取画布首地址
        lv_color_t * disp_buf_first = disp_buf + disp_w * draw_area->y1 + draw_area->x1;
    
        int32_t x;
        int32_t y;
        // 开始填充画布,当然这里面还有判断是否mask
        if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
            if(opa > LV_OPA_MAX) {
                /*Software rendering*/
                for(y = 0; y < draw_area_h; y++) {
                    lv_color_fill(disp_buf_first, color, draw_area_w);
                    disp_buf_first += disp_w;
                }
        ......
    }
    

    三,总结

    昨天看了style初始化及其存储的的数据结构,今天主要看了哪些样式是什么时候用的,怎么用的。因为我对风格化最感兴趣。主要就是在signal_cb函数中最一些变化进行设置,然后就是周期任务中绘制对象,画布填充color主要是lv_obj_design函数进行处理的。而画布显示到显示屏之前第一篇源码分析已经说过了,主要思路就是设置invalid区,则要重绘,然后有单framebuffer是双framebuffer(部分+完整)的3中重绘方式。
    源码分析1到4系列,分析完了关于如何画一个按钮的大体流程,对于各个层次的对象结构体设计也有了一定的认识。反正我觉得在图像引擎中归递的设计方法看的比较多。然后就是看到c++的影子,比如创建子类的时候构造父类对象,以及用钩子函数充当虚方法。

    相关文章

      网友评论

        本文标题:littlevgl_7.11源码分析(4)--Apple的学习笔

        本文链接:https://www.haomeiwen.com/subject/siibjltx.html