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"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pixi/react": "7",
|
||||||
"@radix-ui/react-menubar": "^1.1.2",
|
"@radix-ui/react-menubar": "^1.1.2",
|
||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"konva": "^9.3.16",
|
|
||||||
"lucide-react": "^0.464.0",
|
"lucide-react": "^0.464.0",
|
||||||
|
"pixi.js": "7",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-konva": "^18.2.10",
|
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"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 React, { useEffect, useRef, useState } from "react";
|
||||||
import { Layer, Stage } from "react-konva";
|
|
||||||
import { Eraser, Hand, Pencil } from "lucide-react";
|
import { Eraser, Hand, Pencil } from "lucide-react";
|
||||||
|
|
||||||
|
import * as PIXI from "pixi.js";
|
||||||
|
import { Container, Stage } from "@pixi/react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Menubar,
|
Menubar,
|
||||||
MenubarContent,
|
MenubarContent,
|
||||||
|
|
@ -20,80 +22,62 @@ import Blocks from "./components/blocks";
|
||||||
import Cursor from "./components/cursor";
|
import Cursor from "./components/cursor";
|
||||||
import CursorInformation from "./components/cursor-information";
|
import CursorInformation from "./components/cursor-information";
|
||||||
|
|
||||||
|
// Set scale mode to NEAREST
|
||||||
|
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const stageContainerRef = useRef<HTMLDivElement>(null);
|
const stageContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const stageRef = useRef(null);
|
|
||||||
|
|
||||||
const [stageSize, setStageSize] = useState({
|
const [stageSize, setStageSize] = useState({ width: 0, height: 0 });
|
||||||
width: 0,
|
const [coords, setCoords] = useState<Position>({ x: 0, y: 0 });
|
||||||
height: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [stageScale, setStageScale] = useState(1);
|
|
||||||
const [stageCoords, setStageCoords] = useState<Position>({ x: 0, y: 0 });
|
|
||||||
const [mousePosition, setMousePosition] = useState<Position>({ x: 0, y: 0 });
|
const [mousePosition, setMousePosition] = useState<Position>({ x: 0, y: 0 });
|
||||||
|
const [localMousePosition, setLocalMousePosition] = useState<Position>({ x: 0, y: 0 });
|
||||||
const [tool, setTool] = useState<Tool>("hand");
|
const [dragging, setDragging] = useState(false);
|
||||||
const [selectedBlock, setSelectedBlock] = useState("stone");
|
const [scale, setScale] = useState(1);
|
||||||
const [blocks, setBlocks] = useState<Block[]>([]);
|
const [blocks, setBlocks] = useState<Block[]>([]);
|
||||||
|
|
||||||
const [cssCursor, setCssCursor] = useState("grab");
|
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);
|
setTool(value);
|
||||||
|
setCssCursor(value === "hand" ? "grab" : "pointer");
|
||||||
switch (value) {
|
|
||||||
case "hand":
|
|
||||||
setCssCursor("grab");
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
setCssCursor("auto");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseMove = (e) => {
|
const onMouseMove = (e: React.MouseEvent) => {
|
||||||
const stage = e.target.getStage();
|
if (dragging) {
|
||||||
const oldScale = stage.scaleX();
|
if (tool === "hand") {
|
||||||
const pointer = stage.getPointerPosition();
|
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({
|
setMousePosition({
|
||||||
x: (pointer.x - stage.x()) / oldScale,
|
x: mouseX,
|
||||||
y: (pointer.y - stage.y()) / oldScale,
|
y: mouseY
|
||||||
});
|
});
|
||||||
|
setLocalMousePosition({
|
||||||
if (mouseDown) {
|
x: (mouseX - coords.x) / scale,
|
||||||
onClick(e);
|
y: (mouseY - coords.y) / scale
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClick = (e) => {
|
const onMouseDown = (e: React.MouseEvent) => {
|
||||||
const blockX = Math.floor(mousePosition.x / 16);
|
setDragging(true);
|
||||||
const blockY = Math.floor(mousePosition.y / 16);
|
|
||||||
|
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));
|
const updatedBlocks = blocks.filter((b) => !(b.x === blockX && b.y === blockY));
|
||||||
|
|
||||||
switch (tool) {
|
switch (tool) {
|
||||||
|
|
@ -111,15 +95,32 @@ function App() {
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "eraser": {
|
case "eraser":
|
||||||
setBlocks(updatedBlocks);
|
setBlocks(updatedBlocks);
|
||||||
break;
|
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(() => {
|
useEffect(() => {
|
||||||
if (stageContainerRef.current && stageRef.current) {
|
if (stageContainerRef.current) {
|
||||||
setStageSize({
|
setStageSize({
|
||||||
width: stageContainerRef.current.offsetWidth,
|
width: stageContainerRef.current.offsetWidth,
|
||||||
height: stageContainerRef.current.offsetHeight,
|
height: stageContainerRef.current.offsetHeight,
|
||||||
|
|
@ -182,26 +183,19 @@ function App() {
|
||||||
<Stage
|
<Stage
|
||||||
width={stageSize.width}
|
width={stageSize.width}
|
||||||
height={stageSize.height}
|
height={stageSize.height}
|
||||||
draggable={tool == "hand"}
|
|
||||||
ref={stageRef}
|
|
||||||
x={stageCoords.x}
|
|
||||||
y={stageCoords.y}
|
|
||||||
scaleX={stageScale}
|
|
||||||
scaleY={stageScale}
|
|
||||||
onMouseMove={onMouseMove}
|
onMouseMove={onMouseMove}
|
||||||
onMouseDown={() => setMouseDown(true)}
|
onMouseDown={onMouseDown}
|
||||||
onMouseUp={onMouseUp}
|
onMouseUp={onMouseUp}
|
||||||
onWheel={onWheel}
|
onWheel={onWheel}
|
||||||
onClick={onClick}
|
|
||||||
style={{ cursor: cssCursor }}
|
style={{ cursor: cssCursor }}
|
||||||
>
|
>
|
||||||
<Layer imageSmoothingEnabled={false}>
|
<Container x={coords.x} y={coords.y} scale={scale}>
|
||||||
<Blocks blocks={blocks} setBlocks={setBlocks} />
|
<Blocks blocks={blocks} setBlocks={setBlocks} />
|
||||||
<Cursor mousePosition={mousePosition} />
|
<Cursor localMousePosition={localMousePosition} />
|
||||||
</Layer>
|
</Container>
|
||||||
</Stage>
|
</Stage>
|
||||||
|
|
||||||
<CursorInformation mousePosition={mousePosition} blocks={blocks} />
|
<CursorInformation localMousePosition={localMousePosition} blocks={blocks} />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Image as KonvaImage } from "react-konva";
|
|
||||||
|
|
||||||
import blocksData from "@/lib/blocks/programmer-art/average_colors.json";
|
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[]>> }) {
|
function Blocks({ blocks, setBlocks }: { blocks: Block[]; setBlocks: React.Dispatch<React.SetStateAction<Block[]>> }) {
|
||||||
const [images, setImages] = useState<{ [key: string]: HTMLImageElement }>({});
|
const [images, setImages] = useState<{ [key: string]: HTMLImageElement }>({});
|
||||||
|
|
@ -43,9 +43,9 @@ function Blocks({ blocks, setBlocks }: { blocks: Block[]; setBlocks: React.Dispa
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
canvas.width = image.width;
|
canvas.width = image.width;
|
||||||
canvas.height = image.height;
|
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[] = [];
|
const newBlocks: Block[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||||
|
|
@ -76,7 +76,7 @@ function Blocks({ blocks, setBlocks }: { blocks: Block[]; setBlocks: React.Dispa
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{blocks.map((block, index) => (
|
{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";
|
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 [position, setPosition] = useState({ x: 0, y: 0 });
|
||||||
const [block, setBlock] = useState<Block>();
|
const [block, setBlock] = useState<Block>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mousePosition) {
|
if (localMousePosition) {
|
||||||
const snappedX = Math.floor(mousePosition.x / 16);
|
const x = Math.floor(localMousePosition.x / 16);
|
||||||
const snappedY = Math.floor(mousePosition.y / 16);
|
const y = Math.floor(localMousePosition.y / 16);
|
||||||
|
|
||||||
setPosition({
|
setPosition({
|
||||||
x: snappedX,
|
x,
|
||||||
y: snappedY,
|
y,
|
||||||
});
|
});
|
||||||
|
|
||||||
setBlock(blocks.find((b) => b.x === snappedX && b.y === snappedY));
|
setBlock(blocks.find((b) => b.x === x && b.y === y));
|
||||||
}
|
}
|
||||||
}, [mousePosition]);
|
}, [localMousePosition]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute left-4 bottom-4 flex flex-col gap-1">
|
<div className="absolute left-4 bottom-4 flex flex-col gap-1">
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,29 @@
|
||||||
import { useEffect, useState } from "react";
|
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 });
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mousePosition) {
|
if (localMousePosition) {
|
||||||
const snappedX = Math.floor(mousePosition.x / 16) * 16;
|
const x = Math.floor(localMousePosition.x / 16) * 16;
|
||||||
const snappedY = Math.floor(mousePosition.y / 16) * 16;
|
const y = Math.floor(localMousePosition.y / 16) * 16;
|
||||||
|
|
||||||
setPosition({
|
setPosition({ x, y });
|
||||||
x: snappedX,
|
|
||||||
y: snappedY,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [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;
|
export default Cursor;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue