chore: pixi.js rewrite
This commit is contained in:
parent
a5bc500553
commit
b30ba187e9
6 changed files with 780 additions and 152 deletions
|
|
@ -10,16 +10,16 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pixi/react": "7",
|
||||
"@radix-ui/react-menubar": "^1.1.2",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"konva": "^9.3.16",
|
||||
"lucide-react": "^0.464.0",
|
||||
"pixi.js": "7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-konva": "^18.2.10",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
|
|
|
|||
733
pnpm-lock.yaml
733
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
142
src/App.tsx
142
src/App.tsx
|
|
@ -1,7 +1,9 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { Layer, Stage } from "react-konva";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Eraser, Hand, Pencil } from "lucide-react";
|
||||
|
||||
import * as PIXI from "pixi.js";
|
||||
import { Container, Stage } from "@pixi/react";
|
||||
|
||||
import {
|
||||
Menubar,
|
||||
MenubarContent,
|
||||
|
|
@ -20,80 +22,62 @@ import Blocks from "./components/blocks";
|
|||
import Cursor from "./components/cursor";
|
||||
import CursorInformation from "./components/cursor-information";
|
||||
|
||||
// Set scale mode to NEAREST
|
||||
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
|
||||
|
||||
function App() {
|
||||
const stageContainerRef = useRef<HTMLDivElement>(null);
|
||||
const stageRef = useRef(null);
|
||||
|
||||
const [stageSize, setStageSize] = useState({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
const [stageScale, setStageScale] = useState(1);
|
||||
const [stageCoords, setStageCoords] = useState<Position>({ x: 0, y: 0 });
|
||||
const [stageSize, setStageSize] = useState({ width: 0, height: 0 });
|
||||
const [coords, setCoords] = useState<Position>({ x: 0, y: 0 });
|
||||
const [mousePosition, setMousePosition] = useState<Position>({ x: 0, y: 0 });
|
||||
|
||||
const [tool, setTool] = useState<Tool>("hand");
|
||||
const [selectedBlock, setSelectedBlock] = useState("stone");
|
||||
const [localMousePosition, setLocalMousePosition] = useState<Position>({ x: 0, y: 0 });
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [blocks, setBlocks] = useState<Block[]>([]);
|
||||
|
||||
const [cssCursor, setCssCursor] = useState("grab");
|
||||
const [mouseDown, setMouseDown] = useState(false);
|
||||
const [tool, setTool] = useState<Tool>("hand");
|
||||
const [selectedBlock, setSelectedBlock] = useState("stone");
|
||||
|
||||
const onToolChange = (value) => {
|
||||
const onToolChange = (value: Tool) => {
|
||||
setTool(value);
|
||||
|
||||
switch (value) {
|
||||
case "hand":
|
||||
setCssCursor("grab");
|
||||
break;
|
||||
|
||||
default:
|
||||
setCssCursor("auto");
|
||||
break;
|
||||
}
|
||||
setCssCursor(value === "hand" ? "grab" : "pointer");
|
||||
};
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
const stage = e.target.getStage();
|
||||
const oldScale = stage.scaleX();
|
||||
const pointer = stage.getPointerPosition();
|
||||
const onMouseMove = (e: React.MouseEvent) => {
|
||||
if (dragging) {
|
||||
if (tool === "hand") {
|
||||
setCoords((prevCoords) => ({
|
||||
x: prevCoords.x + e.movementX,
|
||||
y: prevCoords.y + e.movementY,
|
||||
}));
|
||||
}
|
||||
|
||||
onMouseDown(e);
|
||||
}
|
||||
|
||||
const stageRect = stageContainerRef.current?.getBoundingClientRect();
|
||||
if (!stageRect) return;
|
||||
|
||||
const mouseX = e.clientX - stageRect.left;
|
||||
const mouseY = e.clientY - stageRect.top;
|
||||
|
||||
setMousePosition({
|
||||
x: (pointer.x - stage.x()) / oldScale,
|
||||
y: (pointer.y - stage.y()) / oldScale,
|
||||
x: mouseX,
|
||||
y: mouseY
|
||||
});
|
||||
|
||||
if (mouseDown) {
|
||||
onClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = (e) => {
|
||||
setMouseDown(false);
|
||||
|
||||
if (tool == "hand") {
|
||||
setCssCursor("grab");
|
||||
}
|
||||
};
|
||||
|
||||
const onWheel = (e) => {
|
||||
const stage = e.target.getStage();
|
||||
const oldScale = stage.scaleX();
|
||||
const pointer = stage.getPointerPosition();
|
||||
|
||||
const newScale = e.evt.deltaY < 0 ? oldScale * 1.05 : oldScale / 1.05;
|
||||
|
||||
setStageScale(newScale);
|
||||
setStageCoords({
|
||||
x: pointer.x - mousePosition.x * newScale,
|
||||
y: pointer.y - mousePosition.y * newScale,
|
||||
setLocalMousePosition({
|
||||
x: (mouseX - coords.x) / scale,
|
||||
y: (mouseY - coords.y) / scale
|
||||
});
|
||||
};
|
||||
|
||||
const onClick = (e) => {
|
||||
const blockX = Math.floor(mousePosition.x / 16);
|
||||
const blockY = Math.floor(mousePosition.y / 16);
|
||||
const onMouseDown = (e: React.MouseEvent) => {
|
||||
setDragging(true);
|
||||
|
||||
const blockX = Math.floor(localMousePosition.x / 16);
|
||||
const blockY = Math.floor(localMousePosition.y / 16);
|
||||
const updatedBlocks = blocks.filter((b) => !(b.x === blockX && b.y === blockY));
|
||||
|
||||
switch (tool) {
|
||||
|
|
@ -111,15 +95,32 @@ function App() {
|
|||
]);
|
||||
break;
|
||||
}
|
||||
case "eraser": {
|
||||
case "eraser":
|
||||
setBlocks(updatedBlocks);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
setDragging(false);
|
||||
setCssCursor(tool === "hand" ? "grab" : "pointer");
|
||||
};
|
||||
|
||||
const onWheel = (e: React.WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const scaleChange = e.deltaY > 0 ? -0.1 : 0.1;
|
||||
const newScale = Math.min(Math.max(scale + scaleChange, 0.25), 16);
|
||||
|
||||
setScale(newScale);
|
||||
setCoords({
|
||||
x: mousePosition.x - localMousePosition.x * newScale,
|
||||
y: mousePosition.y - localMousePosition.y * newScale,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (stageContainerRef.current && stageRef.current) {
|
||||
if (stageContainerRef.current) {
|
||||
setStageSize({
|
||||
width: stageContainerRef.current.offsetWidth,
|
||||
height: stageContainerRef.current.offsetHeight,
|
||||
|
|
@ -182,26 +183,19 @@ function App() {
|
|||
<Stage
|
||||
width={stageSize.width}
|
||||
height={stageSize.height}
|
||||
draggable={tool == "hand"}
|
||||
ref={stageRef}
|
||||
x={stageCoords.x}
|
||||
y={stageCoords.y}
|
||||
scaleX={stageScale}
|
||||
scaleY={stageScale}
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseDown={() => setMouseDown(true)}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
onWheel={onWheel}
|
||||
onClick={onClick}
|
||||
style={{ cursor: cssCursor }}
|
||||
>
|
||||
<Layer imageSmoothingEnabled={false}>
|
||||
<Container x={coords.x} y={coords.y} scale={scale}>
|
||||
<Blocks blocks={blocks} setBlocks={setBlocks} />
|
||||
<Cursor mousePosition={mousePosition} />
|
||||
</Layer>
|
||||
<Cursor localMousePosition={localMousePosition} />
|
||||
</Container>
|
||||
</Stage>
|
||||
|
||||
<CursorInformation mousePosition={mousePosition} blocks={blocks} />
|
||||
<CursorInformation localMousePosition={localMousePosition} blocks={blocks} />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Image as KonvaImage } from "react-konva";
|
||||
|
||||
import blocksData from "@/lib/blocks/programmer-art/average_colors.json";
|
||||
import { Sprite } from "@pixi/react";
|
||||
|
||||
function Blocks({ blocks, setBlocks }: { blocks: Block[]; setBlocks: React.Dispatch<React.SetStateAction<Block[]>> }) {
|
||||
const [images, setImages] = useState<{ [key: string]: HTMLImageElement }>({});
|
||||
|
|
@ -43,9 +43,9 @@ function Blocks({ blocks, setBlocks }: { blocks: Block[]; setBlocks: React.Dispa
|
|||
if (ctx) {
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
ctx.drawImage(image, 0, 0, image.width / 8, image.height / 8);
|
||||
ctx.drawImage(image, 0, 0, image.width / 4, image.height / 4);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, image.width / 8, image.height / 8);
|
||||
const imageData = ctx.getImageData(0, 0, image.width / 4, image.height / 4);
|
||||
const newBlocks: Block[] = [];
|
||||
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||
|
|
@ -76,7 +76,7 @@ function Blocks({ blocks, setBlocks }: { blocks: Block[]; setBlocks: React.Dispa
|
|||
return (
|
||||
<>
|
||||
{blocks.map((block, index) => (
|
||||
<KonvaImage key={index} image={images[block.name]} x={block.x * 16} y={block.y * 16} />
|
||||
<Sprite key={index} image={images[block.name]} x={block.x * 16} y={block.y * 16} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
function CursorInformation({ mousePosition, blocks }: { mousePosition: Position; blocks: Block[] }) {
|
||||
function CursorInformation({ localMousePosition, blocks }: { localMousePosition: Position; blocks: Block[] }) {
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
const [block, setBlock] = useState<Block>();
|
||||
|
||||
useEffect(() => {
|
||||
if (mousePosition) {
|
||||
const snappedX = Math.floor(mousePosition.x / 16);
|
||||
const snappedY = Math.floor(mousePosition.y / 16);
|
||||
if (localMousePosition) {
|
||||
const x = Math.floor(localMousePosition.x / 16);
|
||||
const y = Math.floor(localMousePosition.y / 16);
|
||||
|
||||
setPosition({
|
||||
x: snappedX,
|
||||
y: snappedY,
|
||||
x,
|
||||
y,
|
||||
});
|
||||
|
||||
setBlock(blocks.find((b) => b.x === snappedX && b.y === snappedY));
|
||||
setBlock(blocks.find((b) => b.x === x && b.y === y));
|
||||
}
|
||||
}, [mousePosition]);
|
||||
}, [localMousePosition]);
|
||||
|
||||
return (
|
||||
<div className="absolute left-4 bottom-4 flex flex-col gap-1">
|
||||
|
|
|
|||
|
|
@ -1,22 +1,29 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Rect } from "react-konva";
|
||||
import { Graphics } from "@pixi/react";
|
||||
|
||||
function Cursor({ mousePosition }: { mousePosition: Position }) {
|
||||
function Cursor({ localMousePosition }: { localMousePosition: Position }) {
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
if (mousePosition) {
|
||||
const snappedX = Math.floor(mousePosition.x / 16) * 16;
|
||||
const snappedY = Math.floor(mousePosition.y / 16) * 16;
|
||||
if (localMousePosition) {
|
||||
const x = Math.floor(localMousePosition.x / 16) * 16;
|
||||
const y = Math.floor(localMousePosition.y / 16) * 16;
|
||||
|
||||
setPosition({
|
||||
x: snappedX,
|
||||
y: snappedY,
|
||||
});
|
||||
setPosition({ x, y });
|
||||
}
|
||||
}, [mousePosition]);
|
||||
}, [localMousePosition]);
|
||||
|
||||
return <Rect x={position.x} y={position.y} width={16} height={16} stroke={"white"} strokeWidth={0.5} dash={[2.5]} />;
|
||||
return (
|
||||
<Graphics
|
||||
x={position.x}
|
||||
y={position.y}
|
||||
draw={(g) => {
|
||||
g.clear();
|
||||
g.lineStyle(1, 0xffffff, 1);
|
||||
g.drawRect(0, 0, 16, 16);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Cursor;
|
||||
|
|
|
|||
Loading…
Reference in a new issue