refactor: compare widgets via the .ptr memeber instead of via their addresses

Widget is a handle type. Sort of a smart pointer. Comparing their addresses
is brittle because it requires keeping Widget pointers stable. This is
nonsense because Widget identity is actually determined by the actual
widget object it points to.

This big refactor elimits the requirement that Widget addresses remain
stable to work properly with Widget.walk and Widget.get.
This commit is contained in:
CJ van den Berg 2026-02-23 21:44:24 +01:00
parent 2266c92eab
commit cdd6fee9d6
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
9 changed files with 59 additions and 52 deletions

View file

@ -186,7 +186,14 @@ pub fn State(ctx_type: type) type {
}
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool {
return self.menu.walk(walk_ctx, f, if (self.frame_widget) |*frame| frame else &self.container_widget);
for (self.menu.widgets.items) |*w|
if (w.widget.walk(walk_ctx, f))
return true;
return if (self.frame_widget) |frame|
frame.walk(walk_ctx, f)
else
self.container_widget.walk(walk_ctx, f);
}
pub fn count(self: *Self) usize {

View file

@ -21,7 +21,7 @@ vtable: *const VTable,
const Self = @This();
pub const WalkFn = *const fn (ctx: *anyopaque, w: *Self) bool;
pub const WalkFn = *const fn (ctx: *anyopaque, w: Self) bool;
pub const Direction = enum { horizontal, vertical };
pub const Layout = union(enum) {
@ -51,8 +51,8 @@ pub const VTable = struct {
layout: *const fn (ctx: *anyopaque) Layout,
subscribe: *const fn (ctx: *anyopaque, h: EventHandler) error{NotSupported}!void,
unsubscribe: *const fn (ctx: *anyopaque, h: EventHandler) error{NotSupported}!void,
get: *const fn (ctx: *const anyopaque, name_: []const u8) ?*const Self,
walk: *const fn (ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn, self_widget: *Self) bool,
get: *const fn (ctx: *const anyopaque, name_: []const u8) ?Self,
walk: *const fn (ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn) bool,
focus: *const fn (ctx: *anyopaque) void,
unfocus: *const fn (ctx: *anyopaque) void,
hover: *const fn (ctx: *const anyopaque) bool,
@ -134,13 +134,13 @@ pub fn to(pimpl: anytype) Self {
}
}.unsubscribe,
.get = struct {
pub fn get(ctx: *const anyopaque, name_: []const u8) ?*const Self {
pub fn get(ctx: *const anyopaque, name_: []const u8) ?Self {
return if (comptime @hasDecl(child, "get")) child.get(@as(*const child, @ptrCast(@alignCast(ctx))), name_) else null;
}
}.get,
.walk = struct {
pub fn walk(ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn, self: *Self) bool {
return if (comptime @hasDecl(child, "walk")) child.walk(@as(*child, @ptrCast(@alignCast(ctx))), walk_ctx, f, self) else false;
pub fn walk(ctx: *anyopaque, walk_ctx: *anyopaque, f: WalkFn) bool {
return if (comptime @hasDecl(child, "walk")) child.walk(@as(*child, @ptrCast(@alignCast(ctx))), walk_ctx, f) else false;
}
}.walk,
.focus = struct {
@ -225,16 +225,16 @@ pub fn unsubscribe(self: Self, h: EventHandler) !void {
return self.vtable.unsubscribe(self.ptr, h);
}
pub fn get(self: *const Self, name_: []const u8) ?*const Self {
pub fn get(self: *const Self, name_: []const u8) ?Self {
var buf: [256]u8 = undefined;
return if (std.mem.eql(u8, self.plane.name(&buf), name_))
self
self.*
else
self.vtable.get(self.ptr, name_);
}
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: WalkFn) bool {
return if (self.vtable.walk(self.ptr, walk_ctx, f, self)) true else f(walk_ctx, self);
pub fn walk(self: *const Self, walk_ctx: *anyopaque, f: WalkFn) bool {
return if (self.vtable.walk(self.ptr, walk_ctx, f)) true else f(walk_ctx, self.*);
}
pub fn focus(self: *Self) void {
@ -300,12 +300,12 @@ pub fn empty(allocator: Allocator, parent: Plane, layout_: Layout) !Self {
}
}.unsubscribe,
.get = struct {
pub fn get(_: *const anyopaque, _: []const u8) ?*const Self {
pub fn get(_: *const anyopaque, _: []const u8) ?Self {
return null;
}
}.get,
.walk = struct {
pub fn walk(_: *anyopaque, _: *anyopaque, _: WalkFn, _: *Self) bool {
pub fn walk(_: *anyopaque, _: *anyopaque, _: WalkFn) bool {
return false;
}
}.walk,

View file

@ -115,7 +115,7 @@ pub fn addP(self: *Self, w_: Widget) !*Widget {
return &w.widget;
}
pub fn get(self: *const Self, name_: []const u8) ?*const Widget {
pub fn get(self: *const Self, name_: []const u8) ?Widget {
for (self.widgets.items) |*w|
if (w.widget.get(name_)) |p|
return p;
@ -347,10 +347,10 @@ fn do_resize(self: *Self, padding: Widget.Style.Margin) void {
}
}
pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, self_w: *Widget) bool {
pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn) bool {
for (self.widgets.items) |*w|
if (w.widget.walk(ctx, f)) return true;
return f(ctx, self_w);
return f(ctx, Widget.to(self));
}
pub fn focus(self: *Self) void {

View file

@ -38,7 +38,7 @@ highlight: bool,
symbols: bool,
width: usize,
editor: *ed.Editor,
editor_widget: ?*const Widget = null,
editor_widget: ?Widget = null,
differ: diffz.AsyncDiffer,
const Self = @This();

View file

@ -107,8 +107,8 @@ pub fn handle_resize(self: *Self, pos: Widget.Box) void {
self.update_scrollbar();
}
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool {
return self.menu.container_widget.walk(walk_ctx, f) or f(walk_ctx, w);
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool {
return self.menu.container_widget.walk(walk_ctx, f) or f(walk_ctx, Widget.to(self));
}
pub fn add_item(self: *Self, entry_: Entry) !void {

View file

@ -201,8 +201,8 @@ pub fn update(self: *Self) void {
self.menu.update();
}
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool {
return self.menu.walk(walk_ctx, f) or f(walk_ctx, w);
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool {
return self.menu.walk(walk_ctx, f) or f(walk_ctx, Widget.to(self));
}
pub fn receive(_: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {

View file

@ -1732,8 +1732,8 @@ pub fn get_active_buffer(self: *Self) ?*Buffer {
return if (self.get_active_editor()) |editor| editor.buffer orelse null else null;
}
pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) bool {
return self.floating_views.walk(ctx, f) or self.widgets.walk(ctx, f, &self.widgets_widget) or f(ctx, w);
pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn) bool {
return self.floating_views.walk(ctx, f) or self.widgets.walk(ctx, f) or f(ctx, Widget.to(self));
}
fn close_all_editors(self: *Self) !void {
@ -1754,12 +1754,12 @@ fn add_and_activate_view(self: *Self, widget: Widget) !void {
_ = try self.widgets_widget.msg(.{"splits_updated"});
}
pub fn find_view_for_widget(self: *Self, w_: *const Widget) ?usize {
pub fn find_view_for_widget(self: *Self, w_: Widget) ?usize {
const Ctx = struct {
w: *const Widget,
fn find(ctx_: *anyopaque, w: *Widget) bool {
w: Widget,
fn find(ctx_: *anyopaque, w: Widget) bool {
const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_)));
return ctx.w == w;
return ctx.w.ptr == w.ptr;
}
};
var ctx: Ctx = .{ .w = w_ };
@ -1768,7 +1768,7 @@ pub fn find_view_for_widget(self: *Self, w_: *const Widget) ?usize {
return null;
}
pub fn focus_view_by_widget(self: *Self, w: *const Widget) tui.FocusAction {
pub fn focus_view_by_widget(self: *Self, w: Widget) tui.FocusAction {
const n = self.find_view_for_widget(w) orelse return .notfound;
if (n >= self.views.widgets.items.len) return .notfound;
if (n == self.active_view) return .same;

View file

@ -286,11 +286,11 @@ pub const TabBar = struct {
self.plane = self.splits_list.plane;
}
pub fn get(self: *const Self, name: []const u8) ?*const Widget {
pub fn get(self: *const Self, name: []const u8) ?Widget {
return self.splits_list_widget.get(name);
}
pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn, self_w: *Widget) bool {
pub fn walk(self: *Self, ctx: *anyopaque, f: Widget.WalkFn) bool {
for (self.tabs) |*tab| {
const clipped, _ = self.is_tab_clipped(tab);
if (!clipped)
@ -300,14 +300,14 @@ pub const TabBar = struct {
for (split.widgets.items) |*widget_state| if (widget_state.widget.dynamic_cast(drop_target.ButtonType)) |_| {
if (widget_state.widget.walk(ctx, f)) return true;
};
return f(ctx, self_w);
return f(ctx, Widget.to(self));
}
pub fn hover(self: *Self) bool {
return self.splits_list_widget.hover();
}
fn update_tabs(self: *Self, drag_source: ?*Widget) !void {
fn update_tabs(self: *Self, drag_source: ?Widget) !bool {
const buffers_changed = try self.update_tab_buffers();
const dragging = for (self.tabs) |*tab| {
if (tab.widget.dynamic_cast(Tab.ButtonType)) |btn|

View file

@ -54,12 +54,12 @@ input_mode_outer_: ?Mode = null,
input_listeners_: EventHandler.List,
keyboard_focus: ?Widget = null,
mini_mode_: ?MiniMode = null,
hover_focus: ?*Widget = null,
hover_focus: ?Widget = null,
last_hover_x: c_int = -1,
last_hover_y: c_int = -1,
commands: Commands = undefined,
logger: log.Logger,
drag_source: ?*Widget = null,
drag_source: ?Widget = null,
drag_button: input.MouseType = 0,
dark_theme: Widget.Theme,
dark_parsed_theme: ?std.json.Parsed(Widget.Theme),
@ -787,12 +787,12 @@ fn handle_system_clipboard(self: *Self, text: []const u8) !void {
return command.executeName("paste", command.fmt(.{text}));
}
fn find_coord_widget(self: *Self, y: usize, x: usize) ?*Widget {
fn find_coord_widget(self: *Self, y: usize, x: usize) ?Widget {
const Ctx = struct {
widget: ?*Widget = null,
widget: ?Widget = null,
y: usize,
x: usize,
fn find(ctx_: *anyopaque, w: *Widget) bool {
fn find(ctx_: *anyopaque, w: Widget) bool {
const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_)));
if (w.box().is_abs_coord_inside(ctx.y, ctx.x)) {
ctx.widget = w;
@ -810,12 +810,12 @@ pub fn is_abs_coord_in_widget(w: *const Widget, y: usize, x: usize) bool {
return w.box().is_abs_coord_inside(y, x);
}
fn is_live_widget_ptr(self: *Self, w_: *Widget) bool {
fn is_live_widget_ptr(self: *Self, w_: Widget) bool {
const Ctx = struct {
w: *Widget,
fn find(ctx_: *anyopaque, w: *Widget) bool {
w: Widget,
fn find(ctx_: *anyopaque, w: Widget) bool {
const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_)));
return ctx.w == w;
return ctx.w.ptr == w.ptr;
}
};
var ctx: Ctx = .{ .w = w_ };
@ -824,7 +824,7 @@ fn is_live_widget_ptr(self: *Self, w_: *Widget) bool {
pub const FocusAction = enum { same, changed, notfound };
pub fn set_focus_by_widget(w: *const Widget) FocusAction {
pub fn set_focus_by_widget(w: Widget) FocusAction {
const mv = mainview() orelse return .notfound;
return mv.focus_view_by_widget(w);
}
@ -869,12 +869,12 @@ fn send_mouse_drag(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.mess
if (self.drag_source) |w| _ = try w.send(from, m);
}
fn update_hover(self: *Self, y: c_int, x: c_int) !?*Widget {
fn update_hover(self: *Self, y: c_int, x: c_int) !?Widget {
self.last_hover_y = y;
self.last_hover_x = x;
if (y >= 0 and x >= 0) if (self.find_coord_widget(@intCast(y), @intCast(x))) |w| {
if (if (self.hover_focus) |h| h != w else true) {
tp.trace(tp.channel.debug, .{ "update_hover", if (self.hover_focus) |h| @intFromPtr(h) else 0, @intFromPtr(w) });
if (if (self.hover_focus) |h| h.ptr != w.ptr else true) {
tp.trace(tp.channel.debug, .{ "update_hover", if (self.hover_focus) |h| @as(u64, @intFromPtr(h.ptr)) else 0, @as(u64, @intFromPtr(w.ptr)) });
if (self.hover_focus) |h| if (self.is_live_widget_ptr(h))
try send_hover_msg(h, false);
self.hover_focus = w;
@ -889,19 +889,19 @@ fn update_hover(self: *Self, y: c_int, x: c_int) !?*Widget {
fn clear_hover_focus(self: *Self, src: std.builtin.SourceLocation) tp.result {
if (self.hover_focus) |h| if (self.is_live_widget_ptr(h))
try send_hover_msg(h, false);
tp.trace(tp.channel.debug, .{ "tui", "clear_hover_focus", if (self.hover_focus) |h| @intFromPtr(h) else 0, src.fn_name, src.file, src.line });
tp.trace(tp.channel.debug, .{ "tui", "clear_hover_focus", if (self.hover_focus) |h| @intFromPtr(h.ptr) else 0, src.fn_name, src.file, src.line });
self.hover_focus = null;
}
fn send_hover_msg(widget: *const Widget, hover: bool) tp.result {
fn send_hover_msg(widget: Widget, hover: bool) tp.result {
var buf: [256]u8 = undefined;
tp.trace(tp.channel.debug, .{ "hover_msg", @intFromPtr(widget), hover });
tp.trace(tp.channel.debug, .{ "hover_msg", @intFromPtr(widget.ptr), hover });
_ = try widget.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{ "H", hover }) catch |e| return tp.exit_error(e, @errorReturnTrace()));
}
pub fn refresh_hover(src: std.builtin.SourceLocation) void {
const self = current();
tp.trace(tp.channel.debug, .{ "tui", "refresh_hover", if (self.hover_focus) |h| @intFromPtr(h) else 0, src.fn_name, src.file, src.line });
tp.trace(tp.channel.debug, .{ "tui", "refresh_hover", if (self.hover_focus) |h| @intFromPtr(h.ptr) else 0, src.fn_name, src.file, src.line });
_ = self.update_hover(self.last_hover_y, self.last_hover_x) catch {};
}
@ -1906,18 +1906,18 @@ pub fn get_keybind_mode() ?Mode {
return self.input_mode_ orelse self.delayed_init_input_mode;
}
pub fn update_drag_source(drag_source: *Widget, btn: input.MouseType) void {
pub fn update_drag_source(drag_source: Widget, btn: input.MouseType) void {
const self = current();
self.drag_source = drag_source;
self.drag_button = btn;
}
fn set_drag_source(self: *Self, drag_source: ?*Widget, btn: input.MouseType) void {
fn set_drag_source(self: *Self, drag_source: ?Widget, btn: input.MouseType) void {
self.drag_source = drag_source;
self.drag_button = btn;
}
pub fn get_drag_source() struct { ?*Widget, input.MouseType } {
pub fn get_drag_source() struct { ?Widget, input.MouseType } {
const self = current();
return .{ self.drag_source, self.drag_button };
}