@@ -84,20 +84,29 @@ def __init__(
8484 )
8585
8686 self .step = step
87- self .value = self . _apply_step ( value )
87+ self .value = value
8888 self .min_value = min_value
8989 self .max_value = max_value
9090
9191 self ._cursor_width = self .height // 3
9292
9393 # trigger render on value changes
9494 bind (self , "value" , self .trigger_full_render )
95+ bind (self , "value" , self ._ensure_step )
9596 bind (self , "hovered" , self .trigger_render )
9697 bind (self , "pressed" , self .trigger_render )
9798 bind (self , "disabled" , self .trigger_render )
9899
99100 self .register_event_type ("on_change" )
100101
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+
101110 def _apply_step (self , value : float ):
102111 if self .step :
103112 inverse = 1 / self .step
@@ -120,9 +129,7 @@ def norm_value(self):
120129 @norm_value .setter
121130 def norm_value (self , value ):
122131 """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 )
126133
127134 @property
128135 def _thumb_x (self ):
@@ -147,6 +154,7 @@ def do_render(self, surface: Surface):
147154 """Render the slider, including track and thumb."""
148155 self .prepare_render (surface )
149156 self ._render_track (surface )
157+ self ._render_steps (surface )
150158 self ._render_thumb (surface )
151159
152160 @abstractmethod
@@ -162,6 +170,19 @@ def _render_track(self, surface: Surface):
162170 """
163171 pass
164172
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+
165186 @abstractmethod
166187 def _render_thumb (self , surface : Surface ):
167188 """Render the thumb of the slider.
@@ -236,15 +257,18 @@ class UISliderStyle(UIStyleBase):
236257 border: Border color.
237258 border_width: Width of the border.
238259 filled_track: Color of the filled track.
260+ filled_step: Color of the step in filled area.
239261 unfilled_track: Color of the unfilled track.
240-
262+ unfilled_step: Color of the step in unfilled area.
241263 """
242264
243265 bg : RGBA255 = uicolor .WHITE_SILVER
244266 border : RGBA255 = uicolor .DARK_BLUE_MIDNIGHT_BLUE
245267 border_width : int = 2
246268 filled_track : RGBA255 = uicolor .DARK_BLUE_MIDNIGHT_BLUE
269+ filled_step : RGBA255 | None = uicolor .BLUE_PETER_RIVER
247270 unfilled_track : RGBA255 = uicolor .WHITE_SILVER
271+ unfilled_step : RGBA255 | None = uicolor .BLUE_PETER_RIVER
248272
249273
250274class UISlider (UIStyledWidget [UISliderStyle ], UIBaseSlider ):
@@ -277,20 +301,54 @@ class UISlider(UIStyledWidget[UISliderStyle], UIBaseSlider):
277301 border = uicolor .BLUE_PETER_RIVER ,
278302 border_width = 2 ,
279303 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 ,
280332 ),
281333 "press" : UIStyle (
282334 bg = uicolor .BLUE_PETER_RIVER ,
283335 border = uicolor .DARK_BLUE_WET_ASPHALT ,
284336 border_width = 3 ,
285337 filled_track = uicolor .BLUE_PETER_RIVER ,
338+ filled_step = None ,
339+ unfilled_step = None ,
286340 ),
287341 "disabled" : UIStyle (
288342 bg = uicolor .WHITE_SILVER ,
289343 border_width = 1 ,
290344 filled_track = uicolor .GRAY_ASBESTOS ,
291345 unfilled_track = uicolor .WHITE_SILVER ,
346+ filled_step = None ,
347+ unfilled_step = None ,
292348 ),
293349 }
350+ """Removing the step colors from the style.
351+ So sliders with a step value do not show the steps visually."""
294352
295353 def __init__ (
296354 self ,
@@ -374,6 +432,45 @@ def _render_track(self, surface: Surface):
374432 fg_slider_color ,
375433 )
376434
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+
377474 @override
378475 def _render_thumb (self , surface : Surface ):
379476 style = self .get_current_style ()
0 commit comments