this repo has no description
13
fork

Configure Feed

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

at 990fe29b38e57de69733025ab580b8917c2d092f 250 lines 8.7 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 self.width = @min(last_max -| self.min_width, last_max -| mouse.col -| 1); 92 if (self.max_width) |max| { 93 self.width = @max(self.width, max); 94 } 95 }, 96 } 97 ctx.consume_event = true; 98 } 99} 100 101fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 102 const self: *SplitView = @ptrCast(@alignCast(ptr)); 103 // Fills entire space 104 const max = ctx.max.size(); 105 // Constrain width to the max 106 self.width = @min(self.width, max.width); 107 self.last_max_width = max.width; 108 109 // The constrained side is equal to the width 110 const constrained_min: vxfw.Size = .{ .width = self.width, .height = max.height }; 111 const constrained_max = vxfw.MaxSize.fromSize(constrained_min); 112 113 const unconstrained_min: vxfw.Size = .{ .width = max.width -| self.width -| 1, .height = max.height }; 114 const unconstrained_max = vxfw.MaxSize.fromSize(unconstrained_min); 115 116 var children = try std.ArrayList(vxfw.SubSurface).initCapacity(ctx.arena, 2); 117 118 switch (self.constrain) { 119 .lhs => { 120 if (constrained_max.width.? > 0 and constrained_max.height.? > 0) { 121 const lhs_ctx = ctx.withConstraints(constrained_min, constrained_max); 122 const lhs_surface = try self.lhs.draw(lhs_ctx); 123 children.appendAssumeCapacity(.{ 124 .surface = lhs_surface, 125 .origin = .{ .row = 0, .col = 0 }, 126 }); 127 } 128 if (unconstrained_max.width.? > 0 and unconstrained_max.height.? > 0) { 129 const rhs_ctx = ctx.withConstraints(unconstrained_min, unconstrained_max); 130 const rhs_surface = try self.rhs.draw(rhs_ctx); 131 children.appendAssumeCapacity(.{ 132 .surface = rhs_surface, 133 .origin = .{ .row = 0, .col = self.width + 1 }, 134 }); 135 } 136 var surface = try vxfw.Surface.initWithChildren( 137 ctx.arena, 138 self.widget(), 139 max, 140 children.items, 141 ); 142 for (0..max.height) |row| { 143 surface.writeCell(self.width, @intCast(row), .{ 144 .char = .{ .grapheme = "", .width = 1 }, 145 .style = self.style, 146 }); 147 } 148 return surface; 149 }, 150 .rhs => { 151 if (unconstrained_max.width.? > 0 and unconstrained_max.height.? > 0) { 152 const lhs_ctx = ctx.withConstraints(unconstrained_min, unconstrained_max); 153 const lhs_surface = try self.lhs.draw(lhs_ctx); 154 children.appendAssumeCapacity(.{ 155 .surface = lhs_surface, 156 .origin = .{ .row = 0, .col = 0 }, 157 }); 158 } 159 if (constrained_max.width.? > 0 and constrained_max.height.? > 0) { 160 const rhs_ctx = ctx.withConstraints(constrained_min, constrained_max); 161 const rhs_surface = try self.rhs.draw(rhs_ctx); 162 children.appendAssumeCapacity(.{ 163 .surface = rhs_surface, 164 .origin = .{ .row = 0, .col = unconstrained_max.width.? + 1 }, 165 }); 166 } 167 var surface = try vxfw.Surface.initWithChildren( 168 ctx.arena, 169 self.widget(), 170 max, 171 children.items, 172 ); 173 for (0..max.height) |row| { 174 surface.writeCell(max.width -| self.width -| 1, @intCast(row), .{ 175 .char = .{ .grapheme = "", .width = 1 }, 176 .style = self.style, 177 }); 178 } 179 return surface; 180 }, 181 } 182} 183 184test SplitView { 185 // Boiler plate draw context 186 var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 187 defer arena.deinit(); 188 const ucd = try vaxis.Unicode.init(arena.allocator()); 189 vxfw.DrawContext.init(&ucd, .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 = split_view.width, 223 .row = 0, 224 .type = .press, 225 .button = .left, 226 .mods = .{}, 227 }; 228 229 var ctx: vxfw.EventContext = .{ 230 .alloc = arena.allocator(), 231 .cmds = .empty, 232 }; 233 try split_widget.handleEvent(&ctx, .{ .mouse = mouse }); 234 // We should get a command to change the mouse shape 235 try std.testing.expect(ctx.cmds.items[0] == .set_mouse_shape); 236 try std.testing.expect(ctx.redraw); 237 try std.testing.expect(split_view.pressed); 238 239 // If we move the mouse, we should update the width 240 mouse.col = 2; 241 mouse.type = .drag; 242 try split_widget.handleEvent(&ctx, .{ .mouse = mouse }); 243 try std.testing.expect(ctx.redraw); 244 try std.testing.expect(split_view.pressed); 245 try std.testing.expectEqual(mouse.col, split_view.width); 246} 247 248test "refAllDecls" { 249 std.testing.refAllDecls(@This()); 250}