diff --git a/package.json b/package.json index a1f79d0e39..1c5ce85a87 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "theatre-monorepo", "license": "Apache-2.0", - "version": "0.7.0", + "version": "0.7.1", "workspaces": [ "packages/*", "examples/*", diff --git a/packages/browser-bundles/package.json b/packages/browser-bundles/package.json index 37c2364bed..7afeeb4974 100644 --- a/packages/browser-bundles/package.json +++ b/packages/browser-bundles/package.json @@ -1,6 +1,6 @@ { "name": "@theatre/browser-bundles", - "version": "0.7.0", + "version": "0.7.1", "license": "SEE LICENSE IN LICENSE", "author": { "name": "Aria Minaei", diff --git a/packages/dataverse/package.json b/packages/dataverse/package.json index ed45458254..43765cde39 100644 --- a/packages/dataverse/package.json +++ b/packages/dataverse/package.json @@ -1,6 +1,6 @@ { "name": "@theatre/dataverse", - "version": "0.7.0", + "version": "0.7.1", "license": "Apache-2.0", "author": { "name": "Aria Minaei", diff --git a/packages/r3f/package.json b/packages/r3f/package.json index 97113d15bf..46933190f3 100644 --- a/packages/r3f/package.json +++ b/packages/r3f/package.json @@ -1,6 +1,6 @@ { "name": "@theatre/r3f", - "version": "0.7.0", + "version": "0.7.1", "license": "Apache-2.0", "authors": [ { diff --git a/packages/react/package.json b/packages/react/package.json index d70c8a95c0..f0c33a0cf6 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@theatre/react", - "version": "0.7.0", + "version": "0.7.1", "license": "Apache-2.0", "author": { "name": "Aria Minaei", diff --git a/packages/theatric/package.json b/packages/theatric/package.json index 7ff1bb8b90..3bc45fb2ba 100644 --- a/packages/theatric/package.json +++ b/packages/theatric/package.json @@ -1,6 +1,6 @@ { "name": "theatric", - "version": "0.7.0", + "version": "0.7.1", "license": "Apache-2.0", "author": { "name": "Andrew Prifer", diff --git a/theatre/core/package.json b/theatre/core/package.json index 3c3c8d9fc8..5af6c2c6c5 100644 --- a/theatre/core/package.json +++ b/theatre/core/package.json @@ -1,6 +1,6 @@ { "name": "@theatre/core", - "version": "0.7.0", + "version": "0.7.1", "license": "Apache-2.0", "description": "Motion design editor for the web", "repository": { diff --git a/theatre/core/src/sequences/Sequence.ts b/theatre/core/src/sequences/Sequence.ts index a804e99064..5bb1206d3d 100644 --- a/theatre/core/src/sequences/Sequence.ts +++ b/theatre/core/src/sequences/Sequence.ts @@ -44,6 +44,9 @@ const possibleDirections = [ ] export default class Sequence implements PointerToPrismProvider { + get type(): 'Theatre_Sequence' { + return 'Theatre_Sequence' + } public readonly address: SequenceAddress publicApi: TheatreSequence @@ -98,6 +101,7 @@ export default class Sequence implements PointerToPrismProvider { length: val(this.pointer.length), playing: val(this.pointer.playing), position: val(this.pointer.position), + subUnitsPerUnit: val(this.pointer.subUnitsPerUnit), })) as $IntentionalAny as Prism } if (path.length > 1) { @@ -106,6 +110,8 @@ export default class Sequence implements PointerToPrismProvider { const [prop] = path if (prop === 'length') { return this._lengthD as $IntentionalAny as Prism + } else if (prop === 'subUnitsPerUnit') { + return this._subUnitsPerUnitD as $IntentionalAny as Prism } else if (prop === 'position') { return this._positionD as $IntentionalAny as Prism } else if (prop === 'playing') { diff --git a/theatre/core/src/sequences/TheatreSequence.ts b/theatre/core/src/sequences/TheatreSequence.ts index 9c5d6306aa..7e3cfd74cc 100644 --- a/theatre/core/src/sequences/TheatreSequence.ts +++ b/theatre/core/src/sequences/TheatreSequence.ts @@ -132,6 +132,7 @@ export interface ISequence { playing: boolean length: number position: number + subUnitsPerUnit: number }> /** diff --git a/theatre/core/src/sheets/TheatreSheet.ts b/theatre/core/src/sheets/TheatreSheet.ts index 09ac3836b9..258fd5de34 100644 --- a/theatre/core/src/sheets/TheatreSheet.ts +++ b/theatre/core/src/sheets/TheatreSheet.ts @@ -102,6 +102,10 @@ export interface ISheet { }, ): ISheetObject + __experimental_getExistingObject( + key: string, + ): ISheetObject | undefined + /** * Detaches a previously created child object from the sheet. * @@ -204,6 +208,20 @@ export default class TheatreSheet implements ISheet { } } + __experimental_getExistingObject( + key: string, + ): ISheetObject | undefined { + const internal = privateAPI(this) + const sanitizedPath = validateAndSanitiseSlashedPathOrThrow( + key, + `sheet.object`, + ) + + const existingObject = internal.getObject(sanitizedPath as ObjectAddressKey) + + return existingObject?.publicApi as $IntentionalAny + } + get sequence(): TheatreSequence { return privateAPI(this).getSequence().publicApi } diff --git a/theatre/package.json b/theatre/package.json index 15c6a3cb21..a46e36d2bf 100644 --- a/theatre/package.json +++ b/theatre/package.json @@ -1,7 +1,7 @@ { "name": "theatre", "private": true, - "version": "0.7.0", + "version": "0.7.1", "workspaces": [ "./shared", "./core", diff --git a/theatre/shared/src/instanceTypes.ts b/theatre/shared/src/instanceTypes.ts index 8861ec5620..162edabb4d 100644 --- a/theatre/shared/src/instanceTypes.ts +++ b/theatre/shared/src/instanceTypes.ts @@ -5,6 +5,7 @@ import type SheetObjectTemplate from '@theatre/core/sheetObjects/SheetObjectTemp import type Sheet from '@theatre/core/sheets/Sheet' import type SheetTemplate from '@theatre/core/sheets/SheetTemplate' import type {$IntentionalAny} from './utils/types' +import type Sequence from '@theatre/core/sequences/Sequence' /** * Since \@theatre/core and \@theatre/studio are separate bundles, @@ -22,6 +23,8 @@ export const isSheetTemplate = typeAsserter( export const isSheetObject = typeAsserter('Theatre_SheetObject') +export const isSequence = typeAsserter('Theatre_Sequence') + export const isSheetObjectTemplate = typeAsserter( 'Theatre_SheetObjectTemplate', ) diff --git a/theatre/studio/package.json b/theatre/studio/package.json index 2ac6e96870..95e815c29e 100644 --- a/theatre/studio/package.json +++ b/theatre/studio/package.json @@ -1,6 +1,6 @@ { "name": "@theatre/studio", - "version": "0.7.0", + "version": "0.7.1", "license": "AGPL-3.0-only", "description": "Motion design editor for the web", "repository": { diff --git a/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts b/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts index 4126d133ea..b749801a8e 100644 --- a/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts +++ b/theatre/studio/src/StudioStore/createTransactionPrivateApi.ts @@ -1,7 +1,8 @@ import type {Pointer} from '@theatre/dataverse' -import {isSheetObject} from '@theatre/shared/instanceTypes' +import {isSequence, isSheetObject} from '@theatre/shared/instanceTypes' import type {$FixMe, $IntentionalAny} from '@theatre/shared/utils/types' import get from 'lodash-es/get' +import isInteger from 'lodash-es/isInteger' import type {ITransactionPrivateApi} from './StudioStore' import forEachPropDeep from '@theatre/shared/utils/forEachDeep' import getDeep from '@theatre/shared/utils/getDeep' @@ -186,9 +187,36 @@ export default function createTransactionPrivateApi( } else { setStaticOrKeyframeProp(_value, propConfig, path) } + } else if (isSequence(root)) { + const [prop] = path + if (prop === 'subUnitsPerUnit') { + if (typeof _value !== 'number' || !isInteger(_value) || _value < 1) { + throw new Error( + `Value ${_value} is not an integer, which is required for setting sequence prop ${prop}`, + ) + } + stateEditors.coreByProject.historic.sheetsById.sequence.setSubUnitsPerUnit( + { + ...root.address, + subUnitsPerUnit: _value, + }, + ) + } else if (prop === 'length') { + if (typeof _value !== 'number' || _value <= 0.001) { + throw new Error( + `Value ${_value} is not a positive number, which is required for setting sequence prop ${prop}`, + ) + } + stateEditors.coreByProject.historic.sheetsById.sequence.setLength({ + ...root.address, + length: _value, + }) + } else { + throw new Error(`Setting sequence prop ${prop} is not supported`) + } } else { throw new Error( - 'Only setting props of SheetObject-s is supported in a transaction so far', + 'Only setting props of SheetObject-s and sequences is supported in a transaction so far', ) } }, diff --git a/theatre/studio/src/store/stateEditors.ts b/theatre/studio/src/store/stateEditors.ts index 6fa9414b2d..6b8c1089f0 100644 --- a/theatre/studio/src/store/stateEditors.ts +++ b/theatre/studio/src/store/stateEditors.ts @@ -671,6 +671,12 @@ namespace stateEditors { ) } + export function setSubUnitsPerUnit( + p: WithoutSheetInstance & {subUnitsPerUnit: number}, + ) { + _ensure(p).subUnitsPerUnit = clamp(p.subUnitsPerUnit, 1, 2 ** 12) + } + function _ensureTracksOfObject( p: WithoutSheetInstance, ) { diff --git a/theatre/studio/src/uiComponents/useDrag.ts b/theatre/studio/src/uiComponents/useDrag.ts index ad7ad535c5..9a617fdf84 100644 --- a/theatre/studio/src/uiComponents/useDrag.ts +++ b/theatre/studio/src/uiComponents/useDrag.ts @@ -29,14 +29,14 @@ export enum MouseButton { type OnDragCallback = ( totalDragDeltaX: number, totalDragDeltaY: number, - event: MouseEvent, + event: PointerEvent, dxFromLastEvent: number, dyFromLastEvent: number, ) => void -type OnClickCallback = (mouseUpEvent: MouseEvent) => void +type OnClickCallback = (mouseUpEvent: PointerEvent) => void -type OnDragEndCallback = (dragHappened: boolean, event?: MouseEvent) => void +type OnDragEndCallback = (dragHappened: boolean, event?: PointerEvent) => void export type UseDragOpts = { /** @@ -80,7 +80,7 @@ export type UseDragOpts = { * onDragStart can be undefined, in which case, we always handle useDrag, * but when defined, we can allow the handler to return false to indicate ignore this dragging */ - onDragStart: (event: MouseEvent) => + onDragStart: (event: PointerEvent ) => | false | { /** @@ -162,7 +162,8 @@ export default function useDrag( * Safari has a gross behavior with locking the pointer changes the height of the webpage * See {@link UseDragOpts.shouldPointerLock} for more context. */ - const isPointerLockUsed = opts.shouldPointerLock && !isSafari + // const isPointerLockUsed = opts.shouldPointerLock && !isSafari + const isPointerLockUsed = false const stateRef = useRef({ domDragStarted: false, @@ -181,6 +182,7 @@ export default function useDrag( // via a ref (e.g. via the below layout effect). const [isDraggingRef, isDragging] = useRefAndState(false) useLayoutEffect(() => { + // target?.style.setProperty('touch-action', 'none') if (!target) return const ensureIsDraggingUpToDateForReactLifecycle = () => { const isDragging = @@ -190,14 +192,19 @@ export default function useDrag( } } - const dragHandler = (event: MouseEvent) => { + const dragHandler = (event: PointerEvent) => { + console.log('dragHandler start, probably because of pointerMove') + // console.log('TOUCHES', event_, event_?.touches, event_?.changedTouches) if (!stateRef.current.domDragStarted) return + console.log(' stateRef.current.domDragStarted') const stateStarted = stateRef.current if (didPointerLockCauseMovement(event, stateStarted)) return + console.log(' NOT didPointerLockCauseMovement(event, stateStarted)') if (!stateStarted.detection.detected) { + console.log(' !stateStarted.detection.detected') stateStarted.detection.totalDistanceMoved += Math.abs(event.movementY) + Math.abs(event.movementX) @@ -218,21 +225,27 @@ export default function useDrag( } } + console.log(' before check stateStarted.detection.detected', stateStarted.detection) // drag detection threshold checking if (stateStarted.detection.detected) { + console.log(' stateStarted.detection.detected') stateStarted.detection.dragEventCount += 1 const {dragMovement} = stateStarted.detection if (isPointerLockUsed) { + console.log(' isPointerLockUsed') // when locked, the pointer event screen position is going to be 0s, since the pointer can't move. // So, we use the movement on the event dragMovement.x += event.movementX dragMovement.y += event.movementY + // TODO with mouse pointerdown WE ARE HERE in F1, for MOUSE & TOUCH } else { + console.log(' !isPointerLockUsed') const {startPos} = stateStarted dragMovement.x = event.screenX - startPos.x dragMovement.y = event.screenY - startPos.y } + console.log(' dragMovement after check isPointerLockUsed, before onDrag') callbacksRef.current.onDrag( dragMovement.x, dragMovement.y, @@ -240,10 +253,13 @@ export default function useDrag( event.movementX, event.movementY, ) + console.log(' callbacksRef.current.onDrag executed') } + console.log('dragHandler end') } - const dragEndHandler = (e: MouseEvent) => { + const dragEndHandler = (e: PointerEvent) => { + console.log('dragEndHandler start') removeDragListeners() if (!stateRef.current.domDragStarted) return const dragHappened = stateRef.current.detection.detected @@ -267,17 +283,27 @@ export default function useDrag( } const addDragListeners = () => { - document.addEventListener('mousemove', dragHandler) - document.addEventListener('mouseup', dragEndHandler) + // document.addEventListener('mousemove', dragHandler) + // document.addEventListener('mouseup', dragEndHandler) + // document.addEventListener('touchmove', dragHandler) + // document.addEventListener('touchend', dragEndHandler) + document.addEventListener('pointermove', dragHandler) + document.addEventListener('pointerup', dragEndHandler) } const removeDragListeners = () => { + console.log('removeDragListeners') capturedPointerRef.current?.release() - document.removeEventListener('mousemove', dragHandler) - document.removeEventListener('mouseup', dragEndHandler) + // document.removeEventListener('mousemove', dragHandler) + // document.removeEventListener('mouseup', dragEndHandler) + // document.removeEventListener('touchmove', dragHandler) + // document.removeEventListener('touchend', dragEndHandler) + document.removeEventListener('pointermove', dragHandler) + document.removeEventListener('pointerup', dragEndHandler) } - const preventUnwantedClick = (event: MouseEvent) => { + const preventUnwantedClick = (event: PointerEvent) => { + console.log('preventUnwantedClick'); if (optsRef.current.disabled) return if (!stateRef.current.domDragStarted) return if (stateRef.current.detection.detected) { @@ -293,7 +319,8 @@ export default function useDrag( } } - const dragStartHandler = (event: MouseEvent) => { + const dragStartHandler = (event: PointerEvent ) => { + console.log('dragStartHandler start', event); // defensively release capturedPointerRef.current?.release() @@ -301,27 +328,37 @@ export default function useDrag( if (opts.disabled === true) return const acceptedButtons: MouseButton[] = opts.buttons ?? [MouseButton.Left] + console.log('acceptedButtons', acceptedButtons) + // if ((!acceptedButtons.includes(event.button)) || event.type === 'touchstart') return if (!acceptedButtons.includes(event.button)) return + console.log('event is accepted') const returnOfOnDragStart = opts.onDragStart(event) if (returnOfOnDragStart === false) { + console.log('returnOfOnDragStart === false') // we should ignore the gesture return } + console.log(returnOfOnDragStart) callbacksRef.current.onDrag = returnOfOnDragStart.onDrag callbacksRef.current.onDragEnd = returnOfOnDragStart.onDragEnd ?? noop callbacksRef.current.onClick = returnOfOnDragStart.onClick ?? noop // need to capture pointer after we know the provided handler wants to handle drag start + console.log('---------- ', event.pointerType, event) // pointerType is either mouse or touch + // if (true || (event?.pointerType == 'mouse')) {capturedPointerRef.current = capturePointer('Drag start')} capturedPointerRef.current = capturePointer('Drag start') + console.log('opts.dontBlockMouseDown', opts.dontBlockMouseDown) if (!opts.dontBlockMouseDown) { event.stopPropagation() event.preventDefault() } + + console.log('startPos event.screenX, y: event.screenY', event.screenX, event.screenY) stateRef.current = { domDragStarted: true, @@ -333,19 +370,26 @@ export default function useDrag( } ensureIsDraggingUpToDateForReactLifecycle() + console.log('addDragListeners') addDragListeners() } - const onMouseDown = (e: MouseEvent) => { + const onMouseDown = (e: PointerEvent) => { + console.log('onMouseDown dragStartHandler') dragStartHandler(e) } - target.addEventListener('mousedown', onMouseDown as $FixMe) + // target.addEventListener('mousedown', onMouseDown as $FixMe) + // target.addEventListener('touchstart', onMouseDown as $FixMe) + target.addEventListener('pointerdown', onMouseDown as $FixMe) target.addEventListener('click', preventUnwantedClick as $FixMe) return () => { + console.log('dragstart uselayout beginning') removeDragListeners() - target.removeEventListener('mousedown', onMouseDown as $FixMe) + // target.removeEventListener('mousedown', onMouseDown as $FixMe) + // target.removeEventListener('touchstart', onMouseDown as $FixMe) + target.removeEventListener('pointerdown', onMouseDown as $FixMe) target.removeEventListener('click', preventUnwantedClick as $FixMe) if (stateRef.current.domDragStarted) { @@ -374,9 +418,10 @@ export default function useDrag( * @returns */ function didPointerLockCauseMovement( - event: MouseEvent, + event: PointerEvent, state: IUseDragState_Started, ) { + console.log('didPointerLockCauseMovement'); const isEarlyInDragging = !state.detection.detected || (state.detection.detected && state.detection.dragEventCount < 3)