From 19c87562add7a9527b277fc33362508a66e3c4c8 Mon Sep 17 00:00:00 2001 From: Liu Date: Sat, 1 Apr 2023 22:56:09 +0800 Subject: [PATCH] docs: add rfcs --- docs/rfcs/0000-template.zh-cn.md | 58 +++++ docs/rfcs/README.md | 3 + docs/rfcs/README.zh-cn.md | 96 ++++++++ docs/rfcs/active-refs-cn/0001-style-guide.md | 115 ++++++++++ docs/rfcs/active-refs-cn/0002-architecture.md | 102 +++++++++ docs/rfcs/active-refs-cn/0003-xmake.md | 86 +++++++ docs/rfcs/active-refs-cn/0004-lib-yutil.md | 162 +++++++++++++ docs/rfcs/active-refs-cn/0005-lib-css.md | 117 ++++++++++ .../0006-lib-css-value-definition-syntax.md | 212 ++++++++++++++++++ .../0007-lib-css-computed-style.md | 156 +++++++++++++ docs/rfcs/active-refs-cn/0008-lib-pandagl.md | 119 ++++++++++ docs/rfcs/active-refs-cn/0009-lib-ui.md | 80 +++++++ .../rfcs/active-refs-cn/0010-lib-ui-server.md | 101 +++++++++ docs/rfcs/active-refs-cn/0011-lib-ui-image.md | 80 +++++++ .../0012-lib-ui-mutation-observer.md | 129 +++++++++++ docs/rfcs/active-refs-cn/0013-lib-platform.md | 181 +++++++++++++++ docs/rfcs/active-refs-cn/0014-lib-ctest.md | 108 +++++++++ lib/platform/README.md | 83 ------- tests/test_string_render.c | 25 +-- 19 files changed, 1917 insertions(+), 96 deletions(-) create mode 100644 docs/rfcs/0000-template.zh-cn.md create mode 100644 docs/rfcs/README.md create mode 100644 docs/rfcs/README.zh-cn.md create mode 100644 docs/rfcs/active-refs-cn/0001-style-guide.md create mode 100644 docs/rfcs/active-refs-cn/0002-architecture.md create mode 100644 docs/rfcs/active-refs-cn/0003-xmake.md create mode 100644 docs/rfcs/active-refs-cn/0004-lib-yutil.md create mode 100644 docs/rfcs/active-refs-cn/0005-lib-css.md create mode 100644 docs/rfcs/active-refs-cn/0006-lib-css-value-definition-syntax.md create mode 100644 docs/rfcs/active-refs-cn/0007-lib-css-computed-style.md create mode 100644 docs/rfcs/active-refs-cn/0008-lib-pandagl.md create mode 100644 docs/rfcs/active-refs-cn/0009-lib-ui.md create mode 100644 docs/rfcs/active-refs-cn/0010-lib-ui-server.md create mode 100644 docs/rfcs/active-refs-cn/0011-lib-ui-image.md create mode 100644 docs/rfcs/active-refs-cn/0012-lib-ui-mutation-observer.md create mode 100644 docs/rfcs/active-refs-cn/0013-lib-platform.md create mode 100644 docs/rfcs/active-refs-cn/0014-lib-ctest.md diff --git a/docs/rfcs/0000-template.zh-cn.md b/docs/rfcs/0000-template.zh-cn.md new file mode 100644 index 000000000..35809c708 --- /dev/null +++ b/docs/rfcs/0000-template.zh-cn.md @@ -0,0 +1,58 @@ +# 标题 + +- 开始日期:(填写今天的日期,YYYY-MM-DD) +- 目标主要版本:(2.x / 3.x) +- 参考问题:(填写现有的相关问题,如果有的话) +- 实现 PR:(留空) + +## 概括 + +功能的简要说明。 + +## 基本示例 + +如果提案涉及新的或更改的 API,请包括一个基本代码示例。 +如果不适用,请忽略此部分。 + +## 动机 + +我们为什么要这样做呢? 它支持哪些用例? 什么是预期 +结果? + +请重点解释动机,以便如果不接受此 RFC, +这种动机可用于制定替代解决方案。 换句话说, +枚举你试图解决的约束而不耦合它们 +与您想到的解决方案密切相关。 + +## 详细设计 + +这是 RFC 的大部分内容。 为某人详细解释设计 +熟悉 LCUI 才能理解,对于熟悉的人来说 +实施实施。 这应该进入细节和极端情况, +并包括如何使用该功能的示例。 任何新术语都应该 +在这里定义。 + +## 缺点 + +我们为什么*不*这样做? 请考虑: + +- 实施成本,包括代码大小和复杂性 +- 提议的特性是否可以在用户空间实现 +- 对教人 LCUI 的影响 +- 将此功能与其他现有和计划中的功能集成 +- 迁移现有 LCUI 应用程序的成本(这是一个重大变化吗?) + +选择任何路径都需要权衡。 尝试在此处识别它们。 + +## 备选方案 + +还考虑了哪些其他设计? 不这样做有什么影响? + +## 采用策略 + +如果我们实施这个提案,现有的 LCUI 开发人员将如何采用它? 是... +这是一个破坏性的改动? 我们可以写一个 codemod 吗? 我们能否为它所取代的原始 API 提供一个运行时适配库? 这将如何影响 LCUI 生态系统中的其他项目? + +## 未解决的问题 + +可选,但建议用于初稿。 设计的哪些部分仍然存在 diff --git a/docs/rfcs/README.md b/docs/rfcs/README.md new file mode 100644 index 000000000..97855ac98 --- /dev/null +++ b/docs/rfcs/README.md @@ -0,0 +1,3 @@ +# LCUI RFCs + +[中文](README.zh-cn.md)/**English** diff --git a/docs/rfcs/README.zh-cn.md b/docs/rfcs/README.zh-cn.md new file mode 100644 index 000000000..85f8d5db4 --- /dev/null +++ b/docs/rfcs/README.zh-cn.md @@ -0,0 +1,96 @@ +# LCUI 意见征集 + +**中文**/[English](README.md) + +许多改动,包括错误修复和文档改进,都可以通过正常的 GitHub/Gitee 拉取请求工作流程实施和审查。 + +虽然有些改动是“实质性的”,但我们要求这些改动经过一些设计过程,并在 LCUI 核心团队中达成共识。 + +“RFC”(意见征集)流程旨在为新功能进入项目提供一致且受控的路径。 + +## RFC 生命周期 + +- 待处理:当 RFC 作为 PR 提交时。 +- 已生效:当 RFC PR 已合并、且正在实施时。 +- 已落地:当 RFC 提议的更改在实际版本中发布时。 +- 已拒绝:当一个 RFC PR 在没有被合并的情况下被关闭。 + +[活跃的意见征集稿列表](https://gitee.com/lc-soft/LCUI/pulls?search=%E5%BE%81%E6%B1%82%E6%84%8F%E8%A7%81) + +## 贡献者协议 (CLA) + +为了接受你的拉取请求,我们需要你提交贡献者协议。如果你是第一次提交拉取请求,请告诉我们你已完成 贡献者协议签署。 + +[点击此处签署你的贡献者协议](https://gitee.com/organizations/lc-ui/cla/lcui-cla) + +## 何时遵循此流程 + +如果你打算对 LCUI 或其文档进行“实质性”改动,则应考虑使用此过程。 + +构成“实质性”变化的内容是根据社区规范不断演变的,但可能包括以下内容: + +- 基于新 API 接口实现的新功能 +- 删除已作为发布渠道的一部分提供的功能 +- 引入新的惯用用法或约定,即使它们不包括对 LCUI 本身的代码更改 + +一些更改不需要意见征集: + +- 改写、重组或重构 +- 添加或删除警告 +- 添加的内容只会被其他 LCUI 实现者注意到,而对 LCUI 用户是不可见的 + +## 为什么你需要这样做 + +为了提升稳定性,更多地考虑我们所做的每一项更改对最终用户可能造成的影响,另一方面,也需要防止新 API 变得更复杂。 + +在 LCUI 的 2.x 版本之前,所有改动都没有文档,其中有些改动并没有经过认真思考和设计,在后期维护时,它们会阻碍我们理解相关需求和设计意图。对于用户而言,我们希望公开 LCUI 已实施的和待实施的改动,以征求更好的改进意见,让新增的改动内容更稳定可靠,而不是像以前那样随便增加了一些没有文档的功能,然后又随便删掉,又或是推倒重写。 + +此外,在对 LCUI 进行更改时,RFC 流程可以引导你完成我们的思考过程,这样我们就可以在讨论为什么或为什么不应该进行这些更改时达成共识。 + +## 在提交前收集反馈 + +在深入研究 RFC 所需的 API 设计细节级别之前,获得对你的概念的反馈通常很有帮助。你可以在此仓库上提出问题以展开讨论,目标是最终制定具有特定实现设计的 RFC 拉取请求。 + +## 流程是什么 + +简而言之,要将主要功能添加到 LCUI,首先必须将 RFC 作为 markdown 文件合并到此代码库中。届时 RFC 的状态是“已确认”并且可能以最终包含到 LCUI 中为目标来实现。 + +1. 基于此代码库中的模板 (`0000-template.zh-cn.md`) ,在新的 Markdown 文件中撰写你的提案。 + - 注意细节:RFC 没有提供令人信服的动机,没有展示对设计影响的理解,或者对缺点或替代方案不诚实,往往不会被接受。 +1. 在[讨论板块](https://github.com/lc-soft/LCUI/discussions)中打开一个新主题帖,并确保将类别设置为“RFC Discussions”。 + - 在讨论主题帖中建立共识并整合反馈。 获得广泛支持的 RFC 比那些没有收到任何评论的 RFC 更有可能取得进展。 +1. 如果提案收到社区成员的极大兴趣和普遍积极的反馈,你可以准备一个 拉取请求: + - Fork 此仓库。 + - 创建你的提案并命名为 `active-rfcs/0000-my-feature.md` (其中的 "my-feature" 对提案的描述,而编号无需指定)。 + - 提交拉取请求。确保它链接到讨论帖。 +1. 最终,核心团队将确定是否纳入 LCUI 中的候选 RFC。 + - RFC 可根据核心团队和社区的反馈进行修改。重大修改可能会触发新的最终评论期。 + - RFC 在公众讨论已经解决并且评论总结了拒绝的理由之后可能会被拒绝。然后,核心团队的一名成员应该关闭 RFC 的相关拉取请求。 + - RFC 可能会在其最终评论期结束时被接受。 核心团队成员将合并 RFC 的相关拉取请求,此时 RFC 将变为“已生效”状态。 + +## 有关活动 RFC 的详细信息 + +一旦 RFC 生效,作者就可以实现它并将该功能作为拉取请求提交到 LCUI 存储库。 变为“生效”意味着核心团队已经原则上同意并愿意合并它,并不意味着该功能最终会被合并。 + +此外,给定的 RFC 已被接受并处于“生效”状态这一事实并不意味着分配给其实现的优先级是什么,也不意味着当前是否有人正在处理它。 + +后续的 PR 可对生效的 RFC 进行修改。我们努力以反映功能最终设计的方式编写每个 RFC;但是这个过程的性质意味着我们不能期望每个合并的 RFC 都能实际反映下一个主要版本发布时的最终结果;因此,我们尝试按计划使每个 RFC 文档与语言功能保持同步,并通过对文档的后续拉取请求来跟踪此类更改。 + +## 实现 RFC + +RFC 的作者没有义务实施它。 当然,欢迎 RFC 作者(像任何其他开发人员一样)在 RFC 被接受后发布实现以供审查。 + +一个生效的 RFC 应该有指向实现 PR 的链接(如果有的话)。 对实际实现的反馈应该在实现 PR 而不是原来的 RFC PR 中进行。 + +如果你对“活跃”RFC 的实现感兴趣,但无法确定其他人是否已经在处理它,请随时提问(例如,通过对相关问题发表评论)。 + +## 审查 RFC + +核心团队的成员将尝试定期审查一些已打开的 RFC 拉取请求。 如果核心团队成员认为 RFC PR 已准备好接受进入生效状态,他们可以使用 GitHub/Gitee 的审查功能批准 PR,以表示他们批准了 RFC。 + +## 参考 + +LCUI 的 RFC 流程及文档内容参考自: + +- https://github.com/reactjs/rfcs +- https://github.com/vuejs/rfcs diff --git a/docs/rfcs/active-refs-cn/0001-style-guide.md b/docs/rfcs/active-refs-cn/0001-style-guide.md new file mode 100644 index 000000000..d78289fb1 --- /dev/null +++ b/docs/rfcs/active-refs-cn/0001-style-guide.md @@ -0,0 +1,115 @@ +# 编码规范 + +- 开始日期:2023-03-26 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:无 + +## 概要 + +参考其它 C 开源项目,重新制定统一的编码规范。 + +## 基本示例 + +```c +typedef enum parser_state_t { + PARSER_STATE_START, + PARSER_STATE_DATA_BEGIN, + PARSER_STATE_DATA_END, + PARSER_STATE_ERROR +} parser_state_t; + +typedef struct parser_t { + /** 解析器状态 */ + parser_state_t state; + + /** 当前字符 */ + char *cur; +} parser_t; + +parser_t *parser_create(void) +{ + parser_t *parser; + + parser = malloc(sizeof(parser_t)); + parser->state = PARSER_STATE_START; + parser->cur = NULL; + return parser; +} +``` + +## 动机 + +2.x 版本的代码风格不一致,包含了驼峰、小写下划线风格以及各种命名规则,导致源码阅读体验和接口使用体验较差。 + +## 详细设计 + +使用 clang-format 工具对源码进行格式化,其配置文件 `.clang-format` 存放于源码根目录中。 + +由于预设的格式化规则已经满足此编码规范的大部分要求,以下仅对需要在编码时注意的规范进行简单说明,不再详细说明规范的全部内容。 + +### 命名 + +常见的 C 语言开源库的编码风格大都是蛇形命名法(`snake_case`),例如:[libgit2](https://github.com/libgit2/libgit2)、[cairo](https://www.cairographics.org/)、[leveldb](https://github.com/google/leveldb/blob/main/include/leveldb/c.h),因此,LCUI 项目也采用该编码风格。 + +由于 LCUI 已被拆分为多个子库,这些库内的标识符不再需要添加 `LCUI_` 前缀。 + +类型名以 `_t` 结尾,例如: + +```c +typedef struct foo_t { + int bar; +} foo_t; +``` + +宏定义和枚举采用大写下划线命名法,例如: + +```c +typedef enum pd_color_type_t { + PD_COLOR_TYPE_INDEX8, + PD_COLOR_TYPE_GRAY8, + PD_COLOR_TYPE_RGB323, + PD_COLOR_TYPE_ARGB2222, + PD_COLOR_TYPE_RGB555, + PD_COLOR_TYPE_RGB565, + PD_COLOR_TYPE_RGB888, + PD_COLOR_TYPE_ARGB8888 +} pd_color_type_t; + +#define PANDAGL_VERSION "0.1.0" +``` + +### 注释 + +推荐写在代码行上面: + +```ts +struct foo { + /** comment */ + int bar; + + /** string */ + char *str; +} +``` + +不推荐写在右侧: + +```ts +struct foo { + int bar; /**< comment */ + char *str; /**< string */ +} +``` + +### 缩进 + +为确保代码在网页端和编辑器内的显示效果一致,以八个空格为一个缩进层级,不使用制表符缩进。 + +## 缺点 + +改动很大,涉及几乎所有代码,从 2.x 版本迁移到 3.x 版本需要参照头文件内容对旧的标识符进行全局替换。 + +## 备选方案 + +全部采用驼峰命名。 diff --git a/docs/rfcs/active-refs-cn/0002-architecture.md b/docs/rfcs/active-refs-cn/0002-architecture.md new file mode 100644 index 000000000..0d3b2b234 --- /dev/null +++ b/docs/rfcs/active-refs-cn/0002-architecture.md @@ -0,0 +1,102 @@ +# 架构 + +- 开始日期:2023-03-26 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:无 + +## 概要 + +重新设计源码目录结构,将 LCUI 拆分为多个子库,重构部分模块以减少不必要的耦合。 + +## 基本示例 + +新的目录结构: + +```text +docs/ +examples/ +include/ +lib/ + css/ + pandagl/ + platform/ + thread/ + timer/ + ui/ + ui-cursor/ + ui-server/ + ui-widgets/ + ui-xml/ + worker/ + yutil/ +src/ +tests/ +``` + +更精简的头文件路径: + +```c +// 之前 +#include +#include +#include +#include +#include + +// 之后 +#include +#include +#include +#include +#include +``` + +## 动机 + +图形处理、系统窗口管理、输入输出管理等功能已经有更好的开源实现,继续维护和改进它们只是在浪费时间,而且 LCUI 项目维护人员的兴趣并不在底层功能模块上面。因此,LCUI 应该被拆分成多个较为独立、通用的子库,以便于他人根据自身的需要,对 LCUI 进行定制、裁剪,又或是只使用部分子库。典型的适用场景包括: + +- 单独构建 PandaGL 图形库,在应用程序中调用它的接口实现简单的图像编辑和文字渲染。 +- 基于 SDL 实现一系列与平台库 libplatform 相同的接口,使 LCUI 应用程序能够将 SDL 作为后端来为图形界面的各项能力提供更好的支持。 +- 在其它 UI 库中使用 LCUI 的自绘组件。 + +简而言之,这是为了扩大 LCUI 的受众范围,使其从面向单一 UI 开发需求的 UI 库项目,转变为面向多种需求的项目集。 + +## 详细设计 + +新增 lib 目录用于存放子库,子库的目录命名和结构如下: + +``` +[libname]/ + include/ + [libname]/ + config.h + foo.h + bar.h + [libname].h + src/ + config.h.in + foo.c + foo.h + bar.c + bar.h + xmake.lua +``` + +源文件的私有接口声明在同目录同名头文件中,而公开接口则声明在 `include/[libname]` 目录中的同名头文件内。 + +`src` 目录中存放这些子库共同运作所需的代码,包括初始化应用程序资源、字体、主循环、窗口管理等。 + +为明确依赖关系,子库的依赖库都由它的 xmake.lua 配置引入。 + +## 缺点 + +无。 + +## 备选方案 + +无。 + +## 采用策略 + +这是个破坏性改动,从 2.x 版本迁移到 3.x 版本需要更新头文件包含代码。 diff --git a/docs/rfcs/active-refs-cn/0003-xmake.md b/docs/rfcs/active-refs-cn/0003-xmake.md new file mode 100644 index 000000000..263b97d39 --- /dev/null +++ b/docs/rfcs/active-refs-cn/0003-xmake.md @@ -0,0 +1,86 @@ +# 构建工具 + +- 开始日期:2023-03-28 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:无 + +## 概括 + +改用 xmake 来构建 LCUI,降低构建和使用成本。 + +## 基本示例 + +构建: + +```sh +xmake +``` + +打包: + +```sh +xmake package +``` + +安装到指定目录: + +```sh +xmake install -o path/to/your/dir +``` + +生成 CMakelists.txt: + +```sh +xmake project -k cmakelists +``` + +生成 compile_commands: + +```sh +xmake project -k compile_commands +``` + +子库都有自己的构建配置,可单独构建它们: + +```sh +xmake build pandagl +xmake build yutil +xmake build libcss +xmake build libui +xmake build libplatform +``` + +在 xmake.lua 中引入 LCUI 和子库: + +```lua +add_repositories("lcui-repo git@github.com:lcui-dev/xmake-repo.git") +add_requires("lcui", "pandagl", "libcss") + +target("lcuiapp") + add_packages("lcui") + +target("imageeditor") + add_packages("pandagl") + +target("cssparser") + add_packages("libcss") +``` + +## 动机 + +2.x 版本采用的构建工具是 AutoMake 和 VisualStudio,分别用于 Linux、Windows 平台上的构建,而包管理工具是 lcpkg,依赖 Node.js 运行环境和 vcpkg 工具。 + +这些工具不仅增加了编译环境的搭建成本,还增加了项目维护人员的维护成本。因此,应该改用一款跨平台且自带包管理功能的构建工具代替它们。 + +## 详细设计 + +在源码根目录下添加 xmake.lua,然后参考 [XMake 文档](https://xmake.io/)完善配置。 + +## 缺点 + +需要考虑调整 debian 包的打包流程。 + +## 备选方案 + +CMake。不建议采用,无论是中文文档、包管理、配置文件编写体验都不如 XMake,遇到一些使用上的问题还需要花时间搜索资料和验证。 diff --git a/docs/rfcs/active-refs-cn/0004-lib-yutil.md b/docs/rfcs/active-refs-cn/0004-lib-yutil.md new file mode 100644 index 000000000..9d86cf513 --- /dev/null +++ b/docs/rfcs/active-refs-cn/0004-lib-yutil.md @@ -0,0 +1,162 @@ +# 实用工具库 + +- 开始日期:2021-09-28 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:[#264](https://github.com/lc-soft/LCUI/pull/264) + +# 概括 + +添加实用工具库。 + +# 基本示例 + +实用工具库的构建命令: + +```sh +# 构建库 +xmake build yutil + +# 构建测试用例 +xmake build yutil_test + +# 运行测试 +xmake run yutil_test +``` + +list 的使用例子: + +```c +#include + +void test_list(void) +{ + int arr[] = { 0, 4, 8, 16, 32, 64, 1024, 2048 }; + size_t i; + size_t n = sizeof(arr) / sizeof(int); + + //创建一个链表结构 + list_t list; + //创造一个结点类型的指针 + list_node_t *node; + + //初始化链表 + list_create(&list); + + // append data + for (i = 0; i < n; ++i) { + list_append(&list, arr + i); + } + + // delete data + for (i = 0; i < n; ++i) { + list_delete(&list, 0); + } + + // insert data + for (i = 0; i < n; ++i) { + list_insert(&list, i, arr + i); + } + + // insert head + list_insert_head(&list, 0); + // delete head + list_delete_head(&list); + + list_append(&list, 0); + + // delete tail + list_delete_last(&list); + + //遍历 + i = 0; + list_for_each(node, &list) + { + if (node->data != arr + i) { + break; + } + ++i; + } + + // 反向遍历 + i = n - 1; + list_for_each_reverse(node, &list) + { + if (node->data != arr + i) { + break; + } + if (i == 0) { + i = n; + break; + } + --i; + } + + //销毁链表 + list_destroy(&list, NULL); +} +``` + +# 动机 + +# 详细设计 + +参考 glib、tbox 设计 API,将一些工具类函数整理进独立的子库中,子库命名为易工具(yutil),提供以下功能模块: + +- **list:** 列表。基于双向链表实现,由列表元素在内部维护的链表节点。 +- **list_entry:** 双向循环链表。链表节点不包含数据,使用时需将链表节点添加为元素数据结构成员。 +- **rbtree:** 红黑树。 +- **dict:** 字典(哈希表)。基于 2021年 8 月为止最新的 redis 中的 dict 代码做修改。 +- **string:** 提供一些额外的字符串操作函数,包括:转换成小写、生成哈希值、分割字符串、清除字符串首尾的字符。 +- **strpool:** 字符串池。用于解决大量相同字符串的内存占用问题。 +- **strlist:** 字符串列表。针对部件和 CSS 选择器的多字符串存储需求而设计,配合 strpool 使用。 +- **logger:** 日志输出和管理。提供日志输出和按级别过滤。 +- **timer:** 定时器。用法是维护一个定时器列表,每次调用处理函数时获取已过期的定时器,并调用相应的回调函数。 +- **dirent:** 目录操作相关函数。 +- **charset:** UTF-8 编码和解码。 +- **time:** 提供高精度时间相关接口。 +- **math:** 提供一些额外的数学运算操作,包括最大、最小和取整函数。 + +删除原 src/util 目录中的以下文件: + +- parse.c:字符串解析功能只有 CSS 库在用,应该由 CSS 库内部包含它。 +- object.c:主要用于实现 UI 和数据的双向绑定,属于实验性的功能,3.x 版本暂时不需要考虑它。 +- task.c:用于与工作线程的任务处理,应该包含到工作线程库中。 +- event.c:UI 和主循环都用到了事件机制,这块应该由它们内部实现。 +- rect.c:矩形数据结构有浮点型和整型这两个版本,分别用于 UI 库和图形库,它应该由这两个库内部实现。 +- steptimer.c:依赖线程库的互斥锁和条件变量操作函数,不适合放入工具库内,应该考虑由平台库提供。 +- uri.c/uri.cpp:URI 的打开函数属于操作系统接口,应该由平台库提供。 + +子库是贡献者 [@yidianyiko](https://gitee.com/yidianyiko/yutil) 开发的一个独立的开源项目,因此以 git 子模块的方式引入,而不是直接将它的源码整合进 LCUI 项目内。 + +具体细节可查阅以下文档: + +- 需求文档:[lib/yutil/docs/RA](../../../lib/yutil/docs/RA/) +- 调研文档:[lib/yutil/docs/Research_Report](../../../lib/yutil/docs/RA/) +- API 设计文档:[lib/yutil/docs/API](../../../lib/yutil/docs/RA/) + +# 缺点 + +无。 + +# 备选方案 + +直接使用 glib、tbox。缺点是改动量比 yutil 要大,而且会增加 LCUI 项目的体积和构建配置复杂度。 + +# 采用策略 + +这是个破坏性改动,需要对所有用到工具函数的代码进行修改。由于主要改动都在命名上,这些文件通常只需要全局替换名称即可。 + +# 未解决的问题 + +标准库已经提供字符编解码函数 wcstombs() 和 mbstowcs(),charset 模块存在意义不大,应该考虑移除。 + +math 提供的最大、最小和取整函数用途不大,应该考虑移除它们。 + +rbtree 在 LCUI 内的用途很少,应该考虑移除它,用 dict 代替。 + +string 模块中的一些函数的通用性较差,应该考虑移出去,作为内部函数。 + +time 模块中的 `sleep()` 函数与 Linux 提供的 `sleep()` 存在名称冲突,应该考虑调整命名。 + +移除 bool_t? diff --git a/docs/rfcs/active-refs-cn/0005-lib-css.md b/docs/rfcs/active-refs-cn/0005-lib-css.md new file mode 100644 index 000000000..33640375c --- /dev/null +++ b/docs/rfcs/active-refs-cn/0005-lib-css.md @@ -0,0 +1,117 @@ +# CSS 解析器和选择引擎库 + +- 开始日期:2023-05-01 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:无 + +## 概括 + +将 CSS 相关的功能模块整合为独立的子库,移除对 UI 库、线程库、字体库的依赖。 + +## 基本示例 + +```c +#include +#include + +char css_str[] = "\ +* {\ + box-sizing: border-box;\ +}\ +.btn {\ + font-size: 14px;\ +}\ +"; + +int main(void) +{ + css_prop_t *prop; + css_selector_t *selector; + css_style_decl_t *style; + + css_init(); + css_load_from_string(css_str); + selector = css_selector_create(".btn"); + style = css_select_style(selector); + prop = css_style_decl_find(style, css_key_font_size); + if (prop != NULL) { + printf("font-size: %fpx\n", prop->value.unit_value.value); + } + css_selector_destroy(selector); + css_style_decl_destroy(style); + css_destroy(); + return 0; +} +``` + +## 动机 + +- **减少依赖项:** 样式数据库操作依赖线程库的互斥锁、font-family 属性解析器依赖字体库、@font-face 规则解析器依赖 UI 库的 TextView 部件。 +- **纠正 CSS 相关数据类型的命名:** 部分 CSS 相关数据类型命名并不正确,例如:样式表(StyleeSheet)是包含了 CSS 规则集的对象,然而 `LCUI_StyleSheet` 里面却是 CSS 属性列表,正确的命名是 `Style`。 + +## 详细设计 + +### 调整命名 + +参考 LibCSS 库和 MDN 中的 CSS 相关文档,调整标识符的命名。 + +为提升辨识度,所有标识符命名以 `css_` 开头。 + +主要标识符命名改动如下: + +- `LCUI_StyleSheet` -> `css_style_decl_t`,StyleSheet 保存了所有 CSS 属性,数组下标既是属性编号。为节省内存占用,改用 `css_style_decl_t`。 +- `LCUI_StyleList` -> `css_style_decl_t`,StyleList 是一个链表,仅存储了使用的 CSS 属性。 +- `LCUI_Style` -> `css_style_value_t` +- `LCUI_Selector` -> `css_selector_t` +- `LCUI_SelectorNode` -> `css_selector_node_t` +- `LCUI_LoadCSSFile()` -> `css_load_from_file()` +- `LCUI_LoadCSSString()` -> `css_load_from_string()` +- `CSSParser_Begin()` -> `css_parser_create()` +- `CSSParser_End()` -> `css_parser_destroy()` +- `LCUI_GetStyleSheet()` -> `css_select_style()` + +其它改动是相似的,不再赘述。 + +### 调整数据结构 + +参考 [CSS Typed Object Model API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Typed_OM_API) 文档中的各种值类型接口,将 `LCUI_Style` 中的 `px`、`sp`、`style`、`val_string`、`val_image` 等成员改为如下所示的几类成员: + +```c +struct css_style_value_t { + css_style_value_type_t type; + union { + css_private_value_t value; + css_numeric_value_t numeric_value; + css_integer_value_t integer_value; + css_string_value_t string_value; + css_unit_value_t unit_value; + css_color_value_t color_value; + css_image_value_t image_value; + css_unparsed_value_t unparsed_value; + css_keyword_value_t keyword_value; + css_style_array_value_t array_value; + css_boolean_value_t boolean_value; + }; +}; +``` + +### 移除线程库依赖 + +删除 `LCUI_Mutex` 和相关函数调用。 + +### 移除字体库和 UI 库依赖 + +`css_font_face_parser_t` 内新增 callback 函数指针成员,新增 `css_font_face_parser_on_load()` 函数用于设置 callback,由 UI 库调用它来响应字体加载。 + +## 缺点 + +无。 + +## 备选方案 + +参考 LibCSS 的设计,更改数据类型和函数的命名,使 CSS 库的用法与 LibCSS 库高度相似。这个方案成本太高,暂不考虑。 + +## 采用策略 + +这是个破坏性改动,需要对所有用到 CSS 功能的代码进行修改,由于主要改动都在命名上,这些文件通常只需要全局替换名称即可。 diff --git a/docs/rfcs/active-refs-cn/0006-lib-css-value-definition-syntax.md b/docs/rfcs/active-refs-cn/0006-lib-css-value-definition-syntax.md new file mode 100644 index 000000000..cfa8854c5 --- /dev/null +++ b/docs/rfcs/active-refs-cn/0006-lib-css-value-definition-syntax.md @@ -0,0 +1,212 @@ +# CSS 值定义语法 + +- 开始日期:2023-04-09 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:[#287](https://github.com/lc-soft/LCUI/pull/287) + +## 概括 + +添加新的 CSS 属性注册函数,支持使用 CSS 值定义语法来定义 CSS 属性的有效值。 + +## 基本示例 + +注册自定义属性: + +```c +css_register_property( + "custom-prop", + "normal | none", "normal", + css_cascade_custom_prop +); +``` + +内置的 width 属性的注册方式: + +```c +css_register_property_with_key( + css_key_width, + "width", + "auto | | ", "auto", + css_cascade_width +); +``` + +内置的 background 简写属性的定义方式: + +```c +// 先为一些复杂值定义注册别名 +css_register_valdef_alias("image", ""); +css_register_valdef_alias("bg-image", "none | "); +css_register_valdef_alias( + "bg-position", + "[" + " [ left | center | right | top | bottom | ] |" + " [ left | center | right | ]" + " [ top | center | bottom | ] |" + " [ center | [ left | right ] ? ] &&" + " [ center | [ top | bottom ] ? ]" + "]"); +css_register_valdef_alias( + "bg-size", + "[ | | auto ]{1,2} | cover | contain"); +css_register_valdef_alias("repeat-style", + "repeat-x | repeat-x | repeat | no-repeat"); +// 注册简写属性 +css_register_shorthand_property(css_key_background, "background", + " || || || || " + ""); +``` + +## 动机 + +(待补充) + +2.x 版本中的 CSS 属性解析器的设计比较简单, + +- CSS 属性的初始值分散在 UI 库中的各个样式计算函数中 + +## 详细设计 + +参考[CSS属性值定义语法 +](https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax),设计 CSS 值定义解析器和匹配器。 + +### 数据结构 + +先挑几个典型的 CSS 值定义例子: + +- color: `` +- font-size: ` | | ` +- position: `static | relative | absolute | sticky | fixed` +- border: ` || || ` +- box-shadow: `none | ` +- width: `auto | | | min-content | max-content | fit-content | fit-content()` + +像 position 这种由多个关键字组成的定义,最简单的方式是解析成数组然后遍历数组逐个判断,但对于 box-shadow 这种有自定义数据类型的就不好做了,box-shadow 值的完整定义是这样的: + +```text +none | # +where + = inset? && {2,4} && ? +``` + +如果把 box-shadow 值的匹配过程看成一颗树,每个可选值按存在与否分出一个分支,那么这颗树就是这样的: + +```text +- none +- + - inset? + - inset && {2,4} && ? + - inset && {2,4} && + - inset && {2,4} + - {2,4} && ? + - {2,4} && + - {2,4} +``` + +从中可以看出,CSS 值的类型定义数据适合存储在树形结构中,而值的匹配过程就是树的遍历过程。 + +(待补充) + +### 解析器 + +解析器初始创建一个根结点,类型为 Juxtaposition。 + +对于相同类型的结点,解析后将它们存放在同一个数组中,例如: + +```text +left | center | right +``` + +解析结果是: + +```js +SingleBarCombinator(["left", "center", "right"]) +``` + +#### 解析方括号组合器 + +在添加支持方括号之前,解析的都是 `none | auto` ` || || ` 这种线性且类型单一的定义,对解析结果的操作类似于对数组操作,但有了方括号后,数据结构变成了树形,需要操作父子结点,这似乎变得复杂了一点,为此我们不得不重新思考现有的设计是否符合解析方括号的需求。 + +方括号包住的是值定义,因此可以采用递归的方式对方括号内的值定义进行解析,不过与常规的以 `\0` 为终止符的解析方式不同,方括号的终止符是 `]`,为此我们需要让解析器支持自定义终止符。 + +由此我们可以得出方括号的解析流程是在遇到 `[` 时创建子解析器,设置其结束符为 `]`,在子解析器解析完后将它的结果合并进当前的结果中。 + +### 匹配器 + +匹配器的工作流程是先从字符串中读入值,然后将之与值定义进行匹配。 + +#### 读取值 + +字符串中的每个值都由空白符分隔,在判定分隔点时需要考虑到被单引号或双引号的字符串,例如:`"Microsoft YaHei"`,处理引号的方式很简单:对其进行计数,当遇到空白符时,如果计数为 0 则判定为分隔点,否则继续读取下个字符。 + +虽然预先分割所有值是个简单且便于后续操作的做法,但它需要更多的读写操作、内存分配和释放操作,所以出于性能上的考虑,我们应该在匹配新的值定义之前读取一个值。 + +需要特别注意的是,必须在开始解析下个值之前进行切换而不是每次解析完后切换,否则多余的切换会导致整个解析结果错误。 + +```diff ++ if (i > 0) { ++ css_value_matcher_resolve_next_value(matcher); ++ } + if (css_value_matcher_match(matcher, node->data) != 0) { + return -1; + } +- css_value_matcher_resolve_next_value(matcher); ++ i++; +``` + +#### 存储匹配的值 + +将已匹配的值存为数组,然后给匹配函数增加一个用于记录下标的参数。 + +#### 匹配复杂的定义 + +首先看 background-position 的值定义: + +```text +[ + [ left | center | right | top | bottom | ] + | [ left | center | right | ] + [ top | center | bottom | ] + | [ center | [ left | right ] ? ] + && [ center | [ top | bottom ] ? ] +] +``` + +将它拆分开来的话会比较好理解: + +```text +[ left | center | right | top | bottom | ] +``` + +```text +[ left | center | right | ] [ top | center | bottom | ] +``` + +```text +[ center | [ left | right ] ? ] && [ center | [ top | bottom ] ? ] +``` + +假设我们现在要匹配 `top center`,期望匹配器将该值与第二个值定义匹配,但按照现有的设计,匹配器会判定 `top center` 与第一个值定义匹配,因为它在匹配完 top 后没有判断值与值定义的数量是否相同。如何追加这个判断?根匹配器由于独占整个字符串,可以根据是否还有剩余未匹配的值来判断是否完全匹配,但子匹配器是负责匹配字符串内的部分值,并不适合采用这种方式来判断。 + +这个问题的本质在于单杆匹配器匹配的是第一个值定义而不是匹配度最高的值定义,那么解决方法就是给它增加匹配度判断,选择匹配度最高的结果返回,最后由根匹配器根据是否还有剩余未匹配的值来判断是否完全匹配,这样就能解决上述复杂值定义的匹配问题。 + +#### 匹配问号 + +问号修饰的是可选值定义,当该值定义与当前值不匹配时可切换到下个值定义继续匹配。按照现在的值读取规则,匹配器在匹配完可选值定义后会读取下个值,这并不符合预期,那么该如何调整规则? + +通过增加可选值的相关条件判断来调整值读取流程的话,会让它变得更复杂,而且找出相关条件也比较困难,所以这种方法并不可行。 + +我们可以给匹配器增加 next 指针来记录下个值的起始位置,每次寻找下个值时以 next 指针为起点开始找,找到下个值后再将 next 指针指向该值的末尾。这样在可选值未匹配时,我们就能通过将 next 指针指向当前值开头来让匹配器继续使用当前值与下个值定义进行匹配。 + +## 缺点 + +值定义匹配器的存在似乎有点鸡肋,因为属性解析器内部也会对值做一次校验。 + +## 备选方案 + +沿用 2.x 版本的 CSS 解析器设计,新增支持设置 CSS 属性初始值。 + +## 采用策略 + +这是一个破坏性改动,需要用新的属性注册接口注册所有 CSS 属性。 diff --git a/docs/rfcs/active-refs-cn/0007-lib-css-computed-style.md b/docs/rfcs/active-refs-cn/0007-lib-css-computed-style.md new file mode 100644 index 000000000..dd14ddc6d --- /dev/null +++ b/docs/rfcs/active-refs-cn/0007-lib-css-computed-style.md @@ -0,0 +1,156 @@ +# CSS 已计算样式 + +- 开始日期:2023-04-08 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:[#287](https://github.com/lc-soft/LCUI/pull/287) + +## 概括 + +重新设计 CSS 样式计算流程和相关数据存储方式,将 UI 库中的部分样式计算逻辑移入 CSS 库中实现。 + +## 基本示例 + +以下是 UI 部件的计算流程示例: + +```c +css_computed_style_t *s = &w->specified_style; +css_style_decl_t *style; + +css_computed_style_destroy(s); +if (w->custom_style) { + style = css_style_decl_create(); + css_style_decl_merge(style, w->custom_style); + css_style_decl_merge(style, w->matched_style); + css_cascade_style(style, s); + css_style_decl_destroy(style); +} else { + css_cascade_style(w->matched_style, s); +} +w->computed_style = *s; +ui_widget_compute_style(w); +``` + +先清空之前的计算结果,然后层叠(Cascade)已匹配样式和自定义样式,最后对层叠结果进行计算,得出已计算样式。 + +2.x 版本中的 `LCUI_Widget` 结构体成员 `style` 和 `computed_style` 已统一改用 `css_computed_style_t` 类型,并重命名为 `specified_style` 和 `computed_style`。 + +## 动机 + +UI 库中包含了部分 CSS 计算逻辑,例如:width、height、flex-grow 等属性的应用值计算,这有违单一责任原则,应该将 CSS 属性计算移动到 CSS 库内,以让 CSS 库的功能更完备。 + +另一个方面,UI 部件的 `computed_style` 和 `style` 成员的内存占用比较大:`computed_style` 占用 336 字节,`style` 成员占用 8 字节,其中每个 CSS 属性值占用 16 字节,共有 68 个属性值,也就是共占用 336 + 8 + 16 * 68 = 1432 字节,需要优化。 + +## 详细设计 + +参考 [LibCSS](http://www.netsurf-browser.org/projects/libcss/) 的设计,更改样式计算流程为: + +1. 层叠已匹配的样式和自定义样式,计算每个属性的指定值,得出指定样式(`specified_style`)。 +1. 计算每个属性的实际值,得出已计算样式(`computed_style`)。 + +内存优化方面,调整已计算样式的数据结构,以比特位为最小粒度为 CSS 属性值分配存储空间,例如: + +```c +typedef struct css_computed_style_t { + struct css_type_bits_t { + uint8_t display : 5; + uint8_t box_sizing : 2; + uint8_t visibility : 4; + uint8_t vertical_align : 4; + uint8_t pointer_events : 2; + uint8_t position : 3; + ... + } type_bits; + + struct css_unit_bits_t { + css_unit_t left : 4; + css_unit_t right : 4; + css_unit_t top : 4; + css_unit_t bottom : 4; + css_unit_t width : 4; + css_unit_t height : 4; + ... + } unit_bits; + + css_numeric_value_t z_index; + css_numeric_value_t opacity; + + css_numeric_value_t left; + css_numeric_value_t right; + css_numeric_value_t top; + css_numeric_value_t bottom; + ... +} +``` + +以目前的 CSS 属性数量,`css_computed_style_t` 占用 288 字节,相比修改前减少了 `1432 - 288 * 2 = 856` 字节。 + +为方便使用属性值,可为部分 CSS 属性提供 `css_computed_` 开头的辅助函数,例如: + +```c +LIBCSS_PUBLIC uint8_t css_computed_display(const css_computed_style_t *s); + +LIBCSS_PUBLIC uint8_t css_computed_width(const css_computed_style_t *s, + css_numeric_value_t *value, + css_unit_t *unit); + +LIBCSS_PUBLIC uint8_t css_computed_height(const css_computed_style_t *s, + css_numeric_value_t *value, + css_unit_t *unit); +``` + +用法如下: + +```c +css_number_value_t value; +css_unit_t unit; + +switch (css_computed_width(&style, &value, &unit)) { +case CSS_WIDTH_AUTO: + // ... + break; +case CSS_WIDTH_SET: + if (unit == CSS_UNIT_PERCENT) { + // ... + } + break; +default: + break; +} +``` + +还可以添加一些常用的 CSS 值操作相关的工具函数宏,例如: + +```c +#define IS_CSS_LENGTH(S, PROP_KEY) (S)->type_bits.PROP_KEY == CSS_LENGTH_SET + +#define IS_CSS_FIXED_LENGTH(S, PROP_KEY) \ + ((S)->type_bits.PROP_KEY == CSS_LENGTH_SET && \ + (S)->unit_bits.PROP_KEY == CSS_UNIT_PX) +``` + +## 缺点 + +每次样式计算都是计算全部属性的值,影响性能。 + +## 备选方案 + +改用 libcss 库。不建议采用此方案,因为 libcss 的用法与 LCUI 现有的 CSS 库的用法相差较大。 + +## 采用策略 + +这是个破坏性改动,包含数据结构和函数的改动,涉及 UI 库和 CSS 库。 + +数据结构的改动主要是 `LCUI_Widget`: + +```diff +- LCUI_StyleList custom_style; +- LCUI_CachedStyleSheet inherited_style; +- LCUI_WidgetStyle computed_style; ++ css_style_decl_t *custom_style; ++ const css_style_decl_t *matched_style; ++ css_computed_style_t specified_style; ++ css_computed_style_t computed_style; +``` + +UI 库内涉及样式读写的代码都需要重构,包括布局计算、绘制、鼠标事件等。 diff --git a/docs/rfcs/active-refs-cn/0008-lib-pandagl.md b/docs/rfcs/active-refs-cn/0008-lib-pandagl.md new file mode 100644 index 000000000..15957eaef --- /dev/null +++ b/docs/rfcs/active-refs-cn/0008-lib-pandagl.md @@ -0,0 +1,119 @@ +# PandaGL 图形库 + +- 开始日期:2022-04-11 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:[#292](https://github.com/lc-soft/LCUI/pull/292), [#281](https://github.com/lc-soft/LCUI/pull/281) + +## 概括 + +将字体、文字处理、图像读写、图形绘制等功能模块整合为 PandaGL 图形库。 + +## 基本示例 + +```c +#include + +int main(void) +{ + int ret; + pd_canvas_t img; + pd_pos_t pos = { 0, 80 }; + pd_rect_t area = { 0, 0, 320, 240 }; + pd_text_t *text = pd_text_create(); + pd_text_style_t style; + + /* 初始化字体处理功能 */ + pd_font_library_init(); + + /* 创建一个图像,并使用灰色填充 */ + pd_canvas_init(&img); + pd_canvas_create(&img, 320, 240); + pd_canvas_fill(&img, RGB(240, 240, 240)); + + /* 设置文本的字体大小 */ + pd_text_style_init(&style); + style.pixel_size = 24; + style.has_pixel_size = TRUE; + + /* 设置文本图层的固定尺寸、文本样式、文本内容、对齐方式 */ + pd_text_set_fixed_size(text, 320, 240); + pd_text_set_style(text, &style); + pd_text_set_align(text, PD_TEXT_ALIGN_CENTER); + pd_text_write(text, L"这是一段测试文本\nHello, World!", NULL); + pd_text_update(text, NULL); + + /* 将文本图层绘制到图像中,然后将图像写入至 png 文件中 */ + pd_text_render_to(text, area, pos, &img); + ret = pd_write_png_file("test_string_render.png", &img); + pd_canvas_destroy(&img); + + /* 释放字体处理功能相关资源 */ + pd_font_library_destroy(); + return ret; +} +``` + +## 动机 + +2.x 版本中的图形处理相关源文件比较分散,不利于查找和维护,而且按照 3.x 版本的架构设计思想,它们应该被整理为通用的图形库。 + +常见的开源图形库除了具备基本的图形绘制能力外,大都还具备文字渲染、图像读写能力,因此,新的图形库也应该具备这些能力。 + +## 详细设计 + +图形库命名为 PandaGL,标识符命名以 `pd_` 开头。 + +模块分为以下几类: + +- 画布(Canvas):提供像素数据操作和基本图形绘制能力。 +- 字体库(FontLibrary):提供字体文件加载、字形渲染能力。 +- 文字(Text):提供文字排版和渲染能力。 +- 图像(Image):提供图像文件读写能力。 + +由于字体库和图像模块都依赖第三方库,出于可裁剪性和库体积上的考虑,在构建配置中将它们配置为可选模块,示例: + +```sh +# 禁用图像模块 +xmake config --with-pandagl-image=n + +# 禁用字体库模块 +xmake config --with-pandagl-font=n + +# 禁用文字模块 +xmake config --with-pandagl-text=n +``` + +`LCUI_TextLayer` 重命名为 `pd_text_t`,删除 UTF-8 和 ANSI 版本的文本操作函数,仅保留宽字符版本,示例: + +```diff +- /** 插入文本内容(UTF-8版) */ +- LCUI_API int TextLayer_InsertText(LCUI_TextLayer layer, const char *utf8_str); +- +- /** 追加文本内容(宽字符版) */ +- LCUI_API int TextLayer_AppendTextW(LCUI_TextLayer layer, const wchar_t *wstr, +- LinkedList *tag_stack); +- +- /** 追加文本内容 */ +- LCUI_API int TextLayer_AppendTextA(LCUI_TextLayer layer, +- const char *ascii_text); ++ PD_PUBLIC int pd_text_append(pd_text_t *text, const wchar_t *wstr, list_t *tag_stack); +``` + +## 缺点 + +无。 + +## 备选方案 + +删除图形处理相关模块,直接用成熟的开源图形库。不建议采用此方案,因为工作量较大,还需要投入成本去学习其它图形库。 + +## 采用策略 + +这是个破坏性改动,需要对所有用到 PandaGL 功能的代码进行修改。由于主要改动都在命名上,这些文件通常只需要全局替换名称即可。 + +## 未解决的问题 + +出于函数参数复杂度问题上的考虑,`pd_text_write()`、`pd_text_append()`、`pd_text_insert()` 的第三个参数 `tag_stack` 用处不大,应该重新考虑 `tag_stack` 的实现方式,例如:内置在 `pd_text_t` 中。 + +字体库和文字模块的接口有待重新设计。 diff --git a/docs/rfcs/active-refs-cn/0009-lib-ui.md b/docs/rfcs/active-refs-cn/0009-lib-ui.md new file mode 100644 index 000000000..997417caf --- /dev/null +++ b/docs/rfcs/active-refs-cn/0009-lib-ui.md @@ -0,0 +1,80 @@ +# UI 核心库 + +- 开始日期:2023-04-02 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:[#277](https://github.com/lc-soft/LCUI/pull/277) + +## 概括 + +将 UI 相关的功能模块整合为独立的子库,不依赖主循环、窗口等系统相关接口,能被单独编译、使用。 + +## 基本示例 + +```c +#include + +void on_btn_click(ui_widget_t *w, ui_event_t *e, void *arg) +{ + // ... +} + +int main(void) +{ + ui_widget_t *btn; + + ui_init(); + ui_load_css_string(".button {\ + display: inline-block;\ + border: 1px solid #ddd;\ + background: #fff;\ + padding: 4px 8px;\ + font-size: 14px;\ + }"); + + btn = ui_create_widget(); + ui_root_append(btn); + + ui_widget_move(btn, 50, 50); + ui_widget_set_style_string(btn, "border-color", "#f00"); + ui_widget_on(btn, "click", on_btn_click, NULL, NULL); + + ui_update(); + ui_destroy(); +} +``` + +## 动机 + +增强 UI 相关功能模块的通用性、可替代性和可维护性。 + +## 详细设计 + +子库命名为 ui,所有标识符名称以 `ui_` 开头。 + +将 `LCUI_RectF` 重命名为 `ui_rect_t`,相关操作函数移入 `ui_rect.c` 文件内。 + +根级部件由 `ui_root()` 函数获取,对根追加新部件可靠 `ui_root_append()` 函数完成。 + +## 缺点 + +无。 + +## 备选方案 + +无。 + +## 采用策略 + +这是个破坏性改动,需要对所有用到 UI 功能的代码进行修改。由于主要改动都在命名上,这些文件通常只需要全局替换名称即可。 + +## 未解决的问题 + +是否应该都用更精简的命名?例如: + +- `ui_load_css_string` -> `ui_load_css_str` +- `ui_widget_collect_references` -> `ui_widget_collect_refs` + +UI 库可以拆分得更彻底:拆分出布局引擎库,解除对 css 库和 pandagl 库的依赖。 + +直接用全局变量 `ui_root` 访问根级部件是不是更好些? diff --git a/docs/rfcs/active-refs-cn/0010-lib-ui-server.md b/docs/rfcs/active-refs-cn/0010-lib-ui-server.md new file mode 100644 index 000000000..c7b40c486 --- /dev/null +++ b/docs/rfcs/active-refs-cn/0010-lib-ui-server.md @@ -0,0 +1,101 @@ +# UI 服务器 + +- 开始日期:2023-04-09 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:[#272](https://github.com/lc-soft/LCUI/pull/272) + +## 概括 + +添加 UI 服务器,用于实现 UI 部件与系统窗口的数据同步。它基于 UIMutationObserver 接口监听部件的变动,能将部件的图形内容、位置和尺寸等数据同步到与之绑定的系统窗口。 + +## 基本示例 + +```c + +#include +#include + +void example(void) +{ + ui_widget_t *w; + app_window_t *wnd; + + app_init(L"My Application"); + ui_init(); + // 初始化服务器 + ui_server_init(); + // 创建一个部件 + w = ui_widget_create(NULL); + // 创建一个窗口 + wnd = app_window_create("My Widget", 0, 0, 0, 0, NULL); + // 调整部件尺寸 + ui_widget_resize(800, 600); + // 连接部件和窗口 + ui_server_connect(w, wnd); + // 运行主循环 + app_run(); + // 解除部件与窗口的连接 + ui_server_disconnect(w, wnd); + ui_server_destroy(); + ui_destroy(); + return 0; +} +``` + +## 动机 + +在 2.x 版本中,显示控制器实现了 UI 部件与窗口的绑定。它会根据显示模式自动处理根部件和子部件的窗口绑定。然而,由于绑定操作接口并未公开,且默认绑定对象是根部件和子部件,我们无法将其它部件与窗口绑定,导致其灵活性较差。 + +## 详细设计 + +### 命名 + +模块的候选名称有: + +- `ui-mapper`:widget 与 window 之间是映射关系,那么管理这些映射关系的功能模块就是 mapper,但该名称不足以联想到它的作用,容易被理解为其它功能模块。 +- `ui-player`:如果将“widget 内容同步至 window 内”这一过程称为“播放”的话,那么可以将该功能模块命名为 player,但这名字更容易让其他人理解为媒体播放器组件。 +- `ui-display`:原模块的名称就是 display,既是动词也是名词,在新的命名风格中可能会产生歧义,例如:`ui_display_map`,该理解为“将 map 显示出来”,还是“调用显示模块的 map 方法”? +- `ui-server`:从命名可知这个模块是为 UI 提供服务的,可以联想到 [X Server](https://www.x.org/wiki/XServer/)。 + +综合考虑,ui-server 是最为合适的。 + +### 工作原理 + +内部维护一个列表用于存储各个部件与窗口的连接, UI 部件到系统窗口的同步基于 UIMutationObserver 实现,而窗口到 UI 部件的同步则通过响应窗口事件来实现。 + +### 接口设计 + +出于灵活性上的考虑,不在 ui-server 里提供 2.x 版本中的单窗口模式、全屏模式和无缝模式的切换功能,改为在 lcui 应用层提供,也就是在 `src/lcui_ui.c` 中提供。 + +ui-server 提供的接口比较少,除去初始化和销毁接口,可分为这几类: + +- 连接管理: + ```c + ui_widget_t *ui_server_get_widget(app_window_t *window); + app_window_t *ui_server_get_window(ui_widget_t *widget); + int ui_server_disconnect(ui_widget_t *widget, app_window_t *window); + void ui_server_connect(ui_widget_t *widget, app_window_t *window); + ``` +- 渲染: + ```c + size_t ui_server_render(void); + void ui_server_present(void); + ``` +- 设置: + ```c + void ui_server_set_threads(int threads); + void ui_server_set_paint_flashing_enabled(LCUI_BOOL enabled); + ``` + +## 缺点 + +无。 + +## 备选方案 + +无。 + +## 采用策略 + +这是一个破坏性改动,需要将 2.x 版本中的显示控制模块改为基于 ui-server 实现。 diff --git a/docs/rfcs/active-refs-cn/0011-lib-ui-image.md b/docs/rfcs/active-refs-cn/0011-lib-ui-image.md new file mode 100644 index 000000000..b6844337f --- /dev/null +++ b/docs/rfcs/active-refs-cn/0011-lib-ui-image.md @@ -0,0 +1,80 @@ +# UI 图像缓存和异步加载 + +- 开始日期:2023-04-05 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:无 + +## 概括 + +新增 UIImage 模块来负责异步图像加载。 + +## 基本示例 + +```c +#include +#include + +void on_image_load(ui_image_t *image, void *data) +{ + pd_canvas_t *data = (pd_canvas_t *)image; + + if (data) { + printf("image loaded, size: (%d, %d)\n", data->width, data->height); + } else { + printf("image loading failed!\n"); + } +} + +void example(void) +{ + ui_image_t *image; + + image = ui_image_create("/path/to/image.png"); + ui_image_on_event(image, on_image_load, NULL); +} +``` + +## 动机 + +在 2.x 版本中,部件背景图加载功能的设计是:建立一个以图像路径为索引的缓存表,每个缓存项中记录了引用该图像的部件列表。在计算部件背景样式时,如果指定了图像路径,则会加载它并增加该图像的引用计数。在部件的背景图样式变更以及部件销毁时,会减少图像的引用计数。当图像引用计数为 0 时,释放图像资源。 + +这个设计中的图像缓存依赖于部件,而图像异步加载能力则依赖于 LCUI 的工作线程。整个功能仅供内部使用。实际上,这两个依赖项并不是必要的。其中对 LCUI 的工作线程的依赖可以通过将图像加载函数改为公共函数来解除,由上层代码决定放在哪个线程上执行。 + +按照 3.x 版本的架构设计思想,它可以被设计成较为独立通用的模块,以便于 LCUI 应用层代码也能使用异步图像加载功能。 + +## 详细设计 + +设计灵感来自:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement/Image + +新增 `ui_image_t` 类型,基于 `pd_canvas_t` 扩展增加其它成员,在使用时可将 `ui_image_t` 指针转为 `pd_canvas_t` 指针来访问图像的信息。 + +新增事件绑定函数,用于监听图像的加载结果,回调函数的指针定义如下: + +```c +typedef void (*ui_image_event_handler_t)(ui_image_t *, void *); +``` + +如果图像加载失败,回调函数收到的第一个参数的值会为 NULL。 + +模块内部维护缓存表、待加载的图像列表、事件列表。 + +`ui_image_create()` 函数用于创建图像对象,当指定的图像路径已在缓存表中时则直接返回缓存中的图像,并给该缓存增加引用计数。否则,将图像对象追加到待加载的图像列表中。 + +`ui_image_add_event_listener()` 和 `ui_image_remove_event_listener()` 用于图像事件的绑定与解绑,提供简写宏 `ui_image_on_event` 和 `ui_image_off_event`。 + +`ui_load_images()` 用于处理待加载的图像列表,该函数应该在 UI 线程外的其它线程中调用。 + +`ui_process_image_events()` 用于处理已加载的图像的事件,与图像绑定的事件处理函数都会在这时被调用。 + +## 缺点 + +图像加载失败时,事件处理函数接收的 `ui_image_t` 指针值为 NULL,无法得知图像信息,可考虑将错误信息写入 `ui_image_t` 结构体内。 + +## 备选方案 + +无。 + +## 采用策略 + +这是个新增的功能,需要将部件的背景图片加载逻辑改为基于新的 UIImage。 diff --git a/docs/rfcs/active-refs-cn/0012-lib-ui-mutation-observer.md b/docs/rfcs/active-refs-cn/0012-lib-ui-mutation-observer.md new file mode 100644 index 000000000..c2cfc4943 --- /dev/null +++ b/docs/rfcs/active-refs-cn/0012-lib-ui-mutation-observer.md @@ -0,0 +1,129 @@ +# UI 变更观察器 + +- 开始日期:2023-04-02 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:无 + +## 概括 + +UIMutationObserver 提供监听部件的属性、位置、尺寸、子树等变化的能力。 + +## 基本示例 + +```c +void on_widget_mutation(ui_mutation_list_t *mutation_list, + ui_mutation_observer_t *observer, + void *arg) +{ + + list_node_t *node; + ui_mutation_record_t *mutation; + + for (list_each(node, mutation_list)) { + mutation = node->data; + switch (mutation->type) { + case UI_MUTATION_RECORD_TYPE_PROPERTIES: + printf("property: %ls\n", mutation->property_name); + break; + default: + break; + } + } +} + +void example(void) +{ + ui_widget_t *widget; + ui_mutation_observer_t *observer; + ui_mutation_observer_init_t options = { 0 }; + + ... + + // 创建一个观察器 + observer = ui_mutation_observer_create(on_widget_mutation, NULL); + + ... + + // 监听属性变化 + options.properties = TRUE; + // 监听子组件的变化 + options.child_list = TRUE; + // 监听子树的变化 + options.subtree = TRUE; + // 监听组件 + ui_mutation_observer_observe(observer, widget, options); + + ... + + // 销毁观察器 + ui_mutation_observer_destroy(observer); +} +``` + +## 动机 + +为了让部件与系统窗口能够同步数据,2.x 版本中的 UI 模块包含了对 Surface 的通信代码。然而,Surface 属于平台库的接口,而平台库与 UI 库在 3.x 版本中是需要解耦的。因此,需要一种全新且通用的机制来代替旧的通信方式。 + +## 详细设计 + +设计参考自:[MutationObserver - Web API 接口参考 | MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver) + +主要实现方式是,在部件结构体中添加观察器的连接列表,利用已有的样式和布局差异比较机制,在检测出差异时将变更记录添加到已连接该部件的各个观察器中。等部件树更新完毕后,再批量处理所有变更记录,也就是调用所有观察器的回调函数,将变更记录列表传给它们。 + +连接是双向的,当部件被销毁时会解除所有与之相连的观察器的连接,当观察器被销毁时它所观察的部件中的连接记录也会随之移除。 + +API 设计方面,添加以下类型: + +- `ui_mutation_observer_t`:变更观察器。用于存储观察器的连接、变更记录、回掉函数等数据。为减少数据结构依赖,此类型的定义不在公共头文件公开。 +- `ui_mutation_record_t`: 变更记录。 +- `ui_mutation_record_type_t` 变更类型。 +- `ui_mutation_connection_t` 连接记录,包含已建立连接的部件、观察器和配置。该类型仅供内部使用。 + +观察器的主要操作函数有: + +- `ui_mutation_observer_observe()`: 设置观察对象和相关参数。 +- `ui_mutation_observer_disconnect()`: 解除与观察对象的连接。 +- `ui_process_mutation_observers()`: 处理所有观察器的变更记录,这时观察器中注册的回调函数会被调用。 + +`ui_mutation_record_t` 的具体定义如下: + +```c +typedef struct ui_mutation_record_t { + ui_mutation_record_type_t type; + ui_widget_t *target; + list_t added_widgets; + list_t removed_widgets; + char *attribute_name; + char *property_name; +} ui_mutation_record_t; +``` + +`ui_mutation_record_type_t` 的具体定义如下: + +```c +typedef enum ui_mutation_record_type_t { + UI_MUTATION_RECORD_TYPE_NONE, + UI_MUTATION_RECORD_TYPE_ATTRIBUTES, + UI_MUTATION_RECORD_TYPE_PROPERTIES, + UI_MUTATION_RECORD_TYPE_CHILD_LIST, +} ui_mutation_record_type_t; +``` + +`UI_MUTATION_RECORD_TYPE_ATTRIBUTES` 表示部件的 attributes 成员变更,考虑到实现成本和实际需求,暂不实现这类变更检测。 + +`UI_MUTATION_RECORD_TYPE_PROPERTIES` 表示部件自身属性的变更,例如:x、y、width、height。考虑到实现成本和实际需求,暂只实现 x、y、width、height 的变更检测,这些属性是比较常用的,而且 UIServer 也需要它们。 + +`UI_MUTATION_RECORD_TYPE_CHILD_LIST` 表示部件的子部件列表的变更,当处理到该类型的变更时,可访问 `ui_mutation_record_t` 对象中的 added_widgets 和 removed_widgets 成员来获取新增和删除的子部件。 + +## 缺点 + +`ui_mutation_observer_*` 系列操作函数的名称太长。 + +## 备选方案 + +无。 + +## 采用策略 + +这是个新功能,对现有代码无破坏性改动。 diff --git a/docs/rfcs/active-refs-cn/0013-lib-platform.md b/docs/rfcs/active-refs-cn/0013-lib-platform.md new file mode 100644 index 000000000..96edf0bed --- /dev/null +++ b/docs/rfcs/active-refs-cn/0013-lib-platform.md @@ -0,0 +1,181 @@ +# 平台库 + +- 开始日期:2023-04-22 +- 目标主要版本:3.x +- 参考问题:[#246](https://github.com/lc-soft/LCUI/discussions/246) +- 实现 PR:无 + +## 概括 + +将窗口管理、消息循环、系统相关接口整合为 libplatform 平台支持库,为应用程序的平台相关功能提供跨平台统一的接口。 + +## 基本示例 + +应用程序的初始化和主循环: + +```c +#include +#include + +int main(int argc, char *argv[]) +{ + // 初始化应用程序,传入的字符串用于注册窗口类名 + app_init(L"My Application"); + + // 运行主循环 + return app_run(); +} +``` + +事件处理: + +```c +#include +#include + +#define MY_CUSTOM_EVENT (APP_EVENT_USER + 1) + +void on_my_custom_event(app_event_t *e, void *arg) +{ + const char *str = arg; + + printf("my custom event, str: %s\n", str); +} + +int main(int argc, char *argv[]) +{ + app_window_t *wnd; + app_event_t e = { 0 }; + const char *listener_data = "event listener data"; + const char *event_data = "event data"; + + // 设置事件类型 + e.type = MY_CUSTOM_EVENT; + // 设置事件相关数据 + e.data = event_data; + + app_init(L"My Application"); + // 添加事件处理器,也就是将回调函数与事件绑定 + app_on_event(MY_CUSTOM_EVENT, on_my_custom_event, listener_data); + // 投递事件到事件队列,等待被事件循环处理 + app_post_event(&e); + return app_run(); +} +``` + +窗口管理: + +```c +#include +#include + +void on_window_paint(app_event_t *e, void *arg) +{ + app_window_paint_t paint = { 0 }; + // 开始绘制,创建绘制上下文 + paint = app_window_begin_paint(e->window, &e->paint.rect); + // 自定义绘制窗口内容,例如填充白色: + pd_canvas_fill_rect(&paint->canvas, RGB(255, 255, 255), NULL, TRUE); + // 结束绘制,销毁绘制上下文 + app_window_end_paint(e->window, paint); +} + +int main(int argc, char *argv[]) +{ + app_window_t *wnd; + + app_init(L"My Application"); + // 创建一个窗口,并设置初始标题、位置和尺寸 + wnd = app_window_create("Main window", 200, 200, 800, 600, NULL); + // 设置尺寸 + app_window_set_size(wnd, 320, 240); + // 设置位置 + app_window_set_position(wnd, 100, 100); + // 激活窗口 + app_window_activate(wnd); + // 添加窗口绘制事件处理器 + app_on_event(APP_EVENT_PAINT, on_window_paint, NULL); + return app_run(); +} +``` + +使用步进定时器管理定时循环,典型的用法是在渲染每帧之前调用一次 Tick 回调函数,例如 1 秒内调用 60 次以实现每秒渲染 60 帧画面。 + +```c +#include + +void on_tick(step_timer_t *timer, void *data) +{ + int *frames = data; + + *frames += 1; + printf("tick\n"); +} + +int main(int argc, char *argv) +{ + int frames = 0; + step_timer_t timer; + + // 设置每秒 60 tick + timer.target_elapsed_time = 1000 / 60; + timer.is_fixed_time_step = TRUE; + step_timer_init(&timer); + while (frames <= 240) { + step_timer_tick(&timer, on_tick, &frames); + } + return 0; +} +``` + +## 动机 + +## 详细设计 + +### 步进定时器 + +2.x 版本中的步进定时器是基于互斥锁、条件变量定时等待实现的,每次调用 `StepTimer_Remain()` 时若当前帧耗时低于指定的平均耗时则会主动进入休眠以补全总耗时。这种做法会阻塞事件队列,况且步进定时器的目的只是为了限制渲染帧率,没必要连事件队列也限制,因此,应该重新设计步进定时器,取消它的主动休眠能力。 + +2.x 版本中的 UWP 适配代码用到了 DirectX 示例项目中自带的 StepTimer,它的用法如下: + +```cpp +// 加载应用程序时加载并初始化应用程序资产。 +Main::Main(const std::shared_ptr& deviceResources) : + m_deviceResources(deviceResources) +{ + // 注册以在设备丢失或重新创建时收到通知 + m_deviceResources->RegisterDeviceNotify(this); + + m_renderer = std::unique_ptr(new Renderer(m_deviceResources)); + + // TODO: 如果需要默认的可变时间步长模式之外的其他模式,请更改计时器设置。 + // 例如,对于 60 FPS 固定时间步长更新逻辑,请调用: + /* + m_timer.SetFixedTimeStep(true); + m_timer.SetTargetElapsedSeconds(1.0 / 60); + */ +} + +// 每帧更新一次应用程序状态。 +void Main::Update() +{ + // 更新场景对象。 + m_timer.Tick([&]() { + m_renderer->Update(m_timer); + }); +} +``` + +`m_timer.Tick()` 会让 `m_renderer->Update(m_timer)` 仅在合适的时机调用。显然,这种用法与我们的需求是非常匹配的,我们可以参考[Microsoft/DirectXTK/StepTimer](https://github.com/Microsoft/DirectXTK/wiki/StepTimer)的源码,将其改用 C 语言实现,以供 LCUI 的主循环使用。 + +## 缺点 + +无。 + +## 备选方案 + +改用 SDL。不建议采用此方案,因为改动较大,成本太高,还要投入成本去学习 SDL。 + +## 采用策略 + +这是个破坏性的改动,涉及主循环、窗口管理的代码。 diff --git a/docs/rfcs/active-refs-cn/0014-lib-ctest.md b/docs/rfcs/active-refs-cn/0014-lib-ctest.md new file mode 100644 index 000000000..08abae0d6 --- /dev/null +++ b/docs/rfcs/active-refs-cn/0014-lib-ctest.md @@ -0,0 +1,108 @@ +# 测试库 + +- 开始日期:2023-06-03 +- 目标主要版本:3.x +- 参考问题:无 +- 实现 PR:无 + +## 概括 + +一个简单的测试库,用于统计测试用例执行结果和耗时,以友好的格式输出测试结果。 + +## 基本示例 + +```c +#include + +void test_stdio(void) +{ + int num = 0; + + ctest_equal_int("the string \"100\" should be converted to 100", + sscanf("100", "%d", &num), 100); +} + +int main(void) +{ + ctest_describe("stdio.h", test_stdio); + return ctest_finish(); +} +``` + +运行后输出: + +```terminal + stdio.h + ✓ the string "100" should be converted to 100 + + 1 passing (0ms) +``` + +## 动机 + +- **命名规范化:** `describe()`、`print_test_result()`、`it_i()` 命名比较随意,不太容易看出它们同属一个库。 +- **减少依赖:** ctest 的 `it_rectf()`、`it_rect()` 等函数依赖图形库和 UI 库的数据类型,其它用到 ctest 的库会因此依赖这些库。 +- **适应更多的数据类型:** 内置的 `it_s()`、`it_b()`、`it_i()` 不够用,而且它们的大部分实现代码是一样的,应该优化。 + +## 详细设计 + +### 调整命名 + +命名都以 `ctest_` 开头。 + +`it_`开头的测试函数改为以 `ctest_equal_` 开头,并将 s、i、b 改为更完整的单词:str、int、bool。 + + +### 提升扩展性 + +新增 `ctest_equal()` 函数,接收字符串转换函数 `to_str` 和自定义数据的指针。 + +```c +bool ctest_equal(const char *name, int (*to_str)(void *, char *, unsigned), + void *actual, void *expected); +``` + +原有的 `it_s()`、`it_int()` 等函数改为基于 `ctest_equal()` 实现,例如: + +```c +int ctest_int_to_str(void *data, char *str, unsigned max_len) +{ + return snprintf(str, max_len, "%d", *(int *)data); +} + +bool ctest_euqal_int(const char *name, int actual, int expected) +{ + return ctest_equal(name, ctest_int_to_str, &actual, &expected); +} + +``` + +### 减少依赖 + +内部移除 `it_rect()`、`it_rectf()`,由 UI 库和图形库内部提供这些数据类型值的测试函数,例如: + +```c +int ui_rect_to_str(ui_rect_t *rect, char *str, unsigned max_len) +{ + return snprintf(str, max_len, "(%g, %g, %g, %g)", rect->x, rect->y, + rect->width, rect->height); +} + +static inline bool ctest_euqal_ui_rect(const char *name, ui_rect_t *actual, + ui_rect_t *expected) +{ + return ctest_equal(name, (ctest_to_str_func_t)ui_rect_to_str, actual, expected); +} +``` + +## 缺点 + +无。 + +## 备选方案 + +无。 + +## 采用策略 + +全局替换。 diff --git a/lib/platform/README.md b/lib/platform/README.md index 5a7eeaf93..329a40cde 100644 --- a/lib/platform/README.md +++ b/lib/platform/README.md @@ -10,97 +10,14 @@ 应用程序。 -```c -#include - -int main(int argc, char *argv[]) -{ - // 初始化应用程序,传入的字符串用于注册窗口类名 - app_init(L"My Application"); - - // 运行主循环 - return app_run(); -} -``` - `app_run()` 会进入事件循环中一直处理并等待下个事件,直到收到 QUIT 事件时才会返回。 ### Platform::App::Events -事件队列。 - -```c -#include -#include - -#define MY_CUSTOM_EVENT (APP_EVENT_USER + 1) - -void on_my_custom_event(app_event_t *e, void *arg) -{ - const char *str = arg; - - printf("my custom event, str: %s\n", str); -} - -int main(int argc, char *argv[]) -{ - app_window_t *wnd; - app_event_t e = { 0 }; - const char *listener_data = "event listener data"; - const char *event_data = "event data"; - - // 设置事件类型 - e.type = MY_CUSTOM_EVENT; - // 设置事件相关数据 - e.data = event_data; - - app_init(L"My Application"); - // 添加事件处理器,也就是将回调函数与事件绑定 - app_on_event(MY_CUSTOM_EVENT, on_my_custom_event, listener_data); - // 投递事件到事件队列,等待被事件循环处理 - app_post_event(&e); - return app_run(); -} -``` - ### Platform::App::Window 窗口。 -```c -#include -#include - -void on_window_paint(app_event_t *e, void *arg) -{ - app_window_paint_t paint = { 0 }; - // 开始绘制,创建绘制上下文 - paint = app_window_begin_paint(e->window, &e->paint.rect); - // 自定义绘制窗口内容,例如填充白色: - pd_canvas_fill_rect(&paint->canvas, RGB(255, 255, 255), NULL, TRUE); - // 结束绘制,销毁绘制上下文 - app_window_end_paint(e->window, paint); -} - -int main(int argc, char *argv[]) -{ - app_window_t *wnd; - - app_init(L"My Application"); - // 创建一个窗口,并设置初始标题、位置和尺寸 - wnd = app_window_create("Main window", 200, 200, 800, 600, NULL); - // 设置尺寸 - app_window_set_size(wnd, 320, 240); - // 设置位置 - app_window_set_position(wnd, 100, 100); - // 激活窗口 - app_window_activate(wnd); - // 添加窗口绘制事件处理器 - app_on_event(APP_EVENT_PAINT, on_window_paint, NULL); - return app_run(); -} -``` - ### Platform::StepTimer 步进定时器,用于管理定时循环。典型的用法是在渲染每帧之前调用一次 Tick 回调函数,例如 1 秒内调用 60 次以实现每秒渲染 60 帧画面。 diff --git a/tests/test_string_render.c b/tests/test_string_render.c index fef89ded7..bbcafe3c0 100644 --- a/tests/test_string_render.c +++ b/tests/test_string_render.c @@ -1,5 +1,4 @@ -#include -#include +#include int main(void) { @@ -7,8 +6,8 @@ int main(void) pd_canvas_t img; pd_pos_t pos = { 0, 80 }; pd_rect_t area = { 0, 0, 320, 240 }; - pd_text_t* txt = pd_text_create(); - pd_text_style_t txtstyle; + pd_text_t *text = pd_text_create(); + pd_text_style_t style; /* 初始化字体处理功能 */ pd_font_library_init(); @@ -19,19 +18,19 @@ int main(void) pd_canvas_fill(&img, pd_rgb(240, 240, 240)); /* 设置文本的字体大小 */ - pd_text_style_Init(&txtstyle); - txtstyle.pixel_size = 24; - txtstyle.has_pixel_size = TRUE; + pd_text_style_init(&style); + style.pixel_size = 24; + style.has_pixel_size = TRUE; /* 设置文本图层的固定尺寸、文本样式、文本内容、对齐方式 */ - pd_text_set_fixed_size(txt, 320, 240); - pd_text_set_style(txt, &txtstyle); - pd_text_set_align(txt, PD_TEXT_ALIGN_CENTER); - pd_text_write(txt, L"这是一段测试文本\nHello, World!", NULL); - pd_text_update(txt, NULL); + pd_text_set_fixed_size(text, 320, 240); + pd_text_set_style(text, &style); + pd_text_set_align(text, PD_TEXT_ALIGN_CENTER); + pd_text_write(text, L"这是一段测试文本\nHello, World!", NULL); + pd_text_update(text, NULL); /* 将文本图层绘制到图像中,然后将图像写入至 png 文件中 */ - pd_text_render_to(txt, area, pos, &img); + pd_text_render_to(text, area, pos, &img); ret = pd_write_png_file("test_string_render.png", &img); pd_canvas_destroy(&img);