Clay Odin 绑定指南:用 `if` 作用域语法在 Odin 中声明式构建 Clay 布局

Clay Odin 绑定指南:用 `if` 作用域语法在 Odin 中声明式构建 Clay 布局 Clay Odin 绑定指南用if作用域语法在 Odin 中声明式构建 Clay 布局【免费下载链接】clayHigh performance UI layout library in C.项目地址: https://gitcode.com/GitHub_Trending/clay9/clay本篇围绕 bindings/odin/README.md 展开讲解如何将 C 布局库 Clay 的 Odin 语言绑定接入自己的项目从拷贝clay-odin包、初始化内存与错误处理器到用 Odin 特有的if作用域写法声明布局树再到遍历RenderCommand渲染指令完成绘制。读完本文你可以独立完成一个 Clay Odin 项目的完整布局与渲染管线并理解绑定层对 C API 的映射规则。绑定包构成与目录结构Odin 绑定目录bindings/odin/包含两部分核心内容clay-odin绑定包本体。除核心文件 clay.odin 外还按平台预编译好了 Clay 的 C 库产物绑定文件通过foreign import按操作系统自动选择对应库平台链接的库文件Windowswindows/clay.libLinuxlinux/clay.amacOS (arm64)macos-arm64/clay.amacOS (x64)macos/clay.aWASM (wasm32/wasm64p32)wasm/clay.o这段条件导入逻辑见 bindings/odin/clay-odin/clay.odin#L5-L17。examples/clay-official-website用 Odin 实现的 Clay 官网界面示例包含主程序 clay-official-website.odin 与基于 raylib 的渲染器 clay_renderer_raylib.odin可作为可运行的完整参考。目录中还有一份 ols.json用于开启 Odin Language Server 的符号文档、悬停提示与代码片段功能便于在 IDE 中浏览绑定 API。C 与 Odin 的最核心差异if作用域声明子元素绑定文档明确指出C API 与 Odin 绑定之间最显著的区别在于Odin 中声明带子元素的组件时用if语句来创建子元素的作用域。C 侧的 Element 宏形式与 Odin 等价形式对比如下完整继承自原 README// C form of element macros // Define an element with 16px of x and y padding CLAY({ .id CLAY_ID(Outer), .layout { .padding CLAY_PADDING_ALL(16) } }) { // Child elements here }// Odin form of element macros if clay.UI(clay.ID(Outer))({ layout { padding clay.PaddingAll(16) }}) { // Child elements here }从 clay.odin 的源码结构看这个语法背后是一套重载 自动闭合机制UI是一个重载别名提供UI_WithId(id)与UI_AutoId()两个实现调用clay.UI(id)会先执行_OpenElementWithId(id)对应 C 的Clay_OpenElementWithId并返回一个ConfigureOpenElement高阶函数。if的条件表达式就是对该配置函数的调用——{ ... }这个结构体字面量作为ElementDeclaration传入等价于 C 中CLAY(...)宏的参数部分。UI_WithId带有(deferred_none _CloseElement)修饰符当if语句不写else分支时Odin 会自动执行_CloseElement对应 C API 中必须成对调用的Clay_CloseElement。因此无子元素的组件写作if clay.UI()({...}) {}即可自动闭合避免漏写 close 导致的UnbalancedOpenClose错误该错误类型定义见 bindings/odin/clay-odin/clay.odin#L449-L459。Text同样是重载别名静态字符串走TextStatic将字符串标记为静态分配动态字符串走TextDynamic底层都调用_OpenTextElement见 bindings/odin/clay-odin/clay.odin#L544-L557。Quick Start 完整流程以下步骤完整继承自原 README并结合源码补全了参数细节。第 1 步拷贝 clay-odin 目录并导入将 bindings/odin/clay-odin 目录整体拷贝到项目中然后以别名导入import clay clay-odin第 2 步申请静态内存并初始化先通过clay.MinMemorySize()询问 Clay 所需的静态内存量用它创建一块内存和 Arena再调用clay.Initialize(arena, dimensions, errorHandler)。Odin 版错误处理器是一个 C 调用约定的 procerror_handler :: proc c (errorData: clay.ErrorData) { // Do something with the error data. } min_memory_size : clay.MinMemorySize() memory : make([^]u8, min_memory_size) arena: clay.Arena clay.CreateArenaWithCapacityAndMemory(uint(min_memory_size), memory) clay.Initialize(arena, {1080, 720}, { handler error_handler })这三个函数与 C 头文件中的导出函数一一对应Clay_MinMemorySize、Clay_CreateArenaWithCapacityAndMemory见 clay.h#L933-L938Clay_Initialize接收 arena、布局尺寸与错误处理器并返回Clay_Context指针多上下文支持可参考 C 文档。若 Arena 容量小于MinMemorySize()所需值布局阶段会触发ArenaCapacityExceeded错误。第 3 步注册文本测量函数必须提供一个 C 调用约定的measure_text(text, config)proc并通过clay.SetMeasureTextFunction(function)注册Clay 依赖它完成文本测量与换行// Example measure text function measure_text :: proc c ( text: clay.StringSlice, config: ^clay.TextElementConfig, userData: rawptr, ) - clay.Dimensions { // clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing, etc.. // Note: clay.String-chars is not guaranteed to be null terminated return { width f32(text.length * i32(config.fontSize)), height f32(config.fontSize), } } // Tell clay how to measure text clay.SetMeasureTextFunction(measure_text, nil)两个实操细节值得注意clay.TextElementConfig包含fontId、fontSize、letterSpacing、lineHeight、wrapMode、textAlignment等字段定义见 bindings/odin/clay-odin/clay.odin#L108-L117字体选择完全由你自己的渲染器负责Clay 只传递fontId。注释特别强调clay.String-chars不保证以 null 结尾因此处理文本时务必使用length截断。官方示例的 raylib 渲染器中measure_text_unicode用string(text.chars[:text.length])显式按长度截断并按fontSize / font.baseSize做缩放、叠加letterSpacing计算总宽度见 bindings/odin/examples/clay-official-website/clay_renderer_raylib.odin#L24-L89同一文件还提供了 ASCII 快速路径measure_text_ascii在性能敏感时可二选一。第 4 步可选更新指针状态若要支持鼠标悬停/点击等交互每帧调用clay.SetPointerState(pointerPosition, isPointerDown)// Update internal pointer position for handling mouseover / click / touch events clay.SetPointerState( { mouse_pos_x, mouse_pos_y }, is_mouse_down, )对应 C API 的Clay_SetPointerState见 clay.h#L941。配合绑定暴露的Hovered()、PointerOver(id)、OnHover(callback, userData)等函数即可实现完整的鼠标交互均绑定自 bindings/odin/clay-odin/clay.odin#L494-L497。第 5 步BeginLayout 声明布局树调用clay.BeginLayout()后用if clay.UI(...)({...})声明布局。以下是原 README 中“固定宽度侧边栏 弹性主内容区”的完整示例其中展示了可复用的组件 proc、静态布局配置、循环渲染列表项等 Odin 惯用法// Define some colors. COLOR_LIGHT :: clay.Color{224, 215, 210, 255} COLOR_RED :: clay.Color{168, 66, 28, 255} COLOR_ORANGE :: clay.Color{225, 138, 50, 255} COLOR_BLACK :: clay.Color{0, 0, 0, 255} // Layout config is just a struct that can be declared statically, or inline sidebar_item_layout : clay.LayoutConfig { sizing { width clay.SizingGrow({}), height clay.SizingFixed(50) }, } // Re-useable components are just normal procs. sidebar_item_component :: proc(index: u32) { if clay.UI()({ id clay.ID(SidebarBlob, index), layout sidebar_item_layout, backgroundColor COLOR_ORANGE, }) {} } // An example function to create your layout tree create_layout :: proc() - clay.ClayArray(clay.RenderCommand) { // Begin constructing the layout. clay.BeginLayout() // An example of laying out a UI with a fixed-width sidebar and flexible-width main content // NOTE: To create a scope for child components, the Odin API uses if with components that have children if clay.UI()({ id clay.ID(OuterContainer), layout { sizing { width clay.SizingGrow({}), height clay.SizingGrow({}) }, padding { 16, 16, 16, 16 }, childGap 16, }, backgroundColor { 250, 250, 255, 255 }, }) { if clay.UI()({ id clay.ID(SideBar), layout { layoutDirection .TopToBottom, sizing { width clay.SizingFixed(300), height clay.SizingGrow({}) }, padding { 16, 16, 16, 16 }, childGap 16, }, backgroundColor COLOR_LIGHT, }) { if clay.UI()({ id clay.ID(ProfilePictureOuter), layout { sizing { width clay.SizingGrow({}) }, padding { 16, 16, 16, 16 }, childGap 16, childAlignment { y .Center }, }, backgroundColor COLOR_RED, cornerRadius { 6, 6, 6, 6 }, }) { if clay.UI()({ id clay.ID(ProfilePicture), layout { sizing { width clay.SizingFixed(60), height clay.SizingFixed(60) }, }, image { // How you define profile_picture depends on your renderer. imageData profile_picture, sourceDimensions { width 60, height 60, }, }, }) {} clay.Text( Clay - UI Library, clay.TextConfig({ textColor COLOR_BLACK, fontSize 16 }), ) } // Standard Odin code like loops, etc. work inside components. // Here we render 5 sidebar items. for i in u32(0)..5 { sidebar_item_component(i) } } if clay.UI()({ id clay.ID(MainContent), layout { sizing { width clay.SizingGrow({}), height clay.SizingGrow({}) }, }, backgroundColor COLOR_LIGHT, }) {} } // Returns a list of render commands return clay.EndLayout() }布局相关的主要类型在绑定层均有完整定义LayoutConfigsizing、padding、childGap、childAlignment、layoutDirection见 bindings/odin/clay-odin/clay.odin#L420-L426ElementDeclaration则聚合了backgroundColor、border、clip、transition、floating、custom等全部元素属性见 bindings/odin/clay-odin/clay.odin#L434-L447。一点版本提示原 README 示例中EndLayout()未传参数而当前绑定源码中其签名为EndLayout :: proc(deltaTime: c.float)见 bindings/odin/clay-odin/clay.odin#L489官方 Odin 示例中即以每帧时间调用return clay.EndLayout(frametime)见 bindings/odin/examples/clay-official-website/clay-official-website.odin#L428。建议以源码签名和示例为准将deltaTime传入。第 6 步处理渲染指令调用自己的布局 proc得到clay.ClayArray(clay.RenderCommand)再用RenderCommandArray_Get逐条取出指令并在渲染器中分派render_commands : create_layout() for i in 0..i32(render_commands.length) { render_command : clay.RenderCommandArray_Get(render_commands, i) switch render_command.commandType { case .Rectangle: DrawRectangle(render_command.boundingBox, render_command.config.rectangleElementConfig.color) // ... Implement handling of other command types } }RenderCommandType枚举的完整取值None、Rectangle、Border、Text、Image、ScissorStart、ScissorEnd、OverlayColorStart、OverlayColorEnd、Custom见 bindings/odin/clay-odin/clay.odin#L79-L90对应 C 端Clay_RenderCommandArray_Get见 clay.h#L1009。官方示例的 raylib 渲染器 clay_raylib_render 演示了完整的分派实现按commandType切换Text 指令读取renderData.text的字符串内容与字体配置并使用临时分配器维护overlay_colors栈来处理OverlayColorStart/End的叠加色。API 命名映射与辅助构造函数原 README 末尾说明所有 C 公开函数与宏都有 Odin 绑定对应命名规则一般为CLAY_IDC 宏→clay.IDOdinClay_XC 函数→clay.X。绑定通过(link_prefix Clay_, default_calling_convention c)修饰符批量剥离Clay_前缀并统一为 C 调用约定见 bindings/odin/clay-odin/clay.odin#L474。常用映射示例C APIOdin 绑定CLAY_ID(x)clay.ID(x)Clay_MinMemorySize()clay.MinMemorySize()Clay_BeginLayout()/Clay_EndLayout()clay.BeginLayout()/clay.EndLayout(deltaTime)Clay_RenderCommandArray_Get()clay.RenderCommandArray_Get()Clay_Hovered()clay.Hovered()CLAY_SIZING_GROW()clay.SizingGrow({})CLAY_PADDING_ALL(16)clay.PaddingAll(16)此外绑定层提供了一批返回结构体的辅助构造 proc见 bindings/odin/clay-odin/clay.odin#L559-L599用法上替代了 C 端同名宏SizingFit(sizeMinMax {})/SizingGrow(sizeMinMax {})/SizingFixed(size)/SizingPercent(percent)四个方向尺寸构造器均返回SizingAxisPaddingAll(allPadding)四边等值内边距BorderOutside(width)与BorderAll(width)前者betweenChildren为 0后者包含子元素间距CornerRadiusAll(radius)四角等值圆角ID(label, index 0)基于_HashString生成元素 ID支持带索引的重复元素ID_LOCAL(label, index)则以当前打开元素 ID 为种子做哈希偏移适合局部作用域内避免 ID 冲突。一个值得注意的结构差异SizingAxis中的SizingConstraints采用 raw union 区分sizeMinMax与sizePercent源码注释说明其min字段在百分比模式下有不同于 clay.h 的语义“slightly different to clay.h due to lack of C anonymous unions”见 bindings/odin/clay-odin/clay.odin#L375-L384。使用SizingPercent时应直接传 0~1 的百分比浮点值官方示例中即写作clay.SizingPercent(0.55)见 bindings/odin/examples/clay-official-website/clay-official-website.odin#L93。完整实战参考Odin 版 Clay 官网examples/clay-official-website 用 Odin 重写了 Clay 官网界面含桌面端与移动端双布局、滚动容器、渐变动画其 main 函数 展示了生产级用法的关键要点初始化序列与 Quick Start 一致MinMemorySize→CreateArenaWithCapacityAndMemory→Initialize→SetMeasureTextFunction每帧循环中依次调用SetPointerState转换 raylib 鼠标坐标为clay.Vector2、UpdateScrollContainers传入滚轮增量与deltaTime启用滚动容器、SetLayoutDimensions窗口可缩放时同步布局尺寸然后构建布局并交给渲染器错误处理器按errorData.errorType分支示例中对DuplicateId做了专门处理见 bindings/odin/examples/clay-official-website/clay-official-website.odin#L436-L440字体通过loadFont按fontId注册到raylib_fonts动态数组测量函数再按config.fontId查表——这是fontId机制在真实项目中的落地方式按 D 键可切换clay.SetDebugModeEnabled在窗口内叠加调试信息排查布局问题。其布局代码也印证了前文要点滚动容器通过clip {vertical true, childOffset clay.GetScrollOffset()}配合UpdateScrollContainers实现响应式布局则根据窗口宽度在LandingPageDesktop与LandingPageMobile之间切换见 bindings/odin/examples/clay-official-website/clay-official-website.odin#L405-L426。小结与适用边界Odin 绑定以 C 静态库.a/.lib WASM 目标.o的形式分发接入方式是把 clay-odin 目录拷入项目后import clay clay-odin平台库文件由clay.odin按编译目标自动选择与 C 的唯一语法级差异是if作用域if clay.UI(id)(config) { ... }打开元素并在if体中声明子元素deferred_none机制保证无 else 分支时自动_CloseElement完整 API 细节滚动容器、转场、浮动元素等与 C 一一对应所有Clay_*函数与CLAY_*宏均有clay.*对应项可参考根目录 README.md 中的完整 C 文档使用时注意两处与 C 侧的细微差别EndLayout当前签名需要deltaTime参数SizingAxis在百分比模式下min字段语义与 C 匿名联合体略有不同另外绑定层对chars不保证 null 结尾的约束要求文本处理必须按length截断。【免费下载链接】clayHigh performance UI layout library in C.项目地址: https://gitcode.com/GitHub_Trending/clay9/clay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考