最小GUI库GuiLite源码分析--Apple的学习笔记

发布者:快乐奇迹最新更新时间:2025-02-25 来源: jianshu关键字:源码分析 手机看文章 扫描二维码
随时随地手机看文章

前言

之前研究qemu的目的之一就是想用用qemu的stm32二次开发版本进行LCD显示实验。但是真的看了qemu stm32的源码后,发现并不支持LCD驱动的。所以我考虑是否由我自己来添加LCD驱动仿真,进行qemu二次开发。而步骤1就是我要先自己玩下基于stm32的LCD驱动应用编程。而我之前买oled屏幕后也买过一块stm32F407的开发板。oled能正常驱动,并且翻出了10年前买的2.4寸tft屏幕(80接口8bit的ili9325)也能正常驱动。


然后我发现就简单的显示内容没有动画效果,觉得不好玩,于是想起来2019年底下载过一个GUI开源软件GuiLite。然后2021年了我去看看它是否在不断的更新。所以我主要分析的是2019年底的版本,2021最新版也看了下,基础内容差距不大。


当然我也尝试了下将GuiLite移植到stm32F407开发板上,按Doc中help截图的操作步骤,还是很容易的。我现在看似在玩应用,其实我研究的还是底层库源码设计机制,所以我定义的一年视觉相关底层的的研究方向没有跑偏哈~


GuiLite源码研究

首先要明确目标,就是我分析GuiLite源码的目的是想了解GUI的设计原理。因为让我直接写个GUI引擎框架,我暂时不会。因为好奇,所以要去了解,毕竟他就5000行代码搞定的事情,我觉得很神奇。


我看了几个例子后,通过调试跟进源码,基本上已经了解了他的设计方法,让我自己设计的话我也有了方向。虽然源码还没有全看完,但是surface及widgets基本控件的原理都已经了解了,此次的目标已达成。另外的好处是,对这些控件的源码分析后,自己也可以比较灵活的去调用API做些小作品,蛮好玩的。我就喜欢这样小巧的代码,麻雀虽小五脏俱全。如下是我过程中分析源码的笔记,取其精华去其糟粕。我也发现了些bug,以及我觉得某些点,它还有继续完善的空间。

A.HelloStar工程


范围检查技巧

宽度和高度超范处理


    x0 = (x0 < 0) ? 0 : x0;

    y0 = (y0 < 0) ? 0 : y0;

    x1 = (x1 > (m_width - 1)) ? (m_width - 1) : x1;

    y1 = (y1 > (m_height - 1)) ? (m_height - 1) : y1;

随机数不超过范围的技巧,用取余数


    m_x = m_start_x = rand() % UI_WIDTH;

    m_y = m_start_y = rand() % UI_HEIGHT;

物体移动的技巧

先清除之前的绘制变成当前的背景,再重新绘制新的。至于只有一个图层为黑色底色的,就是直接画黑色,就是清除的意思,然后再重新绘制新的物体图像,看上去就是移动的效果。


B.HelloLayers工程


关于Layer的处理技巧

若有2层surface,则一开始需要申请Z_ORDER_LEVEL_1,然后就会进入如下的for循环,会为m_layers[i].fb分配内存空间。

若有3层surface,则一开始需要申请Z_ORDER_LEVEL_2。


    void set_surface(Z_ORDER_LEVEL max_z_order, c_rect layer_rect)

    {

        m_max_zorder = max_z_order;

        if (m_display && (m_display->m_surface_cnt > 1))

        {

            m_fb = calloc(m_width * m_height, m_color_bytes);

        }


        for (int i = Z_ORDER_LEVEL_0; i < m_max_zorder; i++)

        {//Top layber fb always be 0

            ASSERT(m_layers[i].fb = calloc(layer_rect.width() * layer_rect.height(), m_color_bytes));

            m_layers[i].rect = layer_rect;

        }

    }

这个内存在fill_rect函数中进行赋值的,m_layers[z_order].fb。若surface只有1级的话,不会为fb赋值。首先要思考下为什么要为fb赋值,其实就是备份的意思。


        if (z_order == m_top_zorder)

        {

            int x, y;

            c_rect layer_rect = m_layers[z_order].rect;

            unsigned int rgb_16 = GL_RGB_32_to_16(rgb);

            for (y = y0; y <= y1; y++)

            {

                for (x = x0; x <= x1; x++)

                {

                    if (layer_rect.pt_in_rect(x, y))

                    {

                        if (m_color_bytes == 4)

                        {

                            ((unsigned int*)m_layers[z_order].fb)[(y - layer_rect.m_top) * layer_rect.width() + (x - layer_rect.m_left)] = rgb;

                        }

                        else

                        {

                            ((unsigned short*)m_layers[z_order].fb)[(y - layer_rect.m_top) * layer_rect.width() + (x - layer_rect.m_left)] = rgb_16;

                        }

                    }

                }

            }

            return fill_rect_on_fb(x0, y0, x1, y1, rgb);

        }

在draw_pixel函数中也有对fb赋值。也就是说,所有绘制的地方,都会对fb进行更新值,记录最新的值。技巧就是判断if (z_order == m_max_zorder)则直接return绘制结果。若为单层,则此条件一定满足,就不会备份fb了,而对于多层则要在更新图像的时候备份fb。目的是为了将上一层级消除的时候对下一层级进行还原。


        if (z_order == m_max_zorder)

        {

            return draw_pixel_on_fb(x, y, rgb);

        }

        

        if (z_order > (unsigned int)m_top_zorder)

        {

            m_top_zorder = (Z_ORDER_LEVEL)z_order;

        }

        if (m_layers[z_order].rect.pt_in_rect(x, y))

        {

            c_rect layer_rect = m_layers[z_order].rect;

            if (m_color_bytes == 4)

            {

                ((unsigned int*)(m_layers[z_order].fb))[(x - layer_rect.m_left) + (y - layer_rect.m_top) * layer_rect.width()] = rgb;

            }

            else

            {

                ((unsigned short*)(m_layers[z_order].fb))[(x - layer_rect.m_left) + (y - layer_rect.m_top) * layer_rect.width()] = GL_RGB_32_to_16(rgb);

            }

        }

对于c_rect对象的还原方法是overlapped_rect,需要调用2句函数,一句是创建一个rect对象,目的是设置工作局域,另外一句是设置要还原的图层。


    c_rect overlapped_rect(LAYER_1_X, LAYER_1_Y, LAYER_1_WIDTH, LAYER_1_HEIGHT);

    s_surface->show_layer(overlapped_rect, Z_ORDER_LEVEL_0);

可以看到在show_layer中会取出m_layers[z_order].fb,重写到LCD上。其实就是还原。


    int show_layer(c_rect& rect, unsigned int z_order)

    {

        ASSERT(z_order >= Z_ORDER_LEVEL_0 && z_order < Z_ORDER_LEVEL_MAX);

        c_rect layer_rect = m_layers[z_order].rect;

        ASSERT(rect.m_left >= layer_rect.m_left && rect.m_right <= layer_rect.m_right &&

        rect.m_top >= layer_rect.m_top && rect.m_bottom <= layer_rect.m_bottom);

        void* fb = m_layers[z_order].fb;

        int width = layer_rect.width();

        for (int y = rect.m_top; y <= rect.m_bottom; y++)

        {

            for (int x = rect.m_left; x <= rect.m_right; x++)

            {

                unsigned int rgb = (m_color_bytes == 4) ? ((unsigned int*)fb)[(x - layer_rect.m_left) + (y - layer_rect.m_top) * width] : GL_RGB_16_to_32(((unsigned short*)fb)[(x - layer_rect.m_left) + (y - layer_rect.m_top) * width]);

                draw_pixel_on_fb(x, y, rgb);

            }

        }

        return 0;

    }

基于HelloLayers将Hellostar添加入UIcode.c,变成双图层,但是底层图层是动态的。修改后,遇到的问题是,star绘制的时候会擦除顶层的图片。


    load_resource();

    draw_on_layer_0();

    while(1) {

        stars[0].move();

        thread_sleep(70);

        cnt++;

        if (cnt % 60 == 0)

        {

            draw_on_layer_1();  

            layer1 = 0;

        }

        if (cnt % 91 == 0)

        {

            clear_layer_1();

            layer1 = 1;

        }

        if (cnt >= 32767)

        {

            cnt = 0;

        }

见图


然后想到了办法临时解决下。方法就是star运动绘制后它不是会更新顶层区域的图像嘛,所有我的修改时,当顶层小窗口显示时,下一层star重绘后,我立即重绘下顶层图像。可以解决如上问题,但是感觉刷屏比较频繁,屏幕有闪烁感。这个问题要等我全部看完GuiLite看看还有哪些API可以用来更好的解决我遇到问题。


    load_resource();

    draw_on_layer_0();

    while(1) {

        stars[0].move();

        if (layer1 == 0)

            draw_on_layer_1();

        thread_sleep(70);

        cnt++;

        if (cnt % 60 == 0)

        {

            layer1 = 0;

        }

        if (cnt % 91 == 0)

        {

            clear_layer_1();

            layer1 = 1;

        }

        if (cnt >= 32767)

        {

            cnt = 0;

        }

    }

C.HelloWidgets学习窗口对象的原理


链表归递的技巧

UI窗口对象的处理相关函数中会看到链表归递,其实归递函数我平时都不太用的,常用的还是数组,依次扫描。

通过child->show_window()进行归递,退出条件为child==null,依次扫描的目的是为每个对象调用on_paint进行绘制。而双链表对象是通过add_child_2_tail函数添加到双链表末尾的。


void c_wnd::show_window(){

    if (ATTR_VISIBLE == (m_attr & ATTR_VISIBLE))

    {

        on_paint();

        c_wnd *child = m_top_child;

        if ( 0 != child )

        {

            while ( child )

            {

                child->show_window();

                child = child->m_next_sibling;

            }

[1] [2] [3]
关键字:源码分析 引用地址:最小GUI库GuiLite源码分析--Apple的学习笔记

上一篇:01 STM32CubeMX 安装和配置
下一篇:上拉、下拉电阻的原理和作用

推荐阅读最新更新时间:2026-03-20 11:34

我将GuiLite移植到了STM32F4开发板上
摘要:最近在做Github找到一个有趣的开源Gui框架:GuiLite,按照说明移植了GuiLite到STM32F4OLED屏幕上,分析一下自己的移植经验。 一、GuiLite介绍 GuiLite是一个开源的Gui框架,只依赖于一个单一的头文件库(GuiLite.h),不需要很复杂的文件管理,代码量平易近人,GuiLite由4千行C++代码编写,单片机上也能流畅运行,其最低的硬件运行要求如下: CPU主频 ROM大小 RAM大小 24 MHZ 29KB 9KB 同时GuiLite具有很强的跨平台特性: 支持的操作系统:iOS/macOS/WatchOS,Android,Linux(ARM/x86-64),Windo
[单片机]
我将<font color='red'>GuiLite</font>移植到了STM32F4开发板上
工业4.0数字化制造系统MES源码,实现自动化的数据采集、分析和决策
随着工业4.0的推进,制造业正经历着深刻的变革。中小制造企业面临着如何在竞争激烈的市场中保持竞争力的问题,数字化改造已成为必然选择。 MES系统,一种面向制造企业车间执行层的生产信息化管理系统。它在企业资源计划(ERP)系统与底层工业控制系统之间架起桥梁,确保生产计划的有效执行,并提供实时数据反馈和控制。以下是MES系统的关键功能和重要性的详细概述: MES系统的核心功能 1、生产管理 业务流程管理:覆盖从生产工单的记录、领料、退料到报工、入库的全过程,确保标准化和自动化。 数据统计:实时采集生产数据,提升生产过程的透明度,支持决策制定和流程优化。 员工工资统计:自动化处理员工报工信息,用于工资计算,同时提供绩效分析。 2、
[嵌入式]
工业4.0数字化制造系统MES<font color='red'>源码</font>,实现自动化的数据采集、<font color='red'>分析</font>和决策
RK30SDK系统重启源码分析
Linux系统重启的最底层函数是arch_reset,这是一个全局的函数指针变量,定义在 arch/arm/mach-rk30/include/mach/system.h中: extern void (*arch_reset)(char, const char *); 注意,这是一个变量声明,类型为函数指针。并不是函数的声明!它的实现在mach-rk30/reset.c中: static void rk30_arch_reset(char mode, const char *cmd) { u32 boot_flag = 0; u32 boot_mode = BOOT_MODE_REBOOT; if (cmd) {
[单片机]
TQ2440 学习笔记—— 30、移植U-Boot【U-Boot 的启动过程第一阶段源码分析
使用u-boot 从NOR Flash 启动,前面说过u-boot 属于两个阶段的Bootloader ,第一阶段的文件为cpu/arm920t/start.S 和 board/EmbedSky/lowlevel_init.S, 前者是平台相关的,后者是开发板相关的。 一、u-boot 第一阶段代码分析 (1)硬件设备初始化 依次完成如下设置:将CPU 的工作模式设为管理模式(svc),关闭WATCHDOG ,设置FCLK、HCLK、PCLK 的比例(即设置CLKDIVN寄存器),关闭MMU、CACHE。部分代码如下: (2)为加载Bootloader 的第二阶段代码准备RAM 空间 所谓准备RAM 空间,就
[单片机]
TQ2440 学习笔记—— 30、移植U-Boot【U-Boot 的启动过程第一阶段<font color='red'>源码</font><font color='red'>分析</font>】
第六章、Tiny4412 U-BOOT移植六 Nand Flash源码分析
一、U-Boot参考源码 NandFlash的初始化代码我们放在board/samsung/tiny4412/lowlevel_init.S ,这一段代码是三星SMDK4212中没有提供的,所以我们需要自己写。我们在里面增加一个函数叫nand_asm_init。当然,由于 Nand Flash 的操作是有一定的规律的,所以,我们可以去别的地方找一段写好的NandFlash源码,然后根据自己的电路原理图进行移植即可。 二、代码分析 1、初始化Nand Flash 打开原理图,参看原理图配置各个功能引脚----状态引脚R/nB,读使能引用脚nRE,片选信号nCE,命令使能引脚CLE,地址使能引脚ALE,写使能引脚nWE。
[单片机]
第六章、Tiny4412 U-BOOT移植六 Nand Flash<font color='red'>源码</font><font color='red'>分析</font>
HEX转BIN源码分析(51系列)
  以前写的一个Atmel的S5X的下载程序,其中有支持HEX格式的文件,所以将这个程序贴出来,程序的意思是将输入的HEX文件转换为BIN格式的文件,并存储到文件中,注意不支持64K的扩展模式。 int CFlashP51App::HexToBin(CString hexfile, CString binfile) { CFile fhex, fbin; CString pBuffer; BYTE len, len1, len2, len_at_max=0; int start_addr, start_addr_max=0; int nEnd, n, bin_length=0; if(!(fhex
[单片机]
单片机c语言中菜单系统源码分析
最近在学习单片机的菜单系统时,发现有这么一些代码,定义了4个按键,确认键,返回键,上键,下键,先贴出来在vc里边建的,先定义一个结构体KbdTabStruct,在用结构体定义一个const型的数组KBD ,那么数组的每一个成员对应的原本结构体的数则是它的初始化值,并且这个值初始化后就成立,以后不再改变。比如说KBD .KeyCurrentIndex所对应的则是数组table中成员0 的值,这些值就是它的初始化值,相当于KBD .KeyCurrentIndex=0,但如果这样写N层的菜单,如此定义肯定麻烦,所以用这样的数组实现。 int main() { typedef struct { u8 KeyCurrentIndex;//
[单片机]
单片机c语言中菜单系统<font color='red'>源码</font><font color='red'>分析</font>
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved