Clay 如何接入终端 ANSI 渲染器输出字符界面?
2026/9/14 6:38:21 网站建设 项目流程

Clay 如何接入终端 ANSI 渲染器输出字符界面?

【免费下载链接】clayHigh performance UI layout library in C.项目地址: https://gitcode.com/GitHub_Trending/clay9/clay

Clay 是一个纯布局库:它只输出排序好的渲染指令数组(Clay_RenderCommandArray),不负责把指令画到任何平台上。这篇文章的任务是把这些指令接进终端,用 ANSI 转义序列输出一个字符界面。仓库提供了完整的可运行示例 examples/terminal-example/main.c 和渲染器实现 renderers/terminal/clay_renderer_terminal_ansi.c,按下面的顺序照做即可得到同一个效果:初始化 Clay 内存、注册文本测量函数、每帧调用Clay_Terminal_Render把布局绘制到终端。

渲染器提供了哪些接入点

ANSI 渲染器是一个可以直接#include.c文件,对主程序暴露两个函数(见 renderers/terminal/clay_renderer_terminal_ansi.c):

  • Clay_SetMeasureTextFunction注册用的测量函数Console_MeasureText:按字符数估算文本尺寸,并把结果乘以columnWidth放大到 Clay 的布局坐标系。userData参数传一个int指针,指向columnWidth的值。
  • Clay_Terminal_Render(Clay_RenderCommandArray renderCommands, int width, int height, int columnWidth):每帧调用一次,把一帧渲染指令画到终端。

渲染器内部用 ANSI 转义序列工作:

  • 每帧开头输出\033[H\033[J清屏;
  • 通过printf("\033[%d;%dH", y + 1, x + 1)移动光标定位字符;
  • 矩形和边框不画像素,而是把颜色四个通道的平均值换算成四个挡位的块状字符:(平均值 > 0.75)、(> 0.5)、(> 0.25)、(其余)。

它处理的渲染指令类型只有五类:CLAY_RENDER_COMMAND_TYPE_TEXTCLAY_RENDER_COMMAND_TYPE_SCISSOR_STARTCLAY_RENDER_COMMAND_TYPE_SCISSOR_ENDCLAY_RENDER_COMMAND_TYPE_RECTANGLECLAY_RENDER_COMMAND_TYPE_BORDER。遇到其他指令类型(例如图片指令)时,程序会输出Error: unhandled render command.并以退出码 1 结束(定义了CLAY_OVERFLOW_TRAP时先触发SIGTRAP)。所以接入这个渲染器的布局里不要使用图片类元素。

columnWidth是像素与字符格之间的换算系数:布局坐标除以它还原成字符坐标,文本测量结果乘以它放大成布局坐标。示例中取 16。

准备工作

  • 一个支持 C99 的编译器;构建系统若走 CMake,仓库要求的版本是 3.27(见 CMakeLists.txt 的cmake_minimum_required(VERSION 3.27))。
  • 终端示例只在非 MSVC 环境下参与构建:根目录 CMakeLists.txt 中add_subdirectory("examples/terminal-example")包在if (NOT MSVC)里,且由CLAY_INCLUDE_ALL_EXAMPLES(默认 ON)或CLAY_INCLUDE_DEMOS(默认 OFF)控制是否构建。
  • 示例 examples/terminal-example/CMakeLists.txt 将标准设为 C99,Linux 下额外链接m库。

接入步骤

主程序只需要一个 C 文件。以 examples/terminal-example/main.c 为模板,结构如下:

// Must be defined in one file, _before_ #include "clay.h" #define CLAY_IMPLEMENTATION #include <unistd.h> #include "clay.h" #include "renderers/terminal/clay_renderer_terminal_ansi.c" #include "examples/shared-layouts/clay-video-demo.c" void HandleClayErrors(Clay_ErrorData errorData) { printf("%s", errorData.errorText.chars); } int main() { const int width = 145; // 终端布局宽度(字符格) const int height = 41; // 终端布局高度(字符格) int columnWidth = 16; // 每字符格对应的布局坐标尺寸 uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); Clay_Initialize(arena, (Clay_Dimensions) {.width = (float) width * columnWidth, .height = (float) height * columnWidth}, (Clay_ErrorHandler) {HandleClayErrors}); // Tell clay how to measure text Clay_SetMeasureTextFunction(Console_MeasureText, &columnWidth); ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize(); while (true) { Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); Clay_Terminal_Render(renderCommands, width, height, columnWidth); fflush(stdout); sleep(1); } }

上面代码与示例文件一致,唯一差别是#include路径:示例中用相对路径../../clay.h,这里按仓库根目录书写。widthheightcolumnWidth就是示例使用的数值 145、41、16,换成你自己的终端尺寸时,三个值要一起改,且Clay_Initialize里的布局尺寸始终写成width * columnWidthheight * columnWidth

各步骤的用途:

  1. CLAY_IMPLEMENTATION必须先于clay.h定义,且整个工程只能在一个文件里定义。随后#include渲染器的.c文件,而不是链接它。
  2. 初始化顺序按 README.md 中 "Lifecycle for public functions" 一节执行:Clay_MinMemorySize->Clay_CreateArenaWithCapacityAndMemory->Clay_Initialize->Clay_SetMeasureTextFunctionClay_MinMemorySize返回当前配置所需的字节数,示例用malloc分配,README 说明这里用malloc只是示例,任何能提供对应大小地址空间的分配器都可以。
  3. 注册文本测量函数是必做项:布局里用了CLAY_TEXT却没调用Clay_SetMeasureTextFunction(或传了空指针)时,Clay 会报CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED,由错误处理器HandleClayErrors输出。
  4. 渲染循环:每帧用 examples/shared-layouts/clay-video-demo.c 里的ClayVideoDemo_CreateLayout生成布局,再交给Clay_Terminal_Render。这个 demo 内部自己调用了Clay_BeginLayoutClay_EndLayout,如果你换成自己的布局代码,就要按 README 的每帧流程自己写Clay_BeginLayout/CLAY(...)/Clay_EndLayoutfflush(stdout)保证转义序列立刻输出,sleep(1)让界面每秒刷新一次。

构建并验证结果

在仓库根目录构建全部示例(CLAY_INCLUDE_ALL_EXAMPLES默认开启):

cmake -S . -B build cmake --build build ./build/examples/terminal-example/clay_examples_terminal

运行后终端会持续输出 Clay 视频 demo 的字符界面:顶部是标题栏,左侧是文档列表,右侧是内容区,矩形底色显示为不同密度的块状字符,内容每帧重新绘制。判断是否跑通就看两点:

  • 终端每秒刷新一次布局,且widthheight之外的区域没有乱码光标残留——渲染器每帧开头的\033[H\033[J负责清屏;
  • 程序没有打印Error: unhandled render command.。一旦出现这条信息说明布局里出现了该渲染器不处理的指令类型,进程会以退出码 1 结束(CLAY_OVERFLOW_TRAP下先触发SIGTRAP),需要回到布局定义里检查元素类型。

已知限制

  • 文本测量是近似值Console_MeasureText按字符数计宽,源文件中标注this function is very wrong, it measures in characters, I have no idea what is the size in pixels,所以它适合等宽字符格场景,不适合做精确排版。
  • 只能画文本、矩形和边框。图片等其他指令类型会走default分支导致进程退出,前面已说明。
  • 仅支持非 MSVC 平台参与构建,根目录 CMake 对NOT MSVC之外不添加这个示例。
  • Clay 不管终端窗口本身。README 明确 "Clay doesn't handle window related tasks",示例直接写死了 145x41 的布局尺寸;如果要在真实窗口尺寸下自适应,需要自己从终端环境取尺寸并调用Clay_SetLayoutDimensions更新。
  • 布局里用了CLAY_TEXT却没注册测量函数、或 arena 容量不足(CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED)这类错误,都会通过Clay_Initialize传入的HandleClayErrors打印到终端,排查时先看它输出的错误文本。

【免费下载链接】clayHigh performance UI layout library in C.项目地址: https://gitcode.com/GitHub_Trending/clay9/clay

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询