mirror of
https://github.com/ZSeven-W/openpencil.git
synced 2026-05-31 19:04:29 +07:00
feat(canvas): enhance rotation controls and cursor handling
- Update rotation cursor SVG for improved design consistency and clarity. - Refactor rotation control offsets and sizes for better maintainability. - Implement a patch for `_setCursorFromEvent` to ensure rotation cursors display correctly when hovering over active object controls. - Integrate rotation cursor handler setup in the fabric canvas initialization.
This commit is contained in:
parent
afdec6c2d6
commit
d5d33e3e91
3 changed files with 55 additions and 8 deletions
|
|
@ -1,7 +1,9 @@
|
|||
import * as fabric from 'fabric'
|
||||
|
||||
function rotationCursorSvg(angleDeg: number): string {
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g transform="rotate(${angleDeg} 12 12)"><path d="M12 5a7 7 0 0 1 7 7" fill="none" stroke="%23111" stroke-width="1.5" stroke-linecap="round"/><polyline points="16 4 19 7.5 15 8" fill="none" stroke="%23111" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></g></svg>`
|
||||
// 270° clockwise arc (radius 4) with small arrowhead — Figma-style minimal.
|
||||
// Uses single quotes so the SVG doesn't break the outer CSS url("...").
|
||||
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><g transform='rotate(${angleDeg} 12 12)' fill='none' stroke='%23333' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><path d='M16 12 A4 4 0 1 1 12 8'/><polyline points='10 6.5 12 8 10 9.5'/></g></svg>`
|
||||
return `url("data:image/svg+xml,${svg}") 12 12, crosshair`
|
||||
}
|
||||
|
||||
|
|
@ -12,11 +14,14 @@ const CURSORS = {
|
|||
bl: rotationCursorSvg(225),
|
||||
}
|
||||
|
||||
const ROTATION_OFFSET = 14
|
||||
const ROTATION_SIZE = 14
|
||||
|
||||
const ROTATION_POSITIONS = [
|
||||
{ key: 'rtl', x: -0.5, y: -0.5, ox: -10, oy: -10, cursor: CURSORS.tl },
|
||||
{ key: 'rtr', x: 0.5, y: -0.5, ox: 10, oy: -10, cursor: CURSORS.tr },
|
||||
{ key: 'rbr', x: 0.5, y: 0.5, ox: 10, oy: 10, cursor: CURSORS.br },
|
||||
{ key: 'rbl', x: -0.5, y: 0.5, ox: -10, oy: 10, cursor: CURSORS.bl },
|
||||
{ key: 'rtl', x: -0.5, y: -0.5, ox: -ROTATION_OFFSET, oy: -ROTATION_OFFSET, cursor: CURSORS.tl },
|
||||
{ key: 'rtr', x: 0.5, y: -0.5, ox: ROTATION_OFFSET, oy: -ROTATION_OFFSET, cursor: CURSORS.tr },
|
||||
{ key: 'rbr', x: 0.5, y: 0.5, ox: ROTATION_OFFSET, oy: ROTATION_OFFSET, cursor: CURSORS.br },
|
||||
{ key: 'rbl', x: -0.5, y: 0.5, ox: -ROTATION_OFFSET, oy: ROTATION_OFFSET, cursor: CURSORS.bl },
|
||||
]
|
||||
|
||||
export function applyRotationControls(obj: fabric.FabricObject) {
|
||||
|
|
@ -26,8 +31,8 @@ export function applyRotationControls(obj: fabric.FabricObject) {
|
|||
y: pos.y,
|
||||
offsetX: pos.ox,
|
||||
offsetY: pos.oy,
|
||||
sizeX: 20,
|
||||
sizeY: 20,
|
||||
sizeX: ROTATION_SIZE,
|
||||
sizeY: ROTATION_SIZE,
|
||||
actionName: 'rotate',
|
||||
actionHandler: fabric.controlsUtils.rotationWithSnapping,
|
||||
cursorStyleHandler: () => pos.cursor,
|
||||
|
|
@ -35,3 +40,43 @@ export function applyRotationControls(obj: fabric.FabricObject) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fabric.js `_setCursorFromEvent` only checks controls on the `target` found
|
||||
* by `findTarget` (the object directly under the mouse). Rotation controls
|
||||
* are offset outside the object boundary, so `findTarget` returns null there
|
||||
* and the rotation cursor never shows.
|
||||
*
|
||||
* Fix: patch `_setCursorFromEvent` to also check the active object's controls
|
||||
* before falling through to the default behavior.
|
||||
*/
|
||||
export function setupRotationCursorHandler(canvas: fabric.Canvas) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const c = canvas as any
|
||||
const original = c._setCursorFromEvent
|
||||
|
||||
c._setCursorFromEvent = function (
|
||||
e: MouseEvent,
|
||||
target: fabric.FabricObject | undefined,
|
||||
) {
|
||||
// Check the active object's rotation controls first
|
||||
const activeObject = this.getActiveObject()
|
||||
if (activeObject) {
|
||||
const pointer = this.getViewportPoint(e)
|
||||
const found = activeObject.findControl(pointer)
|
||||
if (found && found.control.actionName === 'rotate') {
|
||||
this.setCursor(
|
||||
found.control.cursorStyleHandler(
|
||||
e,
|
||||
found.control,
|
||||
activeObject,
|
||||
found.coord,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
// No rotation control hit — fall through to default behavior
|
||||
original.call(this, e, target)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { ToolType } from '@/types/canvas'
|
|||
|
||||
// Precise crosshair cursor (thin +)
|
||||
const CROSSHAIR_CURSOR = (() => {
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><line x1="12" y1="2" x2="12" y2="10" stroke="%23222" stroke-width="1"/><line x1="12" y1="14" x2="12" y2="22" stroke="%23222" stroke-width="1"/><line x1="2" y1="12" x2="10" y2="12" stroke="%23222" stroke-width="1"/><line x1="14" y1="12" x2="22" y2="12" stroke="%23222" stroke-width="1"/></svg>`
|
||||
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><line x1='12' y1='2' x2='12' y2='10' stroke='%23222' stroke-width='1'/><line x1='12' y1='14' x2='12' y2='22' stroke='%23222' stroke-width='1'/><line x1='2' y1='12' x2='10' y2='12' stroke='%23222' stroke-width='1'/><line x1='14' y1='12' x2='22' y2='12' stroke='%23222' stroke-width='1'/></svg>`
|
||||
return `url("data:image/svg+xml,${svg}") 12 12, crosshair`
|
||||
})()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useCanvasStore } from '@/stores/canvas-store'
|
|||
import { useDocumentStore } from '@/stores/document-store'
|
||||
import type { PenNode } from '@/types/pen'
|
||||
import { getCanvasBackground, SELECTION_BLUE, MIN_ZOOM, MAX_ZOOM } from './canvas-constants'
|
||||
import { setupRotationCursorHandler } from './canvas-controls'
|
||||
|
||||
const FIT_PADDING = 64
|
||||
|
||||
|
|
@ -120,6 +121,7 @@ export function useFabricCanvas(
|
|||
canvas.selectionLineWidth = 1
|
||||
|
||||
useCanvasStore.getState().setFabricCanvas(canvas)
|
||||
setupRotationCursorHandler(canvas)
|
||||
canvas.requestRenderAll()
|
||||
|
||||
// Center viewport on the default frame after a tick (sync needs to run first)
|
||||
|
|
|
|||
Loading…
Reference in a new issue