feat(canvas): support guide selection and editing
This commit is contained in:
parent
103e4ac151
commit
23b7d92ecf
|
|
@ -7,6 +7,7 @@ import Matrix from '@open-pencil/scene-graph/matrix'
|
||||||
import type { RenderOverlays, SkiaRenderer } from './renderer'
|
import type { RenderOverlays, SkiaRenderer } from './renderer'
|
||||||
|
|
||||||
const GUIDE_COLOR = { r: 0.85, g: 0.29, b: 0.2, a: 0.78 }
|
const GUIDE_COLOR = { r: 0.85, g: 0.29, b: 0.2, a: 0.78 }
|
||||||
|
const SELECTED_GUIDE_COLOR = { r: 0.1, g: 0.45, b: 0.95, a: 1 }
|
||||||
const GUIDE_DASH = [3, 4]
|
const GUIDE_DASH = [3, 4]
|
||||||
|
|
||||||
function drawOwnedGuide(
|
function drawOwnedGuide(
|
||||||
|
|
@ -16,8 +17,11 @@ function drawOwnedGuide(
|
||||||
graph: SceneGraph,
|
graph: SceneGraph,
|
||||||
axis: 'x' | 'y',
|
axis: 'x' | 'y',
|
||||||
position: number,
|
position: number,
|
||||||
preview: boolean
|
preview: boolean,
|
||||||
|
selected = false
|
||||||
): void {
|
): void {
|
||||||
|
const color = selected ? SELECTED_GUIDE_COLOR : GUIDE_COLOR
|
||||||
|
r.auxStroke.setColor(r.ck.Color4f(color.r, color.g, color.b, color.a))
|
||||||
const matrix = getWorldMatrix(owner, graph)
|
const matrix = getWorldMatrix(owner, graph)
|
||||||
const start = Matrix.mapPoint(
|
const start = Matrix.mapPoint(
|
||||||
matrix,
|
matrix,
|
||||||
|
|
@ -46,7 +50,8 @@ export function drawPageGuides(
|
||||||
r: SkiaRenderer,
|
r: SkiaRenderer,
|
||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
graph: SceneGraph,
|
graph: SceneGraph,
|
||||||
preview?: RenderOverlays['guidePreview']
|
preview?: RenderOverlays['guidePreview'],
|
||||||
|
selectedGuide?: RenderOverlays['selectedGuide']
|
||||||
): void {
|
): void {
|
||||||
const page = graph.getNode(r.pageId ?? graph.rootId)
|
const page = graph.getNode(r.pageId ?? graph.rootId)
|
||||||
if (!page) return
|
if (!page) return
|
||||||
|
|
@ -55,6 +60,9 @@ export function drawPageGuides(
|
||||||
r.auxStroke.setColor(r.ck.Color4f(GUIDE_COLOR.r, GUIDE_COLOR.g, GUIDE_COLOR.b, GUIDE_COLOR.a))
|
r.auxStroke.setColor(r.ck.Color4f(GUIDE_COLOR.r, GUIDE_COLOR.g, GUIDE_COLOR.b, GUIDE_COLOR.a))
|
||||||
|
|
||||||
for (const guide of page.guides) {
|
for (const guide of page.guides) {
|
||||||
|
const selected = selectedGuide?.ownerId === page.id && selectedGuide.guideId === guide.id
|
||||||
|
const color = selected ? SELECTED_GUIDE_COLOR : GUIDE_COLOR
|
||||||
|
r.auxStroke.setColor(r.ck.Color4f(color.r, color.g, color.b, color.a))
|
||||||
if (guide.axis === 'x') {
|
if (guide.axis === 'x') {
|
||||||
const x = guide.position * r.zoom + r.panX
|
const x = guide.position * r.zoom + r.panX
|
||||||
canvas.drawRect(r.ck.LTRBRect(x, 0, x + 1, r.viewportHeight), r.auxStroke)
|
canvas.drawRect(r.ck.LTRBRect(x, 0, x + 1, r.viewportHeight), r.auxStroke)
|
||||||
|
|
@ -66,7 +74,16 @@ export function drawPageGuides(
|
||||||
|
|
||||||
const visit = (node: SceneNode) => {
|
const visit = (node: SceneNode) => {
|
||||||
for (const guide of node.guides) {
|
for (const guide of node.guides) {
|
||||||
drawOwnedGuide(r, canvas, node, graph, guide.axis, guide.position, false)
|
drawOwnedGuide(
|
||||||
|
r,
|
||||||
|
canvas,
|
||||||
|
node,
|
||||||
|
graph,
|
||||||
|
guide.axis,
|
||||||
|
guide.position,
|
||||||
|
false,
|
||||||
|
selectedGuide?.ownerId === node.id && selectedGuide.guideId === guide.id
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for (const childId of node.childIds) {
|
for (const childId of node.childIds) {
|
||||||
const child = graph.getNode(childId)
|
const child = graph.getNode(childId)
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ export function renderFromEditorState(
|
||||||
marquee: state.marquee,
|
marquee: state.marquee,
|
||||||
snapGuides: state.snapGuides,
|
snapGuides: state.snapGuides,
|
||||||
guidePreview: state.guidePreview,
|
guidePreview: state.guidePreview,
|
||||||
|
selectedGuide: state.selectedGuide,
|
||||||
rotationPreview: state.rotationPreview,
|
rotationPreview: state.rotationPreview,
|
||||||
dropTargetId: state.dropTargetId,
|
dropTargetId: state.dropTargetId,
|
||||||
layoutInsertIndicator: state.layoutInsertIndicator,
|
layoutInsertIndicator: state.layoutInsertIndicator,
|
||||||
|
|
@ -255,7 +256,7 @@ export function render(
|
||||||
|
|
||||||
drawInteractiveOverlays(r, canvas, graph, selectedIds, overlays)
|
drawInteractiveOverlays(r, canvas, graph, selectedIds, overlays)
|
||||||
r.drawFlashes(canvas, graph)
|
r.drawFlashes(canvas, graph)
|
||||||
drawPageGuides(r, canvas, graph, overlays.guidePreview)
|
drawPageGuides(r, canvas, graph, overlays.guidePreview, overlays.selectedGuide)
|
||||||
r.drawSnapGuides(canvas, overlays.snapGuides)
|
r.drawSnapGuides(canvas, overlays.snapGuides)
|
||||||
r.drawMarquee(canvas, overlays.marquee)
|
r.drawMarquee(canvas, overlays.marquee)
|
||||||
r.drawLayoutInsertIndicator(canvas, overlays.layoutInsertIndicator)
|
r.drawLayoutInsertIndicator(canvas, overlays.layoutInsertIndicator)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { VectorRegion, VectorVertex } from '@open-pencil/scene-graph'
|
||||||
import type { Color, Rect, Vector } from '@open-pencil/scene-graph/primitives'
|
import type { Color, Rect, Vector } from '@open-pencil/scene-graph/primitives'
|
||||||
import type { SnapGuide } from '@open-pencil/scene-graph/snap'
|
import type { SnapGuide } from '@open-pencil/scene-graph/snap'
|
||||||
|
|
||||||
import type { GuidePreview } from '#core/editor/guide-preview'
|
import type { GuidePreview, GuideSelection } from '#core/editor/guide-preview'
|
||||||
import type { TextEditor } from '#core/text/editor'
|
import type { TextEditor } from '#core/text/editor'
|
||||||
|
|
||||||
export interface RulerTheme {
|
export interface RulerTheme {
|
||||||
|
|
@ -23,6 +23,7 @@ export interface RenderOverlays {
|
||||||
marquee?: Rect | null
|
marquee?: Rect | null
|
||||||
snapGuides?: SnapGuide[]
|
snapGuides?: SnapGuide[]
|
||||||
guidePreview?: GuidePreview | null
|
guidePreview?: GuidePreview | null
|
||||||
|
selectedGuide?: GuideSelection | null
|
||||||
rotationPreview?: { nodeId: string; angle: number } | null
|
rotationPreview?: { nodeId: string; angle: number } | null
|
||||||
dropTargetId?: string | null
|
dropTargetId?: string | null
|
||||||
layoutInsertIndicator?: {
|
layoutInsertIndicator?: {
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,7 @@ export function createEditor(options?: EditorOptions) {
|
||||||
state.measurementMode = 'off'
|
state.measurementMode = 'off'
|
||||||
state.snapGuides = []
|
state.snapGuides = []
|
||||||
state.guidePreview = null
|
state.guidePreview = null
|
||||||
|
state.selectedGuide = null
|
||||||
state.layoutInsertIndicator = null
|
state.layoutInsertIndicator = null
|
||||||
state.dropTargetId = null
|
state.dropTargetId = null
|
||||||
pages.clearPageViewports()
|
pages.clearPageViewports()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
export interface GuideSelection {
|
||||||
|
ownerId: string
|
||||||
|
guideId: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface GuidePreview {
|
export interface GuidePreview {
|
||||||
ownerId: string
|
ownerId: string
|
||||||
axis: 'x' | 'y'
|
axis: 'x' | 'y'
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ export function createSelectionOverlayActions(ctx: EditorContext) {
|
||||||
ctx.requestRepaint()
|
ctx.requestRepaint()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSelectedGuide(selection: typeof ctx.state.selectedGuide) {
|
||||||
|
ctx.state.selectedGuide = selection
|
||||||
|
if (selection) ctx.setSelectedIds(new Set())
|
||||||
|
ctx.requestRepaint()
|
||||||
|
}
|
||||||
|
|
||||||
function setRotationPreview(preview: { nodeId: string; angle: number } | null) {
|
function setRotationPreview(preview: { nodeId: string; angle: number } | null) {
|
||||||
ctx.state.rotationPreview = preview
|
ctx.state.rotationPreview = preview
|
||||||
ctx.requestRepaint()
|
ctx.requestRepaint()
|
||||||
|
|
@ -67,6 +73,7 @@ export function createSelectionOverlayActions(ctx: EditorContext) {
|
||||||
setMarquee,
|
setMarquee,
|
||||||
setSnapGuides,
|
setSnapGuides,
|
||||||
setGuidePreview,
|
setGuidePreview,
|
||||||
|
setSelectedGuide,
|
||||||
setRotationPreview,
|
setRotationPreview,
|
||||||
setHoveredNode,
|
setHoveredNode,
|
||||||
setMeasurementMode,
|
setMeasurementMode,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export function createDefaultEditorViewState(pageId: string): EditorViewState {
|
||||||
marquee: null,
|
marquee: null,
|
||||||
snapGuides: [],
|
snapGuides: [],
|
||||||
guidePreview: null,
|
guidePreview: null,
|
||||||
|
selectedGuide: null,
|
||||||
rotationPreview: null,
|
rotationPreview: null,
|
||||||
dropTargetId: null,
|
dropTargetId: null,
|
||||||
layoutInsertIndicator: null,
|
layoutInsertIndicator: null,
|
||||||
|
|
@ -37,6 +38,7 @@ export function copyEditorViewState(source: EditorViewState): EditorViewState {
|
||||||
marquee: structuredClone(source.marquee),
|
marquee: structuredClone(source.marquee),
|
||||||
snapGuides: structuredClone(source.snapGuides),
|
snapGuides: structuredClone(source.snapGuides),
|
||||||
guidePreview: structuredClone(source.guidePreview),
|
guidePreview: structuredClone(source.guidePreview),
|
||||||
|
selectedGuide: structuredClone(source.selectedGuide),
|
||||||
rotationPreview: structuredClone(source.rotationPreview),
|
rotationPreview: structuredClone(source.rotationPreview),
|
||||||
layoutInsertIndicator: structuredClone(source.layoutInsertIndicator),
|
layoutInsertIndicator: structuredClone(source.layoutInsertIndicator),
|
||||||
penState: structuredClone(source.penState),
|
penState: structuredClone(source.penState),
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import type { UndoManager } from '@open-pencil/scene-graph/undo'
|
||||||
|
|
||||||
import type { RulerTheme, SkiaRenderer } from '#core/canvas/renderer'
|
import type { RulerTheme, SkiaRenderer } from '#core/canvas/renderer'
|
||||||
import type { MeasurementMode, RenderOverlays } from '#core/canvas/renderer/types'
|
import type { MeasurementMode, RenderOverlays } from '#core/canvas/renderer/types'
|
||||||
import type { GuidePreview } from '#core/editor/guide-preview'
|
import type { GuidePreview, GuideSelection } from '#core/editor/guide-preview'
|
||||||
import type { SnappingPreferences } from '#core/editor/preferences'
|
import type { SnappingPreferences } from '#core/editor/preferences'
|
||||||
import type { TextEditor } from '#core/text/editor'
|
import type { TextEditor } from '#core/text/editor'
|
||||||
import type { FontResolutionEvent, FontResolutionSnapshot } from '#core/text/resolver'
|
import type { FontResolutionEvent, FontResolutionSnapshot } from '#core/text/resolver'
|
||||||
|
|
@ -54,6 +54,7 @@ export interface EditorViewState {
|
||||||
marquee: Rect | null
|
marquee: Rect | null
|
||||||
snapGuides: SnapGuide[]
|
snapGuides: SnapGuide[]
|
||||||
guidePreview: GuidePreview | null
|
guidePreview: GuidePreview | null
|
||||||
|
selectedGuide: GuideSelection | null
|
||||||
rotationPreview: { nodeId: string; angle: number } | null
|
rotationPreview: { nodeId: string; angle: number } | null
|
||||||
dropTargetId: string | null
|
dropTargetId: string | null
|
||||||
layoutInsertIndicator: {
|
layoutInsertIndicator: {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import { onScopeDispose, ref, type Ref } from 'vue'
|
||||||
import { RULER_SIZE } from '@open-pencil/core/constants'
|
import { RULER_SIZE } from '@open-pencil/core/constants'
|
||||||
import type { Editor } from '@open-pencil/core/editor'
|
import type { Editor } from '@open-pencil/core/editor'
|
||||||
import type { SceneNode } from '@open-pencil/scene-graph'
|
import type { SceneNode } from '@open-pencil/scene-graph'
|
||||||
|
import { getWorldMatrix } from '@open-pencil/scene-graph/coordinate'
|
||||||
|
import Matrix from '@open-pencil/scene-graph/matrix'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
handleBendHandleMove,
|
handleBendHandleMove,
|
||||||
|
|
@ -98,6 +100,16 @@ export function useCanvasInput(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteSelectedGuide(event: KeyboardEvent): boolean {
|
||||||
|
if (event.code !== 'Delete' && event.code !== 'Backspace') return false
|
||||||
|
const selected = editor.state.selectedGuide
|
||||||
|
if (!selected || editor.state.editingTextId) return false
|
||||||
|
if (!editor.removeGuide(selected.ownerId, selected.guideId)) return false
|
||||||
|
editor.setSelectedGuide(null)
|
||||||
|
event.preventDefault()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function updateModifier(code: string, held: boolean) {
|
function updateModifier(code: string, held: boolean) {
|
||||||
if (!isEnabled()) return
|
if (!isEnabled()) return
|
||||||
if (code === 'AltLeft' || code === 'AltRight') altHeld = held
|
if (code === 'AltLeft' || code === 'AltRight') altHeld = held
|
||||||
|
|
@ -209,6 +221,95 @@ export function useCanvasInput(
|
||||||
return { id: editor.state.currentPageId, position: 0 }
|
return { id: editor.state.currentPageId, position: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function guideHitTest(sx: number, sy: number) {
|
||||||
|
const tolerance = 5
|
||||||
|
const page = editor.graph.getNode(editor.state.currentPageId)
|
||||||
|
if (!page) return null
|
||||||
|
const hits: Array<{
|
||||||
|
ownerId: string
|
||||||
|
guideId: string
|
||||||
|
axis: 'x' | 'y'
|
||||||
|
position: number
|
||||||
|
distance: number
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
const visit = (owner: SceneNode) => {
|
||||||
|
const matrix = getWorldMatrix(owner, editor.graph)
|
||||||
|
for (const guide of owner.guides) {
|
||||||
|
const start = Matrix.mapPoint(
|
||||||
|
matrix,
|
||||||
|
guide.axis === 'x' ? { x: guide.position, y: 0 } : { x: 0, y: guide.position }
|
||||||
|
)
|
||||||
|
const end = Matrix.mapPoint(
|
||||||
|
matrix,
|
||||||
|
guide.axis === 'x'
|
||||||
|
? { x: guide.position, y: owner.height }
|
||||||
|
: { x: owner.width, y: guide.position }
|
||||||
|
)
|
||||||
|
const x1 =
|
||||||
|
owner.type === 'CANVAS' && guide.axis === 'y'
|
||||||
|
? 0
|
||||||
|
: start.x * editor.state.zoom + editor.state.panX
|
||||||
|
const y1 =
|
||||||
|
owner.type === 'CANVAS' && guide.axis === 'x'
|
||||||
|
? 0
|
||||||
|
: start.y * editor.state.zoom + editor.state.panY
|
||||||
|
const x2 =
|
||||||
|
owner.type === 'CANVAS' && guide.axis === 'y'
|
||||||
|
? (canvasRef.value?.width ?? sx)
|
||||||
|
: end.x * editor.state.zoom + editor.state.panX
|
||||||
|
const y2 =
|
||||||
|
owner.type === 'CANVAS' && guide.axis === 'x'
|
||||||
|
? (canvasRef.value?.height ?? sy)
|
||||||
|
: end.y * editor.state.zoom + editor.state.panY
|
||||||
|
const dx = x2 - x1
|
||||||
|
const dy = y2 - y1
|
||||||
|
const lengthSquared = dx * dx + dy * dy
|
||||||
|
const t =
|
||||||
|
lengthSquared === 0
|
||||||
|
? 0
|
||||||
|
: Math.max(0, Math.min(1, ((sx - x1) * dx + (sy - y1) * dy) / lengthSquared))
|
||||||
|
const distance = Math.hypot(sx - (x1 + t * dx), sy - (y1 + t * dy))
|
||||||
|
if (distance <= tolerance) {
|
||||||
|
hits.push({
|
||||||
|
ownerId: owner.id,
|
||||||
|
guideId: guide.id,
|
||||||
|
axis: guide.axis,
|
||||||
|
position: guide.position,
|
||||||
|
distance
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const childId of owner.childIds) {
|
||||||
|
const child = editor.graph.getNode(childId)
|
||||||
|
if (child) visit(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visit(page)
|
||||||
|
return hits.sort((a, b) => a.distance - b.distance)[0] ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
function startExistingGuideDrag(sx: number, sy: number): boolean {
|
||||||
|
const hit = guideHitTest(sx, sy)
|
||||||
|
if (!hit) return false
|
||||||
|
editor.setSelectedGuide({ ownerId: hit.ownerId, guideId: hit.guideId })
|
||||||
|
setDrag({
|
||||||
|
type: 'guide',
|
||||||
|
axis: hit.axis,
|
||||||
|
ownerId: hit.ownerId,
|
||||||
|
position: hit.position,
|
||||||
|
startScreenX: sx,
|
||||||
|
startScreenY: sy,
|
||||||
|
currentScreenX: sx,
|
||||||
|
currentScreenY: sy,
|
||||||
|
dragStarted: false,
|
||||||
|
guideId: hit.guideId,
|
||||||
|
originalOwnerId: hit.ownerId,
|
||||||
|
originalPosition: hit.position
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function startGuideDrag(sx: number, sy: number, cx: number, cy: number): boolean {
|
function startGuideDrag(sx: number, sy: number, cx: number, cy: number): boolean {
|
||||||
if (!('showRulers' in editor.state) || editor.state.showRulers !== true) return false
|
if (!('showRulers' in editor.state) || editor.state.showRulers !== true) return false
|
||||||
if (sx < RULER_SIZE && sy < RULER_SIZE) return false
|
if (sx < RULER_SIZE && sy < RULER_SIZE) return false
|
||||||
|
|
@ -220,8 +321,17 @@ export function useCanvasInput(
|
||||||
const owner = editor.graph.getNode(target.id)
|
const owner = editor.graph.getNode(target.id)
|
||||||
const local = owner && owner.type !== 'CANVAS' ? canvasToLocal(cx, cy, owner.id) : null
|
const local = owner && owner.type !== 'CANVAS' ? canvasToLocal(cx, cy, owner.id) : null
|
||||||
const position = axis === 'x' ? (local?.lx ?? cx) : (local?.ly ?? cy)
|
const position = axis === 'x' ? (local?.lx ?? cx) : (local?.ly ?? cy)
|
||||||
editor.setGuidePreview({ ownerId: target.id, axis, position })
|
setDrag({
|
||||||
setDrag({ type: 'guide', axis, ownerId: target.id, position })
|
type: 'guide',
|
||||||
|
axis,
|
||||||
|
ownerId: target.id,
|
||||||
|
position,
|
||||||
|
startScreenX: sx,
|
||||||
|
startScreenY: sy,
|
||||||
|
currentScreenX: sx,
|
||||||
|
currentScreenY: sy,
|
||||||
|
dragStarted: false
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,10 +351,15 @@ export function useCanvasInput(
|
||||||
if (!editor.state.editingTextId) canvasRef.value?.focus()
|
if (!editor.state.editingTextId) canvasRef.value?.focus()
|
||||||
editor.setHoveredNode(null)
|
editor.setHoveredNode(null)
|
||||||
const { sx, sy, cx, cy } = getCoords(e)
|
const { sx, sy, cx, cy } = getCoords(e)
|
||||||
|
if (e.button === 0 && startExistingGuideDrag(sx, sy)) {
|
||||||
|
e.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (e.button === 0 && startGuideDrag(sx, sy, cx, cy)) {
|
if (e.button === 0 && startGuideDrag(sx, sy, cx, cy)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
editor.setSelectedGuide(null)
|
||||||
|
|
||||||
const selectedIdsBeforeMouseDown = new Set(editor.state.selectedIds)
|
const selectedIdsBeforeMouseDown = new Set(editor.state.selectedIds)
|
||||||
const clickCount = recordClick(sx, sy)
|
const clickCount = recordClick(sx, sy)
|
||||||
|
|
@ -310,6 +425,10 @@ export function useCanvasInput(
|
||||||
const { sx, sy, cx, cy } = getCoords(e)
|
const { sx, sy, cx, cy } = getCoords(e)
|
||||||
|
|
||||||
if (d.type === 'guide') {
|
if (d.type === 'guide') {
|
||||||
|
d.currentScreenX = sx
|
||||||
|
d.currentScreenY = sy
|
||||||
|
if (!d.dragStarted && Math.hypot(sx - d.startScreenX, sy - d.startScreenY) < 3) return
|
||||||
|
d.dragStarted = true
|
||||||
const target = guideOwner(cx, cy)
|
const target = guideOwner(cx, cy)
|
||||||
const owner = editor.graph.getNode(target.id)
|
const owner = editor.graph.getNode(target.id)
|
||||||
const local = owner && owner.type !== 'CANVAS' ? canvasToLocal(cx, cy, owner.id) : null
|
const local = owner && owner.type !== 'CANVAS' ? canvasToLocal(cx, cy, owner.id) : null
|
||||||
|
|
@ -359,6 +478,22 @@ export function useCanvasInput(
|
||||||
handleMarqueeMove(d, cx, cy)
|
handleMarqueeMove(d, cx, cy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function finishGuideDrag(d: Extract<DragState, { type: 'guide' }>) {
|
||||||
|
if (d.dragStarted) {
|
||||||
|
if (d.currentScreenX < RULER_SIZE || d.currentScreenY < RULER_SIZE) {
|
||||||
|
if (d.guideId && d.originalOwnerId) editor.removeGuide(d.originalOwnerId, d.guideId)
|
||||||
|
} else if (d.guideId && d.originalOwnerId) {
|
||||||
|
if (d.ownerId === d.originalOwnerId) editor.moveGuide(d.ownerId, d.guideId, d.position)
|
||||||
|
else editor.transferGuide(d.originalOwnerId, d.ownerId, d.guideId, d.position)
|
||||||
|
editor.setSelectedGuide({ ownerId: d.ownerId, guideId: d.guideId })
|
||||||
|
} else {
|
||||||
|
const guideId = editor.addGuide(d.ownerId, d.axis, d.position)
|
||||||
|
if (guideId) editor.setSelectedGuide({ ownerId: d.ownerId, guideId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor.setGuidePreview(null)
|
||||||
|
}
|
||||||
|
|
||||||
function onMouseUp() {
|
function onMouseUp() {
|
||||||
if (!isEnabled()) return
|
if (!isEnabled()) return
|
||||||
if (!drag.value) return
|
if (!drag.value) return
|
||||||
|
|
@ -367,8 +502,7 @@ export function useCanvasInput(
|
||||||
if (handleNodeEditMouseUp(drag, editor)) return
|
if (handleNodeEditMouseUp(drag, editor)) return
|
||||||
|
|
||||||
if (d.type === 'guide') {
|
if (d.type === 'guide') {
|
||||||
editor.addGuide(d.ownerId, d.axis, d.position)
|
finishGuideDrag(d)
|
||||||
editor.setGuidePreview(null)
|
|
||||||
} else if (d.type === 'move') handleMoveUp(d, editor)
|
} else if (d.type === 'move') handleMoveUp(d, editor)
|
||||||
else if (d.type === 'text-select') {
|
else if (d.type === 'text-select') {
|
||||||
drag.value = null
|
drag.value = null
|
||||||
|
|
@ -417,7 +551,9 @@ export function useCanvasInput(
|
||||||
useEventListener(canvasRef, 'mousedown', onMouseDown)
|
useEventListener(canvasRef, 'mousedown', onMouseDown)
|
||||||
useEventListener(canvasRef, 'mousemove', onMouseMove)
|
useEventListener(canvasRef, 'mousemove', onMouseMove)
|
||||||
useEventListener(canvasRef, 'mouseup', onMouseUp)
|
useEventListener(canvasRef, 'mouseup', onMouseUp)
|
||||||
useEventListener(window, 'keydown', (event) => updateModifier(event.code, true))
|
useEventListener(window, 'keydown', (event) => {
|
||||||
|
if (!deleteSelectedGuide(event)) updateModifier(event.code, true)
|
||||||
|
})
|
||||||
useEventListener(window, 'keyup', (event) => updateModifier(event.code, false))
|
useEventListener(window, 'keyup', (event) => updateModifier(event.code, false))
|
||||||
useEventListener(window, 'blur', () => {
|
useEventListener(window, 'blur', () => {
|
||||||
resetMeasurementModifiers()
|
resetMeasurementModifiers()
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,14 @@ export interface DragGuide {
|
||||||
axis: 'x' | 'y'
|
axis: 'x' | 'y'
|
||||||
ownerId: string
|
ownerId: string
|
||||||
position: number
|
position: number
|
||||||
|
startScreenX: number
|
||||||
|
startScreenY: number
|
||||||
|
currentScreenX: number
|
||||||
|
currentScreenY: number
|
||||||
|
dragStarted: boolean
|
||||||
|
guideId?: string
|
||||||
|
originalOwnerId?: string
|
||||||
|
originalPosition?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DragState =
|
export type DragState =
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue