Improves mobile touch events

This commit is contained in:
Steve Ruiz 2021-06-18 16:19:10 +01:00
parent 1b9e8857e0
commit 6ff7b0c50d
6 changed files with 81 additions and 44 deletions

View file

@ -2,13 +2,15 @@ import React, { useRef, memo, useEffect } from 'react'
import state, { useSelector } from 'state' import state, { useSelector } from 'state'
import styled from 'styles' import styled from 'styles'
import { getShapeUtils } from 'lib/shape-utils' import { getShapeUtils } from 'lib/shape-utils'
import { getBoundsCenter, getPage } from 'utils/utils' import { getBoundsCenter, getPage, isMobile } from 'utils/utils'
import { ShapeStyles, ShapeType } from 'types' import { ShapeStyles, ShapeType } from 'types'
import useShapeEvents from 'hooks/useShapeEvents' import useShapeEvents from 'hooks/useShapeEvents'
import vec from 'utils/vec' import vec from 'utils/vec'
import { getShapeStyle } from 'lib/shape-styles' import { getShapeStyle } from 'lib/shape-styles'
import ContextMenu from 'components/canvas/context-menu/context-menu' import ContextMenu from 'components/canvas/context-menu/context-menu'
const isMobileDevice = isMobile()
interface ShapeProps { interface ShapeProps {
id: string id: string
isSelecting: boolean isSelecting: boolean
@ -57,17 +59,20 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
` `
return ( return (
<StyledGroup ref={rGroup} transform={transform}> <StyledGroup
{isSelecting && ref={rGroup}
!isShy && transform={transform}
(isForeignObject ? ( device={isMobileDevice ? 'mobile' : 'desktop'}
>
{isSelecting && !isShy && (
<>
{isForeignObject ? (
<HoverIndicator <HoverIndicator
as="rect" as="rect"
width={bounds.width} width={bounds.width}
height={bounds.height} height={bounds.height}
strokeWidth={1.5} strokeWidth={1.5}
variant={'ghost'} variant={'ghost'}
onDoubleClick={() => console.log('aux')}
{...events} {...events}
/> />
) : ( ) : (
@ -78,7 +83,9 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
variant={getShapeUtils(shape).canStyleFill ? 'filled' : 'hollow'} variant={getShapeUtils(shape).canStyleFill ? 'filled' : 'hollow'}
{...events} {...events}
/> />
))} )}
</>
)}
{!shape.isHidden && {!shape.isHidden &&
(isForeignObject ? ( (isForeignObject ? (
@ -157,13 +164,11 @@ const StyledGroup = styled('g', {
[`& ${HoverIndicator}`]: { [`& ${HoverIndicator}`]: {
opacity: '0', opacity: '0',
}, },
[`&:hover ${HoverIndicator}`]: {
opacity: '0.16',
},
[`&:hover *[data-shy="true"]`]: {
opacity: '1',
},
variants: { variants: {
device: {
mobile: {},
desktop: {},
},
isSelected: { isSelected: {
true: { true: {
[`& *[data-shy="true"]`]: { [`& *[data-shy="true"]`]: {
@ -172,12 +177,6 @@ const StyledGroup = styled('g', {
[`& ${HoverIndicator}`]: { [`& ${HoverIndicator}`]: {
opacity: '0.2', opacity: '0.2',
}, },
[`&:hover ${HoverIndicator}`]: {
opacity: '0.3',
},
[`&:active ${HoverIndicator}`]: {
opacity: '0.3',
},
}, },
false: { false: {
[`& ${HoverIndicator}`]: { [`& ${HoverIndicator}`]: {
@ -186,6 +185,32 @@ const StyledGroup = styled('g', {
}, },
}, },
}, },
compoundVariants: [
{
device: 'desktop',
isSelected: 'false',
css: {
[`&:hover ${HoverIndicator}`]: {
opacity: '0.16',
},
[`&:hover *[data-shy="true"]`]: {
opacity: '1',
},
},
},
{
device: 'desktop',
isSelected: 'true',
css: {
[`&:hover ${HoverIndicator}`]: {
opacity: '0.3',
},
[`&:active ${HoverIndicator}`]: {
opacity: '0.3',
},
},
},
],
}) })
function Label({ children }: { children: React.ReactNode }) { function Label({ children }: { children: React.ReactNode }) {

View file

@ -42,6 +42,7 @@ export default function useShapeEvents(
const handlePointerEnter = useCallback( const handlePointerEnter = useCallback(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return if (!inputs.canAccept(e.pointerId)) return
if (isParent) { if (isParent) {
state.send('HOVERED_GROUP', inputs.pointerEnter(e, id)) state.send('HOVERED_GROUP', inputs.pointerEnter(e, id))
} else { } else {
@ -67,6 +68,7 @@ export default function useShapeEvents(
const handlePointerLeave = useCallback( const handlePointerLeave = useCallback(
(e: React.PointerEvent) => { (e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return if (!inputs.canAccept(e.pointerId)) return
if (isParent) { if (isParent) {
state.send('UNHOVERED_GROUP', { target: id }) state.send('UNHOVERED_GROUP', { target: id })
} else { } else {

View file

@ -1,14 +1,9 @@
import React, { useEffect, useRef } from 'react' import { useRef } from 'react'
import state from 'state' import state from 'state'
import inputs from 'state/inputs' import inputs from 'state/inputs'
import vec from 'utils/vec' import vec from 'utils/vec'
import { useGesture } from 'react-use-gesture' import { useGesture } from 'react-use-gesture'
import { import { fastPanUpdate, fastPinchCamera, fastZoomUpdate } from 'state/hacks'
fastBrushSelect,
fastPanUpdate,
fastPinchCamera,
fastZoomUpdate,
} from 'state/hacks'
/** /**
* Capture zoom gestures (pinches, wheels and pans) and send to the state. * Capture zoom gestures (pinches, wheels and pans) and send to the state.

View file

@ -75,7 +75,7 @@ export function fastPinchCamera(
camera.point = vec.sub(camera.point, vec.div(delta, camera.zoom)) camera.point = vec.sub(camera.point, vec.div(delta, camera.zoom))
const next = camera.zoom - (distanceDelta / 300) * camera.zoom const next = camera.zoom - (distanceDelta / 350) * camera.zoom
const p0 = screenToWorld(point, data) const p0 = screenToWorld(point, data)
camera.zoom = getCameraZoom(next) camera.zoom = getCameraZoom(next)

View file

@ -146,6 +146,7 @@ class Inputs {
} }
delete this.points[e.pointerId] delete this.points[e.pointerId]
delete this.activePointerId delete this.activePointerId
if (vec.dist(info.origin, info.point) < 8) { if (vec.dist(info.origin, info.point) < 8) {
@ -162,6 +163,7 @@ class Inputs {
} }
canAccept = (pointerId: PointerEvent['pointerId']) => { canAccept = (pointerId: PointerEvent['pointerId']) => {
// return true
return ( return (
this.activePointerId === undefined || this.activePointerId === pointerId this.activePointerId === undefined || this.activePointerId === pointerId
) )
@ -177,6 +179,12 @@ class Inputs {
vec.dist(origin, point) < 8 vec.dist(origin, point) < 8
) )
} }
clear() {
this.activePointerId = undefined
this.pointer = undefined
this.points = {}
}
} }
export default new Inputs() export default new Inputs()

View file

@ -176,7 +176,7 @@ const state = createState({
initial: 'selecting', initial: 'selecting',
states: { states: {
selecting: { selecting: {
onEnter: 'setActiveToolSelect', onEnter: ['setActiveToolSelect', 'clearInputs'],
on: { on: {
UNDO: 'undo', UNDO: 'undo',
REDO: 'redo', REDO: 'redo',
@ -391,6 +391,7 @@ const state = createState({
onEnter: 'startTranslateSession', onEnter: 'startTranslateSession',
onExit: 'completeSession', onExit: 'completeSession',
on: { on: {
STARTED_PINCHING: { to: 'pinching' },
MOVED_POINTER: 'updateTranslateSession', MOVED_POINTER: 'updateTranslateSession',
PANNED_CAMERA: 'updateTranslateSession', PANNED_CAMERA: 'updateTranslateSession',
PRESSED_SHIFT_KEY: 'keyUpdateTranslateSession', PRESSED_SHIFT_KEY: 'keyUpdateTranslateSession',
@ -1248,6 +1249,10 @@ const state = createState({
/* -------------------- Selection ------------------- */ /* -------------------- Selection ------------------- */
clearInputs() {
inputs.clear()
},
selectAll(data) { selectAll(data) {
const selectedIds = getSelectedIds(data) const selectedIds = getSelectedIds(data)
const page = getPage(data) const page = getPage(data)
@ -1512,6 +1517,8 @@ const state = createState({
point: number[] point: number[]
} }
) { ) {
// This is usually replaced with hacks.fastPinchCamera!
const camera = getCurrentCamera(data) const camera = getCurrentCamera(data)
camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom)) camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom))