grungnet/src/contexts/GridContext.tsx

283 lines
8.4 KiB
TypeScript
Raw Permalink Normal View History

2021-03-11 19:02:58 -05:00
import React, { useContext, useState, useEffect } from "react";
import Vector2 from "../helpers/Vector2";
import Size from "../helpers/Size";
2021-07-16 00:55:33 -04:00
import { getGridPixelSize, getCellPixelSize } from "../helpers/grid";
2021-07-16 00:55:33 -04:00
import { Grid } from "../types/Grid";
import useSetting from "../hooks/useSetting";
import shortcuts from "../shortcuts";
import { useBlur, useKeyboard } from "./KeyboardContext";
/**
* @typedef GridContextValue
* @property {Grid} grid Base grid value
* @property {Size} gridPixelSize Size of the grid in pixels
* @property {Size} gridCellPixelSize Size of each cell in pixels
* @property {Size} gridCellNormalizedSize Size of each cell normalized to the grid
* @property {Vector2} gridOffset Offset of the grid from the top left in pixels
* @property {number} gridStrokeWidth Stroke width of the grid in pixels
* @property {Vector2} gridCellPixelOffset Offset of the grid cells to convert the center position of hex cells to the top left
*/
2021-06-03 01:31:18 -04:00
type GridContextValue = {
2021-07-16 00:55:33 -04:00
grid: Grid;
gridPixelSize: Size;
gridCellPixelSize: Size;
gridCellNormalizedSize: Size;
gridOffset: Vector2;
gridStrokeWidth: number;
gridCellPixelOffset: Vector2;
gridSnappingSensitivity: number;
2021-07-16 00:55:33 -04:00
};
/**
* @type {GridContextValue}
*/
2021-06-03 01:31:18 -04:00
const defaultValue: GridContextValue = {
grid: {
size: new Vector2(0, 0),
inset: { topLeft: new Vector2(0, 0), bottomRight: new Vector2(1, 1) },
type: "square",
measurement: {
scale: "",
type: "euclidean",
},
},
gridPixelSize: new Size(0, 0),
gridCellPixelSize: new Size(0, 0, 0),
gridCellNormalizedSize: new Size(0, 0, 0),
gridOffset: new Vector2(0, 0),
gridStrokeWidth: 0,
gridCellPixelOffset: new Vector2(0, 0),
gridSnappingSensitivity: 0,
};
export const GridContext = React.createContext(defaultValue.grid);
export const GridPixelSizeContext = React.createContext(
defaultValue.gridPixelSize
);
export const GridCellPixelSizeContext = React.createContext(
2021-03-11 19:02:58 -05:00
defaultValue.gridCellPixelSize
);
export const GridCellNormalizedSizeContext = React.createContext(
2021-03-11 19:02:58 -05:00
defaultValue.gridCellNormalizedSize
);
export const GridOffsetContext = React.createContext(defaultValue.gridOffset);
export const GridStrokeWidthContext = React.createContext(
2021-03-11 19:02:58 -05:00
defaultValue.gridStrokeWidth
);
export const GridCellPixelOffsetContext = React.createContext(
2021-03-11 19:02:58 -05:00
defaultValue.gridCellPixelOffset
);
export const GridSnappingSensitivityContext = React.createContext(
defaultValue.gridSnappingSensitivity
);
const defaultStrokeWidth = 1 / 10;
2021-07-17 04:18:57 -04:00
type GridProviderProps = {
grid: Grid | null;
width: number;
height: number;
children: React.ReactNode;
};
2021-07-16 00:55:33 -04:00
export function GridProvider({
grid: inputGrid,
width,
height,
children,
2021-07-17 04:18:57 -04:00
}: GridProviderProps) {
let grid: Grid;
2021-03-11 19:02:58 -05:00
2021-07-17 04:18:57 -04:00
if (!inputGrid || !inputGrid.size.x || !inputGrid.size.y) {
2021-07-16 00:55:33 -04:00
grid = defaultValue.grid;
2021-07-17 04:18:57 -04:00
} else {
grid = inputGrid;
}
2021-03-11 19:02:58 -05:00
const [gridPixelSize, setGridPixelSize] = useState(
defaultValue.gridCellPixelSize
);
2021-03-11 19:02:58 -05:00
const [gridCellPixelSize, setGridCellPixelSize] = useState(
defaultValue.gridCellPixelSize
);
2021-03-11 19:02:58 -05:00
const [gridCellNormalizedSize, setGridCellNormalizedSize] = useState(
defaultValue.gridCellNormalizedSize
);
const [gridOffset, setGridOffset] = useState(defaultValue.gridOffset);
const [gridStrokeWidth, setGridStrokeWidth] = useState(
defaultValue.gridStrokeWidth
);
const [gridCellPixelOffset, setGridCellPixelOffset] = useState(
defaultValue.gridCellPixelOffset
);
useEffect(() => {
const _gridPixelSize = getGridPixelSize(grid, width, height);
const _gridCellPixelSize = getCellPixelSize(
grid,
_gridPixelSize.width,
_gridPixelSize.height
);
const _gridCellNormalizedSize = new Size(
_gridCellPixelSize.width / width,
_gridCellPixelSize.height / height
);
const _gridOffset = Vector2.multiply(grid.inset.topLeft, {
x: width,
y: height,
});
const _gridStrokeWidth =
(_gridCellPixelSize.width < _gridCellPixelSize.height
? _gridCellPixelSize.width
: _gridCellPixelSize.height) * defaultStrokeWidth;
let _gridCellPixelOffset = { x: 0, y: 0 };
// Move hex tiles to top left
if (grid.type === "hexVertical" || grid.type === "hexHorizontal") {
_gridCellPixelOffset = Vector2.multiply(_gridCellPixelSize, 0.5);
}
setGridPixelSize(_gridPixelSize);
setGridCellPixelSize(_gridCellPixelSize);
setGridCellNormalizedSize(_gridCellNormalizedSize);
setGridOffset(_gridOffset);
setGridStrokeWidth(_gridStrokeWidth);
setGridCellPixelOffset(_gridCellPixelOffset);
}, [grid, width, height]);
const [gridSnappingSensitivity, setGridSnappingSensitivity] = useState(
defaultValue.gridSnappingSensitivity
);
const [defaultSnappingSensitivity] = useSetting<number>(
"map.gridSnappingSensitivity"
);
useEffect(() => {
if (
gridSnappingSensitivity !== defaultSnappingSensitivity &&
gridSnappingSensitivity !== -1 // Snapping not disabled
) {
setGridSnappingSensitivity(defaultSnappingSensitivity);
}
}, [defaultSnappingSensitivity, gridSnappingSensitivity]);
function handleKeyDown(event: KeyboardEvent) {
if (shortcuts.disableSnapping(event)) {
setGridSnappingSensitivity(-1);
}
}
function handleKeyUp(event: KeyboardEvent) {
if (shortcuts.disableSnapping(event)) {
setGridSnappingSensitivity(defaultSnappingSensitivity);
}
}
function handleBlur() {
setGridSnappingSensitivity(defaultSnappingSensitivity);
}
useKeyboard(handleKeyDown, handleKeyUp);
useBlur(handleBlur);
2021-03-11 19:02:58 -05:00
return (
<GridContext.Provider value={grid}>
<GridPixelSizeContext.Provider value={gridPixelSize}>
<GridCellPixelSizeContext.Provider value={gridCellPixelSize}>
<GridCellNormalizedSizeContext.Provider
value={gridCellNormalizedSize}
>
<GridOffsetContext.Provider value={gridOffset}>
<GridStrokeWidthContext.Provider value={gridStrokeWidth}>
<GridCellPixelOffsetContext.Provider
value={gridCellPixelOffset}
>
<GridSnappingSensitivityContext.Provider
value={gridSnappingSensitivity}
>
{children}
</GridSnappingSensitivityContext.Provider>
2021-03-11 19:02:58 -05:00
</GridCellPixelOffsetContext.Provider>
</GridStrokeWidthContext.Provider>
</GridOffsetContext.Provider>
</GridCellNormalizedSizeContext.Provider>
</GridCellPixelSizeContext.Provider>
</GridPixelSizeContext.Provider>
</GridContext.Provider>
);
}
export function useGrid() {
const context = useContext(GridContext);
if (context === undefined) {
throw new Error("useGrid must be used within a GridProvider");
}
return context;
}
2021-03-11 19:02:58 -05:00
export function useGridPixelSize() {
const context = useContext(GridPixelSizeContext);
if (context === undefined) {
throw new Error("useGridPixelSize must be used within a GridProvider");
}
return context;
}
export function useGridCellPixelSize() {
const context = useContext(GridCellPixelSizeContext);
if (context === undefined) {
throw new Error("useGridCellPixelSize must be used within a GridProvider");
}
return context;
}
export function useGridCellNormalizedSize() {
const context = useContext(GridCellNormalizedSizeContext);
if (context === undefined) {
throw new Error(
"useGridCellNormalizedSize must be used within a GridProvider"
);
}
return context;
}
export function useGridOffset() {
const context = useContext(GridOffsetContext);
if (context === undefined) {
throw new Error("useGridOffset must be used within a GridProvider");
}
return context;
}
export function useGridStrokeWidth() {
const context = useContext(GridStrokeWidthContext);
if (context === undefined) {
throw new Error("useGridStrokeWidth must be used within a GridProvider");
}
return context;
}
export function useGridCellPixelOffset() {
const context = useContext(GridCellPixelOffsetContext);
if (context === undefined) {
throw new Error(
"useGridCellPixelOffset must be used within a GridProvider"
);
}
return context;
}
export function useGridSnappingSensitivity() {
const context = useContext(GridSnappingSensitivityContext);
if (context === undefined) {
throw new Error(
"useGridSnappingSensitivity must be used within a GridProvider"
);
}
return context;
}