diff --git a/src/components/canvas/Blocks.tsx b/src/components/canvas/Blocks.tsx index b9fbb5b..6e5f87d 100644 --- a/src/components/canvas/Blocks.tsx +++ b/src/components/canvas/Blocks.tsx @@ -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(); @@ -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(); }, []); diff --git a/src/components/canvas/Canvas.tsx b/src/components/canvas/Canvas.tsx index 77a66d1..7cc7462 100644 --- a/src/components/canvas/Canvas.tsx +++ b/src/components/canvas/Canvas.tsx @@ -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(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 }} > + {/* Selection layer */} + {settings.canvasBorder && } @@ -501,6 +533,8 @@ function Canvas() { + + ); } diff --git a/src/components/canvas/SelectionToolbar.tsx b/src/components/canvas/SelectionToolbar.tsx new file mode 100644 index 0000000..1676906 --- /dev/null +++ b/src/components/canvas/SelectionToolbar.tsx @@ -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 && ( +
+ Selection + + {/* todo: place back blocks removed */} + + +
+ ) + ); +} + +export default SelectionToolbar; diff --git a/src/components/menubar/EditMenu.tsx b/src/components/menubar/EditMenu.tsx index 6862ab1..e500340 100644 --- a/src/components/menubar/EditMenu.tsx +++ b/src/components/menubar/EditMenu.tsx @@ -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))); diff --git a/src/context/Selection.tsx b/src/context/Selection.tsx new file mode 100644 index 0000000..0feeab4 --- /dev/null +++ b/src/context/Selection.tsx @@ -0,0 +1,32 @@ +import { createContext, ReactNode, useState } from "react"; + +interface Context { + coords: CoordinateArray; + layerBlocks: Block[]; + setCoords: React.Dispatch>; + setLayerBlocks: React.Dispatch>; +} + +interface Props { + children: ReactNode; +} + +export const SelectionContext = createContext({} as Context); + +export const SelectionProvider = ({ children }: Props) => { + const [coords, setCoords] = useState([]); + const [layerBlocks, setLayerBlocks] = useState([]); + + return ( + + {children} + + ); +}; diff --git a/src/context/Tool.tsx b/src/context/Tool.tsx index e151a87..86a3c2c 100644 --- a/src/context/Tool.tsx +++ b/src/context/Tool.tsx @@ -4,12 +4,10 @@ interface Context { tool: Tool; radius: number; selectedBlock: string; - selectionCoords: CoordinateArray; cssCursor: string; setTool: React.Dispatch>; setRadius: React.Dispatch>; setSelectedBlock: React.Dispatch>; - setSelectionCoords: React.Dispatch>; setCssCursor: React.Dispatch>; } @@ -23,7 +21,6 @@ export const ToolProvider = ({ children }: Props) => { const [tool, setTool] = useState("hand"); const [radius, setRadius] = useState(1); const [selectedBlock, setSelectedBlock] = useState("stone"); - const [selectionCoords, setSelectionCoords] = useState([]); const [cssCursor, setCssCursor] = useState("crosshair"); useEffect(() => { @@ -42,12 +39,10 @@ export const ToolProvider = ({ children }: Props) => { tool, radius, selectedBlock, - selectionCoords, cssCursor, setTool, setRadius, setSelectedBlock, - setSelectionCoords, setCssCursor, }} > diff --git a/src/pages/AppPage.tsx b/src/pages/AppPage.tsx index a4b58a8..291e5b4 100644 --- a/src/pages/AppPage.tsx +++ b/src/pages/AppPage.tsx @@ -1,5 +1,6 @@ import { CanvasProvider } from "@/context/Canvas"; import { LoadingProvider } from "@/context/Loading"; +import { SelectionProvider } from "@/context/Selection"; import { SettingsProvider } from "@/context/Settings"; import { TexturesProvider } from "@/context/Textures"; import { ToolProvider } from "@/context/Tool"; @@ -13,18 +14,20 @@ function AppPage() { return ( - - - -
- - - - -
-
-
-
+ + + + +
+ + + + +
+
+
+
+
);