Stronger visual feedback for keyframe snapping

This commit is contained in:
Aria Minaei 2021-08-07 11:17:30 +02:00
parent f4c2fb2a08
commit 816e67a814
7 changed files with 67 additions and 19 deletions

View 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'

View file

@ -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,

View file

@ -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,

View file

@ -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 {

View file

@ -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

View file

@ -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]
}
})
}

View file

@ -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)`,