canvasengine 2.0.0-beta.12 → 2.0.0-beta.13
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 +2 -2
- package/dist/index.js +326 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Viewport.ts +5 -11
- package/src/directives/Drag.ts +251 -53
- package/src/directives/KeyboardControls.ts +3 -1
- package/src/directives/ViewportFollow.ts +12 -6
- package/src/engine/directive.ts +2 -2
- package/src/engine/reactive.ts +2 -2
- package/src/hooks/useProps.ts +1 -1
package/package.json
CHANGED
|
@@ -48,10 +48,7 @@ export class CanvasViewport extends DisplayObject(PixiViewport) {
|
|
|
48
48
|
onInit(props) {
|
|
49
49
|
super.onInit(props)
|
|
50
50
|
for (let event of EVENTS) {
|
|
51
|
-
|
|
52
|
-
if (props[camelCaseEvent]) {
|
|
53
|
-
this.on(event, props[camelCaseEvent])
|
|
54
|
-
}
|
|
51
|
+
if (props[event]) this.on(event, props[event])
|
|
55
52
|
}
|
|
56
53
|
}
|
|
57
54
|
|
|
@@ -69,6 +66,7 @@ export class CanvasViewport extends DisplayObject(PixiViewport) {
|
|
|
69
66
|
'wheel',
|
|
70
67
|
this.input.wheelFunction
|
|
71
68
|
);
|
|
69
|
+
|
|
72
70
|
this.options.events = renderer.events
|
|
73
71
|
|
|
74
72
|
this.tickSubscription = tick.observable.subscribe(({ value }) => {
|
|
@@ -97,13 +95,9 @@ export class CanvasViewport extends DisplayObject(PixiViewport) {
|
|
|
97
95
|
if (props.worldHeight !== undefined) {
|
|
98
96
|
this.worldHeight = props.worldHeight
|
|
99
97
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// } else {
|
|
104
|
-
// this.drag(props.drag)
|
|
105
|
-
// }
|
|
106
|
-
// }
|
|
98
|
+
if (props.drag) {
|
|
99
|
+
this.drag(props.drag)
|
|
100
|
+
}
|
|
107
101
|
if (props.clamp) {
|
|
108
102
|
this.clamp(props.clamp.value ?? props.clamp)
|
|
109
103
|
}
|
package/src/directives/Drag.ts
CHANGED
|
@@ -1,84 +1,282 @@
|
|
|
1
|
-
import { effect, isSignal } from '@signe/reactive';
|
|
2
|
-
import { Container, Rectangle } from 'pixi.js';
|
|
1
|
+
import { effect, isSignal, signal } from '@signe/reactive';
|
|
2
|
+
import { Container, Rectangle, Point, FederatedPointerEvent } from 'pixi.js';
|
|
3
3
|
import { Directive, registerDirective } from '../engine/directive';
|
|
4
4
|
import { Element } from '../engine/reactive';
|
|
5
5
|
import { snap } from 'popmotion';
|
|
6
6
|
import { addContext } from '../hooks/addContext';
|
|
7
|
+
import { Subscription } from 'rxjs';
|
|
8
|
+
import { useProps } from '../hooks/useProps';
|
|
7
9
|
|
|
8
10
|
export class Drop extends Directive {
|
|
9
|
-
|
|
11
|
+
private elementRef: Element<Container> | null = null;
|
|
12
|
+
|
|
13
|
+
onInit(element: Element<Container>) {
|
|
14
|
+
this.elementRef = element;
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
onMount(element: Element<Container>) {
|
|
12
|
-
addContext(element, 'drop', element)
|
|
18
|
+
addContext(element, 'drop', element);
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
onUpdate() {}
|
|
16
22
|
|
|
17
|
-
onDestroy() {
|
|
23
|
+
onDestroy() {
|
|
24
|
+
this.elementRef = null;
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
export class Drag extends Directive {
|
|
21
|
-
|
|
29
|
+
private elementRef: Element<Container> | null = null;
|
|
30
|
+
private stageRef: Container | null = null;
|
|
31
|
+
private offsetInParent = new Point();
|
|
32
|
+
private isDragging = false;
|
|
33
|
+
private viewport: any | null = null;
|
|
34
|
+
private animationFrameId: number | null = null;
|
|
35
|
+
private lastPointerPosition: Point = new Point();
|
|
36
|
+
|
|
37
|
+
private onDragMoveHandler: (event: FederatedPointerEvent) => void = () => {};
|
|
38
|
+
private onDragEndHandler: () => void = () => {};
|
|
39
|
+
private onDragStartHandler: (event: FederatedPointerEvent) => void = () => {};
|
|
40
|
+
|
|
41
|
+
private subscriptions: Subscription[] = [];
|
|
42
|
+
|
|
43
|
+
onInit(element: Element<Container>) {
|
|
44
|
+
this.elementRef = element;
|
|
45
|
+
this.onDragMoveHandler = this.onDragMove.bind(this);
|
|
46
|
+
this.onDragEndHandler = this.onDragEnd.bind(this);
|
|
47
|
+
this.onDragStartHandler = this.onPointerDown.bind(this);
|
|
48
|
+
}
|
|
22
49
|
|
|
23
50
|
onMount(element: Element<Container>) {
|
|
24
|
-
const { rootElement, canvasSize } = element.props.context
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (drag?.snap) {
|
|
46
|
-
instance.position.x = snapTo(x)
|
|
47
|
-
instance.position.y = snapTo(y)
|
|
48
|
-
} else {
|
|
49
|
-
instance.position.x = x
|
|
50
|
-
instance.position.y = y
|
|
51
|
+
const { rootElement, canvasSize, viewport, tick } = element.props.context;
|
|
52
|
+
const instance = element.componentInstance;
|
|
53
|
+
const dragProps = this.dragProps;
|
|
54
|
+
const haveNotProps = Object.keys(dragProps).length === 0;
|
|
55
|
+
|
|
56
|
+
if (haveNotProps) {
|
|
57
|
+
this.onDestroy();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!instance) return;
|
|
62
|
+
this.stageRef = rootElement.componentInstance;
|
|
63
|
+
if (!this.stageRef) return;
|
|
64
|
+
this.viewport = viewport;
|
|
65
|
+
|
|
66
|
+
instance.eventMode = 'static';
|
|
67
|
+
this.stageRef.eventMode = 'static';
|
|
68
|
+
|
|
69
|
+
const _effect = effect(() => {
|
|
70
|
+
if (this.stageRef) {
|
|
71
|
+
this.stageRef.hitArea = new Rectangle(0, 0, canvasSize().width, canvasSize().height);
|
|
51
72
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
instance.on('pointerdown', this.onDragStartHandler);
|
|
76
|
+
this.stageRef.on('pointerup', this.onDragEndHandler);
|
|
77
|
+
this.stageRef.on('pointerupoutside', this.onDragEndHandler);
|
|
78
|
+
|
|
79
|
+
this.subscriptions = [
|
|
80
|
+
tick.observable.subscribe(() => {
|
|
81
|
+
if (this.isDragging && this.viewport) {
|
|
82
|
+
this.updateViewportPosition(this.lastPointerPosition);
|
|
83
|
+
}
|
|
84
|
+
}),
|
|
85
|
+
_effect.subscription
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get dragProps() {
|
|
90
|
+
const drag = this.elementRef?.props.drag
|
|
91
|
+
const options = useProps(drag?.value ?? drag, {
|
|
92
|
+
snap: 0,
|
|
93
|
+
viewport: {},
|
|
94
|
+
direction: 'all'
|
|
95
|
+
});
|
|
96
|
+
options.viewport = useProps(options.viewport, {
|
|
97
|
+
edgeThreshold: 300,
|
|
98
|
+
maxSpeed: 40
|
|
99
|
+
});
|
|
100
|
+
return options;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get axis() {
|
|
104
|
+
const direction = this.dragProps.direction();
|
|
105
|
+
const axis = {
|
|
106
|
+
x: true,
|
|
107
|
+
y: true,
|
|
108
|
+
}
|
|
109
|
+
if (direction === 'x') {
|
|
110
|
+
axis.y = false;
|
|
111
|
+
}
|
|
112
|
+
if (direction === 'y') {
|
|
113
|
+
axis.x = false;
|
|
114
|
+
}
|
|
115
|
+
return axis;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Updates element position when dragging and starts continuous viewport movement
|
|
120
|
+
* @param event The pointer event that triggered the drag move
|
|
121
|
+
*/
|
|
122
|
+
private onDragMove(event: FederatedPointerEvent) {
|
|
123
|
+
if (!this.isDragging || !this.elementRef?.componentInstance || !this.elementRef.componentInstance.parent) return;
|
|
124
|
+
|
|
125
|
+
const instance = this.elementRef.componentInstance;
|
|
126
|
+
const parent = instance.parent;
|
|
127
|
+
const dragProps = this.dragProps;
|
|
128
|
+
const propObservables = this.elementRef.propObservables;
|
|
129
|
+
const snapTo = snap(dragProps?.snap() ?? 0);
|
|
130
|
+
|
|
131
|
+
dragProps?.move?.(event);
|
|
132
|
+
|
|
133
|
+
const currentParentLocalPointer = parent.toLocal(event.global);
|
|
134
|
+
|
|
135
|
+
const newX = currentParentLocalPointer.x - this.offsetInParent.x;
|
|
136
|
+
const newY = currentParentLocalPointer.y - this.offsetInParent.y;
|
|
137
|
+
|
|
138
|
+
if (dragProps?.snap()) {
|
|
139
|
+
instance.position.x = snapTo(newX);
|
|
140
|
+
instance.position.y = snapTo(newY);
|
|
141
|
+
} else {
|
|
142
|
+
if (this.axis.x) instance.position.x = newX;
|
|
143
|
+
if (this.axis.y) instance.position.y = newY;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Store the last pointer position for continuous viewport movement
|
|
147
|
+
this.lastPointerPosition.copyFrom(event.global);
|
|
148
|
+
|
|
149
|
+
const { x: xProp, y: yProp } = propObservables as any;
|
|
150
|
+
if (xProp !== undefined && isSignal(xProp)) {
|
|
151
|
+
// xProp.set(instance.position.x)
|
|
152
|
+
}
|
|
153
|
+
if (yProp !== undefined && isSignal(yProp)) {
|
|
154
|
+
// yProp.set(instance.position.y)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Moves the viewport if the dragged element is near screen edges
|
|
160
|
+
* @param globalPosition The global pointer position
|
|
161
|
+
*/
|
|
162
|
+
private updateViewportPosition(globalPosition: Point) {
|
|
163
|
+
if (!this.viewport || !this.elementRef) return;
|
|
164
|
+
|
|
165
|
+
const dragProps = this.dragProps;
|
|
166
|
+
const edgeThreshold = dragProps?.viewport?.edgeThreshold(); // Distance from edge to trigger viewport movement
|
|
167
|
+
const maxSpeed = dragProps?.viewport?.maxSpeed(); // Maximum speed when element is at the very edge
|
|
168
|
+
|
|
169
|
+
// Calculate screen boundaries
|
|
170
|
+
const screenLeft = 0;
|
|
171
|
+
const screenRight = this.viewport.screenWidth;
|
|
172
|
+
const screenTop = 0;
|
|
173
|
+
const screenBottom = this.viewport.screenHeight;
|
|
174
|
+
const instance = this.elementRef.componentInstance;
|
|
175
|
+
|
|
176
|
+
// Calculate distances from element to screen edges
|
|
177
|
+
const distanceFromLeft = globalPosition.x - screenLeft;
|
|
178
|
+
const distanceFromRight = screenRight - globalPosition.x;
|
|
179
|
+
const distanceFromTop = globalPosition.y - screenTop;
|
|
180
|
+
const distanceFromBottom = screenBottom - globalPosition.y;
|
|
181
|
+
|
|
182
|
+
let moveX = 0;
|
|
183
|
+
let moveY = 0;
|
|
184
|
+
|
|
185
|
+
// Calculate horizontal movement with dynamic velocity
|
|
186
|
+
if (distanceFromLeft < edgeThreshold) {
|
|
187
|
+
// Velocity increases as distance decreases
|
|
188
|
+
// When distance = 0, velocity = maxSpeed
|
|
189
|
+
// When distance = threshold, velocity = 0
|
|
190
|
+
const velocity = maxSpeed * (1 - (distanceFromLeft / edgeThreshold));
|
|
191
|
+
moveX = -velocity;
|
|
192
|
+
} else if (distanceFromRight < edgeThreshold) {
|
|
193
|
+
const velocity = maxSpeed * (1 - (distanceFromRight / edgeThreshold));
|
|
194
|
+
moveX = velocity;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Calculate vertical movement with dynamic velocity
|
|
198
|
+
if (distanceFromTop < edgeThreshold) {
|
|
199
|
+
const velocity = maxSpeed * (1 - (distanceFromTop / edgeThreshold));
|
|
200
|
+
moveY = -velocity;
|
|
201
|
+
} else if (distanceFromBottom < edgeThreshold) {
|
|
202
|
+
const velocity = maxSpeed * (1 - (distanceFromBottom / edgeThreshold));
|
|
203
|
+
moveY = velocity;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Apply movement with velocity-based displacement
|
|
207
|
+
if (moveX !== 0 || moveY !== 0) {
|
|
208
|
+
const lastViewValue = this.viewport.center;
|
|
209
|
+
this.viewport.moveCenter(
|
|
210
|
+
this.viewport.center.x + moveX,
|
|
211
|
+
this.viewport.center.y + moveY
|
|
212
|
+
);
|
|
213
|
+
if (this.axis.x && lastViewValue.x !== this.viewport.center.x) {
|
|
214
|
+
instance.position.x += moveX;
|
|
55
215
|
}
|
|
56
|
-
if (
|
|
57
|
-
|
|
216
|
+
if (this.axis.y && lastViewValue.y !== this.viewport.center.y) {
|
|
217
|
+
instance.position.y += moveY;
|
|
58
218
|
}
|
|
59
219
|
}
|
|
220
|
+
}
|
|
60
221
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Handles drag end event and stops viewport movement
|
|
224
|
+
*/
|
|
225
|
+
private onDragEnd() {
|
|
226
|
+
if (!this.isDragging || !this.elementRef) return;
|
|
66
227
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
228
|
+
const dragProps = this.dragProps;
|
|
229
|
+
this.isDragging = false;
|
|
230
|
+
dragProps?.end?.();
|
|
231
|
+
|
|
232
|
+
if (this.stageRef) {
|
|
233
|
+
this.stageRef.off('pointermove', this.onDragMoveHandler);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private onPointerDown(event: FederatedPointerEvent) {
|
|
238
|
+
if (!this.elementRef?.componentInstance || !this.stageRef || !this.elementRef.componentInstance.parent) return;
|
|
239
|
+
|
|
240
|
+
const instance = this.elementRef.componentInstance;
|
|
241
|
+
const parent = instance.parent;
|
|
242
|
+
const dragProps = this.dragProps;
|
|
243
|
+
|
|
244
|
+
const parentLocalPointer = parent.toLocal(event.global);
|
|
71
245
|
|
|
72
|
-
|
|
73
|
-
|
|
246
|
+
this.offsetInParent.x = parentLocalPointer.x - instance.position.x;
|
|
247
|
+
this.offsetInParent.y = parentLocalPointer.y - instance.position.y;
|
|
248
|
+
|
|
249
|
+
this.isDragging = true;
|
|
250
|
+
|
|
251
|
+
// Store initial pointer position
|
|
252
|
+
this.lastPointerPosition.copyFrom(event.global);
|
|
253
|
+
|
|
254
|
+
dragProps?.start?.();
|
|
255
|
+
this.stageRef.on('pointermove', this.onDragMoveHandler);
|
|
74
256
|
}
|
|
75
257
|
|
|
76
|
-
onUpdate() {
|
|
258
|
+
onUpdate(props) {
|
|
259
|
+
if (props.type && props.type === 'reset') {
|
|
260
|
+
this.onDestroy();
|
|
261
|
+
this.onMount(this.elementRef);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
77
264
|
|
|
78
265
|
onDestroy() {
|
|
79
|
-
|
|
266
|
+
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
|
267
|
+
const instance = this.elementRef?.componentInstance;
|
|
268
|
+
if (instance) {
|
|
269
|
+
instance.off('pointerdown', this.onDragStartHandler);
|
|
270
|
+
}
|
|
271
|
+
if (this.stageRef) {
|
|
272
|
+
this.stageRef.off('pointermove', this.onDragMoveHandler);
|
|
273
|
+
this.stageRef.off('pointerup', this.onDragEndHandler);
|
|
274
|
+
this.stageRef.off('pointerupoutside', this.onDragEndHandler);
|
|
275
|
+
}
|
|
276
|
+
this.stageRef = null;
|
|
277
|
+
this.viewport = null;
|
|
80
278
|
}
|
|
81
279
|
}
|
|
82
280
|
|
|
83
|
-
|
|
84
|
-
|
|
281
|
+
registerDirective('drag', Drag);
|
|
282
|
+
registerDirective('drop', Drop);
|
|
@@ -366,8 +366,10 @@ export class KeyboardControls extends Directive {
|
|
|
366
366
|
};
|
|
367
367
|
|
|
368
368
|
onInit(element: Element) {
|
|
369
|
+
const value = element.props.controls.value ?? element.props.controls
|
|
370
|
+
if (!value) return
|
|
369
371
|
this.setupListeners();
|
|
370
|
-
this.setInputs(
|
|
372
|
+
this.setInputs(value)
|
|
371
373
|
// The processing is outside the rendering loop because if the FPS are lower (or higher) then the sending to the server would be slower or faster. Here it is constant
|
|
372
374
|
this.interval = setInterval(() => {
|
|
373
375
|
this.preStep()
|
|
@@ -8,18 +8,24 @@ export class ViewportFollow extends Directive {
|
|
|
8
8
|
|
|
9
9
|
}
|
|
10
10
|
onMount(element: Element) {
|
|
11
|
+
this.onUpdate(element.props, element)
|
|
12
|
+
}
|
|
13
|
+
onUpdate(props: any, element: Element) {
|
|
11
14
|
const { viewportFollow } = element.props
|
|
12
15
|
const { viewport } = element.props.context
|
|
13
16
|
if (!viewport) {
|
|
14
17
|
throw error('ViewportFollow directive requires a Viewport component to be mounted in the same context')
|
|
15
18
|
}
|
|
16
|
-
if (viewportFollow)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
if (viewportFollow) {
|
|
20
|
+
viewport.follow(element.componentInstance)
|
|
21
|
+
} else {
|
|
22
|
+
viewport.plugins.remove('follow')
|
|
23
|
+
}
|
|
20
24
|
}
|
|
21
|
-
onDestroy() {
|
|
22
|
-
|
|
25
|
+
onDestroy(element: Element) {
|
|
26
|
+
const { viewportFollow } = element.props
|
|
27
|
+
const { viewport } = element.props.context
|
|
28
|
+
if (viewportFollow) viewport.plugins.remove('follow')
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
|
package/src/engine/directive.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { Element } from "./reactive"
|
|
|
3
3
|
export const directives: { [key: string]: any } = {}
|
|
4
4
|
|
|
5
5
|
export abstract class Directive {
|
|
6
|
-
abstract onDestroy();
|
|
6
|
+
abstract onDestroy(element: Element<any>);
|
|
7
7
|
abstract onInit(element: Element<any>);
|
|
8
8
|
abstract onMount(element: Element<any>);
|
|
9
|
-
abstract onUpdate(props: any);
|
|
9
|
+
abstract onUpdate(props: any, element: Element<any>);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function registerDirective(name: string, directive: any) {
|
package/src/engine/reactive.ts
CHANGED
|
@@ -105,7 +105,7 @@ function destroyElement(element: Element | Element[]) {
|
|
|
105
105
|
element.propSubscriptions.forEach((sub) => sub.unsubscribe());
|
|
106
106
|
element.effectSubscriptions.forEach((sub) => sub.unsubscribe());
|
|
107
107
|
for (let name in element.directives) {
|
|
108
|
-
element.directives[name].onDestroy?.();
|
|
108
|
+
element.directives[name].onDestroy?.(element);
|
|
109
109
|
}
|
|
110
110
|
element.componentInstance.onDestroy?.(element.parent as any);
|
|
111
111
|
element.effectUnmounts.forEach((fn) => fn?.());
|
|
@@ -165,7 +165,7 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
165
165
|
_value.observable.subscribe((value) => {
|
|
166
166
|
_set(path, key, value);
|
|
167
167
|
if (element.directives[key]) {
|
|
168
|
-
element.directives[key].onUpdate?.(value);
|
|
168
|
+
element.directives[key].onUpdate?.(value, element);
|
|
169
169
|
}
|
|
170
170
|
if (key == "tick") {
|
|
171
171
|
return
|
package/src/hooks/useProps.ts
CHANGED