canvasengine 2.0.0-beta.2 → 2.0.0-beta.20
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/index.d.ts +1285 -0
- package/dist/index.js +4150 -0
- package/dist/index.js.map +1 -0
- package/index.d.ts +4 -0
- package/package.json +5 -12
- package/src/components/Canvas.ts +53 -45
- package/src/components/Container.ts +2 -2
- package/src/components/DOMContainer.ts +229 -0
- package/src/components/DisplayObject.ts +263 -189
- package/src/components/Graphic.ts +213 -36
- package/src/components/Mesh.ts +222 -0
- package/src/components/NineSliceSprite.ts +4 -1
- package/src/components/ParticleEmitter.ts +12 -8
- package/src/components/Sprite.ts +77 -14
- package/src/components/Text.ts +34 -14
- package/src/components/Video.ts +110 -0
- package/src/components/Viewport.ts +59 -43
- package/src/components/index.ts +5 -4
- package/src/components/types/DisplayObject.ts +30 -0
- package/src/directives/Drag.ts +357 -52
- package/src/directives/KeyboardControls.ts +3 -1
- package/src/directives/Sound.ts +94 -31
- package/src/directives/ViewportFollow.ts +35 -7
- package/src/engine/animation.ts +41 -5
- package/src/engine/bootstrap.ts +13 -2
- package/src/engine/directive.ts +2 -2
- package/src/engine/reactive.ts +336 -168
- package/src/engine/trigger.ts +65 -9
- package/src/engine/utils.ts +92 -9
- package/src/hooks/useProps.ts +1 -1
- package/src/index.ts +5 -1
- package/src/utils/RadialGradient.ts +29 -0
- package/src/utils/functions.ts +7 -0
- package/testing/index.ts +12 -0
- package/src/components/DrawMap/index.ts +0 -65
- package/src/components/Tilemap/Tile.ts +0 -79
- package/src/components/Tilemap/TileGroup.ts +0 -207
- package/src/components/Tilemap/TileLayer.ts +0 -163
- package/src/components/Tilemap/TileSet.ts +0 -41
- package/src/components/Tilemap/index.ts +0 -80
package/index.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvasengine",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@barvynkoa/particle-emitter": "^0.0.1",
|
|
9
|
-
"@pixi/
|
|
10
|
-
"@
|
|
11
|
-
"@signe/reactive": "^1.0.1",
|
|
9
|
+
"@pixi/layout": "^3.0.2",
|
|
10
|
+
"@signe/reactive": "^2.3.3",
|
|
12
11
|
"howler": "^2.2.4",
|
|
13
|
-
"package.json": "^2.0.1",
|
|
14
12
|
"pixi-filters": "^6.0.5",
|
|
15
13
|
"pixi-viewport": "^6.0.3",
|
|
16
|
-
"pixi.js": "^8.
|
|
14
|
+
"pixi.js": "^8.9.2",
|
|
17
15
|
"popmotion": "^11.0.5",
|
|
18
16
|
"rxjs": "^7.8.1",
|
|
19
|
-
"yoga-layout": "^2.
|
|
17
|
+
"yoga-layout": "^3.2.1"
|
|
20
18
|
},
|
|
21
19
|
"devDependencies": {
|
|
22
20
|
"@types/howler": "^2.2.11"
|
|
@@ -40,11 +38,6 @@
|
|
|
40
38
|
"publishConfig": {
|
|
41
39
|
"access": "public"
|
|
42
40
|
},
|
|
43
|
-
"overrides": {
|
|
44
|
-
"revolt-fx": {
|
|
45
|
-
"pixi.js": "^8.6.4"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
41
|
"scripts": {
|
|
49
42
|
"build": "tsup",
|
|
50
43
|
"dev": "tsup --watch"
|
package/src/components/Canvas.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { effect, Signal, signal } from "@signe/reactive";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import { Application, Container } from "pixi.js";
|
|
3
|
+
import {
|
|
4
|
+
Props,
|
|
5
|
+
createComponent,
|
|
6
|
+
registerComponent,
|
|
7
|
+
Element,
|
|
8
|
+
} from "../engine/reactive";
|
|
5
9
|
import { useProps } from "../hooks/useProps";
|
|
6
10
|
import { ComponentInstance, DisplayObject } from "./DisplayObject";
|
|
7
11
|
import { ComponentFunction } from "../engine/signal";
|
|
@@ -10,12 +14,12 @@ import { Size } from "./types/DisplayObject";
|
|
|
10
14
|
import { Scheduler, Tick } from "../directives/Scheduler";
|
|
11
15
|
|
|
12
16
|
interface CanvasElement extends Element<ComponentInstance> {
|
|
13
|
-
render: (rootElement: HTMLElement) => void;
|
|
17
|
+
render: (rootElement: HTMLElement, app?: Application) => void;
|
|
14
18
|
directives: {
|
|
15
|
-
tick: Scheduler
|
|
19
|
+
tick: Scheduler;
|
|
16
20
|
};
|
|
17
21
|
propObservables: {
|
|
18
|
-
tick: Signal<Tick
|
|
22
|
+
tick: Signal<Tick>;
|
|
19
23
|
};
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -30,33 +34,26 @@ export interface CanvasProps extends Props {
|
|
|
30
34
|
isRoot?: boolean;
|
|
31
35
|
tick?: any;
|
|
32
36
|
class?: SignalOrPrimitive<string>;
|
|
37
|
+
background?: string;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
|
|
36
41
|
let { cursorStyles, width, height, class: className } = useProps(props);
|
|
37
|
-
const Yoga = await loadYoga();
|
|
38
42
|
|
|
39
|
-
if (!props.width) width = signal<Size>(800)
|
|
40
|
-
if (!props.height) height = signal<Size>(600)
|
|
41
|
-
|
|
42
|
-
const renderer = await autoDetectRenderer({
|
|
43
|
-
...props,
|
|
44
|
-
width: width?.(),
|
|
45
|
-
height: height?.(),
|
|
46
|
-
});
|
|
43
|
+
if (!props.width) width = signal<Size>(800);
|
|
44
|
+
if (!props.height) height = signal<Size>(600);
|
|
47
45
|
|
|
48
46
|
const canvasSize = signal({
|
|
49
|
-
width:
|
|
50
|
-
height:
|
|
47
|
+
width: 0,
|
|
48
|
+
height: 0,
|
|
51
49
|
});
|
|
52
50
|
|
|
53
51
|
props.isRoot = true;
|
|
54
52
|
const options: CanvasProps = {
|
|
55
53
|
...props,
|
|
56
54
|
context: {
|
|
57
|
-
Yoga,
|
|
58
|
-
renderer,
|
|
59
55
|
canvasSize,
|
|
56
|
+
app: signal(null),
|
|
60
57
|
},
|
|
61
58
|
width: width?.(),
|
|
62
59
|
height: height?.(),
|
|
@@ -70,9 +67,15 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
|
|
|
70
67
|
deltaRatio: 1,
|
|
71
68
|
});
|
|
72
69
|
}
|
|
70
|
+
|
|
73
71
|
const canvasElement = createComponent("Canvas", options) as CanvasElement;
|
|
74
72
|
|
|
75
|
-
canvasElement.render = (rootElement: HTMLElement) => {
|
|
73
|
+
canvasElement.render = (rootElement: HTMLElement, app?: Application) => {
|
|
74
|
+
if (!app) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const renderer = app.renderer;
|
|
76
79
|
const canvasEl = renderer.view.canvas as HTMLCanvasElement;
|
|
77
80
|
|
|
78
81
|
(globalThis as any).__PIXI_STAGE__ = canvasElement.componentInstance;
|
|
@@ -85,6 +88,34 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
|
|
|
85
88
|
renderer.render(canvasElement.componentInstance as any);
|
|
86
89
|
});
|
|
87
90
|
|
|
91
|
+
app.stage = canvasElement.componentInstance as any;
|
|
92
|
+
|
|
93
|
+
app.stage.layout = {
|
|
94
|
+
width: app.screen.width,
|
|
95
|
+
height: app.screen.height,
|
|
96
|
+
justifyContent: props.justifyContent,
|
|
97
|
+
alignItems: props.alignItems,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
canvasSize.set({ width: app.screen.width, height: app.screen.height })
|
|
101
|
+
|
|
102
|
+
app.renderer.on('resize', (width: number, height: number) => {
|
|
103
|
+
canvasSize.set({ width, height });
|
|
104
|
+
|
|
105
|
+
if (app.stage.layout) {
|
|
106
|
+
app.stage.layout = {
|
|
107
|
+
width,
|
|
108
|
+
height
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (props.tickStart !== false) canvasElement.directives.tick.start();
|
|
114
|
+
|
|
115
|
+
app.ticker.add(() => {
|
|
116
|
+
canvasElement.propObservables!.tick();
|
|
117
|
+
});
|
|
118
|
+
|
|
88
119
|
if (cursorStyles) {
|
|
89
120
|
effect(() => {
|
|
90
121
|
renderer.events.cursorStyles = cursorStyles();
|
|
@@ -97,37 +128,14 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
|
|
|
97
128
|
});
|
|
98
129
|
}
|
|
99
130
|
|
|
100
|
-
const
|
|
101
|
-
let w, h;
|
|
102
|
-
if (width?.() === "100%" && height?.() === "100%") {
|
|
103
|
-
const parent = canvasEl.parentElement;
|
|
104
|
-
w = parent ? parent.clientWidth : window.innerWidth;
|
|
105
|
-
h = parent ? parent.clientHeight : window.innerHeight;
|
|
106
|
-
} else {
|
|
107
|
-
w = width?.() ?? canvasEl.offsetWidth;
|
|
108
|
-
h = height?.() ?? canvasEl.offsetHeight;
|
|
109
|
-
}
|
|
110
|
-
renderer.resize(w, h);
|
|
111
|
-
canvasSize.set({ width: w, height: h });
|
|
112
|
-
canvasElement.componentInstance.setWidth(w)
|
|
113
|
-
canvasElement.componentInstance.setHeight(h)
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// Listen for window resize events
|
|
117
|
-
window.addEventListener("resize", resizeCanvas);
|
|
118
|
-
|
|
119
|
-
// Check if a canvas already exists in the rootElement
|
|
120
|
-
const existingCanvas = rootElement.querySelector('canvas');
|
|
131
|
+
const existingCanvas = rootElement.querySelector("canvas");
|
|
121
132
|
if (existingCanvas) {
|
|
122
|
-
// If it exists, replace it with the new canvas
|
|
123
133
|
rootElement.replaceChild(canvasEl, existingCanvas);
|
|
124
134
|
} else {
|
|
125
|
-
// If it doesn't exist, append the new canvas
|
|
126
135
|
rootElement.appendChild(canvasEl);
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
|
|
130
|
-
resizeCanvas();
|
|
138
|
+
options.context!.app.set(app)
|
|
131
139
|
};
|
|
132
140
|
|
|
133
141
|
return canvasElement;
|
|
@@ -25,8 +25,8 @@ export class CanvasContainer extends DisplayObject(PixiContainer) {
|
|
|
25
25
|
this.sortableChildren = props.sortableChildren;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
onMount(args) {
|
|
29
|
-
super.onMount(args);
|
|
28
|
+
async onMount(args) {
|
|
29
|
+
await super.onMount(args);
|
|
30
30
|
const { componentInstance, props } = args;
|
|
31
31
|
const { pixiChildren } = props;
|
|
32
32
|
if (pixiChildren) {
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { DOMContainer as PixiDOMContainer } from "pixi.js";
|
|
2
|
+
import {
|
|
3
|
+
createComponent,
|
|
4
|
+
Element,
|
|
5
|
+
registerComponent,
|
|
6
|
+
} from "../engine/reactive";
|
|
7
|
+
import { ComponentInstance, DisplayObject } from "./DisplayObject";
|
|
8
|
+
import { ComponentFunction } from "../engine/signal";
|
|
9
|
+
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
10
|
+
|
|
11
|
+
interface DOMContainerProps extends DisplayObjectProps {
|
|
12
|
+
element:
|
|
13
|
+
| string
|
|
14
|
+
| {
|
|
15
|
+
value: HTMLElement;
|
|
16
|
+
};
|
|
17
|
+
textContent?: string;
|
|
18
|
+
attrs?: Record<string, any> & {
|
|
19
|
+
class?:
|
|
20
|
+
| string
|
|
21
|
+
| string[]
|
|
22
|
+
| Record<string, boolean>
|
|
23
|
+
| { items?: string[] }
|
|
24
|
+
| { value?: string | string[] | Record<string, boolean> };
|
|
25
|
+
style?:
|
|
26
|
+
| string
|
|
27
|
+
| Record<string, string | number>
|
|
28
|
+
| { value?: string | Record<string, string | number> };
|
|
29
|
+
};
|
|
30
|
+
sortableChildren?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* DOMContainer class for managing DOM elements within the canvas engine
|
|
35
|
+
*
|
|
36
|
+
* This class extends the DisplayObject functionality to handle DOM elements using
|
|
37
|
+
* PixiJS's native DOMContainer. It provides a bridge between the canvas rendering
|
|
38
|
+
* system and traditional DOM manipulation with proper transform hierarchy and visibility.
|
|
39
|
+
*
|
|
40
|
+
* The DOMContainer is especially useful for rendering standard DOM elements that handle
|
|
41
|
+
* user input, such as `<input>` or `<textarea>`. This is often simpler and more flexible
|
|
42
|
+
* than trying to implement text input directly in PixiJS.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // Basic usage with input element
|
|
47
|
+
* const element = document.createElement('input');
|
|
48
|
+
* element.type = 'text';
|
|
49
|
+
* element.placeholder = 'Enter text...';
|
|
50
|
+
*
|
|
51
|
+
* const domContainer = new DOMContainer({
|
|
52
|
+
* element,
|
|
53
|
+
* x: 100,
|
|
54
|
+
* y: 50,
|
|
55
|
+
* anchor: { x: 0.5, y: 0.5 }
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* // Using different class and style formats
|
|
59
|
+
* const containerWithClasses = new DOMContainer({
|
|
60
|
+
* element: 'div',
|
|
61
|
+
* attrs: {
|
|
62
|
+
* // String format: space-separated classes
|
|
63
|
+
* class: 'container primary-theme',
|
|
64
|
+
*
|
|
65
|
+
* // Array format: array of class names
|
|
66
|
+
* // class: ['container', 'primary-theme'],
|
|
67
|
+
*
|
|
68
|
+
* // Object format: conditional classes
|
|
69
|
+
* // class: {
|
|
70
|
+
* // 'container': true,
|
|
71
|
+
* // 'primary-theme': true,
|
|
72
|
+
* // 'disabled': false
|
|
73
|
+
* // }
|
|
74
|
+
*
|
|
75
|
+
* // String format: CSS style string
|
|
76
|
+
* style: 'background-color: red; padding: 10px;',
|
|
77
|
+
*
|
|
78
|
+
* // Object format: style properties
|
|
79
|
+
* // style: {
|
|
80
|
+
* // backgroundColor: 'red',
|
|
81
|
+
* // padding: '10px',
|
|
82
|
+
* // fontSize: 16
|
|
83
|
+
* // }
|
|
84
|
+
* }
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
const EVENTS = [
|
|
89
|
+
"click",
|
|
90
|
+
"mouseover",
|
|
91
|
+
"mouseout",
|
|
92
|
+
"mouseenter",
|
|
93
|
+
"mouseleave",
|
|
94
|
+
"mousemove",
|
|
95
|
+
"mouseup",
|
|
96
|
+
"mousedown",
|
|
97
|
+
"touchstart",
|
|
98
|
+
"touchend",
|
|
99
|
+
"touchmove",
|
|
100
|
+
"touchcancel",
|
|
101
|
+
"wheel",
|
|
102
|
+
"scroll",
|
|
103
|
+
"resize",
|
|
104
|
+
"focus",
|
|
105
|
+
"blur",
|
|
106
|
+
"change",
|
|
107
|
+
"input",
|
|
108
|
+
"submit",
|
|
109
|
+
"reset",
|
|
110
|
+
"keydown",
|
|
111
|
+
"keyup",
|
|
112
|
+
"keypress",
|
|
113
|
+
"contextmenu",
|
|
114
|
+
"drag",
|
|
115
|
+
"dragend",
|
|
116
|
+
"dragenter",
|
|
117
|
+
"dragleave",
|
|
118
|
+
"dragover",
|
|
119
|
+
"drop",
|
|
120
|
+
"dragstart",
|
|
121
|
+
"select",
|
|
122
|
+
"selectstart",
|
|
123
|
+
"selectend",
|
|
124
|
+
"selectall",
|
|
125
|
+
"selectnone",
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
export class CanvasDOMContainer extends DisplayObject(PixiDOMContainer) {
|
|
129
|
+
disableLayout = true;
|
|
130
|
+
private eventListeners: Map<string, (e: Event) => void> = new Map();
|
|
131
|
+
|
|
132
|
+
onInit(props: DOMContainerProps) {
|
|
133
|
+
super.onInit(props);
|
|
134
|
+
if (props.element === undefined) {
|
|
135
|
+
throw new Error("DOMContainer: element is required");
|
|
136
|
+
}
|
|
137
|
+
if (typeof props.element === "string") {
|
|
138
|
+
this.element = document.createElement(props.element);
|
|
139
|
+
} else {
|
|
140
|
+
this.element = props.element.value;
|
|
141
|
+
}
|
|
142
|
+
for (const event of EVENTS) {
|
|
143
|
+
if (props.attrs?.[event]) {
|
|
144
|
+
const eventHandler = (e: Event) => {
|
|
145
|
+
props.attrs[event]?.(e);
|
|
146
|
+
};
|
|
147
|
+
this.eventListeners.set(event, eventHandler);
|
|
148
|
+
this.element.addEventListener(event, eventHandler, false);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onUpdate(props: DOMContainerProps) {
|
|
154
|
+
super.onUpdate(props);
|
|
155
|
+
|
|
156
|
+
for (const [key, value] of Object.entries(props.attrs || {})) {
|
|
157
|
+
if (key === "class") {
|
|
158
|
+
const classList = value.items || value.value || value;
|
|
159
|
+
|
|
160
|
+
// Clear existing classes first
|
|
161
|
+
this.element.className = "";
|
|
162
|
+
|
|
163
|
+
if (typeof classList === "string") {
|
|
164
|
+
// String: space-separated class names
|
|
165
|
+
this.element.className = classList;
|
|
166
|
+
} else if (Array.isArray(classList)) {
|
|
167
|
+
// Array: array of class names
|
|
168
|
+
this.element.classList.add(...classList);
|
|
169
|
+
} else if (typeof classList === "object" && classList !== null) {
|
|
170
|
+
// Object: { className: boolean }
|
|
171
|
+
for (const [className, shouldAdd] of Object.entries(classList)) {
|
|
172
|
+
if (shouldAdd) {
|
|
173
|
+
this.element.classList.add(className);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else if (key === "style") {
|
|
178
|
+
const styleValue = value.items || value.value || value;
|
|
179
|
+
|
|
180
|
+
if (typeof styleValue === "string") {
|
|
181
|
+
// String: CSS style string
|
|
182
|
+
this.element.setAttribute("style", styleValue);
|
|
183
|
+
} else if (typeof styleValue === "object" && styleValue !== null) {
|
|
184
|
+
// Object: { property: value }
|
|
185
|
+
for (const [styleProp, styleVal] of Object.entries(styleValue)) {
|
|
186
|
+
if (styleVal !== null && styleVal !== undefined) {
|
|
187
|
+
(this.element.style as any)[styleProp] = styleVal;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} else if (!EVENTS.includes(key)) {
|
|
192
|
+
this.element.setAttribute(key, value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (props.textContent) {
|
|
196
|
+
this.element.textContent = props.textContent;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (props.sortableChildren !== undefined) {
|
|
200
|
+
this.sortableChildren = props.sortableChildren;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async onDestroy(
|
|
205
|
+
parent: Element<ComponentInstance>,
|
|
206
|
+
afterDestroy: () => void
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
// Remove all event listeners from the DOM element
|
|
209
|
+
if (this.element) {
|
|
210
|
+
for (const [event, handler] of this.eventListeners) {
|
|
211
|
+
this.element.removeEventListener(event, handler, false);
|
|
212
|
+
}
|
|
213
|
+
this.eventListeners.clear();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const _afterDestroyCallback = async () => {
|
|
217
|
+
afterDestroy();
|
|
218
|
+
};
|
|
219
|
+
await super.onDestroy(parent, _afterDestroyCallback);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface CanvasDOMContainer extends DisplayObjectProps {}
|
|
224
|
+
|
|
225
|
+
registerComponent("DOMContainer", CanvasDOMContainer);
|
|
226
|
+
|
|
227
|
+
export const DOMContainer: ComponentFunction<DOMContainerProps> = (props) => {
|
|
228
|
+
return createComponent("DOMContainer", props);
|
|
229
|
+
};
|