pixifact 0.4.0 → 0.5.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.
Files changed (41) hide show
  1. package/dist/compiler/builtinScenes.d.ts +0 -5
  2. package/dist/compiler/builtinScenes.js +0 -26
  3. package/dist/compiler/commands/index.js +8 -5
  4. package/dist/compiler/compileScenes.d.ts +3 -1
  5. package/dist/compiler/compileScenes.js +32 -2
  6. package/dist/compiler/index.d.ts +0 -1
  7. package/dist/compiler/index.js +0 -1
  8. package/dist/compiler/pixiNodeSchema.d.ts +4 -3
  9. package/dist/compiler/pixiNodeSchema.js +60 -6
  10. package/dist/compiler/sceneValidation.d.ts +3 -1
  11. package/dist/compiler/sceneValidation.js +105 -6
  12. package/dist/compiler/spec.d.ts +18 -7
  13. package/dist/compiler/spec.js +3 -1
  14. package/dist/compiler/templateParser.js +19 -0
  15. package/dist/compiler/templateSerializer.js +7 -1
  16. package/dist/compiler/typescriptCompiler.js +66 -8
  17. package/dist/compiler-node/builtinSceneInterfaces.d.ts +6 -0
  18. package/dist/compiler-node/builtinSceneInterfaces.js +27 -0
  19. package/dist/compiler-node/index.d.ts +2 -0
  20. package/dist/compiler-node/index.js +2 -0
  21. package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.d.ts +1 -1
  22. package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.js +157 -25
  23. package/dist/runtime/BitmapLabel.d.ts +9 -0
  24. package/dist/runtime/BitmapLabel.js +7 -0
  25. package/dist/runtime/Label.d.ts +9 -0
  26. package/dist/runtime/Label.js +7 -0
  27. package/dist/runtime/ScrollContainer.d.ts +2 -0
  28. package/dist/runtime/ScrollContainer.js +12 -2
  29. package/dist/runtime/TextBoxControl.d.ts +48 -0
  30. package/dist/runtime/TextBoxControl.js +162 -0
  31. package/dist/runtime/index.d.ts +2 -0
  32. package/dist/runtime/index.js +2 -0
  33. package/dist/runtime/runtimeBehavior.d.ts +3 -0
  34. package/dist/runtime/runtimeBehavior.js +11 -0
  35. package/dist/scene/decorators.d.ts +2 -1
  36. package/dist/scene/decorators.js +12 -7
  37. package/dist/scene/index.d.ts +1 -0
  38. package/dist/scene/index.js +1 -0
  39. package/dist/scene/sceneBindingRuntime.d.ts +10 -0
  40. package/dist/scene/sceneBindingRuntime.js +124 -0
  41. package/package.json +1 -1
@@ -0,0 +1,162 @@
1
+ import { Graphics, } from 'pixi.js';
2
+ import { Control } from './Control.js';
3
+ export class TextBoxControl extends Control {
4
+ #textNode;
5
+ #clipMask = new Graphics();
6
+ #fontFamily = 'Arial';
7
+ #fontSize = 16;
8
+ #fontWeight = 400;
9
+ #fill = 0x111827;
10
+ #lineHeight = 0;
11
+ #letterSpacing = 0;
12
+ #wordWrap = false;
13
+ #alignX = 'start';
14
+ #alignY = 'start';
15
+ #overflow = 'visible';
16
+ constructor(options, createTextNode) {
17
+ const { text = 'Text', width = 120, height = 28, fontFamily = 'Arial', fontSize = 16, fontWeight = 400, fill = 0x111827, lineHeight = 0, letterSpacing = 0, wordWrap = false, alignX = 'start', alignY = 'start', overflow = 'visible', ...groupOptions } = options;
18
+ super({ ...groupOptions, width, height });
19
+ this.#fontFamily = fontFamily;
20
+ this.#fontSize = fontSize;
21
+ this.#fontWeight = fontWeight;
22
+ this.#fill = fill;
23
+ this.#lineHeight = lineHeight;
24
+ this.#letterSpacing = letterSpacing;
25
+ this.#wordWrap = wordWrap;
26
+ this.#alignX = alignX;
27
+ this.#alignY = alignY;
28
+ this.#overflow = overflow;
29
+ this.#textNode = createTextNode({
30
+ text,
31
+ style: {
32
+ fontFamily,
33
+ fontSize,
34
+ fontWeight: String(fontWeight),
35
+ fill,
36
+ lineHeight,
37
+ letterSpacing,
38
+ wordWrap,
39
+ wordWrapWidth: width,
40
+ align: horizontalTextAlign(alignX),
41
+ },
42
+ });
43
+ super.addChild(this.#textNode, this.#clipMask);
44
+ this.layout();
45
+ }
46
+ get text() {
47
+ return this.#textNode.text;
48
+ }
49
+ set text(value) {
50
+ this.#textNode.text = value;
51
+ this.layout();
52
+ }
53
+ get fontFamily() {
54
+ return this.#fontFamily;
55
+ }
56
+ set fontFamily(value) {
57
+ this.#fontFamily = value;
58
+ this.#textNode.style.fontFamily = value;
59
+ this.layout();
60
+ }
61
+ get fontSize() {
62
+ return this.#fontSize;
63
+ }
64
+ set fontSize(value) {
65
+ this.#fontSize = value;
66
+ this.#textNode.style.fontSize = value;
67
+ this.layout();
68
+ }
69
+ get fontWeight() {
70
+ return this.#fontWeight;
71
+ }
72
+ set fontWeight(value) {
73
+ this.#fontWeight = value;
74
+ this.#textNode.style.fontWeight = String(value);
75
+ this.layout();
76
+ }
77
+ get fill() {
78
+ return this.#fill;
79
+ }
80
+ set fill(value) {
81
+ this.#fill = value;
82
+ this.#textNode.style.fill = value;
83
+ this.layout();
84
+ }
85
+ get lineHeight() {
86
+ return this.#lineHeight;
87
+ }
88
+ set lineHeight(value) {
89
+ this.#lineHeight = value;
90
+ this.#textNode.style.lineHeight = value;
91
+ this.layout();
92
+ }
93
+ get letterSpacing() {
94
+ return this.#letterSpacing;
95
+ }
96
+ set letterSpacing(value) {
97
+ this.#letterSpacing = value;
98
+ this.#textNode.style.letterSpacing = value;
99
+ this.layout();
100
+ }
101
+ get wordWrap() {
102
+ return this.#wordWrap;
103
+ }
104
+ set wordWrap(value) {
105
+ this.#wordWrap = value;
106
+ this.#textNode.style.wordWrap = value;
107
+ this.layout();
108
+ }
109
+ get alignX() {
110
+ return this.#alignX;
111
+ }
112
+ set alignX(value) {
113
+ this.#alignX = value;
114
+ this.#textNode.style.align = horizontalTextAlign(value);
115
+ this.layout();
116
+ }
117
+ get alignY() {
118
+ return this.#alignY;
119
+ }
120
+ set alignY(value) {
121
+ this.#alignY = value;
122
+ this.layout();
123
+ }
124
+ get overflow() {
125
+ return this.#overflow;
126
+ }
127
+ set overflow(value) {
128
+ this.#overflow = value;
129
+ this.layout();
130
+ }
131
+ layout() {
132
+ super.layout();
133
+ if (!this.#textNode) {
134
+ return;
135
+ }
136
+ this.#textNode.style.wordWrapWidth = this.width;
137
+ this.#textNode.anchor.set(alignmentFactor(this.#alignX), alignmentFactor(this.#alignY));
138
+ this.#textNode.position.set(this.width * alignmentFactor(this.#alignX), this.height * alignmentFactor(this.#alignY));
139
+ this.#clipMask.clear().rect(0, 0, this.width, this.height).fill(0xffffff);
140
+ const clip = this.#overflow === 'clip';
141
+ this.#clipMask.visible = clip;
142
+ this.#textNode.mask = clip ? this.#clipMask : null;
143
+ }
144
+ }
145
+ function horizontalTextAlign(align) {
146
+ if (align === 'center') {
147
+ return 'center';
148
+ }
149
+ if (align === 'end') {
150
+ return 'right';
151
+ }
152
+ return 'left';
153
+ }
154
+ function alignmentFactor(align) {
155
+ if (align === 'center') {
156
+ return 0.5;
157
+ }
158
+ if (align === 'end') {
159
+ return 1;
160
+ }
161
+ return 0;
162
+ }
@@ -1,5 +1,7 @@
1
1
  export * from './Group.js';
2
2
  export * from './Control.js';
3
+ export * from './Label.js';
4
+ export * from './BitmapLabel.js';
3
5
  export * from './Rect.js';
4
6
  export * from './Image.js';
5
7
  export * from './NineImage.js';
@@ -1,5 +1,7 @@
1
1
  export * from './Group.js';
2
2
  export * from './Control.js';
3
+ export * from './Label.js';
4
+ export * from './BitmapLabel.js';
3
5
  export * from './Rect.js';
4
6
  export * from './Image.js';
5
7
  export * from './NineImage.js';
@@ -0,0 +1,3 @@
1
+ import type { Container } from 'pixi.js';
2
+ export declare const runtimeBehavior: unique symbol;
3
+ export declare function activateRuntimeTree(root: Container): void;
@@ -0,0 +1,11 @@
1
+ export const runtimeBehavior = Symbol('pixifact.runtimeBehavior');
2
+ const activatedContainers = new WeakSet();
3
+ export function activateRuntimeTree(root) {
4
+ if (runtimeBehavior in root && !activatedContainers.has(root)) {
5
+ activatedContainers.add(root);
6
+ root[runtimeBehavior]();
7
+ }
8
+ for (const child of root.children) {
9
+ activateRuntimeTree(child);
10
+ }
11
+ }
@@ -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;
@@ -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(...args);
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
- for (const [property, propOptions] of metadata.props) {
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
  }
@@ -1,5 +1,6 @@
1
1
  export * from './decorators.js';
2
2
  export * from './eventRuntime.js';
3
3
  export * from './sceneAssetRuntime.js';
4
+ export * from './sceneBindingRuntime.js';
4
5
  export * from './sceneRuntime.js';
5
6
  export * from './slotRuntime.js';
@@ -1,5 +1,6 @@
1
1
  export * from './decorators.js';
2
2
  export * from './eventRuntime.js';
3
3
  export * from './sceneAssetRuntime.js';
4
+ export * from './sceneBindingRuntime.js';
4
5
  export * from './sceneRuntime.js';
5
6
  export * from './slotRuntime.js';
@@ -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.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Pixifact 2D UI 和轻量游戏 Scene 语义框架。",
5
5
  "packageManager": "bun@1.3.13",
6
6
  "main": "./dist/index.js",