Improves rotation? I think?

This commit is contained in:
Steve Ruiz 2021-05-29 23:27:19 +01:00
parent 2755b58f8f
commit bc6f5cf5b7
9 changed files with 222 additions and 42 deletions

View file

@ -14,9 +14,12 @@ import { shades, fills, strokes } from 'lib/colors'
import ColorPicker from './color-picker'
import AlignDistribute from './align-distribute'
import { ShapeStyles } from 'types'
import { MoveType, ShapeStyles } from 'types'
import WidthPicker from './width-picker'
import {
AlignTopIcon,
ArrowDownIcon,
ArrowUpIcon,
AspectRatioIcon,
BoxIcon,
CopyIcon,
@ -24,6 +27,8 @@ import {
EyeOpenIcon,
LockClosedIcon,
LockOpen1Icon,
PinBottomIcon,
PinTopIcon,
RotateCounterClockwiseIcon,
TrashIcon,
} from '@radix-ui/react-icons'
@ -128,10 +133,6 @@ function SelectedShapeStyles({}: {}) {
<label htmlFor="width">Width</label>
<WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
</Row>
<AlignDistribute
hasTwoOrMore={selectedIds.length > 1}
hasThreeOrMore={selectedIds.length > 2}
/>
<ButtonsRow>
<IconButton
disabled={!hasSelection}
@ -163,6 +164,32 @@ function SelectedShapeStyles({}: {}) {
>
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
</IconButton>
</ButtonsRow>
<ButtonsRow>
<IconButton
disabled={!hasSelection}
onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
>
<PinBottomIcon />
</IconButton>
<IconButton
disabled={!hasSelection}
onClick={() => state.send('MOVED', { type: MoveType.Backward })}
>
<ArrowDownIcon />
</IconButton>
<IconButton
disabled={!hasSelection}
onClick={() => state.send('MOVED', { type: MoveType.Forward })}
>
<ArrowUpIcon />
</IconButton>
<IconButton
disabled={!hasSelection}
onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
>
<PinTopIcon />
</IconButton>
<IconButton
disabled={!hasSelection}
onClick={() => state.send('DELETED')}
@ -170,6 +197,10 @@ function SelectedShapeStyles({}: {}) {
<TrashIcon />
</IconButton>
</ButtonsRow>
<AlignDistribute
hasTwoOrMore={selectedIds.length > 1}
hasThreeOrMore={selectedIds.length > 2}
/>
</Content>
</Panel.Layout>
)

View file

@ -6,6 +6,7 @@ import { intersectPolylineBounds } from 'utils/intersections'
import { boundsContainPolygon } from 'utils/bounds'
import getStroke from 'perfect-freehand'
import {
getBoundsCenter,
getBoundsFromPoints,
getRotatedCorners,
getSvgPathFromStroke,
@ -77,9 +78,20 @@ const draw = registerShapeUtils<DrawShape>({
},
getRotatedBounds(shape) {
return getBoundsFromPoints(
getRotatedCorners(this.getBounds(shape), shape.rotation)
const bounds =
this.boundsCache.get(shape) || getBoundsFromPoints(shape.points)
const center = getBoundsCenter(bounds)
const rotatedPts = shape.points.map((pt) =>
vec.rotWith(pt, center, shape.rotation)
)
const rotatedBounds = translateBounds(
getBoundsFromPoints(rotatedPts),
shape.point
)
return rotatedBounds
},
getCenter(shape) {

View file

@ -14,6 +14,7 @@ import transformSingle from './transform-single'
import translate from './translate'
import nudge from './nudge'
import toggle from './toggle'
import rotateCcw from './rotate-ccw'
const commands = {
align,
@ -32,6 +33,7 @@ const commands = {
translate,
nudge,
toggle,
rotateCcw,
}
export default commands

View file

@ -0,0 +1,91 @@
import Command from './command'
import history from '../history'
import { Data } from 'types'
import {
getBoundsCenter,
getCommonBounds,
getPage,
getSelectedShapes,
} from 'utils/utils'
import * as vec from 'utils/vec'
import { getShapeUtils } from 'lib/shape-utils'
const PI2 = Math.PI * 2
export default function rotateCcwCommand(data: Data) {
const { currentPageId, boundsRotation } = data
const page = getPage(data)
const initialShapes = Object.fromEntries(
getSelectedShapes(data).map((shape) => {
const bounds = getShapeUtils(shape).getBounds(shape)
return [
shape.id,
{
rotation: shape.rotation,
point: [...shape.point],
center: getBoundsCenter(bounds),
bounds,
},
]
})
)
const commonBoundsCenter = getBoundsCenter(
getCommonBounds(...Object.values(initialShapes).map((b) => b.bounds))
)
const nextShapes = Object.fromEntries(
Object.entries(initialShapes).map(([id, { point, center }]) => {
const shape = { ...page.shapes[id] }
const offset = vec.sub(center, point)
const nextPoint = vec.sub(
vec.rotWith(center, commonBoundsCenter, -(PI2 / 4)),
offset
)
const rot = (PI2 + (shape.rotation - PI2 / 4)) % PI2
getShapeUtils(shape).rotateTo(shape, rot).translateTo(shape, nextPoint)
return [id, shape]
})
)
const nextboundsRotation = (PI2 + (data.boundsRotation - PI2 / 4)) % PI2
history.execute(
data,
new Command({
name: 'translate_shapes',
category: 'canvas',
do(data) {
const { shapes } = getPage(data, currentPageId)
for (let id in nextShapes) {
const shape = shapes[id]
getShapeUtils(shape)
.rotateTo(shape, nextShapes[id].rotation)
.translateTo(shape, nextShapes[id].point)
}
data.boundsRotation = nextboundsRotation
},
undo(data) {
const { shapes } = getPage(data, currentPageId)
for (let id in initialShapes) {
const { point, rotation } = initialShapes[id]
const shape = shapes[id]
const utils = getShapeUtils(shape)
utils.rotateTo(shape, rotation).translateTo(shape, point)
}
data.boundsRotation = boundsRotation
},
})
)
}

View file

@ -1,9 +1,9 @@
import Command from "./command"
import history from "../history"
import { Data } from "types"
import { RotateSnapshot } from "state/sessions/rotate-session"
import { getPage } from "utils/utils"
import { getShapeUtils } from "lib/shape-utils"
import Command from './command'
import history from '../history'
import { Data } from 'types'
import { RotateSnapshot } from 'state/sessions/rotate-session'
import { getPage } from 'utils/utils'
import { getShapeUtils } from 'lib/shape-utils'
export default function rotateCommand(
data: Data,
@ -13,12 +13,12 @@ export default function rotateCommand(
history.execute(
data,
new Command({
name: "translate_shapes",
category: "canvas",
name: 'translate_shapes',
category: 'canvas',
do(data) {
const { shapes } = getPage(data)
for (let { id, point, rotation } of after.shapes) {
for (let { id, point, rotation } of after.initialShapes) {
const shape = shapes[id]
const utils = getShapeUtils(shape)
utils.rotateTo(shape, rotation).translateTo(shape, point)
@ -29,7 +29,7 @@ export default function rotateCommand(
undo(data) {
const { shapes } = getPage(data, before.currentPageId)
for (let { id, point, rotation } of before.shapes) {
for (let { id, point, rotation } of before.initialShapes) {
const shape = shapes[id]
const utils = getShapeUtils(shape)
utils.rotateTo(shape, rotation).translateTo(shape, point)

View file

@ -33,6 +33,7 @@ export default class BrushSession extends BaseSession {
const page = getPage(data)
const shape = page.shapes[snapshot.id] as DrawShape
getShapeUtils(shape).setProperty(shape, 'points', [...this.points])
}
@ -44,6 +45,30 @@ export default class BrushSession extends BaseSession {
}
complete = (data: Data) => {
if (this.points.length > 1) {
let minX = Infinity
let minY = Infinity
const pts = [...this.points]
for (let pt of pts) {
minX = Math.min(pt[0], minX)
minY = Math.min(pt[1], minY)
}
for (let pt of pts) {
pt[0] -= minX
pt[1] -= minY
}
const { snapshot } = this
const page = getPage(data)
const shape = page.shapes[snapshot.id] as DrawShape
getShapeUtils(shape)
.setProperty(shape, 'points', pts)
.translateTo(shape, vec.add(shape.point, [minX, minY]))
}
commands.draw(data, this.snapshot.id, this.points)
}
}

View file

@ -9,6 +9,7 @@ import {
getCommonBounds,
getPage,
getSelectedShapes,
getRotatedBounds,
getShapeBounds,
} from 'utils/utils'
import { getShapeUtils } from 'lib/shape-utils'
@ -27,11 +28,11 @@ export default class RotateSession extends BaseSession {
}
update(data: Data, point: number[], isLocked: boolean) {
const { boundsCenter, shapes } = this.snapshot
const { commonBoundsCenter, initialShapes } = this.snapshot
const page = getPage(data)
const a1 = vec.angle(boundsCenter, this.origin)
const a2 = vec.angle(boundsCenter, point)
const a1 = vec.angle(commonBoundsCenter, this.origin)
const a2 = vec.angle(commonBoundsCenter, point)
let rot = a2 - a1
@ -41,29 +42,28 @@ export default class RotateSession extends BaseSession {
data.boundsRotation = (PI2 + (this.snapshot.boundsRotation + rot)) % PI2
for (let { id, center, offset, rotation } of shapes) {
for (let { id, center, offset, rotation } of initialShapes) {
const shape = page.shapes[id]
const nextRotation = isLocked
? clampToRotationToSegments(rotation + rot, 24)
: rotation + rot
const nextPoint = vec.sub(
vec.rotWith(center, commonBoundsCenter, rot),
offset
)
getShapeUtils(shape)
.rotateTo(
shape,
(PI2 +
(isLocked
? clampToRotationToSegments(rotation + rot, 24)
: rotation + rot)) %
PI2
)
.translateTo(
shape,
vec.sub(vec.rotWith(center, boundsCenter, rot % PI2), offset)
)
.rotateTo(shape, (PI2 + nextRotation) % PI2)
.translateTo(shape, nextPoint)
}
}
cancel(data: Data) {
const page = getPage(data, this.snapshot.currentPageId)
for (let { id, point, rotation } of this.snapshot.shapes) {
for (let { id, point, rotation } of this.snapshot.initialShapes) {
const shape = page.shapes[id]
getShapeUtils(shape).rotateTo(shape, rotation).translateTo(shape, point)
}
@ -88,20 +88,22 @@ export function getRotateSnapshot(data: Data) {
const bounds = getCommonBounds(...Object.values(shapesBounds))
const commonBoundsCenter = getBoundsCenter(bounds)
return {
hasUnlockedShapes,
currentPageId: data.currentPageId,
boundsRotation: data.boundsRotation,
boundsCenter: getBoundsCenter(bounds),
shapes: initialShapes.map(({ id, point, rotation }) => {
const bounds = shapesBounds[id]
const offset = [bounds.width / 2, bounds.height / 2]
commonBoundsCenter,
initialShapes: initialShapes.map((shape) => {
const bounds = shapesBounds[shape.id]
const center = getBoundsCenter(bounds)
const offset = vec.sub(center, shape.point)
return {
id,
point,
rotation,
id: shape.id,
point: shape.point,
rotation: shape.rotation,
offset,
center,
}

View file

@ -157,6 +157,7 @@ const state = createState({
STRETCHED: { if: 'hasSelection', do: 'stretchSelection' },
DISTRIBUTED: { if: 'hasSelection', do: 'distributeSelection' },
DUPLICATED: { if: 'hasSelection', do: 'duplicateSelection' },
ROTATED_CCW: { if: 'hasSelection', do: 'rotateSelectionCcw' },
},
initial: 'notPointing',
states: {
@ -859,6 +860,9 @@ const state = createState({
deleteSelection(data) {
commands.deleteSelected(data)
},
rotateSelectionCcw(data) {
commands.rotateCcw(data)
},
/* --------------------- Camera --------------------- */

View file

@ -972,7 +972,7 @@ export function vectorToPoint(point: number[] | Vector | undefined) {
return point
}
export function getBoundsFromPoints(points: number[][]): Bounds {
export function getBoundsFromPoints(points: number[][], rotation = 0): Bounds {
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
@ -992,6 +992,15 @@ export function getBoundsFromPoints(points: number[][]): Bounds {
}
}
if (rotation !== 0) {
console.log('returning rotated bounds')
return getBoundsFromPoints(
points.map((pt) =>
vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], rotation)
)
)
}
return {
minX,
minY,
@ -1374,6 +1383,10 @@ export function isMobile() {
return _isMobile()
}
export function getRotatedBounds(shape: Shape) {
return getShapeUtils(shape).getRotatedBounds(shape)
}
export function getShapeBounds(shape: Shape) {
return getShapeUtils(shape).getBounds(shape)
}