main
parent
a907f2781c
commit
2f32d1a166
|
@ -21,6 +21,11 @@ const drawShape = (ctx, shape) => {
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
} else if (shape.type === 'image' && shape.image) {
|
} else if (shape.type === 'image' && shape.image) {
|
||||||
ctx.drawImage(shape.image, shape.x, shape.y, shape.width, shape.height);
|
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 [selectedIndex, setSelectedIndex] = useState(null);
|
||||||
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
||||||
const fileInputRef = useRef(null);
|
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
|
// Resize canvas on window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -73,12 +81,32 @@ const DrawingCanvas = () => {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.strokeStyle = 'red';
|
ctx.strokeStyle = 'red';
|
||||||
ctx.lineWidth = 2;
|
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);
|
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') {
|
} else if (shape.type === 'circle') {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
|
ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
|
||||||
ctx.stroke();
|
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();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
@ -108,6 +136,13 @@ const DrawingCanvas = () => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { x, y } = getPos(e);
|
const { x, y } = getPos(e);
|
||||||
|
|
||||||
|
if (currentTool === 'text') {
|
||||||
|
setTextPosition({ x, y });
|
||||||
|
setIsTextInputVisible(true);
|
||||||
|
setTextInput('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentTool === 'select') {
|
if (currentTool === 'select') {
|
||||||
// Find the topmost shape under the pointer
|
// Find the topmost shape under the pointer
|
||||||
for (let i = shapes.length - 1; i >= 0; i--) {
|
for (let i = shapes.length - 1; i >= 0; i--) {
|
||||||
|
@ -299,6 +334,20 @@ const DrawingCanvas = () => {
|
||||||
link.click();
|
link.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCursor = () => {
|
||||||
|
switch (currentTool) {
|
||||||
|
case 'pencil':
|
||||||
|
return 'crosshair';
|
||||||
|
case 'rectangle':
|
||||||
|
case 'circle':
|
||||||
|
return 'crosshair';
|
||||||
|
case 'select':
|
||||||
|
return 'move';
|
||||||
|
default:
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="drawing-container">
|
<div className="drawing-container">
|
||||||
<div className="toolbar">
|
<div className="toolbar">
|
||||||
|
@ -306,6 +355,7 @@ const DrawingCanvas = () => {
|
||||||
<button onClick={() => setCurrentTool('rectangle')} className={`tool-button ${currentTool === 'rectangle' ? 'active' : ''}`}>🟨</button>
|
<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('circle')} className={`tool-button ${currentTool === 'circle' ? 'active' : ''}`}>⭕</button>
|
||||||
<button onClick={() => setCurrentTool('select')} className={`tool-button ${currentTool === 'select' ? '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={handleUndo} className="tool-button" disabled={history.length === 0}>↺</button>
|
||||||
<button onClick={handleRedo} className="tool-button" disabled={undoHistory.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>
|
<button onClick={() => fileInputRef.current.click()} className="tool-button">🖼️ Insert Image</button>
|
||||||
|
@ -330,10 +380,55 @@ const DrawingCanvas = () => {
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
className="drawing-canvas"
|
className="drawing-canvas"
|
||||||
|
style={{ cursor: getCursor() }}
|
||||||
onMouseDown={handlePointerDown}
|
onMouseDown={handlePointerDown}
|
||||||
onMouseMove={handlePointerMove}
|
onMouseMove={handlePointerMove}
|
||||||
onMouseUp={handlePointerUp}
|
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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue