Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 33 additions & 69 deletions src/components/Canvas/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
// Lib
import {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useRef
} from "react";
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
import { parseColor } from "react-aria-components";
import { useShallow } from "zustand/react/shallow";
import useStoreSubscription from "@/state/hooks/useStoreSubscription";
import useStore from "@/state/hooks/useStore";
import useThrottle from "@/state/hooks/useThrottle";
import useCanvasRedrawListener from "@/state/hooks/useCanvasRedrawListener";
import useCanvasRef from "@/state/hooks/useCanvasRef";
import { redrawCanvas } from "@/lib/utils";
import { redrawCanvas, updateVector2 } from "@/lib/utils";
import ElementsStore from "@/state/stores/ElementsStore";
import LayersStore from "@/state/stores/LayersStore";
import ImageElementStore from "@/state/stores/ImageElementStore";
import useStoreContext from "@/state/hooks/useStoreContext";

// Types
import type {
Dispatch,
MouseEvent as ReactMouseEvent,
SetStateAction
} from "react";
import { type Coordinates, CanvasElementPath } from "@/types";
import useStoreContext from "@/state/hooks/useStoreContext";
import { CanvasElementPath, Vector } from "@/types";

type CanvasProps = {
setLoading: Dispatch<SetStateAction<boolean>>;
Expand Down Expand Up @@ -81,10 +75,7 @@ const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(function Canvas(
const canvasRef = useRef<HTMLCanvasElement>(null);
const currentPath2D = useRef<Path2D>(null);
const currentPath = useRef<CanvasElementPath[]>([]);
const initialPosition = useRef<Coordinates>({
x: 0,
y: 0
});
const initialPosition = useRef<Vector<2>>([0, 0]);

// Handler for when the mouse is pressed down on the canvas.
// This should initiate the drawing process.
Expand Down Expand Up @@ -114,7 +105,7 @@ const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(function Canvas(
const floorY = Math.floor(y);

if (!isDrawing.current) {
initialPosition.current = { x: floorX, y: floorY };
updateVector2(initialPosition.current, floorX, floorY);
}
const activeLayer = getActiveLayer();
isDrawing.current = !activeLayer.hidden;
Expand All @@ -123,7 +114,7 @@ const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(function Canvas(
currentPath2D.current = new Path2D();
currentPath2D.current.moveTo(floorX, floorY);
// Save the current path.
currentPath.current.push({ x: floorX, y: floorY, startingPoint: true });
currentPath.current.push([floorX, floorY]);
} else if (mode === "eye_drop") {
// `getPointerPosition` gives us the position in world coordinates,
// but we need the position in canvas coordinates for `getImageData`.
Expand Down Expand Up @@ -175,72 +166,47 @@ const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(function Canvas(
switch (mode) {
case "brush":
case "eraser": {
const lastPoint = currentPath.current[currentPath.current.length - 1];
const midPointX = lastPoint.x + (floorX - lastPoint.x) / 2;
const midPointY = lastPoint.y + (floorY - lastPoint.y) / 2;

currentPath2D.current.quadraticCurveTo(
lastPoint.x,
lastPoint.y,
midPointX,
midPointY
);
const lastPoint = currentPath.current[currentPath.current.length - 1];
const lastPointX = lastPoint[0];
const lastPointY = lastPoint[1];
const midPointX = lastPointX + (floorX - lastPointX) / 2;
const midPointY = lastPointY + (floorY - lastPointY) / 2;

currentPath2D.current.quadraticCurveTo(
lastPointX,
lastPointY,
midPointX,
midPointY
);
ctx.stroke(currentPath2D.current);

currentPath.current.push({
x: floorX,
y: floorY,
startingPoint: false
});
currentPath.current.push([floorX, floorY]);

drawPaperCanvas(ctx, 0, 0);
break;
}

case "shapes": {
const initX = initialPosition.current[0];
const initY = initialPosition.current[1];
const width = x - initX;
const height = y - initY;
if (shape === "circle") {
const width = x - initialPosition.current.x;
const height = y - initialPosition.current.y;

currentPath2D.current.ellipse(
Math.min(
x + Math.abs(width) / 2,
initialPosition.current.x + Math.abs(width) / 2
),
Math.min(
y + Math.abs(height) / 2,
initialPosition.current.y + Math.abs(height) / 2
),
Math.min(x + Math.abs(width) / 2, initX + Math.abs(width) / 2),
Math.min(y + Math.abs(height) / 2, initY + Math.abs(height) / 2),
Math.abs(width) / 2,
Math.abs(height) / 2,
0,
0,
Math.PI * 2
);
} else if (shape === "rectangle") {
const width = x - initialPosition.current.x;
const height = y - initialPosition.current.y;
currentPath2D.current.rect(
initialPosition.current.x,
initialPosition.current.y,
width,
height
);
currentPath2D.current.rect(initX, initY, width, height);
} else if (shape === "triangle") {
const width = x - initialPosition.current.x;
const height = y - initialPosition.current.y;
currentPath2D.current.moveTo(
initialPosition.current.x + width / 2,
initialPosition.current.y
);
currentPath2D.current.lineTo(
initialPosition.current.x,
initialPosition.current.y + height
);
currentPath2D.current.lineTo(
initialPosition.current.x + width,
initialPosition.current.y + height
);
currentPath2D.current.moveTo(initX + width / 2, initY);
currentPath2D.current.lineTo(initX, initY + height);
currentPath2D.current.lineTo(initX + width, initY + height);
}

currentPath2D.current.closePath();
Expand Down Expand Up @@ -279,7 +245,7 @@ const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(function Canvas(
if (!ctx) throw new Error("Couldn't get the 2D context of the canvas.");

const { x, y } = getPointerPosition(canvas, e.clientX, e.clientY);
const { x: initX, y: initY } = initialPosition.current;
const [initX, initY] = initialPosition.current;

let elementType;
let elementPayload;
Expand All @@ -306,7 +272,7 @@ const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(function Canvas(

const properties = createElement(elementType, elementPayload);

initialPosition.current = { x: 0, y: 0 };
updateVector2(initialPosition.current, 0, 0);

pushHistory({
type: "add_element",
Expand All @@ -324,9 +290,7 @@ const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(function Canvas(

useImperativeHandle(ref, () => canvasRef.current!, []);

const debounceRedraw = useMemo(() => false, []);

useCanvasRedrawListener(canvasRef, undefined, debounceRedraw);
useCanvasRedrawListener(canvasRef);

useEffect(() => {
document.addEventListener("mousemove", onMouseMove);
Expand Down
32 changes: 16 additions & 16 deletions src/components/CanvasPane/CanvasPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useRef, useEffect, useState, memo } from "react";
import useStore from "@/state/hooks/useStore";
import useStoreSubscription from "@/state/hooks/useStoreSubscription";
import { useShallow } from "zustand/react/shallow";
import { redrawCanvas } from "@/lib/utils";
import { redrawCanvas, updateVector2 } from "@/lib/utils";

// Components
import DrawingToolbar from "@/components/DrawingToolbar/DrawingToolbar";
Expand All @@ -13,7 +13,7 @@ import ScaleIndicator from "@/components/ScaleIndicator/ScaleIndicator";

// Types
import type { ReactNode } from "react";
import type { Coordinates } from "@/types";
import type { Vector } from "@/types";

const MemoizedCanvas = memo(Canvas);
const MemoizedDrawingToolbar = memo(DrawingToolbar);
Expand Down Expand Up @@ -45,8 +45,8 @@ function CanvasPane(): ReactNode {
);
const currentShape = useStoreSubscription((state) => state.shape);
const currentColor = useStoreSubscription((state) => state.color);
const clientPosition = useRef<Coordinates>({ x: 0, y: 0 });
const startMovePosition = useRef<Coordinates>({ x: 0, y: 0 });
const clientPosition = useRef<Vector<2>>([0, 0]);
const startMovePosition = useRef<Vector<2>>([0, 0]);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [loading, setLoading] = useState<boolean>(true);

Expand All @@ -64,8 +64,8 @@ function CanvasPane(): ReactNode {
function handleMouseDown(e: MouseEvent) {
if (e.buttons !== 1) return;

clientPosition.current = { x: e.clientX, y: e.clientY };
startMovePosition.current = { x: e.clientX, y: e.clientY };
clientPosition.current = [e.clientX, e.clientY];
startMovePosition.current = [e.clientX, e.clientY];
}

function handleMouseMove(e: MouseEvent) {
Expand All @@ -75,8 +75,10 @@ function CanvasPane(): ReactNode {
const layer = getActiveLayer();
if (!canvas || layer.hidden) return;

let dx = e.clientX - clientPosition.current.x;
let dy = e.clientY - clientPosition.current.y;
const initX = clientPosition.current[0];
const initY = clientPosition.current[1];
let dx = e.clientX - initX;
let dy = e.clientY - initY;

if (isPanning) {
// TODO: Have to revisit the calculation to know how the canvas is considered off screen.
Expand All @@ -103,11 +105,7 @@ function CanvasPane(): ReactNode {
if (state.type === "brush" || state.type === "eraser") {
return {
...state,
path: state.path.map((point) => ({
...point,
x: point.x + dx,
y: point.y + dy
}))
path: state.path.map((point) => [point[0] + dx, point[1] + dy])
};
} else {
return {
Expand All @@ -121,13 +119,15 @@ function CanvasPane(): ReactNode {
);
redrawCanvas();
}
clientPosition.current = { x: e.clientX, y: e.clientY };
updateVector2(clientPosition.current, e.clientX, e.clientY);
}

function handleMouseUp(e: MouseEvent) {
if (isMoving && isClickingOnSpace(e)) {
const dx = e.clientX - startMovePosition.current.x; // total change in x
const dy = e.clientY - startMovePosition.current.y; // total change in y
const initX = startMovePosition.current[0];
const initY = startMovePosition.current[1];
const dx = e.clientX - initX; // total change in x
const dy = e.clientY - initY; // total change in y

const layer = getActiveLayer();

Expand Down
11 changes: 6 additions & 5 deletions src/components/CanvasPointerMarker/CanvasPointerMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { useState, useEffect, useRef } from "react";
import useStore from "@/state/hooks/useStore";
import { useShallow } from "zustand/react/shallow";


// Types
import type { ReactNode, RefObject } from "react";
import type { Coordinates } from "@/types";
import type { Vector } from "@/types";

type CanvasPointerMarker = {
canvasSpaceReference: RefObject<HTMLCanvasElement | null>;
Expand All @@ -22,7 +21,9 @@ function CanvasPointerMarker({
}))
);
const ref = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState<Coordinates>({ x: 0, y: 0 });
const [position, setPosition] = useState<Vector<2>>([0, 0]);
const positionX = position[0];
const positionY = position[1];

const POINTER_SIZE = strokeWidth * scale;

Expand Down Expand Up @@ -62,7 +63,7 @@ function CanvasPointerMarker({
newY = computedY;
}

setPosition({ x: newX, y: newY });
setPosition([newX, newY]);
}

document.addEventListener("mousemove", computeCoordinates);
Expand All @@ -82,7 +83,7 @@ function CanvasPointerMarker({
borderRadius: "50%",
left: -POINTER_SIZE,
top: -POINTER_SIZE,
transform: `translate(${position.x}px, ${position.y}px)`,
transform: `translate(${positionX}px, ${positionY}px)`,
zIndex: 100,
width: POINTER_SIZE,
height: POINTER_SIZE,
Expand Down
Loading
Loading