GuiLite开源GUI学习(一)
GuiLite项目地址
移植相关文件
-
UICode.cpp
:逻辑代码的编写,向外提供一个接口,如startHello3Ddonut
。static c_surface* s_surface; static c_display* s_display; // Demo void create_ui(void* phy_fb, int screen_width, int screen_height, int color_bytes, struct EXTERNAL_GFX_OP* gfx_op) { if (phy_fb) { static c_surface surface(UI_WIDTH, UI_HEIGHT, color_bytes, Z_ORDER_LEVEL_0); static c_display display(phy_fb, screen_width, screen_height, &surface); s_surface = &surface; s_display = &display; } else {//for MCU without framebuffer static c_surface_no_fb surface_no_fb(UI_WIDTH, UI_HEIGHT, color_bytes, gfx_op, Z_ORDER_LEVEL_0); static c_display display(phy_fb, screen_width, screen_height, &surface_no_fb); s_surface = &surface_no_fb; s_display = &display; } s_surface->fill_rect(0, 0, UI_WIDTH - 1, UI_HEIGHT - 1, 0, Z_ORDER_LEVEL_0); // 向主题添加一个字体,改字体的标识为FONT_DEFAULT,后面可以根据这个标识取出。 c_theme::add_font(FONT_DEFAULT, &Consolas_13); while(true) { build_frame(); render_frame(); } } interface for all platform // main.c中调用此接口 extern "C" void startHello3Ddonut(void* phy_fb, int width, int height, int color_bytes, struct EXTERNAL_GFX_OP* gfx_op) { create_ui(phy_fb, width, height, color_bytes, gfx_op); }
-
main.c
:负责画点函数draw_pixel
的移植//Transfer GuiLite 32 bits color to your LCD color #define GL_RGB_32_to_16(rgb) (((((unsigned int)(rgb)) & 0xFF) >> 3) | ((((unsigned int)(rgb)) & 0xFC00) >> 5) | ((((unsigned int)(rgb)) & 0xF80000) >> 8)) //Encapsulate your LCD driver: void gfx_draw_pixel(int x, int y, unsigned int rgb) { LCD_Draw_ColorPoint(x, y, GL_RGB_32_to_16(rgb)); } //Implement it, if you have more fast solution than drawing pixels one by one. void gfx_fill_rect(int x0, int y0, int x1, int y1, unsigned int rgb) { LCD_Fill(x0, y0, x1, y1, GL_RGB_32_to_16(rgb)); } //UI entry struct EXTERNAL_GFX_OP { void (*draw_pixel)(int x, int y, unsigned int rgb); void (*fill_rect)(int x0, int y0, int x1, int y1, unsigned int rgb); } my_gfx_op; extern void startHello3Ddonut(void* phy_fb, int width, int height, int color_bytes, struct EXTERNAL_GFX_OP* gfx_op); int mian() { ...... // 画点函数的移植 my_gfx_op.draw_pixel = gfx_draw_pixel; my_gfx_op.fill_rect = gfx_fill_rect;//gfx_fill_rect; startHello3Ddonut(NULL, 240, 240, 2, &my_gfx_op); }
注意点:
static c_surface_no_fb surface_no_fb(UI_WIDTH, UI_HEIGHT, color_bytes, gfx_op, Z_ORDER_LEVEL_0);
// 虽然画图时都是通过surface,但是下面这句话不能注释掉,因为下面这句话会对surface_no_fb进行赋值,不然的话surface_no_fb是无效值。
static c_display display(phy_fb, screen_width, screen_height, &surface_no_fb);
// 因为:
inline c_display::c_display(..., c_surface* surface)
{
m_color_bytes = surface->m_color_bytes;
// 这句话设置surface为有效的
surface->m_is_active = true;
(m_surface_group[0] = surface)->attach_display(this);
}
s_surface = &surface_no_fb;
// 这句话可以注释掉
s_display = &display;
注意:
使用时只需包含头文件GuiLite.h
,此头文件中包含一些已实现的控件。在GuiLite项目中,作者也将各个模块抽离出来,方便读者研究,即GuiLite的src目录下。
display类
surface是它的友元类,即surface可以访问display的属性和方法。
属性:
void* m_phy_fb; //physical framebuffer
int m_phy_read_index;
int m_phy_write_index;
c_surface* m_surface_group[SURFACE_CNT_MAX]; ///对应多个surface
int m_surface_cnt; //surface count
int m_surface_index;
注意点:
- 一个display只有一个
m_phy_fb
; - 但是有
SURFACE_CNT_MAX
个surface;
方法:
inline c_display(void* phy_fb, int display_width, int display_height, int surface_width, int surface_height, unsigned int color_bytes, int surface_cnt, EXTERNAL_GFX_OP* gfx_op = 0);// 创建拥有surface_cnt个surface的display
inline c_display(void* phy_fb, int display_width, int display_height, c_surface* surface);// 创建单层surface的display,且为fb模式
inline c_surface* alloc_surface(Z_ORDER_LEVEL max_zorder, c_rect layer_rect = c_rect());//为display申请多个surface
inline int swipe_surface(c_surface* s0, c_surface* s1, int x0, int x1, int y0, int y1, int offset);
// 得到最新的fb
void* get_updated_fb(int* width, int* height, bool force_update = false);
// 将当前的fb保存到文件(bmp形式)
int snap_shot(const char* file_name)
surface类
display和bitmap都是surface的友元类。
方法:
// 构造函数只有一个
void c_surface(, Z_ORDER_LEVEL max_zorder = Z_ORDER_LEVEL_0, c_rect overlpa_rect = c_rect()) : m_width(width), m_height(height), m_color_bytes(color_bytes), m_fb(0), m_is_active(false), m_top_zorder(Z_ORDER_LEVEL_0), m_phy_fb(0), m_phy_write_index(0), m_display(0);
// 此函数是将m_fb的值刷新到m_phy_fb。
void flush_screen(x1, y1, x2, y2);
surface分为带有framebuffer
和不带framebuffer
的surface,两者的区别不大,只是不带fb
的多了一个struct EXTERNAL_GFX_OP* m_gfx_op
属性,主要原因是:
- 带有
framebuffer
的可以直接对某块内存进行对写; - 不带有
framebuffer
的surface通过m_gfx_op
中的draw_pixel
来重写virtual void draw_pixel_on_fb(int x, int y, unsigned int rgb)
; - 通过上述可知,最终两种surface在使用上没有区别。
属性:
void* m_fb; //frame buffer you could see
c_layer m_layers[Z_ORDER_LEVEL_MAX];//all graphic layers
bool m_is_active; //active flag
Z_ORDER_LEVEL m_max_zorder; //surface拥有的最多layer个数
Z_ORDER_LEVEL m_top_zorder; //指向你当前想显示的layer
void* m_phy_fb; //physical framebufer
int* m_phy_write_index;
c_display* m_display;
// 没有fb的还要多个struct EXTERNAL_GFX_OP* m_gfx_op成员
注意点:
- 一个surface指向一个display,也指向一个
m_fb
和m_phy_fb
; - 有多个layer;
- 即一个display指向多个surface、一个surface有多个layer;
方法:
// 有很多不同的画图方法,调用的都是
virtual void draw_pixel(int x, int y, unsigned int rgb, unsigned int z_order);
// 但最终是调用的是(没有fb的会被重写,通过m_gfx_op属性):
draw_pixel_on_fb(x, y, rgb);
画图时z_order
的意义
draw_pixel(x, y, rgb, z_order)
的意义(非常重要!!!):
一个surface
有多层layer
,有两个属性z_max_order
(surface最多拥有的layer个数)和z_top_order
(当前surface的最顶层)。而其作用就是:
- 判断点
(x,y)
是否在m_layers[z_order].rect
中,如果在,那么就将m_layers[z_order].fb
更新; - 然后判断
z_order
是否在最大层z_max_order
,如果在,则将其立刻显示到屏幕上去; - 如果
z_order>m_top_order
,则m_top_order=z_order
,即更新最顶层; - 如果
z_order=m_top_order
,也立即将其显示到屏幕上去; - 走到这里,说明
z_order<m_top_order
,那么从最大层tmp=z_max_order-1
开始,从上到下遍历每一层,如果遍历到z_order
,点(x,y)
都没在m_layers[tmp].rect
中,那么说明z_order
的上层都没有把点(x,y)
遮挡住,所以应该把点(x,y)
立即显示到屏幕上去;否则,不立刻显示到屏幕上去,即只做了第一步; - 如果非要显示,那么需要调用
show_layer(c_rect, z_order)
,但是注意,c_rect
必须在m_layers[z_order].rect
之中。
大概如下,但如下没有考虑点(x,y)
与layer.rect
的关系,只是考虑了图层的关系。
surface和display及c_layer的关系
- 一个display指向多个surface,指向一个fb;
- 每个surface都指向一个fb,也指向一个display;
- 每个surface有多个layer,用z_order标识;
- 一个layer其实就是一个c_rect,然后还指向了一个fb,注意这个fb不是物理的,而是一个内存,即每个layer的fb都不同;
- surface的
show_layer(c_rect& rect, z_order)
就是展示第z_order
个layer,但是注意,c_rect
必须在m_layers[z_order].rect
之中;&
是C++
中的引用,用作函数参数时,效果同指针。它会从fb
中读取rgb值,然后再调用draw_pixel_on_fb(x,y,rgb)
。
疑问
-
在创建display时创建了多个surface,怎么获取想要的surface?此问终于解了
在阅读
slide_group.h
和HelloSlide.h
源码时,终于发现怎么获取想要的surface
。display
的部分属性如下:c_surface* m_surface_group[SURFACE_CNT_MAX]; int m_surface_cnt; //在创建时传入 int m_surface_index; c_surface* alloc_surface(Z_ORDER_LEVEL max_zorder, c_rect layer_rect);
m_surface_index
用于索引对应的surface
,我发现其只在alloc_surface
中会被改变,然后又看了slide_group.h
源码:int add_slide(c_wnd* slide, unsigned short resource_id, short x, short y, short width, short height, WND_TREE* p_child_tree = 0, Z_ORDER_LEVEL max_zorder = Z_ORDER_LEVEL_0) { c_surface* old_surface = get_surface(); c_surface* new_surface = old_surface->get_display()->alloc_surface(max_zorder); new_surface->set_active(false); set_surface(new_surface); slide->connect(this, resource_id, 0, x, y, width, height, p_child_tree); set_surface(old_surface); }
先通过第一个surface获取display,然后再调用
alloc_surface()
,这个会返回新的surface
。 -
画图时,都是通过surface的,那么display有什么用?
未完待续…