144 lines
3.8 KiB
TypeScript
144 lines
3.8 KiB
TypeScript
|
|
import { BaseTool } from './base-tool';
|
||
|
|
import type { PointerState } from '@/types';
|
||
|
|
import { useLayerStore } from '@/store/layer-store';
|
||
|
|
import { useShapeStore } from '@/store/shape-store';
|
||
|
|
import { useHistoryStore } from '@/store/history-store';
|
||
|
|
import { DrawCommand } from '@/core/commands/draw-command';
|
||
|
|
import {
|
||
|
|
drawRectangle,
|
||
|
|
drawEllipse,
|
||
|
|
drawLine,
|
||
|
|
drawArrow,
|
||
|
|
drawPolygon,
|
||
|
|
drawStar,
|
||
|
|
drawTriangle,
|
||
|
|
} from '@/lib/shape-utils';
|
||
|
|
|
||
|
|
export class ShapeTool extends BaseTool {
|
||
|
|
private startX = 0;
|
||
|
|
private startY = 0;
|
||
|
|
private currentX = 0;
|
||
|
|
private currentY = 0;
|
||
|
|
private drawCommand: DrawCommand | null = null;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super('Shape');
|
||
|
|
}
|
||
|
|
|
||
|
|
onPointerDown(pointer: PointerState): void {
|
||
|
|
this.isActive = true;
|
||
|
|
this.isDrawing = true;
|
||
|
|
this.startX = pointer.x;
|
||
|
|
this.startY = pointer.y;
|
||
|
|
this.currentX = pointer.x;
|
||
|
|
this.currentY = pointer.y;
|
||
|
|
|
||
|
|
const layer = this.getActiveLayer();
|
||
|
|
if (!layer) return;
|
||
|
|
|
||
|
|
// Create draw command for history
|
||
|
|
this.drawCommand = new DrawCommand(layer.id, 'Draw Shape');
|
||
|
|
}
|
||
|
|
|
||
|
|
onPointerMove(pointer: PointerState, ctx: CanvasRenderingContext2D): void {
|
||
|
|
if (!this.isDrawing) return;
|
||
|
|
|
||
|
|
this.currentX = pointer.x;
|
||
|
|
this.currentY = pointer.y;
|
||
|
|
|
||
|
|
const layer = this.getActiveLayer();
|
||
|
|
if (!layer?.canvas) return;
|
||
|
|
|
||
|
|
const layerCtx = layer.canvas.getContext('2d');
|
||
|
|
if (!layerCtx) return;
|
||
|
|
|
||
|
|
// Clear and redraw from saved state
|
||
|
|
if (this.drawCommand) {
|
||
|
|
const beforeCanvas = (this.drawCommand as any).beforeCanvas;
|
||
|
|
if (beforeCanvas) {
|
||
|
|
layerCtx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
|
||
|
|
layerCtx.drawImage(beforeCanvas, 0, 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Draw preview shape
|
||
|
|
this.drawShape(layerCtx);
|
||
|
|
}
|
||
|
|
|
||
|
|
onPointerUp(): void {
|
||
|
|
if (!this.isDrawing) return;
|
||
|
|
|
||
|
|
const layer = this.getActiveLayer();
|
||
|
|
if (layer?.canvas) {
|
||
|
|
const ctx = layer.canvas.getContext('2d');
|
||
|
|
if (ctx) {
|
||
|
|
// Final draw
|
||
|
|
this.drawShape(ctx);
|
||
|
|
|
||
|
|
// Capture after state and add to history
|
||
|
|
if (this.drawCommand) {
|
||
|
|
this.drawCommand.captureAfterState();
|
||
|
|
const { executeCommand } = useHistoryStore.getState();
|
||
|
|
executeCommand(this.drawCommand);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
this.isDrawing = false;
|
||
|
|
this.isActive = false;
|
||
|
|
this.drawCommand = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
getCursor(): string {
|
||
|
|
return 'crosshair';
|
||
|
|
}
|
||
|
|
|
||
|
|
private drawShape(ctx: CanvasRenderingContext2D): void {
|
||
|
|
const { settings } = useShapeStore.getState();
|
||
|
|
|
||
|
|
const x = Math.min(this.startX, this.currentX);
|
||
|
|
const y = Math.min(this.startY, this.currentY);
|
||
|
|
const width = Math.abs(this.currentX - this.startX);
|
||
|
|
const height = Math.abs(this.currentY - this.startY);
|
||
|
|
|
||
|
|
const cx = (this.startX + this.currentX) / 2;
|
||
|
|
const cy = (this.startY + this.currentY) / 2;
|
||
|
|
const radius = Math.sqrt(Math.pow(width / 2, 2) + Math.pow(height / 2, 2));
|
||
|
|
|
||
|
|
switch (settings.type) {
|
||
|
|
case 'rectangle':
|
||
|
|
drawRectangle(ctx, x, y, width, height, settings);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'ellipse':
|
||
|
|
drawEllipse(ctx, cx, cy, width / 2, height / 2, settings);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'line':
|
||
|
|
drawLine(ctx, this.startX, this.startY, this.currentX, this.currentY, settings);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'arrow':
|
||
|
|
drawArrow(ctx, this.startX, this.startY, this.currentX, this.currentY, settings);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'polygon':
|
||
|
|
drawPolygon(ctx, cx, cy, radius, settings.sides, settings);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'star':
|
||
|
|
drawStar(ctx, cx, cy, radius, settings.sides, settings.innerRadius, settings);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'triangle':
|
||
|
|
drawTriangle(ctx, x, y, width, height, settings);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private getActiveLayer() {
|
||
|
|
const { activeLayerId, layers } = useLayerStore.getState();
|
||
|
|
return layers.find((l) => l.id === activeLayerId);
|
||
|
|
}
|
||
|
|
}
|