mylingo3d
Version:
Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor
153 lines (130 loc) • 4.69 kB
text/typescript
import Events from "@lincode/events"
import { container } from "../engine/renderLoop/renderSetup"
import IMouse, {
LingoMouseEvent,
mouseDefaults,
mouseSchema,
SimpleMouseEvent
} from "../interface/IMouse"
import EventLoopItem from "./core/EventLoopItem"
import { throttle } from "@lincode/utils"
import { appendableRoot } from "./core/Appendable"
import pointerToWorld from "../display/utils/pointerToWorld"
import store from "@lincode/reactivity"
import Nullable from "../interface/utils/Nullable"
import { onBeforeRender } from "../events/onBeforeRender"
import { getEditing } from "../states/useEditing"
import { getEditorMounted } from "../states/useEditorMounted"
import { getCameraRendered } from "../states/useCameraRendered"
import mainCamera from "../engine/mainCamera"
export type MouseEventName = "click" | "rightClick" | "move" | "down" | "up"
export const mouseEvents = new Events<LingoMouseEvent, MouseEventName>()
let downTime = 0
let downX = 0
let downY = 0
let rightClick = false
container.addEventListener("contextmenu", (e) => {
e.preventDefault()
rightClick = true
})
container.addEventListener("touchstart", (e) => {
e.preventDefault()
})
mouseEvents.on("down", (e) => {
downTime = Date.now()
downX = e.clientX
downY = e.clientY
})
mouseEvents.on("up", (e) => {
const upTime = Date.now()
const deltaTime = upTime - downTime
const deltaX = Math.abs(e.clientX - downX)
const deltaY = Math.abs(e.clientY - downY)
downTime = upTime
downX = e.clientX
downY = e.clientY
if (deltaTime < 300 && deltaX < 5 && deltaY < 5)
mouseEvents.emit(rightClick ? "rightClick" : "click", e)
rightClick = false
})
const computeMouse = throttle(pointerToWorld, 0, "leading")
container.addEventListener("pointermove", (ev) => {
mouseEvents.emit("move", computeMouse(ev))
})
let down = false
container.addEventListener("pointerdown", (ev) => {
down = true
const payload = computeMouse(ev)
mouseEvents.emit("down", payload)
mouseEvents.emit("move", payload)
})
const handleUp = (ev: PointerEvent) => {
down && mouseEvents.emit("up", computeMouse(ev))
down = false
}
container.addEventListener("pointerup", handleUp)
container.addEventListener("pointercancel", handleUp)
container.addEventListener("pointerleave", handleUp)
export class Mouse extends EventLoopItem implements IMouse {
public static componentName = "mouse"
public static defaults = mouseDefaults
public static schema = mouseSchema
public onClick: Nullable<(e: SimpleMouseEvent) => void>
public onRightClick: Nullable<(e: SimpleMouseEvent) => void>
public onMouseMove: Nullable<(e: SimpleMouseEvent) => void>
public onMouseDown: Nullable<(e: SimpleMouseEvent) => void>
public onMouseUp: Nullable<(e: SimpleMouseEvent) => void>
public onMousePress: Nullable<(e: SimpleMouseEvent) => void>
public constructor() {
super()
let currentPayload = { clientX: 0, clientY: 0 }
const [setDown, getDown] = store(false)
this.createEffect(() => {
const cb = this.onMousePress
if (!getDown() || !cb) return
const handle = onBeforeRender(() => cb(currentPayload))
return () => {
handle.cancel()
}
}, [getDown])
this.createEffect(() => {
if (
getEditing() ||
(getEditorMounted() && getCameraRendered() === mainCamera)
)
return
const handle0 = mouseEvents.on("move", (e) => {
this.onMouseMove?.(e)
currentPayload = e
})
const handle1 = mouseEvents.on("click", (e) => {
this.onClick?.(e)
currentPayload = e
})
const handle2 = mouseEvents.on("rightClick", (e) => {
this.onRightClick?.(e)
currentPayload = e
})
const handle3 = mouseEvents.on("down", (e) => {
this.onMouseDown?.(e)
currentPayload = e
setDown(true)
})
const handle4 = mouseEvents.on("up", (e) => {
this.onMouseUp?.(e)
currentPayload = e
setDown(false)
})
return () => {
handle0.cancel()
handle1.cancel()
handle2.cancel()
handle3.cancel()
handle4.cancel()
}
}, [getEditing, getEditorMounted, getCameraRendered])
}
}
const mouse = new Mouse()
appendableRoot.delete(mouse)
export default mouse