vxfw(App): implement event capturing phase

This commit is contained in:
Tim Culverhouse 2024-11-01 10:07:23 -05:00
parent a4221bf670
commit b3e6157130
2 changed files with 66 additions and 27 deletions

View file

@ -11,8 +11,6 @@ const Widget = vxfw.Widget;
const App = @This();
quit_key: vaxis.Key = .{ .codepoint = 'c', .mods = .{ .ctrl = true } },
allocator: Allocator,
tty: vaxis.Tty,
vx: vaxis.Vaxis,
@ -85,6 +83,7 @@ pub fn run(self: *App, widget: vxfw.Widget, opts: Options) anyerror!void {
var mouse_handler = MouseHandler.init(widget);
var focus_handler = FocusHandler.init(self.allocator, widget);
focus_handler.intrusiveInit();
try focus_handler.path_to_focused.append(widget);
defer focus_handler.deinit();
// Timestamp of our next frame
@ -92,7 +91,7 @@ pub fn run(self: *App, widget: vxfw.Widget, opts: Options) anyerror!void {
// Create our event context
var ctx: vxfw.EventContext = .{
.phase = .at_target,
.phase = .capturing,
.cmds = vxfw.CommandList.init(self.allocator),
.consume_event = false,
.redraw = false,
@ -114,24 +113,16 @@ pub fn run(self: *App, widget: vxfw.Widget, opts: Options) anyerror!void {
try self.checkTimers(&ctx);
while (loop.tryEvent()) |event| {
ctx.consume_event = false;
defer {
// Reset our context
ctx.consume_event = false;
ctx.phase = .capturing;
ctx.cmds.clearRetainingCapacity();
}
switch (event) {
.key_press => |key| {
.key_press => {
try focus_handler.handleEvent(&ctx, event);
try self.handleCommand(&ctx.cmds);
if (!ctx.consume_event) {
if (key.matches(self.quit_key.codepoint, self.quit_key.mods)) {
ctx.quit = true;
}
if (key.matches(vaxis.Key.tab, .{})) {
try focus_handler.focusNext(&ctx);
try self.handleCommand(&ctx.cmds);
}
if (key.matches(vaxis.Key.tab, .{ .shift = true })) {
try focus_handler.focusPrev(&ctx);
try self.handleCommand(&ctx.cmds);
}
}
},
.focus_out => try mouse_handler.mouseExit(self, &ctx),
.mouse => |mouse| try mouse_handler.handleMouse(self, &ctx, mouse),
@ -141,7 +132,7 @@ pub fn run(self: *App, widget: vxfw.Widget, opts: Options) anyerror!void {
ctx.redraw = true;
},
else => {
try widget.handleEvent(&ctx, event);
try focus_handler.handleEvent(&ctx, event);
try self.handleCommand(&ctx.cmds);
},
}
@ -286,6 +277,7 @@ const FocusHandler = struct {
root: Node,
focused: *Node,
path_to_focused: std.ArrayList(Widget),
maybe_wants_focus: ?vxfw.Widget = null,
const Node = struct {
@ -401,6 +393,7 @@ const FocusHandler = struct {
.focused = undefined,
.arena = std.heap.ArenaAllocator.init(allocator),
.maybe_wants_focus = null,
.path_to_focused = std.ArrayList(Widget).init(allocator),
};
}
@ -421,6 +414,11 @@ const FocusHandler = struct {
for (root.children) |child| {
try self.findFocusableChildren(&self.root, &list, child.surface);
}
self.path_to_focused.clearAndFree();
_ = try childHasFocus(root, &self.path_to_focused, self.focused.widget);
try self.path_to_focused.append(root.widget);
// reverse path_to_focused so that it is root first
std.mem.reverse(Widget, self.path_to_focused.items);
self.root = .{
.widget = root.widget,
.children = list.items,
@ -428,6 +426,27 @@ const FocusHandler = struct {
};
}
/// Returns true if a child of surface is the focused widget
fn childHasFocus(
surface: vxfw.Surface,
list: *std.ArrayList(Widget),
focused: Widget,
) Allocator.Error!bool {
// Check if we are the focused widget
if (focused.eql(surface.widget)) {
try list.append(surface.widget);
return true;
}
for (surface.children) |child| {
// Add child to list if it is the focused widget or one of it's own children is
if (try childHasFocus(child.surface, list, focused)) {
try list.append(surface.widget);
return true;
}
}
return false;
}
/// Walks the surface tree, adding all focusable nodes to list
fn findFocusableChildren(
self: *FocusHandler,
@ -481,11 +500,30 @@ const FocusHandler = struct {
}
fn handleEvent(self: *FocusHandler, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void {
var maybe_node: ?*Node = self.focused;
while (maybe_node) |node| {
try node.widget.handleEvent(ctx, event);
const path = self.path_to_focused.items;
if (path.len == 0) return;
const target_idx = path.len - 1;
// Capturing phase
ctx.phase = .capturing;
for (path[0..target_idx]) |widget| {
try widget.handleEvent(ctx, event);
if (ctx.consume_event) return;
}
// Target phase
ctx.phase = .at_target;
const target = path[target_idx];
try target.handleEvent(ctx, event);
if (ctx.consume_event) return;
// Bubbling phase
ctx.phase = .bubbling;
var iter = std.mem.reverseIterator(path[0..target_idx]);
while (iter.next()) |widget| {
try widget.handleEvent(ctx, event);
if (ctx.consume_event) return;
maybe_node = node.parent;
}
}
};

View file

@ -86,8 +86,7 @@ pub const EventContext = struct {
quit: bool = false,
pub const Phase = enum {
// TODO: Capturing phase
// capturing,
capturing,
at_target,
bubbling,
};
@ -196,11 +195,13 @@ pub const MaxSize = struct {
/// The Widget interface
pub const Widget = struct {
userdata: *anyopaque,
eventHandler: *const fn (userdata: *anyopaque, ctx: *EventContext, event: Event) anyerror!void,
eventHandler: ?*const fn (userdata: *anyopaque, ctx: *EventContext, event: Event) anyerror!void = null,
drawFn: *const fn (userdata: *anyopaque, ctx: DrawContext) Allocator.Error!Surface,
pub fn handleEvent(self: Widget, ctx: *EventContext, event: Event) anyerror!void {
return self.eventHandler(self.userdata, ctx, event);
if (self.eventHandler) |handle| {
return handle(self.userdata, ctx, event);
}
}
pub fn draw(self: Widget, ctx: DrawContext) Allocator.Error!Surface {