Skip to content

Commit e755beb

Browse files
feat: add brush tool options
1 parent 93c7014 commit e755beb

File tree

5 files changed

+234
-40
lines changed

5 files changed

+234
-40
lines changed

client/package-lock.json

Lines changed: 8 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"socket.io-client": "^4.8.1",
2525
"sonner": "^2.0.7",
2626
"tailwind-merge": "^3.3.1",
27-
"tailwindcss": "^4.1.14"
27+
"tailwindcss": "^4.1.14",
28+
"tinycolor2": "^1.6.0"
2829
},
2930
"devDependencies": {
3031
"@eslint/js": "^9.36.0",

client/src/components/Canvas.jsx

Lines changed: 123 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ColorPicker } from "./ColorPicker";
44
import { StrokeControl } from "./StrokeControl";
55
import { toast } from "sonner";
66
import { io } from "socket.io-client";
7+
import tinycolor from "tinycolor2";
78

89
export const Canvas = () => {
910
const canvasRef = useRef(null);
@@ -31,9 +32,7 @@ export const Canvas = () => {
3132
const [socket, setSocket] = useState(null);
3233
const [isModalOpen, setIsModalOpen] = useState(false);
3334

34-
const [isLoggedIn, setIsLoggedIn] = useState(
35-
!!localStorage.getItem("token")
36-
);
35+
const [isLoggedIn, setIsLoggedIn] = useState(!!localStorage.getItem("token"));
3736

3837
const handleLogout = async () => {
3938
// ... (existing logout logic - unchanged)
@@ -257,9 +256,15 @@ export const Canvas = () => {
257256
startPoint.current = { x, y };
258257
setIsDrawing(true);
259258

260-
if (activeTool === "pen" || activeTool === "eraser") {
259+
// Begin drawing for pen, eraser, or brush tools
260+
if (
261+
activeTool === "pen" ||
262+
activeTool === "eraser" ||
263+
activeTool.startsWith("brush-")
264+
) {
261265
ctx.beginPath();
262266
ctx.moveTo(x, y);
267+
263268
if (joined && socket)
264269
socket.emit("draw", {
265270
roomId,
@@ -272,28 +277,28 @@ export const Canvas = () => {
272277
});
273278
}
274279

275-
// Save snapshot for preview tools
280+
// Snapshot for line/rectangle previews
276281
if (activeTool === "line" || activeTool === "rectangle") {
277-
// Snapshot must be taken from the *un-transformed* context
278-
ctx.restore(); // Restore *before* getImageData
282+
ctx.restore(); // restore before snapshot
279283
snapshot.current = ctx.getImageData(0, 0, canvas.width, canvas.height);
280-
// Re-save to apply transform again for next draw
281284
ctx.save();
282285
ctx.setTransform(1, 0, 0, 1, 0, 0);
283286
ctx.translate(offset.x, offset.y);
284287
ctx.scale(scale, scale);
285288
}
289+
286290
ctx.restore(); // Restore transform
287291
};
288292

289293
const draw = (e) => {
290-
if (!isPointerDown) return;
294+
if (!isPointerDown || !isDrawing) return;
291295

292296
const canvas = canvasRef.current;
293297
const ctx = canvas?.getContext("2d");
294298
if (!ctx) return;
295299

296300
// Apply transform
301+
297302
ctx.save();
298303
ctx.setTransform(1, 0, 0, 1, 0, 0);
299304
ctx.translate(offset.x, offset.y);
@@ -311,25 +316,121 @@ export const Canvas = () => {
311316
return;
312317
}
313318

314-
if (!isDrawing) {
315-
ctx.restore();
316-
return;
317-
}
319+
// --- Brush / Pen / Eraser Tools ---
320+
if (
321+
activeTool === "pen" ||
322+
activeTool === "eraser" ||
323+
activeTool.startsWith("brush-")
324+
) {
325+
ctx.lineCap = "round";
326+
ctx.lineJoin = "round";
327+
ctx.setLineDash([]);
328+
ctx.shadowBlur = 0;
329+
ctx.globalAlpha = 1;
330+
331+
let color = activeColor;
332+
let width = strokeWidth;
333+
334+
if (activeTool === "eraser") {
335+
color = "#ffffff";
336+
width = strokeWidth * 3;
337+
}
338+
339+
if (activeTool.startsWith("brush-")) {
340+
const brush = activeTool.split("-")[1];
341+
switch (brush) {
342+
case "dashed": {
343+
const dashLength = strokeWidth * 4;
344+
const gapLength = strokeWidth * 2.5;
345+
ctx.setLineDash([dashLength, gapLength]);
346+
ctx.lineCap = "round";
347+
ctx.lineJoin = "round";
348+
349+
ctx.strokeStyle = activeColor;
350+
ctx.lineWidth = strokeWidth;
351+
352+
const jitterX = x + (Math.random() - 0.5) * 0.5;
353+
const jitterY = y + (Math.random() - 0.5) * 0.5;
354+
ctx.lineTo(jitterX, jitterY);
355+
ctx.stroke();
356+
break;
357+
}
358+
359+
case "paint":
360+
ctx.shadowColor = color;
361+
ctx.shadowBlur = 12;
362+
ctx.globalAlpha = 0.5 + Math.random() * 0.2;
363+
width = strokeWidth * 2;
364+
break;
365+
case "crayon":
366+
ctx.globalAlpha = 0.8;
367+
ctx.shadowBlur = 5;
368+
for (let i = 0; i < 4; i++) {
369+
const jitterX = x + (Math.random() - 0.5) * 1.5;
370+
const jitterY = y + (Math.random() - 0.5) * 1.5;
371+
ctx.lineTo(jitterX, jitterY);
372+
}
373+
break;
374+
case "oil-pastel": {
375+
console.log("this is oil")
376+
ctx.setLineDash([]);
377+
ctx.lineCap = "round";
378+
ctx.lineJoin = "round";
379+
380+
const steps = 10;
381+
for (let i = 0; i < steps; i++) {
382+
const jitterX = x + (Math.random() - 0.5) * 5;
383+
const jitterY = y + (Math.random() - 0.5) * 5;
384+
const jitterWidth = strokeWidth * (0.8 + Math.random() * 0.6);
385+
const opacity = 0.1 + Math.random() * 0.15;
386+
const color = tinycolor(activeColor)
387+
.brighten((Math.random() - 0.5) * 8)
388+
.setAlpha(opacity)
389+
.toRgbString();
390+
391+
ctx.strokeStyle = color;
392+
ctx.lineWidth = jitterWidth;
393+
394+
ctx.beginPath();
395+
ctx.moveTo(
396+
startPoint.current.x + (Math.random() - 0.5) * 3,
397+
startPoint.current.y + (Math.random() - 0.5) * 3
398+
);
399+
ctx.lineTo(jitterX, jitterY);
400+
ctx.stroke();
401+
}
402+
ctx.shadowColor = activeColor;
403+
ctx.shadowBlur = 2;
404+
ctx.globalAlpha = 0.7;
405+
ctx.lineWidth = strokeWidth * 1.5;
406+
ctx.beginPath();
407+
ctx.moveTo(startPoint.current.x, startPoint.current.y);
408+
ctx.lineTo(x, y);
409+
ctx.stroke();
410+
ctx.shadowBlur = 0;
411+
ctx.globalAlpha = 1;
412+
break;
413+
}
414+
415+
default:
416+
ctx.setLineDash([]);
417+
}
418+
}
419+
420+
ctx.strokeStyle = color;
421+
ctx.lineWidth = width;
318422

319-
// --- Handle Drawing ---
320-
if (activeTool === "pen" || activeTool === "eraser") {
321-
ctx.strokeStyle = activeTool === "eraser" ? "#ffffff" : activeColor;
322-
ctx.lineWidth = activeTool === "eraser" ? strokeWidth * 3 : strokeWidth;
323423
ctx.lineTo(x, y);
324424
ctx.stroke();
425+
325426
if (joined && socket)
326427
socket.emit("draw", {
327428
roomId,
328-
x, // Send WORLD coordinates
329-
y, // Send WORLD coordinates
429+
x,
430+
y,
330431
type: "move",
331-
color: activeColor,
332-
width: strokeWidth,
432+
color,
433+
width,
333434
tool: activeTool,
334435
});
335436
} else if (activeTool === "line" || activeTool === "rectangle") {
@@ -583,4 +684,4 @@ export const Canvas = () => {
583684
</div>
584685
</div>
585686
);
586-
};
687+
};

0 commit comments

Comments
 (0)