11// Slider.tsx
22import React , { useState , useEffect , useRef , useCallback } from 'react'
3+ import { useFloating , arrow , FloatingArrow , offset as offsetMiddleware } from '@floating-ui/react'
34import styles from './slider.module.css'
45import SharedHudVars from './SharedHudVars'
56
@@ -18,6 +19,9 @@ interface Props extends React.ComponentProps<'div'> {
1819 updateOnDragEnd ?: boolean ;
1920}
2021
22+ const ARROW_HEIGHT = 7
23+ const GAP = 0
24+
2125const Slider : React . FC < Props > = ( {
2226 label,
2327 unit = '%' ,
@@ -45,6 +49,11 @@ const Slider: React.FC<Props> = ({
4549 const timeoutRef = useRef < NodeJS . Timeout | null > ( null )
4650 const lastValueRef = useRef < number > ( valueProp )
4751
52+ // Gamepad support
53+ const [ showGamepadTooltip , setShowGamepadTooltip ] = useState ( false )
54+ const lastChangeTime = useRef ( 0 )
55+ const containerRef = useRef < HTMLDivElement > ( null ! )
56+
4857 useEffect ( ( ) => {
4958 setValue ( valueProp )
5059 } , [ valueProp ] )
@@ -89,15 +98,80 @@ const Slider: React.FC<Props> = ({
8998 }
9099 } , [ updateValue ] )
91100
101+ // Handle gamepad hover and input changes
102+ useEffect ( ( ) => {
103+ const element = containerRef . current
104+ if ( ! element ) return
105+
106+ const handleMouseOver = ( e : MouseEvent & { isGamepadCursor ?: boolean } ) => {
107+ if ( e . isGamepadCursor && ! disabledReason ) {
108+ setShowGamepadTooltip ( true )
109+ }
110+ }
111+
112+ const handleMouseOut = ( e : MouseEvent & { isGamepadCursor ?: boolean } ) => {
113+ if ( e . isGamepadCursor ) {
114+ setShowGamepadTooltip ( false )
115+ }
116+ }
117+
118+ const handleGamepadInputChange = ( e : CustomEvent < { direction : number , value : number , isStickMovement : boolean } > ) => {
119+ if ( disabledReason ) return
120+
121+ const now = Date . now ( )
122+ // Throttle changes to prevent too rapid updates
123+ if ( now - lastChangeTime . current < 200 && e . detail . isStickMovement ) return
124+ lastChangeTime . current = now
125+
126+ const step = 1
127+ const newValue = value + ( e . detail . direction * step )
128+
129+ // Apply min/max constraints
130+ const constrainedValue = Math . max ( min , Math . min ( max , newValue ) )
131+
132+ setValue ( constrainedValue )
133+ fireValueUpdate ( false , constrainedValue )
134+ }
135+
136+ element . addEventListener ( 'mouseover' , handleMouseOver as EventListener )
137+ element . addEventListener ( 'mouseout' , handleMouseOut as EventListener )
138+ element . addEventListener ( 'gamepadInputChange' , handleGamepadInputChange as EventListener )
139+
140+ return ( ) => {
141+ element . removeEventListener ( 'mouseover' , handleMouseOver as EventListener )
142+ element . removeEventListener ( 'mouseout' , handleMouseOut as EventListener )
143+ element . removeEventListener ( 'gamepadInputChange' , handleGamepadInputChange as EventListener )
144+ }
145+ } , [ disabledReason , value , min , max ] )
146+
92147 const fireValueUpdate = ( dragEnd : boolean , v = value ) => {
93148 throttledUpdateValue ( v , dragEnd )
94149 }
95150
96151 const labelText = `${ label } : ${ valueDisplay ?? value } ${ unit } `
97152
153+ const arrowRef = useRef < any > ( null )
154+ const { refs, floatingStyles, context } = useFloating ( {
155+ middleware : [
156+ arrow ( {
157+ element : arrowRef
158+ } ) ,
159+ offsetMiddleware ( ARROW_HEIGHT + GAP ) ,
160+ ] ,
161+ placement : 'top' ,
162+ } )
163+
98164 return (
99165 < SharedHudVars >
100- < div className = { `${ styles [ 'slider-container' ] } settings-text-container ${ labelText . length > 17 ? 'settings-text-container-long' : '' } ` } style = { { width } } { ...divProps } >
166+ < div
167+ ref = { ( node ) => {
168+ containerRef . current = node !
169+ refs . setReference ( node )
170+ } }
171+ className = { `${ styles [ 'slider-container' ] } settings-text-container ${ labelText . length > 17 ? 'settings-text-container-long' : '' } ` }
172+ style = { { width } }
173+ { ...divProps }
174+ >
101175 < input
102176 type = "range"
103177 className = { styles . slider }
@@ -127,6 +201,26 @@ const Slider: React.FC<Props> = ({
127201 { labelText }
128202 </ label >
129203 </ div >
204+ { showGamepadTooltip && (
205+ < div
206+ ref = { refs . setFloating }
207+ style = { {
208+ ...floatingStyles ,
209+ background : 'rgba(0, 0, 0, 0.8)' ,
210+ fontSize : 10 ,
211+ pointerEvents : 'none' ,
212+ userSelect : 'none' ,
213+ padding : '4px 8px' ,
214+ borderRadius : 4 ,
215+ textShadow : '1px 1px 2px BLACK' ,
216+ zIndex : 1000 ,
217+ whiteSpace : 'nowrap'
218+ } }
219+ >
220+ Use right stick left/right to change value
221+ < FloatingArrow ref = { arrowRef } context = { context } style = { { fill : 'rgba(0, 0, 0, 0.8)' } } />
222+ </ div >
223+ ) }
130224 </ SharedHudVars >
131225 )
132226}
0 commit comments