Adds direction session

This commit is contained in:
Steve Ruiz 2021-05-15 16:20:21 +01:00
parent a4643edd62
commit 8a650a99d6
13 changed files with 245 additions and 93 deletions

View file

@ -6,13 +6,13 @@ export default function Toolbar() {
const activeTool = useSelector((state) => const activeTool = useSelector((state) =>
state.whenIn({ state.whenIn({
selecting: "select", selecting: "select",
creatingDot: "dot", dot: "dot",
creatingCircle: "circle", circle: "circle",
creatingEllipse: "ellipse", ellipse: "ellipse",
creatingRay: "ray", ray: "ray",
creatingLine: "line", line: "line",
creatingPolyline: "polyline", polyline: "polyline",
creatingRectangle: "rectangle", rectangle: "rectangle",
}) })
) )

View file

@ -87,10 +87,6 @@ const circle = createShape<CircleShape>({
return shape return shape
}, },
stretch(shape, scaleX, scaleY) {
return shape
},
transform(shape, bounds, { anchor }) { transform(shape, bounds, { anchor }) {
// Set the new corner or position depending on the anchor // Set the new corner or position depending on the anchor
switch (anchor) { switch (anchor) {

View file

@ -66,16 +66,12 @@ const dot = createShape<DotShape>({
return shape return shape
}, },
translate(shape, delta) {
shape.point = vec.add(shape.point, delta)
return shape
},
scale(shape, scale: number) { scale(shape, scale: number) {
return shape return shape
}, },
stretch(shape, scaleX: number, scaleY: number) { translate(shape, delta) {
shape.point = vec.add(shape.point, delta)
return shape return shape
}, },

View file

@ -93,10 +93,6 @@ const ellipse = createShape<EllipseShape>({
return shape return shape
}, },
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
transform(shape, bounds) { transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
shape.radiusX = bounds.width / 2 shape.radiusX = bounds.width / 2

View file

@ -67,9 +67,6 @@ export interface ShapeUtility<K extends Shape> {
// Apply a scale to a shape. // Apply a scale to a shape.
scale(this: ShapeUtility<K>, shape: K, scale: number): K scale(this: ShapeUtility<K>, shape: K, scale: number): K
// Apply a stretch to a shape.
stretch(this: ShapeUtility<K>, shape: K, scaleX: number, scaleY: number): K
// Render a shape to JSX. // Render a shape to JSX.
render(this: ShapeUtility<K>, shape: K): JSX.Element render(this: ShapeUtility<K>, shape: K): JSX.Element

View file

@ -84,10 +84,6 @@ const line = createShape<LineShape>({
return shape return shape
}, },
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
transform(shape, bounds) { transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]

View file

@ -98,10 +98,6 @@ const polyline = createShape<PolylineShape>({
return shape return shape
}, },
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
transform( transform(
shape, shape,
bounds, bounds,

View file

@ -17,9 +17,12 @@ const ray = createShape<RayShape>({
parentId: "page0", parentId: "page0",
childIndex: 0, childIndex: 0,
point: [0, 0], point: [0, 0],
direction: [0, 0], direction: [0, 1],
rotation: 0, rotation: 0,
style: {}, style: {
stroke: "#000",
strokeWidth: 1,
},
...props, ...props,
} }
}, },
@ -83,11 +86,9 @@ const ray = createShape<RayShape>({
return shape return shape
}, },
stretch(shape, scaleX: number, scaleY: number) {
return shape
},
transform(shape, bounds) { transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY]
return shape return shape
}, },

View file

@ -0,0 +1,36 @@
import Command from "./command"
import history from "../history"
import { DirectionSnapshot } from "state/sessions/direction-session"
import { Data, LineShape, RayShape } from "types"
export default function translateCommand(
data: Data,
before: DirectionSnapshot,
after: DirectionSnapshot
) {
history.execute(
data,
new Command({
name: "set_direction",
category: "canvas",
do(data) {
const { shapes } = data.document.pages[after.currentPageId]
for (let { id, direction } of after.shapes) {
const shape = shapes[id] as RayShape | LineShape
shape.direction = direction
}
},
undo(data) {
const { shapes } = data.document.pages[before.currentPageId]
for (let { id, direction } of after.shapes) {
const shape = shapes[id] as RayShape | LineShape
shape.direction = direction
}
},
})
)
}

View file

@ -2,7 +2,14 @@ import translate from "./translate"
import transform from "./transform" import transform from "./transform"
import generateShapes from "./generate-shapes" import generateShapes from "./generate-shapes"
import createShape from "./create-shape" import createShape from "./create-shape"
import direction from "./direction"
const commands = { translate, transform, generateShapes, createShape } const commands = {
translate,
transform,
generateShapes,
createShape,
direction,
}
export default commands export default commands

View file

@ -0,0 +1,71 @@
import { Data, LineShape, RayShape } from "types"
import * as vec from "utils/vec"
import BaseSession from "./base-session"
import commands from "state/commands"
import { current } from "immer"
export default class DirectionSession extends BaseSession {
delta = [0, 0]
origin: number[]
snapshot: DirectionSnapshot
constructor(data: Data, point: number[]) {
super(data)
this.origin = point
this.snapshot = getDirectionSnapshot(data)
}
update(data: Data, point: number[]) {
const { currentPageId, shapes } = this.snapshot
const { document } = data
for (let { id } of shapes) {
const shape = document.pages[currentPageId].shapes[id] as
| RayShape
| LineShape
shape.direction = vec.uni(vec.vec(shape.point, point))
}
}
cancel(data: Data) {
const { document } = data
for (let { id, direction } of this.snapshot.shapes) {
const shape = document.pages[this.snapshot.currentPageId].shapes[id] as
| RayShape
| LineShape
shape.direction = direction
}
}
complete(data: Data) {
commands.direction(data, this.snapshot, getDirectionSnapshot(data))
}
}
export function getDirectionSnapshot(data: Data) {
const {
document: { pages },
currentPageId,
} = current(data)
const { shapes } = pages[currentPageId]
let snapshapes: { id: string; direction: number[] }[] = []
data.selectedIds.forEach((id) => {
const shape = shapes[id]
if ("direction" in shape) {
snapshapes.push({ id: shape.id, direction: shape.direction })
}
})
return {
currentPageId,
shapes: snapshapes,
}
}
export type DirectionSnapshot = ReturnType<typeof getDirectionSnapshot>

View file

@ -2,5 +2,12 @@ import BaseSession from "./base-session"
import BrushSession from "./brush-session" import BrushSession from "./brush-session"
import TranslateSession from "./translate-session" import TranslateSession from "./translate-session"
import TransformSession from "./transform-session" import TransformSession from "./transform-session"
import DirectionSession from "./direction-session"
export { BrushSession, BaseSession, TranslateSession, TransformSession } export {
BrushSession,
BaseSession,
TranslateSession,
TransformSession,
DirectionSession,
}

View file

@ -44,13 +44,13 @@ const state = createState({
do: "panCamera", do: "panCamera",
}, },
SELECTED_SELECT_TOOL: { to: "selecting" }, SELECTED_SELECT_TOOL: { to: "selecting" },
SELECTED_DOT_TOOL: { unless: "isReadOnly", to: "creatingDot" }, SELECTED_DOT_TOOL: { unless: "isReadOnly", to: "dot" },
SELECTED_CIRCLE_TOOL: { unless: "isReadOnly", to: "creatingCircle" }, SELECTED_CIRCLE_TOOL: { unless: "isReadOnly", to: "circle" },
SELECTED_ELLIPSE_TOOL: { unless: "isReadOnly", to: "creatingEllipse" }, SELECTED_ELLIPSE_TOOL: { unless: "isReadOnly", to: "ellipse" },
SELECTED_RAY_TOOL: { unless: "isReadOnly", to: "creatingRay" }, SELECTED_RAY_TOOL: { unless: "isReadOnly", to: "ray" },
SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "creatingLine" }, SELECTED_LINE_TOOL: { unless: "isReadOnly", to: "line" },
SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "creatingPolyline" }, SELECTED_POLYLINE_TOOL: { unless: "isReadOnly", to: "polyline" },
SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "creatingRectangle" }, SELECTED_RECTANGLE_TOOL: { unless: "isReadOnly", to: "rectangle" },
}, },
initial: "selecting", initial: "selecting",
states: { states: {
@ -154,18 +154,18 @@ const state = createState({
}, },
}, },
}, },
creatingDot: { dot: {
initial: "creating", initial: "creating",
states: { states: {
creating: { creating: {
on: { on: {
POINTED_CANVAS: { POINTED_CANVAS: {
do: "createDot", do: "createDot",
to: "creatingDot.positioning", to: "dot.editing",
}, },
}, },
}, },
positioning: { editing: {
onEnter: "startTranslateSession", onEnter: "startTranslateSession",
on: { on: {
MOVED_POINTER: "updateTranslateSession", MOVED_POINTER: "updateTranslateSession",
@ -179,12 +179,36 @@ const state = createState({
}, },
}, },
}, },
creatingCircle: {}, circle: {},
creatingEllipse: {}, ellipse: {},
creatingRay: {}, ray: {
creatingLine: {}, initial: "creating",
creatingPolyline: {}, states: {
creatingRectangle: {}, creating: {
on: {
POINTED_CANVAS: {
do: "createRay",
to: "ray.editing",
},
},
},
editing: {
onEnter: "startDirectionSession",
on: {
MOVED_POINTER: "updateDirectionSession",
PANNED_CAMERA: "updateDirectionSession",
STOPPED_POINTING: { do: "completeSession", to: "selecting" },
CANCELLED: {
do: ["cancelSession", "deleteSelectedIds"],
to: "selecting",
},
},
},
},
},
line: {},
polyline: {},
rectangle: {},
}, },
conditions: { conditions: {
isPointingBounds(data, payload: PointerInfo) { isPointingBounds(data, payload: PointerInfo) {
@ -216,41 +240,31 @@ const state = createState({
}, },
}, },
actions: { actions: {
// Shapes /* --------------------- Shapes --------------------- */
// Dot
createDot(data, payload: PointerInfo) { createDot(data, payload: PointerInfo) {
const shape = shapeUtilityMap[ShapeType.Dot].create({ const shape = shapeUtilityMap[ShapeType.Dot].create({
point: screenToWorld(payload.point, data), point: screenToWorld(payload.point, data),
}) })
commands.createShape(data, shape) commands.createShape(data, shape)
data.selectedIds.add(shape.id)
}, },
// History // Ray
enableHistory() { createRay(data, payload: PointerInfo) {
history.enable() const shape = shapeUtilityMap[ShapeType.Ray].create({
}, point: screenToWorld(payload.point, data),
disableHistory() { })
history.disable()
}, commands.createShape(data, shape)
undo(data) { data.selectedIds.add(shape.id)
history.undo(data)
},
redo(data) {
history.redo(data)
}, },
// Code /* -------------------- Sessions -------------------- */
setGeneratedShapes(data, payload: { shapes: Shape[] }) {
commands.generateShapes(data, data.currentPageId, payload.shapes)
},
increaseCodeFontSize(data) {
data.settings.fontSize++
},
decreaseCodeFontSize(data) {
data.settings.fontSize--
},
// Sessions // Shared
cancelSession(data) { cancelSession(data) {
session.cancel(data) session.cancel(data)
session = undefined session = undefined
@ -297,20 +311,19 @@ const state = createState({
session.update(data, screenToWorld(payload.point, data)) session.update(data, screenToWorld(payload.point, data))
}, },
// Selection // Direction
deleteSelectedIds(data) { startDirectionSession(data, payload: PointerInfo) {
const { document, currentPageId } = data session = new Sessions.DirectionSession(
const shapes = document.pages[currentPageId].shapes data,
screenToWorld(payload.point, data)
data.selectedIds.forEach((id) => { )
delete shapes[id]
// TODO: recursively delete children
})
data.selectedIds.clear()
data.hoveredId = undefined
data.pointedId = undefined
}, },
updateDirectionSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data))
},
/* -------------------- Selection ------------------- */
setHoveredId(data, payload: PointerInfo) { setHoveredId(data, payload: PointerInfo) {
data.hoveredId = payload.target data.hoveredId = payload.target
}, },
@ -357,6 +370,46 @@ const state = createState({
vec.div(payload.delta, camera.zoom) vec.div(payload.delta, camera.zoom)
) )
}, },
deleteSelectedIds(data) {
const { document, currentPageId } = data
const shapes = document.pages[currentPageId].shapes
data.selectedIds.forEach((id) => {
delete shapes[id]
// TODO: recursively delete children
})
data.selectedIds.clear()
data.hoveredId = undefined
data.pointedId = undefined
},
/* ---------------------- Misc ---------------------- */
// History
enableHistory() {
history.enable()
},
disableHistory() {
history.disable()
},
undo(data) {
history.undo(data)
},
redo(data) {
history.redo(data)
},
// Code
setGeneratedShapes(data, payload: { shapes: Shape[] }) {
commands.generateShapes(data, data.currentPageId, payload.shapes)
},
increaseCodeFontSize(data) {
data.settings.fontSize++
},
decreaseCodeFontSize(data) {
data.settings.fontSize--
},
}, },
values: { values: {
selectedIds(data) { selectedIds(data) {