feat: tool radius
This commit is contained in:
parent
933b287f9a
commit
7803275267
8 changed files with 103 additions and 32 deletions
|
|
@ -29,7 +29,7 @@ function Canvas() {
|
|||
const { setLoading } = useContext(LoadingContext);
|
||||
const { settings } = useContext(SettingsContext);
|
||||
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);
|
||||
|
||||
|
|
@ -40,10 +40,6 @@ function Canvas() {
|
|||
const [holdingAlt, setHoldingAlt] = useState(false);
|
||||
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 blockSize = 16 * scale;
|
||||
|
||||
|
|
@ -87,26 +83,58 @@ function Canvas() {
|
|||
}, [dragging, holdingAlt, tool, setCssCursor]);
|
||||
|
||||
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) {
|
||||
case "pencil": {
|
||||
setBlocks([
|
||||
...updatedBlocks,
|
||||
{
|
||||
name: selectedBlock,
|
||||
x: mouseCoords.x,
|
||||
y: mouseCoords.y,
|
||||
},
|
||||
]);
|
||||
const newBlocks = getBlocksWithinRadius(mouseCoords.x, mouseCoords.y, radius, selectedBlock);
|
||||
const mergedBlocks = blocks.filter((block) => {
|
||||
return !newBlocks.some((newBlock) => block.x === newBlock.x && block.y === newBlock.y);
|
||||
});
|
||||
|
||||
setBlocks([...mergedBlocks, ...newBlocks]);
|
||||
break;
|
||||
}
|
||||
case "eraser":
|
||||
case "eraser": {
|
||||
// Fixes Infinity and NaN errors
|
||||
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;
|
||||
}
|
||||
}
|
||||
}, [tool, mouseCoords, selectedBlock, updatedBlocks, setBlocks, blocks.length]);
|
||||
}, [tool, mouseCoords, selectedBlock, setBlocks, blocks, radius]);
|
||||
|
||||
const onMouseMove = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
|
|
@ -261,7 +289,7 @@ function Canvas() {
|
|||
|
||||
<Container x={coords.x} y={coords.y} scale={scale}>
|
||||
{settings.canvasBorder && <CanvasBorder canvasSize={canvasSize} />}
|
||||
<Cursor mouseCoords={mouseCoords} />
|
||||
<Cursor mouseCoords={mouseCoords} radius={radius} />
|
||||
</Container>
|
||||
|
||||
{settings.grid && (
|
||||
|
|
|
|||
|
|
@ -1,18 +1,25 @@
|
|||
import { Graphics } from "@pixi/react";
|
||||
|
||||
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 (
|
||||
<Graphics
|
||||
x={mouseCoords.x * 16}
|
||||
y={mouseCoords.y * 16}
|
||||
x={(mouseCoords.x + offset) * 16}
|
||||
y={(mouseCoords.y + offset) * 16}
|
||||
draw={(g) => {
|
||||
g.clear();
|
||||
g.lineStyle(1, 0xffffff, 1);
|
||||
g.drawRect(0, 0, 16, 16);
|
||||
g.drawRect(0, 0, size, size);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import GithubIcon from "@/assets/github.svg?react";
|
|||
function Menubar() {
|
||||
return (
|
||||
<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">
|
||||
<BlockmaticText className="h-full w-full" fill="white" />
|
||||
</Link>
|
||||
|
|
|
|||
25
src/components/tool-settings/Radius.tsx
Normal file
25
src/components/tool-settings/Radius.tsx
Normal 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;
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
import SelectorBlocks from "./SelectorBlocks";
|
||||
import Radius from "./Radius";
|
||||
|
||||
function BlockSelector() {
|
||||
function ToolSettings() {
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const [stageWidth, setStageWidth] = useState(0);
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
|
|
@ -17,8 +19,10 @@ function BlockSelector() {
|
|||
|
||||
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">
|
||||
<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">
|
||||
<SelectorBlocks stageWidth={stageWidth} searchInput={searchInput} />
|
||||
</div>
|
||||
|
|
@ -26,4 +30,4 @@ function BlockSelector() {
|
|||
);
|
||||
}
|
||||
|
||||
export default BlockSelector;
|
||||
export default ToolSettings;
|
||||
|
|
@ -6,15 +6,18 @@ interface Props {
|
|||
|
||||
export const ToolContext = createContext({
|
||||
tool: "hand" as Tool,
|
||||
radius: 1,
|
||||
selectedBlock: "stone",
|
||||
cssCursor: "pointer",
|
||||
setTool: (tool: Tool) => {},
|
||||
setSelectedBlock: (block: string) => {},
|
||||
setCssCursor: (cursor: string) => {},
|
||||
setTool: ((tool: Tool) => {}) as React.Dispatch<React.SetStateAction<Tool>>,
|
||||
setRadius: ((value: number) => {}) as React.Dispatch<React.SetStateAction<number>>,
|
||||
setSelectedBlock: ((block: string) => {}) as React.Dispatch<React.SetStateAction<string>>,
|
||||
setCssCursor: ((cursor: string) => {}) as React.Dispatch<React.SetStateAction<string>>,
|
||||
});
|
||||
|
||||
export const ToolProvider = ({ children }: Props) => {
|
||||
const [tool, setTool] = useState<Tool>("hand");
|
||||
const [radius, setRadius] = useState(1);
|
||||
const [selectedBlock, setSelectedBlock] = useState("stone");
|
||||
const [cssCursor, setCssCursor] = useState("pointer");
|
||||
|
||||
|
|
@ -33,5 +36,9 @@ export const ToolProvider = ({ children }: Props) => {
|
|||
}
|
||||
}, [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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { ToolProvider } from "@/context/Tool";
|
|||
import Menubar from "@/components/menubar";
|
||||
import Toolbar from "@/components/toolbar";
|
||||
import Canvas from "@/components/canvas/Canvas";
|
||||
import BlockSelector from "@/components/block-selector";
|
||||
import ToolSettings from "@/components/tool-settings";
|
||||
|
||||
function AppPage() {
|
||||
return (
|
||||
|
|
@ -22,7 +22,7 @@ function AppPage() {
|
|||
<Menubar />
|
||||
<Toolbar />
|
||||
<Canvas />
|
||||
<BlockSelector />
|
||||
<ToolSettings />
|
||||
</main>
|
||||
</ToolProvider>
|
||||
</TexturesProvider>
|
||||
|
|
|
|||
Loading…
Reference in a new issue