GuiLite 学习笔记(一) Mainloop与ViewTree
以GuiLiteSamples中的HelloSlide 为例,剖析一下GuiLite的设计思路和刷新机制;
首先是main.cpp; 可以分成3部分:
1、根据fb mode拿到对应的phy_fb, 后续的绘制都在这个fb上执行;
2、init _std_io(), 初始化输入设备,这里创建一个线程专门用于输入事件的poll, 检测到对应事件后, 直接调用c_slide_group的on_touch方法;
void sendTouch2HelloSlide(int x, int y, bool is_down) { is_down ? s_root.on_touch(x, y, TOUCH_DOWN) : s_root.on_touch(x, y, TOUCH_UP); }
这个s_root就是在layout UI的时候我们创建的一个static的c_slide_group;
//////////////////////// layout UI //////////////////////// c_page s_page1, s_page2, s_page3, s_page4, s_page5; static c_slide_group s_root; static WND_TREE s_root_children[] = { { NULL,0,0,0,0,0,0 } };
3、startHelloSlide(phy_fb, screen_width, screen_height, color_bytes);//never return;
摘取关键的代码片段进行剖析:
在UIcode.cpp中,有UI初始化的code
void create_ui(void* phy_fb, int screen_width, int screen_height, int color_bytes) { load_resource(); s_display = new c_display(phy_fb, screen_width, screen_height, UI_WIDTH, UI_HEIGHT, color_bytes, (1 + 5)/*1 root + 5 pages*/); c_surface* surface = s_display->alloc_surface(Z_ORDER_LEVEL_1); surface->set_active(true); s_root.set_surface(surface); s_root.connect(NULL, ID_ROOT, 0, 0, 0, UI_WIDTH, UI_HEIGHT, s_root_children); s_root.add_slide(&s_page1, ID_PAGE1, 0, 0, UI_WIDTH, UI_HEIGHT, NULL); s_root.add_slide(&s_page2, ID_PAGE2, 0, 0, UI_WIDTH, UI_HEIGHT, NULL); s_root.add_slide(&s_page3, ID_PAGE3, 0, 0, UI_WIDTH, UI_HEIGHT, NULL); s_root.add_slide(&s_page4, ID_PAGE4, 0, 0, UI_WIDTH, UI_HEIGHT, NULL); s_root.add_slide(&s_page5, ID_PAGE5, 0, 0, UI_WIDTH, UI_HEIGHT, NULL); s_root.set_active_slide(0); s_root.show_window(); while(1) { thread_sleep(1000000); } } //////////////////////// interface for all platform //////////////////////// void startHelloSlide(void* phy_fb, int width, int height, int color_bytes) { create_ui(phy_fb, width, height, color_bytes); }
上面只是在比较浅显的层面看了一下,现在要深入进入,看看slide的操作是如何传递的,以及重绘是如何发生的:
首先是s_root.on_touch() 方法;我们在GuiLite.h 中找到对应的类和方法的实现如下:
class c_slide_group : public c_wnd { public: ... inline virtual void on_touch(int x, int y, TOUCH_ACTION action); protected: c_wnd* m_slides[MAX_PAGES]; int m_active_slide_index; c_gesture* m_gesture; };
对应的实现是一个虚函数,虚函数的语义是子类可以重载这个函数,我们先看下在这一层的实现:
inline c_slide_group::c_slide_group() { m_gesture = new c_gesture(this); for (int i = 0; i < MAX_PAGES; i++) { m_slides[i] = 0; } m_active_slide_index = 0; } inline void c_slide_group::on_touch(int x, int y, TOUCH_ACTION action) { x -= m_wnd_rect.m_left; y -= m_wnd_rect.m_top; if (m_gesture->handle_swipe(x, y, action)) { if (m_slides[m_active_slide_index]) { m_slides[m_active_slide_index]->on_touch(x, y, action); } } }
这里看到的是,最后调了c_wnd类的on_touch方法;实现如下:
virtual void on_touch(int x, int y, TOUCH_ACTION action) { c_wnd* model_wnd = 0; c_wnd* tmp_child = m_top_child; while (tmp_child) { if ((tmp_child->m_attr & ATTR_PRIORITY) && (tmp_child->m_attr & ATTR_VISIBLE)) { model_wnd = tmp_child; break; } tmp_child = tmp_child->m_next_sibling; } if (model_wnd) { return model_wnd->on_touch(x, y, action); } x -= m_wnd_rect.m_left; y -= m_wnd_rect.m_top; c_wnd* child = m_top_child; while (child) { if (child->is_focus_wnd()) { c_rect rect; child->get_wnd_rect(rect); if (true == rect.PtInRect(x, y)) { return child->on_touch(x, y, action); } } child = child->m_next_sibling; } }
看到这里,基本上接触到GUI的核心概念了,也就是VieeTree。所谓ViewTree是指,GUI的所有View都是以Tree的数据结构来组织的。我们重点关注c_wnd这个类,看看里面到底有何玄机:首先我们看下一个c_wnd是如何添加到Tree的,首先是,connect方法:
virtual int connect(c_wnd *parent, unsigned short resource_id, const char* str, short x, short y, short width, short height, WND_TREE* p_child_tree = 0) { if (0 == resource_id) { ASSERT(false); return -1; } m_id = resource_id; set_str(str); m_parent = parent; m_status = STATUS_NORMAL; if (parent) { m_z_order = parent->m_z_order; m_surface = parent->m_surface; } if (0 == m_surface) { ASSERT(false); return -2; } /* (cs.x = x * 1024 / 768) for 1027*768=>800*600 quickly*/ m_wnd_rect.m_left = x; m_wnd_rect.m_top = y; m_wnd_rect.m_right = (x + width - 1); m_wnd_rect.m_bottom = (y + height - 1); c_rect rect; get_screen_rect(rect); ASSERT(m_surface->is_valid(rect)); pre_create_wnd(); if (0 != parent) { parent->add_child_2_tail(this); } if (load_child_wnd(p_child_tree) >= 0) { load_cmd_msg(); on_init_children(); } return 0; }
先看接口,再看实现,首先,一个node添加到Tree上,一定是要首先指定其parent节点的,这个毫无疑问;
如果parent不是NULL,说明很可能,这个节点还有兄弟节点,那么这个时候就要调用parent的add_child_2_tail方法把this这个节点add进去,具体如下:
void add_child_2_tail(c_wnd *child) { if (0 == child)return; if (child == get_wnd_ptr(child->m_id))return; if (0 == m_top_child) { m_top_child = child; child->m_prev_sibling = 0; child->m_next_sibling = 0; } else { c_wnd* last_child = get_last_child(); if (0 == last_child) { ASSERT(false); } last_child->m_next_sibling = child; child->m_prev_sibling = last_child; child->m_next_sibling = 0; } }
添加过程叙述如下:
先判空,再判等,这两种情况都无需进行添加操作;然后再判断m_top_child是否为空,m_top_child是当前节点的第一个插入的子节点,所以叫top child, 后面简称长子;若长子为空,则本次插入的child就是长子,否则这次插入的就是兄弟节点,而且要找到当前年龄最小的节点,在其之后进行插入;
这里分析完后,回到刚才的connect函数,继续看后面load_child_wnd的操作:
typedef struct struct_wnd_tree { c_wnd* p_wnd; unsigned int resource_id; const char* str; short x; short y; short width; short height; struct struct_wnd_tree* p_child_tree; }WND_TREE;
int load_child_wnd(WND_TREE *p_child_tree) { if (0 == p_child_tree) { return 0; } int sum = 0; WND_TREE* p_cur = p_child_tree; while (p_cur->p_wnd) { if (0 != p_cur->p_wnd->m_id) {//This wnd has been used! Do not share! ASSERT(false); return -1; } else { p_cur->p_wnd->connect(this, p_cur->resource_id, p_cur->str, p_cur->x, p_cur->y, p_cur->width, p_cur->height, p_cur->p_child_tree); } p_cur++; sum++; } return sum; }
注意,如果这个添加的子树不为空的话,这里会产生递归的操作,也就是说在connect中,又产生了connect。
再次回到之前对c_slide_group类的on_touch方法的回顾,可以看到这个类自带c_gestrure; 通过这个
m_gesture->handle_swipe(x, y, action)
处理swipe的操作,这里会影响m_active_slide_index, 以及gestrue处理后还会让c_slide_group继续在更新后的m_active_slide_index处理swipe的事件;
可以猜到,画面的刷新就是在这里完成的,具体类和时序,我们下一章继续分析;