mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-24 06:58:48 -05:00
fix: corrects textbox selection not displaying correctly
This commit is contained in:
@@ -1,41 +1,23 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { CoordsUtils, getTextWidth, toPx } from 'src/utils';
|
||||
import { toPx, CoordsUtils } from 'src/utils';
|
||||
import { TextBox as TextBoxI } from 'src/types';
|
||||
import { DEFAULT_FONT_FAMILY } from 'src/config';
|
||||
import { useIsoProjection } from 'src/hooks/useIsoProjection';
|
||||
import { useTileSize } from 'src/hooks/useTileSize';
|
||||
import { useTextBoxProps } from 'src/hooks/useTextBoxProps';
|
||||
|
||||
interface Props {
|
||||
textBox: TextBoxI;
|
||||
isSelected?: boolean;
|
||||
}
|
||||
|
||||
export const TextBox = ({ textBox, isSelected }: Props) => {
|
||||
const { unprojectedTileSize } = useTileSize();
|
||||
|
||||
const fontProps = useMemo(() => {
|
||||
return {
|
||||
fontSize: toPx(unprojectedTileSize * textBox.fontSize),
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
fontWeight: 'bold'
|
||||
};
|
||||
}, [unprojectedTileSize, textBox.fontSize]);
|
||||
|
||||
const paddingX = useMemo(() => {
|
||||
return unprojectedTileSize * 0.2;
|
||||
}, [unprojectedTileSize]);
|
||||
|
||||
const textWidth = useMemo(() => {
|
||||
return getTextWidth(textBox.text, fontProps) + paddingX * 2;
|
||||
}, [textBox.text, fontProps, paddingX]);
|
||||
export const TextBox = ({ textBox }: Props) => {
|
||||
const { paddingX, fontProps } = useTextBoxProps(textBox);
|
||||
|
||||
const to = useMemo(() => {
|
||||
return CoordsUtils.add(textBox.tile, {
|
||||
x: Math.ceil(textWidth / unprojectedTileSize),
|
||||
x: textBox.size.width,
|
||||
y: 0
|
||||
});
|
||||
}, [textBox.tile, textWidth, unprojectedTileSize]);
|
||||
}, [textBox.tile, textBox.size.width]);
|
||||
|
||||
const { css } = useIsoProjection({
|
||||
from: textBox.tile,
|
||||
@@ -45,20 +27,6 @@ export const TextBox = ({ textBox, isSelected }: Props) => {
|
||||
|
||||
return (
|
||||
<Box sx={css}>
|
||||
{isSelected && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 1,
|
||||
borderRadius: 2,
|
||||
borderStyle: 'dashed'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -12,9 +12,6 @@ export const TextBoxes = () => {
|
||||
const mouse = useUiStateStore((state) => {
|
||||
return state.mouse;
|
||||
});
|
||||
const itemControls = useUiStateStore((state) => {
|
||||
return state.itemControls;
|
||||
});
|
||||
const textBoxes = useSceneStore((state) => {
|
||||
return state.textBoxes;
|
||||
});
|
||||
@@ -22,14 +19,7 @@ export const TextBoxes = () => {
|
||||
return (
|
||||
<>
|
||||
{textBoxes.map((textBox) => {
|
||||
return (
|
||||
<TextBox
|
||||
textBox={textBox}
|
||||
isSelected={
|
||||
itemControls?.type === 'TEXTBOX' && itemControls.id === textBox.id
|
||||
}
|
||||
/>
|
||||
);
|
||||
return <TextBox textBox={textBox} />;
|
||||
})}
|
||||
{mode.type === 'TEXTBOX' && (
|
||||
<TextBox
|
||||
@@ -38,7 +28,6 @@ export const TextBoxes = () => {
|
||||
...TEXTBOX_DEFAULTS,
|
||||
tile: mouse.position.tile
|
||||
})}
|
||||
isSelected
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ProjectionOrientationEnum } from 'src/types';
|
||||
import { CoordsUtils } from 'src/utils';
|
||||
import { useTextBox } from 'src/hooks/useTextBox';
|
||||
import { TransformControls } from './TransformControls';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const TextBoxTransformControls = ({ id }: Props) => {
|
||||
const textBox = useTextBox(id);
|
||||
|
||||
const to = useMemo(() => {
|
||||
if (textBox.orientation === ProjectionOrientationEnum.X) {
|
||||
return CoordsUtils.add(textBox.tile, {
|
||||
x: textBox.size.width,
|
||||
y: 0
|
||||
});
|
||||
}
|
||||
|
||||
return CoordsUtils.add(textBox.tile, {
|
||||
x: 0,
|
||||
y: -textBox.size.width
|
||||
});
|
||||
}, [textBox.orientation, textBox.size.width, textBox.tile]);
|
||||
|
||||
return <TransformControls from={textBox.tile} to={to} />;
|
||||
};
|
||||
@@ -23,7 +23,6 @@ export const TransformControls = ({
|
||||
onMouseOver,
|
||||
showCornerAnchors
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const { css, pxSize } = useIsoProjection({
|
||||
from,
|
||||
to
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { RectangleTransformControls } from './RectangleTransformControls';
|
||||
import { TextBoxTransformControls } from './TextBoxTransformControls';
|
||||
|
||||
export const TransformControlsManager = () => {
|
||||
const itemControls = useUiStateStore((state) => {
|
||||
@@ -10,6 +11,8 @@ export const TransformControlsManager = () => {
|
||||
switch (itemControls?.type) {
|
||||
case 'RECTANGLE':
|
||||
return <RectangleTransformControls id={itemControls.id} />;
|
||||
case 'TEXTBOX':
|
||||
return <TextBoxTransformControls id={itemControls.id} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ export const CONNECTOR_DEFAULTS: ConnectorDefaults = {
|
||||
|
||||
export const TEXTBOX_DEFAULTS = {
|
||||
fontSize: 0.6,
|
||||
text: 'Text'
|
||||
paddingX: 0.2,
|
||||
text: 'Text',
|
||||
fontWeight: 'bold'
|
||||
};
|
||||
|
||||
export const ZOOM_INCREMENT = 0.2;
|
||||
|
||||
22
src/hooks/useTextBoxProps.ts
Normal file
22
src/hooks/useTextBoxProps.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useMemo } from 'react';
|
||||
import { TextBox } from 'src/types';
|
||||
import { useTileSize } from 'src/hooks/useTileSize';
|
||||
import { DEFAULT_FONT_FAMILY, TEXTBOX_DEFAULTS } from 'src/config';
|
||||
|
||||
export const useTextBoxProps = (textBox: TextBox) => {
|
||||
const { unprojectedTileSize } = useTileSize();
|
||||
|
||||
const fontProps = useMemo(() => {
|
||||
return {
|
||||
fontSize: unprojectedTileSize * textBox.fontSize,
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
fontWeight: TEXTBOX_DEFAULTS.fontWeight
|
||||
};
|
||||
}, [unprojectedTileSize, textBox.fontSize]);
|
||||
|
||||
const paddingX = useMemo(() => {
|
||||
return unprojectedTileSize * TEXTBOX_DEFAULTS.paddingX;
|
||||
}, [unprojectedTileSize]);
|
||||
|
||||
return { paddingX, fontProps };
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import React, { createContext, useRef, useContext } from 'react';
|
||||
import { createStore, useStore } from 'zustand';
|
||||
import { produce } from 'immer';
|
||||
import { SceneStore } from 'src/types';
|
||||
import { DEFAULT_FONT_FAMILY, TEXTBOX_DEFAULTS } from 'src/config';
|
||||
import { sceneInput } from 'src/validation/scene';
|
||||
import {
|
||||
getItemById,
|
||||
@@ -10,7 +11,8 @@ import {
|
||||
connectorInputToConnector,
|
||||
textBoxInputToTextBox,
|
||||
sceneInputToScene,
|
||||
nodeInputToNode
|
||||
nodeInputToNode,
|
||||
getTextWidth
|
||||
} from 'src/utils';
|
||||
|
||||
const initialState = () => {
|
||||
@@ -149,6 +151,17 @@ const initialState = () => {
|
||||
id
|
||||
);
|
||||
|
||||
if (updates.text || updates.fontSize) {
|
||||
draftState.textBoxes[index].size = {
|
||||
width: getTextWidth(updates.text ?? textBox.text, {
|
||||
fontSize: updates.fontSize ?? textBox.fontSize,
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
fontWeight: TEXTBOX_DEFAULTS.fontWeight
|
||||
}),
|
||||
height: 1
|
||||
};
|
||||
}
|
||||
|
||||
draftState.textBoxes[index] = {
|
||||
...textBox,
|
||||
...updates
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
NodeInput,
|
||||
ConnectorStyleEnum
|
||||
} from 'src/types/inputs';
|
||||
import { ProjectionOrientationEnum } from 'src/types/common';
|
||||
import { Coords, Rect } from './common';
|
||||
import { ProjectionOrientationEnum, Coords, Rect, Size } from 'src/types';
|
||||
|
||||
export enum TileOriginEnum {
|
||||
CENTER = 'CENTER',
|
||||
@@ -66,6 +65,7 @@ export interface TextBox {
|
||||
tile: Coords;
|
||||
text: string;
|
||||
orientation: ProjectionOrientationEnum;
|
||||
size: Size;
|
||||
}
|
||||
|
||||
export interface Rectangle {
|
||||
|
||||
@@ -19,9 +19,11 @@ import {
|
||||
NODE_DEFAULTS,
|
||||
DEFAULT_COLOR,
|
||||
CONNECTOR_DEFAULTS,
|
||||
TEXTBOX_DEFAULTS
|
||||
TEXTBOX_DEFAULTS,
|
||||
DEFAULT_FONT_FAMILY,
|
||||
UNPROJECTED_TILE_SIZE
|
||||
} from 'src/config';
|
||||
import { getConnectorPath } from './renderer';
|
||||
import { getConnectorPath, getTextWidth } from 'src/utils';
|
||||
|
||||
export const nodeInputToNode = (nodeInput: NodeInput): Node => {
|
||||
return {
|
||||
@@ -88,13 +90,23 @@ export const connectorInputToConnector = (
|
||||
};
|
||||
|
||||
export const textBoxInputToTextBox = (textBoxInput: TextBoxInput): TextBox => {
|
||||
const fontSize = textBoxInput.fontSize ?? TEXTBOX_DEFAULTS.fontSize;
|
||||
|
||||
return {
|
||||
type: SceneItemTypeEnum.TEXTBOX,
|
||||
id: textBoxInput.id,
|
||||
orientation: textBoxInput.orientation ?? ProjectionOrientationEnum.X,
|
||||
fontSize: textBoxInput.fontSize ?? TEXTBOX_DEFAULTS.fontSize,
|
||||
fontSize,
|
||||
tile: textBoxInput.tile,
|
||||
text: textBoxInput.text
|
||||
text: textBoxInput.text,
|
||||
size: {
|
||||
width: getTextWidth(textBoxInput.text, {
|
||||
fontSize,
|
||||
fontFamily: DEFAULT_FONT_FAMILY,
|
||||
fontWeight: TEXTBOX_DEFAULTS.fontWeight
|
||||
}),
|
||||
height: 1
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
ZOOM_INCREMENT,
|
||||
MAX_ZOOM,
|
||||
MIN_ZOOM,
|
||||
CONNECTOR_DEFAULTS
|
||||
CONNECTOR_DEFAULTS,
|
||||
TEXTBOX_DEFAULTS
|
||||
} from 'src/config';
|
||||
import {
|
||||
Coords,
|
||||
@@ -26,7 +27,8 @@ import {
|
||||
CoordsUtils,
|
||||
clamp,
|
||||
roundToOneDecimalPlace,
|
||||
findPath
|
||||
findPath,
|
||||
toPx
|
||||
} from 'src/utils';
|
||||
|
||||
interface GetProjectedTileSize {
|
||||
@@ -479,11 +481,13 @@ export const getItemAtTile = ({
|
||||
|
||||
interface FontProps {
|
||||
fontWeight: number | string;
|
||||
fontSize: string;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
}
|
||||
|
||||
export const getTextWidth = (text: string, fontProps: FontProps) => {
|
||||
const paddingX = TEXTBOX_DEFAULTS.paddingX * UNPROJECTED_TILE_SIZE;
|
||||
const fontSizePx = toPx(fontProps.fontSize * UNPROJECTED_TILE_SIZE);
|
||||
const canvas: HTMLCanvasElement = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
@@ -491,10 +495,12 @@ export const getTextWidth = (text: string, fontProps: FontProps) => {
|
||||
throw new Error('Could not get canvas context');
|
||||
}
|
||||
|
||||
context.font = `${fontProps.fontWeight} ${fontProps.fontSize} ${fontProps.fontFamily}`;
|
||||
context.font = `${fontProps.fontWeight} ${fontSizePx} ${fontProps.fontFamily}`;
|
||||
const metrics = context.measureText(text);
|
||||
|
||||
return metrics.width;
|
||||
canvas.remove();
|
||||
|
||||
return Math.ceil((metrics.width + paddingX * 2) / UNPROJECTED_TILE_SIZE);
|
||||
};
|
||||
|
||||
export const outermostCornerPositions = [
|
||||
|
||||
Reference in New Issue
Block a user