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 { 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 && (
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
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 { 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;
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue