this repo has no description
13
fork

Configure Feed

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

at 5e39d991f73f59df546c0e0c467a34c2df4e0822 252 lines 8.8 kB view raw
1const std = @import("std"); 2const vaxis = @import("../main.zig"); 3 4const Allocator = std.mem.Allocator; 5 6const vxfw = @import("vxfw.zig"); 7 8const SplitView = @This(); 9 10lhs: vxfw.Widget, 11rhs: vxfw.Widget, 12constrain: enum { lhs, rhs } = .lhs, 13style: vaxis.Style = .{}, 14/// min width for the constrained side 15min_width: u16 = 0, 16/// max width for the constrained side 17max_width: ?u16 = null, 18/// Target width to draw at 19width: u16, 20 21/// Used to calculate mouse events when our constraint is rhs 22last_max_width: ?u16 = null, 23 24// State 25pressed: bool = false, 26mouse_set: bool = false, 27 28pub fn widget(self: *const SplitView) vxfw.Widget { 29 return .{ 30 .userdata = @constCast(self), 31 .eventHandler = typeErasedEventHandler, 32 .drawFn = typeErasedDrawFn, 33 }; 34} 35 36fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { 37 const self: *SplitView = @ptrCast(@alignCast(ptr)); 38 switch (event) { 39 .mouse_leave => { 40 self.pressed = false; 41 return; 42 }, 43 .mouse => {}, 44 else => return, 45 } 46 const mouse = event.mouse; 47 48 const separator_col: u16 = switch (self.constrain) { 49 .lhs => self.width, 50 .rhs => if (self.last_max_width) |max| 51 max -| self.width -| 1 52 else { 53 ctx.redraw = true; 54 return; 55 }, 56 }; 57 58 // If we are on the separator, we always set the mouse shape 59 if (mouse.col == separator_col) { 60 try ctx.setMouseShape(.@"ew-resize"); 61 self.mouse_set = true; 62 // Set pressed state if we are a left click 63 if (mouse.type == .press and mouse.button == .left) { 64 self.pressed = true; 65 } 66 } else if (self.mouse_set) { 67 // If we have set the mouse state and *aren't* over the separator, default the mouse state 68 try ctx.setMouseShape(.default); 69 self.mouse_set = false; 70 } 71 72 // On release, we reset state 73 if (mouse.type == .release) { 74 self.pressed = false; 75 self.mouse_set = false; 76 try ctx.setMouseShape(.default); 77 } 78 79 // If pressed, we always keep the mouse shape and we update the width 80 if (self.pressed) { 81 try ctx.setMouseShape(.@"ew-resize"); 82 switch (self.constrain) { 83 .lhs => { 84 self.width = @max(self.min_width, mouse.col); 85 if (self.max_width) |max| { 86 self.width = @min(self.width, max); 87 } 88 }, 89 .rhs => { 90 const last_max = self.last_max_width orelse return; 91 const mouse_col: u16 = if (mouse.col < 0) 0 else @intCast(mouse.col); 92 self.width = @min(last_max -| self.min_width, last_max -| mouse_col -| 1); 93 if (self.max_width) |max| { 94 self.width = @max(self.width, max); 95 } 96 }, 97 } 98 ctx.consume_event = true; 99 } 100} 101 102fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 103 const self: *SplitView = @ptrCast(@alignCast(ptr)); 104 // Fills entire space 105 const max = ctx.max.size(); 106 // Constrain width to the max 107 self.width = @min(self.width, max.width); 108 self.last_max_width = max.width; 109 110 // The constrained side is equal to the width 111 const constrained_min: vxfw.Size = .{ .width = self.width, .height = max.height }; 112 const constrained_max = vxfw.MaxSize.fromSize(constrained_min); 113 114 const unconstrained_min: vxfw.Size = .{ .width = max.width -| self.width -| 1, .height = max.height }; 115 const unconstrained_max = vxfw.MaxSize.fromSize(unconstrained_min); 116 117 var children = try std.ArrayList(vxfw.SubSurface).initCapacity(ctx.arena, 2); 118 119 switch (self.constrain) { 120 .lhs => { 121 if (constrained_max.width.? > 0 and constrained_max.height.? > 0) { 122 const lhs_ctx = ctx.withConstraints(constrained_min, constrained_max); 123 const lhs_surface = try self.lhs.draw(lhs_ctx); 124 children.appendAssumeCapacity(.{ 125 .surface = lhs_surface, 126 .origin = .{ .row = 0, .col = 0 }, 127 }); 128 } 129 if (unconstrained_max.width.? > 0 and unconstrained_max.height.? > 0) { 130 const rhs_ctx = ctx.withConstraints(unconstrained_min, unconstrained_max); 131 const rhs_surface = try self.rhs.draw(rhs_ctx); 132 children.appendAssumeCapacity(.{ 133 .surface = rhs_surface, 134 .origin = .{ .row = 0, .col = self.width + 1 }, 135 }); 136 } 137 var surface = try vxfw.Surface.initWithChildren( 138 ctx.arena, 139 self.widget(), 140 max, 141 children.items, 142 ); 143 for (0..max.height) |row| { 144 surface.writeCell(self.width, @intCast(row), .{ 145 .char = .{ .grapheme = "", .width = 1 }, 146 .style = self.style, 147 }); 148 } 149 return surface; 150 }, 151 .rhs => { 152 if (unconstrained_max.width.? > 0 and unconstrained_max.height.? > 0) { 153 const lhs_ctx = ctx.withConstraints(unconstrained_min, unconstrained_max); 154 const lhs_surface = try self.lhs.draw(lhs_ctx); 155 children.appendAssumeCapacity(.{ 156 .surface = lhs_surface, 157 .origin = .{ .row = 0, .col = 0 }, 158 }); 159 } 160 if (constrained_max.width.? > 0 and constrained_max.height.? > 0) { 161 const rhs_ctx = ctx.withConstraints(constrained_min, constrained_max); 162 const rhs_surface = try self.rhs.draw(rhs_ctx); 163 children.appendAssumeCapacity(.{ 164 .surface = rhs_surface, 165 .origin = .{ .row = 0, .col = unconstrained_max.width.? + 1 }, 166 }); 167 } 168 var surface = try vxfw.Surface.initWithChildren( 169 ctx.arena, 170 self.widget(), 171 max, 172 children.items, 173 ); 174 for (0..max.height) |row| { 175 surface.writeCell(max.width -| self.width -| 1, @intCast(row), .{ 176 .char = .{ .grapheme = "", .width = 1 }, 177 .style = self.style, 178 }); 179 } 180 return surface; 181 }, 182 } 183} 184 185test SplitView { 186 // Boiler plate draw context 187 var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 188 defer arena.deinit(); 189 vxfw.DrawContext.init(.unicode); 190 191 const draw_ctx: vxfw.DrawContext = .{ 192 .arena = arena.allocator(), 193 .min = .{}, 194 .max = .{ .width = 16, .height = 16 }, 195 .cell_size = .{ .width = 10, .height = 20 }, 196 }; 197 198 // Create LHS and RHS widgets 199 const lhs: vxfw.Text = .{ .text = "Left hand side" }; 200 const rhs: vxfw.Text = .{ .text = "Right hand side" }; 201 202 var split_view: SplitView = .{ 203 .lhs = lhs.widget(), 204 .rhs = rhs.widget(), 205 .width = 8, 206 }; 207 208 const split_widget = split_view.widget(); 209 { 210 const surface = try split_widget.draw(draw_ctx); 211 // SplitView expands to fill the space 212 try std.testing.expectEqual(@as(vxfw.Size, .{ .width = 16, .height = 16 }), surface.size); 213 // It has two children 214 try std.testing.expectEqual(2, surface.children.len); 215 // The left child should have a width = SplitView.width 216 try std.testing.expectEqual(split_view.width, surface.children[0].surface.size.width); 217 } 218 219 // Send the widget a mouse press on the separator 220 var mouse: vaxis.Mouse = .{ 221 // The separator is at width 222 .col = @intCast(split_view.width), 223 .row = 0, 224 .type = .press, 225 .button = .left, 226 .mods = .{}, 227 }; 228 229 var ctx: vxfw.EventContext = .{ 230 .io = std.testing.io, 231 .alloc = arena.allocator(), 232 .cmds = .empty, 233 }; 234 try split_widget.handleEvent(&ctx, .{ .mouse = mouse }); 235 // We should get a command to change the mouse shape 236 try std.testing.expect(ctx.cmds.items[0] == .set_mouse_shape); 237 try std.testing.expect(ctx.redraw); 238 try std.testing.expect(split_view.pressed); 239 240 // If we move the mouse, we should update the width 241 mouse.col = 2; 242 mouse.type = .drag; 243 try split_widget.handleEvent(&ctx, .{ .mouse = mouse }); 244 try std.testing.expect(ctx.redraw); 245 try std.testing.expect(split_view.pressed); 246 const mouse_col: u16 = if (mouse.col < 0) 0 else @intCast(mouse.col); 247 try std.testing.expectEqual(mouse_col, split_view.width); 248} 249 250test "refAllDecls" { 251 std.testing.refAllDecls(@This()); 252}