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' ] } ${ 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' ] } ${ 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