mirror of
https://github.com/ZSeven-W/openpencil.git
synced 2026-06-01 03:14:29 +07:00
feat(canvas): match hover outline to selection style for components and instances
hover outlines now use purple solid for reusable components and #9281f7 dashed for instances, consistent with their selection border styling
This commit is contained in:
parent
009ae2abad
commit
a93a6fa515
1 changed files with 38 additions and 4 deletions
|
|
@ -1,12 +1,29 @@
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useCanvasStore } from '@/stores/canvas-store'
|
import { useCanvasStore } from '@/stores/canvas-store'
|
||||||
|
import { useDocumentStore } from '@/stores/document-store'
|
||||||
import type { FabricObjectWithPenId } from './canvas-object-factory'
|
import type { FabricObjectWithPenId } from './canvas-object-factory'
|
||||||
import { resolveTargetAtDepth, getChildIds } from './selection-context'
|
import { resolveTargetAtDepth, getChildIds } from './selection-context'
|
||||||
|
import { COMPONENT_COLOR, INSTANCE_COLOR } from './canvas-constants'
|
||||||
|
import type { PenNode } from '@/types/pen'
|
||||||
|
|
||||||
const HOVER_COLOR = '#3b82f6'
|
const HOVER_COLOR = '#3b82f6'
|
||||||
const HOVER_LINE_WIDTH = 1.5
|
const HOVER_LINE_WIDTH = 1.5
|
||||||
const CHILD_DASH = [4, 4]
|
const CHILD_DASH = [4, 4]
|
||||||
|
|
||||||
|
function collectReusableIds(nodes: PenNode[], result: Set<string>) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if ('reusable' in node && node.reusable === true) result.add(node.id)
|
||||||
|
if ('children' in node && node.children) collectReusableIds(node.children, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectInstanceIds(nodes: PenNode[], result: Set<string>) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.type === 'ref') result.add(node.id)
|
||||||
|
if ('children' in node && node.children) collectInstanceIds(node.children, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useCanvasHover() {
|
export function useCanvasHover() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
|
|
@ -66,6 +83,12 @@ export function useCanvasHover() {
|
||||||
const zoom = vpt[0]
|
const zoom = vpt[0]
|
||||||
const dpr = el.width / el.offsetWidth
|
const dpr = el.width / el.offsetWidth
|
||||||
|
|
||||||
|
const docChildren = useDocumentStore.getState().document.children
|
||||||
|
const reusableIds = new Set<string>()
|
||||||
|
const instanceIds = new Set<string>()
|
||||||
|
collectReusableIds(docChildren, reusableIds)
|
||||||
|
collectInstanceIds(docChildren, instanceIds)
|
||||||
|
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.setTransform(
|
ctx.setTransform(
|
||||||
vpt[0] * dpr, vpt[1] * dpr,
|
vpt[0] * dpr, vpt[1] * dpr,
|
||||||
|
|
@ -76,13 +99,13 @@ export function useCanvasHover() {
|
||||||
// Solid outline on hovered target — skip if already selected
|
// Solid outline on hovered target — skip if already selected
|
||||||
// (Fabric draws its own selection handles)
|
// (Fabric draws its own selection handles)
|
||||||
if (!isSelected) {
|
if (!isSelected) {
|
||||||
drawNodeOutline(ctx, hoveredId, false, zoom)
|
drawNodeOutline(ctx, hoveredId, false, zoom, reusableIds, instanceIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dashed outlines on direct children — always draw on hover
|
// Dashed outlines on direct children — always draw on hover
|
||||||
const childIds = getChildIds(hoveredId)
|
const childIds = getChildIds(hoveredId)
|
||||||
for (const childId of childIds) {
|
for (const childId of childIds) {
|
||||||
drawNodeOutline(ctx, childId, true, zoom)
|
drawNodeOutline(ctx, childId, true, zoom, reusableIds, instanceIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
|
|
@ -93,6 +116,8 @@ export function useCanvasHover() {
|
||||||
nodeId: string,
|
nodeId: string,
|
||||||
dashed: boolean,
|
dashed: boolean,
|
||||||
zoom: number,
|
zoom: number,
|
||||||
|
reusableIds: Set<string>,
|
||||||
|
instanceIds: Set<string>,
|
||||||
) {
|
) {
|
||||||
const objects = canvas!.getObjects() as FabricObjectWithPenId[]
|
const objects = canvas!.getObjects() as FabricObjectWithPenId[]
|
||||||
const obj = objects.find((o) => o.penNodeId === nodeId)
|
const obj = objects.find((o) => o.penNodeId === nodeId)
|
||||||
|
|
@ -104,6 +129,9 @@ export function useCanvasHover() {
|
||||||
const h = (obj.height ?? 0) * (obj.scaleY ?? 1)
|
const h = (obj.height ?? 0) * (obj.scaleY ?? 1)
|
||||||
const angle = obj.angle ?? 0
|
const angle = obj.angle ?? 0
|
||||||
|
|
||||||
|
const isReusable = reusableIds.has(nodeId)
|
||||||
|
const isInstance = instanceIds.has(nodeId)
|
||||||
|
|
||||||
ctx.save()
|
ctx.save()
|
||||||
|
|
||||||
if (angle !== 0) {
|
if (angle !== 0) {
|
||||||
|
|
@ -114,9 +142,15 @@ export function useCanvasHover() {
|
||||||
ctx.translate(-cx, -cy)
|
ctx.translate(-cx, -cy)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.strokeStyle = HOVER_COLOR
|
ctx.strokeStyle = isReusable
|
||||||
|
? COMPONENT_COLOR
|
||||||
|
: isInstance
|
||||||
|
? INSTANCE_COLOR
|
||||||
|
: HOVER_COLOR
|
||||||
ctx.lineWidth = HOVER_LINE_WIDTH / zoom
|
ctx.lineWidth = HOVER_LINE_WIDTH / zoom
|
||||||
if (dashed) {
|
if (isInstance) {
|
||||||
|
ctx.setLineDash(CHILD_DASH.map((d) => d / zoom))
|
||||||
|
} else if (dashed) {
|
||||||
ctx.setLineDash(CHILD_DASH.map((d) => d / zoom))
|
ctx.setLineDash(CHILD_DASH.map((d) => d / zoom))
|
||||||
} else {
|
} else {
|
||||||
ctx.setLineDash([])
|
ctx.setLineDash([])
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue