7532aa93a6
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
104 lines
2.7 KiB
TypeScript
104 lines
2.7 KiB
TypeScript
import { useState, ReactNode } from 'react';
|
|
import { Text } from '@react-three/drei';
|
|
import * as THREE from 'three';
|
|
|
|
interface XR3DButtonProps {
|
|
position?: [number, number, number];
|
|
size?: [number, number];
|
|
label: string;
|
|
icon?: string;
|
|
active?: boolean;
|
|
disabled?: boolean;
|
|
onClick?: () => void;
|
|
color?: string;
|
|
fontSize?: number;
|
|
}
|
|
|
|
/** Flat 3D button suitable for in-XR HUD. Reacts to controller raycast. */
|
|
export function XR3DButton({
|
|
position = [0, 0, 0],
|
|
size = [0.08, 0.025],
|
|
label,
|
|
icon,
|
|
active = false,
|
|
disabled = false,
|
|
onClick,
|
|
color,
|
|
fontSize = 0.008,
|
|
}: XR3DButtonProps) {
|
|
const [hovered, setHovered] = useState(false);
|
|
const [w, h] = size;
|
|
|
|
const bgColor = disabled
|
|
? '#222'
|
|
: active
|
|
? (color ?? '#3b82f6')
|
|
: hovered
|
|
? '#444'
|
|
: '#1a1a1a';
|
|
const fgColor = disabled ? '#555' : active ? '#ffffff' : '#e5e7eb';
|
|
const borderColor = active ? (color ?? '#3b82f6') : hovered ? '#666' : '#333';
|
|
|
|
return (
|
|
<group
|
|
position={position}
|
|
onPointerOver={(e) => { e.stopPropagation(); if (!disabled) setHovered(true); }}
|
|
onPointerOut={(e) => { e.stopPropagation(); setHovered(false); }}
|
|
onClick={(e) => { e.stopPropagation(); if (!disabled && onClick) onClick(); }}
|
|
>
|
|
{/* Border */}
|
|
<mesh position={[0, 0, -0.0005]}>
|
|
<planeGeometry args={[w + 0.002, h + 0.002]} />
|
|
<meshBasicMaterial color={borderColor} transparent opacity={0.95} />
|
|
</mesh>
|
|
{/* Background */}
|
|
<mesh>
|
|
<planeGeometry args={[w, h]} />
|
|
<meshBasicMaterial color={bgColor} transparent opacity={0.92} side={THREE.DoubleSide} />
|
|
</mesh>
|
|
<Text
|
|
position={[0, 0, 0.001]}
|
|
fontSize={fontSize}
|
|
color={fgColor}
|
|
anchorX="center"
|
|
anchorY="middle"
|
|
maxWidth={w * 0.95}
|
|
>
|
|
{icon ? `${icon} ${label}` : label}
|
|
</Text>
|
|
</group>
|
|
);
|
|
}
|
|
|
|
interface XR3DPanelProps {
|
|
position?: [number, number, number];
|
|
size?: [number, number];
|
|
color?: string;
|
|
opacity?: number;
|
|
children?: ReactNode;
|
|
}
|
|
|
|
/** Background panel for grouping XR3DButtons. */
|
|
export function XR3DPanel({
|
|
position = [0, 0, 0],
|
|
size = [0.4, 0.3],
|
|
color = '#0a0a0a',
|
|
opacity = 0.85,
|
|
children,
|
|
}: XR3DPanelProps) {
|
|
const [w, h] = size;
|
|
return (
|
|
<group position={position}>
|
|
<mesh position={[0, 0, -0.001]}>
|
|
<planeGeometry args={[w + 0.004, h + 0.004]} />
|
|
<meshBasicMaterial color="#3b82f6" transparent opacity={0.6} />
|
|
</mesh>
|
|
<mesh>
|
|
<planeGeometry args={[w, h]} />
|
|
<meshBasicMaterial color={color} transparent opacity={opacity} side={THREE.DoubleSide} />
|
|
</mesh>
|
|
{children}
|
|
</group>
|
|
);
|
|
}
|