canvasengine 1.3.0 → 2.0.1-beta.1

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 (125) hide show
  1. package/.cursorrules +0 -0
  2. package/.github/workflows/ci.yml +29 -0
  3. package/README.md +79 -0
  4. package/dist/compiler/vite.js +119 -0
  5. package/dist/compiler/vite.js.map +1 -0
  6. package/dist/index.d.ts +846 -0
  7. package/dist/index.js +3340 -0
  8. package/dist/index.js.map +1 -0
  9. package/index.d.ts +6 -0
  10. package/logo.png +0 -0
  11. package/package.json +84 -18
  12. package/src/compiler/grammar.pegjs +180 -0
  13. package/src/compiler/vite.ts +166 -0
  14. package/src/components/Canvas.ts +134 -0
  15. package/src/components/Container.ts +46 -0
  16. package/src/components/DisplayObject.ts +458 -0
  17. package/src/components/DrawMap/index.ts +65 -0
  18. package/src/components/Graphic.ts +147 -0
  19. package/src/components/NineSliceSprite.ts +46 -0
  20. package/src/components/ParticleEmitter.ts +39 -0
  21. package/src/components/Scene.ts +6 -0
  22. package/src/components/Sprite.ts +493 -0
  23. package/src/components/Text.ts +145 -0
  24. package/src/components/Tilemap/Tile.ts +79 -0
  25. package/src/components/Tilemap/TileGroup.ts +207 -0
  26. package/src/components/Tilemap/TileLayer.ts +163 -0
  27. package/src/components/Tilemap/TileSet.ts +41 -0
  28. package/src/components/Tilemap/index.ts +80 -0
  29. package/src/components/TilingSprite.ts +39 -0
  30. package/src/components/Viewport.ts +159 -0
  31. package/src/components/index.ts +12 -0
  32. package/src/components/types/DisplayObject.ts +68 -0
  33. package/src/components/types/MouseEvent.ts +3 -0
  34. package/src/components/types/Spritesheet.ts +389 -0
  35. package/src/components/types/index.ts +4 -0
  36. package/src/directives/Drag.ts +84 -0
  37. package/src/directives/KeyboardControls.ts +922 -0
  38. package/src/directives/Scheduler.ts +112 -0
  39. package/src/directives/Sound.ts +91 -0
  40. package/src/directives/Transition.ts +45 -0
  41. package/src/directives/ViewportCull.ts +40 -0
  42. package/src/directives/ViewportFollow.ts +26 -0
  43. package/src/directives/index.ts +7 -0
  44. package/src/engine/animation.ts +113 -0
  45. package/src/engine/bootstrap.ts +19 -0
  46. package/src/engine/directive.ts +23 -0
  47. package/src/engine/reactive.ts +379 -0
  48. package/src/engine/signal.ts +138 -0
  49. package/src/engine/trigger.ts +40 -0
  50. package/src/engine/utils.ts +135 -0
  51. package/src/hooks/addContext.ts +6 -0
  52. package/src/hooks/useProps.ts +155 -0
  53. package/src/hooks/useRef.ts +21 -0
  54. package/src/index.ts +14 -0
  55. package/src/presets/Bar.ts +89 -0
  56. package/src/presets/Button.ts +0 -0
  57. package/src/presets/Joystick.ts +286 -0
  58. package/src/presets/NightAmbiant.ts +122 -0
  59. package/src/presets/Particle.ts +53 -0
  60. package/src/utils/Ease.ts +33 -0
  61. package/src/utils/RadialGradient.ts +86 -0
  62. package/starter/assets/logo.png +0 -0
  63. package/starter/components/app.ce +18 -0
  64. package/starter/components/hello.ce +35 -0
  65. package/starter/index.html +21 -0
  66. package/starter/main.ts +6 -0
  67. package/starter/package.json +20 -0
  68. package/starter/tsconfig.json +32 -0
  69. package/starter/vite.config.ts +12 -0
  70. package/tsconfig.json +32 -0
  71. package/tsconfig.node.json +10 -0
  72. package/tsup.config.ts +28 -0
  73. package/vitest.config.ts +12 -0
  74. package/.gitattributes +0 -22
  75. package/.npmignore +0 -163
  76. package/canvasengine-1.3.0.all.min.js +0 -21
  77. package/canvasengine.js +0 -5802
  78. package/core/DB.js +0 -24
  79. package/core/ModelServer.js +0 -348
  80. package/core/Users.js +0 -190
  81. package/core/engine-common.js +0 -952
  82. package/doc/cocoonjs.md +0 -36
  83. package/doc/doc-lang.yml +0 -43
  84. package/doc/doc-router.yml +0 -14
  85. package/doc/doc-tuto.yml +0 -9
  86. package/doc/doc.yml +0 -39
  87. package/doc/element.md +0 -37
  88. package/doc/entity.md +0 -90
  89. package/doc/extend.md +0 -47
  90. package/doc/get_started.md +0 -19
  91. package/doc/images/entity.png +0 -0
  92. package/doc/multitouch.md +0 -58
  93. package/doc/nodejs.md +0 -142
  94. package/doc/scene.md +0 -44
  95. package/doc/text.md +0 -156
  96. package/examples/server/client.html +0 -31
  97. package/examples/server/server.js +0 -16
  98. package/examples/tiled_server/client.html +0 -52
  99. package/examples/tiled_server/images/tiles_spritesheet.png +0 -0
  100. package/examples/tiled_server/server/map.json +0 -50
  101. package/examples/tiled_server/server/map.tmx +0 -16
  102. package/examples/tiled_server/server/server.js +0 -16
  103. package/extends/Animation.js +0 -910
  104. package/extends/Effect.js +0 -252
  105. package/extends/Gleed2d.js +0 -252
  106. package/extends/Hit.js +0 -1509
  107. package/extends/Input.js +0 -699
  108. package/extends/Marshal.js +0 -716
  109. package/extends/Scrolling.js +0 -388
  110. package/extends/Soundmanager2.js +0 -5466
  111. package/extends/Spritesheet.js +0 -196
  112. package/extends/Text.js +0 -366
  113. package/extends/Tiled.js +0 -403
  114. package/extends/Window.js +0 -575
  115. package/extends/gamepad.js +0 -397
  116. package/extends/socket.io.min.js +0 -2
  117. package/extends/swf/soundmanager2.swf +0 -0
  118. package/extends/swf/soundmanager2_debug.swf +0 -0
  119. package/extends/swf/soundmanager2_flash9.swf +0 -0
  120. package/extends/swf/soundmanager2_flash9_debug.swf +0 -0
  121. package/extends/swf/soundmanager2_flash_xdomain.zip +0 -0
  122. package/extends/workers/transition.js +0 -43
  123. package/index.js +0 -46
  124. package/license.txt +0 -19
  125. package/readme.md +0 -483
@@ -0,0 +1,155 @@
1
+ import { isSignal, signal } from "@signe/reactive"
2
+ import { isPrimitive } from "../engine/reactive"
3
+
4
+ /**
5
+ * Converts props into reactive signals if they are primitive values.
6
+ *
7
+ * @param {object} props - The properties to convert.
8
+ * @param {object} [defaults={}] - Default values for properties.
9
+ * @returns {object} An object with reactive signals.
10
+ *
11
+ * @example
12
+ * const props = useProps({ count: 0, name: "John" });
13
+ * console.log(props.count()); // 0
14
+ * props.count.set(1);
15
+ * console.log(props.count()); // 1
16
+ */
17
+ export const useProps = (props, defaults = {}): any => {
18
+ if (isSignal(props)) {
19
+ return props()
20
+ }
21
+ const obj = {}
22
+ for (let key in props) {
23
+ const value = props[key]
24
+ obj[key] = isPrimitive(value) ? signal(value) : value
25
+ }
26
+ for (let key in defaults) {
27
+ if (!(key in obj)) {
28
+ obj[key] = signal(defaults[key])
29
+ }
30
+ }
31
+ return obj
32
+ }
33
+
34
+ type PropType = NumberConstructor | StringConstructor | BooleanConstructor |
35
+ FunctionConstructor | ObjectConstructor | ArrayConstructor |
36
+ null | (new (...args: any[]) => any);
37
+
38
+ interface PropConfig {
39
+ type?: PropType | PropType[];
40
+ required?: boolean;
41
+ default?: any | ((props: any) => any);
42
+ validator?: (value: any, props: any) => boolean;
43
+ }
44
+
45
+ type PropSchema = {
46
+ [key: string]: PropType | PropType[] | PropConfig;
47
+ }
48
+
49
+ /**
50
+ * Validates and defines properties based on a schema.
51
+ *
52
+ * @param {object} props - The properties to validate.
53
+ * @returns {function} A function that takes a schema and returns validated properties.
54
+ *
55
+ * @example
56
+ * const schema = {
57
+ * age: { type: Number, required: true },
58
+ * name: { type: String, default: "Anonymous" }
59
+ * };
60
+ * const validatedProps = useDefineProps({ age: 25 })(schema);
61
+ * console.log(validatedProps.age()); // 25
62
+ * console.log(validatedProps.name()); // "Anonymous"
63
+ */
64
+ export const useDefineProps = (props: any) => {
65
+ return (schema?: PropSchema) => {
66
+ const rawProps = isSignal(props) ? props() : props
67
+ const validatedProps: { [key: string]: any } = {}
68
+
69
+ for (const key in schema) {
70
+ const propConfig = schema[key]
71
+ const value = rawProps[key]
72
+ let validatedValue: any
73
+
74
+ // Handle simple type definition
75
+ if (typeof propConfig === 'function') {
76
+ validateType(key, value, [propConfig])
77
+ validatedValue = value
78
+ }
79
+ // Handle array of types
80
+ else if (Array.isArray(propConfig)) {
81
+ validateType(key, value, propConfig)
82
+ validatedValue = value
83
+ }
84
+ // Handle detailed configuration object
85
+ else if (propConfig && typeof propConfig === 'object') {
86
+ // Check required prop
87
+ if (propConfig.required && value === undefined) {
88
+ throw new Error(`Missing required prop: ${key}`)
89
+ }
90
+
91
+ // Validate type if specified
92
+ if (propConfig.type) {
93
+ const types = Array.isArray(propConfig.type) ? propConfig.type : [propConfig.type]
94
+ validateType(key, value, types)
95
+ }
96
+
97
+ // Run custom validator if provided
98
+ if (propConfig.validator && !propConfig.validator(value, rawProps)) {
99
+ throw new Error(`Invalid prop: custom validation failed for prop "${key}"`)
100
+ }
101
+
102
+ // Set default value if value is undefined
103
+ if (value === undefined && 'default' in propConfig) {
104
+ validatedValue = typeof propConfig.default === 'function'
105
+ ? propConfig.default(rawProps)
106
+ : propConfig.default
107
+ } else {
108
+ validatedValue = value
109
+ }
110
+ }
111
+
112
+ validatedProps[key] = isSignal(validatedValue)
113
+ ? validatedValue
114
+ : signal(validatedValue)
115
+ }
116
+
117
+ return {
118
+ ...useProps(rawProps),
119
+ ...validatedProps
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Validates the type of a property.
126
+ *
127
+ * @param {string} key - The property key.
128
+ * @param {any} value - The property value.
129
+ * @param {any[]} types - The expected types.
130
+ * @throws Will throw an error if the type check fails.
131
+ */
132
+ function validateType(key: string, value: any, types: any[]) {
133
+ if (value === undefined || value === null) return
134
+
135
+ // Si c'est un signal, on vérifie la valeur du signal
136
+ const valueToCheck = isSignal(value) ? value() : value
137
+
138
+ const valid = types.some(type => {
139
+ if (type === Number) return typeof valueToCheck === 'number'
140
+ if (type === String) return typeof valueToCheck === 'string'
141
+ if (type === Boolean) return typeof valueToCheck === 'boolean'
142
+ if (type === Function) return typeof valueToCheck === 'function'
143
+ if (type === Object) return typeof valueToCheck === 'object'
144
+ if (type === Array) return Array.isArray(valueToCheck)
145
+ if (type === null) return valueToCheck === null
146
+ return valueToCheck instanceof type
147
+ })
148
+
149
+ if (!valid) {
150
+ throw new Error(
151
+ `Invalid prop: type check failed for prop "${key}". ` +
152
+ `Expected ${types.map(t => t.name).join(' or ')}`
153
+ )
154
+ }
155
+ }
@@ -0,0 +1,21 @@
1
+ import { Element } from "../engine/reactive";
2
+ import { ComponentInstance } from "../components/DisplayObject";
3
+
4
+ export function useRef(element: Element<ComponentInstance>, ref: string): Element<ComponentInstance> | null {
5
+ const { props } = element;
6
+ if (props.ref == ref) {
7
+ return element;
8
+ }
9
+ // Recursive search for the component with the given id
10
+ if (props.children) {
11
+ for (const child of props.children) {
12
+ const result = useRef(child, ref);
13
+ if (result) {
14
+ return result;
15
+ }
16
+ }
17
+ }
18
+
19
+ // If not found, return undefined
20
+ return null;
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ import './directives'
2
+ export * from '@signe/reactive'
3
+ export { Howler } from 'howler'
4
+ export * from './components'
5
+ export * from './engine/reactive'
6
+ export * from './engine/signal'
7
+ export * from './engine/trigger'
8
+ export * from './engine/bootstrap'
9
+ export * from './engine/animation'
10
+ export { useProps, useDefineProps } from './hooks/useProps'
11
+
12
+ export * from './presets/Bar'
13
+ export * from './presets/Particle'
14
+ export * from './utils/Ease'
@@ -0,0 +1,89 @@
1
+ import { Graphics } from "../components";
2
+ import { h } from "../engine/signal";
3
+ import * as PIXI from "pixi.js";
4
+ import { useProps } from "../hooks/useProps";
5
+
6
+ interface BarProps {
7
+ backgroundColor?: string;
8
+ foregroundColor?: string;
9
+ value: number;
10
+ maxValue: number;
11
+ width: number;
12
+ height: number;
13
+ }
14
+
15
+ function componentToHex(c) {
16
+ var hex = c.toString(16);
17
+ return hex.length == 1 ? "0" + hex : hex;
18
+ }
19
+
20
+ function rgbToHex(r, g, b) {
21
+ return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
22
+ }
23
+
24
+ export function Bar(opts: BarProps) {
25
+ const {
26
+ width,
27
+ height,
28
+ value,
29
+ maxValue,
30
+ backgroundColor,
31
+ foregroundColor,
32
+ border,
33
+ innerMargin,
34
+ borderRadius,
35
+ } = useProps(opts, {
36
+ backgroundColor: "#000000",
37
+ foregroundColor: "#FFFFFF",
38
+ innerMargin: 0,
39
+ borderRadius: 0,
40
+ });
41
+
42
+ return h(
43
+ Graphics,
44
+ {
45
+ ...opts,
46
+ width,
47
+ height,
48
+ draw(g: PIXI.Graphics) {
49
+ if (borderRadius()) {
50
+ g.roundRect(0, 0, width(), height(), borderRadius());
51
+ } else {
52
+ g.rect(0, 0, width(), height());
53
+ }
54
+ if (border) {
55
+ g.stroke(border);
56
+ }
57
+ g.fill(backgroundColor());
58
+ },
59
+ },
60
+ h(Graphics, {
61
+ width,
62
+ height,
63
+ draw(g: PIXI.Graphics) {
64
+ const margin = innerMargin();
65
+ const _borderRadius = borderRadius();
66
+ const w = Math.max(
67
+ 0,
68
+ Math.min(
69
+ width() - 2 * margin,
70
+ (value() / maxValue()) * (width() - 2 * margin)
71
+ )
72
+ );
73
+ const h = height() - 2 * margin;
74
+ if (borderRadius) {
75
+ g.roundRect(margin, margin, w, h, _borderRadius);
76
+ } else {
77
+ g.rect(margin, margin, w, h);
78
+ }
79
+ const color = foregroundColor();
80
+ if (color.startsWith("rgba")) {
81
+ const [r, g, b, a] = color.match(/\d+(\.\d+)?/g).map(Number);
82
+ g.fill({ color: rgbToHex(r, g, b), alpha: a });
83
+ } else {
84
+ g.fill(color);
85
+ }
86
+ },
87
+ })
88
+ );
89
+ }
File without changes
@@ -0,0 +1,286 @@
1
+ /*
2
+ * Joystick
3
+ *
4
+ * Inspired by https://github.com/endel/pixi-virtual-joystick
5
+ */
6
+
7
+ import * as PIXI from "pixi.js";
8
+ import { Container, Graphics, Sprite } from "../components";
9
+ import { h } from "../engine/signal";
10
+ import { signal } from "@signe/reactive";
11
+
12
+ export interface JoystickChangeEvent {
13
+ angle: number;
14
+ direction: Direction;
15
+ power: number;
16
+ }
17
+
18
+ export enum Direction {
19
+ LEFT = "left",
20
+ TOP = "top",
21
+ BOTTOM = "bottom",
22
+ RIGHT = "right",
23
+ TOP_LEFT = "top_left",
24
+ TOP_RIGHT = "top_right",
25
+ BOTTOM_LEFT = "bottom_left",
26
+ BOTTOM_RIGHT = "bottom_right",
27
+ }
28
+
29
+ export interface JoystickSettings {
30
+ outer?: string;
31
+ inner?: string;
32
+ outerScale?: { x: number; y: number };
33
+ innerScale?: { x: number; y: number };
34
+ onChange?: (data: JoystickChangeEvent) => void;
35
+ onStart?: () => void;
36
+ onEnd?: () => void;
37
+ }
38
+
39
+ export function Joystick(opts: JoystickSettings = {}) {
40
+ const settings = Object.assign(
41
+ {
42
+ outerScale: { x: 1, y: 1 },
43
+ innerScale: { x: 1, y: 1 },
44
+ },
45
+ opts
46
+ );
47
+
48
+ let outerRadius = 70;
49
+ let innerRadius = 10;
50
+ const innerAlphaStandby = 0.5;
51
+
52
+ let dragging = false;
53
+ let startPosition: PIXI.PointData | null = null;
54
+ let power = 0;
55
+
56
+ const innerPositionX = signal(0);
57
+ const innerPositionY = signal(0);
58
+ const innerAlpha = signal(innerAlphaStandby);
59
+
60
+ function getPower(centerPoint: PIXI.Point) {
61
+ const a = centerPoint.x - 0;
62
+ const b = centerPoint.y - 0;
63
+ return Math.min(1, Math.sqrt(a * a + b * b) / outerRadius);
64
+ }
65
+
66
+ function getDirection(center: PIXI.Point) {
67
+ let rad = Math.atan2(center.y, center.x); // [-PI, PI]
68
+ if ((rad >= -Math.PI / 8 && rad < 0) || (rad >= 0 && rad < Math.PI / 8)) {
69
+ return Direction.RIGHT;
70
+ } else if (rad >= Math.PI / 8 && rad < (3 * Math.PI) / 8) {
71
+ return Direction.BOTTOM_RIGHT;
72
+ } else if (rad >= (3 * Math.PI) / 8 && rad < (5 * Math.PI) / 8) {
73
+ return Direction.BOTTOM;
74
+ } else if (rad >= (5 * Math.PI) / 8 && rad < (7 * Math.PI) / 8) {
75
+ return Direction.BOTTOM_LEFT;
76
+ } else if (
77
+ (rad >= (7 * Math.PI) / 8 && rad < Math.PI) ||
78
+ (rad >= -Math.PI && rad < (-7 * Math.PI) / 8)
79
+ ) {
80
+ return Direction.LEFT;
81
+ } else if (rad >= (-7 * Math.PI) / 8 && rad < (-5 * Math.PI) / 8) {
82
+ return Direction.TOP_LEFT;
83
+ } else if (rad >= (-5 * Math.PI) / 8 && rad < (-3 * Math.PI) / 8) {
84
+ return Direction.TOP;
85
+ } else {
86
+ return Direction.TOP_RIGHT;
87
+ }
88
+ }
89
+
90
+ function handleDragStart(event: PIXI.FederatedPointerEvent) {
91
+ startPosition = event.getLocalPosition(this);
92
+ dragging = true;
93
+ innerAlpha.set(1);
94
+ settings.onStart?.();
95
+ }
96
+
97
+ function handleDragEnd() {
98
+ if (!dragging) return;
99
+ innerPositionX.set(0);
100
+ innerPositionY.set(0);
101
+ dragging = false;
102
+ innerAlpha.set(innerAlphaStandby);
103
+ settings.onEnd?.();
104
+ }
105
+
106
+ function handleDragMove(event: PIXI.FederatedPointerEvent) {
107
+ if (dragging == false) {
108
+ return;
109
+ }
110
+
111
+ let newPosition = event.getLocalPosition(this);
112
+
113
+ let sideX = newPosition.x - (startPosition?.x ?? 0);
114
+ let sideY = newPosition.y - (startPosition?.y ?? 0);
115
+
116
+ let centerPoint = new PIXI.Point(0, 0);
117
+ let angle = 0;
118
+
119
+ if (sideX == 0 && sideY == 0) {
120
+ return;
121
+ }
122
+
123
+ let calRadius = 0;
124
+
125
+ if (sideX * sideX + sideY * sideY >= outerRadius * outerRadius) {
126
+ calRadius = outerRadius;
127
+ } else {
128
+ calRadius = outerRadius - innerRadius;
129
+ }
130
+
131
+ /**
132
+ * x: -1 <-> 1
133
+ * y: -1 <-> 1
134
+ * Y
135
+ * ^
136
+ * |
137
+ * 180 | 90
138
+ * ------------> X
139
+ * 270 | 360
140
+ * |
141
+ * |
142
+ */
143
+
144
+ let direction = Direction.LEFT;
145
+
146
+ if (sideX == 0) {
147
+ if (sideY > 0) {
148
+ centerPoint.set(0, sideY > outerRadius ? outerRadius : sideY);
149
+ angle = 270;
150
+ direction = Direction.BOTTOM;
151
+ } else {
152
+ centerPoint.set(
153
+ 0,
154
+ -(Math.abs(sideY) > outerRadius ? outerRadius : Math.abs(sideY))
155
+ );
156
+ angle = 90;
157
+ direction = Direction.TOP;
158
+ }
159
+ innerPositionX.set(centerPoint.x);
160
+ innerPositionY.set(centerPoint.y);
161
+ power = getPower(centerPoint);
162
+ settings.onChange?.({ angle, direction, power });
163
+ return;
164
+ }
165
+
166
+ if (sideY == 0) {
167
+ if (sideX > 0) {
168
+ centerPoint.set(
169
+ Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX),
170
+ 0
171
+ );
172
+ angle = 0;
173
+ direction = Direction.LEFT;
174
+ } else {
175
+ centerPoint.set(
176
+ -(Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX)),
177
+ 0
178
+ );
179
+ angle = 180;
180
+ direction = Direction.RIGHT;
181
+ }
182
+
183
+ innerPositionX.set(centerPoint.x);
184
+ innerPositionY.set(centerPoint.y);
185
+ power = getPower(centerPoint);
186
+ settings.onChange?.({ angle, direction, power });
187
+ return;
188
+ }
189
+
190
+ let tanVal = Math.abs(sideY / sideX);
191
+ let radian = Math.atan(tanVal);
192
+ angle = (radian * 180) / Math.PI;
193
+
194
+ let centerX = 0;
195
+ let centerY = 0;
196
+
197
+ if (sideX * sideX + sideY * sideY >= outerRadius * outerRadius) {
198
+ centerX = outerRadius * Math.cos(radian);
199
+ centerY = outerRadius * Math.sin(radian);
200
+ } else {
201
+ centerX = Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX);
202
+ centerY = Math.abs(sideY) > outerRadius ? outerRadius : Math.abs(sideY);
203
+ }
204
+
205
+ if (sideY < 0) {
206
+ centerY = -Math.abs(centerY);
207
+ }
208
+ if (sideX < 0) {
209
+ centerX = -Math.abs(centerX);
210
+ }
211
+
212
+ if (sideX > 0 && sideY < 0) {
213
+ // < 90
214
+ } else if (sideX < 0 && sideY < 0) {
215
+ // 90 ~ 180
216
+ angle = 180 - angle;
217
+ } else if (sideX < 0 && sideY > 0) {
218
+ // 180 ~ 270
219
+ angle = angle + 180;
220
+ } else if (sideX > 0 && sideY > 0) {
221
+ // 270 ~ 369
222
+ angle = 360 - angle;
223
+ }
224
+ centerPoint.set(centerX, centerY);
225
+ power = getPower(centerPoint);
226
+
227
+ direction = getDirection(centerPoint);
228
+ innerPositionX.set(centerPoint.x);
229
+ innerPositionY.set(centerPoint.y);
230
+
231
+ settings.onChange?.({ angle, direction, power });
232
+ }
233
+
234
+ let innerElement;
235
+ let outerElement;
236
+
237
+ if (!settings.outer) {
238
+ outerElement = h(Graphics, {
239
+ draw: (g) => {
240
+ g.circle(0, 0, outerRadius).fill(0x000000);
241
+ },
242
+ alpha: 0.5,
243
+ });
244
+ } else {
245
+ outerElement = h(Sprite, {
246
+ image: settings.outer,
247
+ anchor: { x: 0.5, y: 0.5 },
248
+ scale: settings.outerScale,
249
+ });
250
+ }
251
+
252
+ const innerOptions: any = {
253
+ scale: settings.innerScale,
254
+ x: innerPositionX,
255
+ y: innerPositionY,
256
+ alpha: innerAlpha,
257
+ };
258
+
259
+ if (!settings.inner) {
260
+ innerElement = h(Graphics, {
261
+ draw: (g) => {
262
+ g.circle(0, 0, innerRadius * 2.5).fill(0x000000);
263
+ },
264
+ ...innerOptions,
265
+ });
266
+ } else {
267
+ innerElement = h(Sprite, {
268
+ image: settings.inner,
269
+ anchor: { x: 0.5, y: 0.5 },
270
+ ...innerOptions,
271
+ });
272
+ }
273
+
274
+ return h(
275
+ Container,
276
+ {
277
+ ...opts,
278
+ pointerdown: handleDragStart,
279
+ pointerup: handleDragEnd,
280
+ pointerupoutside: handleDragEnd,
281
+ pointermove: handleDragMove,
282
+ },
283
+ outerElement,
284
+ innerElement
285
+ );
286
+ }
@@ -0,0 +1,122 @@
1
+ import { Container, Graphics } from "../components";
2
+ import { h, mount } from "../engine/signal";
3
+ import { animatedSignal } from "../engine/animation";
4
+ import { RadialGradient } from "../utils/RadialGradient";
5
+ import { effect, isSignal, signal } from "@signe/reactive";
6
+ import { useProps } from "../hooks/useProps";
7
+ import { isObservable } from "rxjs";
8
+
9
+ export function LightSpot(opts) {
10
+ const { radius } = useProps(opts);
11
+ const scale = animatedSignal(1);
12
+
13
+ const minScale = 1;
14
+ const maxScale = 2; // Reduced max scale for subtler effect
15
+ const scintillationSpeed = 0.001; // Significantly reduced for slower scintillation
16
+
17
+ const animate = () => {
18
+ // Use time-based animation for smoother, slower scintillation
19
+ const time = Date.now() * scintillationSpeed;
20
+
21
+ // Combine multiple sine waves for a more natural, less predictable effect
22
+ const scintillationFactor =
23
+ (Math.sin(time) + Math.sin(time * 1.3) + Math.sin(time * 0.7)) / 3;
24
+
25
+ // Map the scintillation factor to the scale range
26
+ const newScale =
27
+ minScale + (maxScale - minScale) * (scintillationFactor * 0.5 + 0.5);
28
+
29
+ scale.update(() => newScale);
30
+
31
+ requestAnimationFrame(animate);
32
+ };
33
+
34
+ animate();
35
+
36
+ const draw = (g) => {
37
+ const size = radius() * 2;
38
+ const gradient = new RadialGradient(size, size, 0, size, size, 0);
39
+ gradient.addColorStop(0, "rgba(255, 255, 0, 1)");
40
+ gradient.addColorStop(0.5, "rgba(255, 255, 0, 0.3)");
41
+ gradient.addColorStop(0.8, "rgba(255, 255, 0, 0)");
42
+
43
+ const translate = size / 2;
44
+
45
+ g.rect(-translate, -translate, size, size).fill(
46
+ gradient.render({ translate: { x: translate, y: translate } })
47
+ );
48
+ };
49
+
50
+ return h(Graphics, {
51
+ draw,
52
+ ...opts,
53
+ scale,
54
+ });
55
+ }
56
+
57
+ export function NightAmbiant(props) {
58
+ const { children } = props;
59
+ let el
60
+ const width = signal(0);
61
+ const height = signal(0);
62
+ let subscription
63
+ const draw = (rectAndHole) => {
64
+ const margin = 80
65
+ rectAndHole.rect(-margin, -margin, width() + margin*2, height() + margin*2);
66
+ rectAndHole.fill(0x000000);
67
+ const applyChildren = (child) => {
68
+ const x = isSignal(child.propObservables.x)
69
+ ? child.propObservables.x()
70
+ : child.props.x;
71
+ const y = isSignal(child.propObservables.y)
72
+ ? child.propObservables.y()
73
+ : child.props.y;
74
+ const radius = isSignal(child.propObservables.radius)
75
+ ? child.propObservables.radius()
76
+ : child.props.radius;
77
+ rectAndHole.circle(x, y, radius);
78
+ rectAndHole.cut();
79
+ }
80
+ for (let child of children) {
81
+ if (isObservable(child)) {
82
+ if (subscription) {
83
+ subscription.unsubscribe()
84
+ }
85
+ subscription = child.subscribe((event) => {
86
+ for (let child of event.fullElements) {
87
+ applyChildren(child)
88
+ }
89
+ })
90
+ return
91
+ }
92
+ applyChildren(child)
93
+ }
94
+ };
95
+
96
+ mount((el) => {
97
+ effect(() => {
98
+ const { displayWidth, displayHeight } = el.componentInstance
99
+ const w = +displayWidth()
100
+ const h = +displayHeight()
101
+ setTimeout(() => {
102
+ width.update(() => w)
103
+ height.update(() => h)
104
+ }, 0) // hack
105
+ });
106
+ });
107
+
108
+ return h(
109
+ Container,
110
+ {
111
+ width: "100%",
112
+ height: "100%",
113
+ ...props,
114
+ },
115
+ h(Graphics, {
116
+ draw,
117
+ alpha: 0.8,
118
+ blur: 80,
119
+ }),
120
+ ...children
121
+ );
122
+ }