@@ -84,20 +84,29 @@ def __init__(
84
84
)
85
85
86
86
self .step = step
87
- self .value = self . _apply_step ( value )
87
+ self .value = value
88
88
self .min_value = min_value
89
89
self .max_value = max_value
90
90
91
91
self ._cursor_width = self .height // 3
92
92
93
93
# trigger render on value changes
94
94
bind (self , "value" , self .trigger_full_render )
95
+ bind (self , "value" , self ._ensure_step )
95
96
bind (self , "hovered" , self .trigger_render )
96
97
bind (self , "pressed" , self .trigger_render )
97
98
bind (self , "disabled" , self .trigger_render )
98
99
99
100
self .register_event_type ("on_change" )
100
101
102
+ def _ensure_step (self ):
103
+ """Ensure that the step is applied."""
104
+ if self .step is not None :
105
+ # this will trigger the change once again
106
+ # only option to prevent this would be to make `value` a property,
107
+ # which might break code of users
108
+ self .value = self ._apply_step (self .value )
109
+
101
110
def _apply_step (self , value : float ):
102
111
if self .step :
103
112
inverse = 1 / self .step
@@ -120,9 +129,7 @@ def norm_value(self):
120
129
@norm_value .setter
121
130
def norm_value (self , value ):
122
131
"""Normalized value between 0.0 and 1.0"""
123
- self .value = self ._apply_step (
124
- min (value * (self .max_value - self .min_value ) + self .min_value , self .max_value )
125
- )
132
+ self .value = min (value * (self .max_value - self .min_value ) + self .min_value , self .max_value )
126
133
127
134
@property
128
135
def _thumb_x (self ):
@@ -147,6 +154,7 @@ def do_render(self, surface: Surface):
147
154
"""Render the slider, including track and thumb."""
148
155
self .prepare_render (surface )
149
156
self ._render_track (surface )
157
+ self ._render_steps (surface )
150
158
self ._render_thumb (surface )
151
159
152
160
@abstractmethod
@@ -162,6 +170,19 @@ def _render_track(self, surface: Surface):
162
170
"""
163
171
pass
164
172
173
+ @abstractmethod
174
+ def _render_steps (self , surface : Surface ):
175
+ """Render the steps of the slider track.
176
+
177
+ This method should be implemented in a slider implementation.
178
+
179
+ Steps should stay within self.content_rect.
180
+
181
+ Args:
182
+ surface: Surface to render on.
183
+ """
184
+ pass
185
+
165
186
@abstractmethod
166
187
def _render_thumb (self , surface : Surface ):
167
188
"""Render the thumb of the slider.
@@ -236,15 +257,18 @@ class UISliderStyle(UIStyleBase):
236
257
border: Border color.
237
258
border_width: Width of the border.
238
259
filled_track: Color of the filled track.
260
+ filled_step: Color of the step in filled area.
239
261
unfilled_track: Color of the unfilled track.
240
-
262
+ unfilled_step: Color of the step in unfilled area.
241
263
"""
242
264
243
265
bg : RGBA255 = uicolor .WHITE_SILVER
244
266
border : RGBA255 = uicolor .DARK_BLUE_MIDNIGHT_BLUE
245
267
border_width : int = 2
246
268
filled_track : RGBA255 = uicolor .DARK_BLUE_MIDNIGHT_BLUE
269
+ filled_step : RGBA255 | None = uicolor .BLUE_PETER_RIVER
247
270
unfilled_track : RGBA255 = uicolor .WHITE_SILVER
271
+ unfilled_step : RGBA255 | None = uicolor .BLUE_PETER_RIVER
248
272
249
273
250
274
class UISlider (UIStyledWidget [UISliderStyle ], UIBaseSlider ):
@@ -277,20 +301,54 @@ class UISlider(UIStyledWidget[UISliderStyle], UIBaseSlider):
277
301
border = uicolor .BLUE_PETER_RIVER ,
278
302
border_width = 2 ,
279
303
filled_track = uicolor .BLUE_PETER_RIVER ,
304
+ filled_step = uicolor .DARK_BLUE_MIDNIGHT_BLUE ,
305
+ ),
306
+ "press" : UIStyle (
307
+ bg = uicolor .BLUE_PETER_RIVER ,
308
+ border = uicolor .DARK_BLUE_WET_ASPHALT ,
309
+ border_width = 3 ,
310
+ filled_track = uicolor .BLUE_PETER_RIVER ,
311
+ filled_step = uicolor .DARK_BLUE_MIDNIGHT_BLUE ,
312
+ ),
313
+ "disabled" : UIStyle (
314
+ bg = uicolor .WHITE_SILVER ,
315
+ border_width = 1 ,
316
+ filled_track = uicolor .GRAY_ASBESTOS ,
317
+ unfilled_track = uicolor .WHITE_SILVER ,
318
+ ),
319
+ }
320
+
321
+ NO_STEP_STYLE = {
322
+ "normal" : UIStyle (
323
+ filled_step = None ,
324
+ unfilled_step = None ,
325
+ ),
326
+ "hover" : UIStyle (
327
+ border = uicolor .BLUE_PETER_RIVER ,
328
+ border_width = 2 ,
329
+ filled_track = uicolor .BLUE_PETER_RIVER ,
330
+ filled_step = None ,
331
+ unfilled_step = None ,
280
332
),
281
333
"press" : UIStyle (
282
334
bg = uicolor .BLUE_PETER_RIVER ,
283
335
border = uicolor .DARK_BLUE_WET_ASPHALT ,
284
336
border_width = 3 ,
285
337
filled_track = uicolor .BLUE_PETER_RIVER ,
338
+ filled_step = None ,
339
+ unfilled_step = None ,
286
340
),
287
341
"disabled" : UIStyle (
288
342
bg = uicolor .WHITE_SILVER ,
289
343
border_width = 1 ,
290
344
filled_track = uicolor .GRAY_ASBESTOS ,
291
345
unfilled_track = uicolor .WHITE_SILVER ,
346
+ filled_step = None ,
347
+ unfilled_step = None ,
292
348
),
293
349
}
350
+ """Removing the step colors from the style.
351
+ So sliders with a step value do not show the steps visually."""
294
352
295
353
def __init__ (
296
354
self ,
@@ -374,6 +432,45 @@ def _render_track(self, surface: Surface):
374
432
fg_slider_color ,
375
433
)
376
434
435
+ @override
436
+ def _render_steps (self , surface : Surface ):
437
+ if not self .step :
438
+ return
439
+
440
+ style = self .get_current_style ()
441
+ if style is None :
442
+ warnings .warn (f"No style found for state { self .get_current_state ()} " , UserWarning )
443
+ return
444
+
445
+ unfilled_steps = style .get ("unfilled_step" , UISlider .UIStyle .unfilled_step )
446
+ filled_steps = style .get ("filled_step" , UISlider .UIStyle .filled_step )
447
+
448
+ def float_range (start , stop , step ):
449
+ while start < stop :
450
+ yield start
451
+ start += step
452
+ yield stop
453
+
454
+ steps = list (float_range (self .min_value , self .max_value , self .step ))
455
+
456
+ for v in steps :
457
+ step_x = self ._x_for_value (v ) - self .content_rect .left
458
+ step_color = filled_steps if v <= self .value else unfilled_steps
459
+
460
+ if step_color :
461
+ # bigger circle for first and last step
462
+ circle_size = self ._cursor_width // 4
463
+ if v in (steps [0 ], steps [- 1 ]):
464
+ circle_size = self ._cursor_width // 2
465
+
466
+ arcade .draw_circle_filled (
467
+ step_x ,
468
+ self .content_height // 2 ,
469
+ circle_size ,
470
+ step_color ,
471
+ num_segments = 8 ,
472
+ )
473
+
377
474
@override
378
475
def _render_thumb (self , surface : Surface ):
379
476
style = self .get_current_style ()
0 commit comments