Added tile controls
This commit is contained in:
@@ -4,7 +4,7 @@ import React from 'react';
|
|||||||
// Home page for siren
|
// Home page for siren
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ overflow: 'hidden' }}>
|
||||||
<TileGrid />
|
<TileGrid />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
color: black;
|
color: black;
|
||||||
border-bottom: 1px solid #e6e6e6;
|
border-bottom: 1px solid #e6e6e6;
|
||||||
max-height: 70px;
|
max-height: 70px;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .left {
|
.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';
|
'use client';
|
||||||
|
|
||||||
import { Graphics, Stage } from '@pixi/react';
|
import { Container, Graphics, Stage } from '@pixi/react';
|
||||||
import { Graphics as PixiGraphics } from '@pixi/graphics';
|
import { Graphics as PixiGraphics } from '@pixi/graphics';
|
||||||
import { MouseEvent, WheelEvent, useCallback, useState } from 'react';
|
import { MouseEvent, WheelEvent, useCallback, useEffect, useState } from 'react';
|
||||||
|
import TileControls, { EditTool, Tool, defaultColors } from './TileControls';
|
||||||
// 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 });
|
|
||||||
|
|
||||||
|
export default function TileGrid() {
|
||||||
// Offset height of navbar from window height
|
// 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
|
// 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) => {
|
const draw = useCallback((g: PixiGraphics) => {
|
||||||
g.clear();
|
g.clear();
|
||||||
// Draw dot in the corner of each tile
|
// Draw dot in the corner of each tile
|
||||||
for (let x = 0; x < width; x += 32) {
|
for (let x = 0; x < size.width; x += 32 * scale) {
|
||||||
for (let y = 0; y < height; y += 32) {
|
for (let y = 0; y < size.height; y += 32 * scale) {
|
||||||
g.beginFill(0xffffff, 0.5);
|
g.beginFill(0xffffff, 0.5);
|
||||||
g.drawCircle(x, y, 1);
|
g.drawCircle(x, y, 1);
|
||||||
g.endFill();
|
g.endFill();
|
||||||
@@ -27,37 +47,60 @@ 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) {
|
if (mouseDown) {
|
||||||
const dx = position.x + e.clientX - lastPosition.x;
|
const dx = position.x + e.clientX - lastPosition.x;
|
||||||
const dy = position.y + e.clientY - lastPosition.y;
|
const dy = position.y + e.clientY - lastPosition.y;
|
||||||
|
// TODO: Boundaries
|
||||||
setPosition({ x: dx, y: dy });
|
setPosition({ x: dx, y: dy });
|
||||||
setLastPosition({ x: e.clientX, y: e.clientY });
|
setLastPosition({ x: e.clientX, y: e.clientY });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoom(e: WheelEvent) {
|
function zoomEvent(e: WheelEvent) {
|
||||||
console.log('zoom', e);
|
const delta = e.deltaY;
|
||||||
|
if (delta > 0) {
|
||||||
|
setZoom(zoom / 1.1);
|
||||||
|
} else {
|
||||||
|
setZoom(zoom * 1.1);
|
||||||
|
}
|
||||||
|
setScale(scale * zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stage
|
<>
|
||||||
width={width}
|
<Stage
|
||||||
height={height}
|
width={width}
|
||||||
options={{
|
height={height}
|
||||||
backgroundColor: 0x333333,
|
options={{
|
||||||
antialias: false
|
backgroundColor: 0x333333,
|
||||||
}}
|
antialias: false
|
||||||
onMouseDown={(e) => {
|
}}
|
||||||
setMouseDown(true);
|
onMouseDown={(e) => clickEvent(e)}
|
||||||
setLastPosition({ x: e.clientX, y: e.clientY });
|
onMouseUp={() => setMouseDown(false)}
|
||||||
}}
|
onMouseMove={(e) => moveEvent(e)}
|
||||||
onMouseUp={() => setMouseDown(false)}
|
onWheel={(e) => zoomEvent(e)}
|
||||||
onMouseMove={(e) => pan(e)}
|
>
|
||||||
onWheel={(e) => zoom(e)}
|
<Graphics x={position.x} y={position.y} draw={draw} />
|
||||||
onClick={(e) => console.log(e)}
|
<Container></Container>
|
||||||
>
|
</Stage>
|
||||||
<Graphics x={position.x} y={position.y} draw={draw} />
|
<TileControls
|
||||||
</Stage>
|
tool={tool}
|
||||||
|
setTool={setTool}
|
||||||
|
editTool={editTool}
|
||||||
|
setEditTool={setEditTool}
|
||||||
|
colors={colors}
|
||||||
|
setColors={setColors}
|
||||||
|
selectedColor={selectedColor}
|
||||||
|
setSelectedColor={setSelectedColor}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user