├── .gitbook └── assets │ ├── architecture.png │ ├── box-model.png │ ├── canvas_default_grid.png │ ├── css-library-struct.png │ ├── dirty-rectangle.png │ ├── dog.jpg │ ├── font_icons.png │ ├── getting_started_step_1.png │ ├── getting_started_step_2.png │ ├── getting_started_step_3.png │ ├── getting_started_step_4.gif │ ├── getting_started_step_5.gif │ ├── gui_events_touch.gif │ ├── pariallel-rendering.png │ ├── pixel-pipe-1.png │ ├── pixel-pipe-2.png │ ├── pixel-pipe-3.png │ ├── test_char_render.png │ ├── test_fill_rect.png │ ├── test_fill_rect_with_rgba.png │ ├── test_mix_rect_with_opacity.png │ ├── test_paint_background_color.png │ ├── test_paint_background_image.png │ ├── test_paint_background_image_with_position.png │ ├── test_paint_background_image_with_size.png │ ├── test_paint_border.png │ ├── test_paint_boxshadow.png │ ├── test_pixel_manipulation.png │ ├── test_string_render.png │ ├── test_widget_render.png │ ├── widget-lifecircle.png │ └── widget-update.png ├── .gitignore ├── LANGS.md ├── README.md ├── SUMMARY.md ├── app ├── README.md ├── events.md ├── mainloop.md ├── timer.md └── worker.md ├── base ├── README.md ├── architecture.md ├── convention.md ├── css-syntax.md ├── installation.md ├── introduction.md └── xml-syntax.md ├── book.json ├── bu-ju.md ├── css-1 ├── README.md ├── css-jie-xi-qi.md └── css-shu-ju-ku.md ├── css ├── README.md ├── library.md └── parser.md ├── driver ├── README.md ├── events.md ├── keyboard.md ├── mouse.md ├── touchscreen.md └── video.md ├── en-us ├── README.md └── summary.md ├── font ├── README.md ├── engine.md ├── library.md └── textlayer.md ├── graphics ├── README.md ├── drawing-complex-graphics.md ├── drawing-simple-graphics.md ├── drawing.md ├── image-edit.md ├── image-file-operation.md ├── rendering.md └── xiang-su-cao-zuo.md ├── images ├── font_icons.png ├── getting_started_step_1.png ├── getting_started_step_2.png ├── getting_started_step_3.png ├── getting_started_step_4.gif ├── getting_started_step_5.gif ├── gui_events_touch.gif ├── test_char_render.png ├── test_string_render.png └── test_widget_render.png ├── ime.md ├── kuai-su-shang-shou ├── an-zhuang.md ├── css-yu-fa.md ├── jie-shao.md ├── ming-ming-yue-ding.md └── xml-yu-fa.md ├── langs.md ├── layout.md ├── layout ├── README.md ├── box-model.md ├── flexbox.md ├── normal-flow-layout.md └── position.md ├── qu-dong ├── README.md ├── chu-kong.md ├── jian-pan.md ├── shi-jian-xun-huan.md ├── shi-pin.md └── shu-biao.md ├── rendering.md ├── shi-li ├── ding-shi-qi.md ├── gong-zuo-xian-cheng.md └── zhu-xun-huan.md ├── tu-xiang-chu-li ├── README.md ├── tu-xiang-bian-ji.md ├── tu-xiang-wen-jian-cao-zuo.md └── tu-xiang-xuan-ran.md ├── widget ├── README.md ├── attributes.md ├── events.md ├── lifecycle.md ├── painting.md ├── prototype.md ├── rendering.md └── style.md ├── zh-cn ├── README.md ├── book.json ├── css.md ├── css │ └── README.md ├── font_and_text │ ├── README.md │ ├── font_manage.md │ ├── render_char.md │ ├── render_icon_font.md │ └── render_string.md ├── getting_started │ ├── README.md │ ├── step1.md │ ├── step2.md │ ├── step3.md │ └── step4.md ├── gui_events │ ├── README.md │ └── touch.md ├── gui_widgets │ ├── README.md │ ├── data.md │ ├── inherit.md │ ├── prototype.md │ └── render.md ├── install │ ├── README.md │ ├── linux.md │ └── windows.md ├── styles │ ├── syntax.css │ └── website.css ├── summary.md └── ui_thread.md ├── zi-ti-1 ├── README.md ├── zi-ti-shu-ju-ku.md └── zi-ti-yin-qing.md └── zu-jian ├── README.md ├── gu-you-shu-xing.md ├── ji-chu-shu-xing.md ├── kuo-zhan-shu-xing.md ├── sheng-ming-zhou-qi.md ├── shi-jian.md ├── xuan-ran-liu-cheng.md ├── yang-shi.md └── yuan-xing.md /.gitbook/assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/architecture.png -------------------------------------------------------------------------------- /.gitbook/assets/box-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/box-model.png -------------------------------------------------------------------------------- /.gitbook/assets/canvas_default_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/canvas_default_grid.png -------------------------------------------------------------------------------- /.gitbook/assets/css-library-struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/css-library-struct.png -------------------------------------------------------------------------------- /.gitbook/assets/dirty-rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/dirty-rectangle.png -------------------------------------------------------------------------------- /.gitbook/assets/dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/dog.jpg -------------------------------------------------------------------------------- /.gitbook/assets/font_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/font_icons.png -------------------------------------------------------------------------------- /.gitbook/assets/getting_started_step_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/getting_started_step_1.png -------------------------------------------------------------------------------- /.gitbook/assets/getting_started_step_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/getting_started_step_2.png -------------------------------------------------------------------------------- /.gitbook/assets/getting_started_step_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/getting_started_step_3.png -------------------------------------------------------------------------------- /.gitbook/assets/getting_started_step_4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/getting_started_step_4.gif -------------------------------------------------------------------------------- /.gitbook/assets/getting_started_step_5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/getting_started_step_5.gif -------------------------------------------------------------------------------- /.gitbook/assets/gui_events_touch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/gui_events_touch.gif -------------------------------------------------------------------------------- /.gitbook/assets/pariallel-rendering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/pariallel-rendering.png -------------------------------------------------------------------------------- /.gitbook/assets/pixel-pipe-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/pixel-pipe-1.png -------------------------------------------------------------------------------- /.gitbook/assets/pixel-pipe-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/pixel-pipe-2.png -------------------------------------------------------------------------------- /.gitbook/assets/pixel-pipe-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/pixel-pipe-3.png -------------------------------------------------------------------------------- /.gitbook/assets/test_char_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_char_render.png -------------------------------------------------------------------------------- /.gitbook/assets/test_fill_rect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_fill_rect.png -------------------------------------------------------------------------------- /.gitbook/assets/test_fill_rect_with_rgba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_fill_rect_with_rgba.png -------------------------------------------------------------------------------- /.gitbook/assets/test_mix_rect_with_opacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_mix_rect_with_opacity.png -------------------------------------------------------------------------------- /.gitbook/assets/test_paint_background_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_paint_background_color.png -------------------------------------------------------------------------------- /.gitbook/assets/test_paint_background_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_paint_background_image.png -------------------------------------------------------------------------------- /.gitbook/assets/test_paint_background_image_with_position.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_paint_background_image_with_position.png -------------------------------------------------------------------------------- /.gitbook/assets/test_paint_background_image_with_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_paint_background_image_with_size.png -------------------------------------------------------------------------------- /.gitbook/assets/test_paint_border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_paint_border.png -------------------------------------------------------------------------------- /.gitbook/assets/test_paint_boxshadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_paint_boxshadow.png -------------------------------------------------------------------------------- /.gitbook/assets/test_pixel_manipulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_pixel_manipulation.png -------------------------------------------------------------------------------- /.gitbook/assets/test_string_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_string_render.png -------------------------------------------------------------------------------- /.gitbook/assets/test_widget_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/test_widget_render.png -------------------------------------------------------------------------------- /.gitbook/assets/widget-lifecircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/widget-lifecircle.png -------------------------------------------------------------------------------- /.gitbook/assets/widget-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/.gitbook/assets/widget-update.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf -------------------------------------------------------------------------------- /LANGS.md: -------------------------------------------------------------------------------- 1 | * [简体中文](zh-cn) 2 | * [English](en-us) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 这是一个 LCUI 文档及相关资源的概览页面。 3 | --- 4 | 5 | # 开始 6 | 7 | **LCUI** 是一个能让你使用 C、XML 和 CSS 来为桌面应用程序构建图形用户界面的 C 函数库。 8 | 9 | ### 基本假设 10 | 11 | 本文档假设你已经熟悉了 C 语言的中级知识,具备解决开发环境配置和常见编译问题的能力,且了解过包括 [HTML](https://developer.mozilla.org/docs/Learn/HTML/Introduction_to_HTML),[CSS](https://developer.mozilla.org/docs/Learn/CSS/First_steps) 和 [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript) 在内的 Web 开发技术。如果你刚开始学习 C 开发,将函数库作为你的第一步可能不是最好的主意——掌握好基础知识再来吧!之前有其它库的使用经验会有帮助,但这不是必需的。 12 | 13 | ### 术语和约定 14 | 15 | 本文档中使用下列术语和约定: 16 | 17 | * 为方便书写和阅读,本文采用 User Interface 的缩写 "UI" 代替 ”界面“、”用户界面“和"图形用户界面"。 18 | 19 | ### 参考 20 | 21 | 此文档的目录和内容组织方式参考了这些文档: 22 | 23 | * [Vue.js 文档](https://cn.vuejs.org/v2/guide/index.html) 24 | * [React 文档](https://react.docschina.org/docs/getting-started.html) 25 | * [Angular 文档](https://angular.cn/docs) 26 | * [Ant Design 文档](https://ant.design/docs/react/introduce-cn) 27 | * [MDN Web Docs](https://developer.mozilla.org/zh-CN/docs/learn) 28 | * [Electron 文档](http://www.electronjs.org/docs/tutorial/quick-start) 29 | 30 | ### 反馈 31 | 32 | #### 你也可以和我们一起做贡献! 33 | 34 | 我们希望听到你的声音。 35 | 36 | 请到 GitHub 上的仓库中创建 [Pull Request](https://github.com/lc-ui/lcui-guide/pulls) 来为 LCUI 文档做出贡献。 [贡献者指南](https://github.com/lc-soft/LCUI/blob/master/CONTRIBUTING.md)将会帮助你更好的为社区做贡献。 37 | 38 | 希望帮忙改进 LCUI?此文档中的部分章节会列出一些与之相关的待办事项,其中有的还会简单介绍大致的解决思路,如果你有发现自己感兴趣的待办事项,欢迎在 [LCUI 的 GitHub 主页](https://github.com/lc-soft/LCUI)上向我们提交 Pull Request。不过在提交 Pull Request 前,我们建议你先提交一个 Issue,告知我们你准备接手哪一个待办事项,然后简单说明它的解决方案。 39 | 40 | 我们的社区提倡相互尊重、相互支持。 参阅[社区行为规范](https://github.com/lc-soft/LCUI/blob/master/CODE_OF_CONDUCT.md)。 41 | 42 | {% hint style="info" %} 43 | 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。 44 | {% endhint %} 45 | 46 | ### 社区互助 47 | 48 | 如果您在使用的过程中碰到问题,可以通过下面几个途径寻求帮助,同时我们也鼓励资深用户通过下面的途径给新人提供帮助。 49 | 50 | * [GitHub Discussions](https://github.com/lc-soft/LCUI/discussions):提问时,建议使用 `Q&A` 标签。 51 | * [Segment Fault 思否](https://segmentfault.com/):提问时,建议在标题开头处加上 `[LCUI]` 标记。 52 | * [开源问答](https://www.oschina.net/question/ask):提问时,在“软件”输入框中填入 LCUI。 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [开始](README.md) 4 | * [基础](base/README.md) 5 | * [安装](base/installation.md) 6 | * [介绍](base/introduction.md) 7 | * [约定](base/convention.md) 8 | * [XML](base/xml-syntax.md) 9 | * [CSS](base/css-syntax.md) 10 | * [架构](base/architecture.md) 11 | * [实例](app/README.md) 12 | * [主循环](app/mainloop.md) 13 | * [工作线程](app/worker.md) 14 | * [定时器](app/timer.md) 15 | * [事件](app/events.md) 16 | * [组件](widget/README.md) 17 | * [属性](widget/attributes.md) 18 | * [样式](widget/style.md) 19 | * [原型](widget/prototype.md) 20 | * [事件](widget/events.md) 21 | * [生命周期](widget/lifecycle.md) 22 | * [绘制流程](widget/painting.md) 23 | * [性能](rendering.md) 24 | * [CSS](css/README.md) 25 | * [CSS 数据库](css/library.md) 26 | * [CSS 解析器](css/parser.md) 27 | * [布局](layout/README.md) 28 | * [正常流布局](layout/normal-flow-layout.md) 29 | * [弹性盒子布局](layout/flexbox.md) 30 | * [图形](graphics/README.md) 31 | * [绘制简单的图形](graphics/drawing-simple-graphics.md) 32 | * [绘制复杂的图形](graphics/drawing-complex-graphics.md) 33 | * [像素操作](graphics/xiang-su-cao-zuo.md) 34 | * [图像文件操作](graphics/image-file-operation.md) 35 | * [字体](font/README.md) 36 | * [字体数据库](font/library.md) 37 | * [字体渲染引擎](font/engine.md) 38 | * [文本排版与渲染](font/textlayer.md) 39 | * [驱动](driver/README.md) 40 | * [事件循环](driver/events.md) 41 | * [视频](driver/video.md) 42 | * [鼠标](driver/mouse.md) 43 | * [键盘](driver/keyboard.md) 44 | * [触控](driver/touchscreen.md) 45 | * [输入法](ime.md) 46 | 47 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # 实例 2 | 3 | 实例在面向对象程序设计中是指类经过实例化后的对象。本章节中所讲述的实例指的是 LCUI 应用实例,你可以将其理解为用 C++ 代码定义的 `LCUIApplication` 类型的对象,不过由于 LCUI 是 C 语言写的,应用实例的相关方法只不过是一些名字以 `LCUI_` 开头的函数,没法像 C++ 那样用 class 关键字显式的将相关数据和方法抽象为一个类,因此实例的存在感比较弱。 4 | 5 | LCUI 应用实例主要由主循环、工作线程、定时器和事件这几个模块组成,它们是让 LCUI 应用能够正常运作的基础,在接下来的章节中我们将深入了解这些模块的工作原理,结合一些示例代码和应用场景让你了解相关函数的用法。 6 | 7 | -------------------------------------------------------------------------------- /app/events.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 事件的概念和相关函数的介绍。 3 | --- 4 | 5 | # 事件 6 | 7 | 事件是系统内发生的动作或者发生的事情,系统响应事件后,如果需要,你可以某种方式对事件做出回应。例如:如果用户在界面上单击一个按钮,你可能想通过显示一个信息框来响应这个动作。在这一章节中,我们将讨论一些关于事件的重要概念,并且观察它们在 LCUI 上如何运行。本文不会面面俱到,仅聚焦于你现阶段需要掌握的知识。 8 | 9 | 在 LCUI 中,事件在应用程序窗口中被触发并且通常被绑定到窗口内部的特定部分 —— 可能是一个组件、一系列组件、应用程序内的代码或者是整个应用程序窗口。举几个可能发生的不同事件: 10 | 11 | * 用户在某个组件上点击鼠标或悬停光标。 12 | * 用户在键盘中按下某个按键。 13 | * 用户调整应用程序窗口的大小或者关闭应用程序窗口。 14 | * 全局设置被改变。 15 | 16 | 如果你想看看更多其他的事件 ,请查阅 [include/LCUI/main.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/main.h#L38-L58) 中定义的事件。 17 | 18 | 每个可用的事件都会有一个**事件处理器**,也就是事件触发时会运行的代码块。当我们定义了一个用来回应事件被激发的代码块的时候,我们说我们**注册了一个事件处理器**。注意事件处理器有时候被叫做**事件监听器**——从我们的用意来看这两个名字是相同的,尽管严格地来说这块代码既监听也处理事件。监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。 19 | 20 | ### 简单的例子 21 | 22 | ```c 23 | #include 24 | 25 | void OnKeyDown(LCUI_SysEvent e, void *data) 26 | { 27 | printf("key code: %d\n", e->key.code); 28 | LCUI_Quit(); 29 | } 30 | 31 | int main(void) 32 | { 33 | LCUI_Init(); 34 | LCUI_BindEvent(LCUI_KEYDOWN, OnKeyDown, NULL, NULL); 35 | return LCUI_Main(); 36 | } 37 | ``` 38 | 39 | 这个例子实现了按任意键退出的功能,我们定义了一个 `OnKeyDown()` 函数作为按键按下事件的处理器,然后使用 `LCUI_BindEvent()` 函数将它与事件的标识 `LCUI_KEYDOWN` 绑定,当按下任意键时就会调用我们的 `OnKeyDown()` 函数将事件对象中记录的按键码打印出来,然后退出应用程序。 40 | 41 | ### 事件对象 42 | 43 | 有时候在事件处理函数内部,你可能会看到一个固定指定名称的参数,例如`event`,`evt`或简单的`e`。 这被称为**事件对象**,它被自动传递给事件处理函数,以提供额外的信息。 44 | 45 | 你可以在 [include/LCUI/main.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/main.h#L109-L121) 中找到事件对象的定义: 46 | 47 | ```c 48 | typedef struct LCUI_SysEventRec_ { 49 | uint32_t type; 50 | void *data; 51 | union { 52 | LCUI_MouseMotionEvent motion; 53 | LCUI_MouseButtonEvent button; 54 | LCUI_MouseWheelEvent wheel; 55 | LCUI_TextInputEvent text; 56 | LCUI_KeyboardEvent key; 57 | LCUI_TouchEvent touch; 58 | LCUI_PaintEvent paint; 59 | }; 60 | } LCUI_SysEventRec, *LCUI_SysEvent; 61 | ``` 62 | 63 | 从这个定义我们可以看出,所有类型的事件对象都共用同一个数据结构,并以 `type` 成员变量来标识该作为哪种事件对象来使用。在上个例子中,由于我们给 `OnKeyDown()` 函数绑定的是 `LCUI_KYEDOWN` 事件,那么传入该函数的事件对象必定是键盘事件对象,因此我们就能直接从 `key.code` 中获取按键码。 64 | 65 | {% hint style="info" %} 66 | 你可以使用任何你喜欢的名称作为事件对象 - 你只需要选择一个名称,然后可以在事件处理函数中引用它。 开发人员最常使用 e / evt / event,因为它们很简单易记。 坚持标准总是很好。 67 | {% endhint %} 68 | 69 | ### 核心事件 70 | 71 | 核心事件是指 LCUI 内部触发的事件,这些事件的标识号被定义为命名以 `LCUI_` 开头的枚举值,详见 [include/LCUI/main.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/main.h#L109-L121) 中的 `LCUI_SysEventType` 定义。 72 | 73 | ### 系统事件 74 | 75 | 系统事件是指操作系统中的 GUI 系统所提供的事件,它在 Windows 系统中是消息机制中的窗口消息,而在带有 X11 系统的 Linux 系统中则是事件。它是核心事件的来源,LCUI 的驱动模块主要工作流程就是响应系统事件,从中提取必要的数据,然后转换成核心事件。 76 | 77 | 系统事件的相关函数如下: 78 | 79 | ```c 80 | LCUI_API int LCUI_BindSysEvent(int event_id, LCUI_EventFunc func, void *data, 81 | void (*destroy_data)(void *)); 82 | 83 | LCUI_API int LCUI_UnbindSysEvent(int event_id, LCUI_EventFunc func); 84 | ``` 85 | 86 | Windows 系统的事件绑定例子: 87 | 88 | ```c 89 | LCUI_BindSysEvent(WM_KEYDOWN, OnKeyDown, NULL, NULL); 90 | LCUI_UnbindSysEvent(WM_KEYDOWN, OnKeyDown); 91 | ``` 92 | 93 | Linux 系统的事件绑定例子: 94 | 95 | ```c 96 | LCUI_BindSysEvent(KeyPress, OnKeyDown, NULL, NULL); 97 | LCUI_UnbindSysEvent(KeyPress, OnKeyDown); 98 | ``` 99 | 100 | 如需了解更多,可查看 [src/platform](https://github.com/lc-soft/LCUI/tree/master/src/platform) 目录中的驱动源码。 101 | 102 | ### 自定义事件 103 | 104 | 核心事件的标识号是有序递增的,它的取值范围是 `LCUI_NONE` 到 `LCUI_USER`,大于或等于 `LCUI_USER` 的都属于自定义事件。 105 | 106 | 创建自定义事件很简单,你只需要让事件标识号在 `LCUI_USER` 的基础上递增即可: 107 | 108 | ```c 109 | enum MyCustomEvent { 110 | MY_CUSTOM_EVENT_A = LCUI_USER, 111 | MY_CUSTOM_EVENT_B, 112 | MY_CUSTOM_EVENT_C, 113 | MY_CUSTOM_EVENT_D 114 | }; 115 | ``` 116 | -------------------------------------------------------------------------------- /app/mainloop.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 主循环的概念和相关功能介绍。 3 | --- 4 | 5 | # 主循环 6 | 7 | 应用程序在运行的时候,为了能够不断的对用户的操作进行响应和反馈,通常的做法是将事件处理、状态更新和界面重绘等任务往复执行,而这一循环执行的过程即为主循环。 8 | 9 | LCUI 的主循环所执行的任务包括处理定时器、处理事件队列、更新组件、渲染组件等,这些任务的调度代码都在 [src/main.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/main.c#L214-L224) 文件中的 `LCUI_RunFrame()` 函数中: 10 | 11 | ```c 12 | void LCUI_RunFrame(void) 13 | { 14 | LCUI_ProcessTimers(); 15 | LCUI_ProcessEvents(); 16 | LCUICursor_Update(); 17 | LCUIWidget_Update(); 18 | LCUIDisplay_Update(); 19 | LCUIDisplay_Render(); 20 | LCUIDisplay_Present(); 21 | } 22 | ``` 23 | 24 | ### 帧率控制 25 | 26 | 主循环的每次循环即为一帧,为了避免不必要的 CPU 资源占用,主循环的执行频率会受到帧率控制,预设的帧率限制是 120 帧每秒,也就是主循环每秒最多执行 120 遍,每帧至少占用约 8.33 毫秒的时间,如果一帧的耗时低于 8.33 毫秒则会利用剩下的时间进入休眠状态。 27 | 28 | 如果你想要自定义帧率限制,可以调用 `LCUI_ApplySettings()` 修改全局设置中的 `frame_rate_cap` 设置项: 29 | 30 | ```c 31 | #include 32 | #include 33 | 34 | int main(void) 35 | { 36 | LCUI_SettingsRec settings; 37 | 38 | Settings_Init(&settings); 39 | settings.frame_rate_cap = 60; 40 | LCUI_ApplySettings(&settings); 41 | } 42 | ``` 43 | 44 | ### 多个主循环 45 | 46 | 试着考虑这种场景:在用户点击按钮后弹出一个确认框,等待用户点击确认后再执行操作。这种场景比较常见,我们会希望有个 `ShowConfirmDialog()` 函数能够完成这件事情: 47 | 48 | ```c 49 | LCUI_BOOL ShowConfirmDialog(const char *title, const char *content) 50 | { 51 | ... 52 | if (isOkButtonClicked) { 53 | return TRUE; 54 | } 55 | return FALSE; 56 | } 57 | 58 | void OnButtonClick() 59 | { 60 | if (ShowConfirmDialog("Confirm", "Are you sure you want to do it?")) { 61 | DoSomeThing(); 62 | } 63 | } 64 | ``` 65 | 66 | 按钮的点击事件处理器都是在主循环中执行的,如果 `ShowConfirmDialog()` 函数要等到用户点击弹框里的按钮后才退出的话,它在这段等待时间内会一直阻塞主循环的执行,导致整个界面无法响应用户操作,由于界面无法响应操作, `ShowConfirmDialog()` 函数也无法得知用户是否点击了确认按钮或取消按钮,这就成了一个死循环,那么如何解决此问题?有一种方法是在 `ShowConfirmDialog()` 函数中再创建一个主循环以响应后续的用户操作和界面更新,示例如下: 67 | 68 | ```c 69 | typedef struct DialogContextRec_ { 70 | LCUI_BOOL result; 71 | LCUI_MainLoop loop; 72 | } DialogContextRec, *DialogContext; 73 | 74 | static void OnBtnOkClick(LCUI_Widget w, LCUI_WidgetEvent e, void *arg) 75 | { 76 | DialogContext ctx = e->data; 77 | ctx->result = TRUE; 78 | LCUIMainLoop_Quit(ctx->loop); 79 | } 80 | 81 | static void OnBtnCancelClick(LCUI_Widget w, LCUI_WidgetEvent e, void *arg) 82 | { 83 | DialogContext ctx = e->data; 84 | ctx->result = FALSE; 85 | LCUIMainLoop_Quit(ctx->loop); 86 | } 87 | 88 | LCUI_BOOL ShowConfirmDialog(const wchar_t* title, const wchar_t *content) 89 | { 90 | DialogContextRec ctx = { 0 }; 91 | LCUI_Widget btn_cancel = LCUIWidget_New("button"); 92 | LCUI_Widget btn_ok = LCUIWidget_New("button"); 93 | 94 | ... 95 | 96 | Widget_BindEvent(btn_ok, "click", OnBtnOkClick, &ctx, NULL); 97 | Widget_BindEvent(btn_cancel, "click", OnBtnCancelClick, &ctx, NULL); 98 | ctx.loop = LCUIMainLoop_New(); 99 | LCUIMainLoop_Run(ctx.loop); 100 | Widget_Destroy(dialog); 101 | return ctx.result; 102 | } 103 | ``` 104 | 105 | {% hint style="info" %} 106 | 这段代码省略了弹框组件的构造代码,如需了解完整的实现代码可以查看 LC Finder 项目中的 [src/ui/components/dialog\_confirm.c](https://github.com/lc-soft/LC-Finder/blob/573f200698e2604450665716ebc6608837b4b73a/src/ui/components/dialog\_confirm.c) 文件。 107 | {% endhint %} 108 | 109 | 在这段代码中,先定义了`DialogContextRec` 类型的 ctx 变量用于记录按钮点击状态和主循环的指针,然后为确认按和取消按钮绑定点击事件处理器,之后调用 `LCUIMainLop_New()` 新建了一个主循环,再调用 `LCUIMainLoop_Run()` 执行这个新的主循环。在按钮被点击后,事件处理器会修改 ctx 中的按钮点击状态,然后调用 `LCUIMainLoop_Quit()` 退出指定的主循环。在`LCUIMainLoop_Run()` 函数退出后,销毁弹框并将用户的操作结果返回。 110 | 111 | 另一种方法是改用回调函数的响应操作结果: 112 | 113 | ```c 114 | LCUI_BOOL ShowConfirmDialog( 115 | const char *title, 116 | const char *content, 117 | void (*onResult)(LCUI_BOOL, void*) 118 | ); 119 | 120 | void OnConfirm(LCUI_BOOL isConfirmed) 121 | { 122 | if (isConfirmed) { 123 | DoSomeThing(); 124 | } 125 | } 126 | 127 | void OnButtonClick() 128 | { 129 | ShowConfirmDialog( 130 | "Confirm", 131 | "Are you sure you want to do it?", 132 | OnConfirm 133 | ); 134 | } 135 | ``` 136 | 137 | 我们不建议采用这种方法,因为它存在以下几个问题: 138 | 139 | * 需要再定义一个函数接收操作结果,使得操作逻辑被分散。 140 | * 如果这个函数需要额外的参数话,还要给 `ShowConfirmDialog()` 再增加一个参数,增加了函数复杂度和代码量。 141 | * 由于 C 语言没有像 JavaScript 那样的闭包特性和对异步编程的 async/await 关键字支持,使得这种方法的实现代码和调用代码并不优雅。 142 | 143 | ### 不使用主循环 144 | 145 | 如果你的应用程序有自己的主循环,不希望为适应 LCUI 的主循环而做改动,那么可以在主循环中调用 `LCUI_RunFrame()` 函数: 146 | 147 | ```c 148 | while (your_app.active) { 149 | your_app_main_loop_task1(); 150 | your_app_main_loop_task2(); 151 | your_app_main_loop_task3(); 152 | ... 153 | LCUI_RunFrame(); 154 | } 155 | ``` 156 | 157 | -------------------------------------------------------------------------------- /app/timer.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 定时器的概念及用法介绍。 3 | --- 4 | 5 | # 定时器 6 | 7 | 定时器用于将一些操作推迟到指定时间之后执行。LCUI 的定时器都是在主线程中处理的,这意味着定时器的时间粒度受到帧率的限制,不能小于每帧的停留时间。举个例子:当前帧率为 120 帧每秒,那么时间粒度就是 8.33 毫秒,如果你设置定时器的等待时间是 20 毫秒,那么实际的等待时间就会大于等于 25 毫秒,也就是在设置定时器后的第三帧时处理这个定时器。这种精确度的定时器能够应付大多数场景,如果你需要更加精确的定时器,我们建议你选择其它定时器库,或自行编码实现。 8 | 9 | ### 简单的例子 10 | 11 | 这个例子展示了如何设置和释放定时器: 12 | 13 | ```c 14 | #include 15 | #include 16 | 17 | void OnTimeout(void *arg) 18 | { 19 | int *timer_id = arg; 20 | 21 | LCUITimer_Free(*timer_id); 22 | LCUI_Quit(); 23 | printf("timeout\n"); 24 | } 25 | 26 | void OnInterval(void *arg) 27 | { 28 | printf("interval\n"); 29 | } 30 | 31 | int main(void) 32 | { 33 | int timer_id; 34 | 35 | LCUI_Init(); 36 | timer_id = LCUI_SetInterval(1000, OnInterval, 0); 37 | LCUI_SetTimeout(5000, OnTimeout, &timer_id); 38 | return LCUI_Main(); 39 | } 40 | ``` 41 | 42 | 首先调用 `LCUI_SetInterval()` 设置定时器每隔 1 秒调用一次 `OnInterval()` 函数,并将它返回的定时器标识号赋值给 `timer_id` ,然后调用 `LCUI_SetTimeout()` 设置一个定时器在 5 秒后调用 `OnTimeout()` 函数,并将 `timer_id` 的引用作为参数传给 `OnTimeout()` 函数。 43 | -------------------------------------------------------------------------------- /app/worker.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 工作线程的概念、用途以及相关函数的介绍。 3 | --- 4 | 5 | # 工作线程 6 | 7 | 主线程通常被用于运行主循环,而主循环负责的都是 UI 相关的工作,所以也可以说主线程是 UI 线程。为了不影响 UI 线程的工作效率,我们会需要创建额外的线程来负责各种各样的工作,而这些线程就是工作线程。 8 | 9 | 在主循环的章节中,我们已经了解到主循环执行频率影响界面的流畅度,它的每一次循环都会按顺序执行处理定时器、处理事件队列、更新组件、渲染组件等任务,其中最容易影响到主循环的执行频率的任务是处理事件队列,因为大部分的事件处理器都是应用程序主动绑定的,对于缺乏性能优化意识的新手而言,可能会在事件处理器中直接进行一些耗时较高的操作,从而导致界面在操作结束前一直处于未响应状态。 10 | 11 | 解决这一问题的常见做法是将操作移动到另一个线程上执行,我们可以用 LCUI 提供的工作线程池来实现: 12 | 13 | ```c 14 | void DoSomeThing(void *arg1, void *arg2) 15 | { 16 | printf("key: %s\n", arg1); 17 | printf("value: %s\n", arg2); 18 | } 19 | 20 | void OnButtonClick() 21 | { 22 | LCUI_TaskRec task = { 0 }; 23 | 24 | LCUI_Init(); 25 | task.arg[0] = strdup("color"); 26 | task.arg[1] = strdup("red"); 27 | task.destroy_arg[0] = free; 28 | task.destroy_arg[1] = free; 29 | task.func = DoSomeThing; 30 | 31 | LCUI_PostAsyncTask(&task); 32 | } 33 | ``` 34 | 35 | `LCUITaskRec` 类型的 task 变量描述了任务的执行函数及其参数,`arg` 成员变量记录了参数列表,`destroy_arg` 则是这些参数的销毁函数,这里我们用了 `strdup()` 分配了新的内存存储字符串,并指定 `free()` 作为参数的销毁函数。准备完任务后,调用 `LCUI_PostAsyncTask()` 函数将任务添加到工作线程的任务队列中等待执行。 36 | 37 | ### 线程安全问题 38 | 39 | UI 资源是全局共享的,任意线程都能访问和修改它,当有多个线程在操作 UI 资源的时候,任意一个线程对 UI 的改动都有可能影响其它线程操作结果,轻则界面内容异常,重则导致应用程序崩溃,因此,UI 资源不是线程安全的。 40 | 41 | 鉴于多线程操作 UI 的需求量和性能上的考虑,LCUI 未采用互斥锁之类的机制来解决线程安全问题,我们在开发的时候应尽量在 UI 线程上集中进行 UI 操作,以此避免线程安全问题。 42 | 43 | ### **与 UI 线程通信** 44 | 45 | 当我们需要将工作线程的处理结果更新到 UI 上的时候,可以用 `LCUI_PostTask()` 函数将 UI 相关操作移动到 UI 线程上执行,它的用法与 `LCUI_PostAsyncTask()` 相同,示例代码如下: 46 | 47 | ```c 48 | void UpdateText(void *arg1, void *arg2) 49 | { 50 | char *text = arg2; 51 | LCUI_Widget textview = arg1; 52 | TextView_SetText(textview, text); 53 | } 54 | 55 | ... 56 | 57 | LCUI_Widget textview; 58 | char *text = strdup("Task has been completed!"); 59 | 60 | ... 61 | 62 | LCUI_AppTaskRec task = { 0 }; 63 | 64 | task.func = UpdateText; 65 | task.arg[0] = textview; 66 | task.arg[1] = text; 67 | task.destroy_arg[0] = NULL; 68 | task.destroy_arg[1] = free; 69 | 70 | LCUI_PostTask(&task); 71 | // ... 72 | ``` 73 | 74 | 如果任务的参数不需要销毁,则可以用 `LCUI_PostSimpleTask()` 函数式宏代替 `LCUI_PostTask()` ,以节省 `LCUI_AppTask` 对象的构造代码。 75 | 76 | ### 自定义工作线程池 77 | 78 | LCUI 的工作线程池中默认创建了 4 个工作线程,为了让这些工作线程都有任务执行,`LCUI_PostAsyncTask()` 函数会在每次投递完任务后将目标切换到下一个工作线程,如果这种简单的任务分配策略不符合你的需求,你也可以基于 [src/worker.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/worker.c) 提供的函数创建自己的工作线程池: 79 | 80 | ```c 81 | // 创建一个带有任务队列的工作线程,然后运行它 82 | LCUI_Worker worker = LCUIWorker_New(); 83 | LCUIWorker_RunAsync(worker); 84 | 85 | ... 86 | 87 | // 给工作线程投递任务 88 | LCUI_TaskRec task = { 0 }; 89 | 90 | task.arg[0] = strdup("color"); 91 | task.arg[1] = strdup("red"); 92 | task.destroy_arg[0] = free; 93 | task.destroy_arg[1] = free; 94 | task.func = DoSomeThing; 95 | LCUIWorker_PostTask(worker, task); 96 | 97 | ... 98 | 99 | // 销毁工作线程 100 | LCUIWorker_Destroy(worker); 101 | ``` 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /base/README.md: -------------------------------------------------------------------------------- 1 | # 基础 2 | 3 | -------------------------------------------------------------------------------- /base/architecture.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: LCUI 的架构介绍。 3 | --- 4 | 5 | # 架构 6 | 7 | [软件架构](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84)是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计,它包括了软件组件、组件之间的关系,组件特性以及组件间关系的特性。 8 | 9 |  10 | 11 | 如上图所示,从底至上,LCUI 由如下几大模块组成: 12 | 13 | * **平台支持(Platform Support):**将来自不同操作系统的接口进行抽象,为 LCUI 上层提供统一的驱动接口。 14 | * **图形抽象层( Graphics Abstraction Layer):**基于平台支持模块提供的图形驱动,将各个操作系统中的窗口操作接口抽象成表面(Surface)和显示(Display)两类接口,表面模块负责将组件映射到与之绑定的窗口,而显示模块则用于向上层应用程序提供屏幕相关信息和表面渲染模式切换功能。 15 | * **输入抽象层( Input Abstraction Layer):**和图形抽象层类似,输入抽象层将 LCUI 涉及的所有输入设备,如键盘(keyboard)、鼠标(mouse)、触摸屏(touch screen)等抽象了出来,为上层提供一致的接口。 16 | * **图形接口(Graphics Interfaces):**该模块基于图形抽象层为上层应用程序提供图形相关的接口,如绘制边框、阴影、输出文本、填充矩形等等。 17 | * **CSS 接口(CSS Interfaces):**该模块包含 CSS 解析器和 CSS 数据库,用于控制组件的样式。 18 | * **图形用户界面(GUI):**该模块为上层应用程序提供了组件的操作接口和一些基础组件,并负责维护组件树、事件处理、样式计算和渲染。 19 | 20 | -------------------------------------------------------------------------------- /base/convention.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍 LCUI 的命名风格和推荐的 LCUI 应用项目的代码组织方式。 3 | --- 4 | 5 | # 约定 6 | 7 | ### 命名约定 8 | 9 | 在正式开始使用 LCUI 前,我们先了解一下 LCUI 的命名约定,这将有助于记忆和查找你需要的 API。 10 | 11 | #### 数据类型 12 | 13 | 大部分公共的数据类型都采用驼峰式命名法(Camel-Case),并带有 `LCUI_` 前缀,像链表(LinkedList)、红黑树(RBTree) 和字典(Dict)这类基础数据类型,由于名称长度和可替代性,未加上 `LCUI_` 前缀。 14 | 15 | 对于常以指针形态引用的数据类型,它的定义会是这样: 16 | 17 | ```c 18 | typedef struct LCUI_FooRec_* LCUI_Foo; 19 | 20 | typedef struct LCUI_FooRec_ 21 | { 22 | /* fields for the 'foo' class */ 23 | ... 24 | 25 | } LCUI_FooRec; 26 | ``` 27 | 28 | 这种写法参考自 FreeType,如需了解更多,可参考它的设计文档:《[FreeType Design](https://www.freetype.org/freetype2/docs/design/design-3.html#section-1)》 29 | 30 | #### 函数 31 | 32 | 由于 LCUI 在开发初期并未确定最佳的命名风格,受到维护人员的不稳定的编程习惯以及其它开源项目的影响,每当引入新功能的代码时候都有可能采用其它命名风格,因此,你会发现 LCUI 的源码中存在多种命名风格: 33 | 34 | * **面向对象 + 驼峰风格:** 使用与类型名同名的函数作为构造函数,该对象的所有操作函数都以类型名开头,并以下划线分隔,对象的析构函数名称都是 Delete。 35 | 36 | ```c 37 | LCUI_StyleSheet sheet1 = StyleSheet(); 38 | LCUI_StyleSheet sheet2 = StyleSheet(); 39 | StyleSheet_Merge(sheet1, sheet2); 40 | StyleSheet_Delete(sheet2); 41 | ``` 42 | 43 | * `LCUI_` **前缀 + 驼峰风格:** 依赖 LCUI 核心功能的公共函数大都采用此风格,有的函数为避免命名冲突也会加上 `LCUI_` 前缀。 44 | 45 | ```c 46 | LCUI_Init(): 47 | LCUI_SetTimeout(); 48 | LCUI_Quit(); 49 | LCUI_Main(); 50 | LCUI_EncodeString(); 51 | ``` 52 | 53 | * **驼峰风格:** 函数以操作对象的类型名开头,并以下划线分隔对象类型名与方法名。如果函数操作的对象是采用单例模式的全局共享对象,通常还会加上 LCUI 前缀。 54 | 55 | ```c 56 | Widget_Append(); 57 | Dict_FetchValue(); 58 | LinkedList_Append(); 59 | LCUIFont_RenderBitmap(); 60 | LCUIFont_LoadFile(); 61 | ``` 62 | 63 | * **全小写风格:**与标准库函数的命名风格类似。 64 | 65 | ```c 66 | strhash(); 67 | strsplit(); 68 | ``` 69 | 70 | ### 推荐的目录结构 71 | 72 | * **app/:** 应用的工作目录,用于存放运行时所需的资源文件。 73 | * **build/:** 构建工具的工作目录,包含一些配置文件和构建产物。 74 | * **config/:** 配置文件目录。 75 | * **include/:** 头文件目录。 76 | * **src/:** 源码目录。 77 | * **lib/:** 基础库目录,包含应用的核心功能源码。 78 | * **ui/:** 用户界面目录。 79 | * **components/:** 组件目录,包含能够在用户界面中复用的基础组件。 80 | * **effects/:** 效果目录,包含一些动效、手势、拖拽等界面交互效果的实现源码。 81 | * **views/:** 视图目录,包含能够完成特定功能的完整界面。 82 | * **app.c:** 应用的入口。 83 | * **ui.c:** 用户界面的入口。 84 | * **vendor/:** 第三方库目录,包含第三方库的头文件和库文件。 85 | 86 | ### 待办事项 87 | 88 | **重新设计编码规范** 89 | 90 | 参考主流的 C 开源项目的编码风格,为 LCUI 设计最佳编码规范。 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /base/css-syntax.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: CSS 的介绍以及 LCUI 对 CSS 特性的支持情况。 3 | --- 4 | 5 | # CSS 6 | 7 | **层叠样式表** \(Cascading Style Sheets,缩写为 **CSS**),是一种用来为结构化文档(如 HTML 文档或 XML 应用)添加样式(字体、间距和颜色等)的语言。CSS 使我们可以将 UI 中的显示信息隔离出来,存放在用 CSS 语言写的文件中,从而简化 UI 的代码,使其只包含结构和内容的描述代码。 8 | 9 | 出于开发成本的考虑,LCUI 内置的 CSS 解析器仅支持一些常用的 CSS 特性,你可以从以下列表来了解 CSS 特性支持情况,已勾选的是表示已支持(至少支持基本功能),未列出的属性则默认不支持。 10 | 11 | * at rules 12 | * [x] `@font-face` 13 | * [ ] `@keyframes` 14 | * [ ] `@media` 15 | * keywords 16 | * [ ] `!important` 17 | * selectors 18 | * [x] `*` 19 | * [x] `type` 20 | * [x] `#id` 21 | * [x] `.class` 22 | * [x] `:hover` 23 | * [x] `:focus` 24 | * [x] `:active` 25 | * [x] `:first-child` 26 | * [x] `:last-child` 27 | * [ ] `[attr="value"]` 28 | * [ ] `:not()` 29 | * [ ] `:nth-child()` 30 | * [ ] `parent > child` 31 | * [ ] `a ~ b` 32 | * [ ] `::after` 33 | * [ ] `::before` 34 | * [ ] ... 35 | * units 36 | * [x] px 37 | * [x] dp 38 | * [x] sp 39 | * [x] pt 40 | * [x] % 41 | * [ ] rem 42 | * [ ] vh 43 | * [ ] vw 44 | * properties 45 | * [x] top, right, bottom, left 46 | * [x] width, height 47 | * [x] visibility 48 | * [x] display 49 | * [x] none 50 | * [x] inline-block 51 | * [x] block 52 | * [x] flex 53 | * [ ] inline-flex 54 | * [ ] inline 55 | * [ ] grid 56 | * [ ] table 57 | * [ ] table-cell 58 | * [ ] table-row 59 | * [ ] table-column 60 | * [ ] ... 61 | * [x] position 62 | * [x] static 63 | * [x] relative 64 | * [x] absolute 65 | * [ ] fixed 66 | * [x] box-sizing 67 | * [x] border-box 68 | * [x] content-box 69 | * [x] border 70 | * [x] border-radius 71 | * [x] background-color 72 | * [x] background-image 73 | * [x] background-position 74 | * [x] background-cover 75 | * [ ] background 76 | * [x] pointer-events 77 | * [x] font-face 78 | * [x] font-family 79 | * [x] font-size 80 | * [x] font-style 81 | * [x] flex 82 | * [x] flex-shrink 83 | * [x] flex-grow 84 | * [x] flex-basis 85 | * [x] flex-wrap 86 | * [x] flex-direction 87 | * [x] justify-content 88 | * [x] flex-start 89 | * [x] center 90 | * [x] flex-end 91 | * [x] align-items 92 | * [x] flex-start 93 | * [x] center 94 | * [x] flex-end 95 | * [x] stretch 96 | * [ ] float 97 | * [ ] transition 98 | * [ ] transform 99 | * [ ] ... 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /base/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: LCUI 的安装方法以及版本更新相关说明。 3 | --- 4 | 5 | # 安装 6 | 7 | #### 语义化版本控制 8 | 9 | LCUI 在其所有项目中公布的功能和行为都遵循[语义化版本控制](https://semver.org/lang/zh-CN/)。对于未公布的或内部暴露的行为,其变更会描述在[发布说明](https://github.com/lc-soft/LCUI/releases)中。 10 | 11 | #### 更新日志 12 | 13 | 最新稳定版本:2.2.0 14 | 15 | 每个版本的更新日志见 [GitHub](https://github.com/lc-soft/LCUI/releases)。 16 | 17 | ### 直接用已编译好的成品 18 | 19 | 通常新版本的[发布说明](https://github.com/lc-soft/LCUI/releases)中都会附带已编译的二进制文件包,包括适用于 Ubuntu 系统的 deb 安装包和适用于 Windows 系统的 zip 包,你可以根据自己的需求下载,然后配置编译器的头文件和库文件的搜索路径以及链接器参数。 20 | 21 | ### LCPkg 包管理器 22 | 23 | LCPkg 是一个用于管理 C/C++ 项目依赖的命令行工具,目前仅适合在 Windows 系统上使用,使用它你可以很方便的下载 LCUI 的二进制文件包。 24 | 25 | ```bash 26 | # 最新稳定版 27 | lcpkg install github.com/lc-soft/LCUI 28 | ``` 29 | 30 | ### 命令行工具 \(CLI\) 31 | 32 | LCUI 提供了一个[官方的 CLI](https://github.com/lc-ui/lcui-cli),为 LCUI 应用快速搭建繁杂的脚手架。更多详情可查阅 [LCUI CLI 的文档](https://github.com/lc-ui/lcui-cli)。 33 | 34 | ```bash 35 | # 安装命令行工具 36 | npm install -g @lcui/cli 37 | 38 | # 创建项目 39 | lcui create my-lcui-app 40 | 41 | # 进入项目目录 42 | cd my-lcui-app 43 | 44 | # 准备开发环境 45 | lcui setup 46 | 47 | # 构建项目 48 | lcui build 49 | 50 | # 运行 51 | lcui run 52 | ``` 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /base/xml-syntax.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: XML 的写法和常用元素的介绍。 3 | --- 4 | 5 | # XML 6 | 7 | XML 是一种可扩展的标记语言,LCUI 之所以采用 XML 而不是 HTML,主要有以下理由: 8 | 9 | * XML 比 HTML 更简单。 10 | * LCUI 不是浏览器,不打算实现 HTML 中的所有标签的功能,因为这是既浪费时间又没有意义的事情。 11 | * 使用 HTML 会让用户以开发网页的思维方式去编写 LCUI 应用,然后误以为 ``、``、``、``、``、`` 等标签在 LCUI 中会有效果。 12 | 13 | 在开始前,我们假定你已经熟悉 HTML 或 XML 这类标记语言的语法,本文将跳过基本语法和相关术语的介绍,直接讲解 LCUI 的 XML 文档写法和常用元素的用法,如需了解更多请查阅相关文档。 14 | 15 | 在前面的章节中我们已经了解到 LCUI 的 XML 文档内容格式和预定义元素的用法: 16 | 17 | ```markup 18 | 19 | 20 | 21 | 22 | 23 | Hello, World! 24 | 25 | 26 | 27 | ``` 28 | 29 | 第一行声明文档的类型,第二行的`` 声明了它包裹的内容适用于 LCUI 应用,第三行的 `` 包裹了整个用户界面的结构及其所有组件的信息。 30 | 31 | ### 常用元素 32 | 33 | #### \ 34 | 35 | 声明资源信息,可用于加载资源文件。 36 | 37 | | 属性 | 说明 | 38 | | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 39 | | type | 资源类型,可选值有:application/font-ttf :作为 ttf 字体文件加载text/css :作为 css 文本文件加载 text/xml :作为 xml 文档加载当值为 text/xml 时,效果相当于将目标 xml 文档的内容插入到 <resource> 所在位置。 | 40 | | src | 资源文件的来源路径 | 41 | 42 | #### \ 43 | 44 | 声明组件,仅限在 `` 内使用。 45 | 46 | | 属性 | 说明 | 47 | | ----- | ----------------------- | 48 | | type | 组件的类型名称,需要是组件原型库中已注册的名称 | 49 | | id | 唯一标识符 | 50 | | class | 类名称 | 51 | 52 | 在 `` 中,如果元素的标签名不是预定义的,则会视为 `` 元素,因此,你可以使用组件类型名作为标签名,例如以下两行元素是等效的: 53 | 54 | ```markup 55 | hello 56 | hello 57 | ``` 58 | 59 | ### API 60 | 61 | LCUI 提供的 XML 文档相关的函数有两个: 62 | 63 | ```c 64 | LCUI_Widget LCUIBuilder_LoadString(const char *str, int size); 65 | 66 | LCUI_Widget LCUIBuilder_LoadFile(const char *filepath); 67 | ``` 68 | 69 | 从函数原型可以知道,这两个函数分别用于从字符串和文件中加载 XML 文档内容,它们的返回值都是一个根组件,这个根组件只是充当包含了所有组件的容器,真正有用的是它里面组件,因此我们需要使用 `Widget_Unwrap()` 函数展开该容器组件,将它里面的组件暴露到外面。 70 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator": "site", 3 | "language": "zh", 4 | "plugins": ["prism", "-highlight"] 5 | } 6 | -------------------------------------------------------------------------------- /bu-ju.md: -------------------------------------------------------------------------------- 1 | # 布局 2 | 3 | -------------------------------------------------------------------------------- /css-1/README.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | -------------------------------------------------------------------------------- /css-1/css-jie-xi-qi.md: -------------------------------------------------------------------------------- 1 | # CSS 解析器 2 | 3 | -------------------------------------------------------------------------------- /css-1/css-shu-ju-ku.md: -------------------------------------------------------------------------------- 1 | # CSS 数据库 2 | 3 | -------------------------------------------------------------------------------- /css/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: CSS 引擎的工作原理、相关概念和相关接口用法介绍。 3 | --- 4 | 5 | # CSS 6 | 7 | CSS 是浏览器中用来指定文档如何展示给用户的一门语言——如网页的样式、布局、等等,不仅限于浏览器,它也被其它的一些 UI 开发库所支持,而出于研发和学习成本上的考虑,LCUI 也选择采用 CSS 来控制界面样式。 8 | 9 | LCUI 将 CSS 引擎划分为数据库和解析器两模块,前者用于将 CSS 数据以便于操作和查询的数据结构存储在内存中,而后者则负责将包含 CSS 代码的字符串解析成适合读写的数据结构并添加到数据库中。 10 | 11 | 在接下来的章节中,我们将介绍 CSS 引擎的工作原理、数据存储、查询方式和常用接口的用法,希望你在看完后能够对它有更深入的理解,当然,如果你能够发现一些问题并向我们提供改进方案的话,那就更好了。 12 | 13 | ### 待办事项 14 | 15 | **重新设计 CSS 引擎** 16 | 17 | 新的 CSS 引擎应该解决以下问题: 18 | 19 | * 可作为一个独立的项目,能被编译为函数库供其它项目使用 20 | * 可创建多个 CSS 库实例,而不是现在这样全局共用同一个 CSS 库 21 | * 可轻松扩展新的语法解析器 22 | * 可轻松添加新的 CSS 属性解析器 23 | * 有完整的测试用例,且测试覆盖率达到 80% 以上 24 | * 参考 [CSS 的标准文档](https://www.w3.org/TR/2021/WD-css-cascade-5-20210319/),采用严谨的单词来命名标识符 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /css/parser.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: CSS 解析器的流程和解析器的添加方法介绍。 3 | --- 4 | 5 | # CSS 解析器 6 | 7 | ### 简单的例子 8 | 9 | 这个例子展示了如何从字符串中加载 CSS 样式: 10 | 11 | ```c 12 | #include 13 | #include 14 | #include 15 | 16 | int main(void) 17 | { 18 | LCUI_Selector selector = Selector(".button"); 19 | LCUI_StyleSheet stylesheet = StyleSheet(); 20 | 21 | LCUI_InitCSSLibrary(); 22 | LCUI_InitCSSParser(); 23 | LCUI_LoadCSSString(".toolbar .button { color: #000 }"); 24 | LCUI_GetStyleSheet(selector, stylesheet); 25 | LCUI_PrintStyleSheet(stylesheet); 26 | StyleSheet_Delete(stylesheet); 27 | Selector_Delete(selector); 28 | LCUI_FreeCSSParser(); 29 | LCUI_FreeCSSLibrary(); 30 | return 0; 31 | } 32 | ``` 33 | 34 | `LCUI_LoadCSSString()` 函数用于从字符串中加载 CSS 样式到数据库中,它内部会调用一系列的解析器对字符串进行解析,最终转换成便于操作的数据结构。 35 | 36 | ### 工作流程 37 | 38 | CSS 解析器在解析时会将解析目标分为:无、注释、规则名称、规则数据、选择器、CSS 属性名 和 CSS 属性值,例如以下 CSS 代码: 39 | 40 | ```css 41 | @import "common.css"; 42 | 43 | /* button style */ 44 | .btn { 45 | font-size: 16px; 46 | } 47 | ``` 48 | 49 | CSS 解析器会将它识别为以下目标进行解析: 50 | 51 | ```css 52 | @[rule-name] [rule-data]; 53 | 54 | [comment] 55 | [selector] { 56 | [css-property-name]: [css-property-value]; 57 | } 58 | ``` 59 | 60 | 每个解析目标都有对应的解析器,它们的解析行为大致是这样的: 61 | 62 | * **无:**寻找解析目标,遍历字符串,根据当前读取到的字符来决定切换到哪种解析目标,例如读取到的字符是 `@` ,则切换解析目标为规则名称。 63 | * **规则名称:**存储每个字符直到读取的字符是空白符为止,解析完后切换解析目标为规则数据。 64 | * **规则数据:**如果有与规则名称对应的规则解析器,则调用它解析,否则忽略。 65 | * **选择器:**存储每个字符直到解析到左括号 `{` 为止,转换字符串为选择器,然后切换解析目标为 CSS 属性名。 66 | * **CSS 属性名:**存储每个字符直到冒号 `:`为止,将存储的字符串作为属性名,并记录对应的 CSS 属性解析器,然后切换解析目标为 CSS 属性值。 67 | * **CSS 属性值:**存储每个字符直到右括号 `}` 或分号 `;` 为止,如果存在对应的 CSS 属性解析器,则调用它更新当前解析上下文中的样式表。 68 | * **注释:**以上解析器在解析到斜杆 `/` 时都会切换解析目标为注释,当解析完注释后再恢复解析目标为上个解析目标。 69 | 70 | ### 添加 at 规则解析器 71 | 72 | 目前预设的 at 规则有:`@font-face`、`@import` 和 `@media`,其中 `@media` 规则解析暂未实现,如果你想添加新的 at 规则解析器,则必须修改 CSS 解析器源码才能做到。 73 | 74 | 首先编辑[ include/LCUI/gui/css\_parser.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/gui/css_parser.h#L62) 头文件,在 `LCUI_CSSRule` 枚举的定义里追加你的新规则的枚举值(例如:`CSS_RULE_MY_RULE`)。 75 | 76 | 然后定义你的规则解析上下文和解析器函数,例如: 77 | 78 | ```c 79 | #define GetParserContext(CTX) (CTX)->rule.parsers[CSS_RULE_MY_RULE].data 80 | 81 | typedef struct MyRuleParserContextRec_ { 82 | // Define your data 83 | // ... 84 | } MyRuleParserContextRec, *MyRuleParserContext; 85 | 86 | static int MyRuleParser_Begin(LCUI_CSSParserContext ctx) 87 | { 88 | // Initialize your parser context 89 | // ... 90 | // ctx-> 91 | ctx->rule.state = /* your parser initial state */; 92 | return 0; 93 | } 94 | 95 | static void MyRuleParser_End(LCUI_CSSParserContext ctx) 96 | { 97 | MyRuleParserContext data = GetParserContext(ctx); 98 | 99 | // Destroy your data 100 | // ... 101 | // free(data->xxx); 102 | } 103 | 104 | int CSSParser_InitMyRuleParser(LCUI_CSSParserContext ctx) 105 | { 106 | LCUI_CSSRuleParser parser; 107 | FontFaceParserContext data; 108 | 109 | parser = &ctx->rule.parsers[CSS_RULE_MY_RULE]; 110 | data = NEW(MyRuleParserContextRec, 1); 111 | if (!data) { 112 | return -ENOMEM; 113 | } 114 | // Initialize your data 115 | // ... 116 | // data->xxxx = xxxx; 117 | parser->data = data; 118 | parser->parse = MyRuleParser_Parse; 119 | parser->begin = MyRuleParser_Begin; 120 | strcpy(parser->name, "my-rule"); 121 | return 0; 122 | } 123 | 124 | 125 | void CSSParser_FreeMyRuleParser(LCUI_CSSParserContext ctx) 126 | { 127 | FontFaceParserContext data = GetParserContext(ctx); 128 | 129 | MyRuleParser_End(ctx); 130 | free(data); 131 | ctx->rule.parsers[CSS_RULE_MY_RULE].data = NULL; 132 | } 133 | 134 | ``` 135 | 136 | 定义好后,在 [src/gui/css\_parser.c](https://github.com/lc-soft/LCUI/blob/master/src/gui/css_parser.c) 的中追加调用你的解析器的初始化和销毁函数: 137 | 138 | ```c 139 | LCUI_CSSParserContext CSSParser_Begin(size_t buffer_size, const char *space) 140 | { 141 | ... 142 | 143 | CSSParser_InitMyRuleParser(ctx); 144 | return ctx; 145 | } 146 | 147 | void CSSParser_End(LCUI_CSSParserContext ctx) 148 | { 149 | ... 150 | CSSParser_FreeMyRuleParser(ctx); 151 | ... 152 | free(ctx->buffer); 153 | free(ctx); 154 | } 155 | ``` 156 | 157 | 如需了解更详细的规则解析器实现方式,可参考 `@font-face` 规则解析器的源码:[src/gui/css\_rule\_font\_face.c](https://github.com/lc-soft/LCUI/blob/master/src/gui/css_rule_font_face.c) 158 | 159 | ### 添加 CSS 属性解析器 160 | 161 | 在添加新的 CSS 属性解析器前,我们需要先用 `LCUI_AddCssPropertyName()` 注册自定义属性名称,拿到该属性的标识号,也就是该属性在样式表中的下标,然后调用`LCUI_AddCSSPropertyParser()` 函数注册我们的解析器的自定义标识号、解析函数和名称。 162 | 163 | 接下来我们从下面这个简单的示例来了解如何添加 CSS 属性解析器: 164 | 165 | ```c 166 | #define GetMYPropertyKey(CTX) keys[(CTX)->parser->key] 167 | #define SetMyProperty(CTX, S) \ 168 | CSSStyleParser_SetCSSProperty(CTX, GetMYPropertyKey(CTX), S); 169 | 170 | enum CSSMyPropertyKey { 171 | key_my_css_property, 172 | // define other css property keys 173 | // key_xxx 174 | // ... 175 | MY_PROPERTY_KEY_TOTAL 176 | }; 177 | 178 | static int keys[MY_PROPERTY_KEY_TOTAL]; 179 | 180 | static int OnParseMyCSSProperty(LCUI_CSSParserStyleContext ctx, const char *str) 181 | { 182 | LCUI_StyleRec style; 183 | 184 | // parse data from str 185 | // ... 186 | 187 | CSSStyleParser_SetCSSProperty(ctx, keys[ctx->parser-key], style); 188 | return 0; 189 | } 190 | 191 | void InitMyCSSProperties(void) 192 | { 193 | LCUI_CSSPropertyParserRec my_parser = { 194 | key_my_css_property, 195 | "my-css-property", 196 | OnParseMyCSSProperty 197 | }; 198 | 199 | keys[my_parser->key] = LCUI_AddCSSPropertyName(my_parser->name); 200 | LCUI_AddCSSPropertyParser(my_parser); 201 | } 202 | ``` 203 | 204 | 如需了解更详细的 CSS 属性解析器实现方式,可参考字体样式解析器的源码:[src/gui/css\_fontstyle.c](https://github.com/lc-soft/LCUI/blob/master/src/gui/css_fontstyle.c) 205 | 206 | ### 待办事项 207 | 208 | **完善 CSS 解析器的错误处理** 209 | 210 | CSS 解析器是以 CSS 代码完全正确为前提而工作的,一旦加载的 CSS 代码存在语法错误或不支持的语法时,可能会出现怪异的解析行为和解析结果,为解决这一问题,我们应该补充错误判断并输出相关错误信息,以便开发者快速定位问题。 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /driver/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 驱动的工作方式和开发方式的介绍。 3 | --- 4 | 5 | # 驱动 6 | 7 | 驱动是一个允许 LCUI 应用程序与操作系统接口交互的模块,它针对操作系统提供的接口做了一层抽象,提供了一套适合 LCUI 运行机制的接口,使我们在编写 LCUI 应用程序时无需考虑操作系统适配问题即可实现跨平台编译运行。 8 | 9 | 让 LCUI 应用程序能够正常工作的驱动有事件循环驱动、键盘驱动、鼠标驱动、视频驱动和触控驱动,接下来让我们深入了解这些驱动的工作方式和开发方式。 10 | 11 | -------------------------------------------------------------------------------- /driver/events.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 事件循环的概念和驱动开发方式的介绍。 3 | --- 4 | 5 | # 事件循环 6 | 7 | 事件是应用程序与自身各个功能模块以及与操作系统进行通讯的手段,也是实现事件驱动编程模型的基础,应用程序如果要响应这些事件,通常是创建一个事件队列来集中存放它们,从事件队列取出事件并调用对应处理器就是一次事件响应,而往复执行这个操作的过程就是事件循环。 8 | 9 | ### 驱动接口 10 | 11 | LCUI 对事件循环的操作有处理事件、绑定事件和解绑事件,驱动模块的职责就是基于操作系统接口向 LCUI 提供实现了这些操作的接口。首先我们看看 [include/LCUI/main.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/main.h#L133-L142) 中的 `LCUI_AppDirver` 定义: 12 | 13 | ```c 14 | typedef enum LCUI_AppDriverId_ { 15 | LCUI_APP_UNKNOWN, 16 | LCUI_APP_LINUX, 17 | LCUI_APP_LINUX_X11, 18 | LCUI_APP_WINDOWS, 19 | LCUI_APP_UWP 20 | } LCUI_AppDriverId; 21 | 22 | typedef struct LCUI_AppDriverRec_ { 23 | LCUI_AppDriverId id; 24 | void (*ProcessEvents)(void); 25 | int (*BindSysEvent)(int, LCUI_EventFunc, void *, void (*)(void *)); 26 | int (*UnbindSysEvent)(int, LCUI_EventFunc); 27 | int (*UnbindSysEvent2)(int); 28 | void *(*GetData)(void); 29 | } LCUI_AppDriverRec, *LCUI_AppDriver; 30 | ``` 31 | 32 | `id` 是该驱动的标识,在 Linux 系统中,LCUI 针对字符界面和 X11 环境提供了两套驱动,而这个 `id` 的作用就是用来决定该使用哪套驱动,当然 `id` 不只有这一种用途,例如:在非字符界面模式下隐藏鼠标光标。 33 | 34 | `ProcessEvents` 函数用于处理事件队列里的所有事件,如果事件队列中没有事件则会立刻退出,不需要阻塞等待事件。 35 | 36 | `UnbindSysEvent` 和 `UbindSysEvent2` 函数都是用于解绑事件,前者的解绑依据 `eventId + eventHandler` ,后者是 `handlerId` 。 37 | 38 | 剩下的 `GetData` 函数主要用于向其它驱动模块提供数据,例如在 Windows 系统中,虽然各个驱动的代码因出于模块化的考虑而被分割到多个源文件中,但它们都是基于同一个主窗口的消息循环且都依赖主窗口句柄,为了让这些驱动能拿到主窗口句柄,那么就可以靠 `GetData` 函数来获取。 39 | 40 | ### 开发方式 41 | 42 | 综上所述,假设你添加的是适用于 Mac OS 的驱动,那么需要如下步骤: 43 | 44 | 1. 在 `LCUI_AppDriverId` 中的末尾添加 `LCUI_APP_DARWIN` 。 45 | 2. 按照函数指针的原型来定义一些事件处理函数。 46 | 3. 定义 `LCUI_CreateDarwinAppDriver` 函数,在这个函数创建一个 `LCUI_AppDriver` 类型的对象,然后给它的 `id` 和函数指针设置正确的值。 47 | 4. 在[ include/LCUI/platform.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform.h) 中添加针对该操作系统的预处理指令: 48 | 49 | ```c 50 | #elif __APPLE__ 51 | #define LCUI_CreateAppDriver LCUI_CreateDarwinAppDriver 52 | ... 53 | ``` 54 | 55 | ### 参考资料 56 | 57 | 如需了解更多,可参考现有的 Windows 和 Linux 系统的驱动: 58 | 59 | * [include/LCUI/platform/windows/windows\_events.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform/windows/windows_events.h) 60 | * [include/LCUI/platform/linux/linux\_events.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform/linux/linux_events.h) 61 | * [include/LCUI/platform/linux/linux\_x11events.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform/linux/linux_x11events.h) 62 | * [src/platform/windows/windows\_events.c](https://github.com/lc-soft/LCUI/blob/master/src/platform/windows/windows_events.c) 63 | * [src/platform/linux/linux\_events.c](https://github.com/lc-soft/LCUI/blob/master/src/platform/linux/linux_events.c) 64 | * [src/platform/linux/linux\_x11events.c](https://github.com/lc-soft/LCUI/blob/master/src/platform/linux/linux_x11events.c) 65 | 66 | -------------------------------------------------------------------------------- /driver/keyboard.md: -------------------------------------------------------------------------------- 1 | # 键盘 2 | 3 | 鼠标驱动的工作是触发按键按下和释放事件,通常我们只需要绑定系统的按键事件然后转换成 LCUI 的按键事件对象即可。 4 | 5 | 如需了解更多,可参考现有的鼠标驱动代码: 6 | 7 | * [src/platform/linux/linux\_keyboard.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/linux/linux_keyboard.c) 8 | * [src/platform/linux/linux\_keyboard.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/linux/linux_x11keyboard.c) 9 | * [src/platform/windows/windows\_keyboard.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/windows/windows_keyboard.c) 10 | * [src/platform/windows/uwp\_input.cpp](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/windows/uwp_input.cpp#L252-L273) 11 | 12 | -------------------------------------------------------------------------------- /driver/mouse.md: -------------------------------------------------------------------------------- 1 | # 鼠标 2 | 3 | 鼠标驱动的工作是触发包括移动、点击、滚轮在内的鼠标事件,通常我们只需要绑定系统的鼠标事件然后转换成 LCUI 的鼠标事件对象即可。 4 | 5 | 如需了解更多,可参考现有的鼠标驱动代码: 6 | 7 | * [src/platform/linux/linux\_mouse.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/linux/linux_mouse.c) 8 | * [src/platform/linux/linux\_x11mouse.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/linux/linux_x11mouse.c) 9 | * [src/platform/windows/windows\_mouse.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/windows/windows_mouse.c) 10 | * [src/platform/windows/uwp\_input.cpp](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/windows/uwp_input.cpp#L105-L251) 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /driver/touchscreen.md: -------------------------------------------------------------------------------- 1 | # 触控 2 | 3 | 触控驱动的工作是收集触点的状态和坐标然后触发触控事件,通常我们只需要绑定系统的触控事件然后转换成 LCUI 的触控事件对象即可。 4 | 5 | 如需了解更多,可参考现有的触控驱动: 6 | 7 | * [src/platform/windows/uwp\_input.cpp](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/windows/uwp_input.cpp#L213-L224) 8 | * [src/platform/windows/windows\_mouse.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/windows/windows_mouse.c#L95-L143) 9 | 10 | -------------------------------------------------------------------------------- /driver/video.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 视频驱动的概念和开发方式介绍。 3 | --- 4 | 5 | # 视频 6 | 7 | 视频驱动负责实现 LCUI 应用程序与操作系统中的视窗系统的交互,这些交互包括向 LCUI 应用程序提供屏幕尺寸、将组件的信息和渲染结果同步到对应的窗口中,是 LCUI 的几个驱动模块中最为复杂的一个。 8 | 9 | ### 表面 10 | 11 | 表面(Surface)是窗口的抽象,也是 LCUI 的组件与操作系统的窗口进行交互的中间层,它屏蔽了各个操作系统中的视窗操作接口的差异和实现细节,使得 LCUI 应用程序只需要专注于将图形内容渲染到表面上,剩下的工作则交给视频驱动,它会将表面的尺寸、位置、标题等信息以及图形内容同步到对应的窗口。 12 | 13 | LCUI 将表面的数据结构交由视频驱动在内部定义,应用层代码仅靠 `LCUI_Surface` 类型的指针来引用表面,对表面的操作都是靠调用表面的函数来实现的。 14 | 15 | ### 显示模式 16 | 17 | 有三种显示模式: 18 | 19 | * `LCUI_DMODE_WINDOWED`:窗口化模式,将根组件绑定到表面上,组件的宽高与表面宽高相同。 20 | * `LCUI_DMODE_FULLSCREEN`:全屏模式,将根组件绑定到表面上,表面宽高与屏幕宽高相同。 21 | * `LCUI_DMODE_SEAMLESS`:无缝模式,为根组件的每个直系子组件绑定一个表面。 22 | 23 | 初始显示模式是窗口化模式,你可以使用 `LCUIDisplay_SetMode()` 函数更改显示模式。 24 | 25 | ### 驱动接口 26 | 27 | 视频驱动接口在 [include/LCUI/display.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/display.h#L79-L102) 中的定义如下: 28 | 29 | ```c 30 | typedef struct LCUI_DisplayDriverRec_ { 31 | char name[256]; 32 | int (*getWidth)(void); 33 | int (*getHeight)(void); 34 | LCUI_Surface (*create)(void); 35 | void (*destroy)(LCUI_Surface); 36 | void (*close)(LCUI_Surface); 37 | void (*resize)(LCUI_Surface, int, int); 38 | void (*move)(LCUI_Surface, int, int); 39 | void (*show)(LCUI_Surface); 40 | void (*hide)(LCUI_Surface); 41 | void (*update)(LCUI_Surface); 42 | void (*present)(LCUI_Surface); 43 | LCUI_BOOL (*isReady)(LCUI_Surface); 44 | LCUI_PaintContext (*beginPaint)(LCUI_Surface, LCUI_Rect *); 45 | void (*endPaint)(LCUI_Surface, LCUI_PaintContext); 46 | void (*setCaptionW)(LCUI_Surface, const wchar_t *); 47 | void (*setRenderMode)(LCUI_Surface, int); 48 | void *(*getHandle)(LCUI_Surface); 49 | int (*getSurfaceWidth)(LCUI_Surface); 50 | int (*getSurfaceHeight)(LCUI_Surface); 51 | void (*setOpacity)(LCUI_Surface, float); 52 | int (*bindEvent)(int, LCUI_EventFunc, void *, void (*)(void *)); 53 | } LCUI_DisplayDriverRec, *LCUI_DisplayDriver; 54 | ``` 55 | 56 | 接下来让我们深入了解这个结构体中的成员。 57 | 58 | #### name 59 | 60 | 视频驱动的名称,用于标识当前使用的是哪个视频驱动。 61 | 62 | #### getWidth 63 | 64 | 获取屏幕的宽度。 65 | 66 | #### getHeight 67 | 68 | 获取屏幕的高度。 69 | 70 | #### create 71 | 72 | 创建表面。你可以在这里初始化帧缓存、初始化表面信息、调用系统提供的接口创建窗口。 73 | 74 | #### destroy 75 | 76 | 销毁表面。你可以在这里释放帧缓存、释放表面信息、调用系统提供的接口关闭窗口。 77 | 78 | #### close 79 | 80 | 关闭表面。你可以在这里做销毁窗口的准备工作。这个函数是参考 Windows 的窗口关闭流程而设计的,关闭窗口时会收到 `WM_CLOSE` 消息,`DefWindowProc()` 函数对这个消息的处理就是调用 `DestroyWindow()` 销毁窗口。 81 | 82 | #### resize 83 | 84 | 调整表面尺寸。你可以在这里重新分配帧缓存、调用系统提供的接口调整窗口尺寸。 85 | 86 | #### move 87 | 88 | 移动表面位置。你可以在这里调用系统提供的接口调整窗口位置。 89 | 90 | #### show 91 | 92 | 显示表面。你可以在这里调用系统提供的接口显示窗口。 93 | 94 | #### hide 95 | 96 | 隐藏表面。你可以在这里调用系统提供的接口隐藏窗口。 97 | 98 | #### update 99 | 100 | 更新表面。现有的视频驱动由于考虑到在其它线程上操作表面的情况,所以被设计成需要调用 update 函数才会应用所有的表面操作。不过现在还没有这种情况,你可以忽略这个函数。 101 | 102 | #### present 103 | 104 | 呈现表面的最新内容。你可以在这里将帧缓存中的内容同步到主窗口中。 105 | 106 | #### isReady 107 | 108 | 表面是否已经准备就绪。如果你的表面在 `create()` 函数中因某些原因无法立刻完成初始化,那么可以用这个函数返回 `FALSE` 告知 LCUI 这个表面暂时不能用,需要等待一会,直到返回 `TRUE` 为止。 109 | 110 | #### beginPaint 111 | 112 | 开始绘制。你可以在这里完成绘制前的准备工作,例如创建绘制缓存区来存储接下来绘制的内容,然后返回绘制上下文。 113 | 114 | #### endPaint 115 | 116 | 结束绘制。你可以在这里将已绘制的内容更新到帧缓存中。 117 | 118 | #### setCaptionW 119 | 120 | 设置表面的说明文字。你可以在这里将表面的说明文字更新到窗口标题上。 121 | 122 | #### setRenderMode 123 | 124 | 设置表面的渲染模式。现在的渲染模式有拉伸和直接填充这两种,不过自定义渲染模式的场景很少,你可以忽略这个函数。 125 | 126 | #### getHandle 127 | 128 | 获取表面的窗口句柄。在 Windows 的视频驱动中,这个函数被用于在事件循环驱动中处理 `WM_CLOSE` 消息时判断应该关闭哪个表面。在其它系统的视频驱动中并没有这种处理,`getHandle()` 的返回值为 NULL,它们对窗口关闭事件的响应是直接退出 LCUI。 129 | 130 | #### getSurfaceWidth 131 | 132 | 获取表面宽度。 133 | 134 | #### getSurfaceHeight 135 | 136 | 获取表面高度。 137 | 138 | #### setOpacity 139 | 140 | 设置表面透明度。其它视频驱动没有实现该功能,你可以忽略这个函数。 141 | 142 | #### bindEvent 143 | 144 | 绑定事件。LCUI 会在主窗口主动触发尺寸变化、重绘、更新尺寸限制时做一些操作,例如:在用户主动拖拽调整窗口大小时,LCUI 会将表面的尺寸同步到与之绑定的组件上。 145 | 146 | ### 开发方式 147 | 148 | 综上所述,假设你想添加的是适用于 Mac OS 的驱动,那么需要如下步骤: 149 | 150 | 1. 按照 `LCUI_DisplayDriver` 中的函数指针的原型来定义函数。 151 | 2. 定义 `LCUI_CreateDarwinDisplayDriver` 函数,在这个函数创建一个 `LCUI_DisplayDriver` 类型的对象,然后给它的 `name` 和函数指针设置正确的值。 152 | 3. 在[ include/LCUI/platform.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform.h) 中添加针对该操作系统的预处理指令: 153 | 154 | ```c 155 | #elif __APPLE__ 156 | #define LCUI_CreateDisplayDriver LCUI_CreateDarwinDisplayDriver 157 | ... 158 | ``` 159 | 160 | ### 参考资料 161 | 162 | 如需了解更多,可参考现有的 Windows 和 Linux 系统的驱动: 163 | 164 | * [include/LCUI/platform/linux/linux\_display.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform/linux/linux_display.h) 165 | * [include/LCUI/platform/linux/linux\_fbdisplay.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform/linux/linux_fbdisplay.h) 166 | * [include/LCUI/platform/linux/linux\_x11display.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform/linux/linux_x11display.h) 167 | * [include/LCUI/platform/windows/windows\_display.h](https://github.com/lc-soft/LCUI/blob/master/include/LCUI/platform/windows/windows_display.h) 168 | * [src/platform/linux/linux\_display.c](https://github.com/lc-soft/LCUI/blob/master/src/platform/linux/linux_display.c) 169 | * [src/platform/linux/linux\_x11display.c](https://github.com/lc-soft/LCUI/blob/master/src/platform/linux/linux_x11display.c) 170 | * [src/platform/linux/linux\_fbdisplay.c](https://github.com/lc-soft/LCUI/blob/master/src/platform/linux/linux_fbdisplay.c) 171 | * [src/platform/windows/windows\_display.c](https://github.com/lc-soft/LCUI/blob/master/src/platform/windows/windows_display.c) 172 | * [src/platform/windows/uwp\_renderer.cpp](https://github.com/lc-soft/LCUI/blob/master/src/platform/windows/uwp_renderer.cpp) 173 | 174 | ### 待办事项 175 | 176 | **调整视频驱动接口成员的命名** 177 | 178 | `getWidth` 获取的是屏幕的宽度, `getSurfaceWidth` 获取的是表面的宽度,按照这种命名风格,容易让人以为与表面相关的函数指针的命名应该都带有 Surface,然而实际上并没有。我们应该考虑将 `getWidth` 改成 `getScreenWidth`,将 `getSurfaceWidth` 改成 `getWidth`,但这样改的话,`LCUI_DisplayDriver` 是不是应该重命名为 `LCUI_SurfaceDriver` ?毕竟这个接口的操作集大都是针对表面的,而不是屏幕。 179 | 180 | **检验全屏模式和无缝模式是否工作正常** 181 | 182 | 这两个显示模式已经很久没有测试过了,需要添加测试用例来检验是否能够正常工作。 183 | 184 | -------------------------------------------------------------------------------- /en-us/README.md: -------------------------------------------------------------------------------- 1 | # LCUI Guide 2 | 3 | This guide is not available now. 4 | 5 | -------------------------------------------------------------------------------- /en-us/summary.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](./) 4 | 5 | -------------------------------------------------------------------------------- /font/README.md: -------------------------------------------------------------------------------- 1 | # 字体 2 | 3 | [字体](https://zh.wikipedia.org/wiki/%E5%AD%97%E4%BD%93)是一整套具有相同设计特征的字形,系一个或多个字型的一个或多个尺寸的集合。在计算机中,字体也是包含一套字形和字符的电子数据文件,我们可以使用它们来改变界面中的文字显示效果。 4 | 5 | LCUI 使用字体数据库和渲染引擎实现对字体的支持,前者用于加载和记录字体文件并缓存常用的文字位图数据,后者则用于根据字符码和选定的字型从已加载的字体文件中选取对应字形并栅格化成位图。 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /font/engine.md: -------------------------------------------------------------------------------- 1 | # 字体渲染引擎 2 | 3 | 字体渲染引擎的工作主要是字体文件操作和文字渲染,LCUI 将其抽象成了 `LCUI_FontEngine` 接口,使得 LCUI 的字体渲染引擎可被切换和扩展。 4 | 5 | 目前基于该接口实现的引擎有内置引擎和 FreeType 引擎,接下来我们再深入了解它们。 6 | 7 | ### 内置引擎 8 | 9 | 内置引擎是 LCUI 初始化的第一个引擎,它主要用于在无其它可用引擎的情况下加载预置的字体位图数据,以确保界面中的文字能够被渲染出来。 10 | 11 | 内置引擎只能加载 `in-core.inconsolata` 字体,该字体已经转换为点阵字库嵌入在源码中,并随着 LCUI 应用程序的运行而一同被加载到内存中,对于内置引擎而言,渲染过程只是简单的将该字库中的文字位图取出来然后混合到画布上。 12 | 13 | 如果你开发的应用程序是在性能、内存和存储条件较为苛刻的环境中运行的,没有多余的资源供 FreeType 引擎使用,那么只使用内置引擎是开销最小一种办法,但内置的点阵字库包含 12px ~ 18px 的字体位图,你可能需要删除一些位图以缩减字库的大小。 14 | 15 | ### FreeType 引擎 16 | 17 | [FreeType](https://www.freetype.org/) 是一个用于渲染字体的软件库,它采用 C 语言编写,被设计为小型、高效、高可定制、可移植性的同时还能够产生大多数矢量和位图字体格式的高质量输出。 18 | 19 | ### 添加新的引擎 20 | 21 | `LCUI_FontEngine` 在[ include/LCUI/font/fontlibrary.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/font/fontlibrary.h#L82-L88) 中的定义如下: 22 | 23 | ```c 24 | struct LCUI_FontEngine { 25 | char name[64]; 26 | int(*open)(const char*, LCUI_Font**); 27 | int(*render)(LCUI_FontBitmap*, wchar_t, int, LCUI_Font); 28 | void(*close)(void*); 29 | }; 30 | ``` 31 | 32 | 从中我们可以看出,我们需要给新的引擎定义 `open()`、`render()` 和 `close()` 方法,然后设置这四个字段,示例如下: 33 | 34 | ```c 35 | static int MyFontEngine_Open(const char *filepath, LCUI_Font **outfonts) 36 | { 37 | ... 38 | } 39 | 40 | static void MyFontEngine_Close(void *data) 41 | { 42 | ... 43 | } 44 | 45 | static int MyFontEngine_Render(LCUI_FontBitmap *bmp, wchar_t ch, 46 | int pixel_size, LCUI_Font font) 47 | { 48 | ... 49 | } 50 | 51 | int LCUIFont_InitMyFontEngine(LCUI_FontEngine *engine) 52 | { 53 | .... 54 | 55 | strcpy(engine->name, "MyFontEngine"); 56 | engine->render = MyFontEngine_Render; 57 | engine->open = MyFontEngine_Open; 58 | engine->close = MyFontEngine_Close; 59 | return 0; 60 | } 61 | 62 | int LCUIFont_ExitMyFontEngine(void) 63 | { 64 | ... 65 | return 0; 66 | } 67 | ``` 68 | 69 | 如需了解更多,可参考现有引擎的源码: 70 | 71 | * [src/font/freetype.c](https://github.com/lc-soft/LCUI/blob/master/src/font/freetype.c) 72 | * [src/font/in\_core\_font.c](https://github.com/lc-soft/LCUI/blob/master/src/font/in_core_font.c) 73 | 74 | -------------------------------------------------------------------------------- /font/library.md: -------------------------------------------------------------------------------- 1 | # 字体数据库 2 | 3 | ### 简单的示例 4 | 5 | 以下示例代码展示了如何加载一个字体文件: 6 | 7 | ```c 8 | #include 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | int ret; 15 | if (argc < 2) { 16 | printf("Please specify the font file path"); 17 | return -1; 18 | } 19 | LCUI_InitFontLibrary(); 20 | ret = LCUIFont_LoadFile(argv[1]); 21 | LCUI_FreeFontLibrary(); 22 | return ret; 23 | } 24 | ``` 25 | 26 | ### 选择字型 27 | 28 | 字体渲染引擎的接口都依赖于字型 id,为了得到字型 id,我们需要用到这两个函数: 29 | 30 | ```c 31 | int LCUIFont_GetId(const char *family_name, LCUI_FontStyle style, 32 | LCUI_FontWeight weight); 33 | 34 | size_t LCUIFont_GetIdByNames(int **font_ids, LCUI_FontStyle style, 35 | LCUI_FontWeight weight, const char *names); 36 | ``` 37 | 38 | `LCUIFont_GetId()` 用于根据传入的字族名称、样式和字重获取匹配的字型 id,示例: 39 | 40 | ```c 41 | int font_id = LCUIFont_GetId("Verdana", FONT_STYLE_NORMAL, FONT_WEIGHT_NORMAL); 42 | ``` 43 | 44 | `LCUIFont_GetIdByNames()` 是它的批量版本,可获取多个字型 id,示例: 45 | 46 | ```c 47 | int *font_ids = NULL; 48 | 49 | LCUIFont_GetIdByNames( 50 | &font_ids, 51 | FONT_STYLE_NORMAL, 52 | FONT_WEIGHT_NORMAL, 53 | "Verdana, Arial, Helvetica" 54 | ); 55 | ``` 56 | 57 | {% hint style="info" %} 58 | 当这两个函数未找到与指定的风格和字重相匹配的字型时,会按照回退规则选择相近的字型,其中风格的回退规则是按照 oblique -> italic -> normal 顺序回退直到 normal 为止,而字重的回退规则则是采用了与浏览器相同的做法,详见 MDN 上的[字重的回退规则](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-weight#%E5%9B%9E%E9%80%80%E6%9C%BA%E5%88%B6)。 59 | {% endhint %} 60 | 61 | ### 渲染文字 62 | 63 | 渲染文字前,需要先准备一块画布,然后用 `LCUIFont_RenderBitmap()` 函数将指定字符的字型渲染为位图,之后再用 `FontBitmap_Mix()` 将位图混合到画布上。 64 | 65 | 以下例子展示了如何渲染文字并输出到 PNG 图片中: 66 | 67 | ```c 68 | #include 69 | #include 70 | #include 71 | 72 | int main(void) 73 | { 74 | int ret, fid; 75 | LCUI_Graph img; 76 | LCUI_FontBitmap bmp; 77 | LCUI_Pos pos = { 25, 25 }; 78 | LCUI_Color bg = RGB(240, 240, 240); 79 | LCUI_Color color = RGB(255, 0, 0); 80 | 81 | /* 初始化字体处理功能 */ 82 | LCUI_InitFontLibrary(); 83 | 84 | /* 创建一个画布,并填充背景为灰色 */ 85 | Graph_Init(&img); 86 | Graph_Create(&img, 100, 100); 87 | Graph_FillRect(&img, bg, NULL, FALSE); 88 | 89 | /* 载入字体文件 */ 90 | ret = LCUIFont_LoadFile("C:/Windows/fonts/simsun.ttc"); 91 | while (ret == 0) { 92 | /* 获取字体ID */ 93 | fid = LCUIFont_GetId("SimSun", 0, 0); 94 | if (fid < 0) { 95 | break; 96 | } 97 | /* 渲染对应的文字位图,大小为 48 像素 */ 98 | ret = LCUIFont_RenderBitmap(&bmp, L'字', fid, 48); 99 | if (ret != 0) { 100 | break; 101 | } 102 | /* 绘制红色文字到图像上 */ 103 | FontBitmap_Mix(&img, pos, &bmp, color); 104 | LCUI_WritePNGFile("test_char_render.png", &img); 105 | /* 释放内存资源 */ 106 | FontBitmap_Free(&bmp); 107 | Graph_Free(&img); 108 | break; 109 | } 110 | 111 | /* 释放字体处理功能相关资源 */ 112 | LCUI_FreeFontLibrary(); 113 | return ret; 114 | } 115 | ``` 116 | 117 | 运行该程序后打开 `test_char_render.png` 文件,你会看到如下内容: 118 | 119 |  120 | 121 | ### 字体数据库 122 | 123 | 字体数据库由如下数据结构组成: 124 | 125 | * **字族表(Dict):**记录了所有字族结点的哈希表,以字族名称作为索引键值。 126 | * **字族结点(FontFamilyNode):**记录该字族下的所有字体样式结点列表。 127 | * **字体样式结点(FontStyleNode):**记录了该字体样式下的字体列表,字体在数组重的下标与字重对应。 128 | * **字型(Font):**记录了字型的 id、所属字族、样式、字重,以及字体渲染引擎所需的一些数据。 129 | 130 | ### 字型缓存 131 | 132 | 字体文件中的字型数据会在加载时被添加到字型缓存中,该缓存是一个数组,每个元素都包含固定长度数组,结构与二维数组类似。缓存的目的是为了快速访问字型数据,字型的 id 就是它在缓存中的位置,经过如下计算即可访问: 133 | 134 | ```c 135 | font_cache[id / FONT_CACHE_SIZE]->fonts[id % FONT_CACHE_SIZE] 136 | ``` 137 | 138 | ### 位图缓存 139 | 140 | 渲染文本的主要过程是将每个字符的字形栅格化成位图然后绘制到目标面上,其中栅格化的耗时相比直接读取位图的耗时要高一点,而又由于一段文本通常都会包含一些重复的字符,尤其是由 26 个字母和一些符号组成的英文文本,为减少因栅格化这些重复的字符而增加的耗时,于是就有了位图缓存。 141 | 142 | 位图缓存的数据结构由嵌套三层的红黑树(RBTree)组成,这三层红黑树分别以字符码、字型 id 和 字体大小为索引键,实现了对每个字符位图的分类功能。 143 | 144 | ### 待办事项 145 | 146 | **优化位图缓存的存储结构和性能** 147 | 148 | 优化目标如下: 149 | 150 | * 提高缓存读写性能:考虑改用数组代替红黑树。 151 | * 减少内存占用:测试字形的栅格化耗时,如果耗时可以忽略,则可改为缓存字形数据。 152 | 153 | **重新设计字体数据库的接口** 154 | 155 | `LCUIFont_LoadFile()` 的返回值只表示加载是否成功,应用程序无法得知已加载的字体文件的信息,是否需要改造接口使其返回字族名称?或者参考其它字体库,重新设字体数据库的全部接口? 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /font/textlayer.md: -------------------------------------------------------------------------------- 1 | # 文本排版与渲染 2 | 3 | 文字是程序通过界面向用户传递信息的一种最为简单高效的方式,而将这些文字以合理方式的排列并渲染到屏幕上也是界面的基本能力。本章节将介绍 LCUI 中的文字排版与渲染相关概念和用法,并通过一些示例来帮助你快速理解。 4 | 5 | ### 简单的例子 6 | 7 | 以下例子展示了如何使用文本层(TextLayer)将一段文本渲染到 320x240 尺寸的图片中。 8 | 9 | ```c 10 | #include 11 | #include 12 | #include 13 | 14 | int main(void) 15 | { 16 | int ret; 17 | LCUI_Graph img; 18 | LCUI_Pos pos = { 0, 80 }; 19 | LCUI_Rect area = { 0, 0, 320, 240 }; 20 | LCUI_TextLayer txt = TextLayer_New(); 21 | LCUI_TextStyleRec txtstyle; 22 | 23 | /* 初始化字体处理功能 */ 24 | LCUI_InitFontLibrary(); 25 | 26 | /* 创建一个图像,并使用灰色填充 */ 27 | Graph_Init(&img); 28 | Graph_Create(&img, 320, 240); 29 | Graph_FillRect(&img, RGB(240, 240, 240), NULL, FALSE); 30 | 31 | /* 设置文本的字体大小 */ 32 | TextStyle_Init(&txtstyle); 33 | txtstyle.pixel_size = 24; 34 | txtstyle.has_pixel_size = TRUE; 35 | 36 | /* 设置文本图层的固定尺寸、文本样式、文本内容、对齐方式 */ 37 | TextLayer_SetFixedSize(txt, 320, 240); 38 | TextLayer_SetTextStyle(txt, &txtstyle); 39 | TextLayer_SetTextAlign(txt, SV_CENTER); 40 | TextLayer_SetTextW(txt, L"这是一段测试文本\nHello, World!", NULL); 41 | TextLayer_Update(txt, NULL); 42 | 43 | /* 将文本图层绘制到图像中,然后将图像写入至 png 文件中 */ 44 | TextLayer_RenderTo(txt, area, pos, &img); 45 | ret = LCUI_WritePNGFile("test_string_render.png", &img); 46 | Graph_Free(&img); 47 | 48 | /* 释放字体处理功能相关资源 */ 49 | LCUI_FreeFontLibrary(); 50 | return ret; 51 | } 52 | ``` 53 | 54 | ### 最大尺寸和固定尺寸 55 | 56 | 文本的排版效果和尺寸受到最大尺寸和固定尺寸的影响,虽说尺寸包括宽度和高度,但能直接对它们产生影响的主要是宽度,细节如下: 57 | 58 | * 如果两者都未设置,则排版时不对文本做换行处理。文本层宽度取所有行宽度中的最大值,高度是所有行高之和。 59 | * 如果仅设置最大尺寸,则排版时仅在文本行宽度超出最大宽度时做换行处理。文本层宽度取所有行宽度中的最大值,不大于最大宽度;高度是所有行高度之和,不大于最大高度。 60 | * 如果仅设置固定尺寸,则排版时仅在文本行宽度超出固定宽度时做换行处理。文本层的尺寸等于固定尺寸。 61 | * 如果两者都被设置,则效果与仅设置固定尺寸时的效果相同。 62 | 63 | 之所以有这两种尺寸,是为了解决 TextView 组件的尺寸计算问题。当 TextView 组件的显示模式是内联块(inline-block)时,为了让它的尺寸自适应内容尺寸,它会在布局时将文本层的实际尺寸返回给布局引擎,等布局引擎计算出 TextView 组件的合适尺寸后,再将实际尺寸作为固定尺寸,对文本层重新排版。 64 | 65 | ### 排版 66 | 67 | 文字排版的过程是将每个字从左至右排列,每行文本的行高取每个字的高度的最大值。 68 | 69 | 影响排版效果的有两个属性: 70 | 71 | * `enable_mulitiline`:多行模式。启用时,文本中的换行符后面的文本都会被移动到下一行中;禁用时,无视换行符,将所有文本放在一行中。 72 | * `enable_autowrap`:自动换行。仅在多行模式启用的情况下有效,当文本超出最大宽度时会将剩余文本移动到下一行。 73 | 74 | ### 样式标签 75 | 76 | 样式标签用于对标记的文本设置样式,在启用该功能后,文本层会维护一个样式列表,记录每段文本样式,可用 `TextLayer_EnableStyleTag()` 函数设置是否启用该功能。 77 | 78 | 目前支持的标签有: 79 | 80 | * `[color]`:颜色 81 | * `[bgcolor]`:背景色 82 | * `[size]`:大小 83 | * `[b]`:加粗 84 | * `[i]`:斜体 85 | 86 | 用法如下: 87 | 88 | ```text 89 | [color="#f00"]red[/color] 90 | [bgcolor="#f00"]red Background[/color] 91 | [size="18px"]18px size[/size] 92 | [b]bold[/b] 93 | [i]italic[/i] 94 | ``` 95 | 96 | ### 光标 97 | 98 | 光标(Caret)主要为 TextEdit 组件服务,它的位置即是文本插入位置,访问文本层对象的 `insert_x` __和 `insert_y` 成员可获取该位置。当调用 `TextLayer_InsertText()` 函数时,文本就会插入到光标所处的位置。 99 | 100 | `insert_x` __和 `insert_y` 成员分别记录光标所在的列和行,对于像 TextEdit 组件这种需要光标的实际像素坐标来绘制光标的情况,则需要调用: 101 | 102 | ```c 103 | int TextLayer_GetCaretPixelPos(LCUI_TextLayer layer, LCUI_Pos *pixel_pos); 104 | ``` 105 | 106 | 光标的位置操作函数有两个: 107 | 108 | ```c 109 | void TextLayer_SetCaretPos(LCUI_TextLayer layer, int row, int col); 110 | 111 | int TextLayer_SetCaretPosByPixelPos(LCUI_TextLayer layer, int x, int y); 112 | ``` 113 | 114 | `TextLayer_SetCaretPos()` 函数适用于 TextEdit 组件响应按键控制移动光标的情况,而 `TextLayer_SetCaretPosByPixelPos()` 函数则适用于 TextEdit 组件响应鼠标点击时将点击处坐标转换为光标位置的情况。 115 | 116 | ### 坐标偏移量 117 | 118 | 坐标偏移量(Offset)影响文本的绘制位置,主要用于实现滚动功能,访问文本层对象的 `offset_x` 和 `offset_y` 成员可获得它的坐标偏移量。典型的例子就是 TextEdit 组件,当它的文本内容超出自身尺寸时就会显示滚动条,用户拖动滚动条本质上就是在更改坐标偏移量。 119 | 120 | 坐标偏移量的修改函数是: 121 | 122 | ```c 123 | LCUI_BOOL TextLayer_SetOffset(LCUI_TextLayer layer, int offset_x, int offset_y); 124 | ``` 125 | 126 | 该函数会将传入的偏移量赋值给 `new_offset_x` 和 `new_offset_y`,等 `TextLayer_Update()` 被调用时才会更新到 `offset_x` 和 `offset_y`。 127 | 128 | ### 渲染 129 | 130 | 渲染文本层的内容需要用到: 131 | 132 | ```c 133 | int TextLayer_RenderTo(LCUI_TextLayer layer, LCUI_Rect area, LCUI_Pos layer_pos, 134 | LCUI_Graph *canvas); 135 | ``` 136 | 137 | 在它的参数中,`area` 指定了文本层中需要渲染的区域,`pos` 指定了这块区域在画布中的坐标,而`canvas` 就是用于存储渲染结果的画布。 138 | 139 | 如需了解更多用法,可参考预置组件的源码: 140 | 141 | * [src/gui/widget/textview.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/gui/widget/textview.c#L281) 142 | * [src/gui/widget/textedit.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/gui/widget/textedit.c#L933) 143 | 144 | ### 待办事项 145 | 146 | **改进文本的渲染算法** 147 | 148 | 现在绘制文本时所用的基线位于行高的 4 / 5,这可能不是最佳做法。 149 | 150 | **重写文本排版算法** 151 | 152 | 在重写前考虑 LCUI 新布局引擎设计方案,使两者能够共用同一布局引擎。 153 | 154 | **添加文本选中高亮功能** 155 | 156 | 提供一个函数用于选中一段文本,并给这段文本填充背景色,效果类似于用鼠标点选拖动选择网页中的一段文本。 157 | 158 | **添加支持其它书写方向** 159 | 160 | 现在只支持从左至右排列文本,应该添加支持从上至下、从右至左等书写方向。 161 | 162 | **重写 TextLayer 模块** 163 | 164 | TextLayer 的设计从开发之初到现在都没有多大改动,其源文件中的源码已经有一千多行,是时候重写它了,重写前需要考虑内存占用、接口设计和模块划分的问题,例如:采用内存占用更少的数据结构,应用主流设计思想重新设计接口,按照功能职责划分排版、编辑、渲染、光标定位等模块。 165 | 166 | -------------------------------------------------------------------------------- /graphics/README.md: -------------------------------------------------------------------------------- 1 | # 图形 2 | 3 | 本篇教程从一些基础开始,描述了如何使用 LCUI 提供的图形 API 来绘制 2D 图形。教程中提供的例子,会让你明白可以用图形 API 做什么,也会提供一些代码片段来帮助你开始构建自己的内容。 4 | 5 | ### 图形对象 6 | 7 | 图形对象是一个记录了位图的像素数据和宽高等信息的对象,在 LCUI 中它被定义为 `LCUI_Graph` 结构体类型,与之相关的函数都以 `Graph_` 前缀命名。为了书写方便,在下文中我们将用“图像”代替图形对象。 8 | 9 | 图像的主要属性有: 10 | 11 | * `width` : 宽度,单位为像素。 12 | * `height` : 高度,单位为像素。 13 | * `opacity`: 透明度,用于在与其它图像混合时控制混合比例,取值范围 0.0 ~ 1.0,值为 0.0 时完全透明,值为 1.0 时不透明。 14 | * `color_type`: 色彩类型,描述了图像中的像素数据的理解方式,目前该属性的值只支持 `LCUI_COLOR_TYPE_ARGB` 和 `LCUI_COLOR_TYPE_RGB`。 15 | * `quote` : 其它图像的引用信息,调用 `Graph_Quote()` 函数可为指定图像中的区域创建一个引用,操作该图像时实质上是在操作它引用的图像,且读写操作都被限定在引用区域内。 16 | * `bytes`: 像素数据缓存,允许以 1 字节为单位进行读写。 17 | * `argb` : 像素数据缓存,允许以 4 字节为单位进行读写。你可以将它理解为 `LCUI_ARGB` 类型的数组。 18 | * `bytes_per_pixel` : 每个像素占用的字节数。当色彩类型为 `LCUI_COLOR_TYPE_RGB` 时,该值为 3;当色彩类型为 `LCUI_COLOR_TYPE_ARGB` 时,该值为 4。 19 | * `bytes_per_row`: 每行像素数据占用的字节数。在读写像素数据时,我们需要知道每行像素占用多少字节才能准确计算指定坐标的像素点在像素数据缓存中的下标,我们推荐你优先使用该属性,因为手动编码计算还得考虑色彩类型和字节对齐问题。 20 | 21 | 以下示例展示了如何创建一个 800x600 像素的图形对象然后释放它: 22 | 23 | ```c 24 | #include 25 | #include 26 | 27 | int main(void) 28 | { 29 | LCUI_Graph graph; 30 | 31 | Graph_Init(&graph); 32 | Graph_Create(&graph, 800, 600); 33 | Graph_Free(&graph); 34 | return 0; 35 | } 36 | ``` 37 | 38 | ### 栅格 39 | 40 | 在我们开始画图之前,我们需要了解一下栅格(Grid)以及坐标空间。如下图所示,图像被栅格所覆盖,栅格中的一个单元对应图像中的一个像素,它的起点位于左上角坐标(0,0)处,所有元素的位置都相对于原点定位。所以图中蓝色方形左上角的坐标距离左边 x 像素,距离上边 y 像素。 41 | 42 |  43 | 44 | 在概念上,图像被我们赋予了二维坐标系,使得图像的每个像素点都有 x 和 y 坐标,而在计算机的内存中,图像的像素数据都存储在一段连续的内存空间内,通过一维下标来访问某个像素点的数据。在日常的图像处理中,为了能够操作像素数据,我们会需要用到一种二维坐标到一维下标的变换方法: 45 | 46 | ```c 47 | index = y * width + x 48 | ``` 49 | 50 | 用 C 代码来表达它就是这样: 51 | 52 | ```c 53 | LCUI_Graph graph; 54 | 55 | // 以一个像素为单位来访问坐标 (x, y) 上的像素 56 | graph->argb[graph->width * y + x]; 57 | ``` 58 | 59 | 当图像的色彩类型是 `LCUI_COLOR_TYPE_RGB` 时,我们只能以一个字节为单位访问像素数据,用于计算下标的表达式还得考虑到像素点占用的字节数。 60 | 61 | ```c 62 | LCUI_Graph graph; 63 | 64 | // 以一个像素为单位来访问坐标 (x, y) 上的像素 65 | graph->bytes[graph->bytes_per_row * y + graph->bytes_per_pixel * x]; 66 | ``` 67 | 68 | ### 绘制上下文 69 | 70 | 绘制上下文描述了绘制时使用的画布和绘制区域,常用于背景图、阴影、边框等复杂的图形绘制操作,你可以将其理解为图形绘制函数的常用参数集合体。它被定义为 `LCUI_PaintContextRec` 结构体类型的对象,与之相关的函数都以 `LCUIPainter_` 前缀命名。 71 | 72 | 以下示例展示了如何创建和释放绘制上下文: 73 | 74 | ```c 75 | LCUI_PaintContext paint; 76 | LCUI_Graph canvas; 77 | LCUI_Rect rect; 78 | 79 | ... 80 | 81 | paint = LCUIPainter_Begin(&canvas, &rect); 82 | LCUIPainter_End(paint); 83 | ``` 84 | 85 | ### 待办事项 86 | 87 | **重新设计图像处理 API** 88 | 89 | 以开发新的图形库为目的,设计一套图形 API,然后将现有的代码改用这套新 API 来实现。设计时需要考虑的因素有: 90 | 91 | * 不依赖 LCUI 的数据类型和功能。 92 | * 参考主流图形库的 API 设计,使得用过其它图形库的人能够快速上手。 93 | * 能够切换多个渲染后端,例如:纯 CPU 渲染、DirectX、OpenGL、skia、cario。 94 | 95 | **添加 gif 文件读取和渲染支持** 96 | 97 | * 设计合适的数据结构来存储 gif 动画数据。 98 | * 提供相应的函数以实现播放、暂停、渲染功能。 99 | * 当组件的 `background-image` 属性指定了 gif 文件时,应创建一个定时器来渲染动画。 100 | 101 | \*\*\*\* 102 | 103 | -------------------------------------------------------------------------------- /graphics/drawing-simple-graphics.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 图形渲染相关 API 和概念的介绍。 3 | --- 4 | 5 | # 绘制简单的图形 6 | 7 | ### 绘制矩形 8 | 9 | 与其它图形库不同,LCUI 提供的图形 API 只支持矩形这一种形式的图形绘制,不支持基于路径来绘制复杂图形。因此,对于其它复杂的图形,你需要手动编写代码填充像素来绘制。 10 | 11 | LCUI 提供了一种绘制矩形的方法: 12 | 13 | ```c 14 | int Graph_FillRect(LCUI_Graph *graph, LCUI_Color color, 15 | LCUI_Rect *rect, LCUI_BOOL with_alpha); 16 | ``` 17 | 18 | {% hint style="warning" %} 19 | 注意,该函数会将指定区域内的像素替换为指定颜色,而不是与原有颜色混合。 20 | {% endhint %} 21 | 22 | 现在就来使用这个函数: 23 | 24 | ```c 25 | #include 26 | #include 27 | #include 28 | 29 | int main(void) 30 | { 31 | int i, j; 32 | LCUI_Graph canvas; 33 | LCUI_Color color; 34 | LCUI_Rect rect; 35 | 36 | Graph_Init(&canvas); 37 | Graph_Create(&canvas, 150, 150); 38 | for (i = 0; i < 6; ++i) { 39 | for (j = 0; j < 6; ++j) { 40 | color.red = (unsigned char)(255 - 42.5 * i); 41 | color.green = (unsigned char)(255 - 42.5 * j); 42 | color.blue = 0; 43 | rect.x = j * 25; 44 | rect.y = i * 25; 45 | rect.width = 25; 46 | rect.height = 25; 47 | Graph_FillRect(&canvas, color, &rect, FALSE); 48 | } 49 | } 50 | LCUI_WritePNGFile("test_fill_rect.png", &canvas); 51 | Graph_Free(&canvas); 52 | return 0; 53 | } 54 | 55 | ``` 56 | 57 | 在本示例里,我们用两层 `for` 循环来绘制方阵列,每个方格不同的颜色。结果如下图,但实现所用的代码却没那么绚丽。我们用了两个变量 `i` 和 `j` 来为每一个方格产生唯一的 RGB 色彩值,其中仅修改红色和绿色通道的值,而保持蓝色通道的值不变。你可以通过修改这些颜色通道的值来产生各种各样的色板。通过增加渐变的频率,你还可以绘制出类似 Photoshop 里面的那样的调色板。 58 | 59 |  60 | 61 | ### 透明度 62 | 63 | 除了可以绘制实色图形,我们还可以绘制半透明的图形。通过设置画布的 opacity 属性或使用一个半透明颜色作为填充颜色。 64 | 65 | opacity 属性影响整个图像的透明度,有效的取值范围是 0.0(完全透明)到 1.0(完全不透明),默认是 1.0。 66 | 67 | #### opacity 的使用示例 68 | 69 | 在这个例子里,我们使用两个图像,一个填充四色格作为背景,另一个用作前景,设置其 `opacity` 属性为 `0.2`,然后在前景图中填充一系列尺寸递增的半透明矩形并用 `Graph_Mix()` 函数将它们混合到背景图上。最终结果是一个径向渐变效果。矩形叠加得越更多,原先所画的矩形的透明度会越低。通过增加循环次数,画更多的矩形,从中心到边缘部分,背景图会呈现逐渐消失的效果。 70 | 71 | ```c 72 | #include 73 | #include 74 | #include 75 | 76 | int main(void) 77 | { 78 | int i, size; 79 | LCUI_Graph canvas; 80 | LCUI_Graph fore_canvas; 81 | LCUI_Rect rect; 82 | 83 | Graph_Init(&canvas); 84 | Graph_Init(&fore_canvas); 85 | Graph_Create(&canvas, 150, 150); 86 | // 画背景 87 | rect.x = 0; 88 | rect.y = 0; 89 | rect.width = 75; 90 | rect.height = 75; 91 | Graph_FillRect(&canvas, RGB(255, 221, 0), &rect, FALSE); 92 | rect.x = 75; 93 | Graph_FillRect(&canvas, RGB(102, 204, 0), &rect, FALSE); 94 | rect.x = 0; 95 | rect.y = 75; 96 | Graph_FillRect(&canvas, RGB(0, 153, 255), &rect, FALSE); 97 | rect.x = 75; 98 | Graph_FillRect(&canvas, RGB(255, 51, 0), &rect, FALSE); 99 | // 设置前景的 opacity 值 100 | fore_canvas.opacity = 0.2f; 101 | // 仅当色彩模式为 ARGB 时 opacity 属性才会生效 102 | fore_canvas.color_type = LCUI_COLOR_TYPE_ARGB; 103 | for (i = 0; i < 7; ++i) { 104 | size = 2 * (10 + 10 * i); 105 | // 使用新尺寸重新创建前景图 106 | Graph_Create(&fore_canvas, size, size); 107 | // 重新填充颜色 108 | Graph_FillRect(&fore_canvas, RGB(255, 255, 255), NULL, TRUE); 109 | // 将前景图混合到背景图中 110 | Graph_Mix(&canvas, &fore_canvas, 75 - size / 2, 75 - size / 2, FALSE); 111 | } 112 | LCUI_WritePNGFile("test_mix_rect_with_opacity.png", &canvas); 113 | Graph_Free(&fore_canvas); 114 | Graph_Free(&canvas); 115 | return 0; 116 | } 117 | 118 | ``` 119 | 120 |  121 | 122 | #### 半透明色的使用示例 123 | 124 | 上个例子虽然用填充半透明色代替 opacity 属性也能实现同样的效果,但两个示例都共用同一示例代码的话未免有些无聊,所以我们换一种绘制方式,画一个正方形,将它分成四个填充不同颜色的长方形,每个长方形中都画一系列透明度从左递增的白色矩形: 125 | 126 | ```c 127 | #include 128 | #include 129 | #include 130 | #include 131 | 132 | int main(void) 133 | { 134 | int i, j; 135 | LCUI_Graph canvas; 136 | LCUI_Graph fore_canvas; 137 | LCUI_Color color; 138 | LCUI_Rect rect; 139 | 140 | Graph_Init(&canvas); 141 | Graph_Init(&fore_canvas); 142 | Graph_Create(&canvas, 160, 160); 143 | // 画背景 144 | rect.x = 0; 145 | rect.y = 0; 146 | rect.width = 160; 147 | rect.height = 40; 148 | Graph_FillRect(&canvas, RGB(255, 221, 0), &rect, FALSE); 149 | rect.y += 40; 150 | Graph_FillRect(&canvas, RGB(102, 204, 0), &rect, FALSE); 151 | rect.y += 40; 152 | Graph_FillRect(&canvas, RGB(0, 153, 255), &rect, FALSE); 153 | rect.y += 40; 154 | Graph_FillRect(&canvas, RGB(255, 51, 0), &rect, FALSE); 155 | color.red = 255; 156 | color.green = 255; 157 | color.blue = 255; 158 | fore_canvas.color_type = LCUI_COLOR_TYPE_ARGB; 159 | Graph_Create(&fore_canvas, 15, 30); 160 | // 画半透明矩形 161 | for (i = 0; i < 10; ++i) { 162 | color.alpha = (unsigned char)(255 * (i + 1) / 10.0); 163 | Graph_FillRect(&fore_canvas, color, NULL, TRUE); 164 | for (j = 0; j < 4; ++j) { 165 | Graph_Mix(&canvas, &fore_canvas, 5 + i * 15, 5 + j * 40, TRUE); 166 | } 167 | } 168 | LCUI_WritePNGFile("test_fill_rect_with_rgba.png", &canvas); 169 | Graph_Free(&fore_canvas); 170 | Graph_Free(&canvas); 171 | return 0; 172 | } 173 | 174 | ``` 175 | 176 |  177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /graphics/image-edit.md: -------------------------------------------------------------------------------- 1 | # 图像编辑 2 | 3 | ### 混合 4 | 5 | ### 裁剪 6 | 7 | ### 缩放 8 | 9 | -------------------------------------------------------------------------------- /graphics/image-file-operation.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍图像文件操作函数的用法。 3 | --- 4 | 5 | # 图像文件操作 6 | 7 | ### 读取图像文件信息 8 | 9 | 使用图像读取器读取文件头中的信息: 10 | 11 | ```c 12 | #include 13 | #include 14 | #include 15 | 16 | int main(int argc, char *argv[]) 17 | { 18 | FILE *fp; 19 | LCUI_ImageReaderRec reader = { 0 }; 20 | 21 | if (argc != 2) { 22 | printf("Please specify the image file path"); 23 | return -1; 24 | } 25 | fp = fopen(argv[1], "rb"); 26 | if (!fp) { 27 | printf("Cannot open file"); 28 | return -2; 29 | } 30 | LCUI_SetImageReaderForFile(&reader, fp); 31 | if (LCUI_InitImageReader(&reader) != 0) { 32 | printf("Unsupported image format"); 33 | fclose(fp); 34 | return -3; 35 | } 36 | printf("image type: %d\n", reader.header.type); 37 | printf("image color type: %d\n", reader.header.color_type); 38 | printf("image size: %ux%u", reader.header.width, reader.header.height); 39 | LCUI_DestroyImageReader(&reader); 40 | fclose(fp); 41 | return 0; 42 | } 43 | ``` 44 | 45 | 这段代码会打开参数中指定的文件,然后初始化图像读取器,从文件中读取信息并在最后打印图像的类型、色彩类型和尺寸到屏幕上。 46 | 47 | 在使用 `fopen()` 打开文件后,调用 `LCUI_SetImageReaderForFile()` 将图像读取器的读取目标设置为标准文件流,然后调用 `LCUI_InitImageReader()` 初始化文件读取器,初始化时会尝试调用预置的图像读取接口检测该文件的格式。 48 | 49 | ### 读取图像数据 50 | 51 | LCUI 提供了两个用于读取图像数据的接口: 52 | 53 | ```c 54 | int LCUI_ReadImage(LCUI_ImageReader reader, LCUI_Graph *out); 55 | 56 | int LCUI_ReadImageFile(const char *filepath, LCUI_Graph *out); 57 | ``` 58 | 59 | `LCUI_ReadImage()` 适用于从自定义的文件流中读取图像数据,在使用它之前你需要为图像读取器设置自定义文件流的句柄和操作函数。而 `LCUI_ReadImageFile()` 则适用于从标准文件流中读取图像数据,你只需要传入文件路径,它就能基于标准库的 `fopen()`、`fread()` 和 `fclose()` 等文件读取函数完成图像数据读取。 60 | 61 | ### 在读取时反馈进度 62 | 63 | 图像读取器的数据结构中的 [`fn_prog`](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/image.h#L76) 成员变量是个函数指针,它会在每次读取完一行图像数据后调用,你只需要将该指针指向自定义函数即可获得进度信息。 64 | 65 | 基于上个示例代码,需要做这些改动: 66 | 67 | ```c 68 | #include 69 | #include 70 | #include 71 | 72 | void on_progress(void *data, float percent) 73 | { 74 | printf("read %s: %.2f%%\n", data, percent); 75 | } 76 | 77 | int main(int argc, char *argv[]) 78 | { 79 | FILE *fp; 80 | LCUI_ImageReaderRec reader = { 0 }; 81 | 82 | if (argc != 2) { 83 | printf("Please specify the image file path"); 84 | return -1; 85 | } 86 | fp = fopen(argv[1], "rb"); 87 | if (!fp) { 88 | printf("Cannot open file"); 89 | return -2; 90 | } 91 | LCUI_SetImageReaderForFile(&reader, fp); 92 | if (LCUI_InitImageReader(&reader) != 0) { 93 | printf("Unsupported image format"); 94 | fclose(fp); 95 | return -3; 96 | } 97 | reader.fn_prog = on_progress; 98 | reader.prog_arg = argv[1]; 99 | printf("image type: %d\n", reader.header.type); 100 | printf("image color type: %d\n", reader.header.color_type); 101 | printf("image size: %ux%u", reader.header.width, reader.header.height); 102 | LCUI_DestroyImageReader(&reader); 103 | fclose(fp); 104 | return 0; 105 | } 106 | ``` 107 | 108 | ### 自定义文件流 109 | 110 | 图像读取器的读取功能依赖 `fn_read` 、`fn_rewind` 和 `fn_skip` 这三个函数指针来实现,可供参考的最简单的例子是 [src/image/reader.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/image/reader.c#L61-L83) 文件中的`LCUI_SetImageReaderForFile()` 函数,它对标准库的文件操作函数做了一层简单包装。如果你需要更加高级的用法,可以参考 LC Finder 项目的 [UWP/FileService.cpp](https://github.com/lc-soft/LC-Finder/blob/573f200698e2604450665716ebc6608837b4b73a/UWP/FileService.cpp#L611-L678) 文件中的图像文件读取器相关实现代码,它针对 UWP 的异步文件流做了适配。 111 | 112 | ### 将图像数据写入文件 113 | 114 | 由于图像写入功能很少用,因此并未拥有图像读取功能一般的完成度,目前能用的只有这一个函数: 115 | 116 | ```c 117 | int LCUI_WritePNGFile(const char *file_name, const LCUI_Graph *graph); 118 | ``` 119 | 120 | -------------------------------------------------------------------------------- /graphics/rendering.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 图形渲染相关 API 和概念的介绍。 3 | --- 4 | 5 | # 图形渲染 6 | 7 | 图像渲染是 UI 库的核心能力,它直接影响到 UI 的视觉效果和渲染性能。 8 | 9 | ### 创建画布 10 | 11 | 在开始渲染我们的图像前,我们需要创建一个用于存储图像数据的画布(canvas): 12 | 13 | ```c 14 | #include 15 | #include 16 | #include 17 | 18 | int main(void) 19 | { 20 | LCUI_Graph canvas; 21 | 22 | Graph_Init(&canvas); 23 | Graph_Create(&canvas, 800, 600); 24 | LCUI_WritePNGFile("canvas.png", &canvas); 25 | Graph_Free(&canvas); 26 | return 0; 27 | } 28 | ``` 29 | 30 | 这段代码做了这几件事: 31 | 32 | * 初始化一个画布,默认的色彩类型是 RGB 33 | * 为这块画布创建了能存储 800x600 像素的内存空间 34 | * 将画布内的数据写入到 png 文件 35 | * 释放画布占用的资源 36 | 37 | ### 填充颜色 38 | 39 | 打开 canvas.png 文件后我们可以发现图片内容是黑色的,因为给画布分配的内存空间初始填充的都是 0,RGB\(0,0,0\) 就是黑色,为了方便看到我们接下来绘制的内容,我们先将画布填充为白色: 40 | 41 | ```c 42 | #include 43 | #include 44 | #include 45 | 46 | int main(void) 47 | { 48 | LCUI_Graph canvas; 49 | LCUI_Color white = { .value = 0xffffffff }; 50 | 51 | Graph_Init(&canvas); 52 | Graph_Create(&canvas, 800, 600); 53 | Graph_FillRect(&canvas, white, NULL, FALSE); 54 | LCUI_WritePNGFile("canvas.png", &canvas); 55 | Graph_Free(&canvas); 56 | return 0; 57 | } 58 | ``` 59 | 60 | `Graph_FillRect()` 的第三个参数可以指定填充区域,我们可以试试用它将 \(0, 0 , 100, 200\) 区域填充为红色: 61 | 62 | ```c 63 | #include 64 | #include 65 | #include 66 | 67 | int main(void) 68 | { 69 | LCUI_Graph canvas; 70 | LCUI_Color white = { .value = 0xffffffff }; 71 | LCUI_Color red = { .value = 0xffff0000 }; 72 | LCUI_Rect red_area = { 0, 0, 100, 200 }; 73 | 74 | Graph_Init(&canvas); 75 | Graph_Create(&canvas, 800, 600); 76 | Graph_FillRect(&canvas, white, NULL, FALSE); 77 | Graph_FillRect(&canvas, red, &red_area, FALSE); 78 | LCUI_WritePNGFile("canvas.png", &canvas); 79 | Graph_Free(&canvas); 80 | return 0; 81 | } 82 | ``` 83 | 84 | ### 绘制文本 85 | 86 | 填充色块只是最基本的功能,接下来我们再试试在画布上绘制一段文本: 87 | 88 | ```c 89 | #include 90 | #include 91 | #include 92 | #include 93 | 94 | int main(void) 95 | { 96 | LCUI_Graph canvas; 97 | LCUI_Color white = { .value = 0xffffffff }; 98 | LCUI_Color red = { .value = 0xffff0000 }; 99 | LCUI_Color blue = { .value = 0xff0000ff }; 100 | LCUI_Rect red_area = { 0, 0, 100, 200 }; 101 | LCUI_Pos text_pos = { 0, 240 }; 102 | LCUI_TextLayer text_layer = TextLayer_New(); 103 | LCUI_TextStyleRec txxt_style; 104 | 105 | Graph_Init(&canvas); 106 | Graph_Create(&canvas, 800, 600); 107 | Graph_FillRect(&canvas, white, NULL, FALSE); 108 | Graph_FillRect(&canvas, red, &red_area, FALSE); 109 | 110 | LCUI_InitFontLibrary(); 111 | TextStyle_Init(&text_style); 112 | TextStyle_SetSize(&text_style, 24); 113 | TextStyle_SetForeColor(&text_style, white); 114 | TextStyle_SetBackColor(&text_style, blue); 115 | 116 | TextLayer_SetTextStyle(text_layer, &text_style); 117 | TextLayer_SetTextW(text_layer, L"White text and blue background", NULL); 118 | TextLayer_Update(text_layer, NULL); 119 | TextLayer_Render(text_layer, NULL, 0, 240, &canvas); 120 | 121 | LCUI_WritePNGFile("canvas.png", &canvas); 122 | 123 | TextLayer_Destroy(text_layer); 124 | TextStyle_Destroy(text_style); 125 | Graph_Free(&canvas); 126 | LCUI_FreeFontLibrary(); 127 | return 0; 128 | } 129 | ``` 130 | 131 | 这段示例代码用到了字体库、文本样式(TextStyle)和文本层(TextLayer),其中文本样式影响文本层的渲染效果,使用它可以设置文本的字族、风格、大小、颜色等样式,而文本层则是让 LCUI 具备文本渲染能力的关键,它的文字排版和渲染能力依赖于字体库提供的字形数据,在使用它之前需要先调用 `LCUI_InitFontLibrary()` 初始化字体库。 132 | 133 | 在准备好画布和文本样式后,调用文本层的操作函数设置固定大小、文本样式和文本内容,然后调用 `TextLayer_Update()` 应用这些改动,之后调用 `TextLayer_Render()` 将文本层绘制到画布上。 134 | 135 | 运行这个示例后打开 canvas.png,你会看到蓝底白字的 "White text and blue background" 文本。 136 | 137 | ### 绘制背景图 138 | 139 | ```c 140 | #include 141 | #include 142 | #include 143 | #include 144 | 145 | int main(void) 146 | { 147 | LCUI_Graph canvas; 148 | LCUI_Color white = { .value = 0xffffffff }; 149 | LCUI_Rect bg_area = { 200, 100, 400, 300 }; 150 | LCUI_Rect paint_area = { 0, 0, 400, 300 }; 151 | LCUI_Background bg; 152 | LCUI_PaintContext paint; 153 | 154 | Graph_Init(&canvas); 155 | Graph_Create(&canvas, 800, 600); 156 | Graph_FillRect(&canvas, white, NULL, FALSE); 157 | 158 | Background_Init(&bg); 159 | paint = LCUIPainter_Begin(&canvas, &bg_area); 160 | Background_Paint(&bg, &paint_area, paint); 161 | 162 | LCUI_WritePNGFile("canvas.png", &canvas); 163 | 164 | LCUIPainter_End(paint); 165 | Graph_Free(&canvas); 166 | return 0; 167 | } 168 | ``` 169 | 170 | ### 绘制边框 171 | 172 | ### 绘制圆形 173 | 174 | ### 绘制阴影 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /graphics/xiang-su-cao-zuo.md: -------------------------------------------------------------------------------- 1 | # 像素操作 2 | 3 | 在前面我们已经了解到图像的数据结构、栅格、坐标系和坐标转下标的方法,仅凭 LCUI 提供的这点图形 API 无法让我们轻松绘制复杂的图形,所以我们有必要学会如何操作像素数据,以便自己手动编码或借助其它图形库的能力来绘制图形。 4 | 5 | {% hint style="warning" %} 6 | 注意,虽然像素数据的类型名是 `LCUI_ARGB`,但这些颜色通道的值在内存中存储的顺序是 BGRA。之所以采用这种顺序,是因为 Linux 中的帧缓存(FrameBuffer) 和 Windows 中的位图对象(HBITMAP) 都是这样。 7 | {% endhint %} 8 | 9 | ### 图片灰度和反相颜色 10 | 11 | 在这个例子里,我们将图片分成四个部分,其中三个分别进行反色(inverted)、灰度(grayscale)、褐色(sepia)处理,然后将处理结果输出到图片文件中。`invert()` 函数将颜色的最大色值 255 减去像素的色值作为结果,`grayscale()` 函数将红绿蓝三色值的平均值作为结果,你也可以用加权平均,例如 `x = 0.299r + 0.587g + 0.114b` 这个公式,更多资料请参考维基百科的 [Grayscale](http://en.wikipedia.org/wiki/Grayscale)。`sepia()` 函数与 `grayscale()` 函数采用了类似的算法。 12 | 13 | 像素操作的流程是相似的,先使用两层 for 循环在指定区域内定位要操作的像素点,然后获取像素数据进行处理,之后写入数据到图像中。为了减少代码量,我们将重复的代码定义成 `PixelManipulationBegin` 宏和 `PixelManipulationEnd` 宏,其中像素操作用到`GraphGetPixel()` 和 `GraphSetPixel()` 是函数式宏,它封装了坐标转下标的计算代码,使得我们无需再编写冗长的代码,但需要注意的是,它会为每个像素点计算下标,而这计算中包含重复计算,所以效率比较低,如果你在实际项目中比较注重性能的话可以做一点优化,例如在 y 轴的循环里提前计算每行起点下标:`row_start = y * graph.width + rect.x`。 14 | 15 | ```c 16 | #include 17 | #include 18 | #include 19 | 20 | #define PixelManipulationBegin \ 21 | int x, y; \ 22 | LCUI_Color pixel; \ 23 | \ 24 | for (y = rect.y; y < rect.y + rect.height; ++y) { \ 25 | for (x = rect.x; x < rect.x + rect.width; ++x) { \ 26 | Graph_GetPixel(graph, x, y, pixel); 27 | 28 | #define PixelManipulationEnd \ 29 | Graph_SetPixel(graph, x, y, pixel); \ 30 | } \ 31 | } 32 | 33 | void invert(LCUI_Graph *graph, LCUI_Rect rect) 34 | { 35 | PixelManipulationBegin; 36 | pixel.red = (unsigned char)(255 - pixel.red); 37 | pixel.green = (unsigned char)(255 - pixel.green); 38 | pixel.blue = (unsigned char)(255 - pixel.blue); 39 | PixelManipulationEnd; 40 | } 41 | 42 | void grayscale(LCUI_Graph *graph, LCUI_Rect rect) 43 | { 44 | unsigned char avg; 45 | 46 | PixelManipulationBegin; 47 | avg = (unsigned char)((pixel.red + pixel.green + pixel.blue) / 3); 48 | pixel.red = avg; 49 | pixel.green = avg; 50 | pixel.blue = avg; 51 | PixelManipulationEnd; 52 | } 53 | 54 | void sepia(LCUI_Graph *graph, LCUI_Rect rect) 55 | { 56 | PixelManipulationBegin; 57 | pixel.red = (unsigned char)min( 58 | round(0.393 * pixel.red + 0.769 * pixel.green + 0.189 * pixel.blue), 59 | 255); 60 | pixel.green = (unsigned char)min( 61 | round(0.349 * pixel.red + 0.686 * pixel.green + 0.168 * pixel.blue), 62 | 255); 63 | pixel.blue = (unsigned char)min( 64 | round(0.272 * pixel.red + 0.534 * pixel.green + 0.131 * pixel.blue), 65 | 255); 66 | PixelManipulationEnd; 67 | } 68 | 69 | int main(void) 70 | { 71 | int i; 72 | LCUI_Graph graph = { 0 }; 73 | LCUI_Rect rects[4]; 74 | 75 | if (LCUI_ReadImageFile("dog.jpg", &graph) != 0) { 76 | return -1; 77 | } 78 | for (i = 0; i < 4; ++i) { 79 | rects[i].height = graph.height; 80 | rects[i].width = graph.width / 4; 81 | rects[i].x = i * rects[i].width; 82 | rects[i].y = 0; 83 | } 84 | sepia(&graph, rects[1]); 85 | grayscale(&graph, rects[2]); 86 | invert(&graph, rects[3]); 87 | LCUI_WritePNGFile("test_pixel_manipulation.png", &graph); 88 | return 0; 89 | } 90 | 91 | ``` 92 | 93 | 源图片: 94 | 95 |  96 | 97 | 处理结果(原始,褐色,灰度,反色): 98 | 99 |  100 | 101 | -------------------------------------------------------------------------------- /images/font_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/font_icons.png -------------------------------------------------------------------------------- /images/getting_started_step_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/getting_started_step_1.png -------------------------------------------------------------------------------- /images/getting_started_step_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/getting_started_step_2.png -------------------------------------------------------------------------------- /images/getting_started_step_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/getting_started_step_3.png -------------------------------------------------------------------------------- /images/getting_started_step_4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/getting_started_step_4.gif -------------------------------------------------------------------------------- /images/getting_started_step_5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/getting_started_step_5.gif -------------------------------------------------------------------------------- /images/gui_events_touch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/gui_events_touch.gif -------------------------------------------------------------------------------- /images/test_char_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/test_char_render.png -------------------------------------------------------------------------------- /images/test_string_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/test_string_render.png -------------------------------------------------------------------------------- /images/test_widget_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcui-dev/LCUi-Guide/ff6286c3230d8df49097db4a0afd8abffc3be6cc/images/test_widget_render.png -------------------------------------------------------------------------------- /ime.md: -------------------------------------------------------------------------------- 1 | # 输入法 2 | 3 | 输入法是一种将输入设备输入的数据翻译成字符的方法,这个方法可以表示编码方案和输入平台两种含义,本文将输入法作为输入平台来讲解 LCUI 对它的支持方案。 4 | 5 | LCUI 的输入法引擎负责的工作很简单:记录支持的输入法列表,在有按键输入时调用当前激活的输入法驱动进行处理,然后转换成 TEXTINPUT 事件,让 TextEdit 等具有文本输入支持的组件接收输入法输入的文本。 6 | 7 | ### 添加输入法 8 | 9 | LCUI 将输入法驱动抽象成了 `LCUI_IMEHandler` 接口,它在 [include/LCUI/ime.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/ime.h#L36-L42) 中的定义如下: 10 | 11 | ```c 12 | typedef struct LCUI_IMEHandlerRec_ { 13 | LCUI_BOOL (*prockey)(int, int); 14 | void (*totext)(int); 15 | LCUI_BOOL (*open)(void); 16 | LCUI_BOOL (*close)(void); 17 | void (*setcaret)(int, int); 18 | } LCUI_IMEHandlerRec, *LCUI_IMEHandler; 19 | ``` 20 | 21 | 添加输入法过程就是补全这些方法供 LCUI 的输入法引擎调用,示例模板代码如下: 22 | 23 | ```c 24 | static LCUI_BOOL X11IME_ProcessKey(int key, int key_state) 25 | { 26 | // ... 27 | return FALSE; 28 | } 29 | 30 | static void X11IME_ToText(int ch) 31 | { 32 | wchar_t text[256]; 33 | size_t text_len; 34 | 35 | // ... 36 | LCUIIME_Commit(text, text_len); 37 | } 38 | 39 | static LCUI_BOOL X11IME_Open(void) 40 | { 41 | // ... 42 | return TRUE; 43 | } 44 | 45 | static LCUI_BOOL X11IME_Close(void) 46 | { 47 | // ... 48 | return TRUE; 49 | } 50 | 51 | int LCUI_RegisterLinuxIME(void) 52 | { 53 | LCUI_IMEHandlerRec handler; 54 | handler.prockey = X11IME_ProcessKey; 55 | handler.totext = X11IME_ToText; 56 | handler.close = X11IME_Close; 57 | handler.open = X11IME_Open; 58 | handler.setcaret = NULL; 59 | return LCUIIME_Register("My Input Method", &handler); 60 | 61 | } 62 | ``` 63 | 64 | 接下来我们将以中文输入法为例,介绍这些方法: 65 | 66 | `prockey()` 方法用于判断输入法是否处理按键,第一个参数是按键码,第二个参数是按键的状态,当返回值为 TRUE 时会调用 `totext()` 方法。 67 | 68 | `totext()` 用于将按键码转换为文本。当为中文模式时,可以根据输入的按键码组成拼音,并更新候选词列表供用户选择;当为英文模式时,则可以将按键码转换为字符码,然后调用 `LCUIIME_Commit()` 函数提交它。 69 | 70 | `open()` 方法用于在输入法被激活时调用。你可以在这里做一些与初始化相关的操作。 71 | 72 | `close()` 方法用于在输入法被关闭时调用。你可以在这里终止输入法的工作并清理相关资源。 73 | 74 | `setcaret()` 方法用于设置输入光标的位置,参数是光标的 x、y 坐标。TextEdit 组件中的 TextCaret 组件会在它更新时调用该方法来同步输入法光标的位置,以让输入法的界面定位在 TextEdit 组件附近。 75 | 76 | 如需了解更多,可参考现有的 Windows 和 Linux 输入法驱动: 77 | 78 | * [src/platform/windows/windows\_ime.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/windows/windows_ime.c#L90) 79 | * [src/platform/linux/linux\_ime.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/platform/linux/linux_ime.c#L83) 80 | 81 | ### 待办事项 82 | 83 | **重构输入法引擎** 84 | 85 | LCUI 的输入法引擎在设计之初由于可参考的相关资料很少,对输入法的功能需求也不明确,因此只以“能够接收文本输入”为目的做了简单设计,其中一些 API 是参考了 WIndows 输入法相关的文档而设计的。如果你有输入法开发经验,可以帮助我们改进输入法支持模块。 86 | 87 | **将 proc\(\) 的第二个参数改为布尔类型** 88 | 89 | 按键的状态有按下和释放,将其改为布尔类型的 `isPressed` 比判断 `state == LCUI_KSTATE_PRESSED` 更简单。 90 | 91 | **改进 Linux 输入法引擎** 92 | 93 | 现有的 Linux 输入法引擎过于简单,支持输入英文字母和符号。 94 | 95 | -------------------------------------------------------------------------------- /kuai-su-shang-shou/an-zhuang.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: LCUI 的安装方法以及版本更新相关说明。 3 | --- 4 | 5 | # 安装 6 | 7 | #### 语义化版本控制 8 | 9 | LCUI 在其所有项目中公布的功能和行为都遵循[语义化版本控制](https://semver.org/lang/zh-CN/)。对于未公布的或内部暴露的行为,其变更会描述在[发布说明](https://github.com/lc-soft/LCUI/releases)中。 10 | 11 | #### 更新日志 12 | 13 | 最新稳定版本:2.1.0 14 | 15 | 每个版本的更新日志见 [GitHub](https://github.com/lc-soft/LCUI/releases)。 16 | 17 | ### 直接用已编译好的成品 18 | 19 | 通常新版本的[发布说明](https://github.com/lc-soft/LCUI/releases)中都会附带已编译的二进制文件包,包括适用于 Ubuntu 系统的 deb 安装包和适用于 Windows 系统的 zip 包,你可以根据自己的需求下载,然后配置编译器的头文件和库文件的搜索路径以及链接器参数。 20 | 21 | ### LCPkg 包管理器 22 | 23 | LCPkg 是一个用于管理 C/C++ 项目依赖的命令行工具,目前仅适合在 Windows 系统上使用,使用它你可以很方便的下载 LCUI 的二进制文件包。 24 | 25 | ```bash 26 | # 最新稳定版 27 | lcpkg install github.com/lc-soft/LCUI 28 | ``` 29 | 30 | ### 命令行工具 \(CLI\) 31 | 32 | LCUI 提供了一个[官方的 CLI](https://github.com/lc-ui/lcui-cli),为 LCUI 应用快速搭建繁杂的脚手架。更多详情可查阅 [LCUI CLI 的文档](https://github.com/lc-ui/lcui-cli)。 33 | 34 | ```bash 35 | # 安装命令行工具 36 | npm install -g @lcui/cli 37 | 38 | # 创建项目 39 | lcui create my-lcui-app 40 | 41 | # 进入项目目录 42 | cd my-lcui-app 43 | 44 | # 准备开发环境 45 | lcui bootstrap 46 | 47 | # 构建项目 48 | lcui build 49 | 50 | # 运行 51 | lcui run 52 | ``` 53 | 54 | {% hint style="warning" %} 55 | CLI 工具假定用户对 Node.js 和相关构建工具有一定程度的了解。如果你是新手,我们强烈建议先在不用构建工具的情况下通读指南,在熟悉 LCUI 本身之后再使用 CLI。 56 | {% endhint %} 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /kuai-su-shang-shou/css-yu-fa.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | -------------------------------------------------------------------------------- /kuai-su-shang-shou/ming-ming-yue-ding.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍 LCUI 的命名风格和推荐的 LCUI 应用项目的代码组织方式。 3 | --- 4 | 5 | # 约定 6 | 7 | ### 命名约定 8 | 9 | 在正式开始使用 LCUI 前,我们先了解一下 LCUI 的命名约定,这将有助于记忆和查找你需要的 API。 10 | 11 | #### 数据类型 12 | 13 | 大部分公共的数据类型都采用驼峰式命名法(Camel-Case),并带有 `LCUI_` 前缀,像链表(LinkedList)、红黑树(RBTree) 和字典(Dict)这类基础数据类型,由于名称长度和可替代性,未加上 `LCUI_` 前缀。 14 | 15 | 对于常以指针形态引用的数据类型,它的定义会是这样: 16 | 17 | ```c 18 | typedef struct LCUI_FooRec_* LCUI_Foo; 19 | 20 | typedef struct LCUI_FooRec_ 21 | { 22 | /* fields for the 'foo' class */ 23 | ... 24 | 25 | } LCUI_FooRec; 26 | ``` 27 | 28 | 这种写法参考自 FreeType,如需了解更多,可参考它的设计文档:《[FreeType Design](https://www.freetype.org/freetype2/docs/design/design-3.html#section-1)》 29 | 30 | #### 函数 31 | 32 | 由于 LCUI 在开发初期并未确定最佳的命名风格,受到维护人员的不稳定的编程习惯以及其它开源项目的影响,每当引入新功能的代码时候都有可能采用其它其它命名风格,因此,你会发现现在的 LCUI 源码中存在多种命名风格: 33 | 34 | * **面向对象 + 驼峰风格:** 使用与类型名同名的函数作为构造函数,该对象的所有操作函数都以类型名开头,并以下划线分隔,对象的析构函数名称都是 Delete。 35 | 36 | ```c 37 | LCUI_StyleSheet sheet1 = StyleSheet(); 38 | LCUI_StyleSheet sheet2 = StyleSheet(); 39 | StyleSheet_Merge(sheet1, sheet2); 40 | StyleSheet_Delete(sheet2); 41 | ``` 42 | 43 | * `LCUI_` **前缀 + 驼峰风格:** 依赖 LCUI 核心功能的公共函数大都采用此风格,有的函数为避免命名冲突也会加上 `LCUI_` 前缀。 44 | 45 | ```c 46 | LCUI_Init(): 47 | LCUI_SetTimeout(); 48 | LCUI_Quit(); 49 | LCUI_Main(); 50 | LCUI_EncodeString(); 51 | ``` 52 | 53 | * **驼峰风格:** 函数以操作对象的类型名开头,并以下划线分隔对象类型名与方法名。如果函数操作的对象是采用单例模式的全局共享对象,通常还会加上 LCUI 前缀。 54 | 55 | ```c 56 | Widget_Append(); 57 | Dict_FetchValue(); 58 | LinkedList_Append(); 59 | LCUIFont_RenderBitmap(); 60 | LCUIFont_LoadFile(); 61 | ``` 62 | 63 | * **全小写风格:**与标准库函数的命名风格类似。 64 | 65 | ```c 66 | strhash(); 67 | strsplit(); 68 | ``` 69 | 70 | ### 推荐的目录结构 71 | 72 | * **app/:** 应用的工作目录,用于存放运行时所需的资源文件。 73 | * **build/:** 构建工具的工作目录,包含一些配置文件和构建产物。 74 | * **config/:** 配置文件目录。 75 | * **include/:** 头文件目录。 76 | * **src/:** 源码目录。 77 | * **lib/:** 基础库目录,包含应用的核心功能源码。 78 | * **ui/:** 用户界面目录。 79 | * **components/:** 组件目录,包含能够在用户界面中复用的基础组件。 80 | * **effects/:** 效果目录,包含一些动效、手势、拖拽等界面交互效果的实现源码。 81 | * **views/:** 视图目录,包含能够完成特定功能的完整界面。 82 | * **app.c:** 应用的入口。 83 | * **ui.c:** 用户界面的入口。 84 | * **vendor/:** 第三方库目录,包含第三方库的头文件和库文件。 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /kuai-su-shang-shou/xml-yu-fa.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: XML 的写法和常用元素的介绍。 3 | --- 4 | 5 | # XML 6 | 7 | XML 是一种可扩展的标记语言,LCUI 之所以采用 XML 而不是 HTML,主要有以下理由: 8 | 9 | * XML 比 HTML 更简单。 10 | * LCUI 不是浏览器,不打算实现 HTML 中的所有标签的功能,因为这是既浪费时间又没有意义的事情。 11 | * 使用 HTML 会让用户以开发网页的思维方式去编写 LCUI 应用,然后误以为 ``、``、``、``、``、`` 等标签在 LCUI 中会有效果。 12 | 13 | 在开始前,我们假定你已经熟悉 HTML 或 XML 这类标记语言的语法,本文将跳过基本语法和相关术语的介绍,直接讲解 LCUI 的 XML 文档写法和常用元素的用法,如需了解请查阅相关文档。 14 | 15 | 在前面的章节中我们已经了解到 LCUI 的 XML 文档内容格式和预定义元素的用法: 16 | 17 | ```markup 18 | 19 | 20 | 21 | 22 | 23 | Hello, World! 24 | 25 | 26 | 27 | ``` 28 | 29 | 第一行声明文档的类型,第二行的`` 声明了它包裹的内容适用于 LCUI 应用,第三行的 `` 包裹了整个用户界面的结构及其所有组件的信息。 30 | 31 | ### 常用元素 32 | 33 | #### <resource> 34 | 35 | 声明资源信息,可用于加载资源文件。 36 | 37 | 38 | 39 | 40 | 属性 41 | 说明 42 | 43 | 44 | 45 | 46 | type 47 | 48 | 资源类型,可选值有: 49 | 50 | 51 | application/font-ttf :作为 ttf 字体文件加载 52 | text/css :作为 css 文本文件加载 53 | text/xml :作为 xml 文档加载 54 | 55 | 当值为 text/xml 时,效果相当于将目标 56 | xml 文档的内容插入到 <resource> 所在位置。 57 | 58 | 59 | 60 | src 61 | 资源文件的来源路径 62 | 63 | 64 | 65 | 66 | #### <widget> 67 | 68 | 声明组件,仅限在 `` 内使用。 69 | 70 | | 属性 | 说明 | 71 | | :--- | :--- | 72 | | type | 组件的类型名称,需要是组件原型库中已注册的名称 | 73 | | id | 唯一标识符 | 74 | | class | 类名称 | 75 | 76 | 在 `` 中,如果元素的标签名不是预定义的,则会视为 `` 元素,因此,你可以使用组件类型名作为标签名,例如以下两行元素是等效的: 77 | 78 | ```markup 79 | hello 80 | hello 81 | ``` 82 | 83 | ### API 84 | 85 | LCUI 提供的 XML 文档相关的函数有两个: 86 | 87 | ```c 88 | LCUI_Widget LCUIBuilder_LoadString(const char *str, int size); 89 | 90 | LCUI_Widget LCUIBuilder_LoadFile(const char *filepath); 91 | ``` 92 | 93 | 从函数原型可以知道,这两个函数分别用于从字符串和文件中加载 XML 文档内容,它们的返回值都是一个根组件,这个根组件只是充当包含了所有组件的容器,真正有用的是它里面组件,因此我们需要使用 `Widget_Unwrap()` 函数展开该容器组件,将它里面的组件暴露到外面。 94 | 95 | -------------------------------------------------------------------------------- /langs.md: -------------------------------------------------------------------------------- 1 | # LANGS 2 | 3 | * [简体中文](zh-cn/) 4 | * [English](en-us/) 5 | 6 | -------------------------------------------------------------------------------- /layout.md: -------------------------------------------------------------------------------- 1 | # 布局 2 | 3 | ### 待办事项 4 | 5 | **重新设计布局引擎** 6 | 7 | 现有的布局引擎是与 LCUI 的组件系统绑定在一起的,而且布局规则的处理流程也比较复杂,为了降低维护成本,简化布局流程,我们应该重新设计布局引擎,使它能够作为一个独立的项目被用于其他项目。可供参考的同类案例有:[https://github.com/facebook/yoga](https://github.com/facebook/yoga) 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /layout/README.md: -------------------------------------------------------------------------------- 1 | # 布局 2 | 3 | 布局模式,有时简称为布局,是一种基于盒子与其兄弟和祖辈盒子的交互方式来确定盒子的位置和大小的算法。在 LCUI 中参与布局计算的数据包括 display 属性、定位属性、几何属性、盒模型、尺寸规则、布局规则等,这些数据的用途大致如下: 4 | 5 | * 在布局开始前,组件的 display 属性、定位属性和几何属性会被用于计算盒模型和尺寸规则。 6 | * 在布局开始时,组件的 display 属性、定位属性和尺寸规则用于选择合适的布局规则。 7 | * 在布局时,布局算法会根据组件的盒模型计算其兄弟和祖辈组件位置和尺寸。 8 | 9 | 出于学习和开发成本上的考虑,LCUI 的布局引擎以网页浏览器为参考对象,实现了 CSS 布局中常见的几种布局模式,在大多数情况下,同一种布局模式在 LCUI 应用程序和网页浏览器中的效果是一样的,因此,你也可以通过学习 CSS 布局相关文档来加深对布局的理解。 10 | 11 | {% hint style="info" %} 12 | 本文假定你已经熟悉 CSS 布局技术,如果你对 CSS 布局技术还不熟悉,我们建议你阅读 MDN 上的文档:《[CSS 布局 - 学习 Web 开发 \| MDN](https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout)》 13 | {% endhint %} 14 | 15 | 在介绍布局模式之前,我们先深入了解一下这些数据的概念和实现细节。 16 | 17 | ### 盒模型 18 | 19 | 盒模型用于定义组件的布局和渲染参数,它由以下几个部分组成: 20 | 21 | * **Content box**: 这个区域是用来显示内容,大小可通过 `width` 和 `height` 属性设置。 22 | * **Padding box**: 包围在内容区域外部的空白区域,大小通过 `padding` 相关属性设置。 23 | * **Border box**: 边框盒包裹内容和内边距,大小通过 `border` 相关属性设置。 24 | * **Canvas box:** 画布区域包裹了边框盒,与外边距区域重叠,它定义了组件在渲染时所使用的画布的大小,组件的阴影参数会影响它的大小,如果组件没有阴影,则它的大小与边框盒相同。 25 | * **Margin box**: 这是最外面的区域,是盒子和其他元素之间的空白区域。大小通过 `margin` 相关属性设置。 26 | 27 | 如下图所示: 28 | 29 |  30 | 31 | 你可以通过组件对象中的 box 属性来访问这些区域,例如: 32 | 33 | ```c 34 | printf( 35 | "content_box: (%f, %f, %f, %f)\n", 36 | w->box->content.x, w->box->content.y, 37 | w->box->content.width, w->box->content.height, 38 | ); 39 | printf( 40 | "border_box: (%f, %f, %f, %f)\n", 41 | w->box->border.x, w->box->border.y, 42 | w->box->border.width, w->box->border.height, 43 | ); 44 | 45 | ``` 46 | 47 | ### Display 属性 48 | 49 | 实现页面布局的主要方法是设定`display`属性的值,它允许我们更改默认的显示方式。LCUI 目前支持 `display` 属性的三种值: `block`、`inline-block` 、`flex`,该属性的默认值是 `block`。 50 | 51 | ### 定位 52 | 53 | 定位 \(positioning\) 能够让我们把一个组件从它原本在布局中应该在的位置移动到另一个位置。定位 \(positioning\) 并不是一种用来给你做主要界面布局的方式,它更像是让你去管理和微调界面中的一个特殊项的位置。 54 | 55 | LCUI 仅支持以下三种定位: 56 | 57 | * **静态定位(Static positioning):**默认定位,表示使用布局引擎计算好的位置。 58 | * **相对定位(Relative positioning):**相对定位,它允许我们相对于布局引擎计算好的位置来移动组件,这对于微调和精准设计 \(design pinpointing\) 非常有用。 59 | * **绝对定位(Absolute positioning):**将组件从布局中移出,不占据空间,通过指定组件相对于父组件的偏移来确定位置。该定位适用于精确控制组件位置,例如:让组件停靠在右上角、或是让组件随着鼠标移动。 60 | 61 | {% hint style="warning" %} 62 | 注意,绝对定位在浏览器中是相对于元素的最近被定位祖先元素 \(nearest positioned ancestor element\),而在 LCUI 中是直属父组件。 63 | {% endhint %} 64 | 65 | ### 尺寸规则 66 | 67 | 尺寸规则影响到组件的布局规则和布局后的实际尺寸,也会影响到子组件的尺寸规则,它在组件的样式计算和布局阶段会被重新计算。以下是这些规则的作用说明: 68 | 69 | * **FIXED:**固定。表示尺寸属性可在布局前直接计算出确切的值,除了设置 100px 这类值外,父组件的尺寸规则是固定时也会采用该规则,因为在父组件尺寸已知的情况下,即便子组件的尺寸是百分比值或 auto 也能直接计算出来。 70 | * **FILL:**填充。组件的尺寸将填满父组件内容区域。大多数情况下该规则都会转换为固定,因为根组件的尺寸规则必定是固定。 71 | * **PERCENT:**百分比。表示在计算尺寸属性的值之前必须先计算出父组件尺寸的实际值,然后再按照百分比计算实际值。在父组件尺寸规则不为固定且尺寸属性为 50% 这类值时,会采用百分比规则。 72 | * **FIT\_CONTENT:**适应内容。表示在布局完后使用内容区域的尺寸作为实际值。该规则常用于组件的高度,因为在大多数情况下宽度是固定的,由内容撑开高度。当组件采用绝对定位或者显示方式为内联块(inline-block)时,它的宽高都会采用该规则。 73 | 74 | ### 布局规则 75 | 76 | 布局规则在布局前由组件的定位方式、尺寸规则和初始布局规则计算而来,它告诉布局引擎在布局前如何确定最大内容宽度、在布局后如何计算组件实际尺寸。以下是这些规则的作用说明: 77 | 78 | * **AUTO**:自动。这是初始规则,由布局引擎根据组件的显示方式和尺寸规则来选择合适的布局规则。 79 | * **MAX\_CONTENT**:最大内容。在布局时尽可能扩大尺寸以呈现更多的内容。当尺寸规则都不为固定时会采用此布局规则。 80 | * **FIXED\_WIDTH:**固定宽度。在布局时将组件内容区域宽度作为最大宽度,排列的每行子组件都不会超出该宽度。当宽度的尺寸规则是固定时会采用该规则。 81 | * **FIXED\_HEIGHT**:固定高度。与固定宽度相似,但它在正常流布局下不会影响子组件的排列位置。 82 | * **FIXED**:固定。当组件的宽高属性的尺寸规则都是固定时采用该规则。 83 | 84 | ### 最大内容尺寸 85 | 86 | 最大内容尺寸是组件在空间无限的情况下可以容纳全部内容并避免内容溢出的最小尺寸。它在组件主动布局时重新计算,主要用于在弹性盒子布局中为组件提供初始尺寸。 87 | 88 | ### 主动与被动布局 89 | 90 | 因组件自身样式变化而触发的布局是主动布局。在主动布局过程中,如果子组件的尺寸因此发生变化而触发的布局就是被动布局。这两种布局的区别是,主动布局有选择布局规则和更新最大内容尺寸的权力,而被动布局没有,只能基于父组件的布局规则进行布局。 91 | 92 | 区分布局的主动和被动是为了解决父子组件宽高都不确定时的尺寸计算问题。以下拉菜单为例,下拉菜单的宽高是自适应的,里面的每个菜单项的宽度都是相同的,且正好能容纳全部内容而不换行或溢出,那么为了计算它们的尺寸,我们需要先计算每个菜单项的尺寸,根据它们的尺寸进行简单的布局来得出菜单的内容尺寸,然后再对每个菜单项布局一次,让它们根据菜单的内容尺寸计算出自己的实际尺寸。大致布局流程如下: 93 | 94 | * 触发每个菜单项的被动布局,初始布局规则为 `MAX_CONTENT`。 95 | * 利用菜单项的尺寸进行布局,得到菜单的内容尺寸。 96 | * 再次触发每个菜单项的被动布局,初始布局规则为 `FIXED`。 97 | * 布局完成,此时每个菜单项的宽度都等于最大菜单项的宽度,菜单的内容宽度等于菜单项的宽度,菜单的内容高度等于全部菜单项的高度之和。 98 | 99 | ### 布局流程 100 | 101 | 在 LCUI 当前的布局引擎设计中,正常流布局和弹性盒子布局的流程是相似的,都需要经历以下几个阶段: 102 | 103 | * **开始:**创建布局上下文,初始化布局所需的一些数据。 104 | * **载入:**遍历子组件,根据子组件的盒模型对其进行分组,每组子组件即为一行。载入完后根据每行的宽高来估算内容区域的宽高。 105 | * **应用尺寸:**根据布局规则为组件设置合适的尺寸。 106 | * **布局:**遍历子组件,为子组件的宽高属性计算实际值。这时如果子组件的宽高因此发生变化的话会对子组件进行布局。 107 | * **布局自由组件:**计算绝对定位的子组件的位置和宽高。对于绝对定位的子组件,它们的位置和宽高常常依赖父组件的宽高,需要等到这个阶段再计算实际值。 108 | * **结束:**销毁布局上下文。 109 | 110 | ### 待办事项 111 | 112 | **重新设计布局引擎** 113 | 114 | 现有的布局引擎是与 LCUI 的组件系统绑定在一起的,而且布局规则的处理流程也比较复杂,为了降低维护成本,简化布局流程,我们应该重新设计布局引擎,使它能够作为一个独立的项目被用于其他项目。可供参考的同类案例有:[https://github.com/facebook/yoga](https://github.com/facebook/yoga) 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /layout/box-model.md: -------------------------------------------------------------------------------- 1 | # 盒模型 2 | 3 | 盒模型用于定义组件的布局和渲染参数,它由以下几个部分组成: 4 | 5 | * **Content box**: 这个区域是用来显示内容,大小可通过 `width` 和 `height` 属性设置。 6 | * **Padding box**: 包围在内容区域外部的空白区域,大小通过 `padding` 相关属性设置。 7 | * **Border box**: 边框盒包裹内容和内边距,大小通过 `border` 相关属性设置。 8 | * **Canvas box:** 画布区域包裹了边框盒,与外边距区域重叠,它定义了组件在渲染时所使用的画布的大小,组件的阴影参数会影响它的大小,如果组件没有阴影,则它的大小与边框盒相同。 9 | * **Margin box**: 这是最外面的区域,是盒子和其他元素之间的空白区域。大小通过 `margin` 相关属性设置。 10 | 11 | 如下图所示: 12 | 13 |  14 | 15 | 你可以通过组件对象中的 box 属性来访问这些区域,例如: 16 | 17 | ```c 18 | printf( 19 | "content_box: (%f, %f, %f, %f)\n", 20 | w->box->content.x, w->box->content.y, 21 | w->box->content.width, w->box->content.height, 22 | ); 23 | printf( 24 | "border_box: (%f, %f, %f, %f)\n", 25 | w->box->border.x, w->box->border.y, 26 | w->box->border.width, w->box->border.height, 27 | ); 28 | 29 | ``` 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /layout/flexbox.md: -------------------------------------------------------------------------------- 1 | # 弹性盒子布局 2 | 3 | 弹性盒子(Flexible Box)是一种用于按行或按列布局元素的一维布局方法,使用该布局的组件即为弹性容器(flex container),容器内的直系子组件则是弹性项目(flex item),他们可以膨胀以填充额外的空间,收缩以适应更小的空间,它们在 CSS 属性都为初始的情况下都会有下列行为: 4 | 5 | * 项目排列成一行。(容器的 `flex-direction` 属性的初始值是 `row`) 6 | * 项目从主轴的起始线开始。(容器的 `justify-content` 属性的初始值是 `flex-start`) 7 | * 项目不会在主轴维度方向拉伸,但可以缩小。(项目的 `flex-grow` 和 `flex-shirnk` 属性的初始值分别是 `0` 和 `1`) 8 | * 元素被拉升来填充交叉轴大小。(容器的 `align-items` 属性初始值是 `stretch`) 9 | 10 | 该布局中存在主轴(main axis)和交叉轴(cross axis),主轴由 `flex-direction` 定义,交叉轴垂直于主轴,容器内的项目沿着主轴排列,当主轴排满项目后,如果 `flex-wrap` 值为 `wrap` 则会沿着交叉轴追加新的主轴并在新主轴上继续排列。 11 | 12 | {% hint style="info" %} 13 | 如需了解更多关于弹性盒子布局的概念和用法,可参考 MDN 上的文档:《[flex 布局的基本概念 - CSS(层叠样式表) \| MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)》 14 | {% endhint %} 15 | 16 | `test/test_flex_layout.c` 展示了常见布局的实现方法,效果与下面的在线示例一致,你也可以编译并运行它来体验实际效果。 17 | 18 | {% embed url="https://codepen.io/lc-soft/pen/XWNObPw" %} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /layout/normal-flow-layout.md: -------------------------------------------------------------------------------- 1 | # 正常流布局 2 | 3 | 正常流布局的规则是将组件从左到右放置,当一行的空间被占满后则会换到下一行继续放置剩下的组件。默认情况下,一个块级组件的宽度是其父组件的100%,其高度与其内容高度一致,而内联块级组件的宽高则与内容一致。 4 | 5 | 与网页布局的差异: 6 | 7 | * **没有 inline 显示方式:**LCUI 的布局引擎在设计之初就没有考虑支持基于文本的布局,因为实现比较复杂,需要耗费较多的时间和精力去开发和维护,而且当时并没有复杂的文本排版需求。 8 | * **没有 float 属性:**和 inline 一样,在布局引擎开发之初就没有将它考虑在内。 9 | * **没有 margin 重叠特性:**如果相邻的组件都设置了 margin,那么它们的距离是两者 margin 之和,而不是取最大的一个。 10 | 11 | `test/test_block_layout.c` 展示了常见布局的实现方法,效果与下面的在线示例一致,你也可以编译并运行它来体验实际效果。 12 | 13 | {% embed url="https://codepen.io/lc-soft/pen/mdOvyzb" %} 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /layout/position.md: -------------------------------------------------------------------------------- 1 | # 定位 2 | 3 | 定位允许你从正常流布局中取出组件,并使它们具有不同的行为,例如放在另一个组件上面,或者始终保持在容器内的同一位置。 4 | 5 | LCUI 中的定位效果与网页中的区别如下: 6 | 7 | * 仅支持静态定位、相对定位和绝对定位,即 position 属性的值只支持 static、relative 和 absolute。 8 | * 定位上下文是直属父组件,而不是非静态定位的父组件。 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /qu-dong/README.md: -------------------------------------------------------------------------------- 1 | # 驱动 2 | 3 | -------------------------------------------------------------------------------- /qu-dong/chu-kong.md: -------------------------------------------------------------------------------- 1 | # 触控 2 | 3 | -------------------------------------------------------------------------------- /qu-dong/jian-pan.md: -------------------------------------------------------------------------------- 1 | # 键盘 2 | 3 | -------------------------------------------------------------------------------- /qu-dong/shi-jian-xun-huan.md: -------------------------------------------------------------------------------- 1 | # 事件循环 2 | 3 | -------------------------------------------------------------------------------- /qu-dong/shi-pin.md: -------------------------------------------------------------------------------- 1 | # 视频 2 | 3 | -------------------------------------------------------------------------------- /qu-dong/shu-biao.md: -------------------------------------------------------------------------------- 1 | # 鼠标 2 | 3 | -------------------------------------------------------------------------------- /rendering.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍 UI 的渲染流程和渲染性能优化方法。 3 | --- 4 | 5 | # 性能 6 | 7 | 用户希望他们使用的图形界面具有交互性和流畅性,而这正是你需要越来越多地集中时间和精力的地方。 界面不仅要加载快,还要运行良好, 滚动应该很快,动画和交互应该如丝般流畅。 8 | 9 | 要编写高性能应用程序,你需要了解 LCUI 如何渲染界面,并确保你编写的代码以及第三方代码尽可能高效地运行。 10 | 11 | ### 像素管道 12 | 13 | “渲染”就是将组件数据转变为像素数据,这个转变如同一条包含很多区域的单向管道,组件数据经过管道中的每个区域的处理最终变成像素数据。我们可以将这个管道称为像素管道,它的结构如下图所示: 14 | 15 |  16 | 17 | * **事件:**界面的更新是由事件驱动的,通常我们会在事件处理器中实现一些操作和视觉变化的效果。比如显示一个加载中动画、切换到另一个界面、或者往界面里添加一些内容等。 18 | * **样式计算:**此过程是根据匹配选择器(例如 `.button` 或 `.list .list-item`)计算出哪些组件应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。 19 | * **布局:** 在知道对一个组件应用哪些规则之后,LCUI 即可开始计算它要占据的空间大小及其在屏幕的位置。LCUI 所采用的类似于网页的布局模式意味着一个组件可能影响其他组件,例如更改组件的宽度会影响到子组件的位置和宽度以及组件树中各处的节点,因此布局过程是经常发生的。 20 | * **绘制:**绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。 21 | * **合成:**由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染界面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。 22 | 23 | 管道的每个部分都有机会产生卡顿,因此务必准确了解你的代码触发管道的哪些部分。 24 | 25 | 不一定每帧都总是会经过管道每个部分的处理。在实现视觉变化时,管道针对指定帧的运行通常有三种方式: 26 | 27 | **1. 事件 > 样式 > 布局 > 绘制 > 合成** 28 | 29 |  30 | 31 | 如果你修改了组件的布局属性,即改变了组件的几何属性(例如宽度、高度、左侧或顶部位置等),那么 LCUI 将必须检查所有其它组件,然后对界面进行重新布局。任何受影响的部分都需要重新绘制,而且最终绘制的元素需进行合成。 32 | 33 | **2. 事件 > 样式 > 绘制 > 合成** 34 | 35 |  36 | 37 | 如果你修改了组件的绘制属性,即不会影响界面布局的属性(例如背景图片、文字颜色或阴影等),则 LCUI 会跳过布局,但仍将执行绘制。 38 | 39 | **3. 事件 > 样式** 40 | 41 |  42 | 43 | 如果您更改组件的一个既不要布局也不要绘制的属性(例如:pointer-events),则 LCUI 将在计算完样式后跳过剩下的过程。 44 | 45 | 接下来,让我们深入了解此管道的各个不同部分。我们会以一些常见问题为例,阐述如何诊断和修正它们。 46 | 47 | ### 事件 48 | 49 | 事件处理器经常会触发视觉变化。有时是直接通过样式操作,有时是会产生视觉变化的计算,例如搜索数据或将其排序。时机不当或长时间运行的代码可能是导致性能问题的常见原因。您应当设法尽可能减少其影响。 50 | 51 | 在许多情况下,你可以将事件处理器中的耗时长的代码从主线程移动到工作线程,详见[工作线程](app/worker.md)章节。不过在工作线程上你必须确保这些代码不会操作 UI 相关资源, 如果你的工作必须在主线程上执行,请考虑一种分批处理的方法,将大任务分割为小任务,每个小任务所占时间不超过几毫秒,然后使用定时器逐个执行这些任务。 52 | 53 | ### 样式计算 54 | 55 | 通过添加和删除组件,更改属性、类来更改组件,都会导致 LCUI 重新计算组件样式,在很多情况下还会对整个界面或界面的一部分进行布局。 56 | 57 | 计算样式的第一部分是创建一组匹配选择器,也就是计算出给指定元素应用哪些类、伪选择器和 ID。第二部分涉及从匹配选择器中获取所有样式规则,并计算出此元素的最终样式。 58 | 59 | 概要: 60 | 61 | * 降低选择器的复杂度;使用 [BEM](http://getbem.com/introduction/) 这种以类为中心的方法论。 62 | * 为数量多且样式相同的组件预先生成样式哈希值,以减少重复的样式匹配。 63 | * 为含有大量子组件的容器组件设置更新规则,告诉 LCUI 是否需要缓存样式表、是否仅更新可见的子组件、哪些变动可以忽略等。 64 | * 减少需要计算其样式的组件数量。 65 | 66 | ### 布局 67 | 68 | 布局是计算各组件几何信息的过程:组件的大小以及在界面中的位置。根据所用的 CSS、组件的内容或父级组件,每个组件都将有显式或隐含的大小信息。 69 | 70 | 与样式计算相似,布局开销的直接考虑因素如下: 71 | 72 | 1. 需要布局的元素数量。 73 | 2. 这些布局的复杂度。 74 | 75 | 概要: 76 | 77 | * 组件的数量将影响性能;应尽可能避免触发重新布局。 78 | * 评估布局模型的性能;弹性盒子(Flexbox)一般比块(Block)布局模型更慢。 79 | * 宽高为固定值的组件有着较低的重新布局成本;这种组件在重新布局时无需再遍历子组件树来计算内容宽高,而且能减少因其父组件和子组件的几何属性变化而触发的重新布局次数。 80 | 81 | ### 绘制 82 | 83 | 绘制是填充像素的过程,像素最终合成到用户的屏幕上。 它往往是管道中运行时间最长的任务,应尽可能避免此任务。 84 | 85 | 概要: 86 | 87 | * 更改任何属性始都会触发绘制。 88 | * 绘制通常是像素管道中开销最大的部分;应尽可能避免绘制。 89 | * 大量且尺寸小的绘制区域会降低绘制性能,但好在 LCUI 已经针对这种情况做了区域合并和多个区域并行绘制等优化,大部分情况下你不用考虑这个问题。 90 | * 设置 opacity 和 box-shadow 属性会触发使用独立渲染层,组件及其子组件都被在该渲染层中绘制,在全部绘制完后,该渲染层的内容才会被合成到目标面上。 91 | 92 | ### 合成 93 | 94 | 由于 LCUI 还未引入动画和变换系统,我们先暂时跳过这方面的讲解。 95 | 96 | ### 参考资料 97 | 98 | 本文的内容结构和表达方式参考自《[渲染性能 \| Web \| Google Developer](https://developers.google.cn/web/fundamentals/performance/rendering)》。 99 | 100 | ### 待办事项 101 | 102 | **添加** `LCUI_RequestAnimationFrame()` **函数** 103 | 104 | 参考 [window.requestAnimationFrame](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame) 的设计。 105 | 106 | **添加渲染性能监视器** 107 | 108 | 详见 [\#192](https://github.com/lc-soft/LCUI/issues/192) 中的内容。 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /shi-li/ding-shi-qi.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 定时器的概念及用法介绍。 3 | --- 4 | 5 | # 定时器 6 | 7 | LCUI 的定时器都是在主线程中处理的,这意味着定时器的时间粒度受到帧率的限制,不能小于每帧的停留时间。举个例子:当前帧率为 120 帧每秒,那么时间粒度就是 8.33 毫秒,如果你设置定时器的等待时间是 20 毫秒,那么实际的等待时间会大于等于 25 毫秒,也就在设置定时器后的第三帧时处理这个定时器。这种精确度的定时器能够应付大多数场景,如果你需要更加精确的定时器,我们建议你选择其它定时器库,或自行编码实现。 8 | 9 | ### 简单的例子 10 | 11 | 这个例子展示了如何设置和释放定时器: 12 | 13 | ```c 14 | #include 15 | #include 16 | 17 | void OnTimeout(void *arg) 18 | { 19 | int *timer_id = arg; 20 | 21 | LCUITimer_Free(*timer_id); 22 | LCUI_Quit(); 23 | printf("timeout\n"); 24 | } 25 | 26 | void OnInterval(void *arg) 27 | { 28 | printf("interval\n"); 29 | } 30 | 31 | int main(void) 32 | { 33 | int timer_id; 34 | 35 | LCUI_Init(); 36 | timer_id = LCUI_SetInterval(1000, OnInterval, 0); 37 | LCUI_SetTimeout(5000, OnTimeout, &timer_id); 38 | return LCUI_Main(); 39 | } 40 | ``` 41 | 42 | 首先调用 `LCUI_SetInterval()` 设置定时器每隔 1 秒调用一次 `OnInterval()` 函数,并将它返回的定时器标识号赋值给 `timer_id` ,然后调用 `LCUI_SetTimeout()` 设置一个定时器在 5 秒后调用 `OnTimeout()` 函数,并将 `timer_id` 的引用作为参数传给 `OnTimeout()` 函数。 43 | 44 | ### 待办事项 45 | 46 | **重新设计定时器的接口** 47 | 48 | 改用面向对象模式代替单例模式,示例: 49 | 50 | ```c 51 | // 当前的接口设计 52 | LCUI_InitTimer(); 53 | LCUI_FreeTimer(); 54 | LCUITimer_Set(); 55 | LCUITimer_Free(); 56 | LCUI_ProcessTimers(); 57 | 58 | // 新的接口设计 59 | TimerList_New(); 60 | TimerList_Free(); 61 | TimerList_Add(); 62 | TimerList_Remove(); 63 | TimerList_Process(); 64 | ``` 65 | 66 | 在新的设计中,不再是全局共用同一个定时器列表,允许开发者基于定时器接口实现一个定时器线程,以此摆脱 LCUI 主循环对定时器精度的影响。 67 | 68 | -------------------------------------------------------------------------------- /shi-li/gong-zuo-xian-cheng.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 工作线程的概念、用途以及相关函数的介绍。 3 | --- 4 | 5 | # 工作线程 6 | 7 | 主循环执行频率影响界面的流畅度,在主循环的章节中我们已经了解到每一次循环都会按顺序执行处理定时器、处理事件队列、更新组件、渲染组件等任务,其中最容易影响到主循环的执行频率的任务是处理事件队列,因为大部分的事件处理器都是应用程序主动绑定的,对于缺乏性能优化意识的新手而言,可能会在事件处理器中直接进行一些耗时较高的操作,从而导致界面在操作结束前一直处于未响应状态。 8 | 9 | 解决这一问题的常见做法是将操作移动到另一个线程上执行,我们可以用 LCUI 提供的工作线程池来实现: 10 | 11 | ```c 12 | void DoSomeThing(void *arg1, void *arg2) 13 | { 14 | printf("key: %s\n", arg1); 15 | printf("value: %s\n", arg2); 16 | } 17 | 18 | void OnButtonClick() 19 | { 20 | LCUI_TaskRec task = { 0 }; 21 | 22 | LCUI_Init(); 23 | task.arg[0] = strdup("color"); 24 | task.arg[1] = strdup("red"); 25 | task.destroy_arg[0] = free; 26 | task.destroy_arg[1] = free; 27 | task.func = DoSomeThing; 28 | 29 | LCUI_PostAsyncTask(&task); 30 | } 31 | ``` 32 | 33 | `LCUITaskRec` 类型的 task 变量描述了任务的执行函数及其参数,`arg` 成员变量记录了参数列表,`destroy_arg` 则是这些参数的销毁函数,这里我们用了 `strdup()` 分配了新的内存存储字符串,并指定 `free()` 作为参数的销毁函数。准备完任务后,调用 `LCUI_PostAsyncTask()` 函数将任务添加到工作线程的任务队列中等待执行。 34 | 35 | LCUI 的工作线程池中默认创建了 4 个工作线程,为了让这些工作线程都有任务执行,`LCUI_PostAsyncTask()` 函数会在每次投递完任务后将目标切换到下一个工作线程,如果这种简单的任务分配策略不符合你的需求,你也可以基于 [src/worker.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/worker.c) 提供的函数创建自己的工作线程池: 36 | 37 | ```c 38 | // 创建一个带有任务队列的工作线程,然后运行它 39 | LCUI_Worker worker = LCUIWorker_New(); 40 | LCUIWorker_RunAsync(worker); 41 | 42 | ... 43 | 44 | // 给工作线程投递任务 45 | LCUI_TaskRec task = { 0 }; 46 | 47 | task.arg[0] = strdup("color"); 48 | task.arg[1] = strdup("red"); 49 | task.destroy_arg[0] = free; 50 | task.destroy_arg[1] = free; 51 | task.func = DoSomeThing; 52 | LCUIWorker_PostTask(worker, task); 53 | 54 | ... 55 | 56 | // 销毁工作线程 57 | LCUIWorker_Destroy(worker); 58 | ``` 59 | 60 | ### 总结 61 | 62 | 由于主循环负责的都是 UI 相关的工作,而且它通常在主线程上执行,因此主线程也是 UI 线程,为了不影响 UI 线程上的主循环执行频率,我们应该尽可能将耗时较高的操作移动到 UI 线程之外的线程上执行,而这个线程就是工作线程。 63 | 64 | -------------------------------------------------------------------------------- /shi-li/zhu-xun-huan.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 主循环的概念和相关功能介绍。 3 | --- 4 | 5 | # 主循环 6 | 7 | 应用程序在运行的时候,为了能够不断的对用户的操作进行响应和反馈,通常的做法是将事件处理、状态更新和界面重绘等任务往复执行,而这一循环执行的过程即为主循环。 8 | 9 | LCUI 的主循环所执行的任务包括处理定时器、处理事件队列、更新组件、渲染组件等,这些任务的调度代码都在 [src/main.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/main.c#L214-L224) 文件中的 `LCUI_RunFrame()` 函数中: 10 | 11 | ```c 12 | void LCUI_RunFrame(void) 13 | { 14 | LCUI_ProcessTimers(); 15 | LCUI_ProcessEvents(); 16 | LCUICursor_Update(); 17 | LCUIWidget_Update(); 18 | LCUIDisplay_Update(); 19 | LCUIDisplay_Render(); 20 | LCUIDisplay_Present(); 21 | } 22 | ``` 23 | 24 | ### 帧率控制 25 | 26 | 主循环的每次循环即为一帧,为了避免不必要的 CPU 资源占用,主循环的执行频率会受到帧率控制,预设的帧率限制是 120 帧每秒,也就是主循环每秒最多执行 120 遍,每帧至少占用约 8.33 毫秒的时间,如果一帧的耗时低于 0.83 毫秒则会利用剩下的时间进入休眠状态。 27 | 28 | 如果你需要自定义帧率限制,可以调用 `LCUI_ApplySettings()` 修改全局设置中的 `frame_rate_cap` 设置项: 29 | 30 | ```c 31 | #include 32 | #include 33 | 34 | int main(void) 35 | { 36 | LCUI_SettingsRec settings; 37 | 38 | Settings_Init(&settings); 39 | settings.frame_rate_cap = 60; 40 | LCUI_ApplySettings(&settings); 41 | } 42 | ``` 43 | 44 | ### 多个主循环 45 | 46 | 试着考虑这种场景:在用户点击按钮后弹出一个确认框,等待用户点击确认后再执行操作。这种场景比较常见,我们会希望有个 `ShowConfirmDialog()` 函数能够完成这件事情: 47 | 48 | ```c 49 | LCUI_BOOL ShowConfirmDialog(const char *title, const char *content) 50 | { 51 | ... 52 | if (isOkButtonClicked) { 53 | return TRUE; 54 | } 55 | return FALSE; 56 | } 57 | 58 | void OnButtonClick() 59 | { 60 | if (ShowConfirmDialog("Confirm", "Are you sure you want to do it?")) { 61 | DoSomeThing(); 62 | } 63 | } 64 | ``` 65 | 66 | 按钮的点击事件处理器都是在主循环中执行的,如果 `ShowConfirmDialog()` 函数要等到用户点击弹框里的按钮后才退出的话,它在这段等待时间内会一直阻塞主循环的执行,导致整个界面无法响应用户操作,由于界面无法响应操作, `ShowConfirmDialog()` 函数也无法得知用户是否点击了确认按钮或取消按钮,这就成了一个死循环,那么如何解决此问题?有一种方法是在 `ShowConfirmDialog()` 函数中再创建一个主循环以响应后续的用户操作和界面更新,示例如下: 67 | 68 | ```c 69 | typedef struct DialogContextRec_ { 70 | LCUI_BOOL result; 71 | LCUI_MainLoop loop; 72 | } DialogContextRec, *DialogContext; 73 | 74 | static void OnBtnOkClick(LCUI_Widget w, LCUI_WidgetEvent e, void *arg) 75 | { 76 | DialogContext ctx = e->data; 77 | ctx->result = TRUE; 78 | LCUIMainLoop_Quit(ctx->loop); 79 | } 80 | 81 | static void OnBtnCancelClick(LCUI_Widget w, LCUI_WidgetEvent e, void *arg) 82 | { 83 | DialogContext ctx = e->data; 84 | ctx->result = FALSE; 85 | LCUIMainLoop_Quit(ctx->loop); 86 | } 87 | 88 | LCUI_BOOL ShowConfirmDialog(const wchar_t* title, const wchar_t *content) 89 | { 90 | DialogContextRec ctx = { 0 }; 91 | LCUI_Widget btn_cancel = LCUIWidget_New("button"); 92 | LCUI_Widget btn_ok = LCUIWidget_New("button"); 93 | 94 | ... 95 | 96 | Widget_BindEvent(btn_ok, "click", OnBtnOkClick, &ctx, NULL); 97 | Widget_BindEvent(btn_cancel, "click", OnBtnCancelClick, &ctx, NULL); 98 | ctx.loop = LCUIMainLoop_New(); 99 | LCUIMainLoop_Run(ctx.loop); 100 | Widget_Destroy(dialog); 101 | return ctx.result; 102 | } 103 | ``` 104 | 105 | {% hint style="info" %} 106 | 这段代码省略了弹框组件的构造代码,如需了解完整的实现代码可以查看 LC Finder 项目中的 [src/ui/components/dialog\_confirm.c](https://github.com/lc-soft/LC-Finder/blob/573f200698e2604450665716ebc6608837b4b73a/src/ui/components/dialog_confirm.c) 文件。 107 | {% endhint %} 108 | 109 | 在这段代码中,先定义了`DialogContextRec` 类型的 ctx 变量用于记录按钮点击状态和主循环的指针,然后为确认按和取消按钮绑定点击事件处理器,之后调用 `LCUIMainLop_New()` 新建了一个主循环,再调用 `LCUIMainLoop_Run()` 执行这个新的主循环。在按钮被点击后,事件处理器会修改 ctx 中的按钮点击状态,然后调用 `LCUIMainLoop_Quit()` 退出指定的主循环。在`LCUIMainLoop_Run()` 函数退出后,销毁弹框并将用户的操作结果返回。 110 | 111 | 另一种方法是改用回调函数的响应操作结果: 112 | 113 | ```c 114 | LCUI_BOOL ShowConfirmDialog( 115 | const char *title, 116 | const char *content, 117 | void (*onResult)(LCUI_BOOL, void*) 118 | ); 119 | 120 | void OnConfirm(LCUI_BOOL isConfirmed) 121 | { 122 | if (isConfirmed) { 123 | DoSomeThing(); 124 | } 125 | } 126 | 127 | void OnButtonClick() 128 | { 129 | ShowConfirmDialog( 130 | "Confirm", 131 | "Are you sure you want to do it?", 132 | OnConfirm 133 | ); 134 | } 135 | ``` 136 | 137 | 我们不建议采用这种方法,因为它存在以下几个问题: 138 | 139 | * 需要再定义一个函数接收操作结果,使得操作逻辑被分散。 140 | * 如果这个函数需要额外的参数话,还要 `ShowConfirmDialog()` 再增加一个参数,增加了函数复杂度和代码量。 141 | * 由于 C 语言没有像 JavaScript 那样的闭包特性和对异步编程的 async/await 关键字支持,使得这种方法的实现代码和调用代码并不优雅。 142 | 143 | ### 不使用主循环 144 | 145 | 如果你的应用程序有自己的主循环,不希望为适应 LCUI 的主循环而做改动,那么可以在主循环中调用 `LCUI_RunFrame()` 函数: 146 | 147 | ```c 148 | while (your_app.active) { 149 | your_app_main_loop_task1(); 150 | your_app_main_loop_task2(); 151 | your_app_main_loop_task3(); 152 | ... 153 | LCUI_RunFrame(); 154 | } 155 | ``` 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /tu-xiang-chu-li/README.md: -------------------------------------------------------------------------------- 1 | # 图像处理 2 | 3 | ### 待办事项 4 | 5 | **重新设计图像处理 API** 6 | 7 | 以开发新的图形库为目的,设计一套图形 API,然后将现有的代码改用这套新 API 来实现。设计时需要考虑的因素有: 8 | 9 | * 不依赖 LCUI 的数据类型和功能。 10 | * 参考主流图形库的 API 设计,使得用过其它图形库的人能够快速上手。 11 | * 能够切换多个渲染后端,例如:纯 CPU 渲染、DirectX、OpenGL、skia、cario。 12 | 13 | **添加 gif 文件读取和渲染支持** 14 | 15 | * 设计合适的数据结构来存储 gif 动画数据。 16 | * 提供相应的函数以实现播放、暂停、渲染功能。 17 | * 当组件的 `background-image` 属性指定了 gif 文件时,应创建一个定时器来渲染动画。 18 | 19 | \*\*\*\* 20 | 21 | -------------------------------------------------------------------------------- /tu-xiang-chu-li/tu-xiang-bian-ji.md: -------------------------------------------------------------------------------- 1 | # 图像编辑 2 | 3 | ### 混合 4 | 5 | ### 裁剪 6 | 7 | ### 缩放 8 | 9 | -------------------------------------------------------------------------------- /tu-xiang-chu-li/tu-xiang-wen-jian-cao-zuo.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍图像文件操作函数的用法。 3 | --- 4 | 5 | # 图像文件操作 6 | 7 | ### 读取图像文件信息 8 | 9 | 使用图像读取器读取文件头中的信息: 10 | 11 | ```c 12 | #include 13 | #include 14 | #include 15 | 16 | int main(int argc, char *argv[]) 17 | { 18 | FILE *fp; 19 | LCUI_ImageReaderRec reader = { 0 }; 20 | 21 | if (argc != 2) { 22 | printf("Please specify the image file path"); 23 | return -1; 24 | } 25 | fp = fopen(argv[1], "rb"); 26 | if (!fp) { 27 | printf("Cannot open file"); 28 | return -2; 29 | } 30 | LCUI_SetImageReaderForFile(&reader, fp); 31 | if (LCUI_InitImageReader(&reader) != 0) { 32 | printf("Unsupported image format"); 33 | fclose(fp); 34 | return -3; 35 | } 36 | printf("image type: %d\n", reader.header.type); 37 | printf("image color type: %d\n", reader.header.color_type); 38 | printf("image size: %ux%u", reader.header.width, reader.header.height); 39 | LCUI_DestroyImageReader(&reader); 40 | fclose(fp); 41 | return 0; 42 | } 43 | ``` 44 | 45 | 这段代码会打开参数中指定的文件,然后初始化图像读取器,从文件中读取信息并在最后打印图像的类型、色彩类型和尺寸到屏幕上。 46 | 47 | 在使用 `fopen()` 打开文件后,调用 `LCUI_SetImageReaderForFile()` 将图像读取器的读取目标设置为标准文件流,然后调用 `LCUI_InitImageReader()` 初始化文件读取器,初始化时会尝试调用预置的图像读取接口检测该文件的格式。 48 | 49 | ### 读取图像数据 50 | 51 | LCUI 提供了两个用于读取图像数据的接口: 52 | 53 | ```c 54 | int LCUI_ReadImage(LCUI_ImageReader reader, LCUI_Graph *out); 55 | 56 | int LCUI_ReadImageFile(const char *filepath, LCUI_Graph *out); 57 | ``` 58 | 59 | `LCUI_ReadImage()` 适用于从自定义的文件流中读取图像数据,在使用它之前你需要为图像读取器设置自定义文件流的句柄和操作函数。而 `LCUI_ReadImageFile()` 则适用于从标准文件流中读取图像数据,你只需要传入文件路径,它就能基于标准库的 `fopen()`、`fread()` 和 `fclose()` 等文件读取函数完成图像数据读取。 60 | 61 | ### 在读取时反馈进度 62 | 63 | 图像读取器的数据结构中的 [`fn_prog`](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/image.h#L76) 成员变量是个函数指针,它会在每次读取完一行图像数据后调用,你只需要将该指针指向自定义函数即可获得进度信息。 64 | 65 | 基于上个示例代码,需要做这些改动: 66 | 67 | ```c 68 | #include 69 | #include 70 | #include 71 | 72 | void on_progress(void *data, float percent) 73 | { 74 | printf("read %s: %.2f%%\n", data, percent); 75 | } 76 | 77 | int main(int argc, char *argv[]) 78 | { 79 | FILE *fp; 80 | LCUI_ImageReaderRec reader = { 0 }; 81 | 82 | if (argc != 2) { 83 | printf("Please specify the image file path"); 84 | return -1; 85 | } 86 | fp = fopen(argv[1], "rb"); 87 | if (!fp) { 88 | printf("Cannot open file"); 89 | return -2; 90 | } 91 | LCUI_SetImageReaderForFile(&reader, fp); 92 | if (LCUI_InitImageReader(&reader) != 0) { 93 | printf("Unsupported image format"); 94 | fclose(fp); 95 | return -3; 96 | } 97 | reader.fn_prog = on_progress; 98 | reader.prog_arg = argv[1]; 99 | printf("image type: %d\n", reader.header.type); 100 | printf("image color type: %d\n", reader.header.color_type); 101 | printf("image size: %ux%u", reader.header.width, reader.header.height); 102 | LCUI_DestroyImageReader(&reader); 103 | fclose(fp); 104 | return 0; 105 | } 106 | ``` 107 | 108 | ### 自定义文件流 109 | 110 | 图像读取器的读取功能依赖 `fn_read` 、`fn_rewind` 和 `fn_skip` 这三个函数指针来实现,可供参考的最简单的例子是 [src/image/reader.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/image/reader.c#L61-L83) 文件中的`LCUI_SetImageReaderForFile()` 函数,它对标准库的文件操作函数做了一层简单包装。如果你需要更加高级的用法,可以参考 LC Finder 项目的 [UWP/FileService.cpp](https://github.com/lc-soft/LC-Finder/blob/573f200698e2604450665716ebc6608837b4b73a/UWP/FileService.cpp#L611-L678) 文件中的图像文件读取器相关实现代码,它针对 UWP 的异步文件流做了适配。 111 | 112 | ### 将图像数据写入文件 113 | 114 | 由于图像写入功能很少用,因此并未拥有图像读取功能一般的完成度,目前能用的只有这一个函数: 115 | 116 | ```c 117 | int LCUI_WritePNGFile(const char *file_name, const LCUI_Graph *graph); 118 | ``` 119 | 120 | -------------------------------------------------------------------------------- /tu-xiang-chu-li/tu-xiang-xuan-ran.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 图像渲染相关 API 和概念的介绍。 3 | --- 4 | 5 | # 图像渲染 6 | 7 | 图像渲染是 UI 库的核心能力,它直接影响到 UI 的视觉效果和渲染性能。 8 | 9 | ### 创建画布 10 | 11 | 在开始渲染我们的图像前,我们需要创建一个用于存储图像数据的画布(canvas): 12 | 13 | ```c 14 | #include 15 | #include 16 | #include 17 | 18 | int main(void) 19 | { 20 | LCUI_Graph canvas; 21 | 22 | Graph_Init(&canvas); 23 | Graph_Create(&canvas, 800, 600); 24 | LCUI_WritePNGFile("canvas.png", &canvas); 25 | Graph_Free(&canvas); 26 | return 0; 27 | } 28 | ``` 29 | 30 | 这段代码做了这几件事: 31 | 32 | * 初始化一个画布,默认的色彩类型是 RGB 33 | * 为这块画布创建了能存储 800x600 像素的内存空间 34 | * 将画布内的数据写入到 png 文件 35 | * 释放画布占用的资源 36 | 37 | ### 填充颜色 38 | 39 | 打开 canvas.png 文件后我们可以发现图片内容是黑色的,因为给画布分配的内存空间初始填充的都是 0,RGB\(0,0,0\) 就是黑色,为了方便看到我们接下来绘制的内容,我们先将画布填充为白色: 40 | 41 | ```c 42 | #include 43 | #include 44 | #include 45 | 46 | int main(void) 47 | { 48 | LCUI_Graph canvas; 49 | LCUI_Color white = { .value = 0xffffffff }; 50 | 51 | Graph_Init(&canvas); 52 | Graph_Create(&canvas, 800, 600); 53 | Graph_FillRect(&canvas, white, NULL, FALSE); 54 | LCUI_WritePNGFile("canvas.png", &canvas); 55 | Graph_Free(&canvas); 56 | return 0; 57 | } 58 | ``` 59 | 60 | `Graph_FillRect()` 的第三个参数可以指定填充区域,我们可以试试用它将 \(0, 0 , 100, 200\) 区域填充为红色: 61 | 62 | ```c 63 | #include 64 | #include 65 | #include 66 | 67 | int main(void) 68 | { 69 | LCUI_Graph canvas; 70 | LCUI_Color white = { .value = 0xffffffff }; 71 | LCUI_Color red = { .value = 0xffff0000 }; 72 | LCUI_Rect red_area = { 0, 0, 100, 200 }; 73 | 74 | Graph_Init(&canvas); 75 | Graph_Create(&canvas, 800, 600); 76 | Graph_FillRect(&canvas, white, NULL, FALSE); 77 | Graph_FillRect(&canvas, red, &red_area, FALSE); 78 | LCUI_WritePNGFile("canvas.png", &canvas); 79 | Graph_Free(&canvas); 80 | return 0; 81 | } 82 | ``` 83 | 84 | ### 绘制文本 85 | 86 | 填充色块只是最基本的功能,接下来我们再试试在画布上绘制一段文本: 87 | 88 | ```c 89 | #include 90 | #include 91 | #include 92 | #include 93 | 94 | int main(void) 95 | { 96 | LCUI_Graph canvas; 97 | LCUI_Color white = { .value = 0xffffffff }; 98 | LCUI_Color red = { .value = 0xffff0000 }; 99 | LCUI_Color blue = { .value = 0xff0000ff }; 100 | LCUI_Rect red_area = { 0, 0, 100, 200 }; 101 | LCUI_Pos text_pos = { 0, 240 }; 102 | LCUI_TextLayer text_layer = TextLayer_New(); 103 | LCUI_TextStyleRec txxt_style; 104 | 105 | Graph_Init(&canvas); 106 | Graph_Create(&canvas, 800, 600); 107 | Graph_FillRect(&canvas, white, NULL, FALSE); 108 | Graph_FillRect(&canvas, red, &red_area, FALSE); 109 | 110 | LCUI_InitFontLibrary(); 111 | TextStyle_Init(&text_style); 112 | TextStyle_SetSize(&text_style, 24); 113 | TextStyle_SetForeColor(&text_style, white); 114 | TextStyle_SetBackColor(&text_style, blue); 115 | 116 | TextLayer_SetTextStyle(text_layer, &text_style); 117 | TextLayer_SetTextW(text_layer, L"White text and blue background", NULL); 118 | TextLayer_Update(text_layer, NULL); 119 | TextLayer_Render(text_layer, NULL, 0, 240, &canvas); 120 | 121 | LCUI_WritePNGFile("canvas.png", &canvas); 122 | 123 | TextLayer_Destroy(text_layer); 124 | TextStyle_Destroy(text_style); 125 | Graph_Free(&canvas); 126 | LCUI_FreeFontLibrary(); 127 | return 0; 128 | } 129 | ``` 130 | 131 | 这段示例代码用到了字体库、文本样式(TextStyle)和文本层(TextLayer),其中文本样式影响文本层的渲染效果,使用它可以设置文本的字族、风格、大小、颜色等样式,而文本层则是让 LCUI 具备文本渲染能力的关键,它的文字排版和渲染能力依赖于字体库提供的字形数据,在使用它之前需要先调用 `LCUI_InitFontLibrary()` 初始化字体库。 132 | 133 | 在准备好画布和文本样式后,调用文本层的操作函数设置固定大小、文本样式和文本内容,然后调用 `TextLayer_Update()` 应用这些改动,之后调用 `TextLayer_Render()` 将文本层绘制到画布上。 134 | 135 | 运行这个示例后打开 canvas.png,你会看到蓝底白字的 "White text and blue background" 文本。 136 | 137 | ### 绘制背景图 138 | 139 | ```c 140 | #include 141 | #include 142 | #include 143 | #include 144 | 145 | int main(void) 146 | { 147 | LCUI_Graph canvas; 148 | LCUI_Color white = { .value = 0xffffffff }; 149 | LCUI_Rect bg_area = { 200, 100, 400, 300 }; 150 | LCUI_Rect paint_area = { 0, 0, 400, 300 }; 151 | LCUI_Background bg; 152 | LCUI_PaintContext paint; 153 | 154 | Graph_Init(&canvas); 155 | Graph_Create(&canvas, 800, 600); 156 | Graph_FillRect(&canvas, white, NULL, FALSE); 157 | 158 | Background_Init(&bg); 159 | paint = LCUIPainter_Begin(&canvas, &bg_area); 160 | Background_Paint(&bg, &paint_area, paint); 161 | 162 | LCUI_WritePNGFile("canvas.png", &canvas); 163 | 164 | LCUIPainter_End(paint); 165 | Graph_Free(&canvas); 166 | return 0; 167 | } 168 | ``` 169 | 170 | ### 绘制边框 171 | 172 | ### 绘制圆形 173 | 174 | ### 绘制阴影 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /widget/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。 3 | --- 4 | 5 | # 组件 6 | 7 | 组件(Component)是构成 LCUI 应用程序的 UI 的基本元素,其它 UI 开发库可能会将这类元素称之为控件(Control)或部件(Widget),你可以按照自己的习惯称呼它们,不过由于作者受到 Web 前端开发技术的影响,本文档统一采用“组件”这一名字称呼它,即便它的数据类型名是 `LCUI_Widget`。 8 | 9 | 将一个完整的 UI 拆分成多个独立可复用的组件,这过程就是组件化,它与模块化相似,两者的目的都是为了解决代码中的耦合、内聚、复用问题,区别在于组件化注重 UI 层面的复用,模块化注重逻辑层面的职责分离。 10 | 11 | 在学习组件开发之前,我们先简单了解一下如何拆分和设计组件,这将有助于我们以后的组件开发。 12 | 13 | ### 如何拆分组件 14 | 15 | 在拆分组件前,我们应该考虑以下几个条件来决定哪部分需要拆分成组件: 16 | 17 | 1. **这部分的元素数量或逻辑复杂度是否足以证明它应该成为组件?**如果它只是几行代码,那么为了将其组件化我们可能会编写更多的代码,还不如让它继续嵌入在父组件内。 18 | 2. **代码是否重复?**如果某些东西只使用一次,并且服务于一个不太可能在其他地方使用的特定用例,那么不做组件化可能会更好。如果需要,你可以随时将其分开(但不要在需要做这些工作的时候将此作为偷懒的借口)。 19 | 3. **它会减少你需要写的样板代码吗?**以卡片为例,如果你想让列表内的列表项拥有卡片式布局和样式,那么你会需要创建包含卡片各个部分且有特定结构和属性的组件,然后将列表项的内容嵌套进这些部分中。构建这种组件的代码就是样板代码,如果需要编写的样板代码较多的话,可以考虑将其组件化。 20 | 4. **它是否与其它部分存在耦合关系?**如果它与其它部分有着较强的耦合,那么强行将它组件化会需要编写额外的代码来实现与其它部分的交互,从而导致复杂度增加。 21 | 5. **这些好处是否超过了成本?**做组件化不可避免地需要投入时间和精力,这些成本视具体情况而定,所以在做出决定之前需要权衡这两个方面。 22 | 23 | ### 如何设计组件 24 | 25 | 在知道哪些部分需要拆分成组件后,我们还需要考虑以下条件来对组件代码进行拆分。 26 | 27 | * **是否包含复杂的逻辑?**组件应该尽量只包含 UI 相关逻辑,假设你的组件是个带有缩略图缓存、图片懒加载等功能的图片列表,那么我们建议你将这些功能相关代码从组件代码中分离出去,作为独立的模块来开发和维护。 28 | * **是否包含自定义动画?**由于 LCUI 还未实现动画系统,像淡入淡出、弹入弹出等常见动画都得手动编码实现,考虑到复用,我们建议你将它们分离为独立的模块。 29 | * **是否包含复杂的交互?**除了常规鼠标点击交互,我们可能还会添加一些其它交互效果来提升用户体验和操作效率,例如:鼠标操作中的拖拽和拖放,触控操作中的双指缩放和旋转。和上述条件一样,我们也建议你将它们分离为独立的模块。 30 | 31 | 以上条件是类似的,本质上是让你根据业务逻辑、动画和交互这几类再对组件代码做一次模块化。 32 | 33 | ### 待办事项 34 | 35 | **添加 img 组件** 36 | 37 | 功能与 HTML 中的 img 元素相同,组件宽高自适应图片尺寸。 38 | 39 | **改进组件增量更新机制** 40 | 41 | 为了应对数十万量级的组件更新,保证界面的流畅度,现有的做法是统计组件的更新耗时然后为每帧更新的组件总量设置一个合适的限制,这种做法并不是最优的,可参考 React 的 [Fiber reconciler](https://zh-hans.reactjs.org/docs/codebase-overview.html#fiber-reconciler) 的架构,重新设计 LCUI 的组件增量更新机制。 42 | 43 | **完善组件文档** 44 | 45 | 这个章节的内容较少,应该参考 UI 开发相关文章继续完善。 46 | 47 | **** 48 | -------------------------------------------------------------------------------- /widget/attributes.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍组件属性的分类及用途。 3 | --- 4 | 5 | # 属性 6 | 7 | ### 基础属性 8 | 9 | 基础属性是每个组件都有的属性,可通过组件指针直接访问到它们。你可以在 [include/LCUI/gui/widget\_base.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/gui/widget_base.h#L253-L336) 文件中找到它们的定义。 10 | 11 | 基础属性分为只读和可写两种: 12 | 13 | * 只读的基础属性是经过计算后的结果,手动修改它们的值是没有意义的,因为它们的值会在下次计算后更新,例如:x、y、width、height 这些几何属性,它们是组件的样式经过布局引擎计算后的结果。 14 | * 可写的基础属性能影响组件的功能和渲染效果,例如:修改 `disabled` 和 `event_blocked` 能控制组件的事件响应行为, 修改 `custom_style` 能覆盖组件原本的样式。这些属性大都有相关的函数来负责修改它们,我们只需要调用它们即可。 15 | 16 | 接下来让我们通过示例代码来了解一些常用的基础属性。 17 | 18 | ```c 19 | LCUI_Widget w = LCUIWidget_New(NULL); 20 | 21 | // 几何属性的读取 22 | printf("coordinate: (%f, %f)\n", w->x, w->y); 23 | printf("size: (%f, %f)\n", w->width, w->height); 24 | 25 | // 类的增删查 26 | Widget_AddClass(w, "button disabled"); 27 | if (Widget_HasClass(w, "disabled")) { 28 | Widget_RemoveClass(w, "disabled"); 29 | } 30 | 31 | // id 32 | Widget_SetId(w, "btn-submit"); 33 | printf("is same widget? %d\n", w == LCUIWidget_GetById("btn-submit")); 34 | 35 | // 状态/伪类 36 | if (Widget_HasStatus(w, "focus")) { 37 | printf("widget has focus"); 38 | } 39 | 40 | // 打印最终样式 41 | LCUI_PrintStyleSheet(w->style); 42 | // 打印匹配的样式 43 | LCUI_PrintStyleSheet(w->inherited_style); 44 | // 设置自定义样式 45 | Widget_SetStyle(w, key_margin_left, 10, px); 46 | Widget_SetStyleString(w, "margin-left", "10px"); 47 | // 获取已计算的样式 48 | if (w->computed_style.visible) { 49 | Widget_Hide(w); 50 | } else { 51 | Widget_Show(w); 52 | } 53 | ``` 54 | 55 | ### 扩展属性 56 | 57 | 扩展属性是可以任意添加、修改和删除的属性,常用于保存自定义数据,或是让特定组件支持用 xml 语言来设置相关功能。它们主要来自于 xml 文档和手动调用 `Widget_SetAttribute()` 函数,且都以字符串的形式保存在一个以属性名为索引键的哈希表中。 58 | 59 | 扩展属性的操作例子: 60 | 61 | ```c 62 | LCUI_Widget w = LCUIWidget_New(NULL); 63 | 64 | Widget_SetAttribute(w, "attr", "value"); 65 | printf("%s\n", Widget_GetAttribute(w, "attr")); 66 | ``` 67 | 68 | 如果你想让你的组件支持响应 xml 文档中设置属性,举个例子,假设你有个 Timeago 组件提供这些方法: 69 | 70 | ```c 71 | Timeago_SetDate(w, "2021-01-01T08:00:00.000Z"); 72 | Timeago_SetAutoUpdate(w, 60); 73 | Timeago_SetLocale(w, "zh-CN"); 74 | ``` 75 | 76 | 那么,首先你需要添加一个 `Timeago_SetAttribute()` 函数来集中处理属性: 77 | 78 | ```c 79 | void MyWidget_SetAttribute( 80 | LCUI_Widget w, 81 | const char *name, 82 | const char *val 83 | ) 84 | { 85 | if (strcmp(name, "date") === 0) { 86 | ... 87 | } 88 | ... 89 | } 90 | ``` 91 | 92 | 然后,将它与组件原型上 `setattr` 方法进行绑定: 93 | 94 | ```c 95 | my_widget_proto->setattr = MyWidget_SetAttribute; 96 | ``` 97 | 98 | 这样修改后,在 XML 文档中就能这样使用 Timeago 组件: 99 | 100 | ```markup 101 | 102 | ``` 103 | 104 | 而且还能支持用 `Widget_SetAttribute()` 函数设置属性: 105 | 106 | ```c 107 | Widget_SetAttribute(w, "date", "2021-01-01T08:00:00.000Z"); 108 | Widget_SetAttribute(w, "auto-update", "60"); 109 | Widget_SetAttribute(w, "locale", "zh-CN"); 110 | ``` 111 | 112 | ### 待办事项 113 | 114 | **给 `LCUI_WidgetRec_` 结构体中的只读成员加上只读注释** 115 | 116 | 修改 [include/LCUI/gui/widget\_base.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/gui/widget_base.h#L253-L336) 中的 `LCUI_WidgetRec_` 结构体定义,给只读属性加上 `(Readonly)` 之类的注释。 117 | 118 | -------------------------------------------------------------------------------- /widget/events.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 组件事件的相关概念和用法介绍。 3 | --- 4 | 5 | # 事件 6 | 7 | 组件事件来源于 LCUI 的核心事件,当核心事件触发时,组件系统中的相关事件处理器会对其进行处理,然后转换成组件事件派发给对应的组件。 8 | 9 | 组件事件与核心事件的区别在于事件的种类和事件对象的内容,事件种类包括悬停、单击、双击、焦点等事件,而事件对象则包含了关联的组件和事件冒泡控制。 10 | 11 | ### 事件对象 12 | 13 | 首先,让我们看看组件事件对象在[ include/LCUI/gui/widget\_event.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/gui/widget\_event.h#L81-L95) 文件中的定义: 14 | 15 | ```c 16 | typedef struct LCUI_WidgetEventRec_ { 17 | uint32_t type; 18 | void *data; 19 | LCUI_Widget target; 20 | LCUI_BOOL cancel_bubble; 21 | union { 22 | LCUI_WidgetMouseMotionEvent motion; 23 | LCUI_WidgetMouseButtonEvent button; 24 | LCUI_WidgetMouseWheelEvent wheel; 25 | LCUI_WidgetKeyboardEvent key; 26 | LCUI_WidgetTouchEvent touch; 27 | LCUI_WidgetTextInputEvent text; 28 | }; 29 | } LCUI_WidgetEventRec, *LCUI_WidgetEvent; 30 | ``` 31 | 32 | 我们可以发现,组件事件对象与核心事件对象的结构相似,只是多了 `target` 和 `cacnel_buble` 成员: 33 | 34 | * `target` 成员指向的是事件触发时的组件,当你想让多个组件在事件发生时执行某些操作而给它们设置相同的事件处理器时,`target` 非常有用,例如,有 16 个按钮,按钮被点击时会更改文本,那么在事件处理器中,`target` 指向的就是当前被点击的按钮,你只需要修改它的文本即可,无需用复杂的方式去获取它。 35 | * `cancel_bubble` 成员用于标识是否取消该事件的冒泡,将它赋值为 TRUE 时,该事件对象不会冒泡到父级组件。 36 | 37 | ### 事件冒泡 38 | 39 | 事件从触发它的组件向上级组件逐层传递的过程就是事件冒泡。 40 | 41 | ### 事件委托 42 | 43 | 利用事件冒泡机制,我们可以实现事件委托,也就是将子组件的事件委托给父组件处理,这种做法适用于需要为大量组件设置事件处理器的场景,能避免因设置大量事件处理器而导致的性能降低和内存占用增加的问题。 44 | 45 | ### 触控事件 46 | 47 | 触控事件目前仅在 Windows 系统中有效,事件对象的数据结构定义如下: 48 | 49 | ```c 50 | typedef struct LCUI_TouchPointRec_ { 51 | int x; 52 | int y; 53 | int id; 54 | int state; 55 | LCUI_BOOL is_primary; 56 | } LCUI_TouchPointRec, *LCUI_TouchPoint; 57 | 58 | typedef struct LCUI_TouchEvent_ { 59 | int n_points; 60 | LCUI_TouchPoint points; 61 | } LCUI_TouchEvent; 62 | 63 | typedef LCUI_TouchEvent LCUI_WidgetTouchEvent; 64 | ``` 65 | 66 | 触控事件的数据结构设计参考自 Windows API,只保留了主要的成员变量。LCUI 内部的触控事件和组件的触控事件是一样的数据结构。 67 | 68 | 从上述代码中我们可以很容易的理解到:触控事件包含多个触点的信息,`n_points` 表示当前共有多少个触点,每个触点都有自己的 x、y 坐标,并且有个 id 用于标识该触点,而 `state` 表示该触点的状态,它的值有三种:`LCUI_WEVENT_TOUCHDOWN`、`LCUI_WEVENT_TOUCHUP`、`LCUI_WEVENT_TOUCHMOVE`,这些值分别对应:触点按下、触点释放、触点移动这三个状态。 69 | 70 | 以下是测试触控事件的程序: 71 | 72 | ```c 73 | /** test_touch.c -- test touch support */ 74 | 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | 82 | /** 触点绑定记录 */ 83 | typedef struct TouchPointBindingRec_ { 84 | int point_id; /**< 触点 ID */ 85 | LCUI_Widget widget; /**< 组件 */ 86 | LinkedListNode node; /**< 在链表中的结点 */ 87 | LCUI_BOOL is_valid; /**< 是否有效 */ 88 | } TouchPointBindingRec, *TouchPointBinding; 89 | 90 | /** 触点绑定记录列表 */ 91 | static LinkedList touch_bindings; 92 | 93 | static void OnTouchWidget(LCUI_Widget w, LCUI_WidgetEvent e, void *arg) 94 | { 95 | LCUI_TouchPoint point; 96 | TouchPointBinding binding; 97 | 98 | if (e->touch.n_points == 0) { 99 | return; 100 | } 101 | binding = e->data; 102 | point = &e->touch.points[0]; 103 | switch (point->state) { 104 | case LCUI_WEVENT_TOUCHMOVE: 105 | Widget_Move(w, point->x - 32.0f, point->y - 32.0f); 106 | break; 107 | case LCUI_WEVENT_TOUCHUP: 108 | if (!binding->is_valid) { 109 | break; 110 | } 111 | /* 当触点释放后销毁组件及绑定记录 */ 112 | Widget_ReleaseTouchCapture(w, -1); 113 | LinkedList_Unlink(&touch_bindings, &binding->node); 114 | binding->is_valid = FALSE; 115 | Widget_Destroy(w); 116 | free(binding); 117 | break; 118 | case LCUI_WEVENT_TOUCHDOWN: 119 | default: 120 | break; 121 | } 122 | } 123 | 124 | static void OnTouch(LCUI_SysEvent e, void *arg) 125 | { 126 | int i; 127 | LCUI_Widget w; 128 | LinkedListNode *node; 129 | LCUI_TouchPoint point; 130 | LCUI_Color bgcolor = RGB(255, 0, 0); 131 | 132 | for (i = 0; i < e->touch.n_points; ++i) { 133 | TouchPointBinding binding; 134 | LCUI_BOOL is_existed = FALSE; 135 | point = &e->touch.points[i]; 136 | _DEBUG_MSG("point: %d\n", point->id); 137 | /* 检查该触点是否已经被绑定 */ 138 | for (LinkedList_Each(node, &touch_bindings)) { 139 | binding = node->data; 140 | if (binding->point_id == point->id) { 141 | is_existed = TRUE; 142 | } 143 | } 144 | if (is_existed) { 145 | continue; 146 | } 147 | w = LCUIWidget_New(NULL); 148 | /* 新建绑定记录 */ 149 | binding = NEW(TouchPointBindingRec, 1); 150 | binding->point_id = point->id; 151 | binding->node.data = binding; 152 | binding->is_valid = TRUE; 153 | binding->widget = w; 154 | Widget_Resize(w, 64, 64); 155 | Widget_Move(w, point->x - 32.0f, point->y - 32.0f); 156 | /* 设置让该组件捕获当前触点 */ 157 | Widget_SetTouchCapture(w, binding->point_id); 158 | Widget_BindEvent(w, "touch", OnTouchWidget, binding, NULL); 159 | Widget_SetStyle(w, key_position, SV_ABSOLUTE, style); 160 | Widget_SetStyle(w, key_background_color, bgcolor, color); 161 | LinkedList_AppendNode(&touch_bindings, &binding->node); 162 | Widget_Top(w); 163 | } 164 | } 165 | 166 | int main(int argc, char **argv) 167 | { 168 | LCUI_Init(); 169 | LinkedList_Init(&touch_bindings); 170 | LCUI_BindEvent(LCUI_TOUCH, OnTouch, NULL, NULL); 171 | return LCUI_Main(); 172 | } 173 | 174 | ``` 175 | 176 | 编译后运行,如果你的计算机自带触屏,则可以用手指在程序窗口内点击和移动,然后会看到类似于如下图所示的效果: 177 | 178 |  179 | 180 | 这个程序实现的功能是捕获各个触点并用对应的红色方块表示触点的位置,有个 `touch_bindings` 全局变量用于保存各个触点和组件的绑定记录,在响应触控事件时会遍历每个触点,根据触点的 id 在绑定记录中找对应的组件,找到后会根据触点的坐标来更新组件的坐标。为了让组件能够捕获触点在它外面产生的触控事件,并且不被其它组件捕获到,调用了 `Widget_SetTouchCapture()` 函数实现对该触点的独占,在触点释放后,调用 `Widget_ReleaseTouchCapture()` 函数解除对触点的独占。 181 | 182 | ### 待办事项 183 | 184 | **完善焦点管理** 185 | 186 | 需求如下: 187 | 188 | * 添加支持用 Tab 键和 Shift+Tab 组合键上下移动焦点。 189 | * 已获得焦点的组件应该有显眼的外边框。 190 | 191 | 关于外边框的实现,我们可以先为 CSS 解析器添加 outline 属性解析支持,然后在组件渲染流程中增加一个外边框绘制操作。 192 | -------------------------------------------------------------------------------- /widget/lifecycle.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 组件从创建到更新再到销毁的过程。 3 | --- 4 | 5 | # 生命周期 6 | 7 | ### 概览 8 | 9 | 组件的生命周期分为三个阶段:挂载、渲染、卸载,下图展示了解组件在整个生命周期中所涉及到的方法调用、原型方法调用和状态变化。 10 | 11 |  12 | 13 | ### 挂载阶段 14 | 15 | 从组件实例被创建再到被插入根组件树中,所经历的操作如下: 16 | 17 | * 初始化组件实例。 18 | * 根据组件类型绑定对应的原型。 19 | * 调用 `proto->init()` 原型方法。 20 | * 标记组件需要刷新全部样式。 21 | * 因父组件变为另外一个组件,触发 link 事件。 22 | 23 | ### 更新阶段 24 | 25 | 当组件被插入到根组件树中后,在主循环中运行的组件更新机制会负责它的更新,每当样式发生变化时都会触发更新,相关操作的执行顺序如下: 26 | 27 | * 创建更新上下文。 28 | * 遍历父级组件的更新上下文,如果里面有样式缓存则使用它。 29 | * 如果有样式缓存且组件自身有设置样式特征码,则以该特征码作为索引键直接从缓存中获取匹配的样式表,否则为组件生成临时的样式选择器然后从全局样式缓存查询匹配的样式。 30 | * 初始化更新上下文中的布局差异对比缓存。 31 | * 如果被标记为需要更新自身。 32 | * 初始化更新上下文中的样式对比缓存。 33 | * 遍历任务列表,更新自身样式。 34 | * 对比更新前后的样式,根据对比结果来决定是否需要标记无效区域、触发自身重新布局、触发父组件重新布局。 35 | * 如果需要更新子组件,则遍历子组件列表为每个子组件跑一遍上述流程。 36 | * 对比更新前后的布局差异,根据自身的位置和尺寸的变化情况来决定是否需要标记无效区域、触发父组件的重新布局。 37 | * 结束更新,销毁更新上下文。 38 | * 更新子组件的显示顺序。 39 | 40 | 整个更新流程如下图所示: 41 | 42 |  43 | 44 | ### 卸载阶段 45 | 46 | 当调用 `Widget_Destroy()` 函数销毁组件时,组件会被移动到垃圾列表中等待删除。这个阶段内的相关操作的执行顺序如下: 47 | 48 | * 组件状态被切换为 `DELETED` 。 49 | * 从父组件的子组件列表中移除自己,触发 unlink 事件。 50 | * 追加到垃圾列表中。 51 | * 销毁组件的子组件列表。 52 | * 清除原型数据,这时会调用 `proto->destroy()` 原型方法。 53 | * 销毁组件实例。 54 | 55 | {% hint style="info" %} 56 | 垃圾列表中的组件会在这一帧内所有组件更新完后被删除。采用这种延迟批量删除组件的方式是为了应对同一帧内还有其它事件和定时器的处理器需要操作该组件的情况。 57 | {% endhint %} 58 | 59 | ### 状态 60 | 61 | 组件的状态共有以下几种: 62 | 63 | * **CREATED:** 组件创建后的初始状态。 64 | * **UPDTED:** 组件经第一次更新后。 65 | * **LAYOUTED:** 组件经第一次布局后。 66 | * **READY:** 组件已更新且已被布局。这时会触发 READY 事件并直接切换到 NORMAL 状态。在 READY 状态之前,组件的位置和尺寸都是不准确的,如果你组件的一些操作依赖这些属性,例如:在初次显示时将组件定位在另一个组件的附近,则可以将之放到 READY 事件处理器中。 67 | * **NORMAL:** 组件正常可用。 68 | * **DELETED:** 组件已从根组件树中删除,即将被销毁。 69 | 70 | 其中 LAYOUTED 和 READY 是过渡状态,与组件当前状态混合后会变为下一个状态。 71 | 72 | ### 待办事项 73 | 74 | **重新设计组件生命周期** 75 | 76 | 考虑以下问题: 77 | 78 | * link 和 unlink 事件似乎用处不大,是否需要移除? 79 | * 生命周期中的某些阶段是通过触发事件来通知组件的,是否需要统一成函数调用? 80 | * 是否需要添加更多的生命周期方法?例如:`Mounted()` 和 `BeforeUnmount()` 81 | -------------------------------------------------------------------------------- /widget/painting.md: -------------------------------------------------------------------------------- 1 | # 绘制流程 2 | 3 | LCUI 的绘制流程是由脏矩形驱动的,窗口尺寸变化和组件的样式变化都会产生脏矩形,这些脏矩形主要集中在样式计算阶段和布局阶段产生,当脏矩形记录不为空时就会进入绘制流程,绘制流程由以下步骤组成: 4 | 5 | * **开始绘制:**根据当前所在窗口和脏矩形,创建一个绘制上下文,包含绘制区域和充当画布的图形对象,其中图形对象引用自帧缓冲,而帧缓冲与应用程序窗口绑定,对该图形对象写入像素数据会同步到窗口中。 6 | * **绘制组件树:**以根组件为起点,递归向下遍历整个组件树中的组件,将所有出现在脏矩形内的组件都绘制到画布中。 7 | * **结束绘制:**销毁绘制上下文,释放相关资源。 8 | 9 | 所有脏矩形都重绘完后,LCUI 会调用系统提供的窗口 API 将帧缓冲的内容写入到窗口内以让窗口呈现最新内容。接下来我们将深入了解与绘制流程相关的功能和工作原理。 10 | 11 | ### 脏矩形 12 | 13 | 相较于画面更新频繁且内容很多的图形游戏,普通应用的界面更新频率要低得很多,而更新的内容通常也不是很多,甚至只是一个小区域(例如:按钮),如果像游戏一样强制重绘的话会造成资源浪费,对用户而言最明显的感受就是风扇转速和电量消耗速度都很快。脏矩形技术正是利用只更新变化区域来达到提高绘制效率的目的。在脏矩形系统中,屏幕上更新的区域被称为“脏矩形”,绘制引擎仅对脏矩形部分重绘,而其他部分保持原样。 14 | 15 | 出于性能和内存开销上的考虑,LCUI 中的脏矩形系统采用如下设计: 16 | 17 | * 每个组件中都有一个脏矩形类型标识和记录,其中类型标识有五个值:`none`、`custom`、`padding-box`、`border-box`、`canvas-box`,当值为 `custom` 时使用脏矩形记录中的准确数据。 18 | * 在样式计算阶段和布局阶段,组件自身的位置、尺寸、透明度、边框、背景等视觉样式发生变化时会更新脏矩形类型标识。如果组件有自己的绘制逻辑,则会通过调用相关函数来更新脏矩形记录。 19 | * 在脏矩形收集阶段,整个过程是从根组件开始向下遍历整个组件树,组件的脏矩形仅在不被父组件脏矩形包含且在可视区域内时才被收集;收集脏矩形时如果与已有的脏矩形相重叠,则会将它们合并成一个;收集完后重置组件的脏矩形记录。 20 | 21 | 这种设计的好处是在判断脏矩形是否存在和是否重叠时,只需要简单的比较标识值的大小即可,也就是将矩形间的 `x`、`y`、`width`、`height` 的复杂比较简化成单个值的比较,极大的提升了脏矩形收集、去重和合并性能。 22 | 23 | ### 并行绘制 24 | 25 | 绘制本质上是将计算结果写入内存中,这些任务相互独立,可以分配给多个 CPU 核心同时执行以提升绘制效率。 26 | 27 | LCUI 采用的策略如下: 28 | 29 | * 根据屏幕尺寸和预设的并行渲染线程数量,将屏幕区域从上到下划分成多个小区域。 30 | * 分别为这些小区域收集脏矩形,当已收集的脏矩形总面积超过区域面积的 80% 时,将整个区域作为脏矩形,不再逐个重绘脏矩形。 31 | * 当脏矩形总面积超过两个区域面积时,开启并行绘制。这样做是因为并行特性本身也有性能开销,如果计算量过少的话,很容易会导致性能降低。 32 | 33 | 如下图所示,整个屏幕区域按照预设的并行渲染线程数量分成了四个部分,其中第一部分和第三部分区域的脏矩形总面积较少,采用的是局部渲染方案,仅对脏矩形进行重绘;第二部分区域内的脏矩形总面积较大,采用的是全量渲染方案,整个区域都会被重绘;第四部分区域由于没有脏矩形,所以不渲染。 34 | 35 |  36 | 37 | ### 图层 38 | 39 | 图层是一个用于存放临时绘制内容的图形对象,LCUI 在绘制组件时会用到以下图层: 40 | 41 | * **根图层:**该图层指向应用程序窗口的帧缓冲,绘制根组件时使用的就是该图层。 42 | * **上级图层:**绘制父组件时所使用的图层,它引用自父组件的内容图层或上级图层。 43 | * **自身图层:**用于存放组件自身的绘制结果。 44 | * **内容图层:**用于存放用于存放所有在内容区域内可见的子组件的绘制结果。 45 | * **合成图层:**用于存放组件自身图层和组件内容图层的混合结果。 46 | 47 | 这些图层的使用规则如下: 48 | 49 | * **组件无可绘制样式:**不使用图层。 50 | * **组件是自定义组件或有背景色、背景图、边框、阴影样式:**创建自身图层,然后基于该图层创建绘制上下文,让该组件的绘制结果都会输出到该图层中。 51 | * **组件有圆角边框:**创建内容图层,在绘制子组件时基于该图层创建绘制上下文,让所有子组件的绘制结果输出到该图层中。在绘制完所有子组件后,根据圆角边框样式对内容图层进行裁剪,也就是将溢出圆角边框外的像素都清除掉。 52 | * **组件透明度小于 1:**创建自身图层和内容图层,在两个图层都绘制完后,复制自身图层作为合成图层,然后将内容图层混合至该图层中,最后按照组件透明度将混合图层混合到上级图层中。 53 | 54 | ### 绘制上下文 55 | 56 | 绘制上下文在组件绘制前创建,它所包含的绘制缓冲和绘制区域会受到组件样式和父组件绘制上下文的影响,其中绘制缓冲只是个图形对象引用,它的引用对象有:根图层、父组件内容图层、上级图层,而绘制区域则是取自脏矩形与组件边界框重叠的区域。 57 | 58 | ### 绘制组件 59 | 60 | 我们从一个简单的例子来讲解组件的绘制流程。首先,屏幕上有以下三个组件: 61 | 62 | * Avatar:占用屏幕区域 \(20, 20, 80, 80\) 63 | * Text:占用屏幕区域 \(120, 20, 250, 80\) 64 | * Menu:占用屏幕区域 \(60, 60, 140, 160\) 65 | 66 | 然后我们将 Menu 组件的透明度改为 `0.5`,由于透明度影响整个组件的视觉效果,因此整个组件的区域都会标记为脏矩形,如下图中的蓝色区域所示: 67 | 68 |  69 | 70 | 我们可以看出脏矩形包含 Menu 且与 Avatar 和 Text 重叠,这意味着它们与脏矩形重叠的部分都需要重绘,且由于 Avatar 和 Text 都在 Menu 底下,Avatar 和 Text 的绘制顺序会先于 Menu。由此我们可以推测出大致的绘制流程: 71 | 72 | 1. 绘制根组件:将组件内的区域 \(60, 60, 140, 160\) 绘制到屏幕区域 \(60, 60, 140, 160\)。 73 | 2. 绘制 Avatar:将组件内的区域 \(40, 40, 40, 40\) 绘制到屏幕区域 \(60, 60, 40, 40\)。 74 | 3. 绘制 Text:将组件内的区域 \(0, 40, 60, 40\) 绘制到屏幕区域 \(60, 60, 40, 40\)。 75 | 4. 绘制 Menu:将组件内的区域 \(0, 0, 140, 160\) 绘制到屏幕区域 \(60, 60, 140, 160\)。 76 | 77 | 绘制过程中的图层使用情况如下: 78 | 79 | * 根组件:不使用图层,因为根组件没有任何需要绘制的内容。 80 | * Avatar:使用自身图层和内容图层,因为有圆角边界。 81 | * Text:使用自身图层,因为需要填充背景色。 82 | * Menu:使用自身图层、内容图层和合成图层,因为有阴影、透明度小于 1。 83 | 84 | -------------------------------------------------------------------------------- /widget/rendering.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍 UI 的渲染流程和渲染性能优化方法。 3 | --- 4 | 5 | # 渲染性能 6 | 7 | 用户希望他们使用的图形界面具有交互性和流畅性,而这正是你需要越来越多地集中时间和精力的地方。 界面不仅要加载快,还要运行良好, 滚动应该很快,动画和交互应该如丝般流畅。 8 | 9 | 要编写高性能应用程序,你需要了解 LCUI 如何渲染界面,并确保你编写的代码以及第三方代码尽可能高效地运行。 10 | 11 | ### 像素管道 12 | 13 | “渲染”就是将组件数据转变为像素数据,这个转变如同一条包含很多区域的单向管道,组件数据经过管道中的每个区域的处理最终变成像素数据。我们可以将这个管道称为像素管道,它的结构如下图所示: 14 | 15 |  16 | 17 | * **事件:**界面的更新是由事件驱动的,通常我们会在事件处理器中实现一些操作和视觉变化的效果。比如显示一个加载中动画、切换到另一个界面、或者往界面里添加一些内容等。 18 | * **样式计算:**此过程是根据匹配选择器(例如 `.button` 或 `.list .list-item`)计算出哪些组件应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。 19 | * **布局:** 在知道对一个组件应用哪些规则之后,LCUI 即可开始计算它要占据的空间大小及其在屏幕的位置。LCUI 所采用的类似于网页的布局模式意味着一个组件可能影响其他组件,例如更改组件的宽度会影响到子组件的位置和宽度以及组件树中各处的节点,因此布局过程是经常发生的。 20 | * **绘制:**绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。 21 | * **合成:**由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染界面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。 22 | 23 | 管道的每个部分都有机会产生卡顿,因此务必准确了解你的代码触发管道的哪些部分。 24 | 25 | 不一定每帧都总是会经过管道每个部分的处理。在实现视觉变化时,管道针对指定帧的运行通常有三种方式: 26 | 27 | **1. 事件 > 样式 > 布局 > 绘制 > 合成** 28 | 29 |  30 | 31 | 如果你修改了组件的布局属性,即改变了组件的几何属性(例如宽度、高度、左侧或顶部位置等),那么 LCUI 将必须检查所有其它组件,然后对界面进行重新布局。任何受影响的部分都需要重新绘制,而且最终绘制的元素需进行合成。 32 | 33 | **2. 事件 > 样式 > 绘制 > 合成** 34 | 35 |  36 | 37 | 如果你修改了组件的绘制属性,即不会影响界面布局的属性(例如背景图片、文字颜色或阴影等),则 LCUI 会跳过布局,但仍将执行绘制。 38 | 39 | **3. 事件 > 样式** 40 | 41 |  42 | 43 | 如果您更改组件的一个既不要布局也不要绘制的属性(例如:pointer-events),则 LCUI 将在计算完样式后跳过剩下的过程。 44 | 45 | 接下来,让我们深入了解此管道的各个不同部分。我们会以一些常见问题为例,阐述如何诊断和修正它们。 46 | 47 | ### 事件 48 | 49 | 事件处理器经常会触发视觉变化。有时是直接通过样式操作,有时是会产生视觉变化的计算,例如搜索数据或将其排序。时机不当或长时间运行的代码可能是导致性能问题的常见原因。您应当设法尽可能减少其影响。 50 | 51 | 在许多情况下,你可以将事件处理器中的耗时长的代码从主线程移动到工作线程,详见[工作线程](../app/worker.md)章节。不过在工作线程上你必须确保这些代码不会操作 UI 相关资源, 如果你的工作必须在主线程上执行,请考虑一种分批处理的方法,将大任务分割为小任务,每个小任务所占时间不超过几毫秒,然后使用定时器逐个执行这些任务。 52 | 53 | ### 样式计算 54 | 55 | 通过添加和删除组件,更改属性、类来更改组件,都会导致 LCUI 重新计算组件样式,在很多情况下还会对整个界面或界面的一部分进行布局。 56 | 57 | 计算样式的第一部分是创建一组匹配选择器,也就是计算出给指定元素应用哪些类、伪选择器和 ID。第二部分涉及从匹配选择器中获取所有样式规则,并计算出此元素的最终样式。 58 | 59 | 概要: 60 | 61 | * 降低选择器的复杂度;使用 [BEM](http://getbem.com/introduction/) 这种以类为中心的方法论。 62 | * 为数量多且样式相同的组件预先生成样式哈希值,以减少重复的样式匹配。 63 | * 为含有大量子组件的容器组件设置更新规则,告诉 LCUI 是否需要缓存样式表、是否仅更新可见的子组件、哪些变动可以忽略等。 64 | * 减少需要计算其样式的组件数量。 65 | 66 | ### 布局 67 | 68 | 布局是计算各组件几何信息的过程:组件的大小以及在界面中的位置。根据所用的 CSS、组件的内容或父级组件,每个组件都将有显式或隐含的大小信息。 69 | 70 | 与样式计算相似,布局开销的直接考虑因素如下: 71 | 72 | 1. 需要布局的元素数量。 73 | 2. 这些布局的复杂度。 74 | 75 | 概要: 76 | 77 | * 组件的数量将影响性能;应尽可能避免触发重新布局。 78 | * 评估布局模型的性能;弹性盒子(Flexbox)一般比块(Block)布局模型更慢。 79 | * 宽高为固定值的组件有着较低的重新布局成本;这种组件在重新布局时无需再遍历子组件树来计算内容宽高,而且能减少因其父组件和子组件的几何属性变化而触发的重新布局次数。 80 | 81 | ### 绘制 82 | 83 | 绘制是填充像素的过程,像素最终合成到用户的屏幕上。 它往往是管道中运行时间最长的任务,应尽可能避免此任务。 84 | 85 | 概要: 86 | 87 | * 更改任何属性始都会触发绘制。 88 | * 绘制通常是像素管道中开销最大的部分;应尽可能避免绘制。 89 | * 大量且尺寸小的绘制区域会降低绘制性能,但好在 LCUI 已经针对这种情况做了区域合并和多个区域并行绘制等优化,大部分情况下你不用考虑这个问题。 90 | * 设置 opacity 和 box-shadow 属性会触发使用独立渲染层,组件及其子组件都被在该渲染层中绘制,在全部绘制完后,该渲染层的内容才会被合成到目标面上。 91 | 92 | ### 合成 93 | 94 | 由于 LCUI 还未引入动画和变换系统,我们先暂时跳过这方面的讲解。 95 | 96 | ### 参考资料 97 | 98 | 本文的内容结构和表达方式参考自《[渲染性能 \| Web \| Google Developer](https://developers.google.cn/web/fundamentals/performance/rendering)》。 99 | 100 | ### 待办事项 101 | 102 | **添加** `LCUI_RequestAnimationFrame()` **函数** 103 | 104 | 参考 [window.requestAnimationFrame](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame) 的设计。 105 | 106 | **添加渲染性能监视器** 107 | 108 | 详见 [\#192](https://github.com/lc-soft/LCUI/issues/192) 中的内容。 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /widget/style.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍组件的样式表操作方法和相关该概念。 3 | --- 4 | 5 | # 样式 6 | 7 | 每个组件都有自定义样式、已匹配样式、最终样式和已计算样式共四张样式表,其中已计算样式由最终样式计算而来,而最终样式则是已匹配样式与自定义样式合并而来的。 8 | 9 | 当组件的自定义样式、状态、结构以及样式库有改动的时候会触发样式计算,计算过程是先从样式库中查询与组件匹配的样式表,然后合并为一张样式表作为组件的已匹配样式,之后将已匹配样式与自定义样式合并成一张样式表作为最终样式,最后将最终样式转换为已计算样式,以供布局引擎和渲染引擎使用。 10 | 11 | {% hint style="warning" %} 12 | 除自定义样式外,其它样式表都是只读的,你不应该修改它们。 13 | {% endhint %} 14 | 15 | ### 已匹配样式 16 | 17 | 已匹配样式是样式库中所有与组件匹配的样式表的合集,可通过组件的 `inherited_style` 成员访问它,它的生成过程是先在样式库中找到与组件匹配的选择器,然后再合并该选择器关联的样式表,如果组件有样式哈希值,则已匹配样式还会被缓存起来,以供其它同类组件使用,减少重复的样式匹配过程。 18 | 19 | 这张样式表仅在样式计算阶段用到,大多数情况下你用不到它。 20 | 21 | ### 自定义样式 22 | 23 | 自定义样式记录了该组件专属的样式,具有比已匹配样式更高的优先级,可通过组件的 `custom_style` 成员访问它,常见的使用场景是在实现动画、拖拽操作或是自定义布局时动态修改组件的坐标、宽高、透明度等样式。为了节省内存占用,它采用了链表结构来存储样式数据。 24 | 25 | 自定义样式的操作示例: 26 | 27 | ```c 28 | Widget_SetStyle(w, key_width, 100, px); 29 | Widget_SetStyleString(w, "width", "100px"); 30 | 31 | if (Widget_CheckStyleValid(w, key_width)) { 32 | Widget_UnsetStyle(w, key_width); 33 | } 34 | ``` 35 | 36 | 有些常用的样式操作可以改用更精简更具语义的函数代替,示例: 37 | 38 | ```c 39 | Widget_Move(w, 10, 10); 40 | Widget_Resize(w, 100, 200); 41 | Widget_SetPadding(w, 10, 20, 10, 20); 42 | Widget_SetMargin(w, 5, 5, 5, 5); 43 | Widget_SetBorder(w, 1, SV_SOLID, RGB(200, 200, 200)); 44 | Widget_SetBoxShadow(w, 0, 0, 4, ARGB(30, 0, 0, 0)); 45 | Widget_Show(w); 46 | Widget_Hide(w); 47 | ``` 48 | 49 | ### 最终样式 50 | 51 | 最终样式是已匹配样式与自定义样式合并后的产物,它记录了组件用到的全部 CSS 属性,可通过组件的 `style` 成员访问它。出于数据的完整性和读写性能的考虑,它采用了内存占用较大的数组来存储样式数据。 52 | 53 | 最终样式是 `LCUI_StyleSheet` 类型的,你可以用样式表的辅助方法操作它: 54 | 55 | ```c 56 | LCUI_Widget w = LCUIWidget_New(NULL); 57 | LCUI_Style style = StyleSheet_GetStyle(w->style, key_width); 58 | 59 | if (style.is_valid && style.type == LCUI_STYPE_PX) { 60 | printf("widget width: %fpx\n", style.val_px); 61 | } 62 | ``` 63 | 64 | ### 已计算样式 65 | 66 | 已计算样式是最终样式经计算后的产物,它记录了组件用到的全部 CSS 属性的实际值,可通过组件的 `computed_style` 成员访问它。相比最终样式,在访问它的属性时无需判断属性的有效性和属性值的类型,而且可供布局引擎和渲染引擎直接使用。 67 | 68 | 常用操作示例: 69 | 70 | ```c 71 | // 给组件加上淡出动画 72 | void FadeOutAnimation_OnFrame(LCUI_Widget w) 73 | { 74 | float opacity = w->computed_style.opacity; 75 | 76 | if (w->computed_style.visible) { 77 | if (opacity > 0) { 78 | opacity -= 0.01; 79 | } 80 | if (opacity > 0) { 81 | Widget_SetOpacity(w, opacity); 82 | } else { 83 | Widget_Hide(w); 84 | } 85 | } 86 | } 87 | 88 | ... 89 | 90 | LCUI_SetInterval(5, FadeOutAnimation_OnFrame, w); 91 | 92 | ... 93 | ``` 94 | 95 | ### 待办事项 96 | 97 | **重新设计组件样式表的存储方式** 98 | 99 | 由于每个组件都要存储三张样式表,内存占用很大,尤其是组件数量达到上万数量级的时候,我们需要为样式表设计一个内存占用更低的存储方式。 100 | 101 | **移除组件结构体中的已计算样式** 102 | 103 | 组件结构体中的 `computed_style` 成员通常只在更新和渲染组件时使用,大小为 328 字节,可以尝试移除它,让它只在更新和渲染组件前临时创建以节省内存占用。 104 | 105 | **** 106 | -------------------------------------------------------------------------------- /zh-cn/README.md: -------------------------------------------------------------------------------- 1 | # LCUI 编程指南 2 | 3 | 本书讲述了 LCUI 相关概念知识以及基本用法,对于理论知识不会详细讲太多,示例代码都是 C 语言代码,因此,希望你已经具备了以下几点要求: 4 | 5 | * 了解计算机组成原理和计算机操作系统中的基本常识 6 | * 熟悉 C 语言,有较多的编程经验,能够快速理解代码意图 7 | * 能够解决编译器在编译时给出大部分错误 8 | * 熟悉开发环境的配置,能解决常见环境配置问题 9 | * 熟练掌握第三方依赖库的编译方法,能解决常见的依赖问题 10 | 11 | 相信很多人已经习惯于看到那些活跃的开源项目,也习惯于向开源社区索取着各种资源,这些项目有着完善的文档、丰富的示例、活跃的社区还有成群的贡献者一起参与项目,即使自己什么都不用做,他们也能积极的发展下去。然而 LCUI 只是个个人项目,并未拥有这些资源,LCUI 的大多数功能都是按作者的需求而开发的,因此,如果你有什么需求,请先阅读以下内容。 12 | 13 | * 遇到问题时请尽量花时间自己独立解决,实在无法解决的话,请再花些时间准备好详细的问题描述,然后按照下面给出的方法提交问题。 14 | * 普通的使用问题,请在[开源中国社区(问答板块)](https://www.oschina.net/question/tag/LCUI)、[SegmentFault](https://segmentfault.com/search?q=LCUI) 或 [Stackoverflow](https://stackoverflow.com/search?q=LCUI) 上提交问题并 @ 作者,这样作者在帮助你解决问题后至少能赚点积分/声望值,还能让搜索引擎多收录一条 LCUI 相关的内容,帮助其他遇到类似问题的人。 15 | * BUG、新功能建议、代码改进建议等核心开发相关的问题请提交至 GitHub 的 [Issues](https://github.com/lc-soft/LCUI/issues) 页面中。建议你使用英语撰写内容,因为 LCUI 不仅仅面向国内程序员。 16 | * 如果你想提交自己的代码改进方案,请先向作者确认这个方案是否符合要求,确认之后再开始编写代码,务必遵循[现有代码风格](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/docs/CodingStyle.zh-cn.md)。在代码完成后,请提交拉取请求\(Pull Request\),让作者决定是否拉取你的分支里的代码并合并至主分支,必要的话请补充单元测试。 17 | * 发现本书存在错别字、示例代码有误等一些缺陷,可以在 [Github](https://github.com/lc-soft/LCUI-Guide) 上帮助改进此文档。本书在讲述 LCUI 的一些功能特性说明时,会顺便提及现存的缺陷和局限性,如果你有相关问题的解决经验并能够给出不错的改进方案,可以向作者提供技术支持。 18 | * 如果你需要一个成熟稳定的图形界面解决方案,请使用主流的图形界面开发库。 19 | 20 | -------------------------------------------------------------------------------- /zh-cn/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "刘超", 3 | "description": "本书讲述了 LCUI 相关概念和知识,以及基本用法。", 4 | "generator": "site", 5 | "links": { 6 | "sidebar": { 7 | "LCUI 项目主页": "https://lcui.lc-soft.io", 8 | "参与改进此文档": "https://github.com/lc-soft/LCUI-Guide", 9 | "QQ交流群": "https://lcui.lc-soft.io/verify.html" 10 | } 11 | }, 12 | "language": "zh", 13 | "title": "LCUI 编程指南" 14 | } 15 | -------------------------------------------------------------------------------- /zh-cn/css.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | CSS 的英文全称是 Cascading Style Sheets,即:层叠样式表,在 LCUI 中,图形界面元素的样式可以靠 CSS 代码来描述,“样式”指的是元素的视觉效果,包括元素的位置、尺寸、间距、背景色、边框、阴影等,这些样式都存储在样式表中,每个元素都有一张样式表。使用 CSS 能够解决内容与表现分离的问题,如需改变界面效果,只需要简单的修改 CSS 代码,无需修改应用程序的实现代码。 4 | 5 | LCUI 的 CSS 解析器与浏览器中的有一定的差异,受限于 LCUI 现有的功能,很多样式属性都不支持,而作者并不打算让 LCUI 成为一个浏览器,毕竟这样做意义并不大,所以,请不要尝试将网上那些能够实现酷炫效果的 CSS 代码放在 LCUI 应用程序上使用。如果想了解目前支持哪些样式属性,可以查阅 `src/gui/css_parser.c`。 6 | 7 | 以下是目前支持的功能: 8 | 9 | * 通用选择器:`* { display: block; }` 10 | * type 选择器:`textview { font-size: 14px; }` 11 | * class 选择器:`.button { padding: 5px 10px; }` 12 | * id 选择器:`#name { background-color: #fff; }` 13 | * 交集选择器:`textview#test.link { color: #f00; }` 14 | * 并集选择器:`textview, textedit { line-height: 1.42; }` 15 | * 后代元素选择器:`.container button .text { border: 1px solid #000; }` 16 | * 伪类选择器:`button:hover { background-color: #eee; }` 17 | * 选择器权重值计算。样式表的选择器具体性越明确,权重值越高。 18 | * 样式表层叠。当多张表都有定义相同样式时,能够根据它们的权重决定优先使用哪个样式。 19 | 20 | 不支持的有: 21 | 22 | * 继承。这个功能在作者的实际开发中并不是特别需要,常见的继承属性是 font-family、font-size、line-height、color 等,这类属性与文字样式相关,即使不支持继承也能靠其它方法控制文字样式,通常的做法是为 textview 部件设置全局样式,作为正文默认的样式,然后为标题、副标题等内容设置额外的样式类,如:`.title`、`.subtitle` 等。 23 | 24 | (待续...) 25 | 26 | -------------------------------------------------------------------------------- /zh-cn/css/README.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | CSS 的英文全称是 Cascading Style Sheets,即:层叠样式表,在 LCUI 中,图形界面元素的样式可以靠 CSS 代码来描述,“样式”指的是元素的视觉效果,包括元素的位置、尺寸、间距、背景色、边框、阴影等,这些样式都存储在样式表中,每个元素都有一张样式表。使用 CSS 能够解决内容与表现分离的问题,如需改变界面效果,只需要简单的修改 CSS 代码,无需修改应用程序的实现代码。 4 | 5 | LCUI 的 CSS 解析器与浏览器中的有一定的差异,受限于 LCUI 现有的功能,很多样式属性都不支持,而作者并不打算让 LCUI 成为一个浏览器,毕竟这样做意义并不大,所以,请不要尝试将网上那些能够实现酷炫效果的 CSS 代码放在 LCUI 应用程序上使用。如果想了解目前支持哪些样式属性,可以查阅 `src/gui/css_parser.c`。 6 | 7 | 以下是目前支持的功能: 8 | 9 | - 通用选择器:`* { display: block; }` 10 | - type 选择器:`textview { font-size: 14px; }` 11 | - class 选择器:`.button { padding: 5px 10px; }` 12 | - id 选择器:`#name { background-color: #fff; }` 13 | - 交集选择器:`textview#test.link { color: #f00; }` 14 | - 并集选择器:`textview, textedit { line-height: 1.42; }` 15 | - 后代元素选择器:`.container button .text { border: 1px solid #000; }` 16 | - 伪类选择器:`button:hover { background-color: #eee; }` 17 | - 选择器权重值计算。样式表的选择器具体性越明确,权重值越高。 18 | - 样式表层叠。当多张表都有定义相同样式时,能够根据它们的权重决定优先使用哪个样式。 19 | 20 | 不支持的有: 21 | 22 | - 继承。这个功能在作者的实际开发中并不是特别需要,常见的继承属性是 font-family、font-size、line-height、color 等,这类属性与文字样式相关,即使不支持继承也能靠其它方法控制文字样式,通常的做法是为 textview 部件设置全局样式,作为正文默认的样式,然后为标题、副标题等内容设置额外的样式类,如:`.title`、`.subtitle` 等。 23 | 24 | (待续...) 25 | -------------------------------------------------------------------------------- /zh-cn/font_and_text/README.md: -------------------------------------------------------------------------------- 1 | # 字体与文字 2 | 3 | (待完善) 4 | 5 | -------------------------------------------------------------------------------- /zh-cn/font_and_text/font_manage.md: -------------------------------------------------------------------------------- 1 | # 字体管理 2 | 3 | (待完善) 4 | 5 | -------------------------------------------------------------------------------- /zh-cn/font_and_text/render_char.md: -------------------------------------------------------------------------------- 1 | # 渲染一个字 2 | 3 | 以下程序的功能是渲染一个红色的“字”并将它保存至 PNG 文件中,使用的字体为宋体。 4 | 5 | ```c 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int main( void ) 12 | { 13 | int ret, fid; 14 | LCUI_Graph img; 15 | LCUI_FontBitmap bmp; 16 | LCUI_Pos pos = {25, 25}; 17 | 18 | /* 初始化字体处理功能 */ 19 | LCUI_InitFont(); 20 | 21 | /* 创建一个图像,并使用灰色填充 */ 22 | Graph_Init( &img ); 23 | Graph_Create( &img, 100, 100 ); 24 | Graph_FillRect( &img, RGB( 240, 240, 240 ), NULL, FALSE ); 25 | 26 | /* 载入字体文件 */ 27 | ret = LCUIFont_LoadFile( "C:/Windows/fonts/simsun.ttc" ); 28 | while( ret == 0 ) { 29 | /* 获取字体ID */ 30 | fid = LCUIFont_GetId( "SimSun", NULL ); 31 | if( fid < 0 ) { 32 | break; 33 | } 34 | /* 载入对应的文字位图,大小为 48 像素 */ 35 | ret = FontBitmap_Load( &bmp, L'字', fid, 48 ); 36 | if( ret != 0 ) { 37 | break; 38 | } 39 | /* 绘制红色文字到图像上 */ 40 | FontBitmap_Mix( &img, pos, &bmp, RGB( 255, 0, 0 ) ); 41 | Graph_WritePNG( "test_char_render.png", &img ); 42 | /* 释放内存资源 */ 43 | FontBitmap_Free( &bmp ); 44 | Graph_Free( &img ); 45 | break; 46 | } 47 | 48 | /* 退出字体处理功能 */ 49 | LCUI_ExitFont(); 50 | return ret; 51 | } 52 | ``` 53 | 54 | 编译运行后,可以在程序所在工作目录下找到 test\_char\_render.png 文件,打开它可看到如下图所示的内容: 55 | 56 |  57 | 58 | 在绘制文字时都需要指定字体的 ID,这个 ID 标识了字体的字族和风格,如果要使用默认的字体,可以使用 -1 作为 ID。字体位图数据是 `LCUI_FontBitmap` 类型,可以用 `FontBitmap_Mix()` 函数将该字体位图绘制到指定的图像上,该函数支持自定义字体颜色。 59 | 60 | -------------------------------------------------------------------------------- /zh-cn/font_and_text/render_icon_font.md: -------------------------------------------------------------------------------- 1 | # 图标字体 2 | 3 | 图标在图形界面中很常见,以往的软件界面中的图标都是一张张位图,颜色和大小都是固定的,如果想让图标颜色能够根据状态来变化,还要多准备几张位图,按钮就是个典型的例子,它有正常、高亮、按下、禁用这几种状态,一旦按钮多起来的话也不好处理,而且搜集图标也比较麻烦,还需要风格统一。 4 | 5 | 在扁平化设计流行的今天,扁平风格的图标是必不可少的,图标字体能够通过 color 和 font-size 属性设置任何颜色和大小,具备良好的灵活性,在很多网站和手机应用程序的界面中都有应用。图标字体本质上也是字体,常见的文字字体是一个 unicode 码(字符码)对应一个字形,而图标字体是一个 unicode 码对应一个图标。 6 | 7 |  8 | 9 | 通常在程序中直接使用图标字体需要先查找图标对应的 unicode 码然后写在代码里,虽然可以靠宏定义代替图标的 unicode 码来提升代码可读性,但这种做法依然比较麻烦。幸好 LCUI 支持 CSS,我们可以靠预先定义在 CSS 代码中的样式类(class)来使用图标,图标样式类相比于 unicode 码语意明确,书写更直观,可以很容易分辨这个图标大致是什么内容,并且在向图标字体增加新图标后只需要更新 CSS 代码。 10 | 11 | 常见的图标字体有 [FontAwesome](http://fontawesome.io/icons/) 和 [Material Design Icons](https://materialdesignicons.com/),如果觉得这些图标不够用,或者用不到这么多图标想精简体积,可以试试在线图标打包服务,例如:[Iconmoon](https://icomoon.io/)、[阿里巴巴矢量图标库](http://www.iconfont.cn/)。 12 | 13 | 以 FontAwesome 为例,从它的官方网站上下载压缩包,解压后打开其中的 `css/font-awesome.css` 文件,内容大致如下: 14 | 15 | ```css 16 | @font-face { 17 | font-family: 'FontAwesome'; 18 | ... 19 | } 20 | .fa { 21 | ... 22 | } 23 | ... 24 | .fa-search:before { 25 | content: "\f002"; 26 | } 27 | .fa-heart:before { 28 | content: "\f004"; 29 | } 30 | .fa-star:before { 31 | content: "\f005"; 32 | } 33 | .fa-user:before { 34 | content: "\f007"; 35 | } 36 | ... 37 | ``` 38 | 39 | 以上 css 代码在 LCUI 中并不起作用,需要做些修改,删除掉多余的样式,保留选择器以 `.fa-` 开头且只有 content 属性的样式表,然后删除 `:before` 伪类,修改后的内容大致如下: 40 | 41 | ```css 42 | .fa { 43 | font-family: 'FontAwesome'; 44 | } 45 | ... 46 | .fa-search { 47 | content: "\f002"; 48 | } 49 | .fa-heart { 50 | content: "\f004"; 51 | } 52 | .fa-star { 53 | content: "\f005"; 54 | } 55 | .fa-user { 56 | content: "\f007"; 57 | } 58 | ... 59 | ``` 60 | 61 | 之后将 font-awesome.css 和 fontawesome-webfont.ttf 文件复制到应用程序的目录下,并在应用程序中引入它们。引入方式有两种,一种是写在 xml 文件里,另一种是直接在应用程序代码中引入。 62 | 63 | ```markup 64 | 65 | 66 | 67 | 68 | 69 | ... 70 | 71 | 72 | ``` 73 | 74 | ```c 75 | ... 76 | #include 77 | ... 78 | LCUIFont_LoadFile( "fontawesome-webfont.ttf" ); 79 | LCUI_LoadCSSFile( "font-awesome.css" ); 80 | ... 81 | ``` 82 | 83 | 引入后,用 textview 部件呈现图标,通过设置样式类控制它呈现何种图标。 84 | 85 | ```c 86 | LCUI_Widget icon = LCUIWidget_New( NULL ); 87 | Widget_AddClass("fa fa-bicycle"); // 自行车 88 | Widget_AddClass("fa fa-car"); // 汽车 89 | Widget_AddClass("fa fa-taxi"); // 出租车 90 | Widget_AddClass("fa fa-ship"); // 船 91 | Widget_AddClass("fa fa-plane"); // 飞机 92 | ``` 93 | 94 | 想知道这些图标的具体效果的话,可以到该字体图标的官网上预览。 95 | 96 | \(未完待续...\) 97 | 98 | -------------------------------------------------------------------------------- /zh-cn/font_and_text/render_string.md: -------------------------------------------------------------------------------- 1 | # 渲染一段文字 2 | 3 | 绘制一个文字比较容易,但用绘制一个文字的方法去绘制一段文字的话,实现起来会很复杂,针对这个问题,可以用 LCUI 提供的文本图层(TextLayer)来解决,除了基本的文本绘制功能外,还具备一下功能: 4 | 5 | * 自定义全局文字的对齐方式 6 | * 用标签设定其中一段文字的颜色和大小 7 | * 文本的插入、删除功能,以及光标定位 8 | 9 | LCUI 的文本显示(TextView)部件和文本编辑框(TextEdit)部件就是基于该模块实现的。 10 | 11 | 以下是简单的示例程序: 12 | 13 | ```c 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | int main( void ) 20 | { 21 | int ret; 22 | LCUI_Graph img; 23 | LCUI_Pos pos = {0, 80}; 24 | LCUI_Rect area = {0, 0, 320, 240}; 25 | LCUI_TextLayer txt = TextLayer_New(); 26 | LCUI_TextStyle txtstyle; 27 | 28 | /* 初始化字体处理功能 */ 29 | LCUI_InitFont(); 30 | 31 | /* 创建一个图像,并使用灰色填充 */ 32 | Graph_Init( &img ); 33 | Graph_Create( &img, 320, 240 ); 34 | Graph_FillRect( &img, RGB( 240, 240, 240 ), NULL, FALSE ); 35 | 36 | /* 设置文本的字体大小 */ 37 | TextStyle_Init( &txtstyle ); 38 | txtstyle.pixel_size = 24; 39 | txtstyle.has_pixel_size = TRUE; 40 | 41 | /* 设置文本图层的固定尺寸、文本样式、文本内容、对齐方式 */ 42 | TextLayer_SetFixedSize( txt, 320, 240 ); 43 | TextLayer_SetTextStyle( txt, &txtstyle ); 44 | TextLayer_SetTextAlign( txt, SV_CENTER ); 45 | TextLayer_SetTextW( txt, L"这是一段测试文本\nHello, World!", NULL ); 46 | TextLayer_Update( txt, NULL ); 47 | 48 | /* 将文本图层绘制到图像中,然后将图像写入至 png 文件中 */ 49 | TextLayer_DrawToGraph( txt, area, pos, &img ); 50 | ret = Graph_WritePNG( "test_string_render.png", &img ); 51 | Graph_Free( &img ); 52 | 53 | /* 退出字体处理功能 */ 54 | LCUI_ExitFont(); 55 | return ret; 56 | } 57 | ``` 58 | 59 | 编译运行后,可以在程序所在工作目录下找到 test\_string\_render.png 文件,打开它可看到如下图所示的内容: 60 | 61 |  62 | 63 | 在为 TextLayer 设置文本、修改文字样式后,需要调用 `TextLayer_Update()` 函数以应用这些更改,该函数的第二个参数是个链表,用于保存文本图层中需要刷新的区域,如果不需要这些数据可以将该参数设置为 NULL。 64 | 65 | TextLayer 提供了 `TextLayer_DrawToGraph()` 函数用于将文本图层绘制到图像中,第二个参数指定 TextLayer 中需要绘制的区域,第三个参数指定绘制出的内容在图像中的位置。 66 | 67 | -------------------------------------------------------------------------------- /zh-cn/getting_started/README.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | 本文会介绍 LCUI 的基本用法,以便快速上手。这里假定你已经配置好了编译 LCUI 应用程序所需的开发环境,否则请先阅读《[安装](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/install.md)》。 4 | 5 | -------------------------------------------------------------------------------- /zh-cn/getting_started/step1.md: -------------------------------------------------------------------------------- 1 | # 一个最小的应用 2 | 3 | 按常规套路,第一个程序从输出 "Hello, World!" 开始,那么首先,你需要新建一个 helloworld.c 文件,写好 main\(\) 函数,然后引入 LCUI 的基础头文件: 4 | 5 | ```c 6 | #include 7 | #include 8 | 9 | int main(void) 10 | { 11 | return 0; 12 | } 13 | ``` 14 | 15 | LCUI\_Build.h 中主要包含 LCUI 在编译时的相关宏定义,而 LCUI.h 中主要包含 LCUI 应用程序必要的数据结构定义以及常用函数声明。 16 | 17 | 接下来,需要一个容器能够装载 "Hello, World!" 字符串并将它呈现在屏幕上。为了实现这一功能,我们可以使用 LCUI 自带的文本显示(TextView)部件来扮演容器这一角色,但在此之前,我们需要引入依赖的头文件: 18 | 19 | ```c 20 | #include 21 | #include 22 | ``` 23 | 24 | 在使用 LCUI 的功能前,我们需要调用 `LCUI_Init()` 对各个功能进行初始化: 25 | 26 | ```c 27 | ... 28 | int main(void) 29 | { 30 | LCUI_Init(); 31 | return 0; 32 | } 33 | ``` 34 | 35 | 之后,定义一个名为 txt 的 LCUI\_Widget 对象,然后调用 `LCUIWidget_New()` 创建一个类型为 textview 部件对象,并用 txt 保存该对象的引用: 36 | 37 | ```c 38 | ... 39 | int main(void) 40 | { 41 | LCUI_Widget txt; 42 | 43 | LCUI_Init(); 44 | txt = LCUIWidget_New("textview"); 45 | return 0; 46 | } 47 | ``` 48 | 49 | 得到 TextView 部件后,调用该类型部件提供的 `TextView_SetText()` 函数来设置它呈现的文本: 50 | 51 | ```c 52 | ... 53 | int main(void) 54 | { 55 | LCUI_Widget txt; 56 | 57 | LCUI_Init(); 58 | root = LCUIWidget_GetRoot(); 59 | txt = LCUIWidget_New("textview"); 60 | TextView_SetText(txt, "Hello, World!"); 61 | return 0; 62 | } 63 | ``` 64 | 65 | 至此,"Hello, World!" 的容器已经准备好了,但它并不会显示在屏幕上,因为 LCUI 只渲染根(Root)部件中的内容,我们需要先调用 `LCUIWidget_GetRoot()` 获取根部件,然后调用 `Widget_Append()` 将 TextView 部件追加到根部件里: 66 | 67 | ```c 68 | ... 69 | int main(void) 70 | { 71 | LCUI_Widget root, txt; 72 | 73 | LCUI_Init(); 74 | root = LCUIWidget_GetRoot(); 75 | txt = LCUIWidget_New("textview"); 76 | TextView_SetText(txt, "Hello, World!"); 77 | Widget_Append(root, txt); 78 | return 0; 79 | } 80 | ``` 81 | 82 | 最后,我们需要调用 `LCUI_Main()` 让此应用程序进入主循环以执行后续产生的各种任务,其中包括键盘、鼠标等输入事件处理,以及图形界面的更新与渲染工作,顺便让程序保持运行。 83 | 84 | ```c 85 | #include 86 | #include 87 | #include 88 | #include 89 | 90 | int main(void) 91 | { 92 | LCUI_Widget root, txt; 93 | 94 | LCUI_Init(); 95 | root = LCUIWidget_GetRoot(); 96 | txt = LCUIWidget_New("textview"); 97 | TextView_SetText(txt, "Hello, World!"); 98 | Widget_Append(root, txt); 99 | return LCUI_Main(); 100 | } 101 | ``` 102 | 103 | 编译并运行这个程序,你会看到如下效果: 104 | 105 |  106 | 107 | -------------------------------------------------------------------------------- /zh-cn/getting_started/step2.md: -------------------------------------------------------------------------------- 1 | # 改进视觉效果 2 | 3 | 上个程序只实现了简单的文本显示,接下来将介绍如何调整文字的位置、大小、颜色。 4 | 5 | ```c 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int main( int argc, char **argv ) 12 | { 13 | LCUI_Widget root, txt; 14 | LCUI_Color bdcolor = RGB( 0, 0, 0 ); 15 | LCUI_Color bgcolor = RGB( 245, 245, 245 ); 16 | 17 | LCUI_Init(); 18 | root = LCUIWidget_GetRoot(); 19 | txt = LCUIWidget_New( "textview" ); 20 | Widget_SetMargin( txt, 25, 0, 0, 25 ); 21 | Widget_SetPadding( txt, 25, 0, 0, 0 ); 22 | Widget_SetBorder( txt, 1, SV_SOLID, bdcolor ); 23 | Widget_SetStyle( txt, key_background_color, bgcolor, color ); 24 | TextView_SetText( txt, "[size=18px]Hello, World![/size]" ); 25 | TextView_SetTextAlign( txt, SV_CENTER ); 26 | Widget_Resize( txt, 200, 100 ); 27 | Widget_Append( root, txt ); 28 | return LCUI_Main(); 29 | } 30 | ``` 31 | 32 | 在原有的代码上补充了几行代码,运行效果如下: 33 | 34 |  35 | 36 | 为了缩短后面代码的宽度,预先定义了 bdcolor 和 bgcolor 两个变量,分别保存边框色和背景色。 37 | 38 | 在创建好部件后,调用 `Widget_SetMargin()`、`Widget_SetPadding()` 和 `Widget_SetBorder()` 这三个函数分别设置 txt 部件的外间距、内间距和边框,内间距和外间距有上、右、下、左这四个,`Widget_SetMargin()` 和 `Widget_SetPadding()` 函数的后面四个参数也是对应这个顺序的。`Widget_SetBorder()` 函数的后三个参数分别表示边框的大小、风格、颜色,目前边框风格只支持实线(solid),与之对应的值为 SV\_SOLID。 39 | 40 | 之后调用 `Widget_SetStyle()` 函数设置 txt 部件的背景色。`Widget_SetStyle()` 是一个宏,主要用于简化部件样式表的修改操作,它的第二个参数是样式属性的编号,这些编号也就是样式属性在样式表中的位置,为方便记忆和识别,已经将它们定义成以 key\_ 开头的全小写命名的枚举,你可以在 gui/css\_library.h 中找到它们的定义。第三个和第四个参数分别是属性值和属性值的类型,这里就不做过多的说明了。 41 | 42 | 在为 txt 部件设置的文本中包含了 `[size=18px]`,它表示的是将字体的大小设置为 18 像素。TextView 部件默认支持样式标签,但当前支持的样式标签只有 `[size]` 和 `[color]`,你可以直接在文本内使用它们来设置简单的样式。 43 | 44 | `TextView_SetTextAlign()` 函数可以为 TextView 部件设置文本对齐方式,支持的对齐方式有三种:靠左(SV\_LEFT)、居中(SV\_CENTER)、靠右(SV\_RIGHT),SV 是样式值(Style Value)的缩写。 45 | 46 | `Widget_Resize()` 函数用于调整部件的尺寸,这里将部件的宽高分别为 200 像素和 100 像素。 47 | 48 | 看了上面的代码你可能会想:为什么修改部件的坐标需要设置外间距?不是直接设置 xy 坐标就可以了吗? 49 | 50 | LCUI 有提供 `Widget_Move()` 函数,用于修改部件的坐标,但对于默认定位方式的部件来说,这个函数并没有任何效果。LCUI 的布局方式和浏览器中的网页布局类似,部件在默认的定位方式下,能够影响坐标的只有外间距,除非更改该部件的定位方式,具体内容在后面的章节中会介绍。 51 | 52 | -------------------------------------------------------------------------------- /zh-cn/getting_started/step3.md: -------------------------------------------------------------------------------- 1 | # 简化界面描述代码 2 | 3 | 上一章节介绍了如何用 C 代码描述界面效果,只是实现简单的效果的话,这代码看上去还行,但如果需要描述复杂点的界面的话,写出来的 C 代码会变得臃肿且难以阅读和维护,为解决这个问题,可以改用 XML 和 CSS。以下是 CSS 代码,请将其保存为 helloworld.css 文件。 4 | 5 | ```css 6 | textview.text-hello { 7 | color: #8cc63f; 8 | font-size: 18px; 9 | font-family: "Comic Sans MS"; 10 | text-align: center; 11 | padding: 25px; 12 | margin: 25px 0 0 25px; 13 | border: 1px solid #000; 14 | background-color: #aaa; 15 | } 16 | ``` 17 | 18 | 这里的 CSS 代码对于有写过网页的人来说应该很容易理解,如果你对 CSS 代码的语法规则并不了解,可以参考网上的相关教程。LCUI 虽然支持 CSS 代码,但与网页浏览器不同,只支持处理简单的 CSS 样式,并且某些 CSS 样式的实际效果会根据 LCUI 的现有情况做一定的调整,属于定制版的 CSS。 19 | 20 | `textview.text-hello` 是选择器,其中 textview 指的是部件类型,而 `.text-hello` 指的是样式类,也就是说 `{}` 花括号里的 CSS 样式只对拥有 text-hello 类的 textview 部件有效。 21 | 22 | color、font-size、font-family、text-align 这四个属性是 textview 部件扩展的属性,仅对 textview 类型的部件有效,分别用于设置文字的颜色、字体大小、字族名称、对齐方式。 23 | 24 | 新建 helloworld.xml 文件,保存以下代码: 25 | 26 | ```markup 27 | 28 | 29 | 30 | 31 | 32 | Hello, World! 33 | 34 | 35 | ``` 36 | 37 | 以下是这段 XML 代码的说明: 38 | 39 | * 第一行指定了 XML 的版本及文档编码方式。 40 | * <lcui-app> 标签用于表示里面的代码是针对 LCUI 应用程序的。 41 | * <resource> 标签用于指示 LCUI 需要加载的资源,type 属性表示资源的类型,src 属性表示资源文件的位置。 42 | * 加载 helloworld.css 资源文件,将其内容作为 CSS 文本来处理。 43 | * 加载 C:/Windows/Fonts/comic.ttf 资源文件,将其作为 ttf 字体文件来处理。 44 | * <ui> 标签用于容纳所有与界面相关的内容,一个 XML 文档中只能有一个 <ui> 标签,相当于 HTML 文档中的 <body> 标签。 45 | * <widget> 标签指示 LCUI 创建一个部件,type 属性表示部件类型,class 属性表示该部件拥有的样式类。<widget> 标签内可以嵌套文本,但文本是否有用取决于该类型的部件是否支持。 46 | * 创建一个 textivew 类型的部件,拥有 text-hello 样式类,并设置其文本内容为 Hello, World!。 47 | 48 | 接下来是主程序的实现代码: 49 | 50 | ```c 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | int main( int argc, char **argv ) 57 | { 58 | LCUI_Widget root, pack; 59 | 60 | LCUI_Init(); 61 | root = LCUIWidget_GetRoot(); 62 | pack = LCUIBuilder_LoadFile( "helloworld.xml" ); 63 | if( !pack ) { 64 | return -1; 65 | } 66 | Widget_Append( root, pack ); 67 | Widget_Unwrap( pack ); 68 | return LCUI_Main(); 69 | } 70 | ``` 71 | 72 | 当有了 XML 和 CSS 文件后,需要让程序载入它们,XML 文件的载入与解析功能由 `LCUIBuilder_LoadFile()` 函数提供,该函数在 LCUI/gui/builder.h 头文件有声明。如果 XML 文件载入失败,`LCUIBuilder_LoadFile()` 函数会返回 NULL,如果载入成功则会返回一个部件,这个部件主要用于容纳 <ui> 标签中出现的各个部件,相当于一个容器,对于这个容器,可以先将它追加到根级部件中,然后调用 `Widget_Unwrap()` 函数展开该部件的内容,在展开后该部件会被销毁。 73 | 74 | 以下是程序的运行效果: 75 | 76 |  77 | 78 | -------------------------------------------------------------------------------- /zh-cn/getting_started/step4.md: -------------------------------------------------------------------------------- 1 | # 为界面添加用户交互 2 | 3 | 当前应用程序的界面还只是单纯的向用户展示信息,并不能根据用户的操作来做相应的事情,接下来将介绍如何让应用程序通过界面与用户进行交互。 4 | 5 | 首先,修改 XML 文件,添加按钮部件,为了能够在程序中操作它们,还需要为它们设置 ID,ID 内容可以由你自己定义,但 ID 必须是整个程序中唯一存在的。 6 | 7 | ```markup 8 | 9 | 10 | 11 | 12 | 13 | Hello, World! 14 | 确定 15 | 16 | 17 | ``` 18 | 19 | 接下来补充 CSS 代码,设置按钮的外间距,调整按钮的位置。 20 | 21 | ```css 22 | textview.text-hello { 23 | color: #8cc63f; 24 | font-size: 18px; 25 | font-family: "Comic Sans MS"; 26 | text-align: center; 27 | padding: 25px; 28 | margin: 25px 0 0 25px; 29 | border: 1px solid #000; 30 | background-color: #fafafa; 31 | } 32 | #btn-ok { 33 | margin: 25px 0 0 25px; 34 | } 35 | ``` 36 | 37 | 最后,补充事件绑定与事件响应代码: 38 | 39 | ```c 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | static void OnBtnClick( LCUI_Widget self, LCUI_WidgetEvent e, void *arg ) 47 | { 48 | LCUI_Widget txt = e->data; 49 | TextView_SetTextW( txt, L"第一个 LCUI 应用程序" ); 50 | } 51 | 52 | int main( int argc, char **argv ) 53 | { 54 | LCUI_Widget root, pack, btn, txt; 55 | 56 | LCUI_Init(); 57 | root = LCUIWidget_GetRoot(); 58 | pack = LCUIBuilder_LoadFile( "helloworld.xml" ); 59 | if( !pack ) { 60 | return -1; 61 | } 62 | Widget_Append( root, pack ); 63 | Widget_Unwrap( pack ); 64 | txt = LCUIWidget_GetById( "text-hello" ); 65 | btn = LCUIWidget_GetById( "btn-ok" ); 66 | Widget_BindEvent( btn, "click", OnBtnClick, txt, NULL ); 67 | return LCUI_Main(); 68 | } 69 | ``` 70 | 71 | 和常规的图形界面程序一样,LCUI 的应用程序也是事件驱动的,用户操作图形界面会产生事件,程序可以预先绑定这些事件,等事件触发后,程序就能响应这些事件并做相应的事情。 72 | 73 | 以上代码的功能是让按钮在点击后将 "hello, world!" 更改为 "第一个 LCUI 应用程序",代码具体说明如下: 74 | 75 | * 使用 `LCUIWidget_GetById()` 函数根据 ID 来获取需要操作的部件。 76 | * 为按钮绑定点击(click)事件,事件处理函数为 `OnBtnClick()`,附加的数据是 txt,该数据不需要析构函数,所以设为 NULL。 77 | * 在 `OnBtnClick()` 函数中,第一个参数是绑定该事件的部件,第二个参数是事件相关的数据,第三个是触发该事件时传递的额外参数,这个参数通常用不到。 78 | * 绑定事件时保存的附加数据存在于 data 成员变量中,即:`e->data` 。 79 | * `TextView_SetTextW()` 函数是 `TextView_SetText()` 函数的宽字符版本,它的第二个参数是类型为 `wchar_t*` 的指针,这里设置的文本内容包含中文,由于 `TextView_SetText()` 函数是默认将第二个参数作为 UTF-8 编码的字符串进行处理的,而 Windows 系统的编译器会将字符串以 ANSI 编码方式存储,为避免乱码,所以改用宽字符版本的 `TextView_SetTextW()` 函数。 80 | 81 | 以下是该应用程序的运行效果: 82 | 83 |  84 | 85 | 以上供用户操作的只有按钮,接下来将添加文本编辑框,让用户输入自己的内容。 86 | 87 | 修改 helloworld.xml 文件,添加文本编辑框,并设置初始文本为 Hello, World!。 88 | 89 | ```markup 90 | 91 | 92 | 93 | 94 | 95 | Hello, World! 96 | Hello, World! 97 | 确定 98 | 99 | 100 | ``` 101 | 102 | 修改 helloworld.css 文件,调整文本编辑框的外间距。 103 | 104 | ```css 105 | textview.text-hello { 106 | color: #8cc63f; 107 | font-size: 18px; 108 | font-family: "Comic Sans MS"; 109 | text-align: center; 110 | padding: 25px; 111 | margin: 25px 0 0 25px; 112 | border: 1px solid #000; 113 | background-color: #fafafa; 114 | } 115 | #btn-ok, #edit { 116 | margin: 25px 0 0 25px; 117 | } 118 | ``` 119 | 120 | 然后修改 helloworld.c 文件,让程序能够在按钮点击后取出文本编辑框内的文本,并将这些文本显示出来。 121 | 122 | ```c 123 | #include 124 | #include 125 | #include 126 | #include 127 | #include 128 | #include 129 | 130 | static void OnBtnClick( LCUI_Widget self, LCUI_WidgetEvent e, void *arg ) 131 | { 132 | wchar_t str[256]; 133 | LCUI_Widget edit = LCUIWidget_GetById( "edit" ); 134 | LCUI_Widget txt = LCUIWidget_GetById( "text-hello" ); 135 | TextEdit_GetTextW( edit, 0, 255, str ); 136 | TextView_SetTextW( txt, str ); 137 | } 138 | 139 | int main( int argc, char **argv ) 140 | { 141 | LCUI_Widget root, pack, btn; 142 | 143 | LCUI_Init(); 144 | root = LCUIWidget_GetRoot(); 145 | pack = LCUIBuilder_LoadFile( "helloworld.xml" ); 146 | if( !pack ) { 147 | return -1; 148 | } 149 | Widget_Append( root, pack ); 150 | Widget_Unwrap( pack ); 151 | btn = LCUIWidget_GetById( "btn-ok" ); 152 | Widget_BindEvent( btn, "click", OnBtnClick, NULL, NULL ); 153 | return LCUI_Main(); 154 | } 155 | ``` 156 | 157 | `TextEdit_GetTextW()` 函数用于取出文本编辑框内的文本,它的第二个参数是起始读取位置,第三个参数是文本最大长度,返回值为实际读取的文本长度。 158 | 159 | 以下是该应用程序的运行效果: 160 | 161 |  162 | 163 | -------------------------------------------------------------------------------- /zh-cn/gui_events/README.md: -------------------------------------------------------------------------------- 1 | # 事件 2 | 3 | (待完善) 4 | 5 | -------------------------------------------------------------------------------- /zh-cn/gui_events/touch.md: -------------------------------------------------------------------------------- 1 | # 触控事件 2 | 3 | LCUI 支持触控事件,但目前仅在 Windows 系统中有效,以下是触控事件的数据结构定义: 4 | 5 | ```c 6 | typedef struct LCUI_TouchPointRec_ { 7 | int x; 8 | int y; 9 | int id; 10 | int state; 11 | LCUI_BOOL is_primary; 12 | } LCUI_TouchPointRec, *LCUI_TouchPoint; 13 | 14 | typedef struct LCUI_TouchEvent_ { 15 | int n_points; 16 | LCUI_TouchPoint points; 17 | } LCUI_TouchEvent; 18 | 19 | typedef LCUI_TouchEvent LCUI_WidgetTouchEvent; 20 | ``` 21 | 22 | 触控事件的数据结构设计参考自 Windows API,只保留了主要的成员变量。LCUI 内部的触控事件和部件级的触控事件是一样的数据结构。 23 | 24 | 从上述代码中可以比较容易的理解到:触控事件包含多个触点的信息,n\_points 表示当前共有多少个触点,每个触点都有自己的 x、y 坐标,并且有个 id 用于标识该触点,而 state 表示该触点的状态,它的值有三种:WET\_TOUCHDOWN、WET\_TOUCHUP、WET\_TOUCHMOVE,这些值分别对应:触点按下、触点释放、触点移动这三个状态。 25 | 26 | 以下是测试触控事件的程序: 27 | 28 | ```c 29 | /** testtouch.c -- test touch support */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | /** 触点绑定记录 */ 39 | typedef struct TouchPointBindingRec_ { 40 | int point_id; /**< 触点 ID */ 41 | LCUI_Widget widget; /**< 部件 */ 42 | LinkedListNode node; /**< 在链表中的结点 */ 43 | LCUI_BOOL is_valid; /**< 是否有效 */ 44 | } TouchPointBindingRec, *TouchPointBinding; 45 | 46 | /** 触点绑定记录列表 */ 47 | static LinkedList touch_bindings; 48 | 49 | static void OnTouchWidget( LCUI_Widget w, LCUI_WidgetEvent e, void *arg ) 50 | { 51 | LCUI_TouchPoint point; 52 | TouchPointBinding binding; 53 | if( e->touch.n_points == 0 ) { 54 | return; 55 | } 56 | binding = e->data; 57 | point = &e->touch.points[0]; 58 | switch( point->state ) { 59 | case WET_TOUCHMOVE: 60 | Widget_Move( binding->widget, point->x - 32, point->y - 32 ); 61 | break; 62 | case WET_TOUCHUP: 63 | if( !binding->is_valid ) { 64 | break; 65 | } 66 | /* 当触点释放后销毁部件及绑定记录 */ 67 | Widget_ReleaseTouchCapture( binding->widget, -1 ); 68 | LinkedList_Unlink( &touch_bindings, &binding->node ); 69 | binding->is_valid = FALSE; 70 | Widget_Destroy( w ); 71 | free( binding ); 72 | break; 73 | case WET_TOUCHDOWN: 74 | default: break; 75 | } 76 | } 77 | 78 | static void OnTouch( LCUI_SysEvent e, void *arg ) 79 | { 80 | int i; 81 | LinkedListNode *node; 82 | LCUI_StyleSheet sheet; 83 | LCUI_TouchPoint point; 84 | for( i = 0; i < e->touch.n_points; ++i ) { 85 | TouchPointBinding binding; 86 | LCUI_BOOL is_existed = FALSE; 87 | point = &e->touch.points[i]; 88 | /* 检查该触点是否已经被绑定 */ 89 | LinkedList_ForEach( node, &touch_bindings ) { 90 | binding = node->data; 91 | if( binding->point_id == point->id ) { 92 | is_existed = TRUE; 93 | } 94 | } 95 | if( is_existed ) { 96 | continue; 97 | } 98 | /* 新建绑定记录 */ 99 | binding = NEW( TouchPointBindingRec, 1 ); 100 | binding->widget = LCUIWidget_New( NULL ); 101 | binding->point_id = point->id; 102 | binding->is_valid = TRUE; 103 | binding->node.data = binding; 104 | Widget_Resize( binding->widget, 64, 64 ); 105 | Widget_Move( binding->widget, point->x - 32, point->y - 32 ); 106 | /* 设置让该部件捕获当前触点 */ 107 | Widget_SetTouchCapture( binding->widget, binding->point_id ); 108 | Widget_BindEvent( binding->widget, "touch", OnTouchWidget, binding, NULL ); 109 | sheet = binding->widget->custom_style; 110 | SetStyle( sheet, key_position, SV_ABSOLUTE, style ); 111 | SetStyle( sheet, key_background_color, RGB( 255, 0, 0 ), color ); 112 | LinkedList_AppendNode( &touch_bindings, &binding->node ); 113 | Widget_Top( binding->widget ); 114 | } 115 | } 116 | 117 | int main( int argc, char **argv ) 118 | { 119 | LCUI_Init(); 120 | LinkedList_Init( &touch_bindings ); 121 | LCUI_BindEvent( LCUI_TOUCH, OnTouch, NULL, NULL ); 122 | return LCUI_Main(); 123 | } 124 | ``` 125 | 126 | 编译后运行,如果你的计算机自带触屏,可以用手指在程序窗口内点击、移动,会看到类似于如下图所示的效果: 127 | 128 |  129 | 130 | 这个程序实现的功能是捕获各个触点并用对应的红色方块表示触点的位置,有个 `touch_bindings` 全局变量用于保存各个触点和部件的绑定记录,在响应触控事件时会遍历每个触点,根据触点的 id 在绑定记录中找对应的部件,找到后会根据触点的坐标来更新部件的坐标。为了让部件能够捕获触点在它外面产生的触控事件,并且不被其它部件捕获到,调用了 `Widget_SetTouchCapture()` 函数实现对该触点的独占,在触点释放后,调用 `Widget_ReleaseTouchCapture()` 函数解除对触点的独占。 131 | 132 | -------------------------------------------------------------------------------- /zh-cn/gui_widgets/README.md: -------------------------------------------------------------------------------- 1 | # 部件 2 | 3 | (待完善) 4 | 5 | -------------------------------------------------------------------------------- /zh-cn/gui_widgets/data.md: -------------------------------------------------------------------------------- 1 | # 私有数据 2 | 3 | 一个部件的各种功能的实现都会用到数据,例如:文本编辑框,它会保存当前编辑的文本 内容,调用相关函数可以对这个文本内容进行读写操作,在绘制时也会需要用到这些文本 内容以在屏幕上绘制出相应的文字。 4 | 5 | 部件私有数据的操作函数有以下两个: 6 | 7 | ```c 8 | void *Widget_AddData( LCUI_Widget widget, LCUI_WidgetPrototype proto, size_t data_size ); 9 | void *Widget_GetData( LCUI_Widget widget, LCUI_WidgetPrototype proto ); 10 | ``` 11 | 12 | 从以上代码中可以看出部件私有数据有添加和获取这两种方法,私有数据是与部件原型 绑定的,添加时需要指定具体的内存占用大小。添加后,可以调用 `Widget_GetData()` 函数获取私有数据,这个函数也同样需要指定原型。 13 | 14 | 以下是这两个函数的基本用法示例: 15 | 16 | ```c 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** 部件私有数据的结构 */ 25 | typedef struct MyWidgetRec_ { 26 | int a; 27 | char b; 28 | double c; 29 | char *str; 30 | } MyWidgetRec, *MyWidget; 31 | 32 | static struct MyWidgetModule { 33 | LCUI_WidgetPrototype prototype; 34 | // 其它用得到的数据 35 | // xxxx 36 | // ... 37 | } self; 38 | 39 | static void MyWidget_OnInit( LCUI_Widget w ) 40 | { 41 | MyWidget data; 42 | const size_t size = sizeof( MyWidgetRec ); 43 | data = Widget_AddData( w, self.prototype, size ); 44 | // 初始化私有数据 45 | data->a = 123; 46 | data->b = 'b'; 47 | data->c = 3.1415926; 48 | data->str = malloc( 256 * sizeof(char) ); 49 | strcpy( data->str, "this is my widget." ); 50 | printf( "my widget is inited.\n" ); 51 | } 52 | 53 | static void MyWidget_OnDestroy( LCUI_Widget w ) 54 | { 55 | MyWidget data = Widget_GetData( w, self.prototype ); 56 | // 释放私有数据占用的内存资源 57 | free( data->str ); 58 | printf( "my widget is destroied.\n" ); 59 | } 60 | 61 | void LCUIWidget_AddMyWidget( void ) 62 | { 63 | int i; 64 | self.prototype = LCUIWidget_NewPrototype( "mywidget", NULL ); 65 | self.prototype->init = MyWidget_OnInit; 66 | self.prototype->destroy = MyWidget_OnDestroy; 67 | // 如果全局用得到的数据的话 68 | // self.xxxx = ??? 69 | } 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /zh-cn/gui_widgets/inherit.md: -------------------------------------------------------------------------------- 1 | # 继承 2 | 3 | 有时候会发现某个部件的功能不够用,想扩展一些新功能,但不想直接修改它的代码,也 不想重写一个新部件,这个时候可以使用原型的“继承”功能来创建一个该部件的扩展版本, 即能保留原部件的功能,又能使用新加的功能。 4 | 5 | 以 textview 部件为例,现在有这样的功能需求:能够支持设置网址链接,在被点击时会调用浏览器打 开这个链接,网址链接能靠 `href` 属性来设置,以下是示例代码: 6 | 7 | ```c 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef struct LinkRec_ { 15 | char *href; 16 | } LinkedRec, *Link; 17 | 18 | LCUI_WidgetPrototype prototype; 19 | 20 | static void Link_OnClick( LCUI_Widget w, LCUI_WidgetEvent e, void *arg ) 21 | { 22 | Link link = Widget_GetData( w, prototype ); 23 | if( link->href ) { 24 | // 调用浏览器打开链接 25 | // ... 26 | } 27 | } 28 | 29 | static void Link_OnInit( LCUI_Widget w ) 30 | { 31 | Link link; 32 | const size_t size = sizeof( LinkRec ); 33 | link = Widget_AddData( w, prototype, size ); 34 | 35 | link->href = NULL; 36 | Widget_BindEvent( w, "click", Link_OnClick, NULL, NULL ); 37 | // 调用父级原型的 init() 方法,继承父级现有的功能 38 | prototype->proto->init( w ); 39 | } 40 | 41 | static void Link_OnDestroy( LCUI_Widget w ) 42 | { 43 | Link link = Widget_GetData( w, prototype ); 44 | free( link->href ); 45 | prototype->proto->destroy( w ); 46 | } 47 | 48 | void Link_SetHref( LCUI_Widget w, const char *href ) 49 | { 50 | Link link = Widget_GetData( w, prototype ); 51 | if( link->href ) { 52 | free( link->href ); 53 | link->href = NULL; 54 | } 55 | if( href ) { 56 | size_t len = strlen( href ) + 1; 57 | link->href = malloc( len * sizeof( char ) ); 58 | strcpy( link->href, href ); 59 | } 60 | } 61 | 62 | static void Link_OnSetAttr( LCUI_Widget w, const char *name, const char *value ) 63 | { 64 | // 当 XML 解析器解析到的元素属性是 href 时 65 | if( strcmp( name, "href" ) == 0 ) { 66 | Link_SetHref( w, value ); 67 | } 68 | } 69 | 70 | void LCUIWidget_AddLink( void ) 71 | { 72 | // 创建一个名为 link 的部件原型,继承自 textview 73 | prototype = LCUIWidget_NewPrototype( "link", "textview" ); 74 | prototype->init = Link_OnInit; 75 | prototype->destroy = Link_OnDestroy; 76 | prototype->setattr = Link_OnSetAttr; 77 | } 78 | ``` 79 | 80 | 以上代码创建了一个名为 link 的部件原型,接下来将展示如何使用它: 81 | 82 | ```c 83 | // ... 84 | LCUI_Widget link; 85 | LCUIWidget_AddLink(); 86 | // ... 87 | link = LCUIWidget_New( "link" ); 88 | Link_SetHref( link, "https://www.example.com" ); 89 | // ... 90 | ``` 91 | 92 | ```markup 93 | 94 | 95 | 96 | 点击这里 97 | 98 | 99 | ``` 100 | 101 | -------------------------------------------------------------------------------- /zh-cn/gui_widgets/prototype.md: -------------------------------------------------------------------------------- 1 | # 原型 2 | 3 | 在《[快速上手](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/gui_widgets/getting_started/README.md)》章节中有讲到用 `LCUIWidget_New()` 函数创建部件,给定类型名称就能 够创建该类型的部件,实际上,这个函数会先找到与该名称对应的部件原型,然后基于这 个原型来创建部件。 4 | 5 | 图形界面会需要各种各样的部件以丰富用户交互体验,例如:文本框、滚动条、 进度条、单选框等类型的部件,这些部件都会有各自的数据和方法(函数),但在 LCUI 中所有类型的部件都有共同的方法,而这些方法都定义在一个结构体中,代码如下: 6 | 7 | ```c 8 | typedef struct LCUI_WidgetPrototypeRec_ *LCUI_WidgetPrototype; 9 | typedef const struct LCUI_WidgetPrototypeRec_ *LCUI_WidgetPrototypeC; 10 | 11 | typedef void( *LCUI_WidgetFunction )(LCUI_Widget); 12 | typedef void( *LCUI_WidgetResizer )(LCUI_Widget, int*, int*); 13 | typedef void( *LCUI_WidgetAttrSetter )(LCUI_Widget, const char*, const char*); 14 | typedef void( *LCUI_WidgetTextSetter )(LCUI_Widget, const char*); 15 | typedef void( *LCUI_WidgetPainter )(LCUI_Widget, LCUI_PaintContext); 16 | 17 | /** 部件原型数据结构 */ 18 | typedef struct LCUI_WidgetPrototypeRec_ { 19 | char *name; /**< 名称 */ 20 | LCUI_WidgetFunction init; /**< 构造函数 */ 21 | LCUI_WidgetFunction destroy; /**< 析构函数 */ 22 | LCUI_WidgetFunction update; /**< 样式处理函数 */ 23 | LCUI_WidgetFunction runtask; /**< 自定义任务处理函数 */ 24 | LCUI_WidgetAttrSetter setattr; /**< 属性设置函数 */ 25 | LCUI_WidgetTextSetter settext; /**< 文本内容设置函数 */ 26 | LCUI_WidgetResizer autosize; /**< 内容尺寸计算函数 */ 27 | LCUI_WidgetPainter paint; /**< 绘制函数 */ 28 | LCUI_WidgetPrototype proto; /**< 父级原型 */ 29 | } LCUI_WidgetPrototypeRec; 30 | ``` 31 | 32 | 以上代码中列出的方法是 LCUI 在处理部件时都会用到的,以下是简单的说明。 33 | 34 | **init** 35 | 36 | `LCUIWidget_New()` 函数在找到原型后,会调用 `init()` 函数按照该类型的部件预设 的方法初始化部件,不同类型的部件都会有自己的数据以及其它相关的设置,这些数据 可以在 `init()` 函数中初始化。 37 | 38 | **destroy** 39 | 40 | 在 LCUI 销毁部件时会调用 `destroy()` 函数,通常这个函数主要负责销毁部件的私有 数据、解除相关设置等任务。 41 | 42 | **update** 43 | 44 | 对于某些部件而言,预置的 CSS 样式属性无法满足需求,会需要用到扩展样式,而这些 扩展样式的处理方法是 LCUI 无法知道的,因此,LCUI 在处理完预置的样式属性后,会 将剩余的样式处理任务交给 `update()` 函数。 45 | 46 | **runtask** 47 | 48 | 在 LCUI 处理完部件预设的一些任务后,会调用 `runtask()` 去处理部件自己设定的一 些任务。 49 | 50 | **setattr** 51 | 52 | `setattr()` 主要用于 XML 文档解析功能,当解析到 XML 文档元素的属性时,会调用该 函数让部件应用这些属性。 53 | 54 | **settext** 55 | 56 | `settext()` 也同样是用于 XML 文档解析功能,当解析到 XML 文档元素内的文本结点 时,会调用该函数让部件处理文本内容。 57 | 58 | **autosize** 59 | 60 | 在 LCUI 计算部件宽高时,如果部件的宽高被设置为 auto,则会调用 `autosize()` 获 取该部件的尺寸,如果未设置 `autosize`,LCUI 会按照默认的方式计算部件的宽高。 通常像文本显示(TextView)这类有自己内容的部件会需要这个函数来调整自身宽高以 适应文本内容。 61 | 62 | **paint** 63 | 64 | 在 LCUI 按设定的样式绘制好部件后,会调用 `paint()` 绘制部件自己的内容。 65 | 66 | **proto** 67 | 68 | 父级原型,用于访问父级原型的方法,这个属性不需要手动设置。 69 | 70 | ## 基本用法 71 | 72 | 创建部件原型需要用到 `LCUIWidget_NewPrototype()` 函数,函数原型如下: 73 | 74 | ```c 75 | LCUI_WidgetPrototype LCUIWidget_NewPrototype( const char *name, 76 | const char *parent_name ); 77 | ``` 78 | 79 | 需要的参数有两个:原型的名称、继承的父级原型的名称,创建完后会返回部件原型,如果 已经存在同名的部件原型或者原型添加失败,则会返回 NULL。以下代码展示了部件原型的常规创建方法,如需详尽的参考代码请查阅 LCUI 预置部件的代码(例如:/src/gui/widget/textview.c)。 80 | 81 | ```c 82 | #include 83 | #include 84 | #include 85 | 86 | static struct MyWidgetModule { 87 | LCUI_WidgetPrototype prototype; 88 | // 其它用得到的数据 89 | // xxxx 90 | // ... 91 | } self; 92 | 93 | static void MyWidget_OnInit( LCUI_Widget w ) 94 | { 95 | // 初始化一些数据 96 | } 97 | 98 | static void MyWidget_OnDestroy( LCUI_Widget w ) 99 | { 100 | // 释放相关数据 101 | } 102 | 103 | static void MyWidget_UpdateStyle( LCUI_Widget w ) 104 | { 105 | // 处理扩展的样式属性 106 | } 107 | 108 | static void MyWidget_AutoSize( LCUI_Widget w, int *width, int *height ) 109 | { 110 | // 根据自身的内容,计算合适的尺寸 111 | } 112 | 113 | static void MyWidget_OnTask( LCUI_Widget w ) 114 | { 115 | // 处理积累的任务 116 | } 117 | 118 | static void MyWidget_OnPaint( LCUI_Widget w, LCUI_PaintContext paint ) 119 | { 120 | // 利用 paint 上下文绘制自己的内容 121 | } 122 | 123 | static void MyWidget_OnParseText( LCUI_Widget w, const char *text ) 124 | { 125 | // 处理 XML 解析器传来的文本内容 126 | } 127 | 128 | void LCUIWidget_AddMyWidget( void ) 129 | { 130 | int i; 131 | self.prototype = LCUIWidget_NewPrototype( "mywidget", NULL ); 132 | self.prototype->init = MyWidget_OnInit; 133 | self.prototype->paint = MyWidget_OnPaint; 134 | self.prototype->destroy = MyWidget_OnDestroy; 135 | self.prototype->autosize = MyWidget_AutoSize; 136 | self.prototype->update = MyWidget_UpdateStyle; 137 | self.prototype->settext = MyWidget_OnParseText; 138 | self.prototype->runtask = MyWidget_OnTask; 139 | // 如果需要用到全局的数据的话 140 | // self.xxxx = ??? 141 | // ... 142 | } 143 | ``` 144 | 145 | -------------------------------------------------------------------------------- /zh-cn/gui_widgets/render.md: -------------------------------------------------------------------------------- 1 | # 渲染 2 | 3 | 本章节将介绍 `Widget_Render()` 函数的基本用法,`Widget_Render()` 函数的主要功能是渲染部件内容,LCUI 输出的图形界面都是由该函数渲染出来的。 4 | 5 | `Widget_Render()` 函数接受两个参数,第一个参数是需渲染的部件,第二个参数是 `LCUI_PaintContext` 类型的绘制实例数据,它包含 rect 和 canvas 两个成员,rect 用于指定部件中需要渲染的区域,而 canvas 是一个用于接受渲染结果的画板。 6 | 7 | 以下是示例程序: 8 | 9 | ```c 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int main( void ) 17 | { 18 | int ret; 19 | LCUI_Graph canvas; 20 | LCUI_Widget root, box, txt; 21 | LCUI_PaintContextRec paint; 22 | LCUI_Rect area = {40, 40, 320, 240}; 23 | LCUI_Color bgcolor = RGB( 242, 249, 252 ); 24 | LCUI_Color bdcolor = RGB( 201, 230, 242 ); 25 | 26 | LCUI_Init(); 27 | 28 | /* 创建一些部件 */ 29 | root = LCUIWidget_New( NULL ); 30 | box = LCUIWidget_New( NULL ); 31 | txt = LCUIWidget_New( "textview" ); 32 | 33 | /* 创建一块灰色的画板 */ 34 | Graph_Init( &canvas ); 35 | Graph_Create( &canvas, 320, 240 ); 36 | Graph_FillRect( &canvas, RGB( 240, 240, 240 ), NULL, FALSE ); 37 | 38 | /* 初始化一个绘制实例,绘制区域为整个画板 */ 39 | paint.with_alpha = FALSE; 40 | paint.rect.width = 320; 41 | paint.rect.height = 320; 42 | paint.rect.x = paint.rect.y = 0; 43 | Graph_Quote( &paint.canvas, &canvas, &area ); 44 | 45 | /* 设定基本的样式和内容 */ 46 | Widget_SetPadding( box, 20, 20, 20, 20 ); 47 | Widget_SetBorder( box, 1, SV_SOLID, bdcolor ); 48 | Widget_SetStyle( box, key_background_color, bgcolor, color ); 49 | TextView_SetTextW( txt, L"[size=24px]这是一段测试文本[/size]" ); 50 | Widget_Append( box, txt ); 51 | Widget_Append( root, box ); 52 | /* 标记需要更新样式 */ 53 | Widget_UpdateStyle( txt, TRUE ); 54 | Widget_UpdateStyle( box, TRUE ); 55 | Widget_UpdateStyle( root, TRUE ); 56 | /* 更新部件,此处的更新顺序必须是父级到子级 */ 57 | Widget_Update( root ); 58 | Widget_Update( box ); 59 | Widget_Update( txt ); 60 | 61 | /* 渲染部件 */ 62 | Widget_Render( box, &paint ); 63 | ret = Graph_WritePNG( "test_widget_render.png", &canvas ); 64 | Graph_Free( &canvas ); 65 | return ret; 66 | } 67 | ``` 68 | 69 | 编译运行后,可以在程序所在工作目录下找到 test\_widget\_render.png 文件,打开它可看到如下图所示的内容: 70 | 71 |  72 | 73 | 在 LCUI 中,一级部件的尺寸是与屏幕对应的,而本实例中 root 部件仅仅充当容器,因为它并没有与屏幕绑定,在调用 `Widget_Update()` 后尺寸会为 0 x 0,所以实际渲染的对象是 box。 74 | 75 | `Widget_Update()` 需要按照从父到子的顺序调用,因为子级部件的坐标、宽度等属性的计算依赖父级部件。 76 | 77 | -------------------------------------------------------------------------------- /zh-cn/install/README.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | 这篇文档会帮助你搭建基本的 LCUI 开发环境,根据你所使用的操作系统,具体步骤会有所不同,这里有两篇文档可以选择: 4 | 5 | * [Linux](linux.md) 6 | * [Windows](windows.md) 7 | 8 | ## 依赖项 9 | 10 | LCUI 的一些功能实现依赖于以下软件,如果你想构建全特性的 LCUI,在构建 LCUI 前请务必安装它们。 11 | 12 | * [libpng](http://www.libpng.org/pub/png/libpng.html) — PNG 图像压缩库 13 | * [libjpeg](http://www.ijg.org/) — JPEG 图像压缩库 14 | * [libxml2](http://xmlsoft.org/) — XML 解析器及相关工具集 15 | * [libx11](https://www.x.org/) — X11 客户端库 16 | * [freetype](https://www.freetype.org/) — 字体渲染引擎 17 | 18 | -------------------------------------------------------------------------------- /zh-cn/install/linux.md: -------------------------------------------------------------------------------- 1 | # 在 Linux 系统中安装 2 | 3 | 这里假设你已经拥有完整的开发环境,包括构建此项目所需的相关工具(例如:gcc、automake),如果你在按照本章节的方法构建本项目时发现缺少某些工具,请自行安装它们。 4 | 5 | ## 安装依赖项 6 | 7 | 如果你的系统是 Ubuntu,可运行以下命令来安装依赖: 8 | 9 | ```text 10 | apt-get install libpng-dev libjpeg-dev libxml2-dev libfreetype6-dev libx11-dev 11 | ``` 12 | 13 | ## 安装 LCUI 14 | 15 | 在最简单的情况下,你可以运行以下命令行: 16 | 17 | ```text 18 | git clone https://github.com/lc-soft/LCUI.git 19 | cd LCUI 20 | ./autogen.sh 21 | ./configure 22 | ``` 23 | 24 | 在 configure 脚本执行完后,运行以下命令编译源代码并安装 LCUI 的函数库和头文件: 25 | 26 | ```text 27 | make 28 | make install 29 | ``` 30 | 31 | 如果需要运行示例程序,可运行命令来编译生成示例程序: 32 | 33 | ```text 34 | cd test 35 | make 36 | ``` 37 | 38 | ## 配置 39 | 40 | 为编译器添加参数,指定 LCUI 的头文件和库文件的查找位置,例如: 41 | 42 | ```text 43 | gcc -c test.c `pkg-config --cflags lcui` 44 | gcc –o test.o test `pkg-config --libs lcui` 45 | ``` 46 | 47 | 通常情况下,你也可以直接用以下方法: 48 | 49 | ```text 50 | gcc -c test.c 51 | gcc –o test.o test -lLCUI 52 | ``` 53 | 54 | ## 完成 55 | 56 | 至此,你已经完成了 LCUI 开发环境的搭建,接下来你可以开始尝试[编写 Hello World](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/getting_started/step1.html)。 57 | 58 | -------------------------------------------------------------------------------- /zh-cn/install/windows.md: -------------------------------------------------------------------------------- 1 | # 在 Windows 系统中安装 2 | 3 | 通常,LCUI 每次版本更新时都会提供编译好的 LCUI 库及依赖库,你可以直接下载使用。如果你出于某些原因(例如:编译64位版本)需要手动编译一次 LCUI,那么请继续阅读以下内容。 4 | 5 | ## 编译依赖项 6 | 7 | 在 Windows 上手动编译安装依赖库比较麻烦,不过,[vcpkg](https://github.com/Microsoft/vcpkg) 可以解决这种问题,你可以尝试使用它安装这些依赖项。 8 | 9 | 以下列出了大致的编译方法,源码包可以在它们的官网中下载到,有的项目会提供 VisualStudio 工程文件,你可以直接打开它然后编译生成它,在编译完后需要将它们提供的头文件复制到编译器能找到的目录。 10 | 11 | ### libpng 12 | 13 | * 项目主页:[http://www.libpng.org/pub/png/libpng.html](http://www.libpng.org/pub/png/libpng.html) 14 | * 工程文件:projects/vstudio/vstudio.sln 15 | * 头文件:png.h、pngconfig.h、pnglibconf.h 16 | 17 | ### freetype 18 | 19 | * 项目主页:[https://www.freetype.org/](https://www.freetype.org/) 20 | * 工程文件:builds/windows/vc2010/freetype.sln 21 | * 头文件:include/ft2build.h、include/freetype 22 | 23 | ### jpeg 24 | 25 | * 项目主页:[http://www.ijg.org/](http://www.ijg.org/) 26 | * 头文件:jconfig.h、jmorecfg.h、jerror.h、jpeglib.h 27 | 28 | jpeg 库没有提供现成的 sln 工程文件,你需要按照以下步骤手动生成它。 29 | 30 | * 打开开始菜单,找到 Visual Studio 的目录 31 | * 打开目录里的 VisualStudio 提供的开发人员命令提示工具 32 | * 在出现的命令窗口中使用 cd 命令切换目录到 jpeg 源码目录 33 | * 运行命令:`nmake -f makefile.vc setup-v15` 34 | * 关闭命令窗口 35 | * 在源码目录中可看到生成的 jpeg.sln 文件 36 | 37 | 如果在运行 nmake 命令后出现提示 `未找到文件“win32.mak”`,请复制 `C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include` 目录下的 win32.mak 文件到 jpeg 源码目录里,然后重试。 38 | 39 | ### libxml2 40 | 41 | * 项目主页:[http://xmlsoft.org/](http://xmlsoft.org/) 42 | * 工程文件:win32/VC10/libxml2.sln 43 | * 头文件:include/libxml 44 | 45 | 如果你想对 libxml2 进行裁剪,你可以在源码目录里的 win32 目录中运行以下命令: 46 | 47 | ```text 48 | cscript configure.js 49 | ``` 50 | 51 | 可以看到如下输出: 52 | 53 | ```text 54 | Microsoft (R) Windows Script Host Version 5.812 55 | 版权所有(C) Microsoft Corporation。保留所有权利。 56 | 57 | libxml2 version: 2.9.4 58 | Created Makefile. 59 | Created config.h. 60 | 61 | XML processor configuration 62 | --------------------------- 63 | Trio: no 64 | Thread safety: native 65 | FTP client: yes 66 | HTTP client: yes 67 | HTML processor: yes 68 | C14N support: yes 69 | Catalog support: yes 70 | DocBook support: yes 71 | XPath support: yes 72 | XPointer support: yes 73 | XInclude support: yes 74 | iconv support: yes 75 | icu support: no 76 | iso8859x support: no 77 | zlib support: no 78 | lzma support: no 79 | Debugging module: yes 80 | Memory debugging: no 81 | Runtime debugging: no 82 | Regexp support: yes 83 | Module support: yes 84 | Tree support: yes 85 | Reader support: yes 86 | Writer support: yes 87 | Walker support: yes 88 | Pattern support: yes 89 | Push support: yes 90 | Validation support: yes 91 | SAX1 support: yes 92 | Legacy support: yes 93 | Output support: yes 94 | XML Schema support: yes 95 | Schematron support: yes 96 | Python bindings: no 97 | 98 | Win32 build configuration 99 | ------------------------- 100 | Compiler: msvc 101 | C-Runtime option: /MD 102 | Embed Manifest: no 103 | Debug symbols: no 104 | Static xmllint: no 105 | Install prefix: . 106 | Put tools in: $(PREFIX)\bin 107 | Put headers in: $(PREFIX)\include 108 | Put static libs in: $(PREFIX)\lib 109 | Put shared libs in: $(PREFIX)\bin 110 | Include path: . 111 | Lib path: . 112 | ``` 113 | 114 | 其中 FTP 客户端、HTTP 客户端、HTML 处理器等模块是用不到的,可以使用以下命令禁用掉它们: 115 | 116 | ```text 117 | cscript configure.js ftp=no http=no html=no legacy=no iconv=no catalog=no docb=no modules=no 118 | ``` 119 | 120 | 之后开始编译: 121 | 122 | ```text 123 | nmake /f Makefile.msvc 124 | ``` 125 | 126 | 文件会输出到 `bin.msvc` 目录下,这些编译方法在 Readme.txt 文件中有说明。 127 | 128 | ## 编译 LCUI 129 | 130 | LCUI 主要是在 Windows 系统环境下开发的,你可以使用 [VisualStudio](https://www.visualstudio.com) 打开 `build/windows/LCUI.sln` 文件,在编译前,你需要将依赖项的库文件和头文件放在源码目录中的 vendor 目录中,目录结构如下: 131 | 132 | ```text 133 | vendor/include 134 | vendor/lib 135 | ``` 136 | 137 | 之后,在 VisualStudio 的菜单中选择 `生成 -> 生成解决方案` 来编译生成 LCUI。如果你用的是其它 IDE,请尝 试按该 IDE 的方式创建项目并将源文件添加至项目内,然后配置好依赖项再编译。 138 | 139 | ## 安装 LCUI 140 | 141 | 你可以建立一个专门的目录存放 LCUI 的库文件和头文件,例如:`D:/c-dev/`,但如果你的项目需要给其他人编译的话,我们建议你设置一个相对目录,例如在源码目录里建立一个 vendor 目录,这样其他人要编译你的项目时,只需要把 LCUI 库文件和头文件复制到 vendor 目录里,无需手动修改配置。 142 | 143 | ## 配置 144 | 145 | 每次新建 LCUI 应用项目时,你都需要手动为项目修改如下配置。看上去很麻烦,如果你感兴趣可以参考[这篇文章](https://msdn.microsoft.com/zh-cn/library/6db0hwky.aspx)为 VisualStudio 添加 LCUI 应用程序模板。 146 | 147 | * **C/C++ > 常规 > 附加包含目录** 148 | 149 | 将该项设置为:`$(SolutionDir)vendor\include`,如果你的 VisualStudio 工程文件不是建立在源码根目录下,例如:`/build/windows/project.sln`,那么请手动调整该配置项。 150 | 151 | * **链接器 > 常规 > 附加库目录** 152 | 153 | 将该项设置为:`$(OutDir)`,即:将 exe 输出目录作为附加库目录。 154 | 155 | * **输入 > 附加依赖项** 156 | 157 | 将该项设置为:`LCUI.lib; LCUIMain.lib` 158 | 159 | * **生成事件 > 预先生成事件** 160 | 161 | 将该项设置为: 162 | 163 | ```text 164 | copy $(SolutionDir)vendor\lib\LCUI*.lib $(OutDir) 165 | copy $(SolutionDir)vendor\lib\LCUI*.dll $(OutDir) 166 | ``` 167 | 168 | 在编译、调试或运行应用时,需要让 lib、dll 文件和 exe 在一个目录,方便链接器能够找到它们。 169 | 170 | ### Windows 通用应用 171 | 172 | 如果你的应用是 Windows 通用应用,那么你需要在以上配置的基础上再做如下修改。 173 | 174 | * **输入 > 附加依赖项** 175 | 176 | 将该项设置为:`LCUI.lib; LCUIApp.lib` 177 | 178 | * **生成事件 > 预先生成事件** 179 | 180 | 将该项设置为: 181 | 182 | ```text 183 | copy $(SolutionDir)vendor\lib\uwp\LCUI*.lib $(OutDir) 184 | copy $(SolutionDir)vendor\lib\uwp\LCUI*.dll $(OutDir) 185 | ``` 186 | 187 | LCUI 的 Windows 通用应用版的库文件存放在 uwp 目录里。 188 | 189 | * **文件** 190 | 191 | 右键单击项目名称,然后选择 `添加 > 现有项...`,在文件选择器中选择 LCUI.dll 和 LCUIApp.dll 文件。添加后,右键点击这些文件并选择 `属性`,将 `常规 > 内容` 设置为 `是`。 192 | 193 | ## 完成 194 | 195 | 至此,你已经知道了 LCUI 的安装和编译方法,以及 LCUI 应用程序项目的配置方法,接下来你可以开始尝试[编写 Hello World](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/getting_started/step1.html)。 196 | 197 | -------------------------------------------------------------------------------- /zh-cn/styles/syntax.css: -------------------------------------------------------------------------------- 1 | .book code .token.function { 2 | color: #0086b3; 3 | } 4 | .book code .token.string { 5 | color: #183691; 6 | } 7 | .book code .token.keyword { 8 | color: #a71d5d; 9 | } 10 | .book code .token.number { 11 | color: #0086b3; 12 | } 13 | .book code .token.selector { 14 | color: #795da3; 15 | } 16 | .book code .token.tag { 17 | color: #63a35c; 18 | } 19 | .book code .token.attr-name { 20 | color: #795da3; 21 | } -------------------------------------------------------------------------------- /zh-cn/styles/website.css: -------------------------------------------------------------------------------- 1 | @import url("syntax.css"); 2 | 3 | body,.book-header,.book-summary,.book.font-family-1 { 4 | font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft YaHei", sans-serif; 5 | } 6 | .book.font-size-2 .book-body .page-inner section { 7 | font-size: 1.5rem; 8 | } 9 | -------------------------------------------------------------------------------- /zh-cn/summary.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [前言](./) 4 | * [安装](install/) 5 | * [在 Linux 系统中安装](install/linux.md) 6 | * [在 Windows 系统中安装](install/windows.md) 7 | * [快速上手](getting_started/) 8 | * [一个最小的应用](getting_started/step1.md) 9 | * [改进视觉效果](getting_started/step2.md) 10 | * [简化界面描述代码](getting_started/step3.md) 11 | * [为界面添加用户交互](getting_started/step4.md) 12 | * [部件](gui_widgets/) 13 | * [原型](gui_widgets/prototype.md) 14 | * [私有数据](gui_widgets/data.md) 15 | * [继承](gui_widgets/inherit.md) 16 | * [渲染](gui_widgets/render.md) 17 | * [事件](gui_events/) 18 | * [触控事件](gui_events/touch.md) 19 | * [字体与文字](font_and_text/) 20 | * [字体管理](font_and_text/font_manage.md) 21 | * [渲染一个字](font_and_text/render_char.md) 22 | * [渲染一段文字](font_and_text/render_string.md) 23 | * [图标字体](font_and_text/render_icon_font.md) 24 | * [XML](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/xml/README.md) 25 | * [CSS](css.md) 26 | * [UI 线程](ui_thread.md) 27 | * [驱动程序](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/driver/README.md) 28 | * [事件循环](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/driver/events.md) 29 | * [图形输出](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/driver/display.md) 30 | * [鼠标](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/driver/mouse.md) 31 | * [键盘](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/driver/keyboard.md) 32 | * [触控](https://github.com/lc-ui/lcui-guide/tree/ee3696d3ef53c0761c2fac4155b137e7ed4d00e1/zh-cn/driver/touch.md) 33 | 34 | -------------------------------------------------------------------------------- /zh-cn/ui_thread.md: -------------------------------------------------------------------------------- 1 | # UI 线程 2 | 3 | 很多图形界面应用,包括游戏,都会有一个线程专门负责图形界面的更新和重绘任务,这个线程被称为 UI 线程,通常与界面相关的操作都需要在 UI 线程上进行,这样做是为了避免多线程带来的线程安全问题,因为界面相关的资源在整个进程中只有一个,多个线程操作同一块资源若要避免冲突就需要用锁来实现互斥,但这样做的话同一时刻只有一个线程在操作资源,除了在其它线程里写代码操作界面方便点外,和让一个线程专门操作界面资源的做法比起来没多大优势,反而因为互斥锁的时间消耗而拖低了界面整体效率。 4 | 5 | LCUI 的主循环包含了图形界面的更新和重绘操作,主循环所在的线程就是 UI 线程,通常应用程序的界面初始化以及主循环都是直接在主线程上进行的,所以也可以说主线程是 UI 线程。 6 | 7 | **在 UI 线程中操作界面** 8 | 9 | 在 LCUI 中,图形界面的事件响应是在 UI 线程上处理的,你可以在事件处理函数中直接操作界面资源,为了避免产生界面卡顿的问题,建议将所有耗时比较长的操作放在其它线程上进行。比如让应用程序在按钮被点击后从文件中载入图像数据,如果直接在事件响应函数里完成这个任务,那么界面会一直处于假死状态直到图像被载入完为止,通常的做法是新建一个线程作为工作线程,然后为它分配一个任务队列,等需要执行耗时任务时就向该队列添加相关数据并通知工作线程执行任务。 10 | 11 | **在其它线程中操作界面** 12 | 13 | 上面讲到的是在 UI 线程里执行其它任务的问题,在其它线程执行完任务后会需要将任务执行结果反馈到图形界面上,这时可以用 `LCUI_PostTask()` 函数将界面相关的操作放到 UI 线程上执行,示例代码如下: 14 | 15 | ```c 16 | void TaskForUpdateUI( void *arg1, void *arg2 ) 17 | { 18 | char *text = arg2; 19 | LCUI_Widget textview = arg1; 20 | TextView_SetText( textview, text ); 21 | } 22 | 23 | 24 | // ... 25 | 26 | LCUI_Widget textview; 27 | // 分配一段内存用来存放字符串 28 | char *text = strdup( "Task has been completed !" ); 29 | 30 | // ... 31 | 32 | LCUI_AppTaskRec task = { 0 }; 33 | 34 | task.func = TaskForUpdateUI; // 设置回调函数 35 | task.arg[0] = textview; // 设置第一个参数 36 | task.arg[1] = text; // 设置第二个参数 37 | task.destroy_arg[0] = NULL; 38 | task.destroy_arg[1] = free; // 设置第二个参数的销毁函数 39 | 40 | LCUI_PostTask( &task ); 41 | // ... 42 | ``` 43 | 44 | 任务的回调函数原型需要是 `void xxx( void*, void* )`,任务数据中允许为回调函数设置两个参数,这两个参数需要指针类型。由于该任务是异步执行的,如果要向该函数传递参数,那么需要保证这些参数的生命周期在回调函数被调用时没有结束,否则在回调函数操作这些参数时可能会出现内存访问越界、段错误之类的问题。 45 | 46 | 为了方便操作界面资源,如果任务参数不需要销毁则可以用 `LCUI_PostSimpleTask()` 函数,这个函数是 `LCUI_PostTask()` 的简化版本,只需要传递三个参数:回调函数、参数1、参数2。 47 | 48 | **例外情况** 49 | 50 | 通常涉及到数据删除和全局影响范围较大的操作都必须在 UI 线程中执行,例如: 51 | 52 | * `Widget_Destroy()` 53 | * `Widget_RemoveClass()` 54 | * `Widget_RemoveStatus()` 55 | * `Widget_Unwrap()` 56 | * `LCUI_LoadCSSFile()` 57 | * `LCUIFont_LoadFile()` 58 | 59 | 对于只涉及简单的数据修改的操作可以直接在其它线程中执行,例如: 60 | 61 | * `Widget_Move()` 62 | * `Widget_SetPadding()` 63 | * `Widget_SetMargin()` 64 | * `Widget_SetDisabled()` 65 | * `Widget_Resize()` 66 | * `Widget_Show()` 67 | * `Widget_Hide()` 68 | * `Widget_Update()` 69 | 70 | -------------------------------------------------------------------------------- /zi-ti-1/README.md: -------------------------------------------------------------------------------- 1 | # 字体 2 | 3 | -------------------------------------------------------------------------------- /zi-ti-1/zi-ti-shu-ju-ku.md: -------------------------------------------------------------------------------- 1 | # 字体数据库 2 | 3 | -------------------------------------------------------------------------------- /zi-ti-1/zi-ti-yin-qing.md: -------------------------------------------------------------------------------- 1 | # 字体引擎 2 | 3 | -------------------------------------------------------------------------------- /zu-jian/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。 3 | --- 4 | 5 | # 组件 6 | 7 | 在 LCUI 中,组件(Component)是构成用户界面的基本元素,其它 UI 开发库可能会将这类元素称之为控件(Control)或部件(Widget),你可以按照自己的习惯称呼它们,不过由于作者受到 Web 前端开发技术的影响,本文档将统一采用“组件”这一名字,即使它的数据类型是 `LCUI_Widget`。 8 | 9 | ### 待办事项 10 | 11 | **添加 img 组件** 12 | 13 | 功能与 HTML 中的 img 元素相同,组件宽高自适应图片尺寸。 14 | 15 | **改进组件增量更新机制** 16 | 17 | 为了应对数十万量级的组件更新,保证界面的流畅度,现有的做法是统计组件的更新耗时然后为每帧更新的组件总量设置一个合适的限制,这种做法并不是最优的,可参考 React 的 [Fiber reconciler](https://zh-hans.reactjs.org/docs/codebase-overview.html#fiber-reconciler) 的架构,重新设计 LCUI 的组件增量更新机制。 18 | 19 | \*\*\*\* 20 | 21 | -------------------------------------------------------------------------------- /zu-jian/gu-you-shu-xing.md: -------------------------------------------------------------------------------- 1 | # 固有属性 2 | 3 | -------------------------------------------------------------------------------- /zu-jian/ji-chu-shu-xing.md: -------------------------------------------------------------------------------- 1 | # 基础属性 2 | 3 | -------------------------------------------------------------------------------- /zu-jian/kuo-zhan-shu-xing.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍组件属性的分类及用途。 3 | --- 4 | 5 | # 属性 6 | 7 | ### 基础属性 8 | 9 | 基础属性是每个组件都有的属性,可通过组件指针直接访问到它们。你可以在 [include/LCUI/gui/widget\_base.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/gui/widget_base.h#L253-L336) 文件中找到它们的定义。 10 | 11 | 基础属性分为只读和可写两种: 12 | 13 | * 只读的基础属性是经过计算后的结果,手动修改它们的值是没有意义的,因为它们的值会在下次计算后更新,例如:x、y、width、height 这些几何属性,它们是组件的样式经过布局引擎计算后的结果。 14 | * 可写的基础属性能影响组件的功能和渲染效果,例如:修改 `disabled` 和 `event_blocked` 能控制组件的事件响应行为, 修改 `custom_style` 能覆盖组件原本的样式。这些属性大都有相关的函数来负责修改它们,我们只需要它们即可。 15 | 16 | 接下来让我们通过示例代码来了解一些常用的基础属性。 17 | 18 | ```c 19 | LCUI_Widget w = LCUIWidget_New(NULL); 20 | 21 | // 几何属性的读取 22 | printf("coordinate: (%f, %f)\n", w->x, w->y); 23 | printf("size: (%f, %f)\n", w->width, w->height); 24 | 25 | // 类的增删查 26 | Widget_AddClass(w, "button disabled"); 27 | if (Widget_HasClass(w, "disabled")) { 28 | Widget_RemoveClass(w, "disabled"); 29 | } 30 | 31 | // id 32 | Widget_SetId(w, "btn-submit"); 33 | printf("is same widget? %d\n", w == LCUIWidget_GetById("btn-submit")); 34 | 35 | // 状态/伪类 36 | if (Widget_HasStatus(w, "focus")) { 37 | printf("widget has focus"); 38 | } 39 | 40 | // 打印最终样式 41 | LCUI_PrintStyleSheet(w->style); 42 | // 打印继承的样式 43 | LCUI_PrintStyleSheet(w->inherited_style); 44 | // 设置自定义样式 45 | Widget_SetStyle(w, key_margin_left, 10, px); 46 | Widget_SetStyleString(w, "margin-left", "10px"); 47 | // 获取已计算的样式 48 | if (w->computed_style.visible) { 49 | Widget_Hide(w); 50 | } else { 51 | Widget_Show(w); 52 | } 53 | ``` 54 | 55 | ### 扩展属性 56 | 57 | 扩展属性是可以任意添加、修改和删除的属性,常用于保存自定义数据,或是让特定组件支持用 xml 语言来设置相关功能。它们主要来自于 xml 文档和手动调用 `Widget_SetAttribute()` 函数,且都以字符串的形式保存在一个以属性名为索引键的哈希表中。 58 | 59 | 扩展属性的操作例子: 60 | 61 | ```c 62 | LCUI_Widget w = LCUIWidget_New(NULL); 63 | 64 | Widget_SetAttribute(w, "attr", "value"); 65 | printf("%s\n", Widget_GetAttribute(w, "attr")); 66 | ``` 67 | 68 | 如果你想让你的组件支持响应 xml 文档中设置属性,举个例子,假设你有个 Timeago 组件提供这些方法: 69 | 70 | ```c 71 | Timeago_SetDate(w, "2021-01-01T08:00:00.000Z"); 72 | Timeago_SetAutoUpdate(w, 60); 73 | Timeago_SetLocale(w, "zh-CN"); 74 | ``` 75 | 76 | 那么,首先你需要添加一个 `Timeago_SetAttribute()` 函数来集中处理属性: 77 | 78 | ```c 79 | void MyWidget_SetAttribute( 80 | LCUI_Widget w, 81 | const char *name, 82 | const char *val 83 | ) 84 | { 85 | if (strcmp(name, "date") === 0) { 86 | ... 87 | } 88 | ... 89 | } 90 | ``` 91 | 92 | 然后,将它与组件原型上 `setattr` 方法进行绑定: 93 | 94 | ```c 95 | my_widget_proto->setattr = MyWidget_SetAttribute; 96 | ``` 97 | 98 | 这样修改后,在 XML 文档中就能这样使用 Timeago 组件: 99 | 100 | ```markup 101 | 102 | ``` 103 | 104 | 而且还能支持用 `Widget_SetAttribute()` 函数设置属性: 105 | 106 | ```c 107 | Widget_SetAttribute(w, "date", "2021-01-01T08:00:00.000Z"); 108 | Widget_SetAttribute(w, "auto-update", "60"); 109 | Widget_SetAttribute(w, "locale", "zh-CN"); 110 | ``` 111 | 112 | ### 待办事项 113 | 114 | **给 `LCUI_WidgetRec_` 结构体中的只读成员加上只读注释** 115 | 116 | 修改 [include/LCUI/gui/widget\_base.h](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/include/LCUI/gui/widget_base.h#L253-L336) 中的 `LCUI_WidgetRec_` 结构体定义,给只读属性加上 `(Readonly)` 之类的注释。 117 | 118 | -------------------------------------------------------------------------------- /zu-jian/sheng-ming-zhou-qi.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 组件从创建到更新再到销毁的过程。 3 | --- 4 | 5 | # 生命周期 6 | 7 | -------------------------------------------------------------------------------- /zu-jian/shi-jian.md: -------------------------------------------------------------------------------- 1 | # 事件 2 | 3 | -------------------------------------------------------------------------------- /zu-jian/xuan-ran-liu-cheng.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍组件从更新到绘制到屏幕上所经历的流程,以及渲染性能的优化方法。 3 | --- 4 | 5 | # 渲染流程 6 | 7 | “渲染”就是将组件数据转变为像素数据,这个转变如同一条包含很多区域的单向管道,组件数据经过管道中的每个区域的处理最终变成像素数据。我们可以将这个管道称为像素管道,它的结构如下图所示: 8 | 9 |  10 | 11 | * **事件:**界面的更新是由事件驱动的,通常我们会在事件处理器中实现一些操作和视觉变化的效果。比如显示一个加载中动画、切换到另一个界面、或者往界面里添加一些内容等。 12 | * **样式计算:**此过程是根据匹配选择器(例如 `.button` 或 `.list .list-item`)计算出哪些组件应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。 13 | * **布局:** 在知道对一个组件应用哪些规则之后,LCUI 即可开始计算它要占据的空间大小及其在屏幕的位置。LCUI 所采用的类似于网页的布局模式意味着一个组件可能影响其他组件,例如更改组件的宽度会影响到子组件的位置和宽度以及组件树中各处的节点,因此布局过程是经常发生的。 14 | * **绘制:**绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。 15 | * **合成:**由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染界面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。 16 | 17 | 管道的每个部分都有机会产生卡顿,因此务必准确了解你的代码触发管道的哪些部分。 18 | 19 |  20 | 21 | 不一定每帧都总是会经过管道每个部分的处理。在实现视觉变化时,管道针对指定帧的运行通常有三种方式: 22 | 23 | **1. 事件 > 样式 > 布局 > 绘制 > 合成** 24 | 25 |  26 | 27 | 如果你修改了组件的布局属性,即改变了组件的几何属性(例如宽度、高度、左侧或顶部位置等),那么 LCUI 将必须检查所有其它组件,然后对界面进行重新布局。任何受影响的部分都需要重新绘制,而且最终绘制的元素需进行合成。 28 | 29 | **2. 事件 > 样式 > 绘制 > 合成** 30 | 31 |  32 | 33 | 如果你修改了组件的绘制属性,即不会影响界面布局的属性(例如背景图片、文字颜色或阴影等),则 LCUI 会跳过布局,但仍将执行绘制。 34 | 35 | **3. 事件 > 样式** 36 | 37 |  38 | 39 | 如果您更改组件的一个既不要布局也不要绘制的属性(例如:pointer-events),则 LCUI 将在计算完样式后跳过剩下的过程。 40 | 41 | 接下来,让我们深入了解此管道的各个不同部分。我们会以一些常见问题为例,阐述如何诊断和修正它们。 42 | 43 | ### 事件 44 | 45 | 事件的处理器经常会触发视觉变化。有时是直接通过样式操作,有时是会产生视觉变化的计算,例如搜索数据或将其排序。时机不当或长时间运行的代码可能是导致性能问题的常见原因。您应当设法尽可能减少其影响。 46 | 47 | 概要: 48 | 49 | * 将耗时长的代码从主线程移动到工作线程,详见[工作线程](../shi-li/gong-zuo-xian-cheng.md)章节。 50 | 51 | ### 样式计算 52 | 53 | 通过添加和删除组件,更改属性、类来更改组件,全都会导致 LCUI 重新计算组件样式,在很多情况下还会对整个界面或界面的一部分进行布局。 54 | 55 | 计算样式的第一部分是创建一组匹配选择器,这实质上是浏览器计算出给指定元素应用哪些类、伪选择器和 ID。第二部分涉及从匹配选择器中获取所有样式规则,并计算出此元素的最终样式。 56 | 57 | 概要: 58 | 59 | * 降低选择器的复杂度;使用 [BEM](http://getbem.com/introduction/) 这种以类为中心的方法论。 60 | * 为数量多且样式相同的组件预先生成样式哈希值,以减少重复的样式匹配。 61 | * 为含有大量子组件的容器组件设置更新规则,告诉 LCUI 是否需要缓存样式表、是否仅更新可见的子组件、哪些变动可以忽略等。 62 | * 减少需要计算其样式的组件数量。 63 | 64 | ### 布局 65 | 66 | 布局是计算各组件几何信息的过程:组件的大小以及在界面中的位置。根据所用的 CSS、组件的内容或父级组件,每个组件都将有显式或隐含的大小信息。 67 | 68 | 与样式计算相似,布局开销的直接考虑因素如下: 69 | 70 | 1. 需要布局的元素数量。 71 | 2. 这些布局的复杂度。 72 | 73 | 概要: 74 | 75 | * 组件的数量将影响性能;应尽可能避免触发重新布局。 76 | * 评估布局模型的性能;弹性盒子(Flexbox)一般比块(Block)布局模型更慢。 77 | * 宽高为固定值的组件有着较低的重新布局成本;这种组件在重新布局时无需再遍历子组件树来计算内容宽高,而且能减少因其父组件和子组件的几何属性变化而触发的重新布局次数。 78 | 79 | ### 绘制 80 | 81 | 绘制是填充像素的过程,像素最终合成到用户的屏幕上。 它往往是管道中运行时间最长的任务,应尽可能避免此任务。 82 | 83 | 概要: 84 | 85 | * 更改任何属性始都会触发绘制。 86 | * 绘制通常是像素管道中开销最大的部分;应尽可能避免绘制。 87 | * 大量且尺寸小的绘制区域会降低绘制性能,但好在 LCUI 已经针对这种情况做了区域合并和多个区域并行绘制等优化,大部分情况下你不用考虑这个问题。 88 | * 设置 opacity 和 box-shadow 属性会触发使用独立渲染层,组件及其子组件都被在该渲染层中绘制,在全部绘制完后,该渲染层的内容才会被合成到目标面上。 89 | 90 | ### 合成 91 | 92 | 由于 LCUI 还未引入动画和变换系统,我们先暂时跳过这方面的讲解。 93 | 94 | ### 参考资料 95 | 96 | 本文的内容结构和表达方式参考自《[渲染性能 \| Web \| Google Developer](https://developers.google.cn/web/fundamentals/performance/rendering)》。 97 | 98 | ### 待办事项 99 | 100 | **优化样式计算性能** 101 | 102 | 样式匹配是样式计算过程中耗时较高的一个操作,它的实现代码在 [src/gui/css\_library.c](https://github.com/lc-soft/LCUI/blob/345031d74ca65225ec3623e0c92d448f54f5052b/src/gui/css_library.c#L1383-L1423) 中,需要优化的地方就在 `LCUI_FindStyleSheetFromGroup()` ->`SelectorNode_GetNames()` ->`SelectorNode_GetNames()` -> `NamesFinder_Find()` 这个函数调用链中。 103 | 104 | 在查询样式数据库前,`LCUI_FindStyleSheetFromGroup()` 函数会调用 `SelectorNode_GetNames()` 函数获取选择器节点的所有名称组合,该函数只是简单的调用了 `NamesFinder_Find()` 函数,而 `NamesFinder_Find()` 函数则负责根据给定的选择器节点中的 id、类型、类和伪类来生成所有的组合,涉及较多的字符串操作。 105 | 106 | **添加渲染性能监视器** 107 | 108 | 详见 [\#192](https://github.com/lc-soft/LCUI/issues/192) 中的内容。 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /zu-jian/yang-shi.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 介绍组件的样式表操作方法和相关该概念。 3 | --- 4 | 5 | # 样式 6 | 7 | 每个组件都有自定义样式、已匹配样式、最终样式和已计算样式共四张样式表,其中已计算样式由最终样式计算而来,而最终样式则是已匹配样式与自定义样式合并而来的。 8 | 9 | 当组件的自定义样式、状态、结构以及样式库有改动的时候会触发样式计算,计算过程是先从样式库中查询与组件匹配的样式表,然后合并为一张样式表作为组件的已匹配样式,之后将已匹配样式与自定义样式合并成一张样式表作为最终样式,最后将最终样式转换为已计算样式,以供布局引擎和渲染引擎使用。 10 | 11 | {% hint style="warning" %} 12 | 除自定义样式外,其它样式表都是只读的,你不应该修改它们。 13 | {% endhint %} 14 | 15 | ### 已匹配样式 16 | 17 | 已匹配样式是样式库中所有与组件匹配的样式表的合集,可通过组件的 `inherited_style` 成员访问它,它的生成过程是先在样式库中找到与组件匹配的选择器,然后再合并该选择器关联的样式表,如果组件有样式哈希值,则已匹配样式还会被缓存起来,以供其它同类组件使用,减少重复的样式匹配过程。 18 | 19 | 这张样式表仅在样式计算阶段用到,大多数情况下你用不到它。 20 | 21 | ### 自定义样式 22 | 23 | 自定义样式记录了该组件专属的样式,具有比已匹配样式更高的优先级,可通过组件的 `custom_style` 成员访问它,常见的使用场景是在实现动画、拖拽操作或是自定义布局时动态修改组件的坐标、宽高、透明度等样式。为了节省内存占用,它采用了链表结构来存储样式数据。 24 | 25 | 自定义样式的操作示例: 26 | 27 | ```c 28 | Widget_SetStyle(w, key_width, 100, px); 29 | Widget_SetStyleString(w, "width", "100px"); 30 | 31 | if (Widget_CheckStyleValid(w, key_width)) { 32 | Widget_UnsetStyle(w, key_width); 33 | } 34 | ``` 35 | 36 | 有些常用的样式操作可以改用更精简更具语义的函数代替,示例: 37 | 38 | ```c 39 | Widget_Move(w, 10, 10); 40 | Widget_Resize(w, 100, 200); 41 | Widget_SetPadding(w, 10, 20, 10, 20); 42 | Widget_SetMargin(w, 5, 5, 5, 5); 43 | Widget_SetBorder(w, 1, SV_SOLID, RGB(200, 200, 200)); 44 | Widget_SetBoxShadow(w, 0, 0, 4, ARGB(30, 0, 0, 0)); 45 | Widget_Show(w); 46 | Widget_Hide(w); 47 | ``` 48 | 49 | ### 最终样式 50 | 51 | 最终样式是已匹配样式与自定义样式合并后的产物,它记录了组件用到的全部 CSS 属性,可通过组件的 `style` 成员访问它。出于数据的完整性和读写性能的考虑,它采用内存占用较大的数组存储样式数据。 52 | 53 | 最终样式是 `LCUI_StyleSheet` 类型的,你可以用样式表的辅助方法操作它: 54 | 55 | ```c 56 | LCUI_Widget w = LCUIWidget_New(NULL); 57 | LCUI_Style style = StyleSheet_GetStyle(w->style, key_width); 58 | 59 | if (style.is_valid && style.type == LCUI_STYPE_PX) { 60 | printf("widget width: %fpx\n", style.val_px); 61 | } 62 | ``` 63 | 64 | ### 已计算样式 65 | 66 | 已计算样式是最终样式经计算后的产物,它记录了组件用到的全部 CSS 属性的实际值,可通过组件的 `computed_style` 成员访问它。相比最终样式,在访问它的属性时无需判断属性的有效性和属性值的类型,而且可供布局引擎和渲染引擎直接使用。 67 | 68 | 常用操作示例: 69 | 70 | ```c 71 | // 给组件加上淡出动画 72 | void FadeOutAnimation_OnFrame(LCUI_Widget w) 73 | { 74 | float opacity = w->computed_style.opacity; 75 | 76 | if (w->computed_style.visible) { 77 | if (opacity > 0) { 78 | opacity -= 0.01; 79 | } 80 | if (opacity > 0) { 81 | Widget_SetOpacity(w, opacity); 82 | } else { 83 | Widget_Hide(w); 84 | } 85 | } 86 | } 87 | 88 | ... 89 | 90 | LCUI_SetInterval(5, FadeOutAnimation_OnFrame, w); 91 | 92 | ... 93 | ``` 94 | 95 | ### 待办事项 96 | 97 | **重新设计样式表的存储方式** 98 | 99 | 由于每个组件都要存储三张样式表,内存占用很大,尤其时组件数量达到上万数量级的时候,我们需要为样式表设计一个内存占用更低的存储方式。 100 | 101 | **移除组件结构体中的已计算样式** 102 | 103 | 组件结构体中的 `computed_style` 成员通常只在更新和渲染组件时使用,可以尝试移除它,让它只在更新和渲染组件前临时创建。 104 | 105 | **纠正已匹配样式在组件结构体中的命名** 106 | 107 | 已匹配样式在组件结构体中的名称是 `inherited_style`,但它记录的并不是从父级组件继承的样式,具有误导性,应该纠正为 `matched_style` 。 108 | 109 | -------------------------------------------------------------------------------- /zu-jian/yuan-xing.md: -------------------------------------------------------------------------------- 1 | # 原型 2 | 3 | --------------------------------------------------------------------------------
资源类型,可选值有:
application/font-ttf
text/css
text/xml
当值为 text/xml 时,效果相当于将目标 xml 文档的内容插入到 <resource> 所在位置。
<resource>
当值为 text/xml 时,效果相当于将目标 56 | xml 文档的内容插入到 <resource> 所在位置。