From c324473c32eb1edefd3a6e2e3103a92ba0f8883f Mon Sep 17 00:00:00 2001 From: andrepat0 Date: Thu, 7 Nov 2024 17:41:36 +0100 Subject: [PATCH] feat: added position controls for height and depth --- .../positionControls/positionControls.css | 65 +++++++ .../positionControls/positionControls.tsx | 179 ++++++++++++++++++ src/components/Avatar/AvatarView/index.tsx | 30 ++- 3 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css create mode 100644 src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx diff --git a/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css b/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css new file mode 100644 index 00000000..623d161b --- /dev/null +++ b/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css @@ -0,0 +1,65 @@ +.memori--position-controls{ + position: fixed; + z-index: 1000; + top: 60px; + left: 15px; + display: flex; + flex-direction: column; + gap: 0.5rem; + min-width: 400px; + height: auto !important; + text-align: left; + max-width: 400px; + padding: calc(var(--memori-inner-content-pad) / 4) calc(var(--memori-inner-content-pad) / 2); + border-radius: 10px; + margin-left: auto; + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + background-color: var(--memori-inner-bg, #fff); +} + +.memori--position-controls-helper-text{ + font-size: 0.875rem; + color: #3b3e46; +} + +.memori--slider-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.memori--preset-button{ + border: 1px solid #ccc; + padding: 0.5rem; +} + +.memori--preset-button:hover{ + background-color: #f0f0f0; +} + +.memori--preset-buttons{ + display: flex; + gap: 0.5rem; +} + +.memori--slider-label{ + font-size: 0.875rem; + color: #3b3e46; + margin: 0px; +} + +.memori--position-controls-close{ + position: absolute; + top: 0; + right: 0; +} + +.memori--position-controls-close-button{ + padding: 0.5rem; +} + +.memori--position-controls-close-button:hover, .memori--position-controls-close-button:active, .memori--position-controls-close-button:focus { + border: none !important; + box-shadow: none !important; +} \ No newline at end of file diff --git a/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx b/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx new file mode 100644 index 00000000..c1828031 --- /dev/null +++ b/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.tsx @@ -0,0 +1,179 @@ +import { useEffect, useRef } from 'react'; +import { setLocalConfig } from '../../../../../helpers/configuration'; +import { useTranslation } from 'react-i18next'; +import Slider from '../../../../../components/ui/Slider'; +import './positionControls.css'; +import Button from '../../../../ui/Button'; +import Close from '../../../../icons/Close'; + +interface PositionControlsProps { + avatarHeight: number; + avatarDepth: number; + setAvatarHeight: (value: number) => void; + setAvatarDepth: (value: number) => void; + isZoomed?: boolean; + setEnablePositionControls: (value: boolean) => void; +} + +// eslint-disable-next-line no-undef +const PositionControls: React.FC = ({ + avatarHeight, + avatarDepth, + setAvatarHeight, + setAvatarDepth, + isZoomed = false, + setEnablePositionControls, +}: PositionControlsProps) => { + const { t } = useTranslation(); + const settingsRef = useRef>({ + height: avatarHeight, + depth: avatarDepth, + zoomed: false, + normal: false, + far: false, + }); + + // Update settings when values change externally + // useEffect(() => { + // settingsRef.current.height = avatarHeight; + // settingsRef.current.depth = avatarDepth; + // }, [avatarHeight, avatarDepth]); + + // Keyboard controls for depth + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === '-' || event.key === '_') { + const newValue = Math.min(settingsRef.current.depth + 10, 100); + setAvatarDepth(newValue); + setLocalConfig('avatarDepth', newValue); + } else if (event.key === '+' || event.key === '=') { + const newValue = Math.max(settingsRef.current.depth - 10, -100); + setAvatarDepth(newValue); + setLocalConfig('avatarDepth', newValue); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [setAvatarDepth]); + + useEffect(() => { + const handleArrowUp = (event: KeyboardEvent) => { + if (event.key === 'ArrowUp') { + const newValue = settingsRef.current.height + 5; + setAvatarHeight(newValue); + setLocalConfig('avatarHeight', newValue); + } + }; + + const handleArrowDown = (event: KeyboardEvent) => { + if (event.key === 'ArrowDown') { + const newValue = settingsRef.current.height - 5; + setAvatarHeight(newValue); + setLocalConfig('avatarHeight', newValue); + } + }; + + window.addEventListener('keydown', handleArrowUp); + window.addEventListener('keydown', handleArrowDown); + + return () => { + window.removeEventListener('keydown', handleArrowUp); + window.removeEventListener('keydown', handleArrowDown); + }; + }, [setAvatarHeight]); + + return ( +
+
+
+
+

+ {t('Use the arrow keys to adjust the avatar height')} +
+ {t('Use +/- to adjust the avatar depth')} +

+
+
+ {t('Height')}} + step={1} + onChange={(value: number) => { + setAvatarHeight(value); + setLocalConfig('avatarHeight', value); + }} + /> +
+
+ {t('Depth')}} + onChange={(value: number) => { + setAvatarDepth(value); + setLocalConfig('avatarDepth', value); + }} + /> +
+
+ + + +
+
+ ); +}; + +export default PositionControls; diff --git a/src/components/Avatar/AvatarView/index.tsx b/src/components/Avatar/AvatarView/index.tsx index fa9240a9..79525f05 100644 --- a/src/components/Avatar/AvatarView/index.tsx +++ b/src/components/Avatar/AvatarView/index.tsx @@ -11,6 +11,8 @@ import { isAndroid, isiOS } from '../../../helpers/utils'; import { AvatarView } from './AvatarComponent/avatarComponent'; import Loader from './AvatarComponent/components/loader'; import { Vector3 } from 'three'; +import PositionControls from './AvatarComponent/positionControls/positionControls'; +import { getLocalConfig } from '../../../helpers/configuration'; export interface Props { url: string; sex: 'MALE' | 'FEMALE'; @@ -28,7 +30,7 @@ export interface Props { isZoomed?: boolean; chatEmission?: any; enablePositionControls?: boolean; - setEnablePositionControls?: (value: boolean) => void; + setEnablePositionControls: (value: boolean) => void; setMeshRef?: any; stopProcessing: () => void; resetVisemeQueue: () => void; @@ -74,6 +76,8 @@ const getLightingComponent = () => target: [0, 0, 0] }; }; + + export default function ContainerAvatarView({ url, @@ -98,10 +102,12 @@ const getLightingComponent = () => setEnablePositionControls, }: Props) { const [cameraZ, setCameraZ] = useState(() => getCameraSettings(halfBody).position[2]); - + const [avatarHeight, setAvatarHeight] = useState(getLocalConfig('avatarHeight', 50)); + const [avatarDepth, setAvatarDepth] = useState(getLocalConfig('avatarDepth', 50)); return ( - + updateCurrentViseme={updateCurrentViseme} stopProcessing={stopProcessing} resetVisemeQueue={resetVisemeQueue} - enablePositionControls={enablePositionControls} - setEnablePositionControls={setEnablePositionControls} setCameraZ={setCameraZ} + avatarHeight={avatarHeight} + avatarDepth={avatarDepth} /> - + + {enablePositionControls && ( + + )} + ); } \ No newline at end of file