refactor: layer transform controls

This commit is contained in:
2026-03-31 22:32:53 +08:00
parent 56cabea109
commit ceb2da8b1a
6 changed files with 558 additions and 197 deletions
@@ -0,0 +1,211 @@
import { createMemo } from 'solid-js';
import type { DeckStore, DraggingState } from './deckStore';
import type { LayerConfig } from '../types';
export interface LayerClickInfo {
layerProp: string;
gridX: number;
gridY: number;
}
export interface LayerInteractionHandlers {
onLayerClick: (layerProp: string, e: MouseEvent) => void;
onFrameMouseDown: (action: 'drag' | 'resize-corner' | 'resize-edge', anchor?: 'nw' | 'ne' | 'sw' | 'se', edge?: 'n' | 's' | 'e' | 'w', e?: MouseEvent) => void;
onCardMouseMove: (e: MouseEvent, cardEl: HTMLElement) => void;
onCardMouseUp: () => void;
onCardClick: (e: MouseEvent, cardEl: HTMLElement) => void;
getOverlappingLayers: (gridX: number, gridY: number) => LayerConfig[];
}
export function useLayerInteraction(store: DeckStore): LayerInteractionHandlers {
let clickStack: { gridX: number; gridY: number; layers: string[]; currentIndex: number } | null = null;
const currentLayerConfigs = () =>
store.state.activeSide === 'front'
? store.state.frontLayerConfigs.filter(l => l.visible)
: store.state.backLayerConfigs.filter(l => l.visible);
const calculateGridCoords = (e: MouseEvent, cardEl: HTMLElement) => {
const dims = store.state.dimensions;
if (!dims) return { gridX: 1, gridY: 1 };
const rect = cardEl.getBoundingClientRect();
const mmToPxRatio = rect.width / dims.cardWidth;
const offsetX = (e.clientX - rect.left) / mmToPxRatio;
const offsetY = (e.clientY - rect.top) / mmToPxRatio;
const gridX = Math.floor((offsetX - dims.gridOriginX) / dims.cellWidth) + 1;
const gridY = Math.floor((offsetY - dims.gridOriginY) / dims.cellHeight) + 1;
return {
gridX: Math.max(1, Math.min(dims.gridW, gridX)),
gridY: Math.max(1, Math.min(dims.gridH, gridY))
};
};
const isPointInLayer = (gridX: number, gridY: number, layer: LayerConfig): boolean => {
const orientation = layer.orientation || 'n';
if (orientation === 'e' || orientation === 'w') {
const visualX1 = layer.y1;
const visualX2 = layer.y2;
const visualY1 = layer.x1;
const visualY2 = layer.x2;
return gridX >= visualX1 && gridX <= visualX2 && gridY >= visualY1 && gridY <= visualY2;
}
return gridX >= layer.x1 && gridX <= layer.x2 && gridY >= layer.y1 && gridY <= layer.y2;
};
const getOverlappingLayers = (gridX: number, gridY: number): LayerConfig[] => {
return currentLayerConfigs().filter(layer => isPointInLayer(gridX, gridY, layer));
};
const handleLayerClick = (layerProp: string, e: MouseEvent) => {
e.stopPropagation();
const currentlySelected = store.state.selectedLayer;
if (currentlySelected === layerProp) {
store.actions.setSelectedLayer(null);
} else {
store.actions.setSelectedLayer(layerProp);
}
clickStack = null;
};
const handleCardClick = (e: MouseEvent, cardEl: HTMLElement) => {
if (store.state.draggingState) return;
const { gridX, gridY } = calculateGridCoords(e, cardEl);
const overlapping = getOverlappingLayers(gridX, gridY);
if (overlapping.length === 0) {
store.actions.setSelectedLayer(null);
clickStack = null;
return;
}
const overlappingProps = overlapping.map(l => l.prop);
if (clickStack && clickStack.gridX === gridX && clickStack.gridY === gridY) {
clickStack.currentIndex = (clickStack.currentIndex + 1) % overlappingProps.length;
const nextLayer = overlappingProps[clickStack.currentIndex];
store.actions.setSelectedLayer(nextLayer);
} else {
clickStack = {
gridX,
gridY,
layers: overlappingProps,
currentIndex: 0
};
store.actions.setSelectedLayer(overlappingProps[0]);
}
};
const handleFrameMouseDown = (
action: 'drag' | 'resize-corner' | 'resize-edge',
anchor?: 'nw' | 'ne' | 'sw' | 'se',
edge?: 'n' | 's' | 'e' | 'w',
e?: MouseEvent
) => {
if (!store.state.selectedLayer) return;
if (e) e.stopPropagation();
const layer = currentLayerConfigs().find(l => l.prop === store.state.selectedLayer);
if (!layer) return;
const startX = e?.clientX ?? 0;
const startY = e?.clientY ?? 0;
const draggingState: DraggingState = {
layer: store.state.selectedLayer,
action,
anchor,
edge,
startX,
startY,
startGrid: {
x1: layer.x1,
y1: layer.y1,
x2: layer.x2,
y2: layer.y2
}
};
store.actions.setDraggingState(draggingState);
};
const handleCardMouseMove = (e: MouseEvent, cardEl: HTMLElement) => {
const dragging = store.state.draggingState;
if (!dragging) return;
const dims = store.state.dimensions;
if (!dims) return;
const rect = cardEl.getBoundingClientRect();
const mmToPxRatio = rect.width / dims.cardWidth;
const deltaX = e.clientX - dragging.startX;
const deltaY = e.clientY - dragging.startY;
const deltaMmX = deltaX / mmToPxRatio;
const deltaMmY = deltaY / mmToPxRatio;
const deltaGridX = deltaMmX / dims.cellWidth;
const deltaGridY = deltaMmY / dims.cellHeight;
const layer = currentLayerConfigs().find(l => l.prop === dragging.layer);
if (!layer) return;
const orientation = layer.orientation || 'n';
const startGrid = dragging.startGrid;
if (dragging.action === 'drag') {
const moveDx = Math.round(deltaGridX);
const moveDy = Math.round(deltaGridY);
store.actions.moveLayer(dragging.layer, moveDx, moveDy, startGrid);
} else if (dragging.action === 'resize-corner' && dragging.anchor) {
store.actions.resizeLayerCorner(dragging.layer, dragging.anchor, Math.round(deltaGridX), Math.round(deltaGridY), startGrid);
} else if (dragging.action === 'resize-edge' && dragging.edge) {
const delta = orientation === 'e' || orientation === 'w'
? (dragging.edge === 'n' || dragging.edge === 's' ? Math.round(deltaGridX) : Math.round(deltaGridY))
: (dragging.edge === 'n' || dragging.edge === 's' ? Math.round(deltaGridY) : Math.round(deltaGridX));
store.actions.resizeLayerEdge(dragging.layer, dragging.edge, delta, startGrid);
}
};
const handleCardMouseUp = () => {
if (store.state.draggingState) {
const layer = store.state.draggingState.layer;
const dragging = store.state.draggingState;
store.actions.setDraggingState(null);
const newLayerConfig = currentLayerConfigs().find(l => l.prop === layer);
if (newLayerConfig && dragging.startGrid) {
store.actions.setDraggingState({
...dragging,
startGrid: {
x1: newLayerConfig.x1,
y1: newLayerConfig.y1,
x2: newLayerConfig.x2,
y2: newLayerConfig.y2
}
});
store.actions.setDraggingState(null);
}
}
};
return {
onLayerClick: handleLayerClick,
onFrameMouseDown: handleFrameMouseDown,
onCardMouseMove: handleCardMouseMove,
onCardMouseUp: handleCardMouseUp,
onCardClick: handleCardClick,
getOverlappingLayers
};
}