Add hover effects and dynamic anchor computation to Station component
- Integrated `onTextHover` callback for handling text hover states. - Implemented dynamic anchor calculation using `useMemo` for improved positioning. - Updated visual feedback by highlighting labels on hover. - Adjusted offset calculations and added interactive pointer movement refinements. - Added `packageManager` field to `package.json`.
This commit is contained in:
@ -56,5 +56,6 @@
|
|||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.30.1",
|
"typescript-eslint": "^8.30.1",
|
||||||
"vite": "^6.3.5"
|
"vite": "^6.3.5"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FederatedMouseEvent, Graphics } from "pixi.js";
|
import { FederatedMouseEvent, Graphics } from "pixi.js";
|
||||||
import { useCallback, useState, useEffect, useRef, FC } from "react";
|
import {useCallback, useState, useEffect, useRef, FC, useMemo} from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
// --- Заглушки для зависимостей (замените на ваши реальные импорты) ---
|
// --- Заглушки для зависимостей (замените на ваши реальные импорты) ---
|
||||||
@ -67,6 +67,8 @@ interface StationProps {
|
|||||||
labelAlign?: LabelAlign;
|
labelAlign?: LabelAlign;
|
||||||
/** Callback для изменения внутреннего выравнивания */
|
/** Callback для изменения внутреннего выравнивания */
|
||||||
onLabelAlignChange?: (align: LabelAlign) => void;
|
onLabelAlignChange?: (align: LabelAlign) => void;
|
||||||
|
/** Callback для отслеживания наведения на текст */
|
||||||
|
onTextHover?: (isHovered: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LabelAlignmentControlProps {
|
interface LabelAlignmentControlProps {
|
||||||
@ -82,6 +84,18 @@ interface LabelAlignmentControlProps {
|
|||||||
interface StationLabelProps
|
interface StationLabelProps
|
||||||
extends Omit<StationProps, "ruLabelAnchor" | "nameLabelAnchor"> {}
|
extends Omit<StationProps, "ruLabelAnchor" | "nameLabelAnchor"> {}
|
||||||
|
|
||||||
|
const getAnchorFromOffset = (offsetX: number, offsetY: number): { x: number; y: number } => {
|
||||||
|
if (offsetX === 0 && offsetY === 0) {
|
||||||
|
return { x: 0.5, y: 0.5 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const length = Math.hypot(offsetX, offsetY);
|
||||||
|
const nx = offsetX / length;
|
||||||
|
const ny = offsetY / length;
|
||||||
|
|
||||||
|
return { x: (1 - nx) / 2, y: (1 - ny) / 2 };
|
||||||
|
};
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Компонент: Панель управления выравниванием в стиле УрФУ
|
// Компонент: Панель управления выравниванием в стиле УрФУ
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@ -240,6 +254,7 @@ const StationLabel = observer(
|
|||||||
labelBlockAnchor: labelBlockAnchorProp,
|
labelBlockAnchor: labelBlockAnchorProp,
|
||||||
labelAlign: labelAlignProp = "center",
|
labelAlign: labelAlignProp = "center",
|
||||||
onLabelAlignChange,
|
onLabelAlignChange,
|
||||||
|
onTextHover,
|
||||||
}: Readonly<StationLabelProps>) => {
|
}: Readonly<StationLabelProps>) => {
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
const { rotation, scale } = useTransform();
|
const { rotation, scale } = useTransform();
|
||||||
@ -272,6 +287,7 @@ const StationLabel = observer(
|
|||||||
hideTimer.current = null;
|
hideTimer.current = null;
|
||||||
}
|
}
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
|
onTextHover?.(true); // Call the callback to indicate text is hovered
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleControlPointerEnter = () => {
|
const handleControlPointerEnter = () => {
|
||||||
@ -282,6 +298,7 @@ const StationLabel = observer(
|
|||||||
}
|
}
|
||||||
setIsControlHovered(true);
|
setIsControlHovered(true);
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
|
onTextHover?.(true); // Call the callback to indicate text/control is hovered
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleControlPointerLeave = () => {
|
const handleControlPointerLeave = () => {
|
||||||
@ -290,6 +307,7 @@ const StationLabel = observer(
|
|||||||
if (!isHovered) {
|
if (!isHovered) {
|
||||||
hideTimer.current = setTimeout(() => {
|
hideTimer.current = setTimeout(() => {
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
|
onTextHover?.(false); // Call the callback to indicate neither text nor control is hovered
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -302,6 +320,7 @@ const StationLabel = observer(
|
|||||||
if (!isControlHovered) {
|
if (!isControlHovered) {
|
||||||
setIsControlHovered(false);
|
setIsControlHovered(false);
|
||||||
}
|
}
|
||||||
|
onTextHover?.(false); // Call the callback to indicate text is no longer hovered
|
||||||
}, 100); // Увеличиваем время до скрытия панели
|
}, 100); // Увеличиваем время до скрытия панели
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -347,7 +366,7 @@ const StationLabel = observer(
|
|||||||
const compensatedRuFontSize = (26 * 0.75) / scale;
|
const compensatedRuFontSize = (26 * 0.75) / scale;
|
||||||
const compensatedNameFontSize = (16 * 0.75) / scale;
|
const compensatedNameFontSize = (16 * 0.75) / scale;
|
||||||
const minDistance = 30;
|
const minDistance = 30;
|
||||||
const compensatedOffset = Math.max(minDistance / scale, 24 / scale);
|
const compensatedOffset = minDistance / scale;
|
||||||
const textBlockPosition = anchorPoint || position;
|
const textBlockPosition = anchorPoint || position;
|
||||||
const finalLabelBlockAnchor = labelBlockAnchorProp || "right center";
|
const finalLabelBlockAnchor = labelBlockAnchorProp || "right center";
|
||||||
const labelBlockAnchor =
|
const labelBlockAnchor =
|
||||||
@ -372,17 +391,20 @@ const StationLabel = observer(
|
|||||||
|
|
||||||
const handlePointerMove = (e: FederatedMouseEvent) => {
|
const handlePointerMove = (e: FederatedMouseEvent) => {
|
||||||
if (!isPointerDown) return;
|
if (!isPointerDown) return;
|
||||||
|
|
||||||
if (!isDragging) {
|
if (!isDragging) {
|
||||||
const dx = e.global.x - mouseStartPos.current.x;
|
const dx = e.global.x - mouseStartPos.current.x;
|
||||||
const dy = e.global.y - mouseStartPos.current.y;
|
const dy = e.global.y - mouseStartPos.current.y;
|
||||||
if (Math.hypot(dx, dy) > 3) setIsDragging(true);
|
if (Math.hypot(dx, dy) > 3) setIsDragging(true);
|
||||||
else return;
|
else return;
|
||||||
}
|
}
|
||||||
const dx = (e.global.x - mouseStartPos.current.x) / scale;
|
|
||||||
const dy = (e.global.y - mouseStartPos.current.y) / scale;
|
const dx_screen = e.global.x - mouseStartPos.current.x;
|
||||||
|
const dy_screen = e.global.y - mouseStartPos.current.y;
|
||||||
|
|
||||||
const newPosition = {
|
const newPosition = {
|
||||||
x: dragStartPos.current.x + dx,
|
x: dragStartPos.current.x + dx_screen,
|
||||||
y: dragStartPos.current.y + dy,
|
y: dragStartPos.current.y + dy_screen,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Проверяем, изменилась ли позиция
|
// Проверяем, изменилась ли позиция
|
||||||
@ -410,6 +432,11 @@ const StationLabel = observer(
|
|||||||
setStationAlign(station.id, numericAlign);
|
setStationAlign(station.id, numericAlign);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dynamicAnchor = useMemo(
|
||||||
|
() => getAnchorFromOffset(position.x, position.y),
|
||||||
|
[position.x, position.y]
|
||||||
|
);
|
||||||
|
|
||||||
// Функция для расчета позиции нижнего лейбла относительно ширины верхнего
|
// Функция для расчета позиции нижнего лейбла относительно ширины верхнего
|
||||||
const getSecondLabelPosition = (): number => {
|
const getSecondLabelPosition = (): number => {
|
||||||
if (!ruLabelWidth) return 0;
|
if (!ruLabelWidth) return 0;
|
||||||
@ -460,12 +487,10 @@ const StationLabel = observer(
|
|||||||
>
|
>
|
||||||
<pixiContainer
|
<pixiContainer
|
||||||
position={{
|
position={{
|
||||||
x:
|
x: (position.x + Math.cos(Math.atan2(position.y, position.x))) / scale,
|
||||||
textBlockPosition.x +
|
y: (position.y + Math.sin(Math.atan2(position.y, position.x))) / scale,
|
||||||
compensatedOffset * (labelBlockAnchor.x === 1 ? -1 : 1),
|
|
||||||
y: textBlockPosition.y,
|
|
||||||
}}
|
}}
|
||||||
anchor={labelBlockAnchor}
|
anchor={dynamicAnchor}
|
||||||
>
|
>
|
||||||
{ruLabel && (
|
{ruLabel && (
|
||||||
<pixiText
|
<pixiText
|
||||||
@ -524,6 +549,8 @@ export const Station = ({
|
|||||||
labelAlign,
|
labelAlign,
|
||||||
onLabelAlignChange,
|
onLabelAlignChange,
|
||||||
}: Readonly<StationProps>) => {
|
}: Readonly<StationProps>) => {
|
||||||
|
const [isTextHovered, setIsTextHovered] = useState(false);
|
||||||
|
|
||||||
const draw = useCallback(
|
const draw = useCallback(
|
||||||
(g: Graphics) => {
|
(g: Graphics) => {
|
||||||
g.clear();
|
g.clear();
|
||||||
@ -531,12 +558,22 @@ export const Station = ({
|
|||||||
station.latitude,
|
station.latitude,
|
||||||
station.longitude
|
station.longitude
|
||||||
);
|
);
|
||||||
|
|
||||||
const radius = STATION_RADIUS;
|
const radius = STATION_RADIUS;
|
||||||
|
const strokeWidth = STATION_OUTLINE_WIDTH;
|
||||||
|
|
||||||
g.circle(coordinates.x * UP_SCALE, coordinates.y * UP_SCALE, radius);
|
g.circle(coordinates.x * UP_SCALE, coordinates.y * UP_SCALE, radius);
|
||||||
|
|
||||||
|
// Change fill color when text is hovered
|
||||||
|
if (isTextHovered) {
|
||||||
|
g.fill({ color: 0x00AAFF }); // Highlight color when hovered
|
||||||
|
g.stroke({ color: 0xFFFFFF, width: strokeWidth + 1 }); // Brighter outline when hovered
|
||||||
|
} else {
|
||||||
g.fill({ color: PATH_COLOR });
|
g.fill({ color: PATH_COLOR });
|
||||||
g.stroke({ color: BACKGROUND_COLOR, width: STATION_OUTLINE_WIDTH });
|
g.stroke({ color: BACKGROUND_COLOR, width: strokeWidth });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[station.latitude, station.longitude]
|
[station.latitude, station.longitude, isTextHovered]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -549,6 +586,7 @@ export const Station = ({
|
|||||||
labelBlockAnchor={labelBlockAnchor}
|
labelBlockAnchor={labelBlockAnchor}
|
||||||
labelAlign={labelAlign}
|
labelAlign={labelAlign}
|
||||||
onLabelAlignChange={onLabelAlignChange}
|
onLabelAlignChange={onLabelAlignChange}
|
||||||
|
onTextHover={setIsTextHovered}
|
||||||
/>
|
/>
|
||||||
</pixiContainer>
|
</pixiContainer>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user