Skip to content

Commit

Permalink
feat: implement onDragOverMissed & onDropMissed (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
haywirez committed Jul 16, 2022
1 parent e738f58 commit ab76b39
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 5 deletions.
13 changes: 11 additions & 2 deletions example/src/demos/FileDragDrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { OrbitControls } from '@react-three/drei'

export default function Box() {
const [active, setActive] = useState(0)
const [activeBg, setActiveBg] = useState(0)
// create a common spring that will be used later to interpolate other values
const { spring } = useSpring({
spring: active,
Expand All @@ -14,19 +15,27 @@ export default function Box() {
const scale = spring.to([0, 1], [1, 2])
const rotation = spring.to([0, 1], [0, Math.PI])
const color = active ? spring.to([0, 1], ['#6246ea', '#e45858']) : spring.to([0, 1], ['#620000', '#e40000'])
const bgColor = activeBg ? 'lightgreen' : 'lightgray'
const preventDragDropDefaults = {
onDrop: (e: SyntheticEvent) => e.preventDefault(),
onDragEnter: (e: SyntheticEvent) => e.preventDefault(),
onDragOver: (e: SyntheticEvent) => e.preventDefault(),
}
return (
<Canvas {...preventDragDropDefaults}>
<Canvas
{...preventDragDropDefaults}
onDropMissed={(e) => console.log('drop missed!')}
onDragOverMissed={(e) => setActiveBg(1)}>
<color attach="background" args={[bgColor]} />
<a.mesh
rotation-y={rotation}
scale-x={scale}
scale-z={scale}
onDrop={(e) => console.log('dropped!', e)}
onDragOverEnter={() => setActive(1)}
onDragOverEnter={() => {
setActive(1)
setActiveBg(0)
}}
onDragOverLeave={() => setActive(0)}>
<boxBufferGeometry />
<a.meshBasicMaterial color={color} />
Expand Down
33 changes: 30 additions & 3 deletions packages/fiber/src/core/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ export type EventHandlers = {
onContextMenu?: (event: ThreeEvent<MouseEvent>) => void
onDoubleClick?: (event: ThreeEvent<MouseEvent>) => void
onDragOverEnter?: (event: ThreeEvent<DragEvent>) => void
onDragOverLeave?: (event: DragEvent) => void
onDrop?: (event: DragEvent) => void
onDragOverLeave?: (event: ThreeEvent<DragEvent>) => void
onDragOverMissed?: (event: DragEvent) => void
onDrop?: (event: ThreeEvent<DragEvent>) => void
onDropMissed?: (event: DragEvent) => void
onPointerUp?: (event: ThreeEvent<PointerEvent>) => void
onPointerDown?: (event: ThreeEvent<PointerEvent>) => void
onPointerOver?: (event: ThreeEvent<PointerEvent>) => void
Expand Down Expand Up @@ -414,14 +416,15 @@ export function createEvents(store: UseBoundStore<RootState>) {

// Any other pointer goes here ...
return (event: DomEvent) => {
const { onPointerMissed, internal } = store.getState()
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = store.getState()

//prepareRay(event)
internal.lastEvent.current = event

// Get fresh intersects
const isPointerMove = name === 'onPointerMove'
const isDragOver = name === 'onDragOverEnter' || name === 'onDragOverLeave'
const isDrop = name === 'onDrop'
const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick'
const filter = isPointerMove ? filterPointerEvents : undefined
//const hits = patchIntersects(intersect(filter), event)
Expand All @@ -442,6 +445,15 @@ export function createEvents(store: UseBoundStore<RootState>) {
if (onPointerMissed) onPointerMissed(event)
}
}
if (isDragOver && !hits.length) {
dragOverMissed(event as DragEvent, internal.interaction)
if (onDragOverMissed) onDragOverMissed(event as DragEvent)
}
if (isDrop && !hits.length) {
dropMissed(event as DragEvent, internal.interaction)
if (onDropMissed) onDropMissed(event as DragEvent)
}

// Take care of unhover
if (isPointerMove || isDragOver) cancelPointer(hits)

Expand Down Expand Up @@ -481,6 +493,11 @@ export function createEvents(store: UseBoundStore<RootState>) {
} else if (hoveredItem.stopped) {
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
data.stopPropagation()
} else if (internal.initialHits.includes(eventObject)) {
dragOverMissed(
event as DragEvent,
internal.interaction.filter((object) => !internal.initialHits.includes(object)),
)
}
} else {
// All other events ...
Expand Down Expand Up @@ -517,5 +534,15 @@ export function createEvents(store: UseBoundStore<RootState>) {
)
}

function dragOverMissed(event: DragEvent, objects: THREE.Object3D[]) {
objects.forEach((object: THREE.Object3D) =>
(object as unknown as Instance).__r3f?.handlers.onDragOverMissed?.(event),
)
}

function dropMissed(event: DragEvent, objects: THREE.Object3D[]) {
objects.forEach((object: THREE.Object3D) => (object as unknown as Instance).__r3f?.handlers.onDropMissed?.(event))
}

return { handlePointer }
}
10 changes: 10 additions & 0 deletions packages/fiber/src/core/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export type RenderProps<TCanvas extends Element> = {
onCreated?: (state: RootState) => void
/** Response for pointer clicks that have missed any target */
onPointerMissed?: (event: MouseEvent) => void
/** Response for dragover events that have missed any target */
onDragOverMissed?: (event: DragEvent) => void
/** Response for drop events that have missed any target */
onDropMissed?: (event: DragEvent) => void
}

const createRendererInstance = <TElement extends Element>(gl: GLProps, canvas: TElement): THREE.WebGLRenderer => {
Expand Down Expand Up @@ -169,6 +173,8 @@ function createRoot<TCanvas extends Element>(canvas: TCanvas): ReconcilerRoot<TC
raycaster: raycastOptions,
camera: cameraOptions,
onPointerMissed,
onDragOverMissed,
onDropMissed,
} = props

let state = store.getState()
Expand Down Expand Up @@ -282,6 +288,10 @@ function createRoot<TCanvas extends Element>(canvas: TCanvas): ReconcilerRoot<TC
if (state.frameloop !== frameloop) state.setFrameloop(frameloop)
// Check pointer missed
if (!state.onPointerMissed) state.set({ onPointerMissed })
// Check dragover missed
if (!state.onDragOverMissed) state.set({ onDragOverMissed })
// Check drop missed
if (!state.onDropMissed) state.set({ onDropMissed })
// Check performance
if (performance && !is.equ(performance, state.performance, shallowLoose))
state.set((state) => ({ performance: { ...state.performance, ...performance } }))
Expand Down
6 changes: 6 additions & 0 deletions packages/fiber/src/core/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ export type RootState = {
setFrameloop: (frameloop?: 'always' | 'demand' | 'never') => void
/** When the canvas was clicked but nothing was hit */
onPointerMissed?: (event: MouseEvent) => void
/** When the canvas was dragover but nothing was hit */
onDragOverMissed?: (event: DragEvent) => void
/** When the canvas was dropped but nothing was hit */
onDropMissed?: (event: DragEvent) => void
/** If this state model is layerd (via createPortal) then this contains the previous layer */
previousRoot?: UseBoundStore<RootState, StoreApi<RootState>>
/** Internals */
Expand Down Expand Up @@ -209,6 +213,8 @@ const createStore = (

frameloop: 'always',
onPointerMissed: undefined,
onDragOverMissed: undefined,
onDropMissed: undefined,

performance: {
current: 1,
Expand Down
6 changes: 6 additions & 0 deletions packages/fiber/src/web/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const Canvas = /*#__PURE__*/ React.forwardRef<HTMLCanvasElement, Props>(f
raycaster,
camera,
onPointerMissed,
onDragOverMissed,
onDropMissed,
onCreated,
...props
},
Expand All @@ -57,6 +59,8 @@ export const Canvas = /*#__PURE__*/ React.forwardRef<HTMLCanvasElement, Props>(f
React.useImperativeHandle(forwardedRef, () => canvasRef.current)

const handlePointerMissed = useMutableCallback(onPointerMissed)
const handleDragOverMissed = useMutableCallback(onDragOverMissed)
const handleDropMissed = useMutableCallback(onDropMissed)
const [block, setBlock] = React.useState<SetBlock>(false)
const [error, setError] = React.useState<any>(false)

Expand Down Expand Up @@ -85,6 +89,8 @@ export const Canvas = /*#__PURE__*/ React.forwardRef<HTMLCanvasElement, Props>(f
size: { width, height },
// Pass mutable reference to onPointerMissed so it's free to update
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
onDropMissed: (...args) => handleDropMissed.current?.(...args),
onCreated: (state) => {
state.events.connect?.(divRef.current)
onCreated?.(state)
Expand Down

0 comments on commit ab76b39

Please sign in to comment.