Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Help Wanted] Zig & Odin #3

Open
nicbarker opened this issue Aug 27, 2024 · 11 comments
Open

[Help Wanted] Zig & Odin #3

nicbarker opened this issue Aug 27, 2024 · 11 comments
Labels
help wanted Extra attention is needed

Comments

@nicbarker
Copy link
Owner

I would love for clay to be easily usable from both Zig and Odin.
However, there's a major sticking point with both that I don't know how to solve.

The core Element Macros rely on a particular feature of the C preprocessor, specifically "function-like macros" and the ability to pass arbitrary text as an argument to these macros. If you're not familiar with this in C, you can take a look at the definition of any of these macros in clay.h (example) and you'll see that they all follow a general form:

#define CLAY_CONTAINER(id, layoutConfig, children)
    Clay__OpenContainerElement(id, layoutConfig);
    children
    Clay__CloseContainerElement()

You can see that in order to correctly construct the layout hierarchy, child declarations need to preceded by a Clay__Open... and then followed by a Clay__Close....
In clay's use case, these macros allows you to automatically sandwhich child layout elements in the required open / close, by "passing a block as a macro argument" - creating (imo) a very nice developer experience:

CLAY_CONTAINER(id, layout, { // This {} is the "third argument" to the macro
    CLAY_TEXT(id, "Child text", layout);
    CLAY_CONTAINER(id, layout, {
        // ... etc
    });
});

As a result it's not actually possible to "forget" to close these containers and end up with a mismatch or with elements incorrectly parented - this macro syntax functions almost like a form of RAII.

Neither Zig nor Odin support this type of "function-like macro" where arbitrary text can be pasted in.

In Odin's case, it might be possible through some combination of defer and non capturing lambdas to replicate this type of behaviour, but what I'm really looking for is something fool proof - where you don't have to spend time debugging a missing call to Clay__Close..., and I don't have to build debug tools to help you with that 😛

In Zig's case, AFAIK there is even less official support for closures, and just glossing over the docs I can't really think of a way to implement it that wouldn't make layout definition a mess.

Any help or out of the box ideas would be greatly appreciated!

@nicbarker nicbarker added the help wanted Extra attention is needed label Aug 27, 2024
Repository owner deleted a comment Aug 27, 2024
Repository owner deleted a comment Aug 27, 2024
@Dudejoe870
Copy link
Contributor

For Odin:
You do actually have some amount of support for this, for example, vendor:microui uses a special attribute to allow you to do

if window(...) { }

instead of

if begin_window(...) {
    defer end_window()
}

an example from the source code how such a function is defined:

@(deferred_in_out=scoped_end_window)
window :: proc(ctx: ^Context, title: string, rect: Rect, opt := Options{}) -> bool {
	return begin_window(ctx, title, rect, opt)
}

scoped_end_window :: proc(ctx: ^Context, _: string, _: Rect, _: Options, ok: bool) {
	if ok {
		end_window(ctx)	
	}
}

now, I see that these macros don't actually have return values, but you could certainly leverage this behavior anyway, to make fairly nice to use Odin bindings (which would be great, someone just posted this library in the Odin Discord, and it looks really useful!)

@nicbarker
Copy link
Owner Author

Just for posterity the folks over at the Odin discord have been very helpful, and I'm going to have a crack at writing the Odin bindings today.

@illfygli
Copy link

illfygli commented Aug 27, 2024

I'm not an expert, but here's a Zig idea I toyed with:

const std = @import("std");
const LayoutConfig = struct {};
// Would be the real imported Clay
const clay_ns = @This();

pub fn clay(layout: anytype) void {
    const fields = std.meta.fields(@TypeOf(layout));

    inline for (fields) |el| {
        comptime var name: [el.name.len]u8 = undefined;
        @memcpy(&name, el.name);
        // Crudely capitalize the field name.
        name[0] = comptime std.ascii.toUpper(name[0]);

        const openFn = @field(clay_ns, "Clay__Open" ++ name ++ "Element");
        const closeFn = @field(clay_ns, "Clay__Close" ++ name ++ "Element");

        // The Clay element functions have different arities,
        // so more code would be needed here.
        const args = @field(layout, el.name);
        const id_arg = args[0];
        const config_arg = args[1];

        openFn(id_arg, config_arg);
        defer closeFn();

        if (3 <= args.len) {
            clay(args[2]);
        }
    }
}

const id = 123;

pub fn main() void {
    clay(.{
        .container = .{
            id,
            LayoutConfig{},
            .{
                .text = .{
                    id,
                    "text goes here",
                },
                .rectangle = .{
                    id,
                    LayoutConfig{},
                },
            },
        },
    });
}

fn Clay__OpenContainerElement(id_: usize, layout_config: LayoutConfig) void {
    std.log.debug("open container element {d} {any}", .{ id_, layout_config });
}

fn Clay__CloseContainerElement() void {
    std.log.debug("close container element", .{});
}

fn Clay__OpenTextElement(id_: usize, text: []const u8) void {
    std.log.debug("open text element {d} {s}", .{ id_, text });
}

fn Clay__CloseTextElement() void {
    std.log.debug("close text element", .{});
}

fn Clay__OpenRectangleElement(id_: usize, layout_config: LayoutConfig) void {
    std.log.debug("open rectangle element {d} {any}", .{ id_, layout_config });
}

fn Clay__CloseRectangleElement() void {
    std.log.debug("close rectangle element", .{});
}

Running this program prints:

debug: open container element 123 test.LayoutConfig{ }
debug: open text element 123 text goes here
debug: close text element
debug: open rectangle element 123 test.LayoutConfig{ }
debug: close rectangle element
debug: close container element

So you get the sandwiching, but it would need more work, and I don't know if it's a good direction.

@nicbarker
Copy link
Owner Author

@illfygli That is super cool, thanks so much for the code samples! I will do some investigation when I have time, would be great to get it working from Zig.

@nicbarker
Copy link
Owner Author

I've made some great progress with the Odin bindings today: #5

@crystalthoughts
Copy link

crystalthoughts commented Aug 28, 2024

I'd love to see Nim bindings too, the macro system will allow for the functional form :)

Edit: Missed the part where you explain how the macros work, should be simple to map to other DSL forms! I might take a crack if there are no takers

@nicbarker
Copy link
Owner Author

@crystalthoughts Please feel free, I've never used nim myself but it looks cool 😁

@nicbarker
Copy link
Owner Author

Now that the odin bindings are out the door and I've got a reasonable idea of how long it takes to write them for another language, I'm probably going to delay writing the zig bindings until after I've finished some feature work. Still high on the priority list, though!

@nicbarker nicbarker changed the title Help Wanted: Zig & Odin [Help Wanted] Zig & Odin Sep 15, 2024
@Srekel
Copy link

Srekel commented Nov 3, 2024

Very cool! We would love Zig bindings for Tides of Revival, though us needing complex UI is a bit off in the future, so there's no urgency on our part. But please ping me if you think we can help :)

image

@nicbarker
Copy link
Owner Author

@Srekel that's great to hear, Tides looks like an awesome and ambitious project 😁 Now that I've landed the majority of the planned breaking changes in #34, I feel a bit more confident making an attempt on the Zig bindings, and will likely ask in the language discord server for help.

@AYM1607
Copy link

AYM1607 commented Dec 21, 2024

Hey @nicbarker, I just watched your YT video and clay looks great! I have a Zig project that would benefit from a nice UI and I'd love to pitch in if you need any help with the bindings when you come around to it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

7 participants
@Srekel @nicbarker @crystalthoughts @Dudejoe870 @AYM1607 @illfygli and others