feat: tool radius

This commit is contained in:
trafficlunar 2024-12-24 20:43:03 +00:00
parent 933b287f9a
commit 7803275267
8 changed files with 103 additions and 32 deletions

View file

@ -29,7 +29,7 @@ function Canvas() {
const { setLoading } = useContext(LoadingContext); const { setLoading } = useContext(LoadingContext);
const { settings } = useContext(SettingsContext); const { settings } = useContext(SettingsContext);
const { missingTexture, textures, solidTextures } = useContext(TexturesContext); const { missingTexture, textures, solidTextures } = useContext(TexturesContext);
const { tool, selectedBlock, cssCursor, setTool, setCssCursor } = useContext(ToolContext); const { tool, radius, selectedBlock, cssCursor, setTool, setCssCursor } = useContext(ToolContext);
const stageContainerRef = useRef<HTMLDivElement>(null); const stageContainerRef = useRef<HTMLDivElement>(null);
@ -40,10 +40,6 @@ function Canvas() {
const [holdingAlt, setHoldingAlt] = useState(false); const [holdingAlt, setHoldingAlt] = useState(false);
const [oldTool, setOldTool] = useState<Tool>("hand"); const [oldTool, setOldTool] = useState<Tool>("hand");
const updatedBlocks = useMemo(() => {
return blocks.filter((b) => !(b.x === mouseCoords.x && b.y === mouseCoords.y));
}, [blocks, mouseCoords]);
const visibleArea = useMemo(() => { const visibleArea = useMemo(() => {
const blockSize = 16 * scale; const blockSize = 16 * scale;
@ -87,26 +83,58 @@ function Canvas() {
}, [dragging, holdingAlt, tool, setCssCursor]); }, [dragging, holdingAlt, tool, setCssCursor]);
const onToolUse = useCallback(() => { const onToolUse = useCallback(() => {
// Radius calculation - if odd number cursor is in center, if even cursor is in top-left corner
const getBlocksWithinRadius = (centerX: number, centerY: number, radius: number, blockName: string): Block[] => {
const radiusBlocks = [];
const halfSize = Math.floor(radius / 2);
const startX = centerX - (radius % 2 === 0 ? 0 : halfSize);
const startY = centerY - (radius % 2 === 0 ? 0 : halfSize);
for (let x = 0; x < radius; x++) {
for (let y = 0; y < radius; y++) {
const tileX = startX + x;
const tileY = startY + y;
radiusBlocks.push({
name: blockName,
x: tileX,
y: tileY,
});
}
}
return radiusBlocks;
};
switch (tool) { switch (tool) {
case "pencil": { case "pencil": {
setBlocks([ const newBlocks = getBlocksWithinRadius(mouseCoords.x, mouseCoords.y, radius, selectedBlock);
...updatedBlocks, const mergedBlocks = blocks.filter((block) => {
{ return !newBlocks.some((newBlock) => block.x === newBlock.x && block.y === newBlock.y);
name: selectedBlock, });
x: mouseCoords.x,
y: mouseCoords.y, setBlocks([...mergedBlocks, ...newBlocks]);
},
]);
break; break;
} }
case "eraser": case "eraser": {
// Fixes Infinity and NaN errors // Fixes Infinity and NaN errors
if (blocks.length == 1) break; if (blocks.length == 1) break;
setBlocks(updatedBlocks); const halfSize = Math.floor(radius / 2);
const startX = mouseCoords.x - (radius % 2 === 0 ? 0 : halfSize);
const startY = mouseCoords.y - (radius % 2 === 0 ? 0 : halfSize);
const updated = blocks.filter((block) => {
const withinSquare = block.x >= startX && block.x < startX + radius && block.y >= startY && block.y < startY + radius;
return !withinSquare;
});
setBlocks(updated);
break; break;
} }
}, [tool, mouseCoords, selectedBlock, updatedBlocks, setBlocks, blocks.length]); }
}, [tool, mouseCoords, selectedBlock, setBlocks, blocks, radius]);
const onMouseMove = useCallback( const onMouseMove = useCallback(
(e: React.MouseEvent) => { (e: React.MouseEvent) => {
@ -261,7 +289,7 @@ function Canvas() {
<Container x={coords.x} y={coords.y} scale={scale}> <Container x={coords.x} y={coords.y} scale={scale}>
{settings.canvasBorder && <CanvasBorder canvasSize={canvasSize} />} {settings.canvasBorder && <CanvasBorder canvasSize={canvasSize} />}
<Cursor mouseCoords={mouseCoords} /> <Cursor mouseCoords={mouseCoords} radius={radius} />
</Container> </Container>
{settings.grid && ( {settings.grid && (

View file

@ -1,18 +1,25 @@
import { Graphics } from "@pixi/react"; import { Graphics } from "@pixi/react";
interface Props { interface Props {
mouseCoords: Position mouseCoords: Position;
radius: number;
} }
function Cursor({ mouseCoords }: Props) { function Cursor({ mouseCoords, radius }: Props) {
const isOddRadius = radius % 2 !== 0;
const halfSize = Math.floor(radius / 2);
const offset = isOddRadius ? -halfSize : 0;
const size = radius * 16;
return ( return (
<Graphics <Graphics
x={mouseCoords.x * 16} x={(mouseCoords.x + offset) * 16}
y={mouseCoords.y * 16} y={(mouseCoords.y + offset) * 16}
draw={(g) => { draw={(g) => {
g.clear(); g.clear();
g.lineStyle(1, 0xffffff, 1); g.lineStyle(1, 0xffffff, 1);
g.drawRect(0, 0, 16, 16); g.drawRect(0, 0, size, size);
}} }}
/> />
); );

View file

@ -13,7 +13,7 @@ import GithubIcon from "@/assets/github.svg?react";
function Menubar() { function Menubar() {
return ( return (
<DialogProvider> <DialogProvider>
<UIMenubar className="rounded-none border-t-0 border-x-0 col-span-2"> <UIMenubar className="rounded-none border-t-0 border-x-0 col-span-3">
<Link to={{ pathname: "/" }} className="px-4 w-32"> <Link to={{ pathname: "/" }} className="px-4 w-32">
<BlockmaticText className="h-full w-full" fill="white" /> <BlockmaticText className="h-full w-full" fill="white" />
</Link> </Link>

View file

@ -0,0 +1,25 @@
import { useContext } from "react";
import { ToolContext } from "@/context/Tool";
import { Input } from "@/components/ui/input";
import { Label } from "../ui/label";
function Radius() {
const { radius, setRadius } = useContext(ToolContext);
return (
<div className="grid grid-cols-2 items-center">
<Label htmlFor="radius">Radius</Label>
<Input
name="radius"
type="number"
min={1}
max={10}
value={radius}
onChange={(e) => setRadius(Math.min(Math.max(parseInt(e.target.value), 1), 10))}
/>
</div>
);
}
export default Radius;

View file

@ -1,10 +1,12 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import SelectorBlocks from "./SelectorBlocks"; import SelectorBlocks from "./SelectorBlocks";
import Radius from "./Radius";
function BlockSelector() { function ToolSettings() {
const divRef = useRef<HTMLDivElement>(null); const divRef = useRef<HTMLDivElement>(null);
const [stageWidth, setStageWidth] = useState(0); const [stageWidth, setStageWidth] = useState(0);
const [searchInput, setSearchInput] = useState(""); const [searchInput, setSearchInput] = useState("");
@ -17,8 +19,10 @@ function BlockSelector() {
return ( return (
<div className="w-72 border-l border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-2 pb-0 flex flex-col h-full gap-2"> <div className="w-72 border-l border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-2 pb-0 flex flex-col h-full gap-2">
<Input placeholder="Search for blocks..." value={searchInput} onChange={(e) => setSearchInput(e.target.value)} /> <Radius />
<Separator />
<Input placeholder="Search for blocks..." value={searchInput} onChange={(e) => setSearchInput(e.target.value)} />
<div ref={divRef} className="w-full flex-1 overflow-y-auto pb-2"> <div ref={divRef} className="w-full flex-1 overflow-y-auto pb-2">
<SelectorBlocks stageWidth={stageWidth} searchInput={searchInput} /> <SelectorBlocks stageWidth={stageWidth} searchInput={searchInput} />
</div> </div>
@ -26,4 +30,4 @@ function BlockSelector() {
); );
} }
export default BlockSelector; export default ToolSettings;

View file

@ -6,15 +6,18 @@ interface Props {
export const ToolContext = createContext({ export const ToolContext = createContext({
tool: "hand" as Tool, tool: "hand" as Tool,
radius: 1,
selectedBlock: "stone", selectedBlock: "stone",
cssCursor: "pointer", cssCursor: "pointer",
setTool: (tool: Tool) => {}, setTool: ((tool: Tool) => {}) as React.Dispatch<React.SetStateAction<Tool>>,
setSelectedBlock: (block: string) => {}, setRadius: ((value: number) => {}) as React.Dispatch<React.SetStateAction<number>>,
setCssCursor: (cursor: string) => {}, setSelectedBlock: ((block: string) => {}) as React.Dispatch<React.SetStateAction<string>>,
setCssCursor: ((cursor: string) => {}) as React.Dispatch<React.SetStateAction<string>>,
}); });
export const ToolProvider = ({ children }: Props) => { export const ToolProvider = ({ children }: Props) => {
const [tool, setTool] = useState<Tool>("hand"); const [tool, setTool] = useState<Tool>("hand");
const [radius, setRadius] = useState(1);
const [selectedBlock, setSelectedBlock] = useState("stone"); const [selectedBlock, setSelectedBlock] = useState("stone");
const [cssCursor, setCssCursor] = useState("pointer"); const [cssCursor, setCssCursor] = useState("pointer");
@ -33,5 +36,9 @@ export const ToolProvider = ({ children }: Props) => {
} }
}, [tool]); }, [tool]);
return <ToolContext.Provider value={{ tool, selectedBlock, cssCursor, setTool, setSelectedBlock, setCssCursor }}>{children}</ToolContext.Provider>; return (
<ToolContext.Provider value={{ tool, radius, selectedBlock, cssCursor, setTool, setRadius, setSelectedBlock, setCssCursor }}>
{children}
</ToolContext.Provider>
);
}; };

View file

@ -8,7 +8,7 @@ import { ToolProvider } from "@/context/Tool";
import Menubar from "@/components/menubar"; import Menubar from "@/components/menubar";
import Toolbar from "@/components/toolbar"; import Toolbar from "@/components/toolbar";
import Canvas from "@/components/canvas/Canvas"; import Canvas from "@/components/canvas/Canvas";
import BlockSelector from "@/components/block-selector"; import ToolSettings from "@/components/tool-settings";
function AppPage() { function AppPage() {
return ( return (
@ -22,7 +22,7 @@ function AppPage() {
<Menubar /> <Menubar />
<Toolbar /> <Toolbar />
<Canvas /> <Canvas />
<BlockSelector /> <ToolSettings />
</main> </main>
</ToolProvider> </ToolProvider>
</TexturesProvider> </TexturesProvider>