Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Commit

Permalink
feat: add mouse events to plugin API (#280)
Browse files Browse the repository at this point in the history
* add mouse event api

* clear code

* refactor

* refactor

* fix type

* update type

* refactor: update type

* add screen x y to mouse event props

* add test for useEngineRef

* refactor: add empty line between functions

* refactor: update type definition

* fix: type error

* refactor: rename type
  • Loading branch information
airslice authored Aug 1, 2022
1 parent 7eead9b commit 9445f01
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 6 deletions.
81 changes: 79 additions & 2 deletions src/components/molecules/Visualizer/Engine/Cesium/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ import { useCustomCompareCallback } from "use-custom-compare";
import { Camera, LatLng } from "@reearth/util/value";

import type { SelectLayerOptions, Ref as EngineRef, SceneProperty } from "..";
import { MouseEvent, MouseEvents } from "../ref";

import { useCameraLimiter } from "./cameraLimiter";
import { getCamera, isDraggable, isSelectable, layerIdField } from "./common";
import {
getCamera,
isDraggable,
isSelectable,
layerIdField,
getLocationFromScreenXY,
} from "./common";
import terrain from "./terrain";
import useEngineRef from "./useEngineRef";
import { convertCartesian3ToPosition } from "./utils";
Expand Down Expand Up @@ -173,8 +180,68 @@ export default ({
viewer.selectedEntity = entity;
}, [cesium, selectedLayerId]);

const handleMouseEvent = useCallback(
(type: keyof MouseEvents, e: CesiumMovementEvent, target: RootEventTarget) => {
if (engineAPI.mouseEventCallbacks[type]) {
const viewer = cesium.current?.cesiumElement;
if (!viewer || viewer.isDestroyed()) return;
const position = e.position || e.startPosition;
const props: MouseEvent = {
x: position?.x,
y: position?.y,
...(position ? getLocationFromScreenXY(viewer.scene, position.x, position.y) ?? {} : {}),
};
const layerId = getLayerId(target);
if (layerId) props.layerId = layerId;
engineAPI.mouseEventCallbacks[type]?.(props);
}
},
[engineAPI],
);

const handleMouseWheel = useCallback(
(delta: number) => {
engineAPI.mouseEventCallbacks.wheel?.({ delta });
},
[engineAPI],
);

const mouseEventHandles = useMemo(() => {
const mouseEvents: { [index in keyof MouseEvents]: undefined | any } = {
click: undefined,
doubleclick: undefined,
mousedown: undefined,
mouseup: undefined,
rightclick: undefined,
rightdown: undefined,
rightup: undefined,
middleclick: undefined,
middledown: undefined,
middleup: undefined,
mousemove: undefined,
mouseenter: undefined,
mouseleave: undefined,
pinchstart: undefined,
pinchend: undefined,
pinchmove: undefined,
wheel: undefined,
};
(Object.keys(mouseEvents) as (keyof MouseEvents)[]).forEach(type => {
mouseEvents[type] =
type === "wheel"
? (delta: number) => {
handleMouseWheel(delta);
}
: (e: CesiumMovementEvent, target: RootEventTarget) => {
handleMouseEvent(type as keyof MouseEvents, e, target);
};
});
return mouseEvents;
}, [handleMouseEvent, handleMouseWheel]);

const handleClick = useCallback(
(_: CesiumMovementEvent, target: RootEventTarget) => {
mouseEventHandles.click?.(_, target);
const viewer = cesium.current?.cesiumElement;
if (!viewer || viewer.isDestroyed()) return;

Expand All @@ -198,7 +265,7 @@ export default ({

onLayerSelect?.();
},
[onLayerSelect],
[onLayerSelect, mouseEventHandles],
);

// E2E test
Expand Down Expand Up @@ -278,6 +345,7 @@ export default ({
handleClick,
handleCameraChange,
handleCameraMoveEnd,
mouseEventHandles,
};
};

Expand Down Expand Up @@ -306,3 +374,12 @@ function findEntity(viewer: CesiumViewer, layerId: string | undefined): Entity |
}
return entity;
}

function getLayerId(target: RootEventTarget) {
if (target && "id" in target && target.id instanceof Entity) {
return target.id.id;
} else if (target && target instanceof Cesium3DTileFeature) {
return (target.primitive as any)?.[layerIdField];
}
return undefined;
}
19 changes: 18 additions & 1 deletion src/components/molecules/Visualizer/Engine/Cesium/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const Cesium: React.ForwardRefRenderFunction<EngineRef, EngineProps> = (
handleClick,
handleCameraChange,
handleCameraMoveEnd,
mouseEventHandles,
} = useHooks({
ref,
property,
Expand Down Expand Up @@ -99,7 +100,23 @@ const Cesium: React.ForwardRefRenderFunction<EngineRef, EngineProps> = (
!property?.timeline?.animation && !isLayerDraggable ? Infinity : undefined
}
shadows={!!property?.atmosphere?.shadows}
onClick={handleClick}>
onClick={handleClick}
onDoubleClick={mouseEventHandles.doubleclick}
onMouseDown={mouseEventHandles.mousedown}
onMouseUp={mouseEventHandles.mouseup}
onRightClick={mouseEventHandles.rightclick}
onRightDown={mouseEventHandles.rightdown}
onRightUp={mouseEventHandles.rightup}
onMiddleClick={mouseEventHandles.middleclick}
onMiddleDown={mouseEventHandles.middledown}
onMiddleUp={mouseEventHandles.middleup}
onMouseMove={mouseEventHandles.mousemove}
onMouseEnter={mouseEventHandles.mouseenter}
onMouseLeave={mouseEventHandles.mouseleave}
onPinchStart={mouseEventHandles.pinchstart}
onPinchEnd={mouseEventHandles.pinchend}
onPinchMove={mouseEventHandles.pinchmove}
onWheel={mouseEventHandles.wheel}>
<Event onMount={handleMount} onUnmount={handleUnmount} />
<Clock property={property} />
<ImageryLayers tiles={property?.tiles} cesiumIonAccessToken={property?.default?.ion} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { renderHook } from "@testing-library/react";
import type { Viewer as CesiumViewer } from "cesium";
import { useRef } from "react";
import type { CesiumComponentRef } from "resium";
import { vi, expect, test } from "vitest";

import { EngineRef } from "../ref";

import useEngineRef from "./useEngineRef";

const fn = vi.fn(e => e);
const props = { x: 1, y: 1 };

test("engine should be cesium", () => {
const { result } = renderHook(() => {
const cesium = useRef<CesiumComponentRef<CesiumViewer>>(null);
const engineRef = useRef<EngineRef>(null);
useEngineRef(engineRef, cesium);
return engineRef;
});
expect(result.current.current?.name).toBe("cesium");
});

test("bind mouse events", () => {
const { result } = renderHook(() => {
const cesium = useRef<CesiumComponentRef<CesiumViewer>>(null);
const engineRef = useRef<EngineRef>(null);
useEngineRef(engineRef, cesium);
return engineRef;
});

result.current.current?.onClick(fn);
expect(result.current.current?.mouseEventCallbacks.click).toBe(fn);

result.current.current?.mouseEventCallbacks.click?.(props);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onDoubleClick(fn);
expect(result.current.current?.mouseEventCallbacks.doubleclick).toBe(fn);

result.current.current?.mouseEventCallbacks.doubleclick?.(props);
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onMouseDown(fn);
expect(result.current.current?.mouseEventCallbacks.mousedown).toBe(fn);

result.current.current?.mouseEventCallbacks.mousedown?.(props);
expect(fn).toHaveBeenCalledTimes(3);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onMouseUp(fn);
expect(result.current.current?.mouseEventCallbacks.mouseup).toBe(fn);

result.current.current?.mouseEventCallbacks.mouseup?.(props);
expect(fn).toHaveBeenCalledTimes(4);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onRightClick(fn);
expect(result.current.current?.mouseEventCallbacks.rightclick).toBe(fn);

result.current.current?.mouseEventCallbacks.rightclick?.(props);
expect(fn).toHaveBeenCalledTimes(5);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onRightDown(fn);
expect(result.current.current?.mouseEventCallbacks.rightdown).toBe(fn);

result.current.current?.mouseEventCallbacks.rightdown?.(props);
expect(fn).toHaveBeenCalledTimes(6);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onRightUp(fn);
expect(result.current.current?.mouseEventCallbacks.rightup).toBe(fn);

result.current.current?.mouseEventCallbacks.rightup?.(props);
expect(fn).toHaveBeenCalledTimes(7);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onMiddleClick(fn);
expect(result.current.current?.mouseEventCallbacks.middleclick).toBe(fn);

result.current.current?.mouseEventCallbacks.middleclick?.(props);
expect(fn).toHaveBeenCalledTimes(8);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onMiddleDown(fn);
expect(result.current.current?.mouseEventCallbacks.middledown).toBe(fn);

result.current.current?.mouseEventCallbacks.middledown?.(props);
expect(fn).toHaveBeenCalledTimes(9);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onMiddleUp(fn);
expect(result.current.current?.mouseEventCallbacks.middleup).toBe(fn);

result.current.current?.mouseEventCallbacks.middleup?.(props);
expect(fn).toHaveBeenCalledTimes(10);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onMouseMove(fn);
expect(result.current.current?.mouseEventCallbacks.mousemove).toBe(fn);

result.current.current?.mouseEventCallbacks.mousemove?.(props);
expect(fn).toHaveBeenCalledTimes(11);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onMouseEnter(fn);
expect(result.current.current?.mouseEventCallbacks.mouseenter).toBe(fn);

result.current.current?.mouseEventCallbacks.mouseenter?.(props);
expect(fn).toHaveBeenCalledTimes(12);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onMouseLeave(fn);
expect(result.current.current?.mouseEventCallbacks.mouseleave).toBe(fn);

result.current.current?.mouseEventCallbacks.mouseleave?.(props);
expect(fn).toHaveBeenCalledTimes(13);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onPinchStart(fn);
expect(result.current.current?.mouseEventCallbacks.pinchstart).toBe(fn);

result.current.current?.mouseEventCallbacks.pinchstart?.(props);
expect(fn).toHaveBeenCalledTimes(14);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onPinchEnd(fn);
expect(result.current.current?.mouseEventCallbacks.pinchend).toBe(fn);

result.current.current?.mouseEventCallbacks.pinchend?.(props);
expect(fn).toHaveBeenCalledTimes(15);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onPinchMove(fn);
expect(result.current.current?.mouseEventCallbacks.pinchmove).toBe(fn);

result.current.current?.mouseEventCallbacks.pinchmove?.(props);
expect(fn).toHaveBeenCalledTimes(16);
expect(fn).toHaveBeenCalledWith(props);

result.current.current?.onWheel(fn);
expect(result.current.current?.mouseEventCallbacks.wheel).toBe(fn);

const wheelProps = { delta: 1 };
result.current.current?.mouseEventCallbacks.wheel?.(wheelProps);
expect(fn).toHaveBeenCalledTimes(17);
expect(fn).toHaveBeenCalledWith(wheelProps);
});
72 changes: 72 additions & 0 deletions src/components/molecules/Visualizer/Engine/Cesium/useEngineRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useImperativeHandle, Ref, RefObject, useMemo, useRef } from "react";
import type { CesiumComponentRef } from "resium";

import type { Ref as EngineRef } from "..";
import type { MouseEvents, MouseEvent } from "../ref";

import builtinPrimitives from "./builtin";
import Cluster from "./Cluster";
Expand All @@ -14,6 +15,25 @@ export default function useEngineRef(
cesium: RefObject<CesiumComponentRef<Cesium.Viewer>>,
): EngineRef {
const cancelCameraFlight = useRef<() => void>();
const mouseEventCallbacks = useRef<MouseEvents>({
click: undefined,
doubleclick: undefined,
mousedown: undefined,
mouseup: undefined,
rightclick: undefined,
rightdown: undefined,
rightup: undefined,
middleclick: undefined,
middledown: undefined,
middleup: undefined,
mousemove: undefined,
mouseenter: undefined,
mouseleave: undefined,
pinchstart: undefined,
pinchend: undefined,
pinchmove: undefined,
wheel: undefined,
});
const e = useMemo((): EngineRef => {
return {
name: "cesium",
Expand Down Expand Up @@ -91,6 +111,58 @@ export default function useEngineRef(
break;
}
},
onClick: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.click = cb;
},
onDoubleClick: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.doubleclick = cb;
},
onMouseDown: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.mousedown = cb;
},
onMouseUp: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.mouseup = cb;
},
onRightClick: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.rightclick = cb;
},
onRightDown: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.rightdown = cb;
},
onRightUp: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.rightup = cb;
},
onMiddleClick: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.middleclick = cb;
},
onMiddleDown: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.middledown = cb;
},
onMiddleUp: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.middleup = cb;
},
onMouseMove: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.mousemove = cb;
},
onMouseEnter: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.mouseenter = cb;
},
onMouseLeave: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.mouseleave = cb;
},
onPinchStart: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.pinchstart = cb;
},
onPinchEnd: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.pinchend = cb;
},
onPinchMove: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.pinchmove = cb;
},
onWheel: (cb: ((props: MouseEvent) => void) | undefined) => {
mouseEventCallbacks.current.wheel = cb;
},
mouseEventCallbacks: mouseEventCallbacks.current,
builtinPrimitives,
clusterComponent: Cluster,
};
Expand Down
Loading

0 comments on commit 9445f01

Please sign in to comment.