@@ -19,6 +19,7 @@ use bevy_input::keyboard::{KeyCode, KeyboardInput};
19
19
use bevy_input:: ButtonState ;
20
20
use bevy_input_focus:: FocusedInput ;
21
21
use bevy_log:: warn_once;
22
+ use bevy_math:: ops;
22
23
use bevy_picking:: events:: { Drag , DragEnd , DragStart , Pointer , Press } ;
23
24
use bevy_ui:: { ComputedNode , ComputedNodeTarget , InteractionDisabled , UiGlobalTransform , UiScale } ;
24
25
@@ -38,7 +39,8 @@ pub enum TrackClick {
38
39
39
40
/// A headless slider widget, which can be used to build custom sliders. Sliders have a value
40
41
/// (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`].
42
44
///
43
45
/// You can also control the slider remotely by triggering a [`SetSliderValue`] event on it. This
44
46
/// can be useful in a console environment for controlling the value gamepad inputs.
@@ -187,6 +189,25 @@ impl Default for SliderStep {
187
189
}
188
190
}
189
191
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
+
190
211
/// Component used to manage the state of a slider during dragging.
191
212
#[ derive( Component , Default ) ]
192
213
pub struct CoreSliderDragState {
@@ -204,6 +225,7 @@ pub(crate) fn slider_on_pointer_down(
204
225
& SliderValue ,
205
226
& SliderRange ,
206
227
& SliderStep ,
228
+ Option < & SliderPrecision > ,
207
229
& ComputedNode ,
208
230
& ComputedNodeTarget ,
209
231
& UiGlobalTransform ,
@@ -217,8 +239,17 @@ pub(crate) fn slider_on_pointer_down(
217
239
if q_thumb. contains ( trigger. target ( ) ) {
218
240
// Thumb click, stop propagation to prevent track click.
219
241
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 ( ) )
222
253
{
223
254
// Track click
224
255
trigger. propagate ( false ) ;
@@ -257,7 +288,9 @@ pub(crate) fn slider_on_pointer_down(
257
288
value. 0 + step. 0
258
289
}
259
290
}
260
- TrackClick :: Snap => click_val,
291
+ TrackClick :: Snap => precision
292
+ . map ( |prec| prec. round ( click_val) )
293
+ . unwrap_or ( click_val) ,
261
294
} ) ;
262
295
263
296
if matches ! ( slider. on_change, Callback :: Ignore ) {
@@ -296,6 +329,7 @@ pub(crate) fn slider_on_drag(
296
329
& ComputedNode ,
297
330
& CoreSlider ,
298
331
& SliderRange ,
332
+ Option < & SliderPrecision > ,
299
333
& UiGlobalTransform ,
300
334
& mut CoreSliderDragState ,
301
335
Has < InteractionDisabled > ,
@@ -305,7 +339,8 @@ pub(crate) fn slider_on_drag(
305
339
mut commands : Commands ,
306
340
ui_scale : Res < UiScale > ,
307
341
) {
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 ( ) )
309
344
{
310
345
trigger. propagate ( false ) ;
311
346
if drag. dragging && !disabled {
@@ -320,17 +355,22 @@ pub(crate) fn slider_on_drag(
320
355
let slider_width = ( ( node. size ( ) . x - thumb_size) * node. inverse_scale_factor ) . max ( 1.0 ) ;
321
356
let span = range. span ( ) ;
322
357
let new_value = if span > 0. {
323
- range . clamp ( drag. offset + ( distance. x * span) / slider_width)
358
+ drag. offset + ( distance. x * span) / slider_width
324
359
} else {
325
360
range. start ( ) + span * 0.5
326
361
} ;
362
+ let rounded_value = range. clamp (
363
+ precision
364
+ . map ( |prec| prec. round ( new_value) )
365
+ . unwrap_or ( new_value) ,
366
+ ) ;
327
367
328
368
if matches ! ( slider. on_change, Callback :: Ignore ) {
329
369
commands
330
370
. entity ( trigger. target ( ) )
331
- . insert ( SliderValue ( new_value ) ) ;
371
+ . insert ( SliderValue ( rounded_value ) ) ;
332
372
} else {
333
- commands. notify_with ( & slider. on_change , new_value ) ;
373
+ commands. notify_with ( & slider. on_change , rounded_value ) ;
334
374
}
335
375
}
336
376
}
@@ -491,3 +531,24 @@ impl Plugin for CoreSliderPlugin {
491
531
. add_observer ( slider_on_set_value) ;
492
532
}
493
533
}
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