Improves rotation? I think?
This commit is contained in:
parent
2755b58f8f
commit
bc6f5cf5b7
9 changed files with 222 additions and 42 deletions
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
91
state/commands/rotate-ccw.ts
Normal file
91
state/commands/rotate-ccw.ts
Normal 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
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 --------------------- */
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue