@limrun/ui 0.6.0 → 0.7.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.
- package/dist/index.cjs +1 -1
- package/dist/index.js +388 -403
- package/package.json +1 -1
- package/src/components/remote-control.tsx +64 -102
package/package.json
CHANGED
|
@@ -319,7 +319,6 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
319
319
|
|
|
320
320
|
// Minimal geometry for single-finger touch events (no mirror/container coords needed).
|
|
321
321
|
type PointerGeometry = {
|
|
322
|
-
inside: boolean;
|
|
323
322
|
videoX: number;
|
|
324
323
|
videoY: number;
|
|
325
324
|
videoWidth: number;
|
|
@@ -332,7 +331,7 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
332
331
|
geometry: PointerGeometry | null,
|
|
333
332
|
) => {
|
|
334
333
|
if (!geometry) return;
|
|
335
|
-
const {
|
|
334
|
+
const { videoX, videoY, videoWidth, videoHeight } = geometry;
|
|
336
335
|
|
|
337
336
|
let action: number | null = null;
|
|
338
337
|
let positionToSend: { x: number; y: number } | null = null;
|
|
@@ -341,35 +340,26 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
341
340
|
|
|
342
341
|
switch (eventType) {
|
|
343
342
|
case 'down':
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
videoRef.current?.focus();
|
|
356
|
-
}
|
|
357
|
-
} else {
|
|
358
|
-
// If the initial down event is outside, ignore it for this pointer
|
|
359
|
-
activePointers.current.delete(pointerId);
|
|
343
|
+
// For multi-touch: use ACTION_DOWN for first pointer, ACTION_POINTER_DOWN for additional pointers
|
|
344
|
+
const currentPointerCount = activePointers.current.size;
|
|
345
|
+
action =
|
|
346
|
+
currentPointerCount === 0
|
|
347
|
+
? AMOTION_EVENT.ACTION_DOWN
|
|
348
|
+
: AMOTION_EVENT.ACTION_POINTER_DOWN;
|
|
349
|
+
positionToSend = { x: videoX, y: videoY };
|
|
350
|
+
activePointers.current.set(pointerId, positionToSend);
|
|
351
|
+
if (pointerId === -1) {
|
|
352
|
+
// Focus on mouse down
|
|
353
|
+
videoRef.current?.focus();
|
|
360
354
|
}
|
|
361
355
|
break;
|
|
362
356
|
|
|
363
357
|
case 'move':
|
|
364
358
|
if (activePointers.current.has(pointerId)) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
activePointers.current.set(pointerId, positionToSend);
|
|
370
|
-
} else {
|
|
371
|
-
// Moved outside while active - do nothing, UP/CANCEL will use last known pos
|
|
372
|
-
}
|
|
359
|
+
action = AMOTION_EVENT.ACTION_MOVE;
|
|
360
|
+
positionToSend = { x: videoX, y: videoY };
|
|
361
|
+
// Update the last known position for this active pointer
|
|
362
|
+
activePointers.current.set(pointerId, positionToSend);
|
|
373
363
|
}
|
|
374
364
|
break;
|
|
375
365
|
|
|
@@ -401,7 +391,6 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
401
391
|
eventType,
|
|
402
392
|
action,
|
|
403
393
|
actionName: motionActionToString(action),
|
|
404
|
-
isInside,
|
|
405
394
|
positionToSend,
|
|
406
395
|
video: { width: videoWidth, height: videoHeight },
|
|
407
396
|
altHeld: isAltHeldRef.current,
|
|
@@ -431,7 +420,6 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
431
420
|
sendBinaryControlMessage(message);
|
|
432
421
|
}
|
|
433
422
|
} else if (eventType === 'up' || eventType === 'cancel') {
|
|
434
|
-
// Clean up map just in case if 'down' was outside and 'up'/'cancel' is triggered
|
|
435
423
|
activePointers.current.delete(pointerId);
|
|
436
424
|
}
|
|
437
425
|
};
|
|
@@ -524,8 +512,8 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
524
512
|
};
|
|
525
513
|
};
|
|
526
514
|
|
|
527
|
-
// Map a client point to video coordinates using a pre-computed context
|
|
528
|
-
//
|
|
515
|
+
// Map a client point to video coordinates using a pre-computed context,
|
|
516
|
+
// clamping points outside the rendered video to the nearest point on the video.
|
|
529
517
|
const mapClientPointToVideo = (
|
|
530
518
|
ctx: VideoMappingContext,
|
|
531
519
|
clientX: number,
|
|
@@ -534,25 +522,12 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
534
522
|
const relativeX = clientX - ctx.videoRect.left - ctx.offsetX;
|
|
535
523
|
const relativeY = clientY - ctx.videoRect.top - ctx.offsetY;
|
|
536
524
|
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (!isInside) {
|
|
542
|
-
return {
|
|
543
|
-
inside: false,
|
|
544
|
-
videoX: 0,
|
|
545
|
-
videoY: 0,
|
|
546
|
-
videoWidth: ctx.videoWidth,
|
|
547
|
-
videoHeight: ctx.videoHeight,
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const videoX = Math.max(0, Math.min(ctx.videoWidth, (relativeX / ctx.actualWidth) * ctx.videoWidth));
|
|
552
|
-
const videoY = Math.max(0, Math.min(ctx.videoHeight, (relativeY / ctx.actualHeight) * ctx.videoHeight));
|
|
525
|
+
const clampedRelativeX = Math.max(0, Math.min(ctx.actualWidth, relativeX));
|
|
526
|
+
const clampedRelativeY = Math.max(0, Math.min(ctx.actualHeight, relativeY));
|
|
527
|
+
const videoX = Math.max(0, Math.min(ctx.videoWidth, (clampedRelativeX / ctx.actualWidth) * ctx.videoWidth));
|
|
528
|
+
const videoY = Math.max(0, Math.min(ctx.videoHeight, (clampedRelativeY / ctx.actualHeight) * ctx.videoHeight));
|
|
553
529
|
|
|
554
530
|
return {
|
|
555
|
-
inside: true,
|
|
556
531
|
videoX,
|
|
557
532
|
videoY,
|
|
558
533
|
videoWidth: ctx.videoWidth,
|
|
@@ -560,7 +535,8 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
560
535
|
};
|
|
561
536
|
};
|
|
562
537
|
|
|
563
|
-
// Compute full hover point with mirror/container coordinates (for Alt indicator rendering)
|
|
538
|
+
// Compute full hover point with mirror/container coordinates (for Alt indicator rendering),
|
|
539
|
+
// clamping points outside the rendered video to the nearest point on the video.
|
|
564
540
|
const computeFullHoverPoint = (
|
|
565
541
|
ctx: VideoMappingContext,
|
|
566
542
|
clientX: number,
|
|
@@ -569,25 +545,19 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
569
545
|
const relativeX = clientX - ctx.videoRect.left - ctx.offsetX;
|
|
570
546
|
const relativeY = clientY - ctx.videoRect.top - ctx.offsetY;
|
|
571
547
|
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
if (!isInside) {
|
|
577
|
-
return null;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
const videoX = Math.max(0, Math.min(ctx.videoWidth, (relativeX / ctx.actualWidth) * ctx.videoWidth));
|
|
581
|
-
const videoY = Math.max(0, Math.min(ctx.videoHeight, (relativeY / ctx.actualHeight) * ctx.videoHeight));
|
|
548
|
+
const clampedRelativeX = Math.max(0, Math.min(ctx.actualWidth, relativeX));
|
|
549
|
+
const clampedRelativeY = Math.max(0, Math.min(ctx.actualHeight, relativeY));
|
|
550
|
+
const videoX = Math.max(0, Math.min(ctx.videoWidth, (clampedRelativeX / ctx.actualWidth) * ctx.videoWidth));
|
|
551
|
+
const videoY = Math.max(0, Math.min(ctx.videoHeight, (clampedRelativeY / ctx.actualHeight) * ctx.videoHeight));
|
|
582
552
|
const mirrorVideoX = ctx.videoWidth - videoX;
|
|
583
553
|
const mirrorVideoY = ctx.videoHeight - videoY;
|
|
584
554
|
|
|
585
555
|
const contentLeft = ctx.videoRect.left + ctx.offsetX;
|
|
586
556
|
const contentTop = ctx.videoRect.top + ctx.offsetY;
|
|
587
|
-
const containerX = contentLeft - ctx.containerRect.left +
|
|
588
|
-
const containerY = contentTop - ctx.containerRect.top +
|
|
589
|
-
const mirrorContainerX = contentLeft - ctx.containerRect.left + (ctx.actualWidth -
|
|
590
|
-
const mirrorContainerY = contentTop - ctx.containerRect.top + (ctx.actualHeight -
|
|
557
|
+
const containerX = contentLeft - ctx.containerRect.left + clampedRelativeX;
|
|
558
|
+
const containerY = contentTop - ctx.containerRect.top + clampedRelativeY;
|
|
559
|
+
const mirrorContainerX = contentLeft - ctx.containerRect.left + (ctx.actualWidth - clampedRelativeX);
|
|
560
|
+
const mirrorContainerY = contentTop - ctx.containerRect.top + (ctx.actualHeight - clampedRelativeY);
|
|
591
561
|
|
|
592
562
|
return {
|
|
593
563
|
containerX,
|
|
@@ -751,29 +721,25 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
751
721
|
|
|
752
722
|
if (!twoFingerStateRef.current) {
|
|
753
723
|
// Starting a new two-finger gesture
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
t0.identifier, t1.identifier);
|
|
766
|
-
}
|
|
724
|
+
twoFingerStateRef.current = {
|
|
725
|
+
finger0: { x: g0.videoX, y: g0.videoY },
|
|
726
|
+
finger1: { x: g1.videoX, y: g1.videoY },
|
|
727
|
+
videoSize: { width: g0.videoWidth, height: g0.videoHeight },
|
|
728
|
+
source: 'real-touch',
|
|
729
|
+
pointerId0: t0.identifier,
|
|
730
|
+
pointerId1: t1.identifier,
|
|
731
|
+
};
|
|
732
|
+
applyTwoFingerEvent('down', g0.videoWidth, g0.videoHeight,
|
|
733
|
+
g0.videoX, g0.videoY, g1.videoX, g1.videoY,
|
|
734
|
+
t0.identifier, t1.identifier);
|
|
767
735
|
} else if (twoFingerStateRef.current.source === 'real-touch') {
|
|
768
736
|
// Continuing two-finger gesture (move)
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
twoFingerStateRef.current.pointerId1);
|
|
776
|
-
}
|
|
737
|
+
twoFingerStateRef.current.finger0 = { x: g0.videoX, y: g0.videoY };
|
|
738
|
+
twoFingerStateRef.current.finger1 = { x: g1.videoX, y: g1.videoY };
|
|
739
|
+
applyTwoFingerEvent('move', g0.videoWidth, g0.videoHeight,
|
|
740
|
+
g0.videoX, g0.videoY, g1.videoX, g1.videoY,
|
|
741
|
+
twoFingerStateRef.current.pointerId0,
|
|
742
|
+
twoFingerStateRef.current.pointerId1);
|
|
777
743
|
}
|
|
778
744
|
} else if (allTouches.length < 2 && twoFingerStateRef.current?.source === 'real-touch') {
|
|
779
745
|
// Finger lifted - end two-finger gesture using last known state
|
|
@@ -841,7 +807,6 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
841
807
|
altHeldRef: isAltHeldRef.current,
|
|
842
808
|
inTwoFingerMode,
|
|
843
809
|
geometry: {
|
|
844
|
-
inside: geometry.inside,
|
|
845
810
|
videoX: geometry.videoX,
|
|
846
811
|
videoY: geometry.videoY,
|
|
847
812
|
videoWidth: geometry.videoWidth,
|
|
@@ -868,44 +833,41 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
868
833
|
eventType: 'down' | 'move' | 'up' | 'cancel',
|
|
869
834
|
geometry: PointerGeometry,
|
|
870
835
|
) => {
|
|
871
|
-
const {
|
|
836
|
+
const { videoX, videoY, videoWidth, videoHeight } = geometry;
|
|
872
837
|
const mirrorX = videoWidth - videoX;
|
|
873
838
|
const mirrorY = videoHeight - videoY;
|
|
874
839
|
|
|
875
840
|
if (eventType === 'down') {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
ALT_POINTER_ID_PRIMARY, ALT_POINTER_ID_MIRROR);
|
|
889
|
-
}
|
|
841
|
+
// Start two-finger gesture
|
|
842
|
+
twoFingerStateRef.current = {
|
|
843
|
+
finger0: { x: videoX, y: videoY },
|
|
844
|
+
finger1: { x: mirrorX, y: mirrorY },
|
|
845
|
+
videoSize: { width: videoWidth, height: videoHeight },
|
|
846
|
+
source: 'alt-mouse',
|
|
847
|
+
pointerId0: ALT_POINTER_ID_PRIMARY,
|
|
848
|
+
pointerId1: ALT_POINTER_ID_MIRROR,
|
|
849
|
+
};
|
|
850
|
+
videoRef.current?.focus();
|
|
851
|
+
applyTwoFingerEvent('down', videoWidth, videoHeight, videoX, videoY, mirrorX, mirrorY,
|
|
852
|
+
ALT_POINTER_ID_PRIMARY, ALT_POINTER_ID_MIRROR);
|
|
890
853
|
return;
|
|
891
854
|
}
|
|
892
855
|
|
|
893
856
|
if (eventType === 'move') {
|
|
894
|
-
if (twoFingerStateRef.current?.source === 'alt-mouse'
|
|
857
|
+
if (twoFingerStateRef.current?.source === 'alt-mouse') {
|
|
895
858
|
// Update positions
|
|
896
859
|
twoFingerStateRef.current.finger0 = { x: videoX, y: videoY };
|
|
897
860
|
twoFingerStateRef.current.finger1 = { x: mirrorX, y: mirrorY };
|
|
898
861
|
applyTwoFingerEvent('move', videoWidth, videoHeight, videoX, videoY, mirrorX, mirrorY,
|
|
899
862
|
ALT_POINTER_ID_PRIMARY, ALT_POINTER_ID_MIRROR);
|
|
900
863
|
}
|
|
901
|
-
// If outside, we just don't send a move - UP will use last known position
|
|
902
864
|
return;
|
|
903
865
|
}
|
|
904
866
|
|
|
905
867
|
if (eventType === 'up' || eventType === 'cancel') {
|
|
906
868
|
const state = twoFingerStateRef.current;
|
|
907
869
|
if (state?.source === 'alt-mouse') {
|
|
908
|
-
// End gesture at last known
|
|
870
|
+
// End gesture at last known positions
|
|
909
871
|
const { finger0, finger1, videoSize } = state;
|
|
910
872
|
applyTwoFingerEvent('up', videoSize.width, videoSize.height,
|
|
911
873
|
finger0.x, finger0.y, finger1.x, finger1.y,
|