Stronger visual feedback for keyframe snapping
This commit is contained in:
parent
f4c2fb2a08
commit
816e67a814
7 changed files with 67 additions and 19 deletions
5
examples/dom-cra/src/setupTests.js
Normal file
5
examples/dom-cra/src/setupTests.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom'
|
|
@ -14,7 +14,7 @@ require('esbuild')
|
|||
{
|
||||
entryPoints: [path.join(playgroundDir, 'src/index.tsx')],
|
||||
target: ['firefox88'],
|
||||
loader: {'.png': 'file', '.glb': 'file'},
|
||||
loader: {'.png': 'file', '.glb': 'file', '.svg': 'dataurl'},
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
define: definedGlobals,
|
||||
|
|
|
@ -14,7 +14,7 @@ export function createBundles(watch: boolean) {
|
|||
const esbuildConfig: Parameters<typeof build>[0] = {
|
||||
entryPoints: [path.join(pathToPackage, 'src/index.ts')],
|
||||
target: ['es6'],
|
||||
loader: {'.png': 'file'},
|
||||
loader: {'.png': 'file', '.svg': 'dataurl'},
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
define: definedGlobals,
|
||||
|
|
|
@ -15,12 +15,14 @@ import type KeyframeEditor from './KeyframeEditor'
|
|||
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
|
||||
import SnapCursor from './SnapCursor.svg'
|
||||
|
||||
export const dotSize = 6
|
||||
const hitZoneSize = 12
|
||||
const snapCursorSize = 34
|
||||
|
||||
const dims = (size: number) => `
|
||||
left: ${-size / 2 + 1}px;
|
||||
left: ${-size / 2}px;
|
||||
top: ${-size / 2}px;
|
||||
width: ${size}px;
|
||||
height: ${size}px;
|
||||
|
@ -54,6 +56,16 @@ const HitZone = styled.div`
|
|||
|
||||
#pointer-root.draggingPositionInSequenceEditor & {
|
||||
pointer-events: auto;
|
||||
&:hover:after {
|
||||
position: absolute;
|
||||
top: calc(50% - ${snapCursorSize / 2}px);
|
||||
left: calc(50% - ${snapCursorSize / 2}px);
|
||||
width: ${snapCursorSize}px;
|
||||
height: ${snapCursorSize}px;
|
||||
display: block;
|
||||
content: ' ';
|
||||
background: url(${SnapCursor}) no-repeat 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.beingDragged {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 7V1H7" stroke="#74FFDE" stroke-width="0.25" />
|
||||
<path d="M7 33H1L1 27" stroke="#74FFDE" stroke-width="0.25" />
|
||||
<path d="M33 27V33H27" stroke="#74FFDE" stroke-width="0.25" />
|
||||
<path d="M27 1L33 1V7" stroke="#74FFDE" stroke-width="0.25" />
|
||||
</svg>
|
After Width: | Height: | Size: 358 B |
|
@ -18,13 +18,23 @@ type FrameStampPositionLock = {
|
|||
set: (pointerPositonInUnitSpace: number) => void
|
||||
}
|
||||
|
||||
export enum FrameStampPositionType {
|
||||
hidden,
|
||||
locked,
|
||||
snapped,
|
||||
free,
|
||||
}
|
||||
|
||||
const context = createContext<{
|
||||
currentD: IDerivation<number>
|
||||
currentD: IDerivation<[pos: number, posType: FrameStampPositionType]>
|
||||
getLock(): FrameStampPositionLock
|
||||
}>(null as $IntentionalAny)
|
||||
|
||||
type LockItem = {
|
||||
position: number
|
||||
position: [
|
||||
pos: number,
|
||||
posType: FrameStampPositionType.locked | FrameStampPositionType.hidden,
|
||||
]
|
||||
id: number
|
||||
}
|
||||
|
||||
|
@ -57,7 +67,7 @@ const FrameStampPositionProvider: React.FC<{
|
|||
...list,
|
||||
{
|
||||
id,
|
||||
position: -1,
|
||||
position: [-1, FrameStampPositionType.hidden],
|
||||
},
|
||||
])
|
||||
|
||||
|
@ -79,7 +89,12 @@ const FrameStampPositionProvider: React.FC<{
|
|||
|
||||
newList.splice(index, 1, {
|
||||
id,
|
||||
position: posInUnitSpace,
|
||||
position: [
|
||||
posInUnitSpace,
|
||||
posInUnitSpace === -1
|
||||
? FrameStampPositionType.hidden
|
||||
: FrameStampPositionType.locked,
|
||||
],
|
||||
})
|
||||
|
||||
return newList
|
||||
|
@ -132,16 +147,17 @@ export const useLockFrameStampPosition = (shouldLock: boolean, val: number) => {
|
|||
*/
|
||||
export const attributeNameThatLocksFramestamp =
|
||||
'data-theatre-lock-framestamp-to'
|
||||
|
||||
const pointerPositionInUnitSpace = (
|
||||
layoutP: Pointer<SequenceEditorPanelLayout>,
|
||||
): IDerivation<number> => {
|
||||
): IDerivation<[pos: number, posType: FrameStampPositionType]> => {
|
||||
return prism(() => {
|
||||
const rightDims = val(layoutP.rightDims)
|
||||
const clippedSpaceToUnitSpace = val(layoutP.clippedSpace.toUnitSpace)
|
||||
const leftPadding = val(layoutP.scaledSpace.leftPadding)
|
||||
|
||||
const mousePos = val(mousePositionD)
|
||||
if (!mousePos) return -1
|
||||
if (!mousePos) return [-1, FrameStampPositionType.hidden]
|
||||
|
||||
for (const el of mousePos.composedPath()) {
|
||||
if (!(el instanceof HTMLElement || el instanceof SVGElement)) break
|
||||
|
@ -149,10 +165,11 @@ const pointerPositionInUnitSpace = (
|
|||
if (el.hasAttribute(attributeNameThatLocksFramestamp)) {
|
||||
const val = el.getAttribute(attributeNameThatLocksFramestamp)
|
||||
if (typeof val !== 'string') continue
|
||||
if (val === 'hide') return -1
|
||||
if (val === 'hide') return [-1, FrameStampPositionType.hidden]
|
||||
const double = parseFloat(val)
|
||||
|
||||
if (isFinite(double) && double >= 0) return double
|
||||
if (isFinite(double) && double >= 0)
|
||||
return [double, FrameStampPositionType.snapped]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,9 +184,9 @@ const pointerPositionInUnitSpace = (
|
|||
const posInRightDims = clientX - x
|
||||
const posInUnitSpace = clippedSpaceToUnitSpace(posInRightDims)
|
||||
|
||||
return posInUnitSpace
|
||||
return [posInUnitSpace, FrameStampPositionType.free]
|
||||
} else {
|
||||
return -1
|
||||
return [-1, FrameStampPositionType.hidden]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@ import styled from 'styled-components'
|
|||
import {stampsGridTheme} from '@theatre/studio/panels/SequenceEditorPanel/FrameGrid/StampsGrid'
|
||||
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
|
||||
import {topStripTheme} from './TopStrip'
|
||||
import {useFrameStampPositionD} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {
|
||||
FrameStampPositionType,
|
||||
useFrameStampPositionD,
|
||||
} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
|
||||
const Label = styled.div`
|
||||
position: absolute;
|
||||
|
@ -23,12 +26,12 @@ const Label = styled.div`
|
|||
z-index: ${() => zIndexes.currentFrameStamp};
|
||||
`
|
||||
|
||||
const Line = styled.div`
|
||||
const Line = styled.div<{posType: FrameStampPositionType}>`
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
left: -0px;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
width: 0.5px;
|
||||
background: rgba(100, 100, 100, 0.2);
|
||||
pointer-events: none;
|
||||
`
|
||||
|
@ -36,7 +39,7 @@ const Line = styled.div`
|
|||
const FrameStamp: React.FC<{
|
||||
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||
}> = React.memo(({layoutP}) => {
|
||||
const posInUnitSpace = useVal(useFrameStampPositionD())
|
||||
const [posInUnitSpace, posType] = useVal(useFrameStampPositionD())
|
||||
const unitSpaceToClippedSpace = useVal(layoutP.clippedSpace.fromUnitSpace)
|
||||
const {sequence, formatter, clippedSpaceWidth} = usePrism(() => {
|
||||
const sequence = val(layoutP.sheet).getSequence()
|
||||
|
@ -48,7 +51,11 @@ const FrameStamp: React.FC<{
|
|||
return <></>
|
||||
}
|
||||
|
||||
const snappedPosInUnitSpace = sequence.closestGridPosition(posInUnitSpace)
|
||||
const snappedPosInUnitSpace =
|
||||
posType === FrameStampPositionType.free
|
||||
? sequence.closestGridPosition(posInUnitSpace)
|
||||
: posInUnitSpace
|
||||
|
||||
const posInClippedSpace = unitSpaceToClippedSpace(snappedPosInUnitSpace)
|
||||
|
||||
const isVisible =
|
||||
|
@ -65,6 +72,7 @@ const FrameStamp: React.FC<{
|
|||
{formatter.formatForPlayhead(snappedPosInUnitSpace)}
|
||||
</Label>
|
||||
<Line
|
||||
posType={posType}
|
||||
style={{
|
||||
opacity: isVisible ? 1 : 0,
|
||||
transform: `translate3d(${posInClippedSpace}px, 0, 0)`,
|
||||
|
|
Loading…
Reference in a new issue