From 37aeabc6470aa2e9f7fb5f2d2704714914c2fc24 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Sun, 3 Nov 2024 18:10:19 -0600 Subject: [PATCH] vxfw: improve .mouse_leave delivery --- examples/split_view.zig | 73 +++++++++++++++++++++++++++++++++++++++++ src/vxfw/App.zig | 17 ++++++---- 2 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 examples/split_view.zig diff --git a/examples/split_view.zig b/examples/split_view.zig new file mode 100644 index 0000000..78d4e6f --- /dev/null +++ b/examples/split_view.zig @@ -0,0 +1,73 @@ +const std = @import("std"); +const vaxis = @import("vaxis"); +const vxfw = vaxis.vxfw; + +const Model = struct { + split: vxfw.SplitView, + lhs: vxfw.Text, + rhs: vxfw.Text, + children: [1]vxfw.SubSurface = undefined, + + 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 => { + self.split.lhs = self.lhs.widget(); + self.split.rhs = self.rhs.widget(); + }, + .key_press => |key| { + if (key.matches('c', .{ .ctrl = true })) { + ctx.quit = true; + return; + } + }, + else => {}, + } + } + + fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface { + const self: *Model = @ptrCast(@alignCast(ptr)); + const surf = try self.split.widget().draw(ctx); + self.children[0] = .{ + .surface = surf, + .origin = .{ .row = 0, .col = 0 }, + }; + return .{ + .size = ctx.max.size(), + .widget = self.widget(), + .buffer = &.{}, + .children = &self.children, + }; + } +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + + var app = try vxfw.App.init(allocator); + defer app.deinit(); + + const model = try allocator.create(Model); + defer allocator.destroy(model); + model.* = .{ + .lhs = .{ .text = "Left hand side" }, + .rhs = .{ .text = "right hand side" }, + .split = .{ .lhs = undefined, .rhs = undefined, .width = 10 }, + }; + + model.split.lhs = model.lhs.widget(); + model.split.rhs = model.rhs.widget(); + + try app.run(model.widget(), .{}); +} diff --git a/src/vxfw/App.zig b/src/vxfw/App.zig index c21d86d..4999053 100644 --- a/src/vxfw/App.zig +++ b/src/vxfw/App.zig @@ -240,6 +240,17 @@ const MouseHandler = struct { if (sub.containsPoint(mouse_point)) { try last_frame.hitTest(&hits, mouse_point); } + + // See if our new hit test contains our last handler. If it doesn't we'll send a mouse_leave + // event + if (self.maybe_last_handler) |last_handler| { + for (hits.items) |item| { + if (item.widget.eql(last_handler)) break; + } else { + try last_handler.handleEvent(ctx, .mouse_leave); + try app.handleCommand(&ctx.cmds); + } + } while (hits.popOrNull()) |item| { var m_local = mouse; m_local.col = item.local.col; @@ -250,12 +261,6 @@ const MouseHandler = struct { // If the event wasn't consumed, we keep passing it on if (!ctx.consume_event) continue; - if (self.maybe_last_handler) |last_mouse_handler| { - if (!last_mouse_handler.eql(item.widget)) { - try last_mouse_handler.handleEvent(ctx, .mouse_leave); - try app.handleCommand(&ctx.cmds); - } - } self.maybe_last_handler = item.widget; return; }