Added tile controls

This commit is contained in:
Benjamin Sherriff
2023-10-24 16:02:48 -04:00
parent bdd1fc7e37
commit 75fd00fb7f
4 changed files with 246 additions and 34 deletions

View File

@@ -4,7 +4,7 @@ import React from 'react';
// Home page for siren
export default function Page() {
return (
<div>
<div style={{ overflow: 'hidden' }}>
<TileGrid />
</div>
);

View File

@@ -4,6 +4,7 @@
color: black;
border-bottom: 1px solid #e6e6e6;
max-height: 70px;
user-select: none;
}
.navbar .left {

View 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>
);
}

View File

@@ -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}
/>
</>
);
}