Remove DraggableArea in favor of useDrag

This commit is contained in:
vezwork 2022-05-13 11:08:49 -04:00 committed by Cole Lawrence
parent 386a8fafac
commit b547282d95
15 changed files with 179 additions and 338 deletions

View file

@ -155,7 +155,7 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
return {
debugName: 'useDragKeyframe',
lockCursorTo: 'ew-resize',
lockCSSCursorTo: 'ew-resize',
onDragStart(event) {
const props = propsRef.current
let tempTransaction: CommitOrDiscard | undefined

View file

@ -253,7 +253,7 @@ function useKeyframeDrag(
const handlers = useFreezableMemo<Parameters<typeof useDrag>[1]>(
(setFrozen) => ({
debugName: 'CurveSegmentEditor/useKeyframeDrag',
lockCursorTo: 'move',
lockCSSCursorTo: 'move',
onDragStart() {
setFrozen(true)
return {

View file

@ -60,7 +60,7 @@ function useCaptureSelection(
return {
debugName: 'DopeSheetSelectionView/useCaptureSelection',
dontBlockMouseDown: true,
lockCursorTo: 'cell',
lockCSSCursorTo: 'cell',
onDragStart(event) {
if (!event.shiftKey || event.target instanceof HTMLInputElement) {
return false

View file

@ -265,7 +265,7 @@ function useHandlePanAndZoom(
debugName: 'HorizontallyScrollableArea Middle Button Drag',
buttons: [1],
lockCursorTo: 'grabbing',
lockCSSCursorTo: 'grabbing',
}
}, [layoutP]),
)

View file

@ -226,7 +226,7 @@ function useDragBulge(
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
return {
debugName: 'LengthIndicator/useDragBulge',
lockCursorTo: 'ew-resize',
lockCSSCursorTo: 'ew-resize',
onDragStart(event) {
let tempTransaction: CommitOrDiscard | undefined

View file

@ -125,7 +125,7 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
const handlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
return {
debugName: 'CurveHandler/useOurDrags',
lockCursorTo: 'move',
lockCSSCursorTo: 'move',
onDragStart() {
let tempTransaction: CommitOrDiscard | undefined

View file

@ -107,7 +107,7 @@ function useDragKeyframe(
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
return {
debugName: 'GraphEditorDotNonScalar/useDragKeyframe',
lockCursorTo: 'ew-resize',
lockCSSCursorTo: 'ew-resize',
onDragStart(event) {
setIsDragging(true)
const propsAtStartOfDrag = propsRef.current

View file

@ -108,7 +108,7 @@ function useDragKeyframe(
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
return {
debugName: 'GraphEditorDotScalar/useDragKeyframe',
lockCursorTo: 'move',
lockCSSCursorTo: 'move',
onDragStart(event) {
setIsDragging(true)
const keepSpeeds = !!event.altKey

View file

@ -246,7 +246,7 @@ const FocusRangeStrip: React.FC<{
}
},
lockCursorTo: 'grabbing',
lockCSSCursorTo: 'grabbing',
}
}, [sheet, scaledSpaceToUnitSpace])

View file

@ -169,7 +169,7 @@ function usePanelDragZoneGestureHandlers(
}
},
lockCursorTo: 'ew-resize',
lockCSSCursorTo: 'ew-resize',
}
}
@ -214,7 +214,7 @@ function usePanelDragZoneGestureHandlers(
},
}
},
lockCursorTo: 'move',
lockCSSCursorTo: 'move',
}
}

View file

@ -1,5 +1,4 @@
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import DraggableArea from '@theatre/studio/uiComponents/DraggableArea'
import {useVal} from '@theatre/react'
import type {IRange} from '@theatre/shared/utils/types'
import type {Pointer} from '@theatre/dataverse'
@ -11,6 +10,8 @@ import styled from 'styled-components'
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
import useDrag from '@theatre/studio/uiComponents/useDrag'
const Container = styled.div`
--threadHeight: 6px;
@ -174,13 +175,10 @@ const HorizontalScrollbar: React.FC<{
}
const self = {
onDragStart() {
onRangeDragStart() {
noteValuesBeforeDrag()
},
onDragEnd() {
setBeingDragged('nothing')
},
dragRange: (dx: number) => {
return {
onDrag(dx: number) {
setBeingDragged('both')
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
@ -191,14 +189,23 @@ const HorizontalScrollbar: React.FC<{
val(layoutP.clippedSpace.setRange)(newRange)
},
onDragEnd() {
setBeingDragged('nothing')
},
}
},
dragRangeStart: (dx: number) => {
onRangeStartDragStart() {
noteValuesBeforeDrag()
return {
onDrag(dx: number) {
setBeingDragged('start')
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
const newRange: IRange = {
start: valuesBeforeDrag.clippedSpaceRange.start + deltaPosInUnitSpace,
start:
valuesBeforeDrag.clippedSpaceRange.start + deltaPosInUnitSpace,
end: valuesBeforeDrag.clippedSpaceRange.end,
}
@ -212,8 +219,16 @@ const HorizontalScrollbar: React.FC<{
val(layoutP.clippedSpace.setRange)(newRange)
},
onDragEnd() {
setBeingDragged('nothing')
},
}
},
dragRangeEnd: (dx: number) => {
onRangeEndDragStart() {
noteValuesBeforeDrag()
return {
onDrag(dx: number) {
setBeingDragged('end')
const deltaPosInUnitSpace = deltaXToDeltaPos(dx)
@ -233,58 +248,70 @@ const HorizontalScrollbar: React.FC<{
val(layoutP.clippedSpace.setRange)(newRange)
},
onDragEnd() {
setBeingDragged('nothing')
},
}
},
}
return self
}, [layoutP, relevantValuesD])
const [refRangeDrag, nodeRangeDrag] = useRefAndState<HTMLDivElement | null>(
null,
)
useDrag(nodeRangeDrag, {
debugName: 'HorizontalScrollbar/onRangeDrag',
onDragStart: handles.onRangeDragStart,
lockCSSCursorTo: 'ew-resize',
})
const [refRangeStartDrag, nodeRangeStartDrag] =
useRefAndState<HTMLDivElement | null>(null)
useDrag(nodeRangeStartDrag, {
debugName: 'HorizontalScrollbar/onRangeStartDrag',
onDragStart: handles.onRangeStartDragStart,
lockCSSCursorTo: 'w-resize',
})
const [refRangeEndDrag, nodeRangeEndDrag] =
useRefAndState<HTMLDivElement | null>(null)
useDrag(nodeRangeEndDrag, {
debugName: 'HorizontalScrollbar/onRangeEndDrag',
onDragStart: handles.onRangeEndDragStart,
lockCSSCursorTo: 'e-resize',
})
return (
<Container
style={{bottom: bottom + 8 + 'px'}}
{...includeLockFrameStampAttrs('hide')}
>
<TimeThread>
<DraggableArea
onDragStart={handles.onDragStart}
onDragEnd={handles.onDragEnd}
onDrag={handles.dragRange}
lockCursorTo="ew-resize"
>
<RangeBar
ref={refRangeDrag}
style={{
width: `${rangeEndX - rangeStartX}px`,
transform: `translate3d(${rangeStartX}px, 0, 0)`,
}}
/>
</DraggableArea>
<DraggableArea
onDragStart={handles.onDragStart}
onDrag={handles.dragRangeStart}
lockCursorTo="w-resize"
>
<RangeStartHandle
ref={refRangeStartDrag}
style={{transform: `translate3d(${rangeStartX}px, 0, 0)`}}
>
<Tooltip
active={beingDragged === 'both' || beingDragged === 'start'}
>
<Tooltip active={beingDragged === 'both' || beingDragged === 'start'}>
{unitPosToHumanReadablePos(clippedSpaceRange.start)}
</Tooltip>
</RangeStartHandle>
</DraggableArea>
<DraggableArea
onDragStart={handles.onDragStart}
onDrag={handles.dragRangeEnd}
lockCursorTo="e-resize"
>
<RangeEndHandle
ref={refRangeEndDrag}
style={{transform: `translate3d(${rangeEndX}px, 0, 0)`}}
>
<Tooltip active={beingDragged === 'both' || beingDragged === 'end'}>
{unitPosToHumanReadablePos(clippedSpaceRange.end)}
</Tooltip>
</RangeEndHandle>
</DraggableArea>
</TimeThread>
</Container>
)

View file

@ -1,176 +0,0 @@
import {voidFn} from '@theatre/shared/utils'
import React from 'react'
function createCursorLock(cursor: string) {
const el = document.createElement('div')
el.style.cssText = `
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999999;`
el.style.cursor = cursor
document.body.appendChild(el)
const relinquish = () => {
document.body.removeChild(el)
}
return relinquish
}
type IProps = {
children: React.ReactElement<HTMLElement | SVGElement>
onDragStart?: (
event: React.MouseEvent<HTMLElement | SVGElement>,
) => void | false
onDragEnd?: (dragHappened: boolean) => void
onDrag: (dx: number, dy: number, event: MouseEvent) => void
enabled?: boolean
shouldReturnMovement?: boolean
dontBlockMouseDown?: boolean
lockCursorTo?: string
}
type State = {
dragHappened: boolean
startPos: {
x: number
y: number
}
}
/**
* TODO low-hanging-fruit replace all DraggableArea instances with `react-use-gesture`
*/
class DraggableArea extends React.PureComponent<IProps, {}> {
s: State
mode: 'dragStartCalled' | 'dragging' | 'notDragging' = 'notDragging'
getDeltas: (e: MouseEvent) => [number, number]
relinquishCursorLock: () => void = voidFn
constructor(props: IProps) {
super(props)
this.s = {
dragHappened: false,
startPos: {
x: 0,
y: 0,
},
}
if (props.shouldReturnMovement) {
this.getDeltas = this.getMovements
} else {
this.getDeltas = this.getDistances
}
}
render() {
const shouldRegisterEvents = this.props.enabled !== false
const children = shouldRegisterEvents
? React.cloneElement(this.props.children, {
onMouseDown: this.dragStartHandler,
onClickCapture: this.disableUnwantedClick,
})
: this.props.children
return children
}
addDragListeners() {
document.addEventListener('mousemove', this.dragHandler)
document.addEventListener('mouseup', this.dragEndHandler)
}
removeDragListeners() {
document.removeEventListener('mousemove', this.dragHandler)
document.removeEventListener('mouseup', this.dragEndHandler)
}
disableUnwantedClick = (event: MouseEvent) => {
if (this.s.dragHappened) {
if (!this.props.dontBlockMouseDown && this.mode !== 'notDragging') {
event.stopPropagation()
event.preventDefault()
}
this.s.dragHappened = false
}
}
UNSAFE_componentWillReceiveProps(newProps: IProps) {
if (
newProps.lockCursorTo !== this.props.lockCursorTo &&
this.s.dragHappened
) {
this.relinquishCursorLock()
this.relinquishCursorLock = voidFn
if (newProps.lockCursorTo) {
this.relinquishCursorLock = createCursorLock(newProps.lockCursorTo)
}
}
}
dragStartHandler = (event: React.MouseEvent<HTMLElement>) => {
if (event.button !== 0) return
const resultOfStart =
this.props.onDragStart && this.props.onDragStart(event)
if (resultOfStart === false) return
if (!this.props.dontBlockMouseDown) {
event.stopPropagation()
event.preventDefault()
}
this.mode = 'dragStartCalled'
const {screenX, screenY} = event
this.s.startPos = {x: screenX, y: screenY}
this.s.dragHappened = false
this.addDragListeners()
}
dragEndHandler = () => {
this.removeDragListeners()
this.mode = 'notDragging'
this.props.onDragEnd && this.props.onDragEnd(this.s.dragHappened)
this.relinquishCursorLock()
this.relinquishCursorLock = voidFn
}
dragHandler = (event: MouseEvent) => {
if (!this.s.dragHappened && this.props.lockCursorTo) {
this.relinquishCursorLock = createCursorLock(this.props.lockCursorTo)
}
if (!this.s.dragHappened) this.s.dragHappened = true
this.mode = 'dragging'
const deltas = this.getDeltas(event)
this.props.onDrag(deltas[0], deltas[1], event)
}
getDistances(event: MouseEvent): [number, number] {
const {startPos} = this.s
return [event.screenX - startPos.x, event.screenY - startPos.y]
}
getMovements(event: MouseEvent): [number, number] {
return [event.movementX, event.movementY]
}
componentWillUnmount() {
if (this.mode !== 'notDragging') {
this.relinquishCursorLock()
this.relinquishCursorLock = voidFn
this.removeDragListeners()
this.props.onDragEnd && this.props.onDragEnd(this.mode === 'dragging')
this.mode = 'notDragging'
}
}
}
export default DraggableArea

View file

@ -39,10 +39,10 @@ const CursorOverride = styled.div`
`
type Context = {
getLock: (className: string, cursor: string) => () => void
getLock: (className: string, cursor?: string) => () => void
}
type Lock = {className: string; cursor: string}
type Lock = {className: string; cursor?: string}
const context = createContext<Context>({} as $IntentionalAny)
@ -51,7 +51,7 @@ const PointerEventsHandler: React.FC<{
}> = (props) => {
const [locks, setLocks] = useState<Lock[]>([])
const contextValue = useMemo<Context>(() => {
const getLock = (className: string, cursor: string) => {
const getLock = (className: string, cursor?: string) => {
const lock = {className, cursor}
setLocks((s) => [...s, lock])
const unlock = () => {
@ -104,7 +104,7 @@ export const useCssCursorLock = (
enabled: boolean,
className: string,
/** e.g. `"ew"`, `"help"`, `"pointer"`, `"text"`, etc */
cursor: string,
cursor?: string,
) => {
const ctx = useContext(context)
useLayoutEffect(() => {

View file

@ -2,10 +2,10 @@ import {clamp, isInteger, round} from 'lodash-es'
import type {MutableRefObject} from 'react'
import React, {useMemo, useRef} from 'react'
import styled from 'styled-components'
import DraggableArea from '@theatre/studio/uiComponents/DraggableArea'
import mergeRefs from 'react-merge-refs'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
import useOnClickOutside from '@theatre/studio/uiComponents/useOnClickOutside'
import useDrag from '@theatre/studio/uiComponents/useDrag'
const Container = styled.div`
height: 100%;
@ -71,9 +71,9 @@ const FillIndicator = styled.div`
}
`
function isFiniteFloat(s: string) {
return isFinite(parseFloat(s))
}
const DragWrap = styled.div`
display: contents;
`
type IState_NoFocus = {
mode: 'noFocus'
@ -87,8 +87,6 @@ type IState_EditingViaKeyboard = {
type IState_Dragging = {
mode: 'dragging'
valueBeforeDragging: number
currentDraggingValue: number
}
type IState = IState_NoFocus | IState_EditingViaKeyboard | IState_Dragging
@ -223,14 +221,32 @@ const BasicNumberInput: React.FC<{
stateRef.current = {
mode: 'dragging',
valueBeforeDragging: curValue,
currentDraggingValue: curValue,
}
let valueBeforeDragging = curValue
let valueDuringDragging = curValue
bodyCursorBeforeDrag.current = document.body.style.cursor
}
const onDragEnd = (happened: boolean) => {
return {
// note: we use mx because we need to constrain the `valueDuringDragging`
// and dx will keep accumulating past any constraints
onDrag(_dx: number, _dy: number, _e: MouseEvent, mx: number) {
const newValue =
valueDuringDragging +
propsA.nudge({
deltaX: mx,
deltaFraction: mx / inputWidth,
magnitude: 1,
})
valueDuringDragging = propsA.range
? clamp(newValue, propsA.range[0], propsA.range[1])
: newValue
propsRef.current.temporarilySetValue(valueDuringDragging)
},
onDragEnd(happened: boolean) {
if (!happened) {
propsRef.current.discardTemporaryValue()
stateRef.current = {mode: 'noFocus'}
@ -238,38 +254,15 @@ const BasicNumberInput: React.FC<{
inputRef.current!.focus()
inputRef.current!.setSelectionRange(0, 100)
} else {
const curState = stateRef.current as IState_Dragging
const value = curState.currentDraggingValue
if (curState.valueBeforeDragging === value) {
if (valueBeforeDragging === valueDuringDragging) {
propsRef.current.discardTemporaryValue()
} else {
propsRef.current.permanentlySetValue(value)
propsRef.current.permanentlySetValue(valueDuringDragging)
}
stateRef.current = {mode: 'noFocus'}
}
},
}
const onDrag = (deltaX: number, _dy: number) => {
const curState = stateRef.current as IState_Dragging
let newValue =
curState.valueBeforeDragging +
propsA.nudge({
deltaX,
deltaFraction: deltaX / inputWidth,
magnitude: 1,
})
if (propsA.range) {
newValue = clamp(newValue, propsA.range[0], propsA.range[1])
}
stateRef.current = {
...curState,
currentDraggingValue: newValue,
}
propsRef.current.temporarilySetValue(newValue)
}
return {
@ -279,8 +272,6 @@ const BasicNumberInput: React.FC<{
onInputKeyDown,
onClick,
onFocus,
onDragEnd,
onDrag,
}
}, [])
@ -329,18 +320,17 @@ const BasicNumberInput: React.FC<{
/>
) : null
const [refDrag, nodeDrag] = useRefAndState<HTMLDivElement | null>(null)
useDrag(nodeDrag, {
debugName: 'form/BasicNumberInput',
onDragStart: callbacks.transitionToDraggingMode,
lockCSSCursorTo: 'ew-resize',
disabled: stateRef.current.mode === 'editingViaKeyboard',
})
return (
<Container className={propsA.className + ' ' + stateRef.current.mode}>
<DraggableArea
key="draggableArea"
onDragStart={callbacks.transitionToDraggingMode}
onDragEnd={callbacks.onDragEnd}
onDrag={callbacks.onDrag}
enabled={stateRef.current.mode !== 'editingViaKeyboard'}
lockCursorTo="ew-resize"
>
{theInput}
</DraggableArea>
<DragWrap ref={refDrag}>{theInput}</DragWrap>
{fillIndicator}
</Container>
)

View file

@ -44,7 +44,7 @@ export type UseDragOpts = {
/**
* The css cursor property during the gesture will be locked to this value
*/
lockCursorTo?: string
lockCSSCursorTo?: string
/**
* Called at the start of the gesture. Mind you, that this would be called, even
* if the user is just clicking (and not dragging). However, if the gesture turns
@ -88,9 +88,9 @@ export default function useDrag(
>('notDragging')
useCssCursorLock(
mode === 'dragging' && typeof opts.lockCursorTo === 'string',
mode === 'dragging' && typeof opts.lockCSSCursorTo === 'string',
'dragging',
opts.lockCursorTo!,
opts.lockCSSCursorTo,
)
const {capturePointer} = usePointerCapturing(`useDrag for ${opts.debugName}`)