├── .gitignore ├── examples └── video-demo-raylib │ ├── src │ ├── backends │ └── main.zig │ ├── resources │ └── Roboto-Regular.ttf │ ├── build.zig.zon │ └── build.zig ├── clay.c ├── LICENSE ├── README.md ├── backends └── raylib.zig └── src └── root.zig /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache/ 2 | zig-out/ 3 | -------------------------------------------------------------------------------- /examples/video-demo-raylib/src/backends: -------------------------------------------------------------------------------- 1 | ../../../backends -------------------------------------------------------------------------------- /clay.c: -------------------------------------------------------------------------------- 1 | #define CLAY_IMPLEMENTATION 2 | #include "clay.h" 3 | -------------------------------------------------------------------------------- /examples/video-demo-raylib/resources/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gota7/zig-clay/HEAD/examples/video-demo-raylib/resources/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/video-demo-raylib/build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "video-demo-raylib", 3 | .version = "0.0.0", 4 | .dependencies = .{ 5 | .clay = .{ 6 | .url = "git+https://github.com/Gota7/zig-clay#fc762b8366af0235c8fe2f0a837c41a2acbdfd08", 7 | .hash = "1220f6788bef883e4859c943d42737c495505c524d39c132e10226d604b2f99f895d", 8 | }, 9 | .@"raylib-zig" = .{ 10 | .url = "git+https://github.com/Not-Nik/raylib-zig?ref=devel#845af357e4bc5ed8d008a8bb64bd7c4b4906851d", 11 | .hash = "122081321a9b6653f90fee325553c9f64934f94b789e7ddbadf27f754a5e42537c4d", 12 | }, 13 | }, 14 | .paths = .{ 15 | "build.zig", 16 | "build.zig.zon", 17 | "src", 18 | "rescources", 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Gota7 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/video-demo-raylib/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // Although this function looks imperative, note that its job is to 4 | // declaratively construct a build graph that will be executed by an external 5 | // runner. 6 | pub fn build(b: *std.Build) void { 7 | const target = b.standardTargetOptions(.{}); 8 | const optimize = b.standardOptimizeOption(.{}); 9 | 10 | // Setup clay dependency. 11 | const clay_dep = b.dependency("clay", .{ 12 | .target = target, 13 | .optimize = optimize, 14 | }); 15 | const clay = clay_dep.module("clay"); 16 | 17 | const exe = b.addExecutable(.{ 18 | .name = "video-demo-raylib", 19 | .root_source_file = b.path("src/main.zig"), 20 | .target = target, 21 | .optimize = optimize, 22 | }); 23 | 24 | // Clay dep. 25 | exe.root_module.addImport("clay", clay); 26 | 27 | // Raylib dep. 28 | const raylib = b.dependency("raylib-zig", .{ 29 | .target = target, 30 | .optimize = optimize, 31 | }); 32 | exe.root_module.addImport("raylib", raylib.module("raylib")); 33 | const raylib_artifact = raylib.artifact("raylib"); 34 | exe.linkLibrary(raylib_artifact); 35 | 36 | b.installArtifact(exe); 37 | const run_cmd = b.addRunArtifact(exe); 38 | run_cmd.step.dependOn(b.getInstallStep()); 39 | if (b.args) |args| { 40 | run_cmd.addArgs(args); 41 | } 42 | const run_step = b.step("run", "Run the app"); 43 | run_step.dependOn(&run_cmd.step); 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-clay 2 | 3 | Tweaked bindings for the [clay](https://github.com/nicbarker/clay) C UI library. 4 | 5 | ## Features 6 | 7 | * Zig 0.13.0 compatibility. 8 | * Naming conventions follow zig standards. 9 | * Callbacks are more zig-like. 10 | * Config structures have default initializations. 11 | * Code is very similar to how it looks in C. 12 | 13 | ## Usage 14 | 15 | The main difference between this and the C API is the lack of macros. 16 | To get around this, a different approach was taken to emulate the feel of the C API: 17 | 18 | ```zig 19 | const layout = clay.beginLayout(); 20 | if (clay.child(&.{ 21 | clay.id("OuterContainer"), 22 | clay.rectangle(.{ .color = .{ .r = 43, .g = 41, .b = 51, .a = 255 } }), 23 | clay.layout(.{ 24 | .layout_direction = .top_to_bottom, 25 | .sizing = .{ 26 | .width = clay.sizingGrow(.{}), 27 | .height = clay.sizingGrow(.{}), 28 | }, 29 | .padding = .{ .x = 16, .y = 16 }, 30 | .child_gap = 16, 31 | }), 32 | 33 | // More config here. 34 | })) |outer_container| { 35 | defer outer_container.end(); 36 | clay.text("Hello World!", .{ 37 | .font_id = 0, 38 | .font_size = 24, 39 | .text_color = .{ .r = 255, .g = 255, .b = 255, .a = 255 }, 40 | }); 41 | 42 | // More child elements here. 43 | } 44 | 45 | // ... 46 | 47 | const commands = layout.end(); 48 | ``` 49 | 50 | C version for comparison: 51 | ```c 52 | Clay_BeginLayout(); 53 | CLAY( 54 | CLAY_ID("OuterContainer"), 55 | CLAY_RECTANGLE({ .color = { 43, 41, 51, 255 } }), 56 | CLAY_LAYOUT({ 57 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 58 | .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, 59 | .padding = { 16, 16 }, 60 | .childGap = 16 61 | }) 62 | 63 | // More config here. 64 | ) { 65 | CLAY_TEXT("Hello World!", CLAY_TEXT_CONFIG({ 66 | .fontId = 0, 67 | .fontSize = 24, 68 | .textColor = { 255, 255, 255, 255 } 69 | })); 70 | 71 | // More child elements here. 72 | } 73 | 74 | // ... 75 | 76 | Clay_RenderCommandArray renderCommands = Clay_EndLayout(); 77 | ``` 78 | 79 | While not as frequent, sometimes control flow is required during element configuration. 80 | In this scenario, manual configuration should be done: 81 | 82 | ```zig 83 | if (clay.childManualControlFlow()) |sidebar_button| { 84 | defer sidebar_button.end(); 85 | 86 | sidebar_button.config(clay.layout(sidebar_button_layout)); 87 | clay.onHover(handle_sidebar_interaction, @ptrFromInt(i)); 88 | if (clay.hovered()) 89 | sidebar_button.config(clay.rectangle(.{ 90 | .color = .{ .r = 120, .g = 120, .b = 120, .a = 255 }, 91 | .corner_radius = clay.cornerRadius(8), 92 | })); 93 | 94 | // More config here. 95 | 96 | sidebar_button.endConfig(); 97 | 98 | // Children. 99 | clay.text(document.title, .{ 100 | .font_id = font_id_body_16, 101 | .font_size = 20, 102 | .text_color = color_white, 103 | }); 104 | 105 | // More child elements here. 106 | } 107 | ``` 108 | 109 | In this scenario, care should be taken that the order is: 110 | 111 | 1. Call `childManualControlFlow()` 112 | 2. Config (calls to `.config()`) 113 | 3. Call `.endConfig()` 114 | 4. Child elements 115 | 5. Call `.end()` 116 | 117 | ## Getting Started 118 | 119 | The `example` folder has an example of a working project. 120 | Other than the syntatical differences seen above, usage of the zig API should be fairly 1-to-1 with the C library. 121 | -------------------------------------------------------------------------------- /backends/raylib.zig: -------------------------------------------------------------------------------- 1 | const clay = @import("clay"); 2 | const raylib = @import("raylib"); 3 | const std = @import("std"); 4 | 5 | /// Font wrapper for raylib renderer. 6 | pub const Font = struct { 7 | /// Font ID for clay. 8 | id: u32, 9 | /// Raylib font information. 10 | font: raylib.Font, 11 | }; 12 | 13 | /// Global font list. 14 | pub var fonts: ?std.ArrayList(Font) = null; 15 | 16 | /// Convert a clay color to a raylib one. 17 | /// * `color` - Color to convert to a raylib one. 18 | pub fn clayColorToRaylib(color: clay.Color) raylib.Color { 19 | return .{ 20 | .r = @intFromFloat(color.r), 21 | .g = @intFromFloat(color.g), 22 | .b = @intFromFloat(color.b), 23 | .a = @intFromFloat(color.a), 24 | }; 25 | } 26 | 27 | /// Callback for clay to get the dimensions of text data. 28 | /// * `text` - Input text to measure. 29 | /// * `config` - Configuration properties of the input text. 30 | pub fn measureText(text: []const u8, config: clay.TextElementConfig) clay.Dimensions { 31 | if (fonts == null) { 32 | std.debug.print("Global font list has not been initialized.\n", .{}); 33 | return .{}; 34 | } 35 | if (config.font_id >= fonts.?.items.len) { 36 | std.debug.print("Font ID {d} is invalid.\n", .{config.font_id}); 37 | return .{}; 38 | } 39 | 40 | var text_size = clay.Dimensions{ .width = 0, .height = 0 }; 41 | 42 | var max_text_width: f32 = 0; 43 | var line_text_width: f32 = 0; 44 | 45 | const text_height = config.font_size; 46 | const font = fonts.?.items[config.font_id].font; 47 | const scale_factor = @as(f32, @floatFromInt(config.font_size)) / @as(f32, @floatFromInt(font.baseSize)); 48 | 49 | for (0..text.len) |ind| { 50 | if (text[ind] == '\n') { 51 | max_text_width = @max(max_text_width, line_text_width); 52 | line_text_width = 0; 53 | continue; 54 | } 55 | const index = text[ind] - 32; 56 | if (font.glyphs[index].advanceX != 0) { 57 | line_text_width += @floatFromInt(font.glyphs[index].advanceX); 58 | } else { 59 | line_text_width += font.recs[index].width + @as(f32, @floatFromInt(font.glyphs[index].offsetX)); 60 | } 61 | } 62 | 63 | max_text_width = @max(max_text_width, line_text_width); 64 | 65 | text_size.width = max_text_width * scale_factor; 66 | text_size.height = @floatFromInt(text_height); 67 | 68 | return text_size; 69 | } 70 | 71 | /// Initialize the clay-raylib renderer with the given configuration flags and window info. 72 | /// * `width` - Initial window width. 73 | /// * `height` - Initial window height. 74 | /// * `title` - Initial window title. 75 | /// * `config_flags` - Configuration flags for the window. 76 | pub fn initialize(width: i32, height: i32, title: [*:0]const u8, config_flags: raylib.ConfigFlags) void { 77 | raylib.setConfigFlags(config_flags); 78 | raylib.initWindow(width, height, title); 79 | } 80 | 81 | /// Handle rendering a clay command list. 82 | /// * `commands` - Render commands provided from `.end()` for a `Layout`. 83 | /// * `text_arena` - Arena to allocate text from. 84 | pub fn render(commands: *clay.RenderCommandArray, text_arena: *std.heap.ArenaAllocator) void { 85 | var iter = commands.iter(); 86 | while (iter.next()) |command| { 87 | switch (command.config) { 88 | .none => {}, 89 | .text => |text| { 90 | const allocator = text_arena.allocator(); 91 | const cloned = allocator.allocSentinel(u8, command.text.?.len, 0) catch unreachable; 92 | defer _ = text_arena.reset(.retain_capacity); 93 | std.mem.copyForwards(u8, cloned, command.text.?); 94 | const font = fonts.?.items[text.font_id].font; 95 | raylib.drawTextEx( 96 | font, 97 | cloned, 98 | .{ .x = command.bounding_box.x, .y = command.bounding_box.y }, 99 | @floatFromInt(text.font_size), 100 | @floatFromInt(text.letter_spacing), 101 | clayColorToRaylib(text.text_color), 102 | ); 103 | }, 104 | .image => |image| { 105 | const image_texture = @as(*raylib.Texture2D, @ptrCast(@alignCast(image.image_data))).*; 106 | raylib.drawTextureEx( 107 | image_texture, 108 | .{ .x = command.bounding_box.x, .y = command.bounding_box.y }, 109 | 0, 110 | command.bounding_box.width / @as(f32, @floatFromInt(image_texture.width)), 111 | raylib.Color.white, 112 | ); 113 | }, 114 | .scissor_start => { 115 | raylib.beginScissorMode( 116 | @intFromFloat(@round(command.bounding_box.x)), 117 | @intFromFloat(@round(command.bounding_box.y)), 118 | @intFromFloat(@round(command.bounding_box.width)), 119 | @intFromFloat(@round(command.bounding_box.height)), 120 | ); 121 | }, 122 | .scissor_end => { 123 | raylib.endScissorMode(); 124 | }, 125 | .rectangle => |rect| { 126 | if (rect.corner_radius.top_left > 0) { 127 | const radius = rect.corner_radius.top_left * 2 / (if (command.bounding_box.width > command.bounding_box.height) command.bounding_box.height else command.bounding_box.width); 128 | raylib.drawRectangleRounded( 129 | .{ 130 | .x = command.bounding_box.x, 131 | .y = command.bounding_box.y, 132 | .width = command.bounding_box.width, 133 | .height = command.bounding_box.height, 134 | }, 135 | radius, 136 | 8, 137 | clayColorToRaylib(rect.color), 138 | ); 139 | } else { 140 | raylib.drawRectangle( 141 | @intFromFloat(command.bounding_box.x), 142 | @intFromFloat(command.bounding_box.y), 143 | @intFromFloat(command.bounding_box.width), 144 | @intFromFloat(command.bounding_box.height), 145 | clayColorToRaylib(rect.color), 146 | ); 147 | } 148 | }, 149 | .border => |border| { 150 | 151 | // Left border. 152 | if (border.left.width > 0) { 153 | raylib.drawRectangle( 154 | @intFromFloat(@round(command.bounding_box.x)), 155 | @intFromFloat(@round(command.bounding_box.y + border.corner_radius.top_left)), 156 | @intCast(border.left.width), 157 | @intFromFloat(@round(command.bounding_box.height - border.corner_radius.top_left - border.corner_radius.bottom_left)), 158 | clayColorToRaylib(border.left.color), 159 | ); 160 | } 161 | 162 | // Right border. 163 | if (border.right.width > 0) { 164 | raylib.drawRectangle( 165 | @as(i32, @intFromFloat(@round(command.bounding_box.x + command.bounding_box.width))) - @as(i32, @intCast(border.right.width)), 166 | @intFromFloat(@round(command.bounding_box.y + border.corner_radius.top_right)), 167 | @intCast(border.right.width), 168 | @intFromFloat(@round(command.bounding_box.height - border.corner_radius.top_right - border.corner_radius.bottom_right)), 169 | clayColorToRaylib(border.right.color), 170 | ); 171 | } 172 | 173 | // Top border. 174 | if (border.top.width > 0) { 175 | raylib.drawRectangle( 176 | @intFromFloat(@round(command.bounding_box.x + border.corner_radius.top_left)), 177 | @intFromFloat(@round(command.bounding_box.y)), 178 | @intFromFloat(@round(command.bounding_box.width - border.corner_radius.top_left - border.corner_radius.top_right)), 179 | @intCast(border.top.width), 180 | clayColorToRaylib(border.top.color), 181 | ); 182 | } 183 | 184 | // Bottom border. 185 | if (border.bottom.width > 0) { 186 | raylib.drawRectangle( 187 | @intFromFloat(@round(command.bounding_box.x + border.corner_radius.bottom_left)), 188 | @as(i32, @intFromFloat(@round(command.bounding_box.y + command.bounding_box.height))) - @as(i32, @intCast(border.bottom.width)), 189 | @intFromFloat(@round(command.bounding_box.width - border.corner_radius.bottom_left - border.corner_radius.bottom_right)), 190 | @intCast(border.bottom.width), 191 | clayColorToRaylib(border.bottom.color), 192 | ); 193 | } 194 | 195 | // Rings. 196 | if (border.corner_radius.top_left > 0) { 197 | raylib.drawRing( 198 | .{ 199 | .x = @round(command.bounding_box.x + border.corner_radius.top_left), 200 | .y = @round(command.bounding_box.y + border.corner_radius.top_left), 201 | }, 202 | @round(border.corner_radius.top_left) - @as(f32, @floatFromInt(border.top.width)), 203 | border.corner_radius.top_left, 204 | 180, 205 | 270, 206 | 10, 207 | clayColorToRaylib(border.top.color), 208 | ); 209 | } 210 | if (border.corner_radius.top_right > 0) { 211 | raylib.drawRing( 212 | .{ 213 | .x = @round(command.bounding_box.x + command.bounding_box.width - border.corner_radius.top_right), 214 | .y = @round(command.bounding_box.y + border.corner_radius.top_right), 215 | }, 216 | @round(border.corner_radius.top_right) - @as(f32, @floatFromInt(border.top.width)), 217 | border.corner_radius.top_right, 218 | 270, 219 | 360, 220 | 10, 221 | clayColorToRaylib(border.top.color), 222 | ); 223 | } 224 | if (border.corner_radius.bottom_left > 0) { 225 | raylib.drawRing( 226 | .{ 227 | .x = @round(command.bounding_box.x + border.corner_radius.bottom_left), 228 | .y = @round(command.bounding_box.y + command.bounding_box.height - border.corner_radius.bottom_left), 229 | }, 230 | @round(border.corner_radius.bottom_left) - @as(f32, @floatFromInt(border.top.width)), 231 | border.corner_radius.bottom_left, 232 | 90, 233 | 180, 234 | 10, 235 | clayColorToRaylib(border.bottom.color), 236 | ); 237 | } 238 | if (border.corner_radius.bottom_right > 0) { 239 | raylib.drawRing( 240 | .{ 241 | .x = @round(command.bounding_box.x + command.bounding_box.width - border.corner_radius.bottom_right), 242 | .y = @round(command.bounding_box.y + command.bounding_box.height - border.corner_radius.bottom_right), 243 | }, 244 | @round(border.corner_radius.bottom_right) - @as(f32, @floatFromInt(border.bottom.width)), 245 | border.corner_radius.bottom_right, 246 | 0.1, 247 | 90, 248 | 10, 249 | clayColorToRaylib(border.bottom.color), 250 | ); 251 | } 252 | }, 253 | .custom => { 254 | std.debug.print("Unsupported render command \"Custom\"\n", .{}); 255 | }, 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /examples/video-demo-raylib/src/main.zig: -------------------------------------------------------------------------------- 1 | const RendererRaylib = @import("backends/raylib.zig"); 2 | 3 | const clay = @import("clay"); 4 | const raylib = @import("raylib"); 5 | const std = @import("std"); 6 | 7 | const font_id_body_16: u16 = 0; 8 | const color_white = clay.Color{ .r = 255, .g = 255, .b = 255, .a = 255 }; 9 | 10 | fn render_header_button(text: []const u8) void { 11 | if (clay.child(&.{ 12 | clay.layout(.{ 13 | .padding = .{ .x = 16, .y = 8 }, 14 | }), 15 | clay.rectangle(.{ 16 | .color = .{ .r = 140, .g = 140, .b = 140, .a = 255 }, 17 | .corner_radius = clay.cornerRadius(5), 18 | }), 19 | })) |child| { 20 | defer child.end(); 21 | clay.text(text, .{ 22 | .font_id = font_id_body_16, 23 | .font_size = 16, 24 | .text_color = color_white, 25 | }); 26 | } 27 | } 28 | 29 | fn render_dropdown_menu_item(text: []const u8) void { 30 | if (clay.child(&.{ 31 | clay.layout(.{ 32 | .padding = .{ .x = 16, .y = 16 }, 33 | }), 34 | })) |child| { 35 | defer child.end(); 36 | clay.text(text, .{ 37 | .font_id = font_id_body_16, 38 | .font_size = 16, 39 | .text_color = color_white, 40 | }); 41 | } 42 | } 43 | 44 | const Document = struct { 45 | title: []const u8, 46 | contents: []const u8, 47 | }; 48 | 49 | const documents = [_]Document{ 50 | Document{ .title = "Squirrels", .contents = "The Secret Life of Squirrels: Nature's Clever Acrobats\nSquirrels are often overlooked creatures, dismissed as mere park inhabitants or backyard nuisances. Yet, beneath their fluffy tails and twitching noses lies an intricate world of cunning, agility, and survival tactics that are nothing short of fascinating. As one of the most common mammals in North America, squirrels have adapted to a wide range of environments from bustling urban centers to tranquil forests and have developed a variety of unique behaviors that continue to intrigue scientists and nature enthusiasts alike.\n\nMaster Tree Climbers\nAt the heart of a squirrel's skill set is its impressive ability to navigate trees with ease. Whether they're darting from branch to branch or leaping across wide gaps, squirrels possess an innate talent for acrobatics. Their powerful hind legs, which are longer than their front legs, give them remarkable jumping power. With a tail that acts as a counterbalance, squirrels can leap distances of up to ten times the length of their body, making them some of the best aerial acrobats in the animal kingdom.\nBut it's not just their agility that makes them exceptional climbers. Squirrels' sharp, curved claws allow them to grip tree bark with precision, while the soft pads on their feet provide traction on slippery surfaces. Their ability to run at high speeds and scale vertical trunks with ease is a testament to the evolutionary adaptations that have made them so successful in their arboreal habitats.\n\nFood Hoarders Extraordinaire\nSquirrels are often seen frantically gathering nuts, seeds, and even fungi in preparation for winter. While this behavior may seem like instinctual hoarding, it is actually a survival strategy that has been honed over millions of years. Known as \"scatter hoarding,\" squirrels store their food in a variety of hidden locations, often burying it deep in the soil or stashing it in hollowed-out tree trunks.\nInterestingly, squirrels have an incredible memory for the locations of their caches. Research has shown that they can remember thousands of hiding spots, often returning to them months later when food is scarce. However, they don't always recover every stash some forgotten caches eventually sprout into new trees, contributing to forest regeneration. This unintentional role as forest gardeners highlights the ecological importance of squirrels in their ecosystems.\n\nThe Great Squirrel Debate: Urban vs. Wild\nWhile squirrels are most commonly associated with rural or wooded areas, their adaptability has allowed them to thrive in urban environments as well. In cities, squirrels have become adept at finding food sources in places like parks, streets, and even garbage cans. However, their urban counterparts face unique challenges, including traffic, predators, and the lack of natural shelters. Despite these obstacles, squirrels in urban areas are often observed using human infrastructure such as buildings, bridges, and power lines as highways for their acrobatic escapades.\nThere is, however, a growing concern regarding the impact of urban life on squirrel populations. Pollution, deforestation, and the loss of natural habitats are making it more difficult for squirrels to find adequate food and shelter. As a result, conservationists are focusing on creating squirrel-friendly spaces within cities, with the goal of ensuring these resourceful creatures continue to thrive in both rural and urban landscapes.\n\nA Symbol of Resilience\nIn many cultures, squirrels are symbols of resourcefulness, adaptability, and preparation. Their ability to thrive in a variety of environments while navigating challenges with agility and grace serves as a reminder of the resilience inherent in nature. Whether you encounter them in a quiet forest, a city park, or your own backyard, squirrels are creatures that never fail to amaze with their endless energy and ingenuity.\nIn the end, squirrels may be small, but they are mighty in their ability to survive and thrive in a world that is constantly changing. So next time you spot one hopping across a branch or darting across your lawn, take a moment to appreciate the remarkable acrobat at work a true marvel of the natural world.\n" }, 51 | Document{ .title = "Lorem Ipsum", .contents = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }, 52 | Document{ .title = "Vacuum Instructions", .contents = "Chapter 3: Getting Started - Unpacking and Setup\n\nCongratulations on your new SuperClean Pro 5000 vacuum cleaner! In this section, we will guide you through the simple steps to get your vacuum up and running. Before you begin, please ensure that you have all the components listed in the \"Package Contents\" section on page 2.\n\n1. Unboxing Your Vacuum\nCarefully remove the vacuum cleaner from the box. Avoid using sharp objects that could damage the product. Once removed, place the unit on a flat, stable surface to proceed with the setup. Inside the box, you should find:\n\n The main vacuum unit\n A telescoping extension wand\n A set of specialized cleaning tools (crevice tool, upholstery brush, etc.)\n A reusable dust bag (if applicable)\n A power cord with a 3-prong plug\n A set of quick-start instructions\n\n2. Assembling Your Vacuum\nBegin by attaching the extension wand to the main body of the vacuum cleaner. Line up the connectors and twist the wand into place until you hear a click. Next, select the desired cleaning tool and firmly attach it to the wand's end, ensuring it is securely locked in.\n\nFor models that require a dust bag, slide the bag into the compartment at the back of the vacuum, making sure it is properly aligned with the internal mechanism. If your vacuum uses a bagless system, ensure the dust container is correctly seated and locked in place before use.\n\n3. Powering On\nTo start the vacuum, plug the power cord into a grounded electrical outlet. Once plugged in, locate the power switch, usually positioned on the side of the handle or body of the unit, depending on your model. Press the switch to the \"On\" position, and you should hear the motor begin to hum. If the vacuum does not power on, check that the power cord is securely plugged in, and ensure there are no blockages in the power switch.\n\nNote: Before first use, ensure that the vacuum filter (if your model has one) is properly installed. If unsure, refer to \"Section 5: Maintenance\" for filter installation instructions." }, 53 | Document{ .title = "Article 4", .contents = "Article 4" }, 54 | Document{ .title = "Article 5", .contents = "Article 5" }, 55 | }; 56 | 57 | var selected_document_index: usize = 0; 58 | 59 | fn handle_sidebar_interaction(element_id: clay.ElementId, pointer_data: clay.PointerData, user_data: ?*anyopaque) void { 60 | _ = element_id; 61 | const doc_ind: usize = @intFromPtr(user_data); 62 | if (pointer_data.state == .pressed_this_frame and doc_ind < documents.len) { 63 | selected_document_index = doc_ind; 64 | } 65 | } 66 | 67 | fn err(error_text: clay.ErrorData(void)) void { 68 | std.debug.print("Clay: \"{s}\", {s}\n", .{ error_text.error_text, @tagName(error_text.error_type) }); 69 | } 70 | 71 | pub fn main() !void { 72 | RendererRaylib.initialize( 73 | 1024, 74 | 768, 75 | "Introducing Clay Demo", 76 | .{ .window_resizable = true, .window_highdpi = true, .msaa_4x_hint = true, .vsync_hint = true }, 77 | ); 78 | defer raylib.closeWindow(); 79 | 80 | // Setup arena. 81 | const required_mem = clay.minMemorySize(); 82 | const arena_data = try std.heap.c_allocator.alloc(u8, @intCast(required_mem)); 83 | defer std.heap.c_allocator.free(arena_data); 84 | const arena = clay.createArenaWithCapacityAndMemory(arena_data); 85 | 86 | // Initialize clay. 87 | var empty = {}; 88 | clay.initialize(arena, .{ 89 | .width = @floatFromInt(raylib.getRenderWidth()), 90 | .height = @floatFromInt(raylib.getRenderHeight()), 91 | }, void, .{ 92 | .handler_function = err, 93 | .user_data = &empty, 94 | }); 95 | clay.setMeasureTextFunction(RendererRaylib.measureText); 96 | RendererRaylib.fonts = std.ArrayList(RendererRaylib.Font).init(std.heap.c_allocator); 97 | defer RendererRaylib.fonts.?.deinit(); 98 | try RendererRaylib.fonts.?.append(.{ 99 | .font = raylib.loadFontEx("resources/Roboto-Regular.ttf", 48, null), 100 | .id = font_id_body_16, 101 | }); 102 | raylib.setTextureFilter(RendererRaylib.fonts.?.items[font_id_body_16].font.texture, .bilinear); 103 | defer raylib.unloadFont(RendererRaylib.fonts.?.items[font_id_body_16].font); 104 | clay.C.Clay_SetDebugModeEnabled(true); 105 | 106 | // Make text arena. 107 | var text_arena = std.heap.ArenaAllocator.init(std.heap.c_allocator); 108 | defer text_arena.deinit(); 109 | 110 | // Main loop. 111 | while (!raylib.windowShouldClose()) { 112 | 113 | // External events. 114 | const width: f32 = @as(f32, @floatFromInt(raylib.getRenderWidth())) / raylib.getWindowScaleDPI().x; 115 | const height: f32 = @as(f32, @floatFromInt(raylib.getRenderHeight())) / raylib.getWindowScaleDPI().y; 116 | clay.setLayoutDimensions(.{ .width = width, .height = height,}); 117 | 118 | const mouse_pos = raylib.getMousePosition(); 119 | const scroll_delta = raylib.getMouseWheelMoveV(); 120 | clay.setPointerState(.{ .x = mouse_pos.x, .y = mouse_pos.y }, raylib.isMouseButtonDown(.left)); 121 | clay.updateScrollContainers(true, .{ .x = scroll_delta.x, .y = scroll_delta.y }, raylib.getFrameTime()); 122 | 123 | const layout_expand = clay.Sizing{ 124 | .width = clay.sizingGrow(.{}), 125 | .height = clay.sizingGrow(.{}), 126 | }; 127 | 128 | const content_background_config = clay.RectangleElementConfig{ 129 | .color = .{ .r = 90, .g = 90, .b = 90, .a = 255 }, 130 | .corner_radius = clay.cornerRadius(8), 131 | }; 132 | 133 | // Build UI here. 134 | const layout = clay.beginLayout(); 135 | if (clay.child(&.{ 136 | clay.id("OuterContainer"), 137 | clay.rectangle(.{ .color = .{ .r = 43, .g = 41, .b = 51, .a = 255 } }), 138 | clay.layout(.{ 139 | .layout_direction = .top_to_bottom, 140 | .sizing = layout_expand, 141 | .padding = .{ .x = 16, .y = 16 }, 142 | .child_gap = 16, 143 | }), 144 | })) |outer_container| { 145 | defer outer_container.end(); 146 | 147 | // Child elements go inside braces. 148 | if (clay.child(&.{ 149 | clay.id("HeaderBar"), 150 | clay.rectangle(content_background_config), 151 | clay.layout(.{ 152 | .sizing = .{ 153 | .width = clay.sizingGrow(.{}), 154 | .height = clay.sizingFixed(60), 155 | }, 156 | .padding = .{ .x = 16 }, 157 | .child_gap = 16, 158 | .child_alignment = .{ .y = .center }, 159 | }), 160 | })) |header_bar| { 161 | defer header_bar.end(); 162 | 163 | // Header buttons go here. 164 | if (clay.child(&.{ 165 | clay.id("FileButton"), 166 | clay.layout(.{ 167 | .padding = .{ .x = 16, .y = 8 }, 168 | }), 169 | clay.rectangle(.{ 170 | .color = .{ .r = 140, .g = 140, .b = 140, .a = 255 }, 171 | .corner_radius = clay.cornerRadius(5), 172 | }), 173 | })) |file_button| { 174 | defer file_button.end(); 175 | clay.text("File", .{ 176 | .font_id = font_id_body_16, 177 | .font_size = 16, 178 | .text_color = color_white, 179 | }); 180 | 181 | const file_menu_visible = clay.pointerOver(clay.getElementId("FileButton")) or clay.pointerOver(clay.getElementId("FileMenu")); 182 | 183 | if (file_menu_visible) { 184 | if (clay.child(&.{ 185 | clay.id("FileMenu"), 186 | clay.floating(.{ 187 | .attachment = .{ .parent = .left_bottom }, 188 | }), 189 | clay.layout(.{ 190 | .padding = .{ .x = 0, .y = 8 }, 191 | }), 192 | })) |file_menu| { 193 | defer file_menu.end(); 194 | if (clay.child(&.{ clay.layout(.{ 195 | .layout_direction = .top_to_bottom, 196 | .sizing = .{ .width = clay.sizingFixed(200) }, 197 | }), clay.rectangle(.{ 198 | .color = .{ .r = 40, .g = 40, .b = 40, .a = 255 }, 199 | .corner_radius = clay.cornerRadius(8), 200 | }) })) |dropdown_area| { 201 | defer dropdown_area.end(); 202 | render_dropdown_menu_item("New"); 203 | render_dropdown_menu_item("Open"); 204 | render_dropdown_menu_item("Close"); 205 | } 206 | } 207 | } 208 | } 209 | render_header_button("Edit"); 210 | if (clay.child(&.{clay.layout(.{ 211 | .sizing = .{ .width = clay.sizingGrow(.{}) }, 212 | })})) |spacer| { 213 | defer spacer.end(); 214 | } 215 | render_header_button("Upload"); 216 | render_header_button("Media"); 217 | render_header_button("Support"); 218 | } 219 | 220 | if (clay.child(&.{ 221 | clay.id("LowerContent"), 222 | clay.layout(.{ 223 | .sizing = layout_expand, 224 | .child_gap = 16, 225 | }), 226 | })) |lower_content| { 227 | defer lower_content.end(); 228 | if (clay.child(&.{ 229 | clay.id("Sidebar"), 230 | clay.rectangle(content_background_config), 231 | clay.layout(.{ 232 | .layout_direction = .top_to_bottom, 233 | .padding = .{ .x = 16, .y = 16 }, 234 | .child_gap = 8, 235 | .sizing = .{ 236 | .width = clay.sizingFixed(250), 237 | .height = clay.sizingGrow(.{}), 238 | }, 239 | }), 240 | })) |sidebar| { 241 | defer sidebar.end(); 242 | for (documents, 0..) |document, i| { 243 | const sidebar_button_layout = clay.LayoutConfig{ 244 | .sizing = .{ .width = clay.sizingGrow(.{}) }, 245 | .padding = .{ .x = 16, .y = 16 }, 246 | }; 247 | 248 | if (i == selected_document_index) { 249 | if (clay.child(&.{ 250 | clay.layout(sidebar_button_layout), 251 | clay.rectangle(.{ 252 | .color = .{ .r = 120, .g = 120, .b = 120, .a = 255 }, 253 | .corner_radius = clay.cornerRadius(8), 254 | }), 255 | })) |sidebar_button| { 256 | defer sidebar_button.end(); 257 | clay.text(document.title, .{ 258 | .font_id = font_id_body_16, 259 | .font_size = 20, 260 | .text_color = color_white, 261 | }); 262 | } 263 | } else { 264 | if (clay.childManualControlFlow()) |sidebar_button| { 265 | defer sidebar_button.end(); 266 | 267 | // Do config here. 268 | sidebar_button.config(clay.layout(sidebar_button_layout)); 269 | clay.onHover(handle_sidebar_interaction, @ptrFromInt(i)); 270 | if (clay.hovered()) 271 | sidebar_button.config(clay.rectangle(.{ 272 | .color = .{ .r = 120, .g = 120, .b = 120, .a = 255 }, 273 | .corner_radius = clay.cornerRadius(8), 274 | })); 275 | sidebar_button.endConfig(); 276 | 277 | // Children. 278 | clay.text(document.title, .{ 279 | .font_id = font_id_body_16, 280 | .font_size = 20, 281 | .text_color = color_white, 282 | }); 283 | } 284 | } 285 | } 286 | } 287 | if (clay.child(&.{ 288 | clay.id("MainContent"), 289 | clay.rectangle(content_background_config), 290 | clay.scroll(.{ .vertical = true }), 291 | clay.layout(.{ 292 | .layout_direction = .top_to_bottom, 293 | .child_gap = 16, 294 | .padding = .{ .x = 16, .y = 16 }, 295 | .sizing = layout_expand, 296 | }), 297 | })) |main_content| { 298 | defer main_content.end(); 299 | const selected_document = documents[selected_document_index]; 300 | clay.text(selected_document.title, .{ 301 | .font_id = font_id_body_16, 302 | .font_size = 24, 303 | .text_color = color_white, 304 | }); 305 | clay.text(selected_document.contents, .{ 306 | .font_id = font_id_body_16, 307 | .font_size = 23, 308 | .text_color = color_white, 309 | }); 310 | } 311 | } 312 | } 313 | 314 | // Finish drawing. 315 | raylib.beginDrawing(); 316 | raylib.clearBackground(raylib.Color.black); 317 | var commands = layout.end(); 318 | RendererRaylib.render(&commands, &text_arena); 319 | raylib.endDrawing(); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/root.zig: -------------------------------------------------------------------------------- 1 | pub const C = @cImport(@cInclude("clay.h")); 2 | const std = @import("std"); 3 | 4 | /// Used for configuring layout options (affect the final position and size of an element, parent, siblings, and children). 5 | /// * `config` - Layout configuration. 6 | pub fn layout(config: LayoutConfig) ChildConfigOption { 7 | return .{ .layout = config }; 8 | } 9 | 10 | /// Configures a clay element to background-fill its bounding box with a color. 11 | /// * `config` - Rectangle specific options. 12 | pub fn rectangle(config: RectangleElementConfig) ChildConfigOption { 13 | return .{ .rectangle = config }; 14 | } 15 | 16 | /// Configures a clay element to render an image as its background. 17 | /// * `config` - Image specific options. 18 | pub fn image(config: ImageElementConfig) ChildConfigOption { 19 | return .{ .image = config }; 20 | } 21 | 22 | /// Define an element that floats above other content. Useful for tooltips and modals. 23 | /// Don't affect the width and height of their parent. 24 | /// Don't affect the positioning of sibling elements. 25 | /// Can appear above or below other elements depending on z-index. 26 | /// Work like other standard child elements. 27 | /// These are easiest to think of as separate UI hierarchies anchored to a point on their parent. 28 | /// * `config` - Floating element specific configuration. 29 | pub fn floating(config: FloatingElementConfig) ChildConfigOption { 30 | return .{ .floating = config }; 31 | } 32 | 33 | /// Allows the user to pass custom data to the renderer. 34 | /// * `config` - Custom data configuration. 35 | pub fn customElement(config: CustomElementConfig) ChildConfigOption { 36 | return .{ .custom_element = config }; 37 | } 38 | 39 | /// Configures the element as a scrolling container, enabling masking children that extend beyond its boundaries. 40 | /// In order to process scrolling based on pointer position and mouse wheel or touch interactions, you must call `setPointerState()` and `updateScrollContainer()` before calling `beginLayout()`. 41 | /// * `config` - Scroll specific config. 42 | pub fn scroll(config: ScrollElementConfig) ChildConfigOption { 43 | return .{ .scroll = config }; 44 | } 45 | 46 | /// Adds borders to the edges or between the children of elements. 47 | /// * `config` - Border specific options. 48 | pub fn border(config: BorderElementConfig) ChildConfigOption { 49 | return .{ .border = config }; 50 | } 51 | 52 | /// Shorthand for configuring all 4 outside borders at once. 53 | /// * `config` - Border config. 54 | pub fn borderOutside(config: Border) ChildConfigOption { 55 | return .{ 56 | .border = .{ 57 | .left = config, 58 | .right = config, 59 | .top = config, 60 | .bottom = config, 61 | }, 62 | }; 63 | } 64 | 65 | /// Shorthand for configuring all 4 outside borders at once with the provided corner radius. 66 | /// * `width` - Border width for outside borders. 67 | /// * `color` - Color of outside borders. 68 | /// * `radius` - Corner radius of each border. 69 | pub fn borderOutsideRadius(width: u32, color: Color, radius: f32) ChildConfigOption { 70 | return .{ 71 | .border = .{ 72 | .left = .{ .width = width, .color = color }, 73 | .right = .{ .width = width, .color = color }, 74 | .top = .{ .width = width, .color = color }, 75 | .bottom = .{ .width = width, .color = color }, 76 | .corder_radius = cornerRadius(radius), 77 | }, 78 | }; 79 | } 80 | 81 | /// Shorthand for configuring all borders at once. 82 | /// * `config` - Border config. 83 | pub fn borderAll(config: Border) ChildConfigOption { 84 | return .{ 85 | .border = .{ 86 | .left = config, 87 | .right = config, 88 | .top = config, 89 | .bottom = config, 90 | .between_children = config, 91 | }, 92 | }; 93 | } 94 | 95 | /// Shorthand for configuring all borders at once with the provided corner radius. 96 | /// * `width` - Border width for all borders. 97 | /// * `color` - Color of all borders. 98 | /// * `radius` - Corner radius of each border. 99 | pub fn borderAllBetweenChildren(width: u32, color: Color, radius: f32) ChildConfigOption { 100 | return .{ 101 | .border = .{ 102 | .left = .{ .width = width, .color = color }, 103 | .right = .{ .width = width, .color = color }, 104 | .top = .{ .width = width, .color = color }, 105 | .bottom = .{ .width = width, .color = color }, 106 | .between_children = .{ .width = width, .color = color }, 107 | .corder_radius = cornerRadius(radius), 108 | }, 109 | }; 110 | } 111 | 112 | /// Shorthand to set a corner radius for all corners. 113 | /// * `radius` - Radius to create a corner radius from. 114 | pub fn cornerRadius(radius: f32) CornerRadius { 115 | return .{ 116 | .top_left = radius, 117 | .top_right = radius, 118 | .bottom_left = radius, 119 | .bottom_right = radius, 120 | }; 121 | } 122 | 123 | /// The element will be sized to fit its children (plus padding and gaps), up to `max`. 124 | /// When elements are compressed to fit into a smaller parent, this element will not shrink below `min`. 125 | /// * `fit` - How to fit the element. 126 | pub fn sizingFit(fit: SizingMinMax) SizingAxis { 127 | return .{ .fit = fit }; 128 | } 129 | 130 | /// The element will grow to fill available space in its parent, up to `max`. 131 | /// When elements are compressed to fit into a smaller parent, this element will not shrink below `min`. 132 | /// * `grow` - Grow constraints. 133 | pub fn sizingGrow(grow: SizingMinMax) SizingAxis { 134 | return .{ .grow = grow }; 135 | } 136 | 137 | /// The final size will always be exactly the provided fixed value. 138 | /// * `fixed` - Shorthand for `sizingFit(fixed, fixed). 139 | pub fn sizingFixed(fixed: f32) SizingAxis { 140 | return .{ .fixed = .{ .min = fixed, .max = fixed } }; 141 | } 142 | 143 | /// Final size will be a percentage of the parent size, minus padding and child gaps. 144 | /// * `percent` - Percentage of the parent to fill between `0` and `1`. 145 | pub fn sizingPercent(percent: f32) SizingAxis { 146 | return .{ .percent = percent }; 147 | } 148 | 149 | /// Used to generate and attach an `ElementId` to a layout element during declaration. 150 | /// To regenerate the same ID outside of layout declaration when using utility functions such as `pointerOver()`, use the `getElementId()` function. 151 | /// * `label` - ID label to attach to the child. 152 | pub fn id(label: []const u8) ChildConfigOption { 153 | return .{ .id = label }; 154 | } 155 | 156 | /// And offset version of `id()`. 157 | /// Generates an `ElementId` that is combined with an index. 158 | /// Great for generating IDs for elements in loops. 159 | /// * `label` - ID label to attach to the child. 160 | /// * `index` - Index of the element to offset the ID with. 161 | pub fn idi(label: []const u8, index: u32) ChildConfigOption { 162 | return .{ .idi = .{ label, index } }; 163 | } 164 | 165 | // TODO!!! 166 | 167 | // pub fn idLocal(label: []const u8) ChildConfigOption { 168 | // return .{ .id_local = label }; 169 | // } 170 | 171 | // pub fn idiLocal(label: []const u8, index: usize) ChildConfigOption { 172 | // return .{ .idi_local = .{ label, index } }; 173 | // } 174 | 175 | /// Text is a measured, auto-wrapped text element. 176 | /// Note that fonts are passed around by an ID through clay to be agnostic to the backend. 177 | /// * `text_data` - Text to render. 178 | /// * `config` - Text-specific options. 179 | pub fn text(text_data: []const u8, config: TextElementConfig) void { 180 | C.Clay__OpenTextElement( 181 | .{ .chars = text_data.ptr, .length = @intCast(text_data.len) }, 182 | C.Clay__StoreTextElementConfig(.{ 183 | .fontId = config.font_id, 184 | .fontSize = config.font_size, 185 | .letterSpacing = config.letter_spacing, 186 | .lineHeight = config.line_height, 187 | .textColor = config.text_color, 188 | .wrapMode = @intCast(@intFromEnum(config.wrap_mode)), 189 | }), 190 | ); 191 | } 192 | 193 | /// A child element context created by `child()`. 194 | pub const Child = struct { 195 | /// Finish the current child element. 196 | pub fn end(self: Child) void { 197 | _ = self; 198 | C.Clay__CloseElement(); 199 | } 200 | }; 201 | 202 | /// Represent a child element configuration option. 203 | /// Item is set by using the helper function above with the same name as the item (call `floating` to set this as a floating container). 204 | pub const ChildConfigOption = union(enum) { 205 | layout: LayoutConfig, 206 | rectangle: RectangleElementConfig, 207 | image: ImageElementConfig, 208 | floating: FloatingElementConfig, 209 | custom_element: CustomElementConfig, 210 | scroll: ScrollElementConfig, 211 | border: BorderElementConfig, 212 | id: []const u8, 213 | idi: struct { []const u8, u32 }, 214 | }; 215 | 216 | fn handleChildElement(opt: ChildConfigOption) void { 217 | switch (opt) { 218 | .layout => |config| { 219 | C.Clay__AttachLayoutConfig( 220 | C.Clay__StoreLayoutConfig(.{ 221 | .childAlignment = .{ .x = @intFromEnum(config.child_alignment.x), .y = @intFromEnum(config.child_alignment.y) }, 222 | .childGap = config.child_gap, 223 | .layoutDirection = @intFromEnum(config.layout_direction), 224 | .padding = .{ .x = config.padding.x, .y = config.padding.y }, 225 | .sizing = .{ 226 | .width = config.sizing.width.toC(), 227 | .height = config.sizing.height.toC(), 228 | }, 229 | }), 230 | ); 231 | }, 232 | .rectangle => |config| { 233 | C.Clay__AttachElementConfig( 234 | .{ .rectangleElementConfig = C.Clay__StoreRectangleElementConfig( 235 | .{ 236 | .color = config.color, 237 | .cornerRadius = config.corner_radius.toC(), 238 | }, 239 | ) }, 240 | C.CLAY__ELEMENT_CONFIG_TYPE_RECTANGLE, 241 | ); 242 | }, 243 | .image => |config| { 244 | C.Clay__AttachElementConfig( 245 | .{ .imageElementConfig = C.Clay__StoreImageElementConfig( 246 | .{ 247 | .imageData = config.image_data, 248 | .sourceDimensions = config.source_dimensions, 249 | }, 250 | ) }, 251 | C.CLAY__ELEMENT_CONFIG_TYPE_IMAGE, 252 | ); 253 | }, 254 | .floating => |config| { 255 | C.Clay__AttachElementConfig( 256 | .{ .floatingElementConfig = C.Clay__StoreFloatingElementConfig( 257 | config.toC(), 258 | ) }, 259 | C.CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER, 260 | ); 261 | }, 262 | .custom_element => |config| { 263 | C.Clay__AttachElementConfig( 264 | .{ .customElementConfig = C.Clay__StoreCustomElementConfig( 265 | .{ 266 | .customData = config.custom_data, 267 | }, 268 | ) }, 269 | C.CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER, 270 | ); 271 | }, 272 | .scroll => |config| { 273 | C.Clay__AttachElementConfig( 274 | .{ 275 | .scrollElementConfig = C.Clay__StoreScrollElementConfig(.{ 276 | .horizontal = config.horizontal, 277 | .vertical = config.vertical, 278 | }), 279 | }, 280 | C.CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER, 281 | ); 282 | }, 283 | .border => |config| { 284 | C.Clay__AttachElementConfig( 285 | .{ 286 | .borderElementConfig = C.Clay__StoreBorderElementConfig(.{ 287 | .betweenChildren = config.between_children.toC(), 288 | .bottom = config.bottom.toC(), 289 | .cornerRadius = config.corner_radius.toC(), 290 | .left = config.left.toC(), 291 | .right = config.right.toC(), 292 | .top = config.top.toC(), 293 | }), 294 | }, 295 | C.CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER, 296 | ); 297 | }, 298 | .id => |label| { 299 | C.Clay__AttachId( 300 | C.Clay__HashString( 301 | .{ .chars = label.ptr, .length = @intCast(label.len) }, 302 | 0, 303 | 0, 304 | ), 305 | ); 306 | }, 307 | .idi => |label| { 308 | C.Clay__AttachId( 309 | C.Clay__HashString( 310 | .{ .chars = label[0].ptr, .length = @intCast(label[0].len) }, 311 | label[1], 312 | 0, 313 | ), 314 | ); 315 | }, 316 | } 317 | } 318 | 319 | /// Opens a generic empty container that is configurable and supports nested children. 320 | /// Remember to call `.end()` on the resulting child. 321 | /// * `options` - Configuration options. If more control flow is needed, use `childManualControlFlow()`. 322 | pub fn child(options: []const ChildConfigOption) ?Child { 323 | C.Clay__OpenElement(); 324 | for (options) |opt| { 325 | handleChildElement(opt); 326 | } 327 | C.Clay__ElementPostConfiguration(); 328 | return .{}; 329 | } 330 | 331 | /// A child element context created with `childManualControlFlow()`. 332 | pub const ChildManualControlFlow = struct { 333 | /// Add a configuration option to the child. 334 | /// Must be called before `.endConfig()`. 335 | /// * `opt` - Configuration option to add to the child element. 336 | pub fn config(self: ChildManualControlFlow, opt: ChildConfigOption) void { 337 | _ = self; 338 | handleChildElement(opt); 339 | } 340 | 341 | /// End the child configuration. 342 | /// No more calls to `.config()` are allowed after this. 343 | pub fn endConfig(self: ChildManualControlFlow) void { 344 | _ = self; 345 | C.Clay__ElementPostConfiguration(); 346 | } 347 | 348 | /// End the child element. 349 | pub fn end(self: ChildManualControlFlow) void { 350 | _ = self; 351 | C.Clay__CloseElement(); 352 | } 353 | }; 354 | 355 | /// Opens a generic empty container that is configurable via control flow and supports nested children. 356 | /// Remember to call `.endConfig()` and then `.end()` on the resulting child. 357 | /// The `.config()` function may be invoked before `.endConfig()` any number of times to add configuration options. 358 | pub fn childManualControlFlow() ?ChildManualControlFlow { 359 | C.Clay__OpenElement(); 360 | return .{}; 361 | } 362 | 363 | /// Clay memory arena to use. 364 | pub const Arena = C.Clay_Arena; 365 | 366 | /// Width and height dimensions. 367 | pub const Dimensions = C.Clay_Dimensions; 368 | 369 | /// X and Y positioning. 370 | pub const Vector2 = C.Clay_Vector2; 371 | 372 | /// RGBA color with values typically from 0 to 255. 373 | pub const Color = C.Clay_Color; 374 | 375 | /// A bounding box for an element. 376 | pub const BoundingBox = C.Clay_BoundingBox; 377 | 378 | /// Contains a hash ID to the element it refers to as well as a source string used to generate it. 379 | pub const ElementId = struct { 380 | /// Unique identifier create from `id()` or `idi()`. 381 | id: u32, 382 | /// If generated with `idi()`, this is the value passed as `index`. 383 | offset: u32, 384 | /// If generated with `idi()`, this is the hash of the base string passed before it is additionally hashed with `offset`. 385 | base_id: u32, 386 | /// The string that was passed when `id()` or `idi()` was called. 387 | string_id: ?[]const u8, 388 | 389 | fn fromC(element_id: C.Clay_ElementId) ElementId { 390 | return .{ 391 | .id = element_id.id, 392 | .offset = element_id.offset, 393 | .base_id = element_id.baseId, 394 | .string_id = if (element_id.stringId.chars != null) element_id.stringId.chars[0..@intCast(element_id.stringId.length)] else null, 395 | }; 396 | } 397 | 398 | fn toC(self: ElementId) C.Clay_ElementId { 399 | return .{ 400 | .id = self.id, 401 | .offset = self.offset, 402 | .baseId = self.base_id, 403 | .stringId = if (self.string_id) |str| .{ .chars = str.ptr, .length = @intCast(str.len) } else .{ .chars = null, .length = 0 }, 404 | }; 405 | } 406 | }; 407 | 408 | /// Radius for corners of a box. 409 | pub const CornerRadius = struct { 410 | /// Radius of the top left. 411 | top_left: f32, 412 | /// Radius of the top right. 413 | top_right: f32, 414 | /// Radius of the bottom left. 415 | bottom_left: f32, 416 | /// Radius of the bottom right. 417 | bottom_right: f32, 418 | 419 | fn fromC(rad: C.Clay_CornerRadius) CornerRadius { 420 | return .{ 421 | .top_left = rad.topLeft, 422 | .top_right = rad.topRight, 423 | .bottom_left = rad.bottomLeft, 424 | .bottom_right = rad.bottomRight, 425 | }; 426 | } 427 | 428 | fn toC(self: CornerRadius) C.Clay_CornerRadius { 429 | return .{ 430 | .topLeft = self.top_left, 431 | .topRight = self.top_right, 432 | .bottomLeft = self.bottom_left, 433 | .bottomRight = self.bottom_right, 434 | }; 435 | } 436 | }; 437 | 438 | /// What an element may contain. 439 | pub const ElementConfigType = packed struct(u8) { 440 | rectangle: bool, 441 | border_container: bool, 442 | floating_container: bool, 443 | scroll_container: bool, 444 | image: bool, 445 | text: bool, 446 | custom: bool, 447 | }; 448 | 449 | /// Direction of a layout. 450 | pub const LayoutDirection = enum(u8) { 451 | /// Items go from left to right. 452 | left_to_right = C.CLAY_LEFT_TO_RIGHT, 453 | /// Items go from top to bottom. 454 | top_to_bottom = C.CLAY_TOP_TO_BOTTOM, 455 | }; 456 | 457 | /// How child elements are aligned in the X direction. 458 | pub const LayoutAlignmentX = enum(u8) { 459 | /// Left of the parent. 460 | left = C.CLAY_ALIGN_X_LEFT, 461 | /// Right of the parent. 462 | right = C.CLAY_ALIGN_X_RIGHT, 463 | /// Center of the parent. 464 | center = C.CLAY_ALIGN_X_CENTER, 465 | }; 466 | 467 | /// How child elements are aligned in the Y direction. 468 | pub const LayoutAlignmentY = enum(u8) { 469 | /// Top of the parent. 470 | top = C.CLAY_ALIGN_Y_TOP, 471 | /// Bottom of the parent. 472 | bottom = C.CLAY_ALIGN_Y_BOTTOM, 473 | /// Center of the parent. 474 | center = C.CLAY_ALIGN_Y_CENTER, 475 | }; 476 | 477 | /// How a layout will be sized along an axis. 478 | pub const SizingType = enum(u8) { 479 | /// Size to fit children with padding and gaps. 480 | /// Use `sizingFit()`. 481 | fit = C.CLAY__SIZING_TYPE_FIT, 482 | /// Size to grow to the size of the parent. 483 | /// Use `sizingGrow()`. 484 | grow = C.CLAY__SIZING_TYPE_GROW, 485 | /// Size to fill a percentage of the parent. 486 | /// Use `sizingPercent()`. 487 | percent = C.CLAY__SIZING_TYPE_PERCENT, 488 | /// Set to a fixed size. 489 | /// Use `sizingFixed()`. 490 | fixed = C.CLAY__SIZING_TYPE_FIXED, 491 | }; 492 | 493 | /// Alignment of a child element. 494 | pub const ChildAlignment = struct { 495 | /// Alignment in the x direction. 496 | x: LayoutAlignmentX = .left, 497 | /// Alignment in the y direction. 498 | y: LayoutAlignmentY = .top, 499 | }; 500 | 501 | /// Minimum and maximum bounds of a size. 502 | pub const SizingMinMax = struct { 503 | /// Minimum size (will not collapse more than this). 504 | min: f32 = 0, 505 | /// Maximum size (will not expand more than this). 506 | max: f32 = std.math.floatMax(f32), 507 | 508 | fn toC(self: SizingMinMax) C.Clay_SizingMinMax { 509 | return .{ 510 | .min = self.min, 511 | .max = self.max, 512 | }; 513 | } 514 | }; 515 | 516 | /// Sizing along an axis. 517 | pub const SizingAxis = union(SizingType) { 518 | /// Size to fit children with padding and gaps. 519 | /// Use `sizingFit()`. 520 | fit: SizingMinMax, 521 | /// Size to grow to the size of the parent. 522 | /// Use `sizingGrow()`. 523 | grow: SizingMinMax, 524 | /// Size to fill a percentage of the parent. 525 | /// Use `sizingPercent()`. 526 | percent: f32, 527 | /// Set to a fixed size. 528 | /// Use `sizingFixed()`. 529 | fixed: SizingMinMax, 530 | 531 | fn toC(self: SizingAxis) C.Clay_SizingAxis { 532 | return switch (self) { 533 | .fit => |fit| .{ .type = C.CLAY__SIZING_TYPE_FIT, .size = .{ .minMax = fit.toC() } }, 534 | .grow => |grow| .{ .type = C.CLAY__SIZING_TYPE_GROW, .size = .{ .minMax = grow.toC() } }, 535 | .percent => |percent| .{ .type = C.CLAY__SIZING_TYPE_PERCENT, .size = .{ .percent = percent } }, 536 | .fixed => |fixed| .{ .type = C.CLAY__SIZING_TYPE_FIXED, .size = .{ .minMax = fixed.toC() } }, 537 | }; 538 | } 539 | }; 540 | 541 | /// Sizing of a layout. 542 | pub const Sizing = struct { 543 | /// Horizontal sizing. 544 | width: SizingAxis = .{ .fit = .{ .min = 0, .max = std.math.floatMax(f32) } }, 545 | /// Vertical sizing. 546 | height: SizingAxis = .{ .fit = .{ .min = 0, .max = std.math.floatMax(f32) } }, 547 | }; 548 | 549 | /// Padding between elements. 550 | pub const Padding = C.Clay_Padding; 551 | 552 | /// Layout configuration for an element. 553 | pub const LayoutConfig = struct { 554 | /// Controls how the final width and height of element are calculated. 555 | sizing: Sizing = .{}, 556 | /// Controls the horizontal and vertical white-space "padding" around the outside of child elements. 557 | padding: Padding = .{ .x = 0, .y = 0 }, 558 | /// Controls the white-space between child elements as they are laid out. 559 | child_gap: u16 = 0, 560 | /// Controls the alignment of children relative to the width and height of the parent container. 561 | child_alignment: ChildAlignment = .{ .x = .left, .y = .top }, 562 | /// Controls the axis/direction in which child elements are laid out. 563 | layout_direction: LayoutDirection = .left_to_right, 564 | }; 565 | 566 | // pub fn layoutDefault() *LayoutConfig { 567 | // return &C.Clay_LayoutConfig; 568 | // } 569 | 570 | /// Rectangle specific element configuration. 571 | pub const RectangleElementConfig = struct { 572 | /// RGBA float values between 0 and 255. 573 | color: Color = .{ .r = 0, .g = 0, .b = 0, .a = 0 }, 574 | /// Defines the radius in pixels for the arc of rectangle corners (0 is square, rectangle.width / 2 is circular). 575 | /// Note that `cornerRadius()` is available to set all 4 radii to the same value. 576 | corner_radius: CornerRadius = cornerRadius(0), 577 | // TODO: CONFIG EXTENSIONS!!! 578 | }; 579 | 580 | /// Dictates how text will be wrapped. 581 | pub const TextElementConfigWrapMode = enum(c_int) { 582 | /// Text will wrap on whitespace characters as container width shrinks, preserving whole words. 583 | words = C.CLAY_TEXT_WRAP_WORDS, 584 | /// Will only wrap when encountering newline characters. 585 | newlines = C.CLAY_TEXT_WRAP_NEWLINES, 586 | /// Text will never wrap even if its container is compressed beyond the text measured width. 587 | none = C.CLAY_TEXT_WRAP_NONE, 588 | }; 589 | 590 | /// Text element specific element configuration. 591 | pub const TextElementConfig = struct { 592 | /// RGBA float values between 0 and 255. 593 | text_color: Color = .{ .r = 0, .g = 0, .b = 0, .a = 0 }, 594 | /// ID of the font to use. 595 | font_id: u16 = 0, 596 | /// Usually how tall a font is in pixels, but not necessarily. 597 | font_size: u16 = 0, 598 | /// Horizontal white space between individual characters. 599 | letter_spacing: u16 = 0, 600 | /// When non-zero, the height of each wrapped line of text is the given number of pixels tall. 601 | /// Will affect the layout of parent and siblings. 602 | /// A value of `0` will use the measured height of the font. 603 | line_height: u16 = 0, 604 | /// Under what conditions text should wrap. 605 | wrap_mode: TextElementConfigWrapMode = .words, 606 | 607 | fn fromC(config: C.Clay_TextElementConfig) TextElementConfig { 608 | return .{ 609 | .text_color = config.textColor, 610 | .font_id = config.fontId, 611 | .font_size = config.fontSize, 612 | .letter_spacing = config.letterSpacing, 613 | .line_height = config.lineHeight, 614 | .wrap_mode = @enumFromInt(config.wrapMode), 615 | }; 616 | } 617 | }; 618 | 619 | /// Image specific element configuration. 620 | pub const ImageElementConfig = struct { 621 | /// Generic pointer used to pass through image data to the renderer. 622 | /// Not recommended for type safety, but extensions are not in the zig version atm. 623 | image_data: *anyopaque, 624 | /// Used to perform aspect ratio scaling on the image element. 625 | /// At the moment, image height will scale with width growth and limitations, but width will not scale with height growth and limitations. 626 | source_dimensions: Dimensions = .{ .width = 0, .height = 0 }, 627 | }; 628 | 629 | /// How to attach a floating element to its parent. 630 | pub const FloatingAttachPointType = enum(u8) { 631 | left_top = C.CLAY_ATTACH_POINT_LEFT_TOP, 632 | left_center = C.CLAY_ATTACH_POINT_LEFT_CENTER, 633 | left_bottom = C.CLAY_ATTACH_POINT_LEFT_BOTTOM, 634 | center_top = C.CLAY_ATTACH_POINT_CENTER_TOP, 635 | center_center = C.CLAY_ATTACH_POINT_CENTER_CENTER, 636 | center_bottom = C.CLAY_ATTACH_POINT_CENTER_BOTTOM, 637 | right_top = C.CLAY_ATTACH_POINT_RIGHT_TOP, 638 | right_center = C.CLAY_ATTACH_POINT_RIGHT_CENTER, 639 | right_bottom = C.CLAY_ATTACH_POINT_RIGHT_BOTTOM, 640 | }; 641 | 642 | /// How to attach a floating element to its parent. 643 | pub const FloatingAttachPoints = struct { 644 | /// Attach point on the floating element (this). 645 | element: FloatingAttachPointType = .left_top, 646 | /// Attach point on the parent element. 647 | parent: FloatingAttachPointType = .left_top, 648 | }; 649 | 650 | /// How pointer interaction should pass through to elements below. 651 | pub const PointerCaptureMode = enum(c_int) { 652 | /// Input is eaten by the floating container. 653 | capture = C.CLAY_POINTER_CAPTURE_MODE_CAPTURE, 654 | /// Input is passed through to the content underneath. 655 | passthrough = C.CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, 656 | }; 657 | 658 | /// Floating element specific configuration. 659 | pub const FloatingElementConfig = struct { 660 | /// Used to apply a position offset to the floating container after all other layout has been calculated. 661 | offset: Vector2 = .{ .x = 0, .y = 0 }, 662 | /// Used to expand the width and height of the floating container before laying out child elements. 663 | expand: Dimensions = .{ .width = 0, .height = 0 }, 664 | /// The higher the index, the more on top the floating element will be drawn. 665 | z_index: u16 = 0, 666 | /// Is set to the parent declared inside by default, but can be explicitly specified here. 667 | parent_id: u32 = 0, 668 | /// The point on the floating container that attaches to the parent. 669 | attachment: FloatingAttachPoints = .{}, 670 | /// How hover and click evens should be passed to content underneath. 671 | pointer_capture_mode: PointerCaptureMode = .capture, 672 | 673 | fn toC(self: FloatingElementConfig) C.Clay_FloatingElementConfig { 674 | return C.Clay_FloatingElementConfig{ 675 | .offset = self.offset, 676 | .expand = self.expand, 677 | .zIndex = self.z_index, 678 | .parentId = self.parent_id, 679 | .attachment = .{ .element = @intFromEnum(self.attachment.element), .parent = @intFromEnum(self.attachment.parent) }, 680 | .pointerCaptureMode = @intCast(@intFromEnum(self.pointer_capture_mode)), 681 | }; 682 | } 683 | }; 684 | 685 | /// Custom element specific configuration. 686 | pub const CustomElementConfig = struct { 687 | /// Generic data to pass data through. 688 | /// Not type safe, but there are no extensions yet in zig. 689 | custom_data: ?*anyopaque = null, 690 | }; 691 | 692 | /// Scroll element specific configuration. 693 | pub const ScrollElementConfig = struct { 694 | /// Enables or disables horizontal scrolling for the container element. 695 | horizontal: bool = false, 696 | /// Enables or disables vertical scrolling for the container element. 697 | vertical: bool = false, 698 | }; 699 | 700 | /// Border data. 701 | /// Note that a border configuration does not effect the layout. 702 | /// Border containers with zero padding will be drawn over the top of child elements. 703 | pub const Border = struct { 704 | /// Border thickness. 705 | width: u32 = 0, 706 | /// Border color, RGBA from 0 to 255. 707 | color: Color = .{ .r = 0, .g = 0, .b = 0, .a = 0 }, 708 | 709 | fn fromC(other: C.Clay_Border) Border { 710 | return .{ 711 | .width = other.width, 712 | .color = other.color, 713 | }; 714 | } 715 | 716 | fn toC(self: Border) C.Clay_Border { 717 | return .{ 718 | .width = self.width, 719 | .color = self.color, 720 | }; 721 | } 722 | }; 723 | 724 | /// Border specific configuration. 725 | pub const BorderElementConfig = struct { 726 | /// Border on the left edge of the element. 727 | left: Border = .{}, 728 | /// Border on the right edge of the element. 729 | right: Border = .{}, 730 | /// Border on the top edge of the element. 731 | top: Border = .{}, 732 | /// Border on the bottom edge of the element. 733 | bottom: Border = .{}, 734 | /// Border drawn between child elements. 735 | /// Vertical lines if the layout is top to bottom, horizontal lines if the layout is left to right. 736 | between_children: Border = .{}, 737 | /// Defines the radius in pixels for the arc of border corners (`0` is square, `rectangle.width / 2` is circular). 738 | corner_radius: CornerRadius = cornerRadius(0), 739 | }; 740 | 741 | /// Render command configuration. 742 | pub const RenderCommandConfig = union(RenderCommandType) { 743 | none: void, 744 | rectangle: RectangleElementConfig, 745 | border: BorderElementConfig, 746 | text: TextElementConfig, 747 | image: ImageElementConfig, 748 | scissor_start: void, 749 | scissor_end: void, 750 | custom: CustomElementConfig, 751 | 752 | fn fromC(com: C.Clay_RenderCommand) RenderCommandConfig { 753 | return switch (com.commandType) { 754 | C.CLAY_RENDER_COMMAND_TYPE_NONE => .{ .none = {} }, 755 | C.CLAY_RENDER_COMMAND_TYPE_RECTANGLE => .{ .rectangle = .{ 756 | .color = com.config.rectangleElementConfig.*.color, 757 | .corner_radius = CornerRadius.fromC(com.config.rectangleElementConfig.*.cornerRadius), 758 | } }, 759 | C.CLAY_RENDER_COMMAND_TYPE_BORDER => .{ 760 | .border = BorderElementConfig{ 761 | .between_children = Border.fromC(com.config.borderElementConfig.*.betweenChildren), 762 | .left = Border.fromC(com.config.borderElementConfig.*.betweenChildren), 763 | .right = Border.fromC(com.config.borderElementConfig.*.betweenChildren), 764 | .top = Border.fromC(com.config.borderElementConfig.*.betweenChildren), 765 | .bottom = Border.fromC(com.config.borderElementConfig.*.betweenChildren), 766 | .corner_radius = CornerRadius.fromC(com.config.borderElementConfig.*.cornerRadius), 767 | }, 768 | }, 769 | C.CLAY_RENDER_COMMAND_TYPE_TEXT => .{ 770 | .text = TextElementConfig.fromC(com.config.textElementConfig.*), 771 | }, 772 | C.CLAY_RENDER_COMMAND_TYPE_IMAGE => .{ .image = .{ 773 | .image_data = com.config.imageElementConfig.*.imageData.?, 774 | .source_dimensions = com.config.imageElementConfig.*.sourceDimensions, 775 | } }, 776 | C.CLAY_RENDER_COMMAND_TYPE_SCISSOR_START => .{ .scissor_start = {} }, 777 | C.CLAY_RENDER_COMMAND_TYPE_SCISSOR_END => .{ .scissor_end = {} }, 778 | C.CLAY_RENDER_COMMAND_TYPE_CUSTOM => .{ .custom = .{ 779 | .custom_data = com.config.customElementConfig.*.customData, 780 | } }, 781 | else => .{ .none = {} }, 782 | }; 783 | } 784 | }; 785 | 786 | /// A scroll container's internal data. 787 | pub const ScrollContainerData = struct { 788 | /// Pointer to internal scroll position of the data. 789 | /// Can mutate this to manually scroll. 790 | scroll_position: *Vector2, 791 | /// Width and height of the scroll container. 792 | scroll_container_dimensions: Dimensions, 793 | /// Width and height of the content to scroll (is larger). 794 | content_dimensions: Dimensions, 795 | /// Scroll element specific configuration. 796 | config: ScrollElementConfig, 797 | /// Container found for the requested element. 798 | found: bool, 799 | }; 800 | 801 | /// Type of render command. 802 | pub const RenderCommandType = enum(c_int) { 803 | /// Should be ignored, never emitted under normal conditions. 804 | none = C.CLAY_RENDER_COMMAND_TYPE_NONE, 805 | /// A rectangle should be drawn. 806 | rectangle = C.CLAY_RENDER_COMMAND_TYPE_RECTANGLE, 807 | /// A border should be drawn. 808 | border = C.CLAY_RENDER_COMMAND_TYPE_BORDER, 809 | /// Text should be drawn. 810 | text = C.CLAY_RENDER_COMMAND_TYPE_TEXT, 811 | /// An image should be drawn. 812 | image = C.CLAY_RENDER_COMMAND_TYPE_IMAGE, 813 | /// The renderer should cull pixels drawn outside the bounding box. 814 | scissor_start = C.CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, 815 | /// End a previously started scissor command. 816 | scissor_end = C.CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, 817 | /// Custom render command. 818 | custom = C.CLAY_RENDER_COMMAND_TYPE_CUSTOM, 819 | }; 820 | 821 | /// A command to pass to the renderer. 822 | pub const RenderCommand = struct { 823 | /// A rectangle representing the bounding box of this render command. 824 | bounding_box: BoundingBox, 825 | /// Render command specific configuration paired with type. 826 | config: RenderCommandConfig, 827 | /// Text data that is only used for text commands. 828 | text: ?[]const u8, 829 | /// Element ID that created this render command. 830 | id: u32, 831 | }; 832 | 833 | /// Represents the final calculated layout. 834 | /// Has an interator that can be used to get all the render commands stored in it. 835 | pub const RenderCommandArray = struct { 836 | arr: C.Clay_RenderCommandArray, 837 | 838 | /// Create an iterator to iterate over each command. 839 | pub fn iter(self: *RenderCommandArray) RenderCommandIterator { 840 | return .{ 841 | .arr = &self.arr, 842 | }; 843 | } 844 | 845 | /// Iterator of each command in the command array. 846 | pub const RenderCommandIterator = struct { 847 | arr: *C.Clay_RenderCommandArray, 848 | index: i32 = 0, 849 | 850 | /// Get the next render command in the array. 851 | pub fn next(self: *RenderCommandIterator) ?RenderCommand { 852 | if (self.index < @as(usize, @intCast(self.arr.length))) { 853 | const com = C.Clay_RenderCommandArray_Get(self.arr, self.index).*; 854 | const ret: RenderCommand = .{ 855 | .bounding_box = com.boundingBox, 856 | .config = RenderCommandConfig.fromC(com), 857 | .id = com.id, 858 | .text = if (com.text.chars) |chars| chars[0..@intCast(com.text.length)] else null, 859 | }; 860 | self.index += 1; 861 | return ret; 862 | } 863 | return null; 864 | } 865 | }; 866 | }; 867 | 868 | /// Current state of pointer interaction. 869 | pub const PointerDataInteractionState = enum(c_int) { 870 | /// First frame that the mouse button has been pressed while on an element. 871 | pressed_this_frame = C.CLAY_POINTER_DATA_PRESSED_THIS_FRAME, 872 | /// Mouse button is down and over the element. 873 | pressed = C.CLAY_POINTER_DATA_PRESSED, 874 | /// Mouse button has been released over an element. 875 | released_this_frame = C.CLAY_POINTER_DATA_RELEASED_THIS_FRAME, 876 | /// Mouse is being hovered over an element. 877 | release = C.CLAY_POINTER_DATA_RELEASED, 878 | }; 879 | 880 | /// State of the pointer. 881 | pub const PointerData = struct { 882 | /// Current pointer position relative to UI. 883 | position: Vector2, 884 | /// Click state of the pointer. 885 | state: PointerDataInteractionState, 886 | }; 887 | 888 | /// Type of error encountered by clay. 889 | pub const ErrorType = enum(c_int) { 890 | /// User attempted to use `text()` and forgot to call `setMeasureTextFunction()`. 891 | text_measurement_function_not_provided = C.CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, 892 | /// Clay was initialized with an arena that was too small for the configured max element count. 893 | /// Try using `minMemorySize()` to get the exact number of bytes needed. 894 | arena_capacity_exceeded = C.CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, 895 | /// The declared UI hierarchy has too many elements fo the configured max element count. 896 | /// Use `setMaxElementCount()` to increase the max, then call `minMemorySize()` again. 897 | elements_capacity_exceeded = C.CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, 898 | /// The declared UI hierarchy has too much text for the configured text measure cache size. 899 | /// Use `setMaxMeasureTextCacheWordCount()` to increase the max, then call `minMemorySize()` again. 900 | text_measurement_capacity_exceeded = C.CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, 901 | /// Two elements have been declared with the same ID. 902 | /// Set a breakpoint in your error handler for a stack back trace to where this occurred. 903 | duplicate_id = C.CLAY_ERROR_TYPE_DUPLICATE_ID, 904 | /// A floating element was declared with a `parent_id` property, but no element with that ID was found. 905 | /// Set a breakpoint in your error handler for a stack back trace to where this occurred. 906 | floating_container_parent_not_found = C.CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, 907 | /// Clay has encountered an internal logic or memory error. Report this. 908 | internal_error = C.CLAY_ERROR_TYPE_INTERNAL_ERROR, 909 | }; 910 | 911 | /// Context of an error received. 912 | pub fn ErrorData(comptime UserData: type) type { 913 | return struct { 914 | /// Type of error encountered. 915 | error_type: ErrorType, 916 | /// Description of the error. 917 | error_text: []const u8, 918 | /// Extra user data to the error handler. 919 | user_data: *UserData, 920 | }; 921 | } 922 | 923 | /// Callback data for handling an error. 924 | pub fn ErrorHandler(comptime UserData: type) type { 925 | return struct { 926 | /// Function to call when an error is encountered. 927 | handler_function: *const fn (error_text: ErrorData(UserData)) void, 928 | /// User data to pass to the error handler. 929 | user_data: *UserData, 930 | }; 931 | } 932 | 933 | const HandlerCtx = struct { 934 | func: ?*const anyopaque, 935 | data: ?*anyopaque, 936 | }; 937 | 938 | const HandlerCtxConst = struct { 939 | func: ?*const anyopaque, 940 | data: ?*const anyopaque, 941 | }; 942 | 943 | var error_handler_ctx: HandlerCtx = .{ 944 | .func = null, 945 | .data = null, 946 | }; 947 | 948 | fn errorHandlerFn(error_text: C.Clay_ErrorData) callconv(.C) void { 949 | const ctx: *HandlerCtx = @ptrFromInt(error_text.userData); 950 | const func: *const fn (error_text: ErrorData(anyopaque)) void = @ptrCast(@alignCast(ctx.func.?)); 951 | func(.{ 952 | .error_text = error_text.errorText.chars[0..@intCast(error_text.errorText.length)], 953 | .error_type = @enumFromInt(error_text.errorType), 954 | .user_data = ctx.data.?, 955 | }); 956 | } 957 | 958 | /// Returns the minimum amount of memory in bytes that clay needs to accomodate the current max element count. 959 | pub fn minMemorySize() u32 { 960 | return C.Clay_MinMemorySize(); 961 | } 962 | 963 | /// Creates an `Arena` struct with the given memory slice which can be passed to `initialize()`. 964 | /// * `buffer` - Block of memory to use as an arena. 965 | pub fn createArenaWithCapacityAndMemory(buffer: []u8) Arena { 966 | return C.Clay_CreateArenaWithCapacityAndMemory(@intCast(buffer.len), buffer.ptr); 967 | } 968 | 969 | /// Sets the internal pointer position and state (i.e. current mouse / touch position) and recalculates overlap info, 970 | /// which is used for mouseover / click calculation (via `pointerOver()` and updating scroll containers with `updateScrollContainers()`). 971 | /// * `position` - Mouse position relative to the root UI. 972 | /// * `pointer_down` - Current state this frame (true as long as the left mouse button is held down). 973 | pub fn setPointerState(position: Vector2, pointer_down: bool) void { 974 | C.Clay_SetPointerState(position, pointer_down); 975 | } 976 | 977 | /// Initializes the internal memory mapping, sets the internal dimensions for the layout, and binds an error handler for clay to use when something goes wrong. 978 | /// * `arena` - Arena to use created with `createArenaWithCapacityAndMemory()`. 979 | /// * `layout_dimensions` - Size of the root layout. 980 | /// * `ErrorHandlerUserData` - Type of user data to be used for the error handler. 981 | /// * `error_handler` - Called whenever clay encounters an error. 982 | pub fn initialize(arena: Arena, layout_dimensions: Dimensions, comptime ErrorHandlerUserData: type, error_handler: ErrorHandler(ErrorHandlerUserData)) void { 983 | error_handler_ctx.func = error_handler.handler_function; 984 | error_handler_ctx.data = error_handler.user_data; 985 | C.Clay_Initialize(arena, layout_dimensions, .{ 986 | .errorHandlerFunction = errorHandlerFn, 987 | .userData = @intFromPtr(&error_handler_ctx), 988 | }); 989 | } 990 | 991 | /// This function handles scrolling of containers. 992 | /// * `enable_drag_scrolling` - If to allow touch/drag scrolling utilizing the pointer position (will only work if `setPointerState()` was also called). 993 | /// * `scroll_delta` - Mouse wheel or trackpad scrolling this frame. 994 | /// * `delta_time` - Time in seconds since the last frame to normalize and smooth scrolling across different refresh rates. 995 | pub fn updateScrollContainers(enable_drag_scrolling: bool, scroll_delta: Vector2, delta_time: f32) void { 996 | C.Clay_UpdateScrollContainers(enable_drag_scrolling, scroll_delta, delta_time); 997 | } 998 | 999 | /// Sets the internal layout dimensions. 1000 | /// Cheap enough to be called every frame with your screen dimensions to automatically respond to window resizing, etc. 1001 | /// 1002 | /// * `dimensions` - New size of the root layout. 1003 | pub fn setLayoutDimensions(dimensions: Dimensions) void { 1004 | C.Clay_SetLayoutDimensions(dimensions); 1005 | } 1006 | 1007 | /// Layout structure created from `beginLayout()`. 1008 | /// Has an `end()` function to call to get the render commands. 1009 | const Layout = struct { 1010 | /// Ends declaration of elements and calculates the results of the current layout. 1011 | /// Renders a `RenderCommandArray` containing the results of the layout calculation. 1012 | pub fn end(self: Layout) RenderCommandArray { 1013 | _ = self; 1014 | return .{ 1015 | .arr = C.Clay_EndLayout(), 1016 | }; 1017 | } 1018 | }; 1019 | 1020 | /// Prepares clay to calculate a new layout. 1021 | /// Called each frame before any of the elements. 1022 | pub fn beginLayout() Layout { 1023 | C.Clay_BeginLayout(); 1024 | return .{}; 1025 | } 1026 | 1027 | /// Returns an `ElementId` for the provided ID string, used for querying element info such as mouseover state, scroll container data, etc. 1028 | /// * `id_string` - ID string to get the element ID for. 1029 | pub fn getElementId(id_string: []const u8) ElementId { 1030 | return ElementId.fromC(C.Clay_GetElementId(.{ .chars = id_string.ptr, .length = @intCast(id_string.len) })); 1031 | } 1032 | 1033 | /// Returns an `ElementId` for the provided the ID string with an index mixed into the has for case of say looping. 1034 | /// * `id_string` - ID string to get the element ID for. 1035 | /// * `index` - Index of the element to mix in with the hash. 1036 | pub fn getElementIdWithIndex(id_string: []const u8, index: u32) ElementId { 1037 | return ElementId.fromC(C.Clay_GetElementIdWithIndex(.{ .chars = id_string.ptr, .length = @intCast(id_string.len) }, index)); 1038 | } 1039 | 1040 | /// Called during layout declaration and returns true if the pointer position previously set with `setPointerState()` is inside the bounding box of the currently open element. 1041 | /// Note that this is based on the element's position from the last frame. 1042 | pub fn hovered() bool { 1043 | return C.Clay_Hovered(); 1044 | } 1045 | 1046 | var on_hover_cb: ?*const anyopaque = null; 1047 | 1048 | fn onHoverCb(element_id: C.Clay_ElementId, pointer_data: C.Clay_PointerData, user_data: isize) callconv(.C) void { 1049 | const func: *const fn (element_id: ElementId, pointer_data: PointerData, user_data: ?*anyopaque) void = @ptrCast(@alignCast(on_hover_cb.?)); 1050 | func(ElementId.fromC(element_id), .{ .position = pointer_data.position, .state = @enumFromInt(pointer_data.state) }, @ptrFromInt(@as(usize, @intCast(user_data)))); 1051 | } 1052 | 1053 | /// Called during layout declaration, this function allows you to attach a function pointer to the currently open element that will be called once per layout if the pointer position previously set with `setPointerState()` is inside the bounding box of the currently open element. 1054 | /// See `PointerData` for more information on the `pointer_data` argument. 1055 | /// * `on_hover_function` - Function to call on hover of the element. 1056 | /// * `user_data` - User data to pass to the hover function. 1057 | pub fn onHover(on_hover_function: *const fn (element_id: ElementId, pointer_data: PointerData, user_data: ?*anyopaque) void, user_data: ?*anyopaque) void { 1058 | on_hover_cb = on_hover_function; 1059 | C.Clay_OnHover(onHoverCb, @intCast(@intFromPtr(user_data))); 1060 | } 1061 | 1062 | /// Returns true if the pointer position previously set with `setPointerState()` is inside the bounding box of the layout element with the provided `element_id`. 1063 | /// Note that this position from the last frame. 1064 | /// If frame-accurate pointer overlap detection is required, perhaps in the case of significant change in UI layout between frames, you can simply run your layout code twice that frame. 1065 | /// The second call to `pointerOver()` will be frame accurate. 1066 | /// * `element_id` - Element to check if the pointer is over. 1067 | pub fn pointerOver(element_id: ElementId) bool { 1068 | return C.Clay_PointerOver(element_id.toC()); 1069 | } 1070 | 1071 | /// Returns `ScrollContainerData` for the scroll container matching the provided ID. 1072 | /// This function allows for manipulation of scroll position, allowing you to build things such as scroll bars, buttons that jump to somewhere in the scroll container, etc. 1073 | /// * `element_id` - Element to get the scroll container data too. 1074 | pub fn getScrollContainerData(element_id: ElementId) ScrollContainerData { 1075 | const ret = C.Clay_GetScrollContainerData(element_id.toC()); 1076 | return .{ 1077 | .config = .{ .horizontal = ret.config.horizontal, .vertical = ret.config.vertical }, 1078 | .content_dimensions = ret.contentDimensions, 1079 | .found = ret.found, 1080 | .scroll_container_dimensions = ret.scrollContainerDimensions, 1081 | .scroll_position = ret.scrollPosition, 1082 | }; 1083 | } 1084 | 1085 | var measure_text_function_cb: ?*const fn (text: []const u8, config: TextElementConfig) Dimensions = null; 1086 | 1087 | fn setMeasureTextFunction_cb(text_data: [*c]C.Clay_String, config: [*c]C.Clay_TextElementConfig) callconv(.C) Dimensions { 1088 | if (measure_text_function_cb) |cb| { 1089 | return cb(text_data.*.chars[0..@intCast(text_data.*.length)], TextElementConfig.fromC(config.*)); 1090 | } 1091 | return .{ .width = 0, .height = 0 }; 1092 | } 1093 | 1094 | /// Takes a function pointer that can be used to measure the width and height of a string. 1095 | /// Used by clay layout to determine `text` element sizing and wrapping. 1096 | /// 1097 | /// The string is not guaranteed to be null terminated. 1098 | /// 1099 | /// It is essential that this function is as fast as possible. 1100 | /// * `measure_text_function` - Function pointer to measure given text utilizing its text characters and config. 1101 | pub fn setMeasureTextFunction(measure_text_function: *const fn (text: []const u8, config: TextElementConfig) Dimensions) void { 1102 | measure_text_function_cb = measure_text_function; 1103 | C.Clay_SetMeasureTextFunction(setMeasureTextFunction_cb); 1104 | } 1105 | 1106 | var set_query_scroll_offset_function_cb: ?*const fn (element_id: u32) Vector2 = null; 1107 | 1108 | fn setQueryScrollOffsetFunction_cb(element_id: u32) callconv(.C) Vector2 { 1109 | if (set_query_scroll_offset_function_cb) |cb| { 1110 | return cb(element_id); 1111 | } 1112 | return .{ .x = 0, .y = 0 }; 1113 | } 1114 | 1115 | pub fn setQueryScrollOffsetFunction(query_scroll_offset_function: *const fn (element_id: u32) Vector2) void { 1116 | set_query_scroll_offset_function_cb = query_scroll_offset_function; 1117 | C.Clay_SetQueryScrollOffsetFunction(setQueryScrollOffsetFunction_cb); 1118 | } 1119 | 1120 | /// Enable or disable debug mode. 1121 | /// * `enabled` - If to enable debug mode. 1122 | pub fn setDebugModeEnabled(enabled: bool) void { 1123 | C.Clay_SetDebugModeEnabled(enabled); 1124 | } 1125 | 1126 | /// If debug mode is enabled. 1127 | pub fn isDebugModeEnabled() bool { 1128 | return C.Clay_IsDebugModeEnabled(); 1129 | } 1130 | 1131 | /// Enable or disable culling. 1132 | /// * `enabled` - If to enable culling. 1133 | pub fn setCullingEnabled(enabled: bool) void { 1134 | C.Clay_SetCullingEnabled(enabled); 1135 | } 1136 | 1137 | /// Updates the internal maximum element count, allowing clay to allocate larger UI hierarchies. 1138 | /// 1139 | /// Clay will need to be re-initialized. Re-check the value of `minMemorySize()`. 1140 | /// * `max_element_count` - New maximum number of elements. 1141 | pub fn setMaxElementCount(max_element_count: u32) void { 1142 | C.Clay_SetMaxElementCount(max_element_count); 1143 | } 1144 | 1145 | /// Updates the internal text measurement cache size, allowing clay to allocate more text. 1146 | /// 1147 | /// Clay will need to be re-initialized. Re-check the value of `minMemorySize()`. 1148 | /// * `max_measurement_text_cache_word_count` - How many separate words can be stored in the text measurement cache. 1149 | pub fn setMaxMeasureTextCacheWordCount(max_measure_text_cache_word_count: u32) void { 1150 | C.Clay_SetMaxMeasureTextCacheWordCount(max_measure_text_cache_word_count); 1151 | } 1152 | 1153 | /// Get a pointer to the current debug view highlight color. 1154 | pub fn debugViewHighlightColor() *Color { 1155 | return &C.Clay__debugViewHighlightColor; 1156 | } 1157 | 1158 | /// Get a pointer to the current debug view width. 1159 | pub fn debugViewWidth() *u32 { 1160 | return &C.Clay__debugViewWidth; 1161 | } 1162 | --------------------------------------------------------------------------------