this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

at b698a3641da7f3886bde4aeda7fcecc8d3642580 215 lines 6.7 kB view raw
1const std = @import("std"); 2const vaxis = @import("../main.zig"); 3 4const vxfw = @import("vxfw.zig"); 5 6const Allocator = std.mem.Allocator; 7 8const Center = @import("Center.zig"); 9const Text = @import("Text.zig"); 10 11const Button = @This(); 12 13// User supplied values 14label: []const u8, 15onClick: *const fn (?*anyopaque, ctx: *vxfw.EventContext) anyerror!void, 16userdata: ?*anyopaque = null, 17 18// Styles 19style: struct { 20 default: vaxis.Style = .{ .reverse = true }, 21 mouse_down: vaxis.Style = .{ .fg = .{ .index = 4 }, .reverse = true }, 22 hover: vaxis.Style = .{ .fg = .{ .index = 3 }, .reverse = true }, 23 focus: vaxis.Style = .{ .fg = .{ .index = 5 }, .reverse = true }, 24} = .{}, 25 26// State 27mouse_down: bool = false, 28has_mouse: bool = false, 29focused: bool = false, 30 31pub fn widget(self: *Button) vxfw.Widget { 32 return .{ 33 .userdata = self, 34 .eventHandler = typeErasedEventHandler, 35 .drawFn = typeErasedDrawFn, 36 }; 37} 38 39fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { 40 const self: *Button = @ptrCast(@alignCast(ptr)); 41 return self.handleEvent(ctx, event); 42} 43 44pub fn handleEvent(self: *Button, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { 45 switch (event) { 46 .key_press => |key| { 47 if (key.matches(vaxis.Key.enter, .{}) or key.matches('j', .{ .ctrl = true })) { 48 return self.doClick(ctx); 49 } 50 }, 51 .mouse => |mouse| { 52 if (self.mouse_down and mouse.type == .release) { 53 self.mouse_down = false; 54 return self.doClick(ctx); 55 } 56 if (mouse.type == .press and mouse.button == .left) { 57 self.mouse_down = true; 58 return ctx.consumeAndRedraw(); 59 } 60 return ctx.consumeEvent(); 61 }, 62 .mouse_enter => { 63 // implicit redraw 64 self.has_mouse = true; 65 try ctx.setMouseShape(.pointer); 66 return ctx.consumeAndRedraw(); 67 }, 68 .mouse_leave => { 69 self.has_mouse = false; 70 self.mouse_down = false; 71 // implicit redraw 72 try ctx.setMouseShape(.default); 73 }, 74 .focus_in => { 75 self.focused = true; 76 ctx.redraw = true; 77 }, 78 .focus_out => { 79 self.focused = false; 80 ctx.redraw = true; 81 }, 82 else => {}, 83 } 84} 85 86fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 87 const self: *Button = @ptrCast(@alignCast(ptr)); 88 return self.draw(ctx); 89} 90 91pub fn draw(self: *Button, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 92 const style: vaxis.Style = if (self.mouse_down) 93 self.style.mouse_down 94 else if (self.has_mouse) 95 self.style.hover 96 else if (self.focused) 97 self.style.focus 98 else 99 self.style.default; 100 101 const text: Text = .{ 102 .style = style, 103 .text = self.label, 104 .text_align = .center, 105 }; 106 107 const center: Center = .{ .child = text.widget() }; 108 const surf = try center.draw(ctx); 109 110 const button_surf = try vxfw.Surface.initWithChildren(ctx.arena, self.widget(), surf.size, surf.children); 111 @memset(button_surf.buffer, .{ .style = style }); 112 return button_surf; 113} 114 115fn doClick(self: *Button, ctx: *vxfw.EventContext) anyerror!void { 116 try self.onClick(self.userdata, ctx); 117 ctx.consume_event = true; 118} 119 120test Button { 121 // Create some object which reacts to a button press 122 const Foo = struct { 123 count: u8, 124 125 fn onClick(ptr: ?*anyopaque, ctx: *vxfw.EventContext) anyerror!void { 126 const foo: *@This() = @ptrCast(@alignCast(ptr)); 127 foo.count +|= 1; 128 ctx.consumeAndRedraw(); 129 } 130 }; 131 var foo: Foo = .{ .count = 0 }; 132 133 var button: Button = .{ 134 .label = "Test Button", 135 .onClick = Foo.onClick, 136 .userdata = &foo, 137 }; 138 139 // Event handlers need a context 140 var ctx: vxfw.EventContext = .{ 141 .alloc = std.testing.allocator, 142 .cmds = .empty, 143 }; 144 defer ctx.cmds.deinit(ctx.alloc); 145 146 // Get the widget interface 147 const b_widget = button.widget(); 148 149 // Create a synthetic mouse event 150 var mouse_event: vaxis.Mouse = .{ 151 .col = 0, 152 .row = 0, 153 .mods = .{}, 154 .button = .left, 155 .type = .press, 156 }; 157 // Send the button a mouse press event 158 try b_widget.handleEvent(&ctx, .{ .mouse = mouse_event }); 159 160 // A press alone doesn't trigger onClick 161 try std.testing.expectEqual(0, foo.count); 162 163 // Send the button a mouse release event. The onClick handler is called 164 mouse_event.type = .release; 165 try b_widget.handleEvent(&ctx, .{ .mouse = mouse_event }); 166 try std.testing.expectEqual(1, foo.count); 167 168 // Send it another press 169 mouse_event.type = .press; 170 try b_widget.handleEvent(&ctx, .{ .mouse = mouse_event }); 171 172 // Now the mouse leaves 173 try b_widget.handleEvent(&ctx, .mouse_leave); 174 175 // Then it comes back. We don't know it but the button was pressed outside of our widget. We 176 // receie the release event 177 mouse_event.type = .release; 178 try b_widget.handleEvent(&ctx, .{ .mouse = mouse_event }); 179 180 // But we didn't have the press registered, so we don't call onClick 181 try std.testing.expectEqual(1, foo.count); 182 183 // Now we receive an enter keypress. This also triggers the onClick handler 184 try b_widget.handleEvent(&ctx, .{ .key_press = .{ .codepoint = vaxis.Key.enter } }); 185 try std.testing.expectEqual(2, foo.count); 186 187 // Now we draw the button. Set up our context with some unicode data 188 var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 189 defer arena.deinit(); 190 const ucd = try vaxis.Unicode.init(arena.allocator()); 191 vxfw.DrawContext.init(&ucd, .unicode); 192 193 const draw_ctx: vxfw.DrawContext = .{ 194 .arena = arena.allocator(), 195 .min = .{}, 196 .max = .{ .width = 13, .height = 3 }, 197 .cell_size = .{ .width = 10, .height = 20 }, 198 }; 199 const surface = try b_widget.draw(draw_ctx); 200 201 // The button should fill the available space. 202 try std.testing.expectEqual(surface.size.width, draw_ctx.max.width.?); 203 try std.testing.expectEqual(surface.size.height, draw_ctx.max.height.?); 204 205 // It should have one child, the label 206 try std.testing.expectEqual(1, surface.children.len); 207 208 // The label should be centered 209 try std.testing.expectEqual(1, surface.children[0].origin.row); 210 try std.testing.expectEqual(1, surface.children[0].origin.col); 211} 212 213test "refAllDecls" { 214 std.testing.refAllDecls(@This()); 215}