Skip to content

Commit 7aa25be

Browse files
authored
gui(experimental): make scrollbars draggable (#2474)
gui(experimental): make scrollbars draggable
1 parent d4e124d commit 7aa25be

File tree

1 file changed

+81
-22
lines changed

1 file changed

+81
-22
lines changed

arcade/gui/experimental/scroll_area.py

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
UILayout,
1414
UIMouseDragEvent,
1515
UIMouseEvent,
16+
UIMouseMovementEvent,
17+
UIMousePressEvent,
18+
UIMouseReleaseEvent,
1619
UIMouseScrollEvent,
1720
UIWidget,
1821
bind,
@@ -24,10 +27,12 @@ class UIScrollBar(UIWidget):
2427
"""Scroll bar for a UIScrollLayout.
2528
2629
Indicating the current view position of the scroll area.
27-
28-
Does not support mouse interaction yet.
30+
Supports mouse dragging to scroll the content.
2931
"""
3032

33+
_thumb_hover = Property(False)
34+
_dragging = Property(False)
35+
3136
def __init__(self, scroll_area: UIScrollArea, vertical: bool = True):
3237
size_hint = (0.05, 1) if vertical else (1, 0.05)
3338

@@ -37,17 +42,65 @@ def __init__(self, scroll_area: UIScrollArea, vertical: bool = True):
3742
self.with_border(color=arcade.uicolor.GRAY_CONCRETE)
3843
self.vertical = vertical
3944

45+
self._scroll_bar_size = 20
46+
47+
bind(self, "_thumb_hover", self.trigger_render)
48+
bind(self, "_dragging", self.trigger_render)
49+
bind(scroll_area, "scroll_x", self.trigger_full_render)
4050
bind(scroll_area, "scroll_y", self.trigger_full_render)
4151
bind(scroll_area, "content_height", self.trigger_full_render)
52+
bind(scroll_area, "content_width", self.trigger_full_render)
4253

43-
def do_render(self, surface: Surface):
44-
"""Render the scroll bar."""
45-
self.prepare_render(surface)
54+
def on_event(self, event: UIEvent) -> Optional[bool]:
55+
# detect if event is mouse down and inside the scroll thumb
56+
# if so, start dragging the thumb
57+
thumb_rect_relative = self._thumb_rect()
58+
thumb_rect = thumb_rect_relative.move(*self.rect.bottom_left)
59+
60+
if isinstance(event, UIMouseMovementEvent):
61+
self._thumb_hover = thumb_rect.point_in_rect(event.pos)
62+
63+
if isinstance(event, UIMousePressEvent) and thumb_rect.point_in_rect(event.pos):
64+
self._dragging = True
65+
return True
66+
67+
# detect if event is mouse drag and thumb is being dragged
68+
# if so, update the scroll position
69+
if isinstance(event, UIMouseDragEvent) and self._dragging:
70+
sx, sy = event.pos - self.rect.bottom_left
71+
sx -= self._scroll_bar_size / 2
72+
sy -= self._scroll_bar_size / 2
73+
74+
scroll_area = self.scroll_area
75+
76+
if self.vertical:
77+
available_track_size = self.content_height - self._scroll_bar_size
78+
target_progress = 1 - sy / available_track_size
79+
target_progress = max(0, min(1, target_progress))
80+
81+
scroll_range = scroll_area.surface.height - scroll_area.content_height
82+
scroll_area.scroll_y = -target_progress * scroll_range
83+
else:
84+
available_track_size = self.content_width - self._scroll_bar_size
85+
target_progress = sx / available_track_size
86+
target_progress = max(0, min(1, target_progress))
87+
scroll_range = scroll_area.surface.width - scroll_area.content_width
88+
scroll_area.scroll_x = -target_progress * scroll_range
4689

47-
# calc position and size of the scroll bar
90+
return True
91+
92+
# detect if event is mouse up and thumb is being dragged
93+
# if so, stop dragging the thumb
94+
if isinstance(event, UIMouseReleaseEvent) and self._dragging:
95+
self._dragging = False
96+
return True
97+
98+
return EVENT_UNHANDLED
99+
100+
def _thumb_rect(self):
101+
"""Calculate the rect of the thumb."""
48102
scroll_area = self.scroll_area
49103

50-
# calculate the scroll bar position
51104
scroll_value = scroll_area.scroll_y if self.vertical else scroll_area.scroll_x
52105
scroll_range = (
53106
scroll_area.surface.height - scroll_area.content_height
@@ -57,29 +110,35 @@ def do_render(self, surface: Surface):
57110

58111
scroll_progress = -scroll_value / scroll_range
59112

60-
scroll_bar_size = 20
61113
content_size = self.content_height if self.vertical else self.content_width
62-
available_track_size = content_size - scroll_bar_size
114+
available_track_size = content_size - self._scroll_bar_size
63115

64116
if self.vertical:
65-
scroll_bar_y = scroll_bar_size / 2 + available_track_size * (1 - scroll_progress)
117+
scroll_bar_y = self._scroll_bar_size / 2 + available_track_size * (1 - scroll_progress)
66118
scroll_bar_x = self.content_width / 2
67-
68-
# draw the scroll bar
69-
arcade.draw_rect_filled(
70-
XYWH(scroll_bar_x, scroll_bar_y, self.content_height, scroll_bar_size),
71-
color=arcade.uicolor.GRAY_ASBESTOS,
72-
)
119+
return XYWH(scroll_bar_x, scroll_bar_y, self.content_width, self._scroll_bar_size)
73120

74121
else:
75-
scroll_bar_x = scroll_bar_size / 2 + available_track_size * scroll_progress
122+
scroll_bar_x = self._scroll_bar_size / 2 + available_track_size * scroll_progress
76123
scroll_bar_y = self.content_height / 2
124+
return XYWH(scroll_bar_x, scroll_bar_y, self._scroll_bar_size, self.content_height)
77125

78-
# draw the scroll bar
79-
arcade.draw_rect_filled(
80-
XYWH(scroll_bar_x, scroll_bar_y, scroll_bar_size, self.content_width),
81-
color=arcade.uicolor.GRAY_ASBESTOS,
82-
)
126+
def do_render(self, surface: Surface):
127+
"""Render the scroll bar."""
128+
self.prepare_render(surface)
129+
130+
if self._dragging:
131+
thumb_color = arcade.uicolor.DARK_BLUE_WET_ASPHALT
132+
elif self._thumb_hover:
133+
thumb_color = arcade.uicolor.GRAY_CONCRETE
134+
else:
135+
thumb_color = arcade.uicolor.GRAY_ASBESTOS
136+
137+
# draw the thumb
138+
arcade.draw_rect_filled(
139+
rect=self._thumb_rect(),
140+
color=thumb_color,
141+
)
83142

84143

85144
class UIScrollArea(UILayout):

0 commit comments

Comments
 (0)