109 lines
2.8 KiB
TypeScript
109 lines
2.8 KiB
TypeScript
|
|
import { BaseTool } from './base-tool';
|
||
|
|
import type { PointerState } from '@/types';
|
||
|
|
import { useSelectionStore } from '@/store/selection-store';
|
||
|
|
import { useLayerStore } from '@/store/layer-store';
|
||
|
|
import {
|
||
|
|
createRectangularMask,
|
||
|
|
createSelection,
|
||
|
|
combineMasks,
|
||
|
|
} from '@/lib/selection-utils';
|
||
|
|
|
||
|
|
export class RectangularSelectionTool extends BaseTool {
|
||
|
|
private startX = 0;
|
||
|
|
private startY = 0;
|
||
|
|
private currentX = 0;
|
||
|
|
private currentY = 0;
|
||
|
|
private previewCanvas: HTMLCanvasElement | null = null;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super('Rectangular Selection');
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
onPointerMove(pointer: PointerState, ctx: CanvasRenderingContext2D): void {
|
||
|
|
if (!this.isDrawing) return;
|
||
|
|
|
||
|
|
this.currentX = pointer.x;
|
||
|
|
this.currentY = pointer.y;
|
||
|
|
|
||
|
|
// Draw preview rectangle
|
||
|
|
const layer = this.getActiveLayer();
|
||
|
|
if (!layer?.canvas) return;
|
||
|
|
|
||
|
|
ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
|
||
|
|
ctx.save();
|
||
|
|
ctx.strokeStyle = '#000';
|
||
|
|
ctx.lineWidth = 1;
|
||
|
|
ctx.setLineDash([4, 4]);
|
||
|
|
|
||
|
|
const x = Math.min(this.startX, this.currentX);
|
||
|
|
const y = Math.min(this.startY, this.currentY);
|
||
|
|
const w = Math.abs(this.currentX - this.startX);
|
||
|
|
const h = Math.abs(this.currentY - this.startY);
|
||
|
|
|
||
|
|
ctx.strokeRect(x, y, w, h);
|
||
|
|
|
||
|
|
ctx.strokeStyle = '#fff';
|
||
|
|
ctx.lineDashOffset = 4;
|
||
|
|
ctx.strokeRect(x, y, w, h);
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|
||
|
|
|
||
|
|
onPointerUp(): void {
|
||
|
|
if (!this.isDrawing) return;
|
||
|
|
|
||
|
|
const layer = this.getActiveLayer();
|
||
|
|
if (!layer?.canvas) return;
|
||
|
|
|
||
|
|
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);
|
||
|
|
|
||
|
|
if (width > 0 && height > 0) {
|
||
|
|
const { selectionMode, feather, activeSelection } =
|
||
|
|
useSelectionStore.getState();
|
||
|
|
|
||
|
|
const newMask = createRectangularMask(
|
||
|
|
x,
|
||
|
|
y,
|
||
|
|
width,
|
||
|
|
height,
|
||
|
|
layer.canvas.width,
|
||
|
|
layer.canvas.height
|
||
|
|
);
|
||
|
|
|
||
|
|
let finalMask = newMask;
|
||
|
|
|
||
|
|
// Combine with existing selection if needed
|
||
|
|
if (activeSelection && selectionMode !== 'new') {
|
||
|
|
finalMask = combineMasks(activeSelection.mask, newMask, selectionMode);
|
||
|
|
}
|
||
|
|
|
||
|
|
const selection = createSelection(layer.id, finalMask, feather);
|
||
|
|
useSelectionStore.getState().setActiveSelection(selection);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.isDrawing = false;
|
||
|
|
this.isActive = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
getCursor(): string {
|
||
|
|
return 'crosshair';
|
||
|
|
}
|
||
|
|
|
||
|
|
private getActiveLayer() {
|
||
|
|
const { activeLayerId, layers } = useLayerStore.getState();
|
||
|
|
return layers.find((l) => l.id === activeLayerId);
|
||
|
|
}
|
||
|
|
}
|