Skip to content

Commit

Permalink
[feat] Improve canvas zoom #44 (#57)
Browse files Browse the repository at this point in the history
* feat: improved canvas zoom

* feat: improved canvas and fix lint format

* feat: improved canvas and fix lint format

* feat: added min and max scale variable
  • Loading branch information
addegbenga authored Apr 19, 2024
1 parent 3ce4077 commit 425e38a
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 68 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"d3": "^7.9.0",
"get-starknet": "^3.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
109 changes: 41 additions & 68 deletions frontend/src/canvas/Canvas.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useRef, useEffect, useState } from 'react'
import { select, zoom, zoomIdentity } from "d3"
import useWebSocket, { ReadyState } from 'react-use-websocket'
import './Canvas.css';
// import TemplateOverlay from './TemplateOverlay.js';
Expand All @@ -7,20 +8,17 @@ import backendConfig from "../configs/backend.config.json"

const Canvas = props => {
const backendUrl = "http://" + backendConfig.host + ":" + backendConfig.port
// TODO: Pressing "Canvas" resets the view / positioning
//TODO: Pressing "Canvas" resets the view / positioning
//TODO: Way to configure tick rates to give smooth xp for all users

//Todo: Make this dynamic
const minScale = 1;
const maxScale = 40;

const [canvasPositionX, setCanvasPositionX] = useState(0)
const [canvasPositionY, setCanvasPositionY] = useState(0)
const [isDragging, setIsDragging] = useState(false)
const [dragStartX, setDragStartX] = useState(0)
const [dragStartY, setDragStartY] = useState(0)

const [canvasScale, setCanvasScale] = useState(6)
const minScale = 1 // TODO: To config
const maxScale = 40
//TODO: Way to configure tick rates to give smooth xp for all users

const canvasRef = useRef(null)
const canvasPositionRef = useRef(null)
const canvasScaleRef = useRef(null)

// Read canvas config from environment variable file json
const width = canvasConfig.canvas.width
Expand All @@ -35,58 +33,33 @@ const Canvas = props => {
shouldReconnect: () => true,
},
)

// TODO: Weird positioning behavior when clicking into devtools

// Handle wheel event for zooming
const handleWheel = (e) => {
let newScale = canvasScale
if (e.deltaY < 0) {
newScale = Math.min(maxScale, newScale + 0.2)
} else {
newScale = Math.max(minScale, newScale - 0.2)
}
// TODO: Smart positioning of canvas zoom ( zoom to center of mouse pointer )
//let newCanvasPositionX = canvasPositionX
//let newCanvasPositionY = canvasPositionY
//const canvasOriginX = canvasPositionX + width / 2
//const canvasOriginY = canvasPositionY + height / 2
//setCanvasPositionX(newCanvasPositionX)
//setCanvasPositionY(newCanvasPositionY)

setCanvasScale(newScale)
}
// TODO: Weird positioning behavior when clicking into devtools
useEffect(() => {
const canvas = select(canvasPositionRef.current)
const Dzoom = zoom().scaleExtent([minScale, maxScale]).on("zoom", zoomHandler)

const handlePointerDown = (e) => {
setIsDragging(true)
setDragStartX(e.clientX)
setDragStartY(e.clientY)
}
// Set default zoom level and center the canvas
canvas
.call(Dzoom)
.call(Dzoom.transform, zoomIdentity.translate(0, 0).scale(4))

const handlePointerUp = () => {
setIsDragging(false)
setDragStartX(0)
setDragStartY(0)
}
return () => {
canvas.on(".zoom", null); // Clean up zoom event listeners
};
}, []);

const handlePointerMove = (e) => {
if (isDragging) {
// TODO: Prevent dragging outside of canvas container
setCanvasPositionX(canvasPositionX + e.clientX - dragStartX)
setCanvasPositionY(canvasPositionY + e.clientY - dragStartY)
setDragStartX(e.clientX)
setDragStartY(e.clientY)
}
const zoomHandler = (event) => {
const ele = canvasScaleRef.current
const {
k: newScale,
x: newCanvasPositionX,
y: newCanvasPositionY,
} = event.transform;
const transformValue = `translate(${newCanvasPositionX}px, ${newCanvasPositionY}px) scale(${newScale})`
ele.style.transform = transformValue
}

useEffect(() => {
document.addEventListener('pointerup', handlePointerUp)

return () => {
document.removeEventListener('pointerup', handlePointerUp)
}
}, [])

const [setup, setSetup] = useState(false)

const draw = useCallback((ctx, imageData) => {
Expand All @@ -104,7 +77,7 @@ const Canvas = props => {
const context = canvas.getContext('2d')

let getCanvasEndpoint = backendUrl + "/getCanvas"
fetch(getCanvasEndpoint, {mode: 'cors'}).then(response => {
fetch(getCanvasEndpoint, { mode: 'cors' }).then(response => {
return response.arrayBuffer()
}).then(data => {
let colorData = new Uint8Array(data, 0, data.byteLength)
Expand Down Expand Up @@ -205,7 +178,7 @@ const Canvas = props => {
if (props.selectedPositionX === null || props.selectedPositionY === null) {
return
}

const position = props.selectedPositionX + props.selectedPositionY * width
const colorIdx = props.selectedColorId
let placePixelEndpoint = backendUrl + "/placePixelDevnet"
Expand All @@ -228,7 +201,7 @@ const Canvas = props => {
props.setSelectedColorId(-1)
// TODO: Optimistic update
}

// TODO: Deselect pixel when clicking outside of color palette or pixel
// TODO: Show small position vec in bottom right corner of canvas
const getSelectedColor = () => {
Expand Down Expand Up @@ -287,19 +260,19 @@ const Canvas = props => {

// TODO: both place options
return (
<div className="Canvas__container" onWheel={handleWheel} onPointerDown={handlePointerDown} onPointerMove={handlePointerMove}>
<div className="Canvas__position" style={{transform: `translate(${canvasPositionX}px, ${canvasPositionY}px )`}}>
<div className="Canvas__scale" style={{transform: `scale(${canvasScale})`}}>
{ props.pixelSelectedMode && (
<div className="Canvas__selected" style={{left: props.selectedPositionX, top: props.selectedPositionY}}>
<div className="Canvas__selected__pixel" style={{backgroundColor: getSelectorsColor(), boxShadow: `0 0 .2px .1px ${getSelectorsColorInverse()} inset`}}></div>
<div className="Canvas__container">
<div ref={canvasPositionRef} className="Canvas__position">
<div ref={canvasScaleRef} className="Canvas__scale">
{props.pixelSelectedMode && (
<div className="Canvas__selected" style={{ left: props.selectedPositionX, top: props.selectedPositionY }}>
<div className="Canvas__selected__pixel" style={{ backgroundColor: getSelectorsColor(), boxShadow: `0 0 .2px .1px ${getSelectorsColorInverse()} inset` }}></div>
</div>
)}
<canvas ref={canvasRef} className="Canvas" onClick={pixelClicked}/>
<canvas ref={canvasRef} className="Canvas" onClick={pixelClicked} />
</div>
</div>
</div>
);
}

export default Canvas
export default Canvas

0 comments on commit 425e38a

Please sign in to comment.