@@ -60,18 +60,28 @@ pub fn topdrop(onoff: enum { show, remove }) void {
6060/// provided as btns arg to modal.
6161pub const ModalButtonCallbackFn = * const fn (index : usize ) void ;
6262
63+ /// Context stored in the modal window's user data.
64+ /// This avoids storing a function pointer directly in LVGL user_data (void*),
65+ /// which is not portable/safe and can break across Zig versions/ABIs.
66+ const ModalCtx = struct {
67+ cb : ModalButtonCallbackFn ,
68+ };
69+
6370/// shows a non-dismissible window using the whole screen real estate;
6471/// for use in place of lv_msgbox_create.
6572///
6673/// while all heap-alloc'ed resources are free'd automatically right before cb is called,
6774/// the value of title, text and btns args must live at least as long as cb; they are
6875/// memory-managed by the callers.
69- ///
70- /// note: the cb callback must have @alignOf(ModalbuttonCallbackFn) alignment.
7176pub fn modal (title : [* :0 ]const u8 , text : [* :0 ]const u8 , btns : []const [* :0 ]const u8 , cb : ModalButtonCallbackFn ) ! void {
7277 const win = try lvgl .Window .newTop (60 , title );
7378 errdefer win .destroy (); // also deletes all children created below
74- win .setUserdata (cb );
79+
80+ // Store a data pointer in LVGL user_data (safe), containing the callback.
81+ const ctx = try std .heap .c_allocator .create (ModalCtx );
82+ errdefer std .heap .c_allocator .destroy (ctx );
83+ ctx .* = .{ .cb = cb };
84+ win .setUserdata (ctx );
7585
7686 const wincont = win .content ().flex (.column , .{ .cross = .center , .track = .center });
7787 const msg = try lvgl .Label .new (wincont , text , .{ .pos = .center });
@@ -89,26 +99,44 @@ pub fn modal(title: [*:0]const u8, text: [*:0]const u8, btns: []const [*:0]const
8999 const btn = try lvgl .TextButton .new (btncont , btext );
90100 btn .setFlag (.event_bubble );
91101 btn .setFlag (.user1 ); // .user1 indicates actionable button in callback
92- btn .setUserdata (@ptrFromInt (i )); // button index in callback
102+
103+ // Store (i+1) so userdata is never null (0).
104+ // In callback: btn_index = intFromPtr - 1.
105+ btn .setUserdata (@ptrFromInt (i + 1 ));
106+
93107 btn .setWidth (btnwidth );
94108 if (i == 0 ) {
95109 btn .addStyle (lvgl .nm_style_btn_red (), .{});
96110 }
97111 }
112+
113+ // Pass the window object pointer as event user data (edata)
98114 _ = btncont .on (.click , nm_modal_callback , win .lvobj );
99115}
100116
101117export fn nm_modal_callback (e : * lvgl.LvEvent ) callconv (.C ) void {
102- if (e .userdata ()) | edata | {
103- const target = lvgl.Container { .lvobj = e .target () }; // type doesn't really matter
104- if (! target .hasFlag (.user1 )) { // .user1 is set in modal setup
105- return ;
106- }
118+ const edata = e .userdata () orelse return ;
107119
108- const btn_index = @intFromPtr (target .userdata ());
109- const win = lvgl.Window { .lvobj = @ptrCast (edata ) };
110- const cb : ModalButtonCallbackFn = @alignCast (@ptrCast (win .userdata ()));
111- win .destroy ();
112- cb (btn_index );
120+ const target = lvgl.Container { .lvobj = e .target () }; // type doesn't really matter
121+ if (! target .hasFlag (.user1 )) { // .user1 is set in modal setup
122+ return ;
113123 }
124+
125+ const idx_any = target .userdata () orelse return ;
126+ const btn_index : usize = @intFromPtr (idx_any ) - 1 ;
127+
128+ const win = lvgl.Window { .lvobj = @ptrCast (edata ) };
129+
130+ const ctx_any = win .userdata () orelse return ;
131+ const ctx : * ModalCtx = @ptrCast (@alignCast (ctx_any ));
132+ const cb = ctx .cb ;
133+
134+ // Destroy LVGL objects first (matches your earlier intent: free UI resources)
135+ win .destroy ();
136+
137+ // Free the context we allocated for this modal
138+ std .heap .c_allocator .destroy (ctx );
139+
140+ // Now call user callback (may open other screens, etc.)
141+ cb (btn_index );
114142}
0 commit comments