I like that proposal, but I also liked stack capturing macros
I don’t see how it would pass the “no major language changes” gate. Womp womp.
The double function approach in the first post is needlessly weird, but at least that if(clay.open(.{
approach used by raugl/clay-zig has the concrete benefit of being able to skip subtrees.
Maybe that’s needed less often in clay, but skipping subtrees or earlying-out is a big part of using dearimgui and similar push/pop oriented apis.
Inline blocks or stack capturing macros or a similar construct would really help with these ImGui and alike libraries. Defer is usually enough, but sometimes some control flow abstraction would be helpful.
As an example, here’s a snippet from my code handling being able to draw a property editor inside of an existing window, optionally supporting being drawn inside an ImGui::TreeNode()
pair:
const EditBlock = struct {
pop_tree: bool,
draw_editor: bool,
fn begin(root_editor_drawer: *RootEditorDrawer) EditBlock {
return if (root_editor_drawer.scratch.popLabel()) |label| blk: {
zgui.setNextItemOpen(.{ .is_open = true, .cond = .once });
const is_open = zgui.treeNode(label);
break :blk .{
.pop_tree = is_open,
.draw_editor = is_open,
};
} else .{
.pop_tree = false,
.draw_editor = true,
};
}
fn end(self: @This()) void {
if (self.pop_tree) {
zgui.treePop();
}
}
};
pub fn drawListEditor(
data: *Data,
ctx: EditContext,
) bool {
// BEGIN: drawEditor function prelude
const edit_block: EditBlock = .begin(ctx.root_editor_drawer);
if (!edit_block.draw_editor) {
return false;
}
defer edit_block.end();
// END: drawEditor function prelude
return drawTheRestOfTheEditor();
}
That 5 line prelude isn’t so bad in just this one function, but there are currently 4 or 5 which use that prelude, and more will be added. Inline blocks would collapse those down to 1 line:
pub fn drawListEditor(
data: *Data,
ctx: EditContext,
) bool {
return editBlock(ctx.root_editor_drawer, inline {
return drawTheRestOfTheEditor();
});
}
With Zig today I could write a wrapper that takes a comptime function ref and the args, or maybe just anytype for both, but it gets a little ugly and pushes the decision of whether or not to use the EditBlock outside of the function doing the core work, which is sometimes helpful and sometimes not.
The thing I dislike about the wrapper approach is it makes me prone to injecting a bunch of generic layers between functions at the top coordinating work and functions at the bottom doing the work. That’s why I’m just living with copy-pasting these 5 lines into every drawEditor function. Maybe I’ll use a context struct, but that feels like it’ll just muddy up the code even more.
Anyway, preaching to the choir, I’m sure.