@@ -19,6 +19,7 @@ use bevy_input::keyboard::{KeyCode, KeyboardInput};
1919use bevy_input:: ButtonState ;
2020use bevy_input_focus:: FocusedInput ;
2121use bevy_log:: warn_once;
22+ use bevy_math:: ops;
2223use bevy_picking:: events:: { Drag , DragEnd , DragStart , Pointer , Press } ;
2324use bevy_ui:: { ComputedNode , ComputedNodeTarget , InteractionDisabled , UiGlobalTransform , UiScale } ;
2425
@@ -38,7 +39,8 @@ pub enum TrackClick {
3839
3940/// A headless slider widget, which can be used to build custom sliders. Sliders have a value
4041/// (represented by the [`SliderValue`] component) and a range (represented by [`SliderRange`]). An
41- /// optional step size can be specified via [`SliderStep`].
42+ /// optional step size can be specified via [`SliderStep`], and you can control the rounding
43+ /// during dragging with [`SliderPrecision`].
4244///
4345/// You can also control the slider remotely by triggering a [`SetSliderValue`] event on it. This
4446/// can be useful in a console environment for controlling the value gamepad inputs.
@@ -187,6 +189,25 @@ impl Default for SliderStep {
187189 }
188190}
189191
192+ /// A component which controls the rounding of the slider value during dragging.
193+ ///
194+ /// Stepping is not affected, although presumably the step size will be an integer multiple of the
195+ /// rounding factor. This also doesn't prevent the slider value from being set to non-rounded values
196+ /// by other means, such as manually entering digits via a numeric input field.
197+ ///
198+ /// The value in this component represents the number of decimal places of desired precision, so a
199+ /// value of 2 would round to the nearest 1/100th. A value of -3 would round to the nearest
200+ /// thousand.
201+ #[ derive( Component , Debug , Default , Clone , Copy ) ]
202+ pub struct SliderPrecision ( pub i32 ) ;
203+
204+ impl SliderPrecision {
205+ fn round ( & self , value : f32 ) -> f32 {
206+ let factor = ops:: powf ( 10.0_f32 , self . 0 as f32 ) ;
207+ ( value * factor) . round ( ) / factor
208+ }
209+ }
210+
190211/// Component used to manage the state of a slider during dragging.
191212#[ derive( Component , Default ) ]
192213pub struct CoreSliderDragState {
@@ -204,6 +225,7 @@ pub(crate) fn slider_on_pointer_down(
204225 & SliderValue ,
205226 & SliderRange ,
206227 & SliderStep ,
228+ Option < & SliderPrecision > ,
207229 & ComputedNode ,
208230 & ComputedNodeTarget ,
209231 & UiGlobalTransform ,
@@ -217,8 +239,17 @@ pub(crate) fn slider_on_pointer_down(
217239 if q_thumb. contains ( trigger. target ( ) ) {
218240 // Thumb click, stop propagation to prevent track click.
219241 trigger. propagate ( false ) ;
220- } else if let Ok ( ( slider, value, range, step, node, node_target, transform, disabled) ) =
221- q_slider. get ( trigger. target ( ) )
242+ } else if let Ok ( (
243+ slider,
244+ value,
245+ range,
246+ step,
247+ precision,
248+ node,
249+ node_target,
250+ transform,
251+ disabled,
252+ ) ) = q_slider. get ( trigger. target ( ) )
222253 {
223254 // Track click
224255 trigger. propagate ( false ) ;
@@ -257,7 +288,9 @@ pub(crate) fn slider_on_pointer_down(
257288 value. 0 + step. 0
258289 }
259290 }
260- TrackClick :: Snap => click_val,
291+ TrackClick :: Snap => precision
292+ . map ( |prec| prec. round ( click_val) )
293+ . unwrap_or ( click_val) ,
261294 } ) ;
262295
263296 if matches ! ( slider. on_change, Callback :: Ignore ) {
@@ -296,6 +329,7 @@ pub(crate) fn slider_on_drag(
296329 & ComputedNode ,
297330 & CoreSlider ,
298331 & SliderRange ,
332+ Option < & SliderPrecision > ,
299333 & UiGlobalTransform ,
300334 & mut CoreSliderDragState ,
301335 Has < InteractionDisabled > ,
@@ -305,7 +339,8 @@ pub(crate) fn slider_on_drag(
305339 mut commands : Commands ,
306340 ui_scale : Res < UiScale > ,
307341) {
308- if let Ok ( ( node, slider, range, transform, drag, disabled) ) = q_slider. get_mut ( trigger. target ( ) )
342+ if let Ok ( ( node, slider, range, precision, transform, drag, disabled) ) =
343+ q_slider. get_mut ( trigger. target ( ) )
309344 {
310345 trigger. propagate ( false ) ;
311346 if drag. dragging && !disabled {
@@ -320,17 +355,22 @@ pub(crate) fn slider_on_drag(
320355 let slider_width = ( ( node. size ( ) . x - thumb_size) * node. inverse_scale_factor ) . max ( 1.0 ) ;
321356 let span = range. span ( ) ;
322357 let new_value = if span > 0. {
323- range . clamp ( drag. offset + ( distance. x * span) / slider_width)
358+ drag. offset + ( distance. x * span) / slider_width
324359 } else {
325360 range. start ( ) + span * 0.5
326361 } ;
362+ let rounded_value = range. clamp (
363+ precision
364+ . map ( |prec| prec. round ( new_value) )
365+ . unwrap_or ( new_value) ,
366+ ) ;
327367
328368 if matches ! ( slider. on_change, Callback :: Ignore ) {
329369 commands
330370 . entity ( trigger. target ( ) )
331- . insert ( SliderValue ( new_value ) ) ;
371+ . insert ( SliderValue ( rounded_value ) ) ;
332372 } else {
333- commands. notify_with ( & slider. on_change , new_value ) ;
373+ commands. notify_with ( & slider. on_change , rounded_value ) ;
334374 }
335375 }
336376 }
@@ -491,3 +531,24 @@ impl Plugin for CoreSliderPlugin {
491531 . add_observer ( slider_on_set_value) ;
492532 }
493533}
534+
535+ #[ cfg( test) ]
536+ mod tests {
537+ use super :: * ;
538+
539+ #[ test]
540+ fn test_slider_precision_rounding ( ) {
541+ // Test positive precision values (decimal places)
542+ let precision_2dp = SliderPrecision ( 2 ) ;
543+ assert_eq ! ( precision_2dp. round( 1.234567 ) , 1.23 ) ;
544+ assert_eq ! ( precision_2dp. round( 1.235 ) , 1.24 ) ;
545+
546+ // Test zero precision (rounds to integers)
547+ let precision_0dp = SliderPrecision ( 0 ) ;
548+ assert_eq ! ( precision_0dp. round( 1.4 ) , 1.0 ) ;
549+
550+ // Test negative precision (rounds to tens, hundreds, etc.)
551+ let precision_neg1 = SliderPrecision ( -1 ) ;
552+ assert_eq ! ( precision_neg1. round( 14.0 ) , 10.0 ) ;
553+ }
554+ }
0 commit comments