mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-24 06:58:48 -05:00
refactor: simplifies how zooming & scrolling is applied
This commit is contained in:
@@ -2,7 +2,8 @@ import React, { useMemo } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import gridTileSvg from 'src/assets/grid-tile-bg.svg';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { getProjectedTileSize } from 'src/utils';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { SizeUtils } from 'src/utils/SizeUtils';
|
||||
|
||||
export const Grid = () => {
|
||||
const scroll = useUiStateStore((state) => {
|
||||
@@ -12,7 +13,7 @@ export const Grid = () => {
|
||||
return state.zoom;
|
||||
});
|
||||
const projectedTileSize = useMemo(() => {
|
||||
return getProjectedTileSize({ zoom });
|
||||
return SizeUtils.multiply(PROJECTED_TILE_SIZE, zoom);
|
||||
}, [zoom]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -52,6 +52,7 @@ export const Renderer = () => {
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 0,
|
||||
bgcolor: (theme) => {
|
||||
return theme.customVars.customPalette.diagramBg;
|
||||
}
|
||||
@@ -60,9 +61,17 @@ export const Renderer = () => {
|
||||
<SceneLayer>
|
||||
<Rectangles />
|
||||
</SceneLayer>
|
||||
<SceneLayer sx={{ width: '100%', height: '100%', top: 0, left: 0 }}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
top: 0,
|
||||
left: 0
|
||||
}}
|
||||
>
|
||||
<Grid />
|
||||
</SceneLayer>
|
||||
</Box>
|
||||
{mode.showCursor && (
|
||||
<SceneLayer>
|
||||
<Cursor />
|
||||
@@ -86,9 +95,10 @@ export const Renderer = () => {
|
||||
<TransformControlsManager />
|
||||
</SceneLayer>
|
||||
{/* Interaction layer: this is where events are detected */}
|
||||
<SceneLayer
|
||||
<Box
|
||||
ref={containerRef}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import React from 'react';
|
||||
import { Box, SxProps } from '@mui/material';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
@@ -7,24 +8,31 @@ interface Props {
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
export const SceneLayer = forwardRef(
|
||||
({ children, order = 0, sx }: Props, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
zIndex: order,
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
width: 0,
|
||||
height: 0,
|
||||
userSelect: 'none',
|
||||
...sx
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
export const SceneLayer = ({ children, order = 0, sx }: Props) => {
|
||||
const scroll = useUiStateStore((state) => {
|
||||
return state.scroll;
|
||||
});
|
||||
const zoom = useUiStateStore((state) => {
|
||||
return state.zoom;
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
zIndex: order,
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
width: 0,
|
||||
height: 0,
|
||||
userSelect: 'none',
|
||||
...sx
|
||||
}}
|
||||
style={{
|
||||
transform: `translate(${scroll.position.x}px, ${scroll.position.y}px) scale(${zoom})`
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Box, Typography } from '@mui/material';
|
||||
import { Connector } from 'src/types';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
import { connectorPathTileToGlobal } from 'src/utils';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useTileSize } from 'src/hooks/useTileSize';
|
||||
|
||||
interface Props {
|
||||
connector: Connector;
|
||||
@@ -15,7 +15,6 @@ export const ConnectorLabel = ({ connector }: Props) => {
|
||||
const zoom = useUiStateStore((state) => {
|
||||
return state.zoom;
|
||||
});
|
||||
const { projectedTileSize } = useTileSize();
|
||||
|
||||
const labelPosition = useMemo(() => {
|
||||
const tileIndex = Math.floor(connector.path.tiles.length / 2);
|
||||
@@ -40,7 +39,7 @@ export const ConnectorLabel = ({ connector }: Props) => {
|
||||
}}
|
||||
style={{
|
||||
transform: `translate(-50%, -50%) scale(${zoom})`,
|
||||
maxWidth: projectedTileSize.width * (1 / zoom),
|
||||
maxWidth: PROJECTED_TILE_SIZE.width,
|
||||
left: labelPosition.x,
|
||||
top: labelPosition.y
|
||||
}}
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
getAllAnchors
|
||||
} from 'src/utils';
|
||||
import { Circle } from 'src/components/Circle/Circle';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useSceneStore } from 'src/stores/sceneStore';
|
||||
import { Svg } from 'src/components/Svg/Svg';
|
||||
import { useIsoProjection } from 'src/hooks/useIsoProjection';
|
||||
@@ -23,12 +22,6 @@ export const Connector = ({ connector }: Props) => {
|
||||
const { css, pxSize } = useIsoProjection({
|
||||
...connector.path.rectangle
|
||||
});
|
||||
const zoom = useUiStateStore((state) => {
|
||||
return state.zoom;
|
||||
});
|
||||
const unprojectedTileSize = useMemo(() => {
|
||||
return UNPROJECTED_TILE_SIZE * zoom;
|
||||
}, [zoom]);
|
||||
const nodes = useSceneStore((state) => {
|
||||
return state.nodes;
|
||||
});
|
||||
@@ -38,18 +31,18 @@ export const Connector = ({ connector }: Props) => {
|
||||
|
||||
const drawOffset = useMemo(() => {
|
||||
return {
|
||||
x: unprojectedTileSize / 2,
|
||||
y: unprojectedTileSize / 2
|
||||
x: UNPROJECTED_TILE_SIZE / 2,
|
||||
y: UNPROJECTED_TILE_SIZE / 2
|
||||
};
|
||||
}, [unprojectedTileSize]);
|
||||
}, []);
|
||||
|
||||
const pathString = useMemo(() => {
|
||||
return connector.path.tiles.reduce((acc, tile) => {
|
||||
return `${acc} ${tile.x * unprojectedTileSize + drawOffset.x},${
|
||||
tile.y * unprojectedTileSize + drawOffset.y
|
||||
return `${acc} ${tile.x * UNPROJECTED_TILE_SIZE + drawOffset.x},${
|
||||
tile.y * UNPROJECTED_TILE_SIZE + drawOffset.y
|
||||
}`;
|
||||
}, '');
|
||||
}, [unprojectedTileSize, connector.path.tiles, drawOffset]);
|
||||
}, [connector.path.tiles, drawOffset]);
|
||||
|
||||
const anchorPositions = useMemo(() => {
|
||||
return connector.anchors.map((anchor) => {
|
||||
@@ -57,21 +50,18 @@ export const Connector = ({ connector }: Props) => {
|
||||
|
||||
return {
|
||||
id: anchor.id,
|
||||
x: (connector.path.rectangle.from.x - position.x) * unprojectedTileSize,
|
||||
y: (connector.path.rectangle.from.y - position.y) * unprojectedTileSize
|
||||
x:
|
||||
(connector.path.rectangle.from.x - position.x) *
|
||||
UNPROJECTED_TILE_SIZE,
|
||||
y:
|
||||
(connector.path.rectangle.from.y - position.y) * UNPROJECTED_TILE_SIZE
|
||||
};
|
||||
});
|
||||
}, [
|
||||
connector.path.rectangle,
|
||||
connector.anchors,
|
||||
nodes,
|
||||
connectors,
|
||||
unprojectedTileSize
|
||||
]);
|
||||
}, [connector.path.rectangle, connector.anchors, nodes, connectors]);
|
||||
|
||||
const connectorWidthPx = useMemo(() => {
|
||||
return (unprojectedTileSize / 100) * connector.width;
|
||||
}, [connector.width, unprojectedTileSize]);
|
||||
return (UNPROJECTED_TILE_SIZE / 100) * connector.width;
|
||||
}, [connector.width]);
|
||||
|
||||
const strokeDashArray = useMemo(() => {
|
||||
switch (connector.style) {
|
||||
@@ -121,16 +111,16 @@ export const Connector = ({ connector }: Props) => {
|
||||
<g key={anchor.id}>
|
||||
<Circle
|
||||
tile={CoordsUtils.add(anchor, drawOffset)}
|
||||
radius={18 * zoom}
|
||||
radius={18}
|
||||
fill={theme.palette.common.white}
|
||||
fillOpacity={0.7}
|
||||
/>
|
||||
<Circle
|
||||
tile={CoordsUtils.add(anchor, drawOffset)}
|
||||
radius={12 * zoom}
|
||||
radius={12}
|
||||
stroke={theme.palette.common.black}
|
||||
fill={theme.palette.common.white}
|
||||
strokeWidth={6 * zoom}
|
||||
strokeWidth={6}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useTileSize } from 'src/hooks/useTileSize';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { useResizeObserver } from 'src/hooks/useResizeObserver';
|
||||
|
||||
interface Props {
|
||||
@@ -10,7 +10,6 @@ interface Props {
|
||||
|
||||
export const IsometricIcon = ({ url, onImageLoaded }: Props) => {
|
||||
const ref = useRef();
|
||||
const { projectedTileSize } = useTileSize();
|
||||
const { size, observe, disconnect } = useResizeObserver();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -29,7 +28,7 @@ export const IsometricIcon = ({ url, onImageLoaded }: Props) => {
|
||||
src={url}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: projectedTileSize.width * 0.8,
|
||||
width: PROJECTED_TILE_SIZE.width * 0.8,
|
||||
top: -size.height,
|
||||
left: -size.width / 2,
|
||||
pointerEvents: 'none'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { Icon } from 'src/types';
|
||||
import { useTileSize } from 'src/hooks/useTileSize';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { getIsoProjectionCss } from 'src/utils';
|
||||
|
||||
interface Props {
|
||||
@@ -9,25 +9,22 @@ interface Props {
|
||||
}
|
||||
|
||||
export const NonIsometricIcon = ({ icon }: Props) => {
|
||||
const { projectedTileSize } = useTileSize();
|
||||
|
||||
return (
|
||||
<Box sx={{ pointerEvents: 'none' }}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: -projectedTileSize.width / 2,
|
||||
top: -projectedTileSize.height / 2,
|
||||
left: -PROJECTED_TILE_SIZE.width / 2,
|
||||
top: -PROJECTED_TILE_SIZE.height / 2,
|
||||
transformOrigin: 'top left',
|
||||
transform: getIsoProjectionCss(),
|
||||
userSelect: 'none'
|
||||
transform: getIsoProjectionCss()
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={icon.url}
|
||||
alt={`icon-${icon.id}`}
|
||||
sx={{ width: projectedTileSize.width * 0.7 }}
|
||||
sx={{ width: PROJECTED_TILE_SIZE.width * 0.7 }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { useEffect, useRef, useMemo } from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { MoreHoriz as ReadMoreIcon } from '@mui/icons-material';
|
||||
import { useResizeObserver } from 'src/hooks/useResizeObserver';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useTileSize } from 'src/hooks/useTileSize';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { Gradient } from 'src/components/Gradient/Gradient';
|
||||
|
||||
const MAX_LABEL_HEIGHT = 80;
|
||||
@@ -21,13 +20,9 @@ export const LabelContainer = ({
|
||||
}: Props) => {
|
||||
const contentRef = useRef<HTMLDivElement>();
|
||||
const { observe, size: contentSize } = useResizeObserver();
|
||||
const zoom = useUiStateStore((state) => {
|
||||
return state.zoom;
|
||||
});
|
||||
const { projectedTileSize } = useTileSize();
|
||||
const yOffset = useMemo(() => {
|
||||
return projectedTileSize.height / 2;
|
||||
}, [projectedTileSize]);
|
||||
return PROJECTED_TILE_SIZE.height / 2;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!contentRef.current) return;
|
||||
@@ -39,8 +34,7 @@ export const LabelContainer = ({
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
transformOrigin: 'top center',
|
||||
transform: `scale(${zoom})`
|
||||
transformOrigin: 'top center'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Typography, useTheme } from '@mui/material';
|
||||
import { Node as NodeI, TileOriginEnum } from 'src/types';
|
||||
import { useTileSize } from 'src/hooks/useTileSize';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
|
||||
import { useIcon } from 'src/hooks/useIcon';
|
||||
import { MarkdownEditor } from 'src/components/MarkdownEditor/MarkdownEditor';
|
||||
@@ -14,7 +14,6 @@ interface Props {
|
||||
|
||||
export const Node = ({ node, order }: Props) => {
|
||||
const theme = useTheme();
|
||||
const { projectedTileSize } = useTileSize();
|
||||
const { getTilePosition } = useGetTilePosition();
|
||||
const { iconComponent } = useIcon(node.icon);
|
||||
|
||||
@@ -51,7 +50,7 @@ export const Node = ({ node, order }: Props) => {
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -projectedTileSize.height
|
||||
top: -PROJECTED_TILE_SIZE.height
|
||||
}}
|
||||
/>
|
||||
<LabelContainer labelHeight={node.labelHeight} connectorDotSize={3}>
|
||||
|
||||
@@ -4,9 +4,10 @@ import { Card, SxProps } from '@mui/material';
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
sx?: SxProps;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const UiElement = ({ children, sx }: Props) => {
|
||||
export const UiElement = ({ children, sx, style }: Props) => {
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
@@ -15,6 +16,7 @@ export const UiElement = ({ children, sx }: Props) => {
|
||||
borderColor: 'grey.400',
|
||||
...sx
|
||||
}}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</Card>
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, useTheme, Typography } from '@mui/material';
|
||||
import { EditorModeEnum } from 'src/types';
|
||||
import { UiElement } from 'components/UiElement/UiElement';
|
||||
import { toPx } from 'src/utils';
|
||||
import { SceneLayer } from 'src/components/SceneLayer/SceneLayer';
|
||||
import { DragAndDrop } from 'src/components/DragAndDrop/DragAndDrop';
|
||||
import { ItemControlsManager } from 'src/components/ItemControls/ItemControlsManager';
|
||||
@@ -71,9 +70,21 @@ export const UiOverlay = () => {
|
||||
const availableTools = useMemo(() => {
|
||||
return getEditorModeMapping(editorMode);
|
||||
}, [editorMode]);
|
||||
const rendererSize = useUiStateStore((state) => {
|
||||
return state.rendererSize;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
{availableTools.includes('ITEM_CONTROLS') && itemControls && (
|
||||
<UiElement
|
||||
sx={{
|
||||
@@ -81,12 +92,14 @@ export const UiOverlay = () => {
|
||||
top: appPadding.y * 2 + spacing(2),
|
||||
left: appPadding.x,
|
||||
width: '360px',
|
||||
maxHeight: `calc(100% - ${toPx(appPadding.y * 6)})`,
|
||||
overflowY: 'scroll',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none'
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
maxHeight: rendererSize.height - appPadding.y * 6
|
||||
}}
|
||||
>
|
||||
<ItemControlsManager />
|
||||
</UiElement>
|
||||
@@ -95,9 +108,10 @@ export const UiOverlay = () => {
|
||||
{availableTools.includes('TOOL_MENU') && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: appPadding.x,
|
||||
transform: 'translateX(-100%)',
|
||||
left: rendererSize.width - appPadding.x,
|
||||
top: appPadding.y
|
||||
}}
|
||||
>
|
||||
@@ -113,10 +127,11 @@ export const UiOverlay = () => {
|
||||
|
||||
{availableTools.includes('ZOOM_CONTROLS') && (
|
||||
<Box
|
||||
sx={{
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: appPadding.x,
|
||||
bottom: appPadding.y
|
||||
transformOrigin: 'bottom left',
|
||||
top: rendererSize.height - appPadding.y * 2,
|
||||
left: appPadding.x
|
||||
}}
|
||||
>
|
||||
<ZoomControls />
|
||||
@@ -135,21 +150,32 @@ export const UiOverlay = () => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<UiElement
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: appPadding.y,
|
||||
left: '50%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
left: rendererSize.width / 2,
|
||||
top: rendererSize.height - appPadding.y * 2,
|
||||
width: rendererSize.width - 500,
|
||||
height: appPadding.y,
|
||||
transform: 'translateX(-50%)',
|
||||
px: 2,
|
||||
py: 1,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
>
|
||||
<Typography fontWeight={600} color="text.secondary">
|
||||
{sceneTitle}
|
||||
</Typography>
|
||||
</UiElement>
|
||||
<UiElement
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
px: 2,
|
||||
alignItems: 'center',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<Typography fontWeight={600} color="text.secondary">
|
||||
{sceneTitle}
|
||||
</Typography>
|
||||
</UiElement>
|
||||
</Box>
|
||||
|
||||
{debugMode && (
|
||||
<UiElement
|
||||
@@ -165,6 +191,6 @@ export const UiOverlay = () => {
|
||||
<DebugUtils />
|
||||
</UiElement>
|
||||
)}
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Size, Coords, SceneInput, Connector, Mode } from 'src/types';
|
||||
import { Size, Coords, SceneInput, Connector } from 'src/types';
|
||||
import { customVars } from './styles/theme';
|
||||
|
||||
// TODO: This file could do with better organisation and convention for easier reading.
|
||||
@@ -7,6 +7,10 @@ export const TILE_PROJECTION_MULTIPLIERS: Size = {
|
||||
width: 1.415,
|
||||
height: 0.819
|
||||
};
|
||||
export const PROJECTED_TILE_SIZE = {
|
||||
width: UNPROJECTED_TILE_SIZE * TILE_PROJECTION_MULTIPLIERS.width,
|
||||
height: UNPROJECTED_TILE_SIZE * TILE_PROJECTION_MULTIPLIERS.height
|
||||
};
|
||||
export const DEFAULT_COLOR = customVars.customPalette.blue;
|
||||
export const DEFAULT_FONT_FAMILY = 'Roboto, Arial, sans-serif';
|
||||
export const NODE_DEFAULTS = {
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { Coords, TileOriginEnum } from 'src/types';
|
||||
import { getTilePosition as getTilePositionUtil } from 'src/utils';
|
||||
|
||||
export const useGetTilePosition = () => {
|
||||
const { scroll, zoom, rendererSize } = useUiStateStore((state) => {
|
||||
return {
|
||||
scroll: state.scroll,
|
||||
zoom: state.zoom,
|
||||
rendererSize: state.rendererSize
|
||||
};
|
||||
});
|
||||
|
||||
const getTilePosition = useCallback(
|
||||
({ tile, origin }: { tile: Coords; origin?: TileOriginEnum }) => {
|
||||
return getTilePositionUtil({
|
||||
tile,
|
||||
scroll,
|
||||
zoom,
|
||||
origin,
|
||||
rendererSize
|
||||
origin
|
||||
});
|
||||
},
|
||||
[scroll, zoom, rendererSize]
|
||||
[]
|
||||
);
|
||||
|
||||
return { getTilePosition };
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from 'src/types';
|
||||
import { getBoundingBox, getIsoProjectionCss } from 'src/utils';
|
||||
import { UNPROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useGetTilePosition } from './useGetTilePosition';
|
||||
|
||||
interface Props {
|
||||
@@ -28,9 +27,6 @@ export const useIsoProjection = ({
|
||||
gridSize: Size;
|
||||
pxSize: Size;
|
||||
} => {
|
||||
const zoom = useUiStateStore((state) => {
|
||||
return state.zoom;
|
||||
});
|
||||
const { getTilePosition } = useGetTilePosition();
|
||||
|
||||
const gridSize = useMemo(() => {
|
||||
@@ -59,10 +55,10 @@ export const useIsoProjection = ({
|
||||
|
||||
const pxSize = useMemo(() => {
|
||||
return {
|
||||
width: gridSize.width * UNPROJECTED_TILE_SIZE * zoom,
|
||||
height: gridSize.height * UNPROJECTED_TILE_SIZE * zoom
|
||||
width: gridSize.width * UNPROJECTED_TILE_SIZE,
|
||||
height: gridSize.height * UNPROJECTED_TILE_SIZE
|
||||
};
|
||||
}, [zoom, gridSize]);
|
||||
}, [gridSize]);
|
||||
|
||||
return {
|
||||
css: {
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { useMemo } from 'react';
|
||||
import { TextBox } from 'src/types';
|
||||
import { useTileSize } from 'src/hooks/useTileSize';
|
||||
import { DEFAULT_FONT_FAMILY, TEXTBOX_DEFAULTS } from 'src/config';
|
||||
import {
|
||||
UNPROJECTED_TILE_SIZE,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
TEXTBOX_DEFAULTS
|
||||
} from 'src/config';
|
||||
|
||||
export const useTextBoxProps = (textBox: TextBox) => {
|
||||
const { unprojectedTileSize } = useTileSize();
|
||||
|
||||
const fontProps = useMemo(() => {
|
||||
return {
|
||||
fontSize: unprojectedTileSize * textBox.fontSize,
|
||||
fontSize: UNPROJECTED_TILE_SIZE * textBox.fontSize,
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
fontWeight: TEXTBOX_DEFAULTS.fontWeight
|
||||
};
|
||||
}, [unprojectedTileSize, textBox.fontSize]);
|
||||
}, [textBox.fontSize]);
|
||||
|
||||
const paddingX = useMemo(() => {
|
||||
return unprojectedTileSize * TEXTBOX_DEFAULTS.paddingX;
|
||||
}, [unprojectedTileSize]);
|
||||
return UNPROJECTED_TILE_SIZE * TEXTBOX_DEFAULTS.paddingX;
|
||||
}, []);
|
||||
|
||||
return { paddingX, fontProps };
|
||||
};
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { getProjectedTileSize } from 'src/utils';
|
||||
import { UNPROJECTED_TILE_SIZE } from 'src/config';
|
||||
|
||||
export const useTileSize = () => {
|
||||
const zoom = useUiStateStore((state) => {
|
||||
return state.zoom;
|
||||
});
|
||||
|
||||
const projectedTileSize = useMemo(() => {
|
||||
return getProjectedTileSize({ zoom });
|
||||
}, [zoom]);
|
||||
|
||||
const unprojectedTileSize = useMemo(() => {
|
||||
return UNPROJECTED_TILE_SIZE * zoom;
|
||||
}, [zoom]);
|
||||
|
||||
return { projectedTileSize, unprojectedTileSize };
|
||||
};
|
||||
@@ -76,9 +76,6 @@ export const TransformRectangle: ModeActions = {
|
||||
const anchorPositions = rectangleBounds.map((corner, i) => {
|
||||
return getTilePosition({
|
||||
tile: corner,
|
||||
scroll: uiState.scroll,
|
||||
zoom: uiState.zoom,
|
||||
rendererSize: uiState.rendererSize,
|
||||
origin: outermostCornerPositions[i]
|
||||
});
|
||||
});
|
||||
|
||||
33
src/utils/SizeUtils.ts
Normal file
33
src/utils/SizeUtils.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Size } from 'src/types';
|
||||
|
||||
export class SizeUtils {
|
||||
static isEqual(base: Size, operand: Size) {
|
||||
return base.width === operand.width && base.height === operand.height;
|
||||
}
|
||||
|
||||
static subtract(base: Size, operand: Size): Size {
|
||||
return {
|
||||
width: base.width - operand.width,
|
||||
height: base.height - operand.height
|
||||
};
|
||||
}
|
||||
|
||||
static add(base: Size, operand: Size): Size {
|
||||
return {
|
||||
width: base.width + operand.width,
|
||||
height: base.height + operand.height
|
||||
};
|
||||
}
|
||||
|
||||
static multiply(base: Size, operand: number): Size {
|
||||
return { width: base.width * operand, height: base.height * operand };
|
||||
}
|
||||
|
||||
static toString(size: Size) {
|
||||
return `width: ${size.width}, height: ${size.height}`;
|
||||
}
|
||||
|
||||
static zero() {
|
||||
return { width: 0, y: 0 };
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './CoordsUtils';
|
||||
export * from './SizeUtils';
|
||||
export * from './common';
|
||||
export * from './inputs';
|
||||
export * from './pathfinder';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { produce } from 'immer';
|
||||
import {
|
||||
TILE_PROJECTION_MULTIPLIERS,
|
||||
UNPROJECTED_TILE_SIZE,
|
||||
PROJECTED_TILE_SIZE,
|
||||
ZOOM_INCREMENT,
|
||||
MAX_ZOOM,
|
||||
MIN_ZOOM,
|
||||
@@ -28,24 +28,13 @@ import {
|
||||
} from 'src/types';
|
||||
import {
|
||||
CoordsUtils,
|
||||
SizeUtils,
|
||||
clamp,
|
||||
roundToOneDecimalPlace,
|
||||
findPath,
|
||||
toPx
|
||||
} from 'src/utils';
|
||||
|
||||
interface GetProjectedTileSize {
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
// Gets the size of a tile at a given zoom level
|
||||
export const getProjectedTileSize = ({ zoom }: GetProjectedTileSize): Size => {
|
||||
return {
|
||||
width: UNPROJECTED_TILE_SIZE * TILE_PROJECTION_MULTIPLIERS.width * zoom,
|
||||
height: UNPROJECTED_TILE_SIZE * TILE_PROJECTION_MULTIPLIERS.height * zoom
|
||||
};
|
||||
};
|
||||
|
||||
interface ScreenToIso {
|
||||
mouse: Coords;
|
||||
zoom: number;
|
||||
@@ -60,7 +49,7 @@ export const screenToIso = ({
|
||||
scroll,
|
||||
rendererSize
|
||||
}: ScreenToIso) => {
|
||||
const projectedTileSize = getProjectedTileSize({ zoom });
|
||||
const projectedTileSize = SizeUtils.multiply(PROJECTED_TILE_SIZE, zoom);
|
||||
const halfW = projectedTileSize.width / 2;
|
||||
const halfH = projectedTileSize.height / 2;
|
||||
|
||||
@@ -85,25 +74,19 @@ export const screenToIso = ({
|
||||
|
||||
interface GetTilePosition {
|
||||
tile: Coords;
|
||||
scroll: Scroll;
|
||||
zoom: number;
|
||||
origin?: TileOriginEnum;
|
||||
rendererSize: Size;
|
||||
}
|
||||
|
||||
export const getTilePosition = ({
|
||||
tile,
|
||||
scroll,
|
||||
zoom,
|
||||
origin = TileOriginEnum.CENTER
|
||||
}: GetTilePosition) => {
|
||||
const projectedTileSize = getProjectedTileSize({ zoom });
|
||||
const halfW = projectedTileSize.width / 2;
|
||||
const halfH = projectedTileSize.height / 2;
|
||||
const halfW = PROJECTED_TILE_SIZE.width / 2;
|
||||
const halfH = PROJECTED_TILE_SIZE.height / 2;
|
||||
|
||||
const position: Coords = {
|
||||
x: halfW * tile.x - halfW * tile.y + scroll.position.x,
|
||||
y: -(halfH * tile.x + halfH * tile.y) + scroll.position.y
|
||||
x: halfW * tile.x - halfW * tile.y,
|
||||
y: -(halfH * tile.x + halfH * tile.y)
|
||||
};
|
||||
|
||||
switch (origin) {
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { Coords, Size, Scroll } from 'src/types';
|
||||
import { CoordsUtils } from 'src/utils';
|
||||
import {
|
||||
getGridSubset,
|
||||
isWithinBounds,
|
||||
screenToIso,
|
||||
getProjectedTileSize
|
||||
} from '../renderer';
|
||||
import { CoordsUtils, SizeUtils } from 'src/utils';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { getGridSubset, isWithinBounds, screenToIso } from '../renderer';
|
||||
|
||||
const getRendererSize = (tileSize: Size, zoom: number = 1): Size => {
|
||||
const projectedTileSize = getProjectedTileSize({ zoom });
|
||||
const projectedTileSize = SizeUtils.multiply(PROJECTED_TILE_SIZE, zoom);
|
||||
|
||||
return {
|
||||
width: projectedTileSize.width * tileSize.width,
|
||||
|
||||
Reference in New Issue
Block a user