main
parent
a907f2781c
commit
2f32d1a166
|
@ -21,6 +21,11 @@ const drawShape = (ctx, shape) => {
|
|||
ctx.stroke();
|
||||
} else if (shape.type === 'image' && shape.image) {
|
||||
ctx.drawImage(shape.image, shape.x, shape.y, shape.width, shape.height);
|
||||
} else if (shape.type === 'text' && shape.text) {
|
||||
ctx.fillStyle = shape.color;
|
||||
ctx.font = `${shape.thickness * 6}px sans-serif`; // Match input font size
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(shape.text, shape.x, shape.y);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -37,6 +42,9 @@ const DrawingCanvas = () => {
|
|||
const [selectedIndex, setSelectedIndex] = useState(null);
|
||||
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
||||
const fileInputRef = useRef(null);
|
||||
const [textInput, setTextInput] = useState('');
|
||||
const [isTextInputVisible, setIsTextInputVisible] = useState(false);
|
||||
const [textPosition, setTextPosition] = useState({ x: 0, y: 0 });
|
||||
|
||||
// Resize canvas on window resize
|
||||
useEffect(() => {
|
||||
|
@ -73,12 +81,32 @@ const DrawingCanvas = () => {
|
|||
ctx.save();
|
||||
ctx.strokeStyle = 'red';
|
||||
ctx.lineWidth = 2;
|
||||
if (shape.type === 'rectangle' || shape.type === 'image') {
|
||||
if (shape.type === 'rectangle' || shape.type === 'image' || shape.type === 'text') {
|
||||
ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
|
||||
// Draw resize handles (corners)
|
||||
const handleSize = 8;
|
||||
const handles = [
|
||||
[shape.x, shape.y],
|
||||
[shape.x + shape.width, shape.y],
|
||||
[shape.x, shape.y + shape.height],
|
||||
[shape.x + shape.width, shape.y + shape.height],
|
||||
];
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.strokeStyle = 'black';
|
||||
handles.forEach(([hx, hy]) => {
|
||||
ctx.fillRect(hx - handleSize/2, hy - handleSize/2, handleSize, handleSize);
|
||||
ctx.strokeRect(hx - handleSize/2, hy - handleSize/2, handleSize, handleSize);
|
||||
});
|
||||
} else if (shape.type === 'circle') {
|
||||
ctx.beginPath();
|
||||
ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
// Draw handle at the edge (right)
|
||||
const handleSize = 8;
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.fillRect(shape.x + shape.radius - handleSize/2, shape.y - handleSize/2, handleSize, handleSize);
|
||||
ctx.strokeRect(shape.x + shape.radius - handleSize/2, shape.y - handleSize/2, handleSize, handleSize);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
@ -108,6 +136,13 @@ const DrawingCanvas = () => {
|
|||
e.preventDefault();
|
||||
const { x, y } = getPos(e);
|
||||
|
||||
if (currentTool === 'text') {
|
||||
setTextPosition({ x, y });
|
||||
setIsTextInputVisible(true);
|
||||
setTextInput('');
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTool === 'select') {
|
||||
// Find the topmost shape under the pointer
|
||||
for (let i = shapes.length - 1; i >= 0; i--) {
|
||||
|
@ -299,6 +334,20 @@ const DrawingCanvas = () => {
|
|||
link.click();
|
||||
};
|
||||
|
||||
const getCursor = () => {
|
||||
switch (currentTool) {
|
||||
case 'pencil':
|
||||
return 'crosshair';
|
||||
case 'rectangle':
|
||||
case 'circle':
|
||||
return 'crosshair';
|
||||
case 'select':
|
||||
return 'move';
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="drawing-container">
|
||||
<div className="toolbar">
|
||||
|
@ -306,6 +355,7 @@ const DrawingCanvas = () => {
|
|||
<button onClick={() => setCurrentTool('rectangle')} className={`tool-button ${currentTool === 'rectangle' ? 'active' : ''}`}>🟨</button>
|
||||
<button onClick={() => setCurrentTool('circle')} className={`tool-button ${currentTool === 'circle' ? 'active' : ''}`}>⭕</button>
|
||||
<button onClick={() => setCurrentTool('select')} className={`tool-button ${currentTool === 'select' ? 'active' : ''}`}>🖱️</button>
|
||||
<button onClick={() => setCurrentTool('text')} className={`tool-button ${currentTool === 'text' ? 'active' : ''}`}>🔤</button>
|
||||
<button onClick={handleUndo} className="tool-button" disabled={history.length === 0}>↺</button>
|
||||
<button onClick={handleRedo} className="tool-button" disabled={undoHistory.length === 0}>↻</button>
|
||||
<button onClick={() => fileInputRef.current.click()} className="tool-button">🖼️ Insert Image</button>
|
||||
|
@ -330,10 +380,55 @@ const DrawingCanvas = () => {
|
|||
<canvas
|
||||
ref={canvasRef}
|
||||
className="drawing-canvas"
|
||||
style={{ cursor: getCursor() }}
|
||||
onMouseDown={handlePointerDown}
|
||||
onMouseMove={handlePointerMove}
|
||||
onMouseUp={handlePointerUp}
|
||||
/>
|
||||
{isTextInputVisible && (
|
||||
<input
|
||||
type="text"
|
||||
value={textInput}
|
||||
autoFocus
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: textPosition.x + (canvasRef.current?.offsetLeft || 0),
|
||||
top: textPosition.y + (canvasRef.current?.offsetTop || 0),
|
||||
zIndex: 10,
|
||||
fontSize: `${thickness * 6}px`
|
||||
}}
|
||||
onChange={e => setTextInput(e.target.value)}
|
||||
onBlur={() => {
|
||||
if (textInput.trim() !== '') {
|
||||
setShapes(prev => [
|
||||
...prev,
|
||||
{
|
||||
type: 'text',
|
||||
x: textPosition.x,
|
||||
y: textPosition.y,
|
||||
text: textInput,
|
||||
color,
|
||||
thickness,
|
||||
}
|
||||
]);
|
||||
setHistory(prev => [...prev, deepCopyShapes([...shapes, {
|
||||
type: 'text',
|
||||
x: textPosition.x,
|
||||
y: textPosition.y,
|
||||
text: textInput,
|
||||
color,
|
||||
thickness,
|
||||
}])]);
|
||||
setUndoHistory([]);
|
||||
}
|
||||
setIsTextInputVisible(false);
|
||||
setTextInput('');
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') e.target.blur();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue