|
const std = @import("std"); |
|
const rl = @import("raylib"); |
|
|
|
// Global or in main |
|
var font: rl.Font = undefined; |
|
|
|
// Import Nuklear cleanly from your cached dependency include path |
|
// Clear out implementation macros; look up headers only |
|
const nk = @cImport({ |
|
@cDefine("NK_INCLUDE_FIXED_TYPES", ""); |
|
@cDefine("NK_INCLUDE_DEFAULT_ALLOCATOR", ""); |
|
@cInclude("nuklear.h"); |
|
}); |
|
|
|
// Callback wrapper to measure string pixel widths using Raylib's default metrics |
|
// fn textWidthCallback(handle: nk.nk_handle, height: f32, text: [*c]const u8, len: c_int) callconv(.c) f32 { |
|
// _ = handle; |
|
// _ = height; |
|
// // 1. Create a safe slice from the raw text pointer |
|
// const slice = text[0..@intCast(len)]; |
|
// // 2. Prepare a 256-byte stack buffer (plenty for standard UI text lengths) |
|
// var buf: [256]u8 = undefined; |
|
// if (slice.len >= buf.len) return 0.0; // Fail-safe boundary check |
|
// // 3. Copy the string data and null-terminate it manually |
|
// @memcpy(buf[0..slice.len], slice); |
|
// buf[slice.len] = 0; |
|
// // 4. Create a valid sentinel-terminated slice for Raylib |
|
// const safe_text: [:0]const u8 = buf[0..slice.len :0]; |
|
// return @floatFromInt(rl.measureText(safe_text, 14)); |
|
// } |
|
|
|
fn textWidthCallback(handle: nk.nk_handle, height: f32, text: [*c]const u8, len: c_int) callconv(.c) f32 { |
|
_ = handle; |
|
// _ = height; |
|
|
|
if (len <= 0) return 0.0; |
|
|
|
const slice = text[0..@intCast(len)]; |
|
|
|
var buf: [512:0]u8 = undefined; |
|
if (slice.len >= buf.len) return 0.0; |
|
|
|
@memcpy(buf[0..slice.len], slice); |
|
buf[slice.len] = 0; |
|
|
|
const safe_text: [:0]const u8 = buf[0..slice.len :0]; |
|
|
|
// const size = rl.measureTextEx(font, safe_text, 18.0, 0); // ← use same size as nk_font.height |
|
const size = rl.measureTextEx(font, safe_text, height, 0); // ← use same size as nk_font.height |
|
return size.x; |
|
} |
|
|
|
pub fn main() !void { |
|
const screenWidth = 800; |
|
const screenHeight = 450; |
|
|
|
rl.initWindow(screenWidth, screenHeight, "Raylib + Nuklear v4.13.3"); |
|
defer rl.closeWindow(); |
|
|
|
// Option A: Load from file (TTF, OTF, etc.) |
|
// font = rl.loadFont("assets/JetBrainsMonoNL-Light.ttf"); |
|
font = try rl.loadFont("assets/JetBrainsMonoNL-Light.ttf"); |
|
rl.setTextureFilter(font.texture, .bilinear);// this handle text render correctly. Without it will blur text. |
|
defer rl.unloadFont(font); |
|
|
|
rl.setTargetFPS(60); |
|
|
|
// New safe manual initialization block |
|
var user_font: nk.nk_user_font = undefined; |
|
user_font.userdata = .{ .ptr = null }; |
|
user_font.height = 18.0; |
|
user_font.width = textWidthCallback; |
|
|
|
var ctx: nk.nk_context = undefined; |
|
_ = nk.nk_init_default(&ctx, &user_font); |
|
defer nk.nk_free(&ctx); |
|
|
|
while (!rl.windowShouldClose()) { |
|
// Handle input state... |
|
nk.nk_input_begin(&ctx); |
|
const mouse_pos = rl.getMousePosition(); |
|
nk.nk_input_motion(&ctx, @intFromFloat(mouse_pos.x), @intFromFloat(mouse_pos.y)); |
|
nk.nk_input_button(&ctx, nk.NK_BUTTON_LEFT, @intFromFloat(mouse_pos.x), @intFromFloat(mouse_pos.y), @intFromBool(rl.isMouseButtonDown(.left))); |
|
nk.nk_input_end(&ctx); |
|
|
|
// GUI Window declaration |
|
if (nk.nk_begin(&ctx, "Nuklear Window", nk.nk_rect(50, 50, 230, 250), nk.NK_WINDOW_BORDER | nk.NK_WINDOW_MOVABLE | nk.NK_WINDOW_TITLE) != 0) { |
|
nk.nk_layout_row_static(&ctx, 30, 80, 1); |
|
if (nk.nk_button_label(&ctx, "Test Zip") != 0) { |
|
std.debug.print("Compiled directly from the raw Zip target!\n", .{}); |
|
} |
|
} |
|
nk.nk_end(&ctx); |
|
|
|
rl.beginDrawing(); |
|
defer rl.endDrawing(); |
|
rl.clearBackground(.ray_white); |
|
|
|
// Primitive Draw Command Loop |
|
// Iterate through Nuklear's draw commands and render them with Raylib |
|
// Render Nuklear commands |
|
var cmd_iter = nk.nk__begin(&ctx); |
|
while (cmd_iter != null) : (cmd_iter = nk.nk__next(&ctx, cmd_iter)) { |
|
const cmd = cmd_iter.*; // base command header |
|
|
|
switch (cmd.type) { |
|
nk.NK_COMMAND_NOP => {}, |
|
|
|
nk.NK_COMMAND_SCISSOR => { |
|
// Optional: implement scissor if you need proper clipping |
|
const c = @as(*const nk.nk_command_scissor, @ptrCast(cmd_iter)); |
|
// rl.BeginScissorMode(...) or similar |
|
_ = c; |
|
}, |
|
|
|
nk.NK_COMMAND_RECT => { // unfilled (border) |
|
const c = @as(*const nk.nk_command_rect, @ptrCast(cmd_iter)); |
|
rl.drawRectangleLinesEx(rl.Rectangle{ .x = @floatFromInt(c.x), .y = @floatFromInt(c.y), .width = @floatFromInt(c.w), .height = @floatFromInt(c.h) }, @floatFromInt(c.line_thickness), rl.Color{ .r = c.color.r, .g = c.color.g, .b = c.color.b, .a = c.color.a }); |
|
}, |
|
|
|
nk.NK_COMMAND_RECT_FILLED => { |
|
const c = @as(*const nk.nk_command_rect_filled, @ptrCast(cmd_iter)); |
|
rl.drawRectangle(@intCast(c.x), @intCast(c.y), @intCast(c.w), @intCast(c.h), rl.Color{ .r = c.color.r, .g = c.color.g, .b = c.color.b, .a = c.color.a }); |
|
}, |
|
nk.NK_COMMAND_TEXT => { |
|
const c = @as(*const nk.nk_command_text, @ptrCast(cmd_iter)); |
|
if (c.length > 0) { |
|
const text_ptr: [*]const u8 = @ptrCast(&c.string); |
|
const slice = text_ptr[0..@intCast(c.length)]; |
|
|
|
var buf: [512:0]u8 = undefined; |
|
if (slice.len >= buf.len) continue; |
|
|
|
@memcpy(buf[0..slice.len], slice); |
|
buf[slice.len] = 0; |
|
const safe_text: [:0]const u8 = buf[0..slice.len :0]; |
|
|
|
const position = rl.Vector2{ .x = @floatFromInt(c.x), .y = @floatFromInt(c.y) }; |
|
const color = rl.Color{ .r = c.foreground.r, .g = c.foreground.g, .b = c.foreground.b, .a = c.foreground.a }; |
|
|
|
// FIX: Pass c.height directly since it is already an f32 |
|
// rl.drawTextEx(font, safe_text, position, c.height, 0.0, color); |
|
rl.drawTextEx(font, safe_text, position, c.height, 0.0, color); |
|
// rl.drawTextEx(font, safe_text, position, 24, 0.0, color); |
|
} |
|
}, |
|
|
|
// nk.NK_COMMAND_TEXT => { |
|
// const c = @as(*const nk.nk_command_text, @ptrCast(cmd_iter)); |
|
// // Safer text extraction |
|
// if (c.length > 0) { |
|
// const text_ptr: [*]const u8 = @ptrCast(&c.string); |
|
// const slice = text_ptr[0..@intCast(c.length)]; |
|
// // Stack buffer + null termination for Raylib |
|
// var buf: [512:0]u8 = undefined; // generous size |
|
// if (slice.len >= buf.len) { |
|
// // truncate or skip very long text |
|
// continue; |
|
// } |
|
// @memcpy(buf[0..slice.len], slice); |
|
// buf[slice.len] = 0; |
|
// const safe_text: [:0]const u8 = buf[0..slice.len :0]; |
|
// rl.drawText(safe_text, @intCast(c.x), @intCast(c.y), 14, // or use @intFromFloat(c.height) |
|
// rl.Color{ |
|
// .r = c.foreground.r, |
|
// .g = c.foreground.g, |
|
// .b = c.foreground.b, |
|
// .a = c.foreground.a, |
|
// }); |
|
// } |
|
// }, |
|
|
|
// Add more as needed (circles, lines, images, etc.) |
|
else => {}, |
|
} |
|
} |
|
|
|
nk.nk_clear(&ctx); |
|
} |
|
} |