python ui: implement inertial scrolling for GuiScrollPanel (#34596)

implement inertial scrolling for GuiScrollPanel
not-so-secret-good-op^2
Dean Lee 2 months ago committed by GitHub
parent d4d0312794
commit fc77ac706e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 24
      system/ui/lib/scroll_panel.py
  2. 4
      system/ui/text.py

@ -2,6 +2,9 @@ import pyray as rl
from cffi import FFI from cffi import FFI
MOUSE_WHEEL_SCROLL_SPEED = 30 MOUSE_WHEEL_SCROLL_SPEED = 30
INERTIA_FRICTION = 0.95 # The rate at which the inertia slows down
MIN_VELOCITY = 0.1 # Minimum velocity before stopping the inertia
class GuiScrollPanel: class GuiScrollPanel:
def __init__(self, bounds: rl.Rectangle, content: rl.Rectangle, show_vertical_scroll_bar: bool = False): def __init__(self, bounds: rl.Rectangle, content: rl.Rectangle, show_vertical_scroll_bar: bool = False):
@ -12,22 +15,28 @@ class GuiScrollPanel:
self._scroll = rl.Vector2(0, 0) self._scroll = rl.Vector2(0, 0)
self._view = rl.Rectangle(0, 0, 0, 0) self._view = rl.Rectangle(0, 0, 0, 0)
self._show_vertical_scroll_bar: bool = show_vertical_scroll_bar self._show_vertical_scroll_bar: bool = show_vertical_scroll_bar
self._velocity_y = 0.0 # Velocity for inertia
def handle_scroll(self)-> rl.Vector2: def handle_scroll(self) -> rl.Vector2:
mouse_pos = rl.get_mouse_position() mouse_pos = rl.get_mouse_position()
# Handle dragging logic
if rl.check_collision_point_rec(mouse_pos, self._bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): if rl.check_collision_point_rec(mouse_pos, self._bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
if not self._dragging: if not self._dragging:
self._dragging = True self._dragging = True
self._last_mouse_y = mouse_pos.y self._last_mouse_y = mouse_pos.y
self._velocity_y = 0.0 # Reset velocity when drag starts
if self._dragging: if self._dragging:
if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
delta_y = mouse_pos.y - self._last_mouse_y delta_y = mouse_pos.y - self._last_mouse_y
self._scroll.y += delta_y self._scroll.y += delta_y
self._last_mouse_y = mouse_pos.y self._last_mouse_y = mouse_pos.y
self._velocity_y = delta_y # Update velocity during drag
else: else:
self._dragging = False self._dragging = False
# Handle mouse wheel scrolling
wheel_move = rl.get_mouse_wheel_move() wheel_move = rl.get_mouse_wheel_move()
if self._show_vertical_scroll_bar: if self._show_vertical_scroll_bar:
self._scroll.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20) self._scroll.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20)
@ -37,4 +46,17 @@ class GuiScrollPanel:
max_scroll_y = self._content.height - self._bounds.height max_scroll_y = self._content.height - self._bounds.height
self._scroll.y = max(min(self._scroll.y, 0), -max_scroll_y) self._scroll.y = max(min(self._scroll.y, 0), -max_scroll_y)
# Apply inertia (continue scrolling after mouse release)
if not self._dragging:
self._scroll.y += self._velocity_y
self._velocity_y *= INERTIA_FRICTION # Slow down velocity over time
# Stop scrolling when velocity is low
if abs(self._velocity_y) < MIN_VELOCITY:
self._velocity_y = 0.0
# Ensure scrolling doesn't go beyond bounds
max_scroll_y = max(self._content.height - self._bounds.height, 0)
self._scroll.y = max(min(self._scroll.y, 0), -max_scroll_y)
return self._scroll return self._scroll

@ -14,7 +14,7 @@ LINE_HEIGHT = 64
BUTTON_SIZE = rl.Vector2(310, 160) BUTTON_SIZE = rl.Vector2(310, 160)
DEMO_TEXT = """This is a sample text that will be wrapped and scrolled if necessary. DEMO_TEXT = """This is a sample text that will be wrapped and scrolled if necessary.
The text is long enough to demonstrate scrolling and word wrapping.""" * 20 The text is long enough to demonstrate scrolling and word wrapping.""" * 30
def wrap_text(text, font_size, max_width): def wrap_text(text, font_size, max_width):
lines = [] lines = []
@ -50,6 +50,8 @@ def main():
rl.begin_scissor_mode(int(textarea_rect.x), int(textarea_rect.y), int(textarea_rect.width), int(textarea_rect.height)) rl.begin_scissor_mode(int(textarea_rect.x), int(textarea_rect.y), int(textarea_rect.width), int(textarea_rect.height))
for i, line in enumerate(wrapped_lines): for i, line in enumerate(wrapped_lines):
position = rl.Vector2(textarea_rect.x + scroll.x, textarea_rect.y + scroll.y + i * LINE_HEIGHT) position = rl.Vector2(textarea_rect.x + scroll.x, textarea_rect.y + scroll.y + i * LINE_HEIGHT)
if position.y + LINE_HEIGHT < textarea_rect.y or position.y > textarea_rect.y + textarea_rect.height:
continue
rl.draw_text_ex(gui_app.font(), line.strip(), position, FONT_SIZE, 0, rl.WHITE) rl.draw_text_ex(gui_app.font(), line.strip(), position, FONT_SIZE, 0, rl.WHITE)
rl.end_scissor_mode() rl.end_scissor_mode()

Loading…
Cancel
Save