pixifact 0.4.0 → 0.6.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/dist/compiler/builtinScenes.d.ts +0 -5
- package/dist/compiler/builtinScenes.js +0 -26
- package/dist/compiler/commands/index.js +8 -5
- package/dist/compiler/compileScenes.d.ts +3 -1
- package/dist/compiler/compileScenes.js +32 -2
- package/dist/compiler/index.d.ts +0 -1
- package/dist/compiler/index.js +0 -1
- package/dist/compiler/pixiNodeSchema.d.ts +5 -3
- package/dist/compiler/pixiNodeSchema.js +61 -7
- package/dist/compiler/sceneValidation.d.ts +3 -1
- package/dist/compiler/sceneValidation.js +105 -6
- package/dist/compiler/spec.d.ts +18 -7
- package/dist/compiler/spec.js +3 -1
- package/dist/compiler/templateParser.js +19 -0
- package/dist/compiler/templateSerializer.js +7 -1
- package/dist/compiler/typescriptCompiler.js +66 -8
- package/dist/compiler-node/builtinSceneInterfaces.d.ts +6 -0
- package/dist/compiler-node/builtinSceneInterfaces.js +27 -0
- package/dist/compiler-node/index.d.ts +3 -0
- package/dist/compiler-node/index.js +3 -0
- package/dist/compiler-node/runtimeVite.d.ts +89 -0
- package/dist/compiler-node/runtimeVite.js +420 -0
- package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.d.ts +1 -1
- package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.js +157 -25
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +4 -1
- package/dist/runtime/BitmapLabel.d.ts +9 -0
- package/dist/runtime/BitmapLabel.js +7 -0
- package/dist/runtime/Label.d.ts +9 -0
- package/dist/runtime/Label.js +7 -0
- package/dist/runtime/ScrollContainer.d.ts +2 -0
- package/dist/runtime/ScrollContainer.js +12 -2
- package/dist/runtime/TextBoxControl.d.ts +48 -0
- package/dist/runtime/TextBoxControl.js +162 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/runtimeBehavior.d.ts +3 -0
- package/dist/runtime/runtimeBehavior.js +11 -0
- package/dist/runtime-dev/index.d.ts +5 -0
- package/dist/runtime-dev/index.js +22 -0
- package/dist/runtime-dev/protocol.d.ts +58 -0
- package/dist/runtime-dev/protocol.js +1 -0
- package/dist/runtime-dev/runtimeClient.d.ts +22 -0
- package/dist/runtime-dev/runtimeClient.js +382 -0
- package/dist/scene/decorators.d.ts +2 -1
- package/dist/scene/decorators.js +12 -7
- package/dist/scene/index.d.ts +1 -0
- package/dist/scene/index.js +1 -0
- package/dist/scene/sceneBindingRuntime.d.ts +10 -0
- package/dist/scene/sceneBindingRuntime.js +124 -0
- package/package.json +6 -1
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { BitmapText, Graphics, Sprite, Text, } from 'pixi.js';
|
|
2
|
+
const maxRetainedLogs = 500;
|
|
3
|
+
const runtimeRequestEvent = 'pixifact:runtime:request';
|
|
4
|
+
const runtimeResponseEvent = 'pixifact:runtime:response';
|
|
5
|
+
const runtimeAnnounceEvent = 'pixifact:runtime:announce';
|
|
6
|
+
function pointSnapshot(point) {
|
|
7
|
+
return { x: point.x, y: point.y };
|
|
8
|
+
}
|
|
9
|
+
function boundsSnapshot(bounds) {
|
|
10
|
+
return {
|
|
11
|
+
x: bounds.x,
|
|
12
|
+
y: bounds.y,
|
|
13
|
+
width: bounds.width,
|
|
14
|
+
height: bounds.height,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function basicNodeSnapshot(node, childIndex) {
|
|
18
|
+
return {
|
|
19
|
+
uid: node.uid,
|
|
20
|
+
type: node.constructor.name,
|
|
21
|
+
label: node.label,
|
|
22
|
+
childIndex,
|
|
23
|
+
position: pointSnapshot(node.position),
|
|
24
|
+
scale: pointSnapshot(node.scale),
|
|
25
|
+
rotation: node.rotation,
|
|
26
|
+
alpha: node.alpha,
|
|
27
|
+
visible: node.visible,
|
|
28
|
+
renderable: node.renderable,
|
|
29
|
+
zIndex: node.zIndex,
|
|
30
|
+
eventMode: node.eventMode ?? null,
|
|
31
|
+
children: node.children.map((child, index) => basicNodeSnapshot(child, index)),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function supportedFill(fill) {
|
|
35
|
+
return typeof fill === 'number' || typeof fill === 'string' ? fill : null;
|
|
36
|
+
}
|
|
37
|
+
function specificNodeSnapshot(node) {
|
|
38
|
+
if (node instanceof Sprite) {
|
|
39
|
+
return {
|
|
40
|
+
kind: 'sprite',
|
|
41
|
+
anchor: pointSnapshot(node.anchor),
|
|
42
|
+
texture: {
|
|
43
|
+
uid: node.texture.uid,
|
|
44
|
+
label: node.texture.label ?? null,
|
|
45
|
+
width: node.texture.width,
|
|
46
|
+
height: node.texture.height,
|
|
47
|
+
},
|
|
48
|
+
tint: node.tint,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (node instanceof BitmapText || node instanceof Text) {
|
|
52
|
+
return {
|
|
53
|
+
kind: node instanceof BitmapText ? 'bitmapText' : 'text',
|
|
54
|
+
text: node.text,
|
|
55
|
+
anchor: pointSnapshot(node.anchor),
|
|
56
|
+
style: {
|
|
57
|
+
align: node.style.align,
|
|
58
|
+
fill: supportedFill(node.style.fill),
|
|
59
|
+
fontFamily: Array.isArray(node.style.fontFamily)
|
|
60
|
+
? [...node.style.fontFamily]
|
|
61
|
+
: node.style.fontFamily,
|
|
62
|
+
fontSize: node.style.fontSize,
|
|
63
|
+
fontStyle: node.style.fontStyle,
|
|
64
|
+
fontWeight: node.style.fontWeight,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (node instanceof Graphics) {
|
|
69
|
+
return { kind: 'graphics' };
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
function findStageNode(stage, uid) {
|
|
74
|
+
if (stage.uid === uid)
|
|
75
|
+
return stage;
|
|
76
|
+
for (const child of stage.children) {
|
|
77
|
+
const result = findStageNode(child, uid);
|
|
78
|
+
if (result)
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
function detailedNodeSnapshot(node) {
|
|
84
|
+
const childIndex = node.parent ? node.parent.children.indexOf(node) : null;
|
|
85
|
+
const specific = specificNodeSnapshot(node);
|
|
86
|
+
return {
|
|
87
|
+
uid: node.uid,
|
|
88
|
+
type: node.constructor.name,
|
|
89
|
+
label: node.label,
|
|
90
|
+
parentUid: node.parent?.uid ?? null,
|
|
91
|
+
childIndex,
|
|
92
|
+
childCount: node.children.length,
|
|
93
|
+
position: pointSnapshot(node.position),
|
|
94
|
+
scale: pointSnapshot(node.scale),
|
|
95
|
+
pivot: pointSnapshot(node.pivot),
|
|
96
|
+
skew: pointSnapshot(node.skew),
|
|
97
|
+
rotation: node.rotation,
|
|
98
|
+
angle: node.angle,
|
|
99
|
+
width: node.width,
|
|
100
|
+
height: node.height,
|
|
101
|
+
localBounds: boundsSnapshot(node.getLocalBounds()),
|
|
102
|
+
globalBounds: boundsSnapshot(node.getBounds()),
|
|
103
|
+
alpha: node.alpha,
|
|
104
|
+
worldAlpha: node.getGlobalAlpha(),
|
|
105
|
+
visible: node.visible,
|
|
106
|
+
renderable: node.renderable,
|
|
107
|
+
zIndex: node.zIndex,
|
|
108
|
+
tint: node.tint,
|
|
109
|
+
blendMode: String(node.blendMode),
|
|
110
|
+
eventMode: node.eventMode ?? null,
|
|
111
|
+
cursor: node.cursor === undefined ? null : String(node.cursor),
|
|
112
|
+
...(specific ? { specific } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function snapshotLogValue(value, seen = new WeakSet(), depth = 0) {
|
|
116
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string') {
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
if (typeof value === 'bigint')
|
|
120
|
+
return `${value}n`;
|
|
121
|
+
if (typeof value === 'undefined')
|
|
122
|
+
return '[undefined]';
|
|
123
|
+
if (typeof value === 'symbol')
|
|
124
|
+
return String(value);
|
|
125
|
+
if (typeof value === 'function')
|
|
126
|
+
return `[Function ${value.name || 'anonymous'}]`;
|
|
127
|
+
if (value instanceof Error) {
|
|
128
|
+
return {
|
|
129
|
+
name: value.name,
|
|
130
|
+
message: value.message,
|
|
131
|
+
stack: value.stack ?? '',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (value instanceof Date)
|
|
135
|
+
return value.toISOString();
|
|
136
|
+
if (depth >= 4)
|
|
137
|
+
return `[${value.constructor?.name ?? 'Object'}]`;
|
|
138
|
+
if (seen.has(value))
|
|
139
|
+
return '[Circular]';
|
|
140
|
+
seen.add(value);
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
return value.map((item) => snapshotLogValue(item, seen, depth + 1));
|
|
143
|
+
}
|
|
144
|
+
const snapshot = {};
|
|
145
|
+
for (const key of Object.keys(value)) {
|
|
146
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
147
|
+
snapshot[key] = descriptor && 'value' in descriptor
|
|
148
|
+
? snapshotLogValue(descriptor.value, seen, depth + 1)
|
|
149
|
+
: '[Getter]';
|
|
150
|
+
}
|
|
151
|
+
return snapshot;
|
|
152
|
+
}
|
|
153
|
+
function logMessage(args) {
|
|
154
|
+
const first = args[0];
|
|
155
|
+
if (typeof first === 'string')
|
|
156
|
+
return first;
|
|
157
|
+
if (first instanceof Error)
|
|
158
|
+
return first.message;
|
|
159
|
+
return JSON.stringify(snapshotLogValue(first));
|
|
160
|
+
}
|
|
161
|
+
function createRuntimeLogCapture(targetConsole, targetWindow, now) {
|
|
162
|
+
const retained = [];
|
|
163
|
+
const originals = new Map();
|
|
164
|
+
let sequence = 0;
|
|
165
|
+
function append(level, args, explicitStack) {
|
|
166
|
+
const stack = explicitStack ?? args.find((value) => value instanceof Error)?.stack;
|
|
167
|
+
retained.push({
|
|
168
|
+
seq: ++sequence,
|
|
169
|
+
time: now().toISOString(),
|
|
170
|
+
level,
|
|
171
|
+
message: logMessage(args),
|
|
172
|
+
args: args.slice(typeof args[0] === 'string' ? 1 : 0).map((value) => snapshotLogValue(value)),
|
|
173
|
+
...(stack ? { stack } : {}),
|
|
174
|
+
});
|
|
175
|
+
if (retained.length > maxRetainedLogs)
|
|
176
|
+
retained.splice(0, retained.length - maxRetainedLogs);
|
|
177
|
+
}
|
|
178
|
+
for (const level of ['debug', 'log', 'info', 'warn', 'error']) {
|
|
179
|
+
const original = targetConsole[level];
|
|
180
|
+
originals.set(level, original);
|
|
181
|
+
targetConsole[level] = ((...args) => {
|
|
182
|
+
original.apply(targetConsole, args);
|
|
183
|
+
append(level, args);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const onWindowError = (event) => {
|
|
187
|
+
append('error', [event.message, event.error ?? null], event.error instanceof Error ? event.error.stack : undefined);
|
|
188
|
+
};
|
|
189
|
+
const onUnhandledRejection = (event) => {
|
|
190
|
+
const reason = event.reason;
|
|
191
|
+
append('error', ['Unhandled promise rejection', reason], reason instanceof Error ? reason.stack : undefined);
|
|
192
|
+
};
|
|
193
|
+
targetWindow.addEventListener('error', onWindowError);
|
|
194
|
+
targetWindow.addEventListener('unhandledrejection', onUnhandledRejection);
|
|
195
|
+
return {
|
|
196
|
+
entries: () => retained,
|
|
197
|
+
latestSeq: () => sequence,
|
|
198
|
+
dispose() {
|
|
199
|
+
for (const [level, original] of originals) {
|
|
200
|
+
targetConsole[level] = original;
|
|
201
|
+
}
|
|
202
|
+
targetWindow.removeEventListener('error', onWindowError);
|
|
203
|
+
targetWindow.removeEventListener('unhandledrejection', onUnhandledRejection);
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function pointerEvent(targetWindow, type, point, buttons) {
|
|
208
|
+
const eventWindow = targetWindow;
|
|
209
|
+
return new eventWindow.PointerEvent(type, {
|
|
210
|
+
bubbles: true,
|
|
211
|
+
cancelable: true,
|
|
212
|
+
pointerId: 1,
|
|
213
|
+
pointerType: 'mouse',
|
|
214
|
+
isPrimary: true,
|
|
215
|
+
button: 0,
|
|
216
|
+
buttons,
|
|
217
|
+
...point,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function clientPoint(app, x, y) {
|
|
221
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
222
|
+
throw new Error('Runtime pointer coordinates must be finite numbers.');
|
|
223
|
+
}
|
|
224
|
+
if (x < 0 || y < 0 || x > app.screen.width || y > app.screen.height) {
|
|
225
|
+
throw new Error(`Runtime pointer coordinates must stay inside ${app.screen.width} x ${app.screen.height}.`);
|
|
226
|
+
}
|
|
227
|
+
const bounds = app.canvas.getBoundingClientRect();
|
|
228
|
+
return {
|
|
229
|
+
clientX: bounds.left + (x / app.screen.width) * bounds.width,
|
|
230
|
+
clientY: bounds.top + (y / app.screen.height) * bounds.height,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function keyboardIdentity(key) {
|
|
234
|
+
if (key === 'Space')
|
|
235
|
+
return { key: ' ', code: 'Space' };
|
|
236
|
+
if (/^Key[A-Z]$/.test(key))
|
|
237
|
+
return { key: key.slice(3).toLowerCase(), code: key };
|
|
238
|
+
if (/^Digit[0-9]$/.test(key))
|
|
239
|
+
return { key: key.slice(5), code: key };
|
|
240
|
+
return { key, code: key };
|
|
241
|
+
}
|
|
242
|
+
function dispatchRuntimeInput(targetWindow, app, request) {
|
|
243
|
+
const eventWindow = targetWindow;
|
|
244
|
+
if (request.action === 'move' || request.action === 'click') {
|
|
245
|
+
const point = clientPoint(app, request.x, request.y);
|
|
246
|
+
app.canvas.dispatchEvent(pointerEvent(targetWindow, 'pointermove', point, 0));
|
|
247
|
+
if (request.action === 'click') {
|
|
248
|
+
app.canvas.dispatchEvent(pointerEvent(targetWindow, 'pointerdown', point, 1));
|
|
249
|
+
app.canvas.dispatchEvent(pointerEvent(targetWindow, 'pointerup', point, 0));
|
|
250
|
+
app.canvas.dispatchEvent(new eventWindow.MouseEvent('click', {
|
|
251
|
+
bubbles: true,
|
|
252
|
+
cancelable: true,
|
|
253
|
+
button: 0,
|
|
254
|
+
...point,
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (!('key' in request))
|
|
260
|
+
throw new Error('Runtime keyboard input requires a key.');
|
|
261
|
+
const identity = keyboardIdentity(request.key);
|
|
262
|
+
const dispatchKey = (type) => {
|
|
263
|
+
targetWindow.dispatchEvent(new eventWindow.KeyboardEvent(type, {
|
|
264
|
+
bubbles: true,
|
|
265
|
+
cancelable: true,
|
|
266
|
+
...identity,
|
|
267
|
+
}));
|
|
268
|
+
};
|
|
269
|
+
if (request.action === 'key' || request.action === 'keydown')
|
|
270
|
+
dispatchKey('keydown');
|
|
271
|
+
if (request.action === 'key' || request.action === 'keyup')
|
|
272
|
+
dispatchKey('keyup');
|
|
273
|
+
}
|
|
274
|
+
function cloneState(value) {
|
|
275
|
+
return JSON.parse(JSON.stringify(value));
|
|
276
|
+
}
|
|
277
|
+
function isHmrRequest(value) {
|
|
278
|
+
return typeof value === 'object'
|
|
279
|
+
&& value !== null
|
|
280
|
+
&& typeof value.requestId === 'string'
|
|
281
|
+
&& typeof value.runtimeId === 'string'
|
|
282
|
+
&& typeof value.request === 'object'
|
|
283
|
+
&& value.request !== null;
|
|
284
|
+
}
|
|
285
|
+
export function createPixifactRuntimeClient(options) {
|
|
286
|
+
const now = options.now ?? (() => new Date());
|
|
287
|
+
const logs = createRuntimeLogCapture(options.console, options.window, now);
|
|
288
|
+
let app;
|
|
289
|
+
let getState;
|
|
290
|
+
function descriptor() {
|
|
291
|
+
return {
|
|
292
|
+
runtimeId: options.runtimeId,
|
|
293
|
+
url: options.window.location.href,
|
|
294
|
+
title: options.window.document.title,
|
|
295
|
+
ready: app !== undefined,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function announce() {
|
|
299
|
+
options.hot.send(runtimeAnnounceEvent, descriptor());
|
|
300
|
+
}
|
|
301
|
+
function requireApplication() {
|
|
302
|
+
if (!app)
|
|
303
|
+
throw new Error('PixiJS Application has not been registered in this runtime.');
|
|
304
|
+
return app;
|
|
305
|
+
}
|
|
306
|
+
async function handleRequest(request) {
|
|
307
|
+
if (request.type === 'logs') {
|
|
308
|
+
return {
|
|
309
|
+
runtimeId: options.runtimeId,
|
|
310
|
+
latestSeq: logs.latestSeq(),
|
|
311
|
+
entries: logs.entries()
|
|
312
|
+
.filter((entry) => request.after === undefined || entry.seq > request.after)
|
|
313
|
+
.filter((entry) => request.level === undefined || entry.level === request.level)
|
|
314
|
+
.map((entry) => ({ ...entry })),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const currentApp = requireApplication();
|
|
318
|
+
if (request.type === 'tree') {
|
|
319
|
+
return {
|
|
320
|
+
runtimeId: options.runtimeId,
|
|
321
|
+
root: basicNodeSnapshot(currentApp.stage, null),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (request.type === 'node') {
|
|
325
|
+
const node = findStageNode(currentApp.stage, request.uid);
|
|
326
|
+
if (!node)
|
|
327
|
+
throw new Error(`PixiJS node uid ${request.uid} is not in app.stage.`);
|
|
328
|
+
return {
|
|
329
|
+
runtimeId: options.runtimeId,
|
|
330
|
+
node: detailedNodeSnapshot(node),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (request.type === 'state') {
|
|
334
|
+
return getState
|
|
335
|
+
? {
|
|
336
|
+
runtimeId: options.runtimeId,
|
|
337
|
+
available: true,
|
|
338
|
+
state: cloneState(getState()),
|
|
339
|
+
}
|
|
340
|
+
: {
|
|
341
|
+
runtimeId: options.runtimeId,
|
|
342
|
+
available: false,
|
|
343
|
+
state: null,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (request.type === 'input') {
|
|
347
|
+
dispatchRuntimeInput(options.window, currentApp, request);
|
|
348
|
+
return {
|
|
349
|
+
runtimeId: options.runtimeId,
|
|
350
|
+
dispatched: true,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
throw new Error('Unknown Pixifact Runtime request.');
|
|
354
|
+
}
|
|
355
|
+
options.hot.on(runtimeRequestEvent, (data) => {
|
|
356
|
+
if (!isHmrRequest(data) || data.runtimeId !== options.runtimeId)
|
|
357
|
+
return;
|
|
358
|
+
void handleRequest(data.request).then((result) => options.hot.send(runtimeResponseEvent, {
|
|
359
|
+
requestId: data.requestId,
|
|
360
|
+
runtimeId: options.runtimeId,
|
|
361
|
+
ok: true,
|
|
362
|
+
result,
|
|
363
|
+
}), (error) => options.hot.send(runtimeResponseEvent, {
|
|
364
|
+
requestId: data.requestId,
|
|
365
|
+
runtimeId: options.runtimeId,
|
|
366
|
+
ok: false,
|
|
367
|
+
error: error instanceof Error ? error.message : String(error),
|
|
368
|
+
}));
|
|
369
|
+
});
|
|
370
|
+
announce();
|
|
371
|
+
return {
|
|
372
|
+
register(application, registerOptions = {}) {
|
|
373
|
+
if (app)
|
|
374
|
+
throw new Error('This page already has a registered PixiJS Application.');
|
|
375
|
+
app = application;
|
|
376
|
+
getState = registerOptions.getState;
|
|
377
|
+
announce();
|
|
378
|
+
},
|
|
379
|
+
handleRequest,
|
|
380
|
+
dispose: logs.dispose,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { SceneClassDecorator, SceneEventDecoratorOptions, SceneMemberDecorator, ScenePartDecoratorOptions, ScenePropDecoratorOptions, SceneSlotDecoratorOptions } from '../compiler/spec.js';
|
|
1
|
+
import type { SceneClassDecorator, SceneEventDecoratorOptions, SceneMemberDecorator, ScenePartDecoratorOptions, ScenePropDecoratorOptions, SceneSlotDecoratorOptions, SceneVariants } from '../compiler/spec.js';
|
|
2
2
|
export declare function scene(): SceneClassDecorator;
|
|
3
3
|
export declare function prop(options: ScenePropDecoratorOptions): SceneMemberDecorator;
|
|
4
|
+
export declare function defineVariants<const TVariants extends SceneVariants>(variants: TVariants): TVariants;
|
|
4
5
|
export declare function event(_options?: SceneEventDecoratorOptions): SceneMemberDecorator;
|
|
5
6
|
export declare function slot(options?: SceneSlotDecoratorOptions): SceneMemberDecorator;
|
|
6
7
|
export declare function part(options?: ScenePartDecoratorOptions): SceneMemberDecorator;
|
package/dist/scene/decorators.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
import { activateRuntimeTree } from '../runtime/runtimeBehavior.js';
|
|
1
2
|
import { mountSceneClass } from './sceneRuntime.js';
|
|
3
|
+
import { initializeSceneProps } from './sceneBindingRuntime.js';
|
|
2
4
|
const metadataByConstructor = new WeakMap();
|
|
3
5
|
export function scene() {
|
|
4
6
|
return ((constructor) => {
|
|
5
7
|
const SceneClass = class extends constructor {
|
|
6
8
|
constructor(...args) {
|
|
7
|
-
super(
|
|
9
|
+
super();
|
|
8
10
|
if (new.target !== SceneClass) {
|
|
9
11
|
return;
|
|
10
12
|
}
|
|
11
|
-
const result = mountSceneClass(this, SceneClass);
|
|
12
13
|
const metadata = sceneMetadata(constructor);
|
|
14
|
+
const initialProps = args[0] && typeof args[0] === 'object'
|
|
15
|
+
? args[0]
|
|
16
|
+
: {};
|
|
17
|
+
initializeSceneProps(this, metadata.props, initialProps);
|
|
18
|
+
const result = mountSceneClass(this, SceneClass);
|
|
13
19
|
for (const [property, id] of metadata.parts) {
|
|
14
20
|
Object.defineProperty(this, property, {
|
|
15
21
|
configurable: true,
|
|
@@ -26,11 +32,7 @@ export function scene() {
|
|
|
26
32
|
writable: false,
|
|
27
33
|
});
|
|
28
34
|
}
|
|
29
|
-
|
|
30
|
-
if (propOptions.default !== undefined) {
|
|
31
|
-
this[property] = propOptions.default;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
35
|
+
activateRuntimeTree(this);
|
|
34
36
|
const ready = this.onMounted;
|
|
35
37
|
if (ready) {
|
|
36
38
|
ready.call(this);
|
|
@@ -46,6 +48,9 @@ export function prop(options) {
|
|
|
46
48
|
sceneMetadata(target.constructor).props.set(memberName(propertyKey), options);
|
|
47
49
|
});
|
|
48
50
|
}
|
|
51
|
+
export function defineVariants(variants) {
|
|
52
|
+
return variants;
|
|
53
|
+
}
|
|
49
54
|
export function event(_options = {}) {
|
|
50
55
|
return noopMemberDecorator;
|
|
51
56
|
}
|
package/dist/scene/index.d.ts
CHANGED
package/dist/scene/index.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Container } from 'pixi.js';
|
|
2
|
+
import type { ScenePropDecoratorOptions, SceneTemplateBindingValue, SceneTemplateInterface, SceneTemplateScalarValue } from '../compiler/spec.js';
|
|
3
|
+
export declare function initializeSceneProps(target: object, definitions: ReadonlyMap<string, ScenePropDecoratorOptions>, initialProps?: Record<string, unknown>): void;
|
|
4
|
+
export declare function initializeScenePropsFromInterface(target: object, sceneInterface: SceneTemplateInterface, initialProps?: Record<string, unknown>): void;
|
|
5
|
+
export declare function setSceneProp(target: object, name: string, value: unknown): void;
|
|
6
|
+
export declare function getSceneProp<T = unknown>(target: object, name: string): T;
|
|
7
|
+
export declare function readSceneBindingValue<T = unknown>(target: object, binding: SceneTemplateBindingValue): T;
|
|
8
|
+
export declare function bindSceneNodeProp(root: object, binding: SceneTemplateBindingValue, target: Container, prop: string): void;
|
|
9
|
+
export declare function applySceneNodeProp(target: Container, prop: string, sourceValue: unknown): void;
|
|
10
|
+
export type SceneBindingInitialValue = SceneTemplateScalarValue | object;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { setFrameLayout } from '../runtime/index.js';
|
|
2
|
+
const states = new WeakMap();
|
|
3
|
+
const layoutProps = new Set(['left', 'right', 'top', 'bottom', 'horizontal', 'vertical']);
|
|
4
|
+
const textStyleProps = new Set(['fontSize', 'fontFamily', 'fontWeight', 'fill']);
|
|
5
|
+
const colorProps = new Set(['fill', 'fillColor', 'strokeColor', 'tint']);
|
|
6
|
+
export function initializeSceneProps(target, definitions, initialProps = {}) {
|
|
7
|
+
const state = {
|
|
8
|
+
definitions: Object.fromEntries(definitions),
|
|
9
|
+
subscribers: new Map(),
|
|
10
|
+
values: {},
|
|
11
|
+
};
|
|
12
|
+
states.set(target, state);
|
|
13
|
+
for (const [name, definition] of Object.entries(state.definitions)) {
|
|
14
|
+
state.values[name] = Object.hasOwn(initialProps, name)
|
|
15
|
+
? initialProps[name]
|
|
16
|
+
: definition.default;
|
|
17
|
+
Object.defineProperty(target, name, {
|
|
18
|
+
configurable: true,
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: () => state.values[name],
|
|
21
|
+
set: (value) => setSceneProp(target, name, value),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function initializeScenePropsFromInterface(target, sceneInterface, initialProps = {}) {
|
|
26
|
+
initializeSceneProps(target, new Map(Object.entries(sceneInterface.props).map(([name, contract]) => [
|
|
27
|
+
name,
|
|
28
|
+
{
|
|
29
|
+
...('default' in contract ? { default: contract.default } : {}),
|
|
30
|
+
...(contract.type === 'variant' ? { variants: contract.variants } : {}),
|
|
31
|
+
},
|
|
32
|
+
])), initialProps);
|
|
33
|
+
}
|
|
34
|
+
export function setSceneProp(target, name, value) {
|
|
35
|
+
const state = sceneBindingState(target);
|
|
36
|
+
if (Object.is(state.values[name], value)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
state.values[name] = value;
|
|
40
|
+
for (const update of state.subscribers.get(name) ?? []) {
|
|
41
|
+
update();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function getSceneProp(target, name) {
|
|
45
|
+
return sceneBindingState(target).values[name];
|
|
46
|
+
}
|
|
47
|
+
export function readSceneBindingValue(target, binding) {
|
|
48
|
+
const state = sceneBindingState(target);
|
|
49
|
+
const [prop, field] = binding.path;
|
|
50
|
+
const value = state.values[prop];
|
|
51
|
+
if (!field) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
const selected = String(value);
|
|
55
|
+
return state.definitions[prop]?.variants?.[selected]?.[field];
|
|
56
|
+
}
|
|
57
|
+
export function bindSceneNodeProp(root, binding, target, prop) {
|
|
58
|
+
const state = sceneBindingState(root);
|
|
59
|
+
const update = () => applySceneNodeProp(target, prop, readSceneBindingValue(root, binding));
|
|
60
|
+
const subscribers = state.subscribers.get(binding.path[0]) ?? new Set();
|
|
61
|
+
subscribers.add(update);
|
|
62
|
+
state.subscribers.set(binding.path[0], subscribers);
|
|
63
|
+
update();
|
|
64
|
+
}
|
|
65
|
+
export function applySceneNodeProp(target, prop, sourceValue) {
|
|
66
|
+
if (prop === 'text') {
|
|
67
|
+
target.text = String(sourceValue ?? '');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const value = normalizeBindingValue(prop, sourceValue);
|
|
71
|
+
if (layoutProps.has(prop)) {
|
|
72
|
+
setFrameLayout(target, { [prop]: value });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const record = target;
|
|
76
|
+
if (textStyleProps.has(prop) && 'text' in record && record.style) {
|
|
77
|
+
record.style[prop] = value;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (prop === 'scaleX' || prop === 'scaleY') {
|
|
81
|
+
const point = record.scale;
|
|
82
|
+
point[prop === 'scaleX' ? 'x' : 'y'] = value;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (prop === 'pivotX' || prop === 'pivotY') {
|
|
86
|
+
const point = record.pivot;
|
|
87
|
+
point[prop === 'pivotX' ? 'x' : 'y'] = value;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (prop === 'skewX' || prop === 'skewY') {
|
|
91
|
+
const point = record.skew;
|
|
92
|
+
point[prop === 'skewX' ? 'x' : 'y'] = value;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (prop === 'anchorX' || prop === 'anchorY') {
|
|
96
|
+
const point = record.anchor;
|
|
97
|
+
point[prop === 'anchorX' ? 'x' : 'y'] = value;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (prop === 'tilePositionX' || prop === 'tilePositionY') {
|
|
101
|
+
const point = record.tilePosition;
|
|
102
|
+
point[prop === 'tilePositionX' ? 'x' : 'y'] = value;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (prop === 'tileScaleX' || prop === 'tileScaleY') {
|
|
106
|
+
const point = record.tileScale;
|
|
107
|
+
point[prop === 'tileScaleX' ? 'x' : 'y'] = value;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
record[prop] = value;
|
|
111
|
+
}
|
|
112
|
+
function normalizeBindingValue(prop, value) {
|
|
113
|
+
if (colorProps.has(prop) && typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value)) {
|
|
114
|
+
return Number.parseInt(value.slice(1), 16);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
function sceneBindingState(target) {
|
|
119
|
+
const state = states.get(target);
|
|
120
|
+
if (!state) {
|
|
121
|
+
throw new Error('Scene props must be initialized before bindings are mounted.');
|
|
122
|
+
}
|
|
123
|
+
return state;
|
|
124
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixifact",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Pixifact 2D UI 和轻量游戏 Scene 语义框架。",
|
|
5
5
|
"packageManager": "bun@1.3.13",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -42,6 +42,11 @@
|
|
|
42
42
|
"import": "./dist/runtime/index.js",
|
|
43
43
|
"require": "./dist/runtime/index.js"
|
|
44
44
|
},
|
|
45
|
+
"./runtime-dev": {
|
|
46
|
+
"types": "./dist/runtime-dev/index.d.ts",
|
|
47
|
+
"import": "./dist/runtime-dev/index.js",
|
|
48
|
+
"require": "./dist/runtime-dev/index.js"
|
|
49
|
+
},
|
|
45
50
|
"./scene": {
|
|
46
51
|
"types": "./dist/scene/index.d.ts",
|
|
47
52
|
"import": "./dist/scene/index.js",
|