ffxz 发表于 2009-10-21 07:52:29

RT-Thread RTOS组件:RTGUI

这是RTGUI的一份系列教程

第一篇:GUI的Hello World

RTGUI设计得与其他GUI是不相类似的,所以很有必要建立一些基本概念。RTGUI内部是完全线程化的,显示一个UI必须依赖于一个线程的上下文,因为后台存在一个GUI Server的服务端进行辅助的服务。这个GUI Server必须要知道,它服务的客户端位于哪个线程中。

接下来的例子中会在屏幕上显示一个窗口,窗口内部显示“Hello World“的文本字符。

例子代码:
void msg(void)
{
        /* UI依赖于一定的线程环境,具体的,它依赖于一个消息队列,这个消息队列会接收来自GUI Server的请求信息 */
        rt_mq_t mq;/* mq即这个消息队列 */
        rt_thread_t tid;
        rt_uint32_t user_data;
        struct rtgui_win* msgbox;/* 显示一个消息窗口,这里是它相应的窗口指针 */
        struct rtgui_rect rect = {50, 50, 200, 200};/* 窗口的显示位置 */

        /* UI中,消息是在线程间传递的,所以接收消息的消息队列需要紧贴到线程中去,RT-Thread中用到了线程的私有字段:user_data */
        tid = rt_thread_self();
        if (tid == RT_NULL) return; /* 获得当前线程,如果当前线程是空,即调度器未启动,UI不能使用,直接返回 */
        user_data = tid->user_data; /* 先保存老的user_data */

        /* 创建一个256 x 4的消息队列*/
        mq = rt_mq_create("msgbox", 256, 4, RT_IPC_FLAG_FIFO);
        /* 注册消息队列mq到当前线程上 */
        rtgui_thread_register(tid, mq);

        /* 创建窗口,参数包含父控件,窗口标题、窗口位置(注意窗口的标题、边框是由GUI Server维护的,所以实际上大小要大上一圈),风格参数 */
        msgbox = rtgui_win_create(RT_NULL, "Information", &rect, RTGUI_WIN_STYLE_DEFAULT);
        if (msgbox != RT_NULL)
        {
                /* box是个盒子,更是一个布局的引擎,内部的空间不需要指定位置,可以由box自动布局 */
                struct rtgui_box* box = rtgui_box_create(RTGUI_VERTICAL, RT_NULL);
                struct rtgui_label* label = rtgui_label_create("Hello World"); /* 创建Hello World的标签 */

                rtgui_win_set_box(msgbox, box); /* 设置窗口的布局盒子 */
                RTGUI_WIDGET(label)->align = RTGUI_ALIGN_CENTER_HORIZONTAL | RTGUI_ALIGN_CENTER_VERTICAL; /* 标签的布局风格,垂直、水平居中 */
                rtgui_widget_set_miniwidth(RTGUI_WIDGET(label),130); /* 设置标签的最小长度 */
                rtgui_box_append(box, RTGUI_WIDGET(label)); /* 添加标签到盒子中 */
                rtgui_box_layout(box); /* 当所有控件添加完时,显式调用layout做盒子的自动布局 */

                rtgui_win_show(msgbox); /* 显示这个窗口 */
        }

        rtgui_win_event_loop(msgbox); /* 执行窗口的事件循环 */

        /* 能够走到这步,就意味着,这个窗口已经关闭了 */

        rtgui_thread_deregister(tid); /* 去注册当前线程 */
        /* 删除相应的消息队列 */
        rt_mq_delete(mq);

        /* 恢复用户数据 */
        tid->user_data = user_data;
}
FINSH_FUNCTION_EXPORT(msg, msg on gui);

执行这个函数,UI截图:
http://cache.amobbs.com/bbs_upload782111/files_20/ourdev_494016.JPG
(原文件名:SV400001.JPG)

在finsh下执行这个函数,有个非常明显的特点,即执行后,finsh就不再响应命令,因为finsh shell的线程已经作为UI线程来使用。有时,UI可能也会有那种自动弹出对话框的提示,显示一段时间后,自动关闭。看看这个在RTGUI中应该如何来实现的:

/* 因为要跨函数访问,所以把下面这些作为全局变量 */
static struct rtgui_timer *timer;
static struct rtgui_label* label;
static struct rtgui_win* msgbox;
static rt_uint8_t label_text;
static int cnt = 5;

/* 显示对话框后的定时器超时回调函数,和RT-Thread中的timer不相同,这个超时函数的执行上下文是显示对话框线程的上下文 */
void diag_close(struct rtgui_timer* timer, void* parameter)
{
        rt_sprintf(label_text, "closed then %d second!", cnt);
       
        /* 重新设置标签文本 */
        rtgui_label_set_text(label, label_text);
        /* 调用update函数更新显示 */
        rtgui_widget_update(RTGUI_WIDGET(label));
        if (cnt == 0)
        {
                /* 当cnt减到零时,删除对话框 */
                rtgui_win_destroy(msgbox);
                rtgui_timer_stop(timer);
                /* 删除定时器 */
                rtgui_timer_destory(timer);
        }

        cnt --;
}

void msg()
{
        rt_mq_t mq;
        rt_thread_t tid;
        rt_uint32_t user_data;
        struct rtgui_rect rect = {50, 50, 200, 200};

        tid = rt_thread_self();
        if (tid == RT_NULL) return; /* can't use in none-scheduler environement */
        user_data = tid->user_data;

        /* create gui message queue */
        mq = rt_mq_create("msgbox", 256, 4, RT_IPC_FLAG_FIFO);
        /* register message queue on current thread */
        rtgui_thread_register(rt_thread_self(), mq);

        msgbox = rtgui_win_create(RT_NULL, "Information", &rect, RTGUI_WIN_STYLE_DEFAULT);
        if (msgbox != RT_NULL)
        {
                struct rtgui_box* box = rtgui_box_create(RTGUI_VERTICAL, RT_NULL);

                cnt = 5;
                rt_sprintf(label_text, "closed then %d second!", cnt);
                label = rtgui_label_create(label_text);

                rtgui_win_set_box(msgbox, box);
                RTGUI_WIDGET(label)->align = RTGUI_ALIGN_CENTER_HORIZONTAL |
                        RTGUI_ALIGN_CENTER_VERTICAL;
                rtgui_widget_set_miniwidth(RTGUI_WIDGET(label),130);
                rtgui_box_append(box, RTGUI_WIDGET(label));
                rtgui_box_layout(box);

                rtgui_win_show(msgbox);
        }

        /* 多了个定时创建的动作,超时时执行的是diag_close函数 */
        timer = rtgui_timer_create(200, RT_TIMER_FLAG_PERIODIC,
                diag_close, RT_NULL);
        rtgui_timer_start(timer);

        rtgui_win_event_loop(msgbox);

        rtgui_thread_deregister(rt_thread_self());
        /* remove RTGUI message queue */
        rt_mq_delete(mq);

        /* recover user data */
        tid->user_data = user_data;
}
FINSH_FUNCTION_EXPORT(msg, msg on gui)

chairang 发表于 2009-10-21 07:57:16

越来越厉害啦,恭喜

ffxz 发表于 2009-10-21 08:00:13

上面这个例子可以在google svn的STM32网络收音机的目录中取的,并能够在网络收音机的板子上运行。

RTGUI的许可证与RT-Thread的许可证相同,不过目前依然处于开发阶段(alpha版本),对于应用到产品中出现的问题不负相应的责任,并且不提供应用到产品中的技术支持(目前仅提供交流)。

ffxz 发表于 2009-10-21 08:00:43

【1楼】 chairang 板砖
好早,好迅速!

lwy86 发表于 2009-10-21 08:24:52

终于发布了!

zhifeng 发表于 2009-10-21 08:58:03

强烈支持!我非常感兴趣,只是要为解决生计问题奔波,无力分身。

ffxz 发表于 2009-10-21 09:19:23

【5楼】 zhifeng
呵呵,RTGUI成形于年初,一直忙着其他事 & 手上没适合的平台,所以一直没发布,一直拖到现在。

另外,你发过来的那块lpc2478板子,可能得失望了,上面缺少太多的器件,在我手上基本比较难焊接得出来。

songzi2018 发表于 2009-10-21 20:00:07

顶一下,RTGUI呢?哪里下载?想抢先测试

ffxz 发表于 2009-10-21 20:39:47

RT-Thread/GUI:
http://code.google.com/p/rt-thread/source/checkout

默认google svn上能够找到RT-Thread的最新代码

kugel 发表于 2009-10-21 20:47:51

顶,关注一下。

smallworm 发表于 2009-10-21 22:16:30

都出gui了 支持

feng200808 发表于 2009-10-23 15:54:55

强烈支持

ffxz 发表于 2009-11-2 22:41:02

RTGUI教程之二 -- workbench 上

workbench从英文的意思来看,
n. 1. 工作台;作业台

RTGUI的workbench是一件很有意义的划分,特别是当RTGUI不支持窗口的时候。RTGUI中的工作台是指,屏幕上互不重叠的区域,或者说这块区域是由每个工作台_独占的(如果不存在window的情况下)。

在这个概念下,相当于上层应用可以直接访问物理底层帧缓冲,而不用通知别人,我这个工作台更新了,你这里如果有重叠部分,那么应该重新刷新你的图形。所以可以说,RTGUI的工作台具备独占性。

上次和东南的研究生聊到了一个示波器的原型,他们采用的是FPGA采集数据,而且可能是采集的数据速度远远大于ARM9的处理速度。不过这个系统有个好处,FPGA的数据实际上就是一幅图像,可以直接放到帧缓冲中进行显示。类似于这类应用,可以在RTGUI如何展开呢:首先可以把显示波形的部分单独独立成一个工作台,即独占的了!然后采用DMA的方式直接从FPGA中把图像数据搬移到帧缓冲的相应位置。够直接吧,速度也超级快的,因为ARM这边的干预非常少,只有DMA的动作。

好了,工作台大致使用目的知道了,那么看看,如何在RTGUI中操作工作台呢。首先一个,需要对屏幕面板进行划分,划分出各自的互不重叠的区域:(以下的代码都可在stm32 radio开发板上运行,下同)

                /* register dock panel */
                rect.x1 = 0;
                rect.y1 = 0;
                rect.x2 = 240;
                rect.y2 = 25;

                rtgui_panel_register("info", &rect);

                /* register main panel */
                rect.x1 = 0;
                rect.y1 = 25;
                rect.x2 = 240;
                rect.y2 = 320;
                rtgui_panel_register("main", &rect);


这段代码的就是用来注册不同的区域的,总计包括:
(00, 00) - (240, 25)的info区域
(00, 25) - (240, 320)的main区域

rtgui_panel_register函数用来注册区域,每个区域需要一个名字,以及它的位置信息。

有了个各个互不重叠的区域信息之后,那就是如何在工作台上绘图了,先看看如何创建一个工作台:

/* 相类似的,GUI应用总是依赖于一个可执行的线程环境,先创建一个GUI线程 */
void workbench_init()
{
        static rt_bool_t inited = RT_FALSE;

        if (inited == RT_FALSE) /* 避免重复初始化而做的保护 */
        {
                rt_thread_t tid;

                tid = rt_thread_create("wb",
                        workbench_entry, RT_NULL,
                        2048, 25, 10);

                if (tid != RT_NULL) rt_thread_startup(tid);

                inited = RT_TRUE;
        }
}

#ifdef RT_USING_RTGUI
#include <finsh.h>
void workbench()
{
        workbench_init();
}
/* finsh的命令输出,可以直接执行workbench()函数以执行上面的函数 */
FINSH_FUNCTION_EXPORT(workbench, workbench demo)
#endif


接下来是,做一个Hello World,这个比上一节窗口稍微复杂些,上节采用的是label控件,这里则直接用绘图的形式绘制出来。

static void workbench_entry(void* parameter)
{
        rt_mq_t mq;
        struct rtgui_view* view;
        struct rtgui_workbench* workbench;

        /* 创建GUI应用需要的消息队列 */
        mq = rt_mq_create("qWB", 256, 4, RT_IPC_FLAG_FIFO);
        /* 注册当前线程为GUI线程 */
        rtgui_thread_register(rt_thread_self(), mq);

        /* 创建一个工作台 */
        workbench = rtgui_workbench_create("main", "workbench");
        if (workbench == RT_NULL) return;

        /* 创建一个工作台上的一个视图 */
        view = rtgui_view_create("widget");
        rtgui_widget_set_event_handler(RTGUI_WIDGET(view), view_event_handler);

        /* 在工作台上添加一个视图 */
        rtgui_workbench_add_view(workbench, view);

        /* 显示这个视图 */
        rtgui_view_show(view);

        /* 执行工作台事件循环 */
        rtgui_workbench_event_loop(workbench);

        /* 去注册GUI线程 */
        rtgui_thread_deregister(rt_thread_self());
        rt_mq_delete(mq);
}


在上面的例子中,我们还看到,除了创建一个工作台之外,还创建了一个view - 视图。我们可以类比下,工作台好比自己的办公桌,桌子上满满的铺上了一张地图,占据了桌面的所有面积。但是,如何换一张地图呢?所以这里有了视图的概念,桌面上的地图可以一张张换,当然了,也可以只是一张。

另外,如上说的,这个例子中要使用的是自行绘制字符串的方式显示“Hello World”,那么如何才能自行绘制呢?要进行绘制必须首先获得一个图形设备的上下文,可以调用函数:
rtgui_dc_begin_drawing(widget);
获得,参数是一个控件(workbench、view也是一类特殊控件)

当绘图完成时,调用
rtgui_dc_end_drawing(dc);
函数即可(当应用绘制完图形时,可能这个图形还只存在于缓冲中,并没有刷新到LCD上,所以rtgui_dc_end_drawing会自动通知底层驱动,进行一次更新操作)。

RTGUI内部是完全事件驱动的,特别是当有窗口支持时,事件会显得特别重要,例如:当覆盖到一个工作台的窗口消失时,RTGUI就需要通知被覆盖的工作台做相应的更新动作。
(还记得上一节定时器窗口,当计数器减到0时,为什么窗口还在吗?因为,它所覆盖的地方没有任何应用,RTGUI也当然不会通知更新了)

应用需要做的就是截获相应的事件:

static rt_bool_t view_event_handler(struct rtgui_widget* widget, struct rtgui_event* event)
{
        /* 我们目前只对绘制事件感兴趣 */
        if (event->type == RTGUI_EVENT_PAINT)
        {
                struct rtgui_dc* dc;
                struct rtgui_rect rect;

                /* 获得一个设备上下文 */
                dc = rtgui_dc_begin_drawing(widget);
                if (dc == RT_NULL) return RT_FALSE; /* 如果获取失败代表什么?这个控件是隐藏的或... */
                rtgui_widget_get_rect(widget, &rect); /* 获得控件的可视区域 */

                /* 先对所在可视区域全部填充为背景色 */
                rtgui_dc_fill_rect(dc, &rect);

                /* 绘制一个hello! */
                rtgui_dc_draw_text(dc, "hello world", &rect);

                /* 通知RTGUI,绘制结束 */
                rtgui_dc_end_drawing(dc);

                return RT_FALSE;
        }

        /* 如果不是绘制事件,使用view原来的事件处理函数处理 */
        return rtgui_view_event_handler(widget, event);
}


完整的代码如下:

#include <rtgui/rtgui.h>
#include <rtgui/rtgui_system.h>

#include <rtgui/widgets/view.h>
#include <rtgui/widgets/workbench.h>

static rt_bool_t view_event_handler(struct rtgui_widget* widget, struct rtgui_event* event)
{
        /* 我们目前只对绘制事件感兴趣 */
        if (event->type == RTGUI_EVENT_PAINT)
        {
                struct rtgui_dc* dc;
                struct rtgui_rect rect;

                /* 获得一个设备上下文 */
                dc = rtgui_dc_begin_drawing(widget);
                if (dc == RT_NULL) return RT_FALSE; /* 如果获取失败代表什么?这个控件是隐藏的或... */
                rtgui_widget_get_rect(widget, &rect); /* 获得控件的可视区域 */

                /* 先对所在可视区域全部填充为背景色 */
                rtgui_dc_fill_rect(dc, &rect);

                /* 绘制一个hello! */
                rtgui_dc_draw_text(dc, "hello world", &rect);

                /* 通知RTGUI,绘制结束 */
                rtgui_dc_end_drawing(dc);

                return RT_FALSE;
        }

        /* 如果不是绘制事件,使用view原来的事件处理函数处理 */
        return rtgui_view_event_handler(widget, event);
}

static void workbench_entry(void* parameter)
{
        rt_mq_t mq;
        struct rtgui_view* view;
        struct rtgui_workbench* workbench;

        /* 创建GUI应用需要的消息队列 */
        mq = rt_mq_create("qWB", 256, 4, RT_IPC_FLAG_FIFO);
        /* 注册当前线程为GUI线程 */
        rtgui_thread_register(rt_thread_self(), mq);

        /* 创建一个工作台 */
        workbench = rtgui_workbench_create("main", "workbench");
        if (workbench == RT_NULL) return;

        /* 创建一个工作台上的一个视图 */
        view = rtgui_view_create("view");
        rtgui_widget_set_event_handler(RTGUI_WIDGET(view), view_event_handler);

        /* 在工作台上添加一个视图 */
        rtgui_workbench_add_view(workbench, view);

        /* 显示这个视图 */
        rtgui_view_show(view);

        /* 执行工作台事件循环 */
        rtgui_workbench_event_loop(workbench);

        /* 去注册GUI线程 */
        rtgui_thread_deregister(rt_thread_self());
        rt_mq_delete(mq);
}

void workbench_init()
{
        static rt_bool_t inited = RT_FALSE;

        if (inited == RT_FALSE) /* 避免重复初始化而做的保护 */
        {
                rt_thread_t tid;

                tid = rt_thread_create("wb",
                        workbench_entry, RT_NULL,
                        2048, 25, 10);

                if (tid != RT_NULL) rt_thread_startup(tid);

                inited = RT_TRUE;
        }
}

#ifdef RT_USING_RTGUI
#include <finsh.h>
void workbench()
{
        workbench_init();
}
/* finsh的命令输出,可以直接执行workbench()函数以执行上面的函数 */
FINSH_FUNCTION_EXPORT(workbench, workbench demo)
#endif

ffxz 发表于 2009-11-2 22:45:45

为radio做的文件列表视图,用于打开mp3文件,明天弄到板子上再拍一张照片出来,后面应该会有更多radio的照片放出。
http://cache.amobbs.com/bbs_upload782111/files_21/ourdev_498969.png
(原文件名:filelist.png)

ffxz 发表于 2009-11-2 22:53:35

前段时间听网友说,RTGUI和picgui很像,而前几天网友也给出了win32下的picgui,终于也能尝试下picgui是什么样子的,看了后不得不承认,picgui的控件比RTGUI的丰富多了,RTGUI很多地方还比较简陋,所以也希望能够通过这次的radio项目把它做到商业应用的级别上来。

当然,看过picgui后,也发觉其中非常重要的一点区别:当picgui显示动画时,例如显示它的那个波形:如果此时点击上面的菜单,波形就动不了了。相反,RTGUI则不同,当workbench上显示动画时,打开一个窗口,workbench上的动画是继续显示的。这个估计就是结构不同的奥秘了,当然另外一个非常重要的区别,RTGUI是面向多线程而设计的。

ffxz 发表于 2009-12-7 10:14:44

=============================
RTGUI教程之三 -- workbench 下
=============================
workbench下这一篇实际上讨论的是workbench上的view(视图)。

----
视图
----
如上节描述:
视图 -- 可以类比下,工作台好比自己的办公桌,桌子上满满的铺上了一张地图,占据了桌面的所有面积。但是,如何换一张地图呢?所以这里有了视图的概念,桌面上的地图可以一张张换,当然了,也可以只是一张。

视图实际上让工作台上有多重显示的能力,只需要把它一个个切换即可。而针对于显示面积比较小的液晶,窗口都不必要了,直接用视图代替:
显示消息对话框时,切换成相应的视图进行消息提示等。

如果是面向内存比较吃紧的嵌入式系统,也建议多使用视图,因为创建一个视图的开销要远远的小于创建窗口的开销,而且视图是GUI服务端不可见的(GUI服务端可见的是一个个的workbench,每个workbench又绑定到独立的线程上)。视图也可以看成是PC上的标签页,只是不相同的是RTGUI的视图少了标签页的按钮(或许以后会加上:-))。

view也是RTGUI里一类控件,继承于容器控件,它能够包含多个子控件等。既然视图是可以做为窗口的替代品,那么它就必须具备某些品质:
例如PC上的文件对话框,当父窗口需要打开一个文件时,通常会:
void MainFrm::OnFileOpen()
{
    /* 创建对话框 */
    FileDiag diag(parent);
   
    /* 显示对话框 */
    if (diag.ShowModal() == OK)
    {
      char* fn = diag.GetFileName();
      
      /* 相应的文件处理操作 */
    }
}

这个其中隐含着:
文件对话框是基于父窗口的,
文件对话框等待用户交互,并取得最终的选择,
父窗口应该在ShowModal()上等待用户的输入,只有当文件对话框有结果时才继续往下运行,然后当获得文件名时进行相应的操作。

这类操作在GUI上叫做:模式对话框显示,以下的是wikipedia的解释:
In user interface design, a modal window is a child window that requires the user to interact with it before they can return to operating the parent application, thus preventing the workflow on the application main window. Modal windows are often called heavy windows or modal dialogs because the window is often used to display a dialog box.

-----------------------
RTGUI里的Modal View使用
-----------------------
在RTGUI里,view创建时并不需要指定是否创建成modal视图,而是在显示时:
rtgui_modal_code_t rtgui_view_show(rtgui_view_t* view, rt_bool_t is_modal);

参数is_modal指示出是否是modal的视图,如果是RT_TRUE,那么将显示一个modal模式的视图,并且这个函数直到用户选择了结果才会返回。

这个函数的返回值rtgui_modal_code_t的类型,目前只支持两种:
RTGUI_MODAL_OK
RTGUI_MODAL_CANCEL

而具体返回的值是由view的实现代码决定的,例如在view的事件处理函数中,当收到用户的键盘输入(RTGUIK_RETURN),它调用函数:
void rtgui_view_end_modal(rtgui_view_t* view, rtgui_modal_code_t modal_code);

设置modal_code为RTGUI_MODAL_OK,这样就会返回RTGUI_MODAL_OK的值。

---------------------
Radio中Modal View示例
---------------------
下面解析了Radio工程中使用Modal View的示例,完整的代码可以从Radio工程中获得。
player_ui.c的function_filelist函数:

/*
* 这个函数是挂接在功能列表里 struct list_item function_list[],
* 被功能视图所使用,从而形成一个类似菜单的功能
*/
void function_filelist(void* parameter)
{
    rtgui_rect_t rect;
    filelist_view_t *view;

    /* 获得父workbench的矩形框信息 */
    rtgui_widget_get_rect(RTGUI_WIDGET(workbench), &rect);
   
    /* 创建一个文件列表视图 */
    view = filelist_view_create(workbench, "/", "*.*", &rect);
    if (view != RT_NULL)
    {
      /* 采用modal模式显示这个视图,这个函数只有在用户有相应交互的时候才返回 */
      if (rtgui_view_show(RTGUI_VIEW(view), RT_TRUE) == RTGUI_MODAL_OK)
      {
            char fn;

            /*
             * filelist视图返回时提供了当前目录,当前文件名的信息,所以这里根据
             * 它们取得相应的文件名
             */
            rt_snprintf(fn, 64, "%s/%s", view->current_directory,
                view->items.name);

            /* mp3文件的处理 */
            if (strstr(view->items.name , ".mp3") != RT_NULL ||
                strstr(view->items.name , ".MP3") != RT_NULL)
            {
                /* clear old play list */
                play_list_clear();
                play_list_append(fn);

                player_mode = PLAYER_PLAY_FILE;
                next_step = PLAYER_STEP_STOP;
                play_mp3_file(play_list_start());
            }
            /* m3u文件的处理 */
            else if (strstr(view->items.name , ".m3u") != RT_NULL ||
                strstr(view->items.name , ".M3U") != RT_NULL)
            {
                /* read all of music filename to a list */
                int fd;
                char line;

                fd = open(fn, O_RDONLY, 0);
                if (fd >= 0)
                {
                  rt_uint32_t length;

                  length = read_line(fd, line, sizeof(line));
                  if (strcmp(line, "#EXTM3U") == 0)
                  {
                        /* clear old play list */
                        play_list_clear();

                        do
                        {
                            length = read_line(fd, line, sizeof(line));
                            if (length > 0)
                            {
                              if (line != '/')
                              {
                                    rt_snprintf(fn, sizeof(fn),
                                        "%s/%s", view->current_directory, line);
                                    play_list_append(fn);
                              }
                              else play_list_append(line);
                            }
                        } while (length > 0);
                  }

                  close(fd);

                  if (play_list_items() > 0)
                  {
                        player_mode = PLAYER_PLAY_FILE;
                        next_step = PLAYER_STEP_NEXT;
                        play_mp3_file(play_list_start());
                  }
                }
            }
            /* wav文件的处理 */
            else if (strstr(view->items.name , ".wav") != RT_NULL ||
                strstr(view->items.name , ".WAV") != RT_NULL)
            {
            }
      }

      /* 注意:从show_modal状态返回,视图并没消失,需要手动的删除这个视图以释放相应的内存 */
      filelist_view_destroy(view);
    }

    return;
}

filelist视图中和modal模式密切相关的代码:(注:filelist视图是一个全新的视图,从view继承而来)
/* fileist.c文件 filelist视图事件处理函数 */
rt_bool_t filelist_view_event_handler(struct rtgui_widget* widget, struct rtgui_event* event)
{
    struct filelist_view* view = RT_NULL;

    /* 这个是filelist视图,先把widget转换成filelist类型的结构 */
    view = FILELIST_VIEW(widget);
    switch (event->type)
    {
    /* 重绘事件 */
    case RTGUI_EVENT_PAINT:
      filelist_view_ondraw(view);
      return RT_FALSE;

    /* 更改大小事件 */
    case RTGUI_EVENT_RESIZE:
      {
            struct rtgui_event_resize* resize;

            resize = (struct rtgui_event_resize*)event;

            /* recalculate page items */
            if (file_image != RT_NULL)
                view->page_items = resize->h/ (1 + rtgui_theme_get_selected_height());
            else
                view->page_items = resize->h / (2 + 14);
      }
      break;

    /* 键盘按键输入事件 */
    case RTGUI_EVENT_KBD:
      {
            struct rtgui_event_kbd* ekbd = (struct rtgui_event_kbd*)event;
            if (ekbd->type == RTGUI_KEYDOWN) /* 当是按键按下事件时 */
            {
                rt_uint16_t old_item;

                old_item = view->current_item;
                switch (ekbd->key)
                {
                case RTGUIK_UP: /* 对UP方向键处理 */
                  if (view->current_item > 0)
                        view->current_item --;
                  filelist_view_update_current(view, old_item);
                  return RT_FALSE;

                case RTGUIK_DOWN: /* 对DOWN方向键处理 */
                  if (view->current_item < view->items_count - 1)
                        view->current_item ++;
                  filelist_view_update_current(view, old_item);
                  return RT_FALSE;

                case RTGUIK_LEFT: /* 对LEFT方向键处理 */
                  if (view->current_item - view->page_items >= 0)
                        view->current_item -= view->page_items;
                  filelist_view_update_current(view, old_item);
                  return RT_FALSE;

                case RTGUIK_RIGHT: /* 对RIGHT方向键处理 */
                  if (view->current_item + view->page_items < view->items_count - 1)
                        view->current_item += view->page_items;
                  filelist_view_update_current(view, old_item);
                  return RT_FALSE;

                case RTGUIK_RETURN: /* 对RETURN方向键处理 */
                  if (view->items.type == FITEM_DIR)
                  {
                        char new_path;

                        /* 如果当前项是目录 */
                        if (strcmp(view->items.name, ".") == 0) return RT_FALSE;
                        if (strcmp(view->items.name, "..") == 0)
                        {
                            /* 父目录的处理 */
                            char *ptr;
                            ptr = strrchr(view->current_directory, PATH_SEPARATOR);

                            if (ptr == RT_NULL) return RT_FALSE;
                            if (ptr == &(view->current_directory))
                            {
                              /* it's root directory */
                              new_path = PATH_SEPARATOR;
                              new_path = '\0';
                            }
                            else
                            {
                              strncpy(new_path, view->current_directory, ptr - view->current_directory + 1);
                              new_path = '\0';
                            }
                        }
                        else if (view->current_item == 0 &&
                            (view->current_directory == '/') && (view->current_directory == '\0'))
                        {
                            /* 如果是根目录,并且是第0个项(即显示的是 "退出文件浏览") */
                            if (RTGUI_VIEW(view)->modal_show == RT_TRUE)
                            {
                              /* 如果是modal模式显示,置modal返回值是RTGUI_MODAL_CANCEL */
                              rtgui_view_end_modal(RTGUI_VIEW(view), RTGUI_MODAL_CANCEL);
                            }
                            else
                            {
                              /* 如果不是modal模式显示,直接删除这个视图 */
                              filelist_view_destroy(view);
                            }

                            return RT_FALSE;
                        }
                        else
                        {
                            /* 新目录的处理(进入这个目录) */
                            if (view->current_directory != PATH_SEPARATOR)
                              sprintf(new_path, "%s%c%s",view->current_directory, PATH_SEPARATOR,
                                    view->items.name);
                            else
                              sprintf(new_path, "%s%s",view->current_directory,
                              view->items.name);
                        }
                        filelist_view_set_directory(view, new_path);
                  }
                  else
                  {
                        /* 是在文件上进行RETURN按键,并且是modal模式显示*/
                        if (RTGUI_VIEW(view)->modal_show == RT_TRUE)
                        {
                            /* 设置返回值为RTGUI_MODAL_OK */
                            rtgui_view_end_modal(RTGUI_VIEW(view), RTGUI_MODAL_OK);
                        }
                  }
                  return RT_FALSE;

                default:
                  break;
                }
            }
      }
      return RT_FALSE;
    }

    /* 不是这个view感兴趣的时间,直接调用父类进行处理 */
    return rtgui_view_event_handler(widget, event);
}

ffxz 发表于 2009-12-10 23:44:57

=====================
RTGUI教程之四:widget
=====================

widget意为小玩意,小器件。在GUI中,这个泛指图形上的基本元素,例如按钮,标签,文本输入框等。

这一章就说说RTGUI里的widget,而且主要集中在RTGUI里的各个图形元素的最底层的、最基本的元素。

----------------
RTGUI Widget结构
----------------

widget结构定义放在rtgui\include\widgets\widget.h 中,是一个相对较大的结构:
struct rtgui_widget
{
   /* 从rtgui_object继承而来 */
    struct rtgui_object object;

    /* 父控件和根控件 */
    struct rtgui_widget *parent, *toplevel;
    /* 控件兄弟列表 */
    rtgui_list_t sibling;

    /* 控件的参数 */
    rt_int32_t flag;
    /* 控件的对齐方式 */
    rt_int32_t align;

    /* 控件的图形上下文(前景色、背景色等,这个是每个控件都独立的) */
    rtgui_gc_t gc;

    /* 控件的外观信息 */
    rtgui_rect_t extent;
    /* 控件的最小宽度和最小高度 */
    rt_int16_t mini_width, mini_height;
    rt_int16_t margin, margin_style;

    /* 控件的剪切域 */
    rtgui_region_t clip;
    /* 控件的剪切域同步标志 */
    rt_uint32_t clip_sync;

    /* 控件的事件处理函数,默认指向rtgui_widget_event_handler,可重载为其他值 */
    rt_bool_t (*event_handler)    (struct rtgui_widget* widget, struct rtgui_event* event);

    /* 一些回调函数 */
    rt_bool_t (*on_draw)      (struct rtgui_widget* widget, struct rtgui_event* event);
    rt_bool_t (*on_focus_in)    (struct rtgui_widget* widget, struct rtgui_event* event);
    rt_bool_t (*on_focus_out)    (struct rtgui_widget* widget, struct rtgui_event* event);
    rt_bool_t (*on_mouseclick)    (struct rtgui_widget* widget, struct rtgui_event* event);
    rt_bool_t (*on_key)            (struct rtgui_widget* widget, struct rtgui_event* event);
    rt_bool_t (*on_size)      (struct rtgui_widget* widget, struct rtgui_event* event);
    rt_bool_t (*on_command)      (struct rtgui_widget* widget, struct rtgui_event* event);
};
typedef struct rtgui_widget rtgui_widget_t;

从结构中,我们可以大致看到几类信息:
控件树的维护节点,parent、toplevel、sibling
图形相关的信息,gc、align、extent等
剪切域信息,clip和clip_sync

然后是为了重载用的事件处理函数,以及一些回调函数(当一些特定的事件发生时,进行相应的回调函数调用)

-------------
RTGUI的控件树
-------------

RTGUI的各个控件是采用树形方式组织的,一般的widget可以看成一个个叶子,但从它派生的类,例如container,则具备了容纳子widget的能力。container是非常重要的一个类,看看它的结构定义:

struct rtgui_container
{
    /* 从rtgui_widget继承而来*/
    struct rtgui_widget parent;

    /* container的焦点widget   */
    struct rtgui_widget* focused;
    /* container包含的子widget */
    rtgui_list_t children;
};
typedef struct rtgui_container rtgui_container_t;

好了,有了container的结构定义,就可以看看RTGUI里的控件树:
http://cache.amobbs.com/bbs_upload782111/files_23/ourdev_513019.png
(原文件名:rtgui_widget_tree.png)

widget间的兄弟节点依靠sibling连接,子节点依靠container的children连接。

---------------
RTGUI的几个控件
---------------

从代码里可以看得出,绝大多数类是从rtgui_widget派生而来的,而所有的控件类都是从widget中派生的:
- workbench,工作台,附加在各个panel上,一个workbench就等于一个RT-Thread线程。view,视图,附加在workbench上(可多个视图附加在一个workbench上)。
- window,窗口,可以是完全独立的窗口(必须附加在一个RT-Thread线程上);也可以是关联在workbench上的窗口(事件处理在workbench的事件处理循环中进行)。
- toplevel控件,绘图上最顶层的控件,workbench和window类都是从它派生的。
- container控件,能够包容子控件的控件容器,toplevel,view从它派生。
- widget控件,所有控件类的基类。
- button,label,textbox等,具体的控件元素,可加入到view或window中。

注:view控件只能加入到workbench上,不能加到window,或其他container控件中。

ffxz 发表于 2009-12-10 23:56:08

吹吹风,

目前和友善之臂达成合作,明年一季度左右会直接放出mini2440的RT-Thread代码,基本包括:
- YAFFS2在RT-Thread的移植
- 动态加载模块支持(这个采用RT-Thread自己实现的链接器,不会和linux kernel动态模块加载雷同,会做一遍静态加载,加载速度会快很多)

基于上面两个特性,会实现出全新的GUI应用程序。还有既然是2440这种系统,是不是应该实现一个比较完善的POSIX标准,这个慢慢考虑吧。RTGUI是一个完全多线程模式的图形用户界面系统!

videre 发表于 2009-12-11 00:09:13

感觉对较小分辨率的LCD适应性不是很好,至少是QVGA才能显示比较好的效果吧

ffxz 发表于 2009-12-11 07:06:44

较小分辨率?

目前RTGUI跑的大多在320x240的分辨率上,但也不是绝对的,太小了参见RTGUI在STM32上的支持情况。在2440上,应该会做出类似iPhone的划屏效果吧,划屏可以看成是针对view控件的一个特殊显示效果。

songzi2018 发表于 2009-12-11 09:04:35

支持RTGUI&RT-Thread

modelfly 发表于 2010-2-4 13:13:27

能否搞一个OS 配置工具。
也就是说,版本释放的时候,基本core是固定的,差异化配置,比如任务个数,任务堆栈大小,优先级等均是可以通过图形界面配置,生成配置文件,然后直接集成就OK。

目前汽车行业的OS和协议栈都是这样的,能够节省很多项目开发时间。

loongsuns 发表于 2010-5-8 14:02:59

mark

psocfans 发表于 2010-5-8 22:48:46

等套件到手后,好好学习。

jichong211 发表于 2010-7-5 21:21:22

做UI的用RTGUI是不是还要学习RTT的内核啊,这样很麻烦啊,是不是应该考虑建个MMK,负责内核和UI的连接,对UI开发人员来说屏蔽掉线程,消息等操作

ffxz 发表于 2010-7-6 06:16:08

因为RT-Thread/GUI是一个面向多线程的GUI系统。

hwdpaley 发表于 2010-11-18 23:15:26

有点明白,不断学习。

zjwznlz 发表于 2010-12-16 11:29:32

已经学会自己设计界面了 呵呵

haigerl 发表于 2010-12-16 11:59:52

mark

taishan 发表于 2010-12-16 15:05:41

DIY可以,做产品还是不敢用。

wxw123321 发表于 2011-1-18 20:13:04

正在学习,谢谢了

jiaren 发表于 2011-1-21 10:47:59

正接触RT-Thread,想学GUI很久了,学习ing,希望能有更多文档,哈哈

flyan.oo 发表于 2011-2-25 17:32:02

由于板子的存储资源不充足,问下RTGUI在STM32上运行的最小存储配置得多少?学习中......

xue110592 发表于 2011-3-31 12:36:02

没想到早就有相关资料介绍了,看懂很吃力。得慢慢啃。

shiguiyuan 发表于 2011-4-1 17:45:57

mark

qingqng 发表于 2011-4-4 12:31:10

学习中,支持下

bin8 发表于 2011-4-16 23:26:17

这样的教程为啥不置顶?版主不尽职

utopiaprince 发表于 2011-8-5 09:31:12

mark下

ryen1987 发表于 2011-10-20 22:21:11

很好的学习资料,最近也在弄这个,马克了

marrylilili 发表于 2011-12-20 16:17:03

mark!!楼主太强大了!!!

liangyurongde 发表于 2011-12-20 16:24:12

mark

hyskt 发表于 2012-1-2 19:59:44

学习中

marrylilili 发表于 2012-2-21 15:37:43

MARK!!

liurm0 发表于 2012-2-22 11:45:09

学习中

xslff 发表于 2012-2-24 13:36:53

好东西啊!

myqq110 发表于 2012-3-7 13:19:28

学习

wych159753 发表于 2012-3-29 17:33:16

mark{:smile:}

nomoneyiv 发表于 2012-3-31 11:33:41

希望新的GUI,示例能继续放出来!!!!{:smile:}

wych159753 发表于 2012-4-10 13:46:10

mark{:smile:}

fm0826 发表于 2012-7-26 22:24:13

RTGUI rtgui

danshi126 发表于 2012-8-16 18:12:42

好家伙呀,,希望流行起来

betbet 发表于 2013-5-1 16:39:22

#include <rtgui/rtgui.h>这个编译通不过啊,提示找不到这个文件,我改为#include “rtgui.h”就OK了,请问你是怎么编译通过的?

寒寒 发表于 2013-6-15 22:47:38

请问如何关联液晶的画线等驱动

MobileLover 发表于 2013-6-30 16:58:45

飘过       了解
页: [1]
查看完整版本: RT-Thread RTOS组件:RTGUI