const std = @import("std"); const builtin = @import("builtin"); const vaxis = @import("vaxis"); const vxfw = vaxis.vxfw; const Allocator = std.mem.Allocator; var debug_allocator: std.heap.DebugAllocator(.{}) = .init; const Column = struct { children: []vxfw.Widget, pub fn widget(self: *Model) vxfw.Widget { return .{ .userdata = self, .eventHandler = @This().typeErasedEventHandler, .drawFn = @This().typeErasedDrawFn, }; } fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { _ = ptr; _ = ctx; _ = event; } fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { const self: *@This() = @ptrCast(@alignCast(ptr)); const max_size = ctx.max.size(); const children = try ctx.arena.alloc(vxfw.SubSurface, self.children.len); for (self.children, 0..) |child, n| { children[n] = child.draw(ctx); } return .{ .size = max_size, .widget = self.widget(), .buffer = &.{}, .children = children, }; } }; const Revset = struct { revset_str: []const u8, }; const Model = struct { count: u32 = 0, header: Revset, button: vxfw.Button, pub fn widget(self: *Model) vxfw.Widget { return .{ .userdata = self, .eventHandler = Model.typeErasedEventHandler, .drawFn = Model.typeErasedDrawFn, }; } fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { const self: *Model = @ptrCast(@alignCast(ptr)); switch (event) { .init => return ctx.requestFocus(self.button.widget()), .key_press => |key| { if (key.matches('q', .{})) { ctx.quit = true; return; } if (key.matches('c', .{ .ctrl = true })) { ctx.quit = true; return; } }, .focus_in => return ctx.requestFocus(self.button.widget()), else => {}, } } fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { const self: *Model = @ptrCast(@alignCast(ptr)); const max_size = ctx.max.size(); const count_text = try std.fmt.allocPrint(ctx.arena, "{d}", .{self.count}); const text: vxfw.Text = .{ .text = count_text }; const header: vxfw.Text = .{ .text = self.header.revset_str }; const header_child: vxfw.SubSurface = .{ .origin = .{ .row = 0, .col = 0 }, .surface = try header.draw(ctx), }; const text_child: vxfw.SubSurface = .{ .origin = .{ .row = 2, .col = 0 }, .surface = try text.draw(ctx), }; const button_child: vxfw.SubSurface = .{ .origin = .{ .row = 4, .col = 2 }, .surface = try self.button.draw(ctx.withConstraints(ctx.min, .{ .width = 16, .height = 3 })), }; const children = try ctx.arena.alloc(vxfw.SubSurface, 3); children[0] = header_child; children[1] = text_child; children[2] = button_child; return .{ .size = max_size, .widget = self.widget(), .buffer = &.{}, .children = children, }; } fn onClick(maybe_ptr: ?*anyopaque, ctx: *vxfw.EventContext) anyerror!void { const ptr = maybe_ptr orelse return; const self: *Model = @ptrCast(@alignCast(ptr)); self.count += 1; return ctx.consumeAndRedraw(); } }; pub fn main() !void { var gpa, const is_debug = gpa: { break :gpa switch (builtin.mode) { .Debug, .ReleaseSafe => .{ debug_allocator, true }, .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, }; }; defer if (is_debug) { _ = debug_allocator.deinit(); }; const allocator: Allocator = gpa.allocator(); var app = try vxfw.App.init(allocator); defer app.deinit(); const model = try allocator.create(Model); defer allocator.destroy(model); model.* = .{ .count = 0, .header = .{ .revset_str = "hello" }, .button = .{ .label = "Click me!", .onClick = Model.onClick, .userdata = model, }, }; try app.run(model.widget(), .{}); }