a modern tui library written in zig

vxfw: improve .mouse_leave delivery

Changed files
+84 -6
examples
src
vxfw
+73
examples/split_view.zig
··· 1 + const std = @import("std"); 2 + const vaxis = @import("vaxis"); 3 + const vxfw = vaxis.vxfw; 4 + 5 + const Model = struct { 6 + split: vxfw.SplitView, 7 + lhs: vxfw.Text, 8 + rhs: vxfw.Text, 9 + children: [1]vxfw.SubSurface = undefined, 10 + 11 + pub fn widget(self: *Model) vxfw.Widget { 12 + return .{ 13 + .userdata = self, 14 + .eventHandler = Model.typeErasedEventHandler, 15 + .drawFn = Model.typeErasedDrawFn, 16 + }; 17 + } 18 + 19 + fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { 20 + const self: *Model = @ptrCast(@alignCast(ptr)); 21 + switch (event) { 22 + .init => { 23 + self.split.lhs = self.lhs.widget(); 24 + self.split.rhs = self.rhs.widget(); 25 + }, 26 + .key_press => |key| { 27 + if (key.matches('c', .{ .ctrl = true })) { 28 + ctx.quit = true; 29 + return; 30 + } 31 + }, 32 + else => {}, 33 + } 34 + } 35 + 36 + fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface { 37 + const self: *Model = @ptrCast(@alignCast(ptr)); 38 + const surf = try self.split.widget().draw(ctx); 39 + self.children[0] = .{ 40 + .surface = surf, 41 + .origin = .{ .row = 0, .col = 0 }, 42 + }; 43 + return .{ 44 + .size = ctx.max.size(), 45 + .widget = self.widget(), 46 + .buffer = &.{}, 47 + .children = &self.children, 48 + }; 49 + } 50 + }; 51 + 52 + pub fn main() !void { 53 + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 54 + defer _ = gpa.deinit(); 55 + 56 + const allocator = gpa.allocator(); 57 + 58 + var app = try vxfw.App.init(allocator); 59 + defer app.deinit(); 60 + 61 + const model = try allocator.create(Model); 62 + defer allocator.destroy(model); 63 + model.* = .{ 64 + .lhs = .{ .text = "Left hand side" }, 65 + .rhs = .{ .text = "right hand side" }, 66 + .split = .{ .lhs = undefined, .rhs = undefined, .width = 10 }, 67 + }; 68 + 69 + model.split.lhs = model.lhs.widget(); 70 + model.split.rhs = model.rhs.widget(); 71 + 72 + try app.run(model.widget(), .{}); 73 + }
+11 -6
src/vxfw/App.zig
··· 240 240 if (sub.containsPoint(mouse_point)) { 241 241 try last_frame.hitTest(&hits, mouse_point); 242 242 } 243 + 244 + // See if our new hit test contains our last handler. If it doesn't we'll send a mouse_leave 245 + // event 246 + if (self.maybe_last_handler) |last_handler| { 247 + for (hits.items) |item| { 248 + if (item.widget.eql(last_handler)) break; 249 + } else { 250 + try last_handler.handleEvent(ctx, .mouse_leave); 251 + try app.handleCommand(&ctx.cmds); 252 + } 253 + } 243 254 while (hits.popOrNull()) |item| { 244 255 var m_local = mouse; 245 256 m_local.col = item.local.col; ··· 250 261 // If the event wasn't consumed, we keep passing it on 251 262 if (!ctx.consume_event) continue; 252 263 253 - if (self.maybe_last_handler) |last_mouse_handler| { 254 - if (!last_mouse_handler.eql(item.widget)) { 255 - try last_mouse_handler.handleEvent(ctx, .mouse_leave); 256 - try app.handleCommand(&ctx.cmds); 257 - } 258 - } 259 264 self.maybe_last_handler = item.widget; 260 265 return; 261 266 }