@gtkx/runtime 1.0.0-rc.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -9
- package/dist/application-class.d.ts +32 -0
- package/dist/application-class.d.ts.map +1 -0
- package/dist/application-class.js +68 -0
- package/dist/application-class.js.map +1 -0
- package/dist/arg.d.ts +9 -3
- package/dist/arg.d.ts.map +1 -1
- package/dist/arg.js +4 -3
- package/dist/arg.js.map +1 -1
- package/dist/bind.d.ts +11 -0
- package/dist/bind.d.ts.map +1 -1
- package/dist/bind.js +11 -0
- package/dist/bind.js.map +1 -1
- package/dist/callback.d.ts +2 -1
- package/dist/callback.d.ts.map +1 -1
- package/dist/callback.js +119 -21
- package/dist/callback.js.map +1 -1
- package/dist/closure.d.ts +26 -0
- package/dist/closure.d.ts.map +1 -0
- package/dist/closure.js +99 -0
- package/dist/closure.js.map +1 -0
- package/dist/descriptors.d.ts +105 -6
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +56 -12
- package/dist/descriptors.js.map +1 -1
- package/dist/error.d.ts +7 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +1 -1
- package/dist/error.js.map +1 -1
- package/dist/fn.d.ts +24 -3
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +70 -33
- package/dist/fn.js.map +1 -1
- package/dist/index.d.ts +29 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/library.d.ts +2 -1
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +2 -1
- package/dist/library.js.map +1 -1
- package/dist/lifecycle.d.ts +52 -10
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +62 -18
- package/dist/lifecycle.js.map +1 -1
- package/dist/listeners.d.ts +6 -0
- package/dist/listeners.d.ts.map +1 -1
- package/dist/listeners.js +3 -1
- package/dist/listeners.js.map +1 -1
- package/dist/mixin.d.ts +16 -2
- package/dist/mixin.d.ts.map +1 -1
- package/dist/mixin.js +18 -6
- package/dist/mixin.js.map +1 -1
- package/dist/native-value.d.ts +8 -0
- package/dist/native-value.d.ts.map +1 -1
- package/dist/native-value.js +12 -3
- package/dist/native-value.js.map +1 -1
- package/dist/object.d.ts +34 -6
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +89 -25
- package/dist/object.js.map +1 -1
- package/dist/param-spec.d.ts +11 -0
- package/dist/param-spec.d.ts.map +1 -0
- package/dist/param-spec.js +149 -0
- package/dist/param-spec.js.map +1 -0
- package/dist/promisify.d.ts +3 -5
- package/dist/promisify.d.ts.map +1 -1
- package/dist/promisify.js +4 -3
- package/dist/promisify.js.map +1 -1
- package/dist/properties.d.ts +39 -7
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +305 -42
- package/dist/properties.js.map +1 -1
- package/dist/register-class.d.ts +119 -14
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +109 -80
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +81 -17
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +72 -26
- package/dist/registry.js.map +1 -1
- package/dist/signal.d.ts +22 -3
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +64 -8
- package/dist/signal.js.map +1 -1
- package/dist/t.d.ts +44 -0
- package/dist/t.d.ts.map +1 -1
- package/dist/t.js.map +1 -1
- package/dist/type.d.ts +2 -2
- package/dist/type.d.ts.map +1 -1
- package/dist/type.js +2 -0
- package/dist/type.js.map +1 -1
- package/dist/value.d.ts +10 -10
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +44 -22
- package/dist/value.js.map +1 -1
- package/dist/vfunc-call.d.ts +37 -0
- package/dist/vfunc-call.d.ts.map +1 -0
- package/dist/vfunc-call.js +168 -0
- package/dist/vfunc-call.js.map +1 -0
- package/dist/vfunc-seeds.d.ts +12 -0
- package/dist/vfunc-seeds.d.ts.map +1 -0
- package/dist/vfunc-seeds.js +19 -0
- package/dist/vfunc-seeds.js.map +1 -0
- package/dist/vfunc.d.ts +8 -0
- package/dist/vfunc.d.ts.map +1 -0
- package/dist/vfunc.js +23 -0
- package/dist/vfunc.js.map +1 -0
- package/package.json +4 -4
- package/src/application-class.ts +116 -0
- package/src/arg.ts +11 -5
- package/src/bind.ts +11 -0
- package/src/callback.ts +163 -23
- package/src/closure.ts +136 -0
- package/src/descriptors.ts +137 -41
- package/src/error.ts +8 -1
- package/src/fn.ts +104 -39
- package/src/index.ts +36 -7
- package/src/internal.ts +1 -0
- package/src/library.ts +2 -1
- package/src/lifecycle.ts +99 -22
- package/src/listeners.ts +9 -1
- package/src/mixin.ts +27 -8
- package/src/native-value.ts +13 -3
- package/src/object.ts +128 -34
- package/src/param-spec.ts +235 -0
- package/src/promisify.ts +8 -8
- package/src/properties.ts +459 -45
- package/src/register-class.ts +329 -114
- package/src/registry.ts +150 -38
- package/src/signal.ts +101 -17
- package/src/t.ts +44 -0
- package/src/type.ts +3 -2
- package/src/value.ts +57 -19
- package/src/vfunc-call.ts +243 -0
- package/src/vfunc-seeds.ts +36 -0
- package/src/vfunc.ts +31 -0
package/src/properties.ts
CHANGED
|
@@ -1,101 +1,511 @@
|
|
|
1
1
|
import type { ExternalObject, Handle, RegisterClassProperty } from "@gtkx/native";
|
|
2
2
|
import type { AnyClass } from "@gtkx/utils";
|
|
3
|
-
import { camelCase } from "@gtkx/utils";
|
|
3
|
+
import { camelCase, kebabCase } from "@gtkx/utils";
|
|
4
4
|
import { bind } from "./bind.js";
|
|
5
|
+
import { biguint64T, stringT, structT, voidT } from "./descriptors.js";
|
|
5
6
|
import { LIB, PARAM_T, VALUE_T } from "./library.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
7
|
+
import {
|
|
8
|
+
getParamFlags,
|
|
9
|
+
getParamValueType,
|
|
10
|
+
isParamConstructOnly,
|
|
11
|
+
isParamLaxlyValidated,
|
|
12
|
+
isParamWritable,
|
|
13
|
+
type ValueGuard,
|
|
14
|
+
valueGuardFor,
|
|
15
|
+
wasParamValueModified,
|
|
16
|
+
} from "./param-spec.js";
|
|
17
|
+
import { getHandle, getInterfaceProperties, instanceClassName, type InterfaceProperty } from "./registry.js";
|
|
18
|
+
import { typeName } from "./type.js";
|
|
19
|
+
import { fromValue, intoValue, newValueForType, type ValueWriter, valueWriterFor } from "./value.js";
|
|
8
20
|
|
|
9
|
-
type
|
|
21
|
+
type PropertyCheck = {
|
|
10
22
|
name: string;
|
|
23
|
+
propertyName: string;
|
|
24
|
+
handle: ExternalObject<Handle>;
|
|
25
|
+
flags: number;
|
|
26
|
+
valueType: bigint;
|
|
27
|
+
canHoldValue: ValueGuard;
|
|
28
|
+
write?: ValueWriter;
|
|
29
|
+
scratch?: ExternalObject<Handle>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type PropertyAccessor = PropertyCheck & {
|
|
33
|
+
memberName: string;
|
|
11
34
|
storage: symbol;
|
|
12
|
-
|
|
35
|
+
hasGeneratedMember: boolean;
|
|
36
|
+
isInterfaceProperty?: boolean;
|
|
37
|
+
delegate?: InterfaceProperty;
|
|
13
38
|
};
|
|
14
39
|
|
|
40
|
+
/** A `GObject.ParamSpec` wrapper describing one property's name, type, flags and default. */
|
|
15
41
|
type PropertySpec = object;
|
|
42
|
+
|
|
43
|
+
type PropertyDispatch = {
|
|
44
|
+
accessors: PropertyAccessor[];
|
|
45
|
+
delegates: Map<string, InterfaceProperty>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type PropertyDispatchSource = {
|
|
49
|
+
klass: AnyClass;
|
|
50
|
+
properties: Record<string, PropertySpec>;
|
|
51
|
+
adoptedTypes: bigint[];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type ConstructProperty = {
|
|
55
|
+
name: string;
|
|
56
|
+
value: ExternalObject<Handle>;
|
|
57
|
+
};
|
|
58
|
+
|
|
16
59
|
type NotifyingObject = { notify?: (propertyName: string) => void };
|
|
17
60
|
|
|
18
61
|
const FIRST_PROPERTY_ID = 1;
|
|
62
|
+
const GET_PROPERTY_VFUNC = "vfuncGetProperty";
|
|
63
|
+
const SET_PROPERTY_VFUNC = "vfuncSetProperty";
|
|
64
|
+
const READ_ONLY_REASON = "the property is read-only";
|
|
65
|
+
const CONSTRUCT_ONLY_REASON = "the property can only be set when the object is constructed";
|
|
66
|
+
const CLASS_T = structT("borrowed");
|
|
67
|
+
const interfaceStorages: Map<string, symbol> = new Map();
|
|
19
68
|
const paramSpecDefaultValue = bind(LIB, "g_param_spec_get_default_value", [PARAM_T], VALUE_T);
|
|
69
|
+
const paramSpecName = bind(LIB, "g_param_spec_get_name", [PARAM_T], stringT("borrowed"));
|
|
70
|
+
const typeClassRef = bind(LIB, "g_type_class_ref", [biguint64T], CLASS_T);
|
|
71
|
+
const typeClassUnref = bind(LIB, "g_type_class_unref", [CLASS_T], voidT);
|
|
72
|
+
const classFindProperty = bind(LIB, "g_object_class_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
|
|
20
73
|
|
|
74
|
+
const getPropertyName = (pspec: PropertySpec): string => paramSpecName(getHandle(pspec)) as string;
|
|
21
75
|
const underscoreCase = (name: string): string => name.replaceAll("-", "_");
|
|
76
|
+
const canonicalCase = (name: string): string => kebabCase(name).replaceAll("_", "-");
|
|
22
77
|
const accessorNames = (name: string): string[] => [...new Set([name, underscoreCase(name), camelCase(name)])];
|
|
78
|
+
const typeLabel = (type: bigint): string => typeName(type) ?? String(type);
|
|
79
|
+
|
|
80
|
+
const defaultValueFor = (handle: ExternalObject<Handle>): ExternalObject<Handle> =>
|
|
81
|
+
paramSpecDefaultValue(handle) as ExternalObject<Handle>;
|
|
82
|
+
|
|
83
|
+
const holdsReason = (check: PropertyCheck): string =>
|
|
84
|
+
`the property holds values of type '${typeLabel(check.valueType)}'`;
|
|
85
|
+
|
|
86
|
+
function checkFor(handle: ExternalObject<Handle>, name: string): PropertyCheck {
|
|
87
|
+
const valueType = getParamValueType(handle);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
name,
|
|
91
|
+
propertyName: paramSpecName(handle) as string,
|
|
92
|
+
handle,
|
|
93
|
+
flags: getParamFlags(handle),
|
|
94
|
+
valueType,
|
|
95
|
+
canHoldValue: valueGuardFor(valueType),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function findPropertySpec(klass: ExternalObject<Handle>, name: string): ExternalObject<Handle> | null {
|
|
100
|
+
const found = classFindProperty(klass, name) as ExternalObject<Handle> | null;
|
|
101
|
+
|
|
102
|
+
return found ?? (classFindProperty(klass, canonicalCase(name)) as ExternalObject<Handle> | null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function describeValue(value: unknown): string {
|
|
106
|
+
if (typeof value === "string") {
|
|
107
|
+
return JSON.stringify(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (typeof value === "object" && value !== null) {
|
|
111
|
+
return instanceClassName(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return String(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function propertyMessage(instance: object, check: PropertyCheck, tail: string): string {
|
|
118
|
+
return `${instanceClassName(instance)}.${check.name}: ${tail}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function refusalTail(check: PropertyCheck, value: unknown, reason: string): string {
|
|
122
|
+
return `cannot set property '${check.propertyName}' to ${describeValue(value)}; ${reason}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function serveTail(check: PropertyCheck, value: unknown): string {
|
|
126
|
+
return `cannot serve property '${check.propertyName}' from ${describeValue(value)}; ${holdsReason(check)}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assertValueFits(instance: object, check: PropertyCheck, value: unknown): void {
|
|
130
|
+
if (check.canHoldValue(value)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const tail = refusalTail(check, value, holdsReason(check));
|
|
135
|
+
throw new TypeError(propertyMessage(instance, check, tail));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function assertValueServes(instance: object, check: PropertyCheck, value: unknown): void {
|
|
139
|
+
if (check.canHoldValue(value)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
throw new TypeError(propertyMessage(instance, check, serveTail(check, value)));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function assertValueValidates(
|
|
147
|
+
instance: object,
|
|
148
|
+
check: PropertyCheck,
|
|
149
|
+
value: unknown,
|
|
150
|
+
gValue: ExternalObject<Handle>,
|
|
151
|
+
): void {
|
|
152
|
+
if (!wasParamValueModified(check.handle, gValue) || isParamLaxlyValidated(check.flags)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const reason =
|
|
157
|
+
`the value is invalid or out of range for type '${typeLabel(check.valueType)}', ` +
|
|
158
|
+
`and GObject would put ${describeValue(fromValue(gValue))} in its place`;
|
|
159
|
+
|
|
160
|
+
throw new RangeError(propertyMessage(instance, check, refusalTail(check, value, reason)));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function assertWritable(instance: object, check: PropertyCheck, value: unknown): void {
|
|
164
|
+
if (isParamWritable(check.flags)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
throw new TypeError(propertyMessage(instance, check, refusalTail(check, value, READ_ONLY_REASON)));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function fillCheckedValue(
|
|
172
|
+
instance: object,
|
|
173
|
+
check: PropertyCheck,
|
|
174
|
+
gValue: ExternalObject<Handle>,
|
|
175
|
+
value: unknown,
|
|
176
|
+
): void {
|
|
177
|
+
assertValueFits(instance, check, value);
|
|
178
|
+
check.write ??= valueWriterFor(check.valueType);
|
|
179
|
+
check.write(gValue, value);
|
|
180
|
+
assertValueValidates(instance, check, value, gValue);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function checkedValueFor(instance: object, check: PropertyCheck, value: unknown): ExternalObject<Handle> {
|
|
184
|
+
const gValue = newValueForType(check.valueType);
|
|
185
|
+
fillCheckedValue(instance, check, gValue, value);
|
|
186
|
+
|
|
187
|
+
return gValue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function assertValueAccepted(instance: object, check: PropertyCheck, value: unknown): void {
|
|
191
|
+
check.scratch ??= newValueForType(check.valueType);
|
|
192
|
+
fillCheckedValue(instance, check, check.scratch, value);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function constructValueFor(wrapper: object, check: PropertyCheck, value: unknown): ConstructProperty {
|
|
196
|
+
assertWritable(wrapper, check, value);
|
|
197
|
+
|
|
198
|
+
return { name: check.propertyName, value: checkedValueFor(wrapper, check, value) };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function constructPropertyFor(
|
|
202
|
+
gtype: bigint,
|
|
203
|
+
name: string,
|
|
204
|
+
value: unknown,
|
|
205
|
+
wrapper: object,
|
|
206
|
+
): ConstructProperty | undefined {
|
|
207
|
+
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const pspec = findPropertySpec(klass, name);
|
|
211
|
+
|
|
212
|
+
return pspec === null ? undefined : constructValueFor(wrapper, checkFor(pspec, name), value);
|
|
213
|
+
} finally {
|
|
214
|
+
typeClassUnref(klass);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
23
217
|
|
|
24
218
|
function readStored(instance: Record<symbol, unknown>, accessor: PropertyAccessor): unknown {
|
|
25
219
|
if (!Object.hasOwn(instance, accessor.storage)) {
|
|
26
|
-
|
|
27
|
-
instance[accessor.storage] = fromValue(defaults);
|
|
220
|
+
instance[accessor.storage] = fromValue(defaultValueFor(accessor.handle));
|
|
28
221
|
}
|
|
29
222
|
|
|
30
223
|
return instance[accessor.storage];
|
|
31
224
|
}
|
|
32
225
|
|
|
33
|
-
function
|
|
34
|
-
|
|
226
|
+
function storeValue(instance: Record<symbol, unknown>, accessor: PropertyAccessor, value: unknown): void {
|
|
227
|
+
instance[accessor.storage] = value;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function writeStored(instance: Record<symbol, unknown>, accessor: PropertyAccessor, value: unknown): void {
|
|
231
|
+
if (readStored(instance, accessor) === value) {
|
|
35
232
|
return;
|
|
36
233
|
}
|
|
37
234
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
235
|
+
storeValue(instance, accessor, value);
|
|
236
|
+
(instance as NotifyingObject).notify?.(accessor.propertyName);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function writeProperty(instance: object, accessor: PropertyAccessor, value: unknown): void {
|
|
240
|
+
const stored = instance as Record<symbol, unknown>;
|
|
241
|
+
|
|
242
|
+
if (readStored(stored, accessor) === value) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
assertValueAccepted(instance, accessor, value);
|
|
247
|
+
storeValue(stored, accessor, value);
|
|
248
|
+
(instance as NotifyingObject).notify?.(accessor.propertyName);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function storedGetter(accessor: PropertyAccessor): (this: object) => unknown {
|
|
252
|
+
return function get(this: object) {
|
|
253
|
+
return readStored(this as Record<symbol, unknown>, accessor);
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function memberGetter(accessor: PropertyAccessor): (this: object) => unknown {
|
|
258
|
+
return function get(this: object) {
|
|
259
|
+
return (this as Record<string, unknown>)[accessor.memberName];
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function memberSetter(accessor: PropertyAccessor): (this: object, value: unknown) => void {
|
|
264
|
+
return function set(this: object, value: unknown) {
|
|
265
|
+
(this as Record<string, unknown>)[accessor.memberName] = value;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function refusalFor(accessor: PropertyAccessor, reason: string): (this: object, value: unknown) => void {
|
|
270
|
+
return function set(this: object, value: unknown) {
|
|
271
|
+
throw new TypeError(propertyMessage(this, accessor, refusalTail(accessor, value, reason)));
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function checkedSetter(accessor: PropertyAccessor): (this: object, value: unknown) => void {
|
|
276
|
+
if (!isParamWritable(accessor.flags)) {
|
|
277
|
+
return refusalFor(accessor, READ_ONLY_REASON);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (isParamConstructOnly(accessor.flags)) {
|
|
281
|
+
return refusalFor(accessor, CONSTRUCT_ONLY_REASON);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return function set(this: object, value: unknown) {
|
|
285
|
+
writeProperty(this, accessor, value);
|
|
286
|
+
};
|
|
287
|
+
}
|
|
48
288
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
289
|
+
function hasOwnMember(prototype: object, alias: string): boolean {
|
|
290
|
+
return Object.getOwnPropertyDescriptor(prototype, alias) !== undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias: string): void {
|
|
294
|
+
if (hasOwnMember(prototype, alias)) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
Object.defineProperty(prototype, alias, descriptor);
|
|
53
299
|
}
|
|
54
300
|
|
|
55
301
|
function installAccessors(klass: AnyClass, accessor: PropertyAccessor): void {
|
|
56
302
|
const prototype = (klass as { prototype: object }).prototype;
|
|
303
|
+
accessor.hasGeneratedMember = !hasOwnMember(prototype, accessor.memberName);
|
|
304
|
+
|
|
305
|
+
const descriptor: PropertyDescriptor = {
|
|
306
|
+
configurable: true,
|
|
307
|
+
enumerable: true,
|
|
308
|
+
get: accessor.hasGeneratedMember ? storedGetter(accessor) : memberGetter(accessor),
|
|
309
|
+
set: accessor.hasGeneratedMember ? checkedSetter(accessor) : memberSetter(accessor),
|
|
310
|
+
};
|
|
57
311
|
|
|
58
312
|
for (const alias of accessorNames(accessor.name)) {
|
|
59
|
-
defineAccessor(prototype,
|
|
313
|
+
defineAccessor(prototype, descriptor, alias);
|
|
60
314
|
}
|
|
61
315
|
}
|
|
62
316
|
|
|
63
|
-
function
|
|
64
|
-
const
|
|
317
|
+
function storageFor(propertyName: string): symbol {
|
|
318
|
+
const existing = interfaceStorages.get(propertyName);
|
|
65
319
|
|
|
66
|
-
if (
|
|
67
|
-
|
|
320
|
+
if (existing !== undefined) {
|
|
321
|
+
return existing;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const created = Symbol(`gtkx:property:${propertyName}`);
|
|
325
|
+
interfaceStorages.set(propertyName, created);
|
|
326
|
+
|
|
327
|
+
return created;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function interfaceAccessorFor(dispatch: PropertyDispatch, pspec: PropertySpec): PropertyAccessor {
|
|
331
|
+
const handle = getHandle(pspec);
|
|
332
|
+
const propertyName = paramSpecName(handle) as string;
|
|
333
|
+
const delegate = dispatch.delegates.get(propertyName);
|
|
334
|
+
|
|
335
|
+
const accessor: PropertyAccessor = {
|
|
336
|
+
...checkFor(handle, propertyName),
|
|
337
|
+
memberName: camelCase(propertyName),
|
|
338
|
+
storage: storageFor(propertyName),
|
|
339
|
+
hasGeneratedMember: false,
|
|
340
|
+
isInterfaceProperty: true,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
if (delegate !== undefined) {
|
|
344
|
+
accessor.delegate = delegate;
|
|
68
345
|
}
|
|
69
346
|
|
|
70
347
|
return accessor;
|
|
71
348
|
}
|
|
72
349
|
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
350
|
+
function resolveAccessor(
|
|
351
|
+
dispatch: PropertyDispatch,
|
|
352
|
+
propertyId: number,
|
|
353
|
+
pspec: PropertySpec,
|
|
354
|
+
): PropertyAccessor {
|
|
355
|
+
const accessor = dispatch.accessors[propertyId - FIRST_PROPERTY_ID];
|
|
356
|
+
|
|
357
|
+
if (accessor !== undefined) {
|
|
358
|
+
return accessor;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const created = interfaceAccessorFor(dispatch, pspec);
|
|
362
|
+
dispatch.accessors[propertyId - FIRST_PROPERTY_ID] = created;
|
|
363
|
+
|
|
364
|
+
return created;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function callMember(instance: object, member: string, args: unknown[]): unknown {
|
|
368
|
+
const fn: unknown = (instance as Record<string, unknown>)[member];
|
|
369
|
+
|
|
370
|
+
if (typeof fn !== "function") {
|
|
371
|
+
throw new TypeError(`registerClass: ${instanceClassName(instance)} has no '${member}' member`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return (fn as (...values: unknown[]) => unknown).apply(instance, args);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function backingMemberFor(instance: object, accessor: PropertyAccessor): string | undefined {
|
|
378
|
+
if (accessor.hasGeneratedMember && !Object.hasOwn(instance, accessor.memberName)) {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return accessor.memberName;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function readCurrent(instance: object, accessor: PropertyAccessor): unknown {
|
|
386
|
+
const getter = accessor.delegate?.getter;
|
|
387
|
+
|
|
388
|
+
if (getter !== undefined) {
|
|
389
|
+
return callMember(instance, getter, []);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (accessor.isInterfaceProperty === true) {
|
|
393
|
+
return readStored(instance as Record<symbol, unknown>, accessor);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const member = backingMemberFor(instance, accessor);
|
|
397
|
+
|
|
398
|
+
return member === undefined
|
|
399
|
+
? readStored(instance as Record<symbol, unknown>, accessor)
|
|
400
|
+
: (instance as Record<string, unknown>)[member];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function writeCurrent(instance: object, accessor: PropertyAccessor, value: unknown): void {
|
|
404
|
+
const setter = accessor.delegate?.setter;
|
|
405
|
+
|
|
406
|
+
if (setter !== undefined) {
|
|
407
|
+
callMember(instance, setter, [value]);
|
|
408
|
+
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (accessor.isInterfaceProperty === true) {
|
|
413
|
+
writeStored(instance as Record<symbol, unknown>, accessor, value);
|
|
414
|
+
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const member = backingMemberFor(instance, accessor);
|
|
419
|
+
|
|
420
|
+
if (member === undefined) {
|
|
421
|
+
storeValue(instance as Record<symbol, unknown>, accessor, value);
|
|
422
|
+
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
(instance as Record<string, unknown>)[member] = value;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function makeGetProperty(dispatch: PropertyDispatch) {
|
|
430
|
+
return function getProperty(this: object, propertyId: number, value: object, pspec: PropertySpec): void {
|
|
431
|
+
const accessor = resolveAccessor(dispatch, propertyId, pspec);
|
|
432
|
+
const current = readCurrent(this, accessor);
|
|
433
|
+
assertValueServes(this, accessor, current);
|
|
77
434
|
intoValue(getHandle(value), current);
|
|
78
435
|
};
|
|
79
436
|
}
|
|
80
437
|
|
|
81
|
-
function makeSetProperty(
|
|
82
|
-
return function setProperty(this: object, propertyId: number, value: object): void {
|
|
83
|
-
const accessor = resolveAccessor(
|
|
84
|
-
|
|
85
|
-
(this as Record<string, unknown>)[camelCase(accessor.name)] = jsValue;
|
|
438
|
+
function makeSetProperty(dispatch: PropertyDispatch) {
|
|
439
|
+
return function setProperty(this: object, propertyId: number, value: object, pspec: PropertySpec): void {
|
|
440
|
+
const accessor = resolveAccessor(dispatch, propertyId, pspec);
|
|
441
|
+
writeCurrent(this, accessor, fromValue(getHandle(value)));
|
|
86
442
|
};
|
|
87
443
|
}
|
|
88
444
|
|
|
89
|
-
function
|
|
90
|
-
const
|
|
445
|
+
function assertCanonicalName(klass: AnyClass, name: string, propertyName: string): void {
|
|
446
|
+
const canonical = canonicalCase(name);
|
|
447
|
+
|
|
448
|
+
if (propertyName === canonical) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
throw new TypeError(
|
|
453
|
+
`registerClass: ${klass.name} keys the property '${name}' to a GObject.ParamSpec named ` +
|
|
454
|
+
`'${propertyName}', which is the name GObject notifies under; name the ParamSpec '${canonical}'`,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
91
457
|
|
|
458
|
+
function assertCanonicalNames(klass: AnyClass, properties: Record<string, PropertySpec>): void {
|
|
92
459
|
for (const [name, pspec] of Object.entries(properties)) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
460
|
+
assertCanonicalName(klass, name, getPropertyName(pspec));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec): PropertyAccessor {
|
|
465
|
+
const accessor: PropertyAccessor = {
|
|
466
|
+
...checkFor(getHandle(pspec), name),
|
|
467
|
+
memberName: camelCase(name),
|
|
468
|
+
storage: Symbol(`gtkx:property:${name}`),
|
|
469
|
+
hasGeneratedMember: false,
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
installAccessors(klass, accessor);
|
|
473
|
+
|
|
474
|
+
return accessor;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function buildAccessors(source: PropertyDispatchSource): PropertyAccessor[] {
|
|
478
|
+
const { klass, properties } = source;
|
|
479
|
+
assertCanonicalNames(klass, properties);
|
|
480
|
+
|
|
481
|
+
return Object.entries(properties).map(([name, pspec]) => buildAccessor(klass, name, pspec));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function addInterfaceDelegates(delegates: Map<string, InterfaceProperty>, gtype: bigint): void {
|
|
485
|
+
const properties = getInterfaceProperties(gtype) ?? {};
|
|
486
|
+
|
|
487
|
+
for (const [name, delegate] of Object.entries(properties)) {
|
|
488
|
+
if (!delegates.has(name)) {
|
|
489
|
+
delegates.set(name, delegate);
|
|
490
|
+
}
|
|
96
491
|
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function interfaceDelegatesFor(adoptedTypes: bigint[]): Map<string, InterfaceProperty> {
|
|
495
|
+
const delegates: Map<string, InterfaceProperty> = new Map();
|
|
97
496
|
|
|
98
|
-
|
|
497
|
+
for (const gtype of adoptedTypes) {
|
|
498
|
+
addInterfaceDelegates(delegates, gtype);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return delegates;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function buildPropertyDispatch(source: PropertyDispatchSource): PropertyDispatch {
|
|
505
|
+
return {
|
|
506
|
+
accessors: buildAccessors(source),
|
|
507
|
+
delegates: interfaceDelegatesFor(source.adoptedTypes),
|
|
508
|
+
};
|
|
99
509
|
}
|
|
100
510
|
|
|
101
511
|
function toNativeProperties(properties: Record<string, PropertySpec>): RegisterClassProperty[] {
|
|
@@ -106,10 +516,14 @@ function toNativeProperties(properties: Record<string, PropertySpec>): RegisterC
|
|
|
106
516
|
}
|
|
107
517
|
|
|
108
518
|
export {
|
|
109
|
-
|
|
519
|
+
buildPropertyDispatch,
|
|
520
|
+
constructPropertyFor,
|
|
521
|
+
GET_PROPERTY_VFUNC,
|
|
110
522
|
makeGetProperty,
|
|
111
523
|
makeSetProperty,
|
|
524
|
+
SET_PROPERTY_VFUNC,
|
|
112
525
|
toNativeProperties,
|
|
113
|
-
type
|
|
526
|
+
type ConstructProperty,
|
|
527
|
+
type PropertyDispatch,
|
|
114
528
|
type PropertySpec,
|
|
115
529
|
};
|