Improves handles for arrows
This commit is contained in:
parent
ff72493381
commit
72b6db12c4
13 changed files with 98 additions and 45 deletions
|
@ -5,7 +5,6 @@ import { useSelector } from 'state'
|
|||
import styled from 'styles'
|
||||
import { deepCompareArrays, getPage } from 'utils/utils'
|
||||
import * as vec from 'utils/vec'
|
||||
import { DotCircle } from '../misc'
|
||||
|
||||
export default function Handles() {
|
||||
const selectedIds = useSelector(
|
||||
|
@ -18,7 +17,9 @@ export default function Handles() {
|
|||
selectedIds.length === 1 && getPage(data).shapes[selectedIds[0]]
|
||||
)
|
||||
|
||||
const isSelecting = useSelector((s) => s.isIn('selecting.notPointing'))
|
||||
const isSelecting = useSelector((s) =>
|
||||
s.isInAny('notPointing', 'pinching', 'translatingHandles')
|
||||
)
|
||||
|
||||
if (!shape.handles || !isSelecting) return null
|
||||
|
||||
|
@ -49,29 +50,43 @@ function Handle({
|
|||
const events = useHandleEvents(id, rGroup)
|
||||
|
||||
return (
|
||||
<g
|
||||
<StyledGroup
|
||||
key={id}
|
||||
className="handles"
|
||||
ref={rGroup}
|
||||
{...events}
|
||||
cursor="pointer"
|
||||
pointerEvents="all"
|
||||
transform={`translate(${point})`}
|
||||
>
|
||||
<HandleCircleOuter r={12} />
|
||||
<DotCircle r={4} />
|
||||
</g>
|
||||
<use href="#handle" pointerEvents="none" />
|
||||
</StyledGroup>
|
||||
)
|
||||
}
|
||||
|
||||
const HandleCircleOuter = styled('circle', {
|
||||
fill: 'transparent',
|
||||
pointerEvents: 'all',
|
||||
cursor: 'pointer',
|
||||
const StyledGroup = styled('g', {
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'&:active': {
|
||||
cursor: 'none',
|
||||
},
|
||||
})
|
||||
|
||||
const HandleCircle = styled('circle', {
|
||||
zStrokeWidth: 2,
|
||||
stroke: '$text',
|
||||
fill: '$panel',
|
||||
pointerEvents: 'none',
|
||||
const HandleCircleOuter = styled('circle', {
|
||||
fill: 'transparent',
|
||||
stroke: 'none',
|
||||
opacity: 0.2,
|
||||
pointerEvents: 'all',
|
||||
cursor: 'pointer',
|
||||
transform: 'scale(var(--scale))',
|
||||
'&:hover': {
|
||||
fill: '$selected',
|
||||
'& > *': {
|
||||
stroke: '$selected',
|
||||
},
|
||||
},
|
||||
'&:active': {
|
||||
fill: '$selected',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -62,7 +62,7 @@ export default function Canvas() {
|
|||
<g ref={rGroup}>
|
||||
<BoundsBg />
|
||||
<Page />
|
||||
{/* <Selected /> */}
|
||||
<Selected />
|
||||
<Bounds />
|
||||
<Handles />
|
||||
<Brush />
|
||||
|
|
|
@ -2,6 +2,7 @@ import { getShapeUtils } from 'lib/shape-utils'
|
|||
import { memo } from 'react'
|
||||
import { useSelector } from 'state'
|
||||
import { deepCompareArrays, getCurrentCamera, getPage } from 'utils/utils'
|
||||
import { DotCircle, Handle } from './misc'
|
||||
|
||||
export default function Defs() {
|
||||
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
|
||||
|
@ -17,6 +18,8 @@ export default function Defs() {
|
|||
{currentPageShapeIds.map((id) => (
|
||||
<Def key={id} id={id} />
|
||||
))}
|
||||
<DotCircle id="dot" r={4} />
|
||||
<Handle id="handle" r={4} />
|
||||
<filter id="expand">
|
||||
<feMorphology operator="dilate" radius={2 / zoom} />
|
||||
</filter>
|
||||
|
|
|
@ -7,6 +7,13 @@ export const DotCircle = styled('circle', {
|
|||
strokeWidth: '2',
|
||||
})
|
||||
|
||||
export const Handle = styled('circle', {
|
||||
transform: 'scale(var(--scale))',
|
||||
fill: '$canvas',
|
||||
stroke: '$selected',
|
||||
strokeWidth: '2',
|
||||
})
|
||||
|
||||
export const ThinLine = styled('line', {
|
||||
zStrokeWidth: 1,
|
||||
})
|
||||
|
|
|
@ -12,7 +12,7 @@ export default function Selected() {
|
|||
return Array.from(data.selectedIds.values())
|
||||
}, deepCompareArrays)
|
||||
|
||||
const isSelecting = useSelector((s) => s.isIn('selecting'))
|
||||
const isSelecting = useSelector((s) => s.isInAny('notPointing', 'pinching'))
|
||||
|
||||
if (!isSelecting) return null
|
||||
|
||||
|
@ -44,7 +44,6 @@ export const ShapeOutline = memo(function ShapeOutline({ id }: { id: string }) {
|
|||
rotate(${shape.rotation * (180 / Math.PI)},
|
||||
${center})
|
||||
translate(${bounds.minX},${bounds.minY})
|
||||
rotate(${(bounds.rotation || 0) * (180 / Math.PI)}, 0, 0)
|
||||
`
|
||||
|
||||
return (
|
||||
|
|
|
@ -93,10 +93,11 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
},
|
||||
|
||||
render(shape) {
|
||||
const { id, bend, points, handles } = shape
|
||||
const { id, bend, handles } = shape
|
||||
const { start, end, bend: _bend } = handles
|
||||
|
||||
const arrowDist = vec.dist(start.point, end.point)
|
||||
|
||||
const showCircle = !vec.isEqual(
|
||||
_bend.point,
|
||||
vec.med(start.point, end.point)
|
||||
|
@ -145,8 +146,8 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
const length = Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2)
|
||||
const u = vec.uni(vec.vec(start.point, end.point))
|
||||
const v = vec.rot(vec.mul(vec.neg(u), length), endAngle)
|
||||
const b = vec.add(points[1], vec.rot(v, Math.PI / 6))
|
||||
const c = vec.add(points[1], vec.rot(v, -(Math.PI / 6)))
|
||||
const b = vec.add(end.point, vec.rot(v, Math.PI / 6))
|
||||
const c = vec.add(end.point, vec.rot(v, -(Math.PI / 6)))
|
||||
|
||||
return (
|
||||
<g id={id}>
|
||||
|
@ -159,7 +160,7 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
strokeDasharray="none"
|
||||
/>
|
||||
<polyline
|
||||
points={[b, points[1], c].join()}
|
||||
points={[b, end.point, c].join()}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
|
@ -170,6 +171,15 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
const { start, end } = shape.handles
|
||||
this.boundsCache.set(shape, getBoundsFromPoints([start.point, end.point]))
|
||||
}
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
},
|
||||
|
||||
getRotatedBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
this.boundsCache.set(shape, getBoundsFromPoints(shape.points))
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { DotShape, ShapeType } from 'types'
|
|||
import { registerShapeUtils } from './index'
|
||||
import { boundsContained } from 'utils/bounds'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
import { DotCircle } from 'components/canvas/misc'
|
||||
import { translateBounds } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
|
||||
|
@ -34,7 +33,7 @@ const dot = registerShapeUtils<DotShape>({
|
|||
},
|
||||
|
||||
render({ id }) {
|
||||
return <DotCircle id={id} cx={0} cy={0} r={3} />
|
||||
return <use href="#dot" />
|
||||
},
|
||||
|
||||
getBounds(shape) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { LineShape, ShapeType } from 'types'
|
|||
import { registerShapeUtils } from './index'
|
||||
import { boundsContained } from 'utils/bounds'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
import { DotCircle, ThinLine } from 'components/canvas/misc'
|
||||
import { ThinLine } from 'components/canvas/misc'
|
||||
import { translateBounds } from 'utils/utils'
|
||||
import styled from 'styles'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
|
@ -42,7 +42,7 @@ const line = registerShapeUtils<LineShape>({
|
|||
return (
|
||||
<g id={id}>
|
||||
<ThinLine x1={x1} y1={y1} x2={x2} y2={y2} />
|
||||
<DotCircle cx={0} cy={0} r={3} />
|
||||
<use href="dot" />
|
||||
</g>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@ import { RayShape, ShapeType } from 'types'
|
|||
import { registerShapeUtils } from './index'
|
||||
import { boundsContained } from 'utils/bounds'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
import { DotCircle, ThinLine } from 'components/canvas/misc'
|
||||
import { ThinLine } from 'components/canvas/misc'
|
||||
import { translateBounds } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
|
||||
|
@ -40,7 +40,7 @@ const ray = registerShapeUtils<RayShape>({
|
|||
return (
|
||||
<g id={id}>
|
||||
<ThinLine x1={0} y1={0} x2={x2} y2={y2} />
|
||||
<DotCircle cx={0} cy={0} r={3} />
|
||||
<use href="#dot" />
|
||||
</g>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { ArrowShape, Data } from 'types'
|
||||
import * as vec from 'utils/vec'
|
||||
import { getPage } from 'utils/utils'
|
||||
import { ArrowSnapshot } from 'state/sessions/arrow-session'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
|
@ -21,7 +22,9 @@ export default function arrowCommand(
|
|||
|
||||
const { initialShape, currentPageId } = after
|
||||
|
||||
getPage(data, currentPageId).shapes[initialShape.id] = initialShape
|
||||
const page = getPage(data, currentPageId)
|
||||
|
||||
page.shapes[initialShape.id] = initialShape
|
||||
|
||||
data.selectedIds.clear()
|
||||
data.selectedIds.add(initialShape.id)
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Data } from 'types'
|
|||
import { getPage } from 'utils/utils'
|
||||
import { HandleSnapshot } from 'state/sessions/handle-session'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import * as vec from 'utils/vec'
|
||||
|
||||
export default function handleCommand(
|
||||
data: Data,
|
||||
|
@ -16,13 +17,26 @@ export default function handleCommand(
|
|||
name: 'moved_handle',
|
||||
category: 'canvas',
|
||||
do(data, isInitial) {
|
||||
if (isInitial) return
|
||||
// if (isInitial) return
|
||||
|
||||
const { initialShape, currentPageId } = after
|
||||
|
||||
const shape = getPage(data, currentPageId).shapes[initialShape.id]
|
||||
const page = getPage(data, currentPageId)
|
||||
const shape = page.shapes[initialShape.id]
|
||||
|
||||
getShapeUtils(shape).onHandleChange(shape, initialShape.handles)
|
||||
|
||||
const bounds = getShapeUtils(shape).getBounds(shape)
|
||||
|
||||
const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
|
||||
|
||||
getShapeUtils(shape).translateTo(shape, vec.add(shape.point, offset))
|
||||
|
||||
const { start, end, bend } = page.shapes[initialShape.id].handles
|
||||
|
||||
start.point = vec.sub(start.point, offset)
|
||||
end.point = vec.sub(end.point, offset)
|
||||
bend.point = vec.sub(bend.point, offset)
|
||||
},
|
||||
undo(data) {
|
||||
const { initialShape, currentPageId } = before
|
||||
|
|
|
@ -330,22 +330,22 @@ const state = createState({
|
|||
CANCELLED: { do: 'cancelSession', to: 'selecting' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pinching: {
|
||||
on: {
|
||||
PINCHED: { do: 'pinchCamera' },
|
||||
},
|
||||
initial: 'selectPinching',
|
||||
states: {
|
||||
selectPinching: {
|
||||
pinching: {
|
||||
on: {
|
||||
STOPPED_PINCHING: { to: 'selecting' },
|
||||
PINCHED: { do: 'pinchCamera' },
|
||||
},
|
||||
},
|
||||
toolPinching: {
|
||||
on: {
|
||||
STOPPED_PINCHING: { to: 'usingTool.previous' },
|
||||
initial: 'selectPinching',
|
||||
states: {
|
||||
selectPinching: {
|
||||
on: {
|
||||
STOPPED_PINCHING: { to: 'selecting' },
|
||||
},
|
||||
},
|
||||
toolPinching: {
|
||||
on: {
|
||||
STOPPED_PINCHING: { to: 'usingTool.previous' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
3
todo.md
3
todo.md
|
@ -8,3 +8,6 @@
|
|||
- Allow single-selected groups to transform their children correctly
|
||||
- (merge transform-session and transform-single-session)
|
||||
- fix drift when moving children of rotated group
|
||||
- shift dragging arrow handles should lock to directions
|
||||
- arrow rotation with handles
|
||||
- fix ellipse when scaleX < 0 or scaleY < 0
|
||||
|
|
Loading…
Reference in a new issue