fix(canvas): polish guide ruler interactions
This commit is contained in:
parent
b8d052f5c4
commit
5acc2235c6
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
### Added
|
||||
|
||||
- Create, select, move, transfer, and delete canvas and frame guides directly from rulers, with undoable edits and `.fig` round-trip fidelity.
|
||||
- Snap vector points, moved layers, and resized edges to nearby geometry, sibling layer bounds, canvas and frame layout guides, and whole-pixel coordinates with visible alignment guides, fractional-coordinate preservation when pixel snapping is off, and persistent geometry, object, and pixel-grid controls in General settings and the Preferences menu.
|
||||
- Run Pi through AI SDK HarnessAgent as a configurable desktop provider with multiple saved model profiles, secure credentials, existing MCP design tools, and per-profile thinking and permission settings.
|
||||
- Open multiple selected design files in separate tabs.
|
||||
|
|
|
|||
|
|
@ -63,14 +63,18 @@ export function createGuideInput({
|
|||
}
|
||||
|
||||
function updateHover(sx: number, sy: number): string | null {
|
||||
const axis = rulerAxis(sx, sy)
|
||||
if (axis) {
|
||||
editor.setHoveredGuide(null)
|
||||
return cursor(axis)
|
||||
}
|
||||
const hit = hitTest(sx, sy)
|
||||
editor.setHoveredGuide(hit ? { ownerId: hit.ownerId, guideId: hit.guideId } : null)
|
||||
if (hit) return cursor(hit.axis)
|
||||
const axis = rulerAxis(sx, sy)
|
||||
return axis ? cursor(axis) : null
|
||||
return hit ? cursor(hit.axis) : null
|
||||
}
|
||||
|
||||
function tryStartExisting(sx: number, sy: number): boolean {
|
||||
if (rulerAxis(sx, sy)) return false
|
||||
const hit = hitTest(sx, sy)
|
||||
if (!hit) return false
|
||||
editor.setSelectedGuide({ ownerId: hit.ownerId, guideId: hit.guideId })
|
||||
|
|
@ -134,8 +138,10 @@ export function createGuideInput({
|
|||
function finish(drag: DragGuide): void {
|
||||
if (drag.dragStarted) {
|
||||
if (drag.currentScreenX < RULER_SIZE || drag.currentScreenY < RULER_SIZE) {
|
||||
if (drag.guideId && drag.originalOwnerId)
|
||||
if (drag.guideId && drag.originalOwnerId) {
|
||||
editor.removeGuide(drag.originalOwnerId, drag.guideId)
|
||||
editor.setSelectedGuide(null)
|
||||
}
|
||||
} else if (drag.guideId && drag.originalOwnerId) {
|
||||
if (drag.ownerId === drag.originalOwnerId)
|
||||
editor.moveGuide(drag.ownerId, drag.guideId, drag.position)
|
||||
|
|
|
|||
59
tests/engine/vue/input/guides.test.ts
Normal file
59
tests/engine/vue/input/guides.test.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { createEditor } from '@open-pencil/core/editor'
|
||||
|
||||
import { createGuideInput } from '#vue/canvas/guides/input'
|
||||
import type { DragState } from '#vue/shared/input/types'
|
||||
|
||||
function setup() {
|
||||
const editor = createEditor()
|
||||
Object.assign(editor.state, { showRulers: true })
|
||||
let drag: DragState | null = null
|
||||
const input = createGuideInput({
|
||||
canvasRef: ref<HTMLCanvasElement | null>(null),
|
||||
editor,
|
||||
canvasToLocal: (cx, cy) => ({ lx: cx, ly: cy }),
|
||||
setDrag: (next) => {
|
||||
drag = next
|
||||
},
|
||||
setCursor: () => undefined
|
||||
})
|
||||
return { editor, input, getDrag: () => drag }
|
||||
}
|
||||
|
||||
describe('guide canvas input', () => {
|
||||
test('does not create a guide from a ruler click without movement', () => {
|
||||
const { editor, input, getDrag } = setup()
|
||||
expect(input.tryStartFromRuler(100, 5, 100, 5)).toBe(true)
|
||||
const drag = getDrag()
|
||||
expect(drag?.type).toBe('guide')
|
||||
if (drag?.type === 'guide') input.finish(drag)
|
||||
expect(editor.graph.getNode(editor.state.currentPageId)?.guides).toEqual([])
|
||||
})
|
||||
|
||||
test('publishes live preview after the drag threshold and commits on release', () => {
|
||||
const { editor, input, getDrag } = setup()
|
||||
input.tryStartFromRuler(100, 5, 100, 5)
|
||||
const drag = getDrag()
|
||||
if (drag?.type !== 'guide') throw new Error('Expected guide drag')
|
||||
|
||||
input.handleMove(drag, 100, 40, 100, 40)
|
||||
expect(editor.state.guides.preview).toMatchObject({ axis: 'y', position: 40 })
|
||||
expect(editor.graph.getNode(editor.state.currentPageId)?.guides).toEqual([])
|
||||
|
||||
input.finish(drag)
|
||||
expect(editor.graph.getNode(editor.state.currentPageId)?.guides[0]).toMatchObject({
|
||||
axis: 'y',
|
||||
position: 40
|
||||
})
|
||||
})
|
||||
|
||||
test('ruler hover takes precedence over an intersecting existing guide', () => {
|
||||
const { editor, input } = setup()
|
||||
editor.addGuide(editor.state.currentPageId, 'x', 100)
|
||||
expect(input.updateHover(100, 5)).toBe('ns-resize')
|
||||
expect(editor.state.guides.hovered).toBeNull()
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue