feat: improved move tool

This commit is contained in:
trafficlunar 2025-01-24 13:23:34 +00:00
parent 9873c068e8
commit ee02e1ae47
7 changed files with 150 additions and 39 deletions

View file

@ -12,12 +12,13 @@ interface Props {
coords: Position;
scale: number;
version: number;
isSelectionLayer?: boolean;
}
// Lifts 16,000 tiles limit
settings.use32bitIndex = true;
function Blocks({ blocks, missingTexture, textures, coords, scale, version }: Props) {
function Blocks({ blocks, missingTexture, textures, coords, scale, version, isSelectionLayer }: Props) {
const app = useApp();
const tilemapRef = useRef<CompositeTilemap>();
@ -33,10 +34,18 @@ function Blocks({ blocks, missingTexture, textures, coords, scale, version }: Pr
};
useEffect(() => {
const container = new PIXI.Container();
// Put selection layer on top of the blocks layer
app.stage.addChildAt(container, isSelectionLayer ? 1 : 0);
const tilemap = new CompositeTilemap();
tilemapRef.current = tilemap;
app.stage.addChildAt(tilemap, 0);
if (isSelectionLayer) {
container.filters = [new PIXI.AlphaFilter(0.5)];
}
container.addChild(tilemap);
tileBlocks();
}, []);

View file

@ -4,6 +4,7 @@ import * as PIXI from "pixi.js";
import { Container, Stage } from "@pixi/react";
import { CanvasContext } from "@/context/Canvas";
import { SelectionContext } from "@/context/Selection";
import { SettingsContext } from "@/context/Settings";
import { TexturesContext } from "@/context/Textures";
import { ThemeContext } from "@/context/Theme";
@ -19,17 +20,23 @@ import CanvasBorder from "./CanvasBorder";
import CursorInformation from "./information/Cursor";
import CanvasInformation from "./information/Canvas";
import SelectionToolbar from "./SelectionToolbar";
// Set scale mode to NEAREST
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
function Canvas() {
const { stageSize, canvasSize, blocks, coords, scale, version, setStageSize, setBlocks, setCoords, setScale } = useContext(CanvasContext);
const {
coords: selectionCoords,
layerBlocks: selectionLayerBlocks,
setCoords: setSelectionCoords,
setLayerBlocks: setSelectionLayerBlocks,
} = useContext(SelectionContext);
const { settings } = useContext(SettingsContext);
const { missingTexture } = useContext(TexturesContext);
const { isDark } = useContext(ThemeContext);
const { tool, radius, selectedBlock, selectionCoords, cssCursor, setTool, setSelectedBlock, setSelectionCoords, setCssCursor } =
useContext(ToolContext);
const { tool, radius, selectedBlock, cssCursor, setTool, setSelectedBlock, setCssCursor } = useContext(ToolContext);
const textures = useTextures(version);
const stageContainerRef = useRef<HTMLDivElement>(null);
@ -126,23 +133,27 @@ function Canvas() {
const mouseMovement = mouseMovementRef.current;
if (!mouseMovement) return;
// If there is no selection currently being moved...
if (selectionLayerBlocks.length == 0) {
const result: Block[] = [];
setBlocks((prev) =>
prev.filter((b) => {
const isSelected = selectionCoords.some(([x, y]) => b.x === x && b.y === y);
// Add blocks in the selection coords to the selection layer
if (isSelected) result.push(b);
// Remove blocks originally there
return !isSelected;
})
);
setSelectionLayerBlocks(result);
}
// Increase each coordinate in the selection by the mouse movement
setSelectionCoords((prev) => prev.map(([x, y]) => [x + mouseMovement.x, y + mouseMovement.y]));
// Increase each block in the selection by the mouse movement
setBlocks((prev) =>
prev.map((block) => {
if (isInSelection(block.x, block.y)) {
return {
...block,
x: block.x + mouseMovement.x,
y: block.y + mouseMovement.y,
};
}
return block;
})
);
setSelectionLayerBlocks((prev) => prev.map((b) => ({ ...b, x: b.x + mouseMovement.x, y: b.y + mouseMovement.y })));
break;
}
case "lasso": {
@ -205,7 +216,18 @@ function Canvas() {
break;
}
}
}, [tool, mouseCoords, selectedBlock, blocks, radius, selectionCoords, setSelectionCoords, setBlocks]);
}, [
tool,
mouseCoords,
selectedBlock,
blocks,
radius,
selectionCoords,
selectionLayerBlocks,
setSelectionCoords,
setSelectionLayerBlocks,
setBlocks,
]);
const onMouseMove = useCallback(
(e: React.MouseEvent) => {
@ -485,6 +507,16 @@ function Canvas() {
options={{ backgroundAlpha: 0 }}
>
<Blocks blocks={visibleBlocks} missingTexture={missingTexture} textures={textures} coords={coords} scale={scale} version={version} />
{/* Selection layer */}
<Blocks
isSelectionLayer
blocks={selectionLayerBlocks}
missingTexture={missingTexture}
textures={textures}
coords={coords}
scale={scale}
version={version}
/>
<Container x={coords.x} y={coords.y} scale={scale}>
{settings.canvasBorder && <CanvasBorder canvasSize={canvasSize} isDark={isDark} />}
@ -501,6 +533,8 @@ function Canvas() {
<CursorInformation mouseCoords={mouseCoords} />
<CanvasInformation />
<SelectionToolbar />
</div>
);
}

View file

@ -0,0 +1,38 @@
import { useContext } from "react";
import { CheckIcon, XIcon } from "lucide-react";
import { CanvasContext } from "@/context/Canvas";
import { SelectionContext } from "@/context/Selection";
import { Button } from "@/components/ui/button";
function SelectionToolbar() {
const { blocks, setBlocks } = useContext(CanvasContext);
const { layerBlocks, setLayerBlocks } = useContext(SelectionContext);
const confirmSelection = () => {
const combinedBlocks = [...blocks, ...layerBlocks];
const uniqueBlocks = Array.from(new Map(combinedBlocks.map((block) => [`${block.x},${block.y}`, block])).values());
setBlocks(uniqueBlocks);
setLayerBlocks([]);
};
return (
layerBlocks.length != 0 && (
<div className="absolute left-1/2 -translate-x-1/2 bottom-4 flex items-center bg-white dark:bg-zinc-950 rounded shadow-xl border border-zinc-200 dark:border-zinc-800">
<span className="mr-4 ml-2">Selection</span>
{/* todo: place back blocks removed */}
<Button variant="ghost" className="w-8 h-8" onClick={() => setLayerBlocks([])}>
<XIcon />
</Button>
<Button variant="ghost" className="w-8 h-8" onClick={confirmSelection}>
<CheckIcon />
</Button>
</div>
)
);
}
export default SelectionToolbar;

View file

@ -1,13 +1,13 @@
import { useContext } from "react";
import { CanvasContext } from "@/context/Canvas";
import { ToolContext } from "@/context/Tool";
import { SelectionContext } from "@/context/Selection";
import { MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@/components/ui/menubar";
function EditMenu() {
const { setBlocks } = useContext(CanvasContext);
const { selectionCoords, setSelectionCoords } = useContext(ToolContext);
const { coords: selectionCoords, setCoords: setSelectionCoords } = useContext(SelectionContext);
const cut = () => {
setBlocks((prev) => prev.filter((b) => !selectionCoords.some(([x2, y2]) => x2 === b.x && y2 === b.y)));