@@ -68,6 +68,8 @@ class ScrollBar(Widget):
68
68
_values_range : List [NumberType ]
69
69
_visible_force : int # -1: not set, 0: hidden, 1: shown
70
70
scrolling : bool
71
+ _at_bottom : bool
72
+ _at_top : bool
71
73
72
74
def __init__ (
73
75
self ,
@@ -133,6 +135,8 @@ def __init__(
133
135
# Page step
134
136
self ._page_step = 0
135
137
self ._single_step = 20
138
+ self ._at_bottom = False
139
+ self ._at_top = True
136
140
137
141
if values_range [1 ] - values_range [0 ] > length :
138
142
self .set_page_step (length )
@@ -174,21 +178,14 @@ def flip(self, *args, **kwargs) -> 'ScrollBar':
174
178
raise WidgetTransformationNotImplemented ()
175
179
176
180
def _apply_size_changes (self ) -> None :
177
- """
178
- Apply scrollbar changes.
179
- """
181
+ """Apply scrollbar changes."""
180
182
opp_orientation = 1 if self ._orientation == 0 else 0 # Opposite of orientation
181
183
dims = ('width' , 'height' )
182
184
setattr (self ._rect , dims [self ._orientation ], int (self ._page_ctrl_length ))
183
185
setattr (self ._rect , dims [opp_orientation ], self ._page_ctrl_thick )
184
- self ._slider_rect = pygame .Rect (0 , 0 , int (self ._rect .width ), int (self ._rect .height ))
185
- setattr (self ._slider_rect , dims [self ._orientation ], int (self ._page_step ))
186
- setattr (self ._slider_rect , dims [opp_orientation ], self ._page_ctrl_thick )
187
-
188
- # Update slider position according to the current one
189
- pos = ('x' , 'y' )
190
- setattr (self ._slider_rect , pos [self ._orientation ], int (self ._slider_position ))
191
- self ._slider_rect = self ._slider_rect .inflate (- 2 * self ._slider_pad , - 2 * self ._slider_pad )
186
+ if self ._slider_rect is None :
187
+ self ._slider_rect = pygame .Rect (0 , 0 , int (self ._rect .width ), int (self ._rect .height ))
188
+ self ._update_slider_rect ()
192
189
193
190
def set_shadow (
194
191
self ,
@@ -311,17 +308,8 @@ def _render(self) -> Optional[bool]:
311
308
slider_color = self ._slider_color if not self .readonly else self ._font_readonly_color
312
309
mouse_hover = (self .scrolling and self ._clicked ) or self ._mouseover
313
310
slider_color = self ._slider_hover_color if mouse_hover else slider_color
314
- if self ._shadow_enabled :
315
- lit_rect = pygame .Rect (self ._slider_rect )
316
- slider_rect = lit_rect .inflate (- self ._shadow_offset * 2 , - self ._shadow_offset * 2 )
317
- shadow_rect = lit_rect .inflate (- self ._shadow_offset , - self ._shadow_offset )
318
- shadow_rect = shadow_rect .move (int (self ._shadow_tuple [0 ] / 2 ), int (self ._shadow_tuple [1 ] / 2 ))
319
-
320
- pygame .draw .rect (self ._surface , self ._font_selected_color , lit_rect )
321
- pygame .draw .rect (self ._surface , self ._shadow_color , shadow_rect )
322
- pygame .draw .rect (self ._surface , slider_color , slider_rect )
323
- else :
324
- pygame .draw .rect (self ._surface , slider_color , self ._slider_rect )
311
+ self ._render_shadow (self ._surface , slider_color )
312
+ return True
325
313
326
314
def _scroll (self , rect : 'pygame.Rect' , pixels : NumberType ) -> bool :
327
315
"""
@@ -351,6 +339,7 @@ def _scroll(self, rect: 'pygame.Rect', pixels: NumberType) -> bool:
351
339
move_pos [axis ] = move
352
340
self ._slider_rect .move_ip (* move_pos )
353
341
self ._slider_position += move
342
+ self ._update_slider_position_flags ()
354
343
return True
355
344
356
345
def set_length (self , value : NumberType ) -> None :
@@ -508,6 +497,107 @@ def get_slider_rect(self) -> 'pygame.Rect':
508
497
"""
509
498
return self ._slider_rect .move (* self .get_rect (to_absolute_position = True ).topleft )
510
499
500
+ def _update_slider_position_flags (self ) -> None :
501
+ """Updates the at_bottom and at_top flags based on slider position."""
502
+ max_slider_position = self ._page_ctrl_length - self ._page_step
503
+ self ._at_bottom = self ._slider_position >= max_slider_position
504
+ self ._at_top = self ._slider_position <= 0
505
+
506
+ def _update_slider_rect (self ) -> None :
507
+ """Updates the slider rect based on position and size."""
508
+ opp_orientation = 1 if self ._orientation == 0 else 0 # Opposite of orientation
509
+ dims = ('width' , 'height' )
510
+ setattr (self ._slider_rect , dims [self ._orientation ], int (self ._page_step ))
511
+ setattr (self ._slider_rect , dims [opp_orientation ], self ._page_ctrl_thick )
512
+ # Update slider position according to the current one
513
+ pos = ('x' , 'y' )
514
+ setattr (self ._slider_rect , pos [self ._orientation ], int (self ._slider_position ))
515
+ self ._slider_rect = self ._slider_rect .inflate (- 2 * self ._slider_pad , - 2 * self ._slider_pad )
516
+
517
+ def _set_slider_position (self , position : int ) -> None :
518
+ """Sets the slider position and updates related flags."""
519
+ self ._slider_position = position
520
+ self ._update_slider_rect ()
521
+ self ._update_slider_position_flags ()
522
+ self ._render ()
523
+
524
+ def is_at_bottom (self ) -> bool :
525
+ """Return True if the scrollbar is at the bottom, False otherwise."""
526
+ self ._update_slider_position_flags ()
527
+ return self ._at_bottom
528
+
529
+ def is_at_top (self ) -> bool :
530
+ """Return True if the scrollbar is at the top, False otherwise."""
531
+ self ._update_slider_position_flags ()
532
+ return self ._at_top
533
+
534
+ def bump_to_top (self ) -> None :
535
+ """Set the scrollbar to the top."""
536
+ self ._set_slider_position (0 )
537
+
538
+ def bump_to_bottom (self ) -> None :
539
+ """Set the scrollbar to the bottom."""
540
+ self ._set_slider_position (self ._page_ctrl_length - self ._page_step )
541
+
542
+ def _handle_mouse_event (self , event : pygame .event .Event , rect : pygame .Rect ) -> bool :
543
+ """Handles mouse related events."""
544
+ # Vertical bar: scroll down (4) or up (5). Mouse must be placed
545
+ # over the area to enable this feature
546
+ if event .button in (4 , 5 ) and self ._orientation == 1 and (
547
+ self ._scrollarea is None or self ._scrollarea .mouse_is_over ()
548
+ ):
549
+ direction = - 1 if event .button == 4 else 1
550
+ if self ._scroll (rect , direction * self ._single_step ):
551
+ self .change ()
552
+ return True
553
+ # Click button (left, middle, right)
554
+ elif event .button in (1 , 2 , 3 ):
555
+ # The _slider_rect origin is related to the widget surface
556
+ if self .get_slider_rect ().collidepoint (* event .pos ):
557
+ self .scrolling = True
558
+ self ._clicked = True
559
+ self ._render ()
560
+ return True
561
+ elif rect .collidepoint (* event .pos ):
562
+ # Moves towards the click by one "page" (= slider length without pad)
563
+ s_rect = self .get_slider_rect ()
564
+ direction = 1 if event .pos [self ._orientation ] > (s_rect .x if self ._orientation == 0 else s_rect .y ) else - 1
565
+ if self ._scroll (rect , direction * self ._page_step ):
566
+ self .change ()
567
+ return True
568
+ return False
569
+
570
+ def _handle_touch_event (self , event : pygame .event .Event , rect : pygame .Rect ) -> bool :
571
+ """Handles touchscreen related events."""
572
+ pos = get_finger_pos (self ._menu , event )
573
+ # The _slider_rect origin is related to the widget surface
574
+ if self .get_slider_rect ().collidepoint (* pos ):
575
+ self .scrolling = True
576
+ self ._clicked = True
577
+ self ._render ()
578
+ return True
579
+ elif rect .collidepoint (* pos ):
580
+ # Moves towards the click by one "page" (= slider length without pad)
581
+ s_rect = self .get_slider_rect ()
582
+ direction = 1 if pos [self ._orientation ] > (s_rect .x if self ._orientation == 0 else s_rect .y ) else - 1
583
+ if self ._scroll (rect , direction * self ._page_step ):
584
+ self .change ()
585
+ return True
586
+ return False
587
+
588
+ def _render_shadow (self , surface : pygame .Surface , slider_color : ColorType ) -> None :
589
+ """Renders the slider shadow if enabled."""
590
+ if self ._shadow_enabled :
591
+ lit_rect = pygame .Rect (self ._slider_rect )
592
+ slider_rect = lit_rect .inflate (- self ._shadow_offset * 2 , - self ._shadow_offset * 2 )
593
+ shadow_rect = lit_rect .inflate (- self ._shadow_offset , - self ._shadow_offset )
594
+ shadow_rect = shadow_rect .move (int (self ._shadow_tuple [0 ] / 2 ), int (self ._shadow_tuple [1 ] / 2 ))
595
+ pygame .draw .rect (surface , self ._font_selected_color , lit_rect )
596
+ pygame .draw .rect (surface , self ._shadow_color , shadow_rect )
597
+ pygame .draw .rect (surface , slider_color , slider_rect )
598
+ else :
599
+ pygame .draw .rect (surface , slider_color , self ._slider_rect )
600
+
511
601
def update (self , events : EventVectorType ) -> bool :
512
602
self .apply_update_callbacks (events )
513
603
@@ -574,6 +664,8 @@ def update(self, events: EventVectorType) -> bool:
574
664
# Check scrolling
575
665
if self ._scroll (rect , rel ):
576
666
self .change ()
667
+ self .is_at_bottom ()
668
+ self .is_at_top ()
577
669
return True
578
670
579
671
# Mouse enters or leaves the window
@@ -593,46 +685,12 @@ def update(self, events: EventVectorType) -> bool:
593
685
self ._scroll (rect , my - lmy )
594
686
595
687
# User clicks the slider rect
596
- elif (
597
- event .type == pygame .MOUSEBUTTONDOWN and self ._mouse_enabled or
598
- event .type == FINGERDOWN and self ._touchscreen_enabled and self ._menu is not None
599
- ):
600
- # Vertical bar: scroll down (4) or up (5). Mouse must be placed
601
- # over the area to enable this feature
602
- if (
603
- not event .type == FINGERDOWN and
604
- event .button in (4 , 5 ) and
605
- self ._orientation == 1 and
606
- (
607
- self ._scrollarea is not None and self ._scrollarea .mouse_is_over () or
608
- self ._scrollarea is None
609
- )
610
- ):
611
- direction = - 1 if event .button == 4 else 1
612
- if self ._scroll (rect , direction * self ._single_step ):
613
- self .change ()
614
- return True
615
-
616
- # Click button (left, middle, right)
617
- elif event .type == FINGERDOWN or event .button in (1 , 2 , 3 ):
618
- event_pos = get_finger_pos (self ._menu , event )
619
-
620
- # The _slider_rect origin is related to the widget surface
621
- if self .get_slider_rect ().collidepoint (* event_pos ):
622
- # Initialize scrolling
623
- self .scrolling = True
624
- self ._clicked = True
625
- self ._render ()
626
- return True
627
-
628
- elif rect .collidepoint (* event_pos ):
629
- # Moves towards the click by one "page" (= slider length without pad)
630
- s_rect = self .get_slider_rect ()
631
- pos = (s_rect .x , s_rect .y )
632
- direction = 1 if event_pos [self ._orientation ] > pos [self ._orientation ] else - 1
633
- if self ._scroll (rect , direction * self ._page_step ):
634
- self .change ()
635
- return True
688
+ elif event .type == pygame .MOUSEBUTTONDOWN and self ._mouse_enabled :
689
+ if self ._handle_mouse_event (event , rect ):
690
+ return True
691
+ elif event .type == FINGERDOWN and self ._touchscreen_enabled and self ._menu is not None :
692
+ if self ._handle_touch_event (event , rect ):
693
+ return True
636
694
637
695
# User releases mouse button if scrolling
638
696
elif (event .type == pygame .MOUSEBUTTONUP and self ._mouse_enabled or
0 commit comments