Added tile controls
This commit is contained in:
@@ -4,7 +4,7 @@ import React from 'react';
|
||||
// Home page for siren
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<div style={{ overflow: 'hidden' }}>
|
||||
<TileGrid />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
color: black;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
max-height: 70px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.navbar .left {
|
||||
|
||||
168
ui/src/components/TileGrid/TileControls.tsx
Normal file
168
ui/src/components/TileGrid/TileControls.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import { ActionIcon, Box, ColorPicker, Menu } from '@mantine/core';
|
||||
import { FaSquare, FaCircle, FaHandPaper, FaRegCircle } from 'react-icons/fa';
|
||||
import { FaMagnifyingGlass, FaPencil } from 'react-icons/fa6';
|
||||
|
||||
export enum Tool {
|
||||
HAND,
|
||||
ZOOM,
|
||||
EDIT,
|
||||
TOKEN
|
||||
}
|
||||
|
||||
export enum EditTool {
|
||||
SQUARE,
|
||||
CIRCLE
|
||||
}
|
||||
|
||||
export const defaultColors = [
|
||||
'#000000',
|
||||
'#1D2B53',
|
||||
'#7E2553',
|
||||
'#008751',
|
||||
'#AB5236',
|
||||
'#5F574F',
|
||||
'#C2C3C7',
|
||||
'#FFF1E8',
|
||||
'#FF004D'
|
||||
];
|
||||
|
||||
interface TileControlsProps {
|
||||
tool: Tool;
|
||||
setTool: (tool: Tool) => void;
|
||||
editTool: EditTool;
|
||||
setEditTool: (editTool: EditTool) => void;
|
||||
colors: string[];
|
||||
setColors: (colors: string[]) => void;
|
||||
selectedColor: number;
|
||||
setSelectedColor: (selectedColor: number) => void;
|
||||
}
|
||||
|
||||
export default function TileControls({
|
||||
tool,
|
||||
setTool,
|
||||
editTool,
|
||||
setEditTool,
|
||||
colors,
|
||||
setColors,
|
||||
selectedColor,
|
||||
setSelectedColor
|
||||
}: TileControlsProps) {
|
||||
window.addEventListener(
|
||||
'keydown',
|
||||
(e) => {
|
||||
if (e.key === ' ') {
|
||||
setTool(Tool.HAND);
|
||||
} else if (e.key === 'z') {
|
||||
setTool(Tool.ZOOM);
|
||||
} else if (e.key === 'e') {
|
||||
setTool(Tool.EDIT);
|
||||
} else if (e.key === 't') {
|
||||
setTool(Tool.TOKEN);
|
||||
} else if (e.key === '1') {
|
||||
setSelectedColor(0);
|
||||
} else if (e.key === '2') {
|
||||
setSelectedColor(1);
|
||||
} else if (e.key === '3') {
|
||||
setSelectedColor(2);
|
||||
} else if (e.key === '4') {
|
||||
setSelectedColor(3);
|
||||
} else if (e.key === '5') {
|
||||
setSelectedColor(4);
|
||||
} else if (e.key === '6') {
|
||||
setSelectedColor(5);
|
||||
} else if (e.key === '7') {
|
||||
setSelectedColor(6);
|
||||
} else if (e.key === '8') {
|
||||
setSelectedColor(7);
|
||||
} else if (e.key === '9') {
|
||||
setSelectedColor(8);
|
||||
}
|
||||
},
|
||||
{ passive: false }
|
||||
);
|
||||
|
||||
function checkIfColorIsDark(color: string) {
|
||||
// If the color is dark, return white, otherwise return black
|
||||
const r = parseInt(color.slice(1, 3), 16);
|
||||
const g = parseInt(color.slice(3, 5), 16);
|
||||
const b = parseInt(color.slice(5, 7), 16);
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
return brightness < 128 ? '#ffffff' : '#000000';
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
position: 'fixed',
|
||||
bottom: '2rem',
|
||||
left: '2rem'
|
||||
}}
|
||||
>
|
||||
{tool === Tool.EDIT && (
|
||||
<ActionIcon.Group orientation='vertical' style={{ paddingBottom: '0.3rem', paddingLeft: '3.5rem' }}>
|
||||
<ActionIcon
|
||||
variant={editTool == EditTool.SQUARE ? 'filled' : 'default'}
|
||||
onClick={() => setEditTool(EditTool.SQUARE)}
|
||||
>
|
||||
<FaSquare />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={editTool == EditTool.CIRCLE ? 'filled' : 'default'}
|
||||
onClick={() => setEditTool(EditTool.CIRCLE)}
|
||||
>
|
||||
<FaCircle />
|
||||
</ActionIcon>
|
||||
</ActionIcon.Group>
|
||||
)}
|
||||
<ActionIcon.Group style={{ paddingBottom: '0.3rem' }}>
|
||||
<ActionIcon variant={tool == Tool.HAND ? 'filled' : 'default'} onClick={() => setTool(Tool.HAND)}>
|
||||
<FaHandPaper />
|
||||
</ActionIcon>
|
||||
<ActionIcon variant={tool == Tool.ZOOM ? 'filled' : 'default'} onClick={() => setTool(Tool.ZOOM)}>
|
||||
<FaMagnifyingGlass />
|
||||
</ActionIcon>
|
||||
<ActionIcon variant={tool == Tool.EDIT ? 'filled' : 'default'} onClick={() => setTool(Tool.EDIT)}>
|
||||
<FaPencil />
|
||||
</ActionIcon>
|
||||
<ActionIcon variant={tool == Tool.TOKEN ? 'filled' : 'default'} onClick={() => setTool(Tool.TOKEN)}>
|
||||
<FaRegCircle />
|
||||
</ActionIcon>
|
||||
</ActionIcon.Group>
|
||||
<ActionIcon.Group>
|
||||
{colors.map((color, index) => (
|
||||
<Menu trigger='hover' openDelay={700} closeDelay={100}>
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
key={`color-${index}`}
|
||||
variant={'filled'}
|
||||
color={color}
|
||||
onClick={() => setSelectedColor(index)}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: checkIfColorIsDark(color),
|
||||
fontWeight: index == selectedColor ? 'bolder' : 'normal',
|
||||
textDecoration: index == selectedColor ? 'underline' : 'none'
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<ColorPicker
|
||||
value={colors[index]}
|
||||
onChange={(v) => {
|
||||
const newColors = [...colors];
|
||||
newColors[index] = v;
|
||||
setColors(newColors);
|
||||
}}
|
||||
/>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
))}
|
||||
</ActionIcon.Group>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import { Graphics, Stage } from '@pixi/react';
|
||||
import { Container, Graphics, Stage } from '@pixi/react';
|
||||
import { Graphics as PixiGraphics } from '@pixi/graphics';
|
||||
import { MouseEvent, WheelEvent, useCallback, useState } from 'react';
|
||||
|
||||
// export default function TileGrid({ width, height }: TileGridProps) {
|
||||
export default function TIleGrid() {
|
||||
const [mouseDown, setMouseDown] = useState(false);
|
||||
const [lastPosition, setLastPosition] = useState({ x: 0, y: 0 });
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
import { MouseEvent, WheelEvent, useCallback, useEffect, useState } from 'react';
|
||||
import TileControls, { EditTool, Tool, defaultColors } from './TileControls';
|
||||
|
||||
export default function TileGrid() {
|
||||
// Offset height of navbar from window height
|
||||
const height = window.innerHeight - 75;
|
||||
const height = window ? window.innerHeight - 75 : 0;
|
||||
// Offset width of layout padding from window width
|
||||
const width = window.innerWidth;
|
||||
const width = window ? window.innerWidth : 0;
|
||||
|
||||
const [scale, setScale] = useState(1);
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const [size, setSize] = useState({ width: width * 2, height: height * 2 });
|
||||
const [boundaries, setBoundaries] = useState({ top: 0, bottom: 0, left: 0, right: 0 });
|
||||
const [mouseDown, setMouseDown] = useState(false);
|
||||
const [lastPosition, setLastPosition] = useState({ x: -width / 2, y: -height / 2 });
|
||||
const [position, setPosition] = useState({ x: -width / 2, y: -height / 2 });
|
||||
const [tool, setTool] = useState<Tool>(Tool.HAND);
|
||||
const [editTool, setEditTool] = useState<EditTool>(EditTool.SQUARE);
|
||||
const [colors, setColors] = useState<string[]>(defaultColors);
|
||||
const [selectedColor, setSelectedColor] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (window) {
|
||||
window.addEventListener(
|
||||
'wheel',
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
},
|
||||
{ passive: false }
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const draw = useCallback((g: PixiGraphics) => {
|
||||
g.clear();
|
||||
// Draw dot in the corner of each tile
|
||||
for (let x = 0; x < width; x += 32) {
|
||||
for (let y = 0; y < height; y += 32) {
|
||||
for (let x = 0; x < size.width; x += 32 * scale) {
|
||||
for (let y = 0; y < size.height; y += 32 * scale) {
|
||||
g.beginFill(0xffffff, 0.5);
|
||||
g.drawCircle(x, y, 1);
|
||||
g.endFill();
|
||||
@@ -27,20 +47,35 @@ export default function TIleGrid() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
function pan(e: MouseEvent) {
|
||||
function clickEvent(e: MouseEvent) {
|
||||
if (tool === Tool.HAND) {
|
||||
setMouseDown(true);
|
||||
setLastPosition({ x: e.clientX, y: e.clientY });
|
||||
}
|
||||
}
|
||||
|
||||
function moveEvent(e: MouseEvent) {
|
||||
if (mouseDown) {
|
||||
const dx = position.x + e.clientX - lastPosition.x;
|
||||
const dy = position.y + e.clientY - lastPosition.y;
|
||||
// TODO: Boundaries
|
||||
setPosition({ x: dx, y: dy });
|
||||
setLastPosition({ x: e.clientX, y: e.clientY });
|
||||
}
|
||||
}
|
||||
|
||||
function zoom(e: WheelEvent) {
|
||||
console.log('zoom', e);
|
||||
function zoomEvent(e: WheelEvent) {
|
||||
const delta = e.deltaY;
|
||||
if (delta > 0) {
|
||||
setZoom(zoom / 1.1);
|
||||
} else {
|
||||
setZoom(zoom * 1.1);
|
||||
}
|
||||
setScale(scale * zoom);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stage
|
||||
width={width}
|
||||
height={height}
|
||||
@@ -48,16 +83,24 @@ export default function TIleGrid() {
|
||||
backgroundColor: 0x333333,
|
||||
antialias: false
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
setMouseDown(true);
|
||||
setLastPosition({ x: e.clientX, y: e.clientY });
|
||||
}}
|
||||
onMouseDown={(e) => clickEvent(e)}
|
||||
onMouseUp={() => setMouseDown(false)}
|
||||
onMouseMove={(e) => pan(e)}
|
||||
onWheel={(e) => zoom(e)}
|
||||
onClick={(e) => console.log(e)}
|
||||
onMouseMove={(e) => moveEvent(e)}
|
||||
onWheel={(e) => zoomEvent(e)}
|
||||
>
|
||||
<Graphics x={position.x} y={position.y} draw={draw} />
|
||||
<Container></Container>
|
||||
</Stage>
|
||||
<TileControls
|
||||
tool={tool}
|
||||
setTool={setTool}
|
||||
editTool={editTool}
|
||||
setEditTool={setEditTool}
|
||||
colors={colors}
|
||||
setColors={setColors}
|
||||
selectedColor={selectedColor}
|
||||
setSelectedColor={setSelectedColor}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user