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.
Files changed (40) hide show
  1. package/dist/index.d.ts +1285 -0
  2. package/dist/index.js +4150 -0
  3. package/dist/index.js.map +1 -0
  4. package/index.d.ts +4 -0
  5. package/package.json +5 -12
  6. package/src/components/Canvas.ts +53 -45
  7. package/src/components/Container.ts +2 -2
  8. package/src/components/DOMContainer.ts +229 -0
  9. package/src/components/DisplayObject.ts +263 -189
  10. package/src/components/Graphic.ts +213 -36
  11. package/src/components/Mesh.ts +222 -0
  12. package/src/components/NineSliceSprite.ts +4 -1
  13. package/src/components/ParticleEmitter.ts +12 -8
  14. package/src/components/Sprite.ts +77 -14
  15. package/src/components/Text.ts +34 -14
  16. package/src/components/Video.ts +110 -0
  17. package/src/components/Viewport.ts +59 -43
  18. package/src/components/index.ts +5 -4
  19. package/src/components/types/DisplayObject.ts +30 -0
  20. package/src/directives/Drag.ts +357 -52
  21. package/src/directives/KeyboardControls.ts +3 -1
  22. package/src/directives/Sound.ts +94 -31
  23. package/src/directives/ViewportFollow.ts +35 -7
  24. package/src/engine/animation.ts +41 -5
  25. package/src/engine/bootstrap.ts +13 -2
  26. package/src/engine/directive.ts +2 -2
  27. package/src/engine/reactive.ts +336 -168
  28. package/src/engine/trigger.ts +65 -9
  29. package/src/engine/utils.ts +92 -9
  30. package/src/hooks/useProps.ts +1 -1
  31. package/src/index.ts +5 -1
  32. package/src/utils/RadialGradient.ts +29 -0
  33. package/src/utils/functions.ts +7 -0
  34. package/testing/index.ts +12 -0
  35. package/src/components/DrawMap/index.ts +0 -65
  36. package/src/components/Tilemap/Tile.ts +0 -79
  37. package/src/components/Tilemap/TileGroup.ts +0 -207
  38. package/src/components/Tilemap/TileLayer.ts +0 -163
  39. package/src/components/Tilemap/TileSet.ts +0 -41
  40. package/src/components/Tilemap/index.ts +0 -80
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module '*.ce' {
2
+ const content: import("./dist/index").ComponentFunction;
3
+ export default content;
4
+ }
package/package.json CHANGED
@@ -1,22 +1,20 @@
1
1
  {
2
2
  "name": "canvasengine",
3
- "version": "2.0.0-beta.2",
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/tilemap": "^5.0.1",
10
- "@rpgjs/tiled": "^4.3.0",
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.6.4",
14
+ "pixi.js": "^8.9.2",
17
15
  "popmotion": "^11.0.5",
18
16
  "rxjs": "^7.8.1",
19
- "yoga-layout": "^2.0.1"
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"
@@ -1,7 +1,11 @@
1
1
  import { effect, Signal, signal } from "@signe/reactive";
2
- import { Container, autoDetectRenderer } from "pixi.js";
3
- import { loadYoga } from "yoga-layout";
4
- import { Props, createComponent, registerComponent, Element } from "../engine/reactive";
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: renderer.width,
50
- height: renderer.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 resizeCanvas = async () => {
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
- // Initial resize
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
+ };