From 2d4a1e169b0c687261b972a9c3d595ad302ed642 Mon Sep 17 00:00:00 2001 From: Alexander Lazarenko Date: Wed, 9 Jul 2025 21:12:54 +0300 Subject: [PATCH] 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`. --- package.json | 3 +- src/pages/Route/route-preview/Station.tsx | 70 +++++++++++++++++------ 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 5076d9e..19c708f 100644 --- a/package.json +++ b/package.json @@ -56,5 +56,6 @@ "typescript": "~5.8.3", "typescript-eslint": "^8.30.1", "vite": "^6.3.5" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/pages/Route/route-preview/Station.tsx b/src/pages/Route/route-preview/Station.tsx index effe436..bcf46b4 100644 --- a/src/pages/Route/route-preview/Station.tsx +++ b/src/pages/Route/route-preview/Station.tsx @@ -1,5 +1,5 @@ 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"; // --- Заглушки для зависимостей (замените на ваши реальные импорты) --- @@ -67,6 +67,8 @@ interface StationProps { labelAlign?: LabelAlign; /** Callback для изменения внутреннего выравнивания */ onLabelAlignChange?: (align: LabelAlign) => void; + /** Callback для отслеживания наведения на текст */ + onTextHover?: (isHovered: boolean) => void; } interface LabelAlignmentControlProps { @@ -82,6 +84,18 @@ interface LabelAlignmentControlProps { interface StationLabelProps extends Omit {} +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, labelAlign: labelAlignProp = "center", onLabelAlignChange, + onTextHover, }: Readonly) => { const { language } = languageStore; const { rotation, scale } = useTransform(); @@ -272,6 +287,7 @@ const StationLabel = observer( hideTimer.current = null; } setIsHovered(true); + onTextHover?.(true); // Call the callback to indicate text is hovered }; const handleControlPointerEnter = () => { @@ -282,6 +298,7 @@ const StationLabel = observer( } setIsControlHovered(true); setIsHovered(true); + onTextHover?.(true); // Call the callback to indicate text/control is hovered }; const handleControlPointerLeave = () => { @@ -290,6 +307,7 @@ const StationLabel = observer( if (!isHovered) { hideTimer.current = setTimeout(() => { setIsHovered(false); + onTextHover?.(false); // Call the callback to indicate neither text nor control is hovered }, 0); } }; @@ -302,6 +320,7 @@ const StationLabel = observer( if (!isControlHovered) { setIsControlHovered(false); } + onTextHover?.(false); // Call the callback to indicate text is no longer hovered }, 100); // Увеличиваем время до скрытия панели }; @@ -347,7 +366,7 @@ const StationLabel = observer( const compensatedRuFontSize = (26 * 0.75) / scale; const compensatedNameFontSize = (16 * 0.75) / scale; const minDistance = 30; - const compensatedOffset = Math.max(minDistance / scale, 24 / scale); + const compensatedOffset = minDistance / scale; const textBlockPosition = anchorPoint || position; const finalLabelBlockAnchor = labelBlockAnchorProp || "right center"; const labelBlockAnchor = @@ -372,17 +391,20 @@ const StationLabel = observer( const handlePointerMove = (e: FederatedMouseEvent) => { if (!isPointerDown) return; + if (!isDragging) { const dx = e.global.x - mouseStartPos.current.x; const dy = e.global.y - mouseStartPos.current.y; if (Math.hypot(dx, dy) > 3) setIsDragging(true); 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 = { - x: dragStartPos.current.x + dx, - y: dragStartPos.current.y + dy, + x: dragStartPos.current.x + dx_screen, + y: dragStartPos.current.y + dy_screen, }; // Проверяем, изменилась ли позиция @@ -410,6 +432,11 @@ const StationLabel = observer( setStationAlign(station.id, numericAlign); }; + const dynamicAnchor = useMemo( + () => getAnchorFromOffset(position.x, position.y), + [position.x, position.y] + ); + // Функция для расчета позиции нижнего лейбла относительно ширины верхнего const getSecondLabelPosition = (): number => { if (!ruLabelWidth) return 0; @@ -459,13 +486,11 @@ const StationLabel = observer( onGlobalPointerMove={handlePointerMove} > {ruLabel && ( ) => { + const [isTextHovered, setIsTextHovered] = useState(false); + const draw = useCallback( (g: Graphics) => { g.clear(); @@ -531,12 +558,22 @@ export const Station = ({ station.latitude, station.longitude ); + const radius = STATION_RADIUS; + const strokeWidth = STATION_OUTLINE_WIDTH; + g.circle(coordinates.x * UP_SCALE, coordinates.y * UP_SCALE, radius); - g.fill({ color: PATH_COLOR }); - g.stroke({ color: BACKGROUND_COLOR, width: STATION_OUTLINE_WIDTH }); + + // 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.stroke({ color: BACKGROUND_COLOR, width: strokeWidth }); + } }, - [station.latitude, station.longitude] + [station.latitude, station.longitude, isTextHovered] ); return ( @@ -549,6 +586,7 @@ export const Station = ({ labelBlockAnchor={labelBlockAnchor} labelAlign={labelAlign} onLabelAlignChange={onLabelAlignChange} + onTextHover={setIsTextHovered} /> );