summaryrefslogtreecommitdiff
path: root/src/apprt/gtk/class.zig
blob: 942666cf48797ef4b6000a2b578fcf4971ca5ac1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
//! This files contains all the GObject classes for the GTK apprt
//! along with helpers to work with them.

const std = @import("std");
const glib = @import("glib");
const gobject = @import("gobject");
const gtk = @import("gtk");

const ext = @import("ext.zig");
pub const Application = @import("class/application.zig").Application;
pub const Window = @import("class/window.zig").Window;
pub const Config = @import("class/config.zig").Config;
pub const Surface = @import("class/surface.zig").Surface;

/// Common methods for all GObject classes we create.
pub fn Common(
    comptime Self: type,
    comptime Private: ?type,
) type {
    return struct {
        /// Upcast our type to a parent type or interface. This will fail at
        /// compile time if the cast isn't 100% safe. For unsafe casts,
        /// use `gobject.ext.cast` instead. We don't have a helper for that
        /// because its uncommon and unsafe behavior should be noisier.
        pub fn as(self: *Self, comptime T: type) *T {
            return gobject.ext.as(T, self);
        }

        /// Increase the reference count of the object.
        pub fn ref(self: *Self) *Self {
            return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object))));
        }

        /// If the reference count is 1 and the object is floating, clear the
        /// floating attribute. Otherwise, increase the reference count by 1.
        pub fn refSink(self: *Self) *Self {
            return @ptrCast(@alignCast(gobject.Object.refSink(self.as(gobject.Object))));
        }

        /// Decrease the reference count of the object.
        pub fn unref(self: *Self) void {
            gobject.Object.unref(self.as(gobject.Object));
        }

        /// Access the private data of the object. This should be forwarded
        /// via a non-pub const usually.
        pub const private = if (Private) |P| (struct {
            fn private(self: *Self) *P {
                return gobject.ext.impl_helpers.getPrivate(
                    self,
                    P,
                    P.offset,
                );
            }
        }).private else {};

        /// Get the class for the object.
        ///
        /// This _seems_ ugly and unsafe but this is how GObject
        /// works under the hood. From the [GObject Type System
        /// Concepts](https://docs.gtk.org/gobject/concepts.html) documentation:
        ///
        ///     Every object must define two structures: its class structure
        ///     and its instance structure. All class structures must contain
        ///     as first member a GTypeClass structure. All instance structures
        ///     must contain as first member a GTypeInstance structure.
        ///     …
        ///     These constraints allow the type system to make sure that
        ///     every object instance (identified by a pointer to the object’s
        ///     instance structure) contains in its first bytes a pointer to the
        ///     object’s class structure.
        ///     …
        ///     The C standard mandates that the first field of a C structure is
        ///     stored starting in the first byte of the buffer used to hold the
        ///     structure’s fields in memory. This means that the first field of
        ///     an instance of an object B is A’s first field which in turn is
        ///     GTypeInstance‘s first field which in turn is g_class, a pointer
        ///     to B’s class structure.
        ///
        /// This means that to access the class structure for an object you cast it
        /// to `*gobject.TypeInstance` and then access the `f_g_class` field.
        ///
        /// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L555-571
        /// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L2673
        ///
        pub fn getClass(self: *Self) ?*Self.Class {
            const type_instance: *gobject.TypeInstance = @ptrCast(self);
            return @ptrCast(type_instance.f_g_class orelse return null);
        }

        /// Define a virtual method. The `Self.Class` type must have a field
        /// named `name` which is a function pointer in the following form:
        ///
        ///   ?*const fn (*Self) callconv(.c) void
        ///
        /// The virtual method may take additional parameters and specify
        /// a non-void return type. The parameters and return type must be
        /// valid for the C calling convention.
        pub fn defineVirtualMethod(
            comptime name: [:0]const u8,
        ) type {
            return struct {
                pub fn call(
                    class: anytype,
                    object: *ClassInstance(@TypeOf(class)),
                    params: anytype,
                ) (fn_info.return_type orelse void) {
                    const func = @field(
                        gobject.ext.as(Self.Class, class),
                        name,
                    ).?;
                    @call(.auto, func, .{
                        gobject.ext.as(Self, object),
                    } ++ params);
                }

                pub fn implement(
                    class: anytype,
                    implementation: *const ImplementFunc(@TypeOf(class)),
                ) void {
                    @field(gobject.ext.as(
                        Self.Class,
                        class,
                    ), name) = @ptrCast(implementation);
                }

                /// The type info of the virtual method.
                const fn_info = fn_info: {
                    // This is broken down like this so its slightly more
                    // readable. We expect a field named "name" on the Class
                    // with the rough type of `?*const fn` and we need the
                    // function info.
                    const Field = @FieldType(Self.Class, name);
                    const opt = @typeInfo(Field).optional;
                    const ptr = @typeInfo(opt.child).pointer;
                    break :fn_info @typeInfo(ptr.child).@"fn";
                };

                /// The instance type for a class.
                fn ClassInstance(comptime T: type) type {
                    return @typeInfo(T).pointer.child.Instance;
                }

                /// The function type for implementations. This is the same type
                /// as the virtual method but the self parameter points to the
                /// target instead of the original class.
                fn ImplementFunc(comptime T: type) type {
                    var params: [fn_info.params.len]std.builtin.Type.Fn.Param = undefined;
                    @memcpy(&params, fn_info.params);
                    params[0].type = *ClassInstance(T);
                    return @Type(.{ .@"fn" = .{
                        .calling_convention = fn_info.calling_convention,
                        .is_generic = fn_info.is_generic,
                        .is_var_args = fn_info.is_var_args,
                        .return_type = fn_info.return_type,
                        .params = &params,
                    } });
                }
            };
        }

        /// A helper that creates a property that reads and writes a
        /// private field with only shallow copies. This is good for primitives
        /// such as bools, numbers, etc.
        pub fn privateShallowFieldAccessor(
            comptime name: []const u8,
        ) gobject.ext.Accessor(
            Self,
            @FieldType(Private.?, name),
        ) {
            return gobject.ext.privateFieldAccessor(
                Self,
                Private.?,
                &Private.?.offset,
                name,
            );
        }

        /// A helper that can be used to create a property that reads and
        /// writes a private boxed gobject field type.
        ///
        /// Reading the property will result in allocating a pointer and
        /// setting it will free the previous pointer.
        ///
        /// The object class (Self) must still free the private field
        /// in finalize!
        pub fn privateBoxedFieldAccessor(
            comptime name: []const u8,
        ) gobject.ext.Accessor(
            Self,
            @FieldType(Private.?, name),
        ) {
            return .{
                .getter = &struct {
                    fn get(self: *Self, value: *gobject.Value) void {
                        gobject.ext.Value.set(
                            value,
                            @field(private(self), name),
                        );
                    }
                }.get,
                .setter = &struct {
                    fn set(self: *Self, value: *const gobject.Value) void {
                        const priv = private(self);
                        if (@field(priv, name)) |v| {
                            ext.boxedFree(
                                @typeInfo(@TypeOf(v)).pointer.child,
                                v,
                            );
                        }

                        const T = @TypeOf(@field(priv, name));
                        @field(
                            priv,
                            name,
                        ) = gobject.ext.Value.dup(value, T);
                    }
                }.set,
            };
        }

        /// A helper that can be used to create a property that reads and
        /// writes a private field gobject field type (reference counted).
        ///
        /// Reading the property will result in taking a reference to the
        /// value and writing the property will unref the previous value.
        ///
        /// The object class (Self) must still free the private field
        /// in finalize!
        pub fn privateObjFieldAccessor(
            comptime name: []const u8,
        ) gobject.ext.Accessor(
            Self,
            @FieldType(Private.?, name),
        ) {
            return .{
                .getter = &struct {
                    fn get(self: *Self, value: *gobject.Value) void {
                        gobject.ext.Value.set(
                            value,
                            @field(private(self), name),
                        );
                    }
                }.get,
                .setter = &struct {
                    fn set(self: *Self, value: *const gobject.Value) void {
                        const priv = private(self);
                        if (@field(priv, name)) |v| v.unref();

                        const T = @TypeOf(@field(priv, name));
                        @field(
                            priv,
                            name,
                        ) = gobject.ext.Value.dup(value, T);
                    }
                }.set,
            };
        }

        /// A helper that can be used to create a property that reads and
        /// writes a private `?[:0]const u8` field type.
        ///
        /// Reading the property will result in a copy of the string
        /// and callers are responsible for freeing it.
        ///
        /// Writing the property will free the previous value and copy
        /// the new value into the private field.
        ///
        /// The object class (Self) must still free the private field
        /// in finalize!
        pub fn privateStringFieldAccessor(
            comptime name: []const u8,
        ) gobject.ext.Accessor(
            Self,
            @FieldType(Private.?, name),
        ) {
            const S = struct {
                fn getter(self: *Self) ?[:0]const u8 {
                    return @field(private(self), name);
                }

                fn setter(self: *Self, value: ?[:0]const u8) void {
                    const priv = private(self);
                    if (@field(priv, name)) |v| {
                        glib.free(@ptrCast(@constCast(v)));
                    }

                    // We don't need to copy this because it was already
                    // copied by the typedAccessor.
                    @field(priv, name) = value;
                }
            };

            return gobject.ext.typedAccessor(
                Self,
                ?[:0]const u8,
                .{
                    .getter = S.getter,
                    .getter_transfer = .none,
                    .setter = S.setter,
                    .setter_transfer = .full,
                },
            );
        }

        /// Common class functions.
        pub const Class = struct {
            pub fn as(class: *Self.Class, comptime T: type) *T {
                return gobject.ext.as(T, class);
            }

            /// Bind a template child to a private entry in the class.
            pub const bindTemplateChildPrivate = if (Private) |P| (struct {
                pub fn bindTemplateChildPrivate(
                    class: *Self.Class,
                    comptime name: [:0]const u8,
                    comptime options: gtk.ext.BindTemplateChildOptions,
                ) void {
                    gtk.ext.impl_helpers.bindTemplateChildPrivate(
                        class,
                        name,
                        P,
                        P.offset,
                        options,
                    );
                }
            }).bindTemplateChildPrivate else {};

            /// Bind a function pointer to a template callback symbol.
            pub fn bindTemplateCallback(
                class: *Self.Class,
                comptime name: [:0]const u8,
                comptime func: anytype,
            ) void {
                {
                    const ptr_ti = @typeInfo(@TypeOf(func));
                    if (ptr_ti != .pointer) {
                        @compileError("bound function must be a pointer type");
                    }
                    if (ptr_ti.pointer.size != .one) {
                        @compileError("bound function must be a pointer to a function");
                    }

                    const func_ti = @typeInfo(ptr_ti.pointer.child);
                    if (func_ti != .@"fn") {
                        @compileError("bound function must be a function pointer");
                    }
                    if (func_ti.@"fn".return_type == bool) {
                        // glib booleans are ints and returning a Zig bool type
                        // I think uses a byte and causes ABI issues.
                        @compileError("bound function must return c_int instead of bool");
                    }
                }

                gtk.Widget.Class.bindTemplateCallbackFull(
                    class.as(gtk.Widget.Class),
                    name,
                    @ptrCast(func),
                );
            }
        };
    };
}

test {
    @import("std").testing.refAllDecls(@This());
}