LUIScrollableRegion.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. from LUIObject import LUIObject
  2. from LUISprite import LUISprite
  3. from LUIInitialState import LUIInitialState
  4. from LUILayouts import LUIHorizontalStretchedLayout
  5. class LUIScrollableRegion(LUIObject):
  6. """ Scrollable region, reparent elements to the .content_node to make them
  7. scroll. """
  8. def __init__(self, parent=None, width=100, height=100, padding=10, **kwargs):
  9. LUIObject.__init__(self)
  10. self.set_size(width, height)
  11. self._content_parent = LUIObject(self)
  12. self._content_parent.set_size("100%", "100%")
  13. self._content_parent.clip_bounds = (0,0,0,0)
  14. self._content_clip = LUIObject(self._content_parent, x=padding, y=padding)
  15. self._content_clip.set_size("100%", "100%")
  16. self._content_scroller = LUIObject(self._content_clip)
  17. self._content_scroller.width = "100%"
  18. self._scrollbar = LUIObject(self, x=0, y=0, w=20)
  19. self._scrollbar.height = "100%"
  20. self._scrollbar.right = -10
  21. self._scrollbar_bg = LUISprite(self._scrollbar, "blank", "skin")
  22. self._scrollbar_bg.color = (1,1,1,0.05)
  23. self._scrollbar_bg.set_size(3, "100%")
  24. self._scrollbar_bg.center_horizontal = True
  25. # Handle
  26. self._scrollbar_handle = LUIObject(self._scrollbar, x=5, y=0, w=10)
  27. self._scroll_handle_top = LUISprite(self._scrollbar_handle, "ScrollbarHandle_Top", "skin")
  28. self._scroll_handle_mid = LUISprite(self._scrollbar_handle, "ScrollbarHandle", "skin")
  29. self._scroll_handle_bottom = LUISprite(self._scrollbar_handle, "ScrollbarHandle_Bottom", "skin")
  30. self._scrollbar_handle.solid = True
  31. self._scrollbar.solid = True
  32. self._scrollbar_handle.bind("mousedown", self._start_scrolling)
  33. self._scrollbar_handle.bind("mouseup", self._stop_scrolling)
  34. self._scrollbar.bind("mousedown", self._on_bar_click)
  35. self._scrollbar.bind("mouseup", self._stop_scrolling)
  36. self._handle_dragging = False
  37. self._drag_start_y = 0
  38. self._scroll_top_position = 0
  39. self._content_height = 400
  40. #scroll_shadow_width = self.width - 10
  41. # Scroll shadow
  42. self._scroll_shadow_top = LUIHorizontalStretchedLayout(parent=self, prefix="ScrollShadowTop", width="100%")
  43. self._scroll_shadow_bottom = LUIHorizontalStretchedLayout(parent=self, prefix="ScrollShadowBottom", width="100%")
  44. self._scroll_shadow_bottom.bottom = 0
  45. self._handle_height = 100
  46. if parent is not None:
  47. self.parent = parent
  48. LUIInitialState.init(self, kwargs)
  49. self.content_node = self._content_scroller
  50. taskMgr.doMethodLater(0.05, lambda task: self._update(), "update_scrollbar")
  51. def _on_bar_click(self, event):
  52. """ Internal handler when the user clicks on the scroll bar """
  53. self._scroll_to_bar_pixels(event.coordinates.y - self._scrollbar.abs_pos.y - self._handle_height / 2.0)
  54. self._update()
  55. self._start_scrolling(event)
  56. def _start_scrolling(self, event):
  57. """ Internal method when we start scrolling """
  58. self.request_focus()
  59. if not self._handle_dragging:
  60. self._drag_start_y = event.coordinates.y
  61. self._handle_dragging = True
  62. def _stop_scrolling(self, event):
  63. """ Internal handler when we should stop scrolling """
  64. if self._handle_dragging:
  65. self._handle_dragging = False
  66. self.blur()
  67. def _scroll_to_bar_pixels(self, pixels):
  68. """ Internal method to convert from pixels to a relative position """
  69. offset = pixels * self._content_height / self.height
  70. self._scroll_top_position = offset
  71. self._scroll_top_position = max(0, min(self._content_height - self._content_clip.height, self._scroll_top_position))
  72. def on_tick(self, event):
  73. """ Internal on tick handler """
  74. if self._handle_dragging:
  75. scroll_abs_pos = self._scrollbar.abs_pos
  76. clamped_coord_y = max(scroll_abs_pos.y, min(scroll_abs_pos.y + self.height, event.coordinates.y))
  77. offset = clamped_coord_y - self._drag_start_y
  78. self._drag_start_y = clamped_coord_y
  79. self._scroll_to_bar_pixels(self._scroll_top_position/self._content_height*self.height + offset)
  80. self._update()
  81. def _set_handle_height(self, height):
  82. """ Internal method to set the scrollbar height """
  83. self._scroll_handle_mid.top = float(self._scroll_handle_top.height)
  84. self._scroll_handle_mid.height = max(0.0, height - self._scroll_handle_top.height - self._scroll_handle_bottom.height)
  85. self._scroll_handle_bottom.top = self._scroll_handle_mid.height + self._scroll_handle_mid.top
  86. self._handle_height = height
  87. def _update(self):
  88. """ Internal method to update the scroll bar """
  89. self._content_height = max(1, self._content_scroller.get_height() + 20)
  90. self._content_scroller.top = -self._scroll_top_position
  91. scrollbar_height = max(0.1, min(1.0, self._content_clip.height / self._content_height))
  92. scrollbar_height_px = scrollbar_height * self.height
  93. self._set_handle_height(scrollbar_height_px)
  94. self._scrollbar_handle.top = self._scroll_top_position / self._content_height * self.height
  95. top_alpha = max(0.0, min(1.0, self._scroll_top_position / 50.0))
  96. bottom_alpha = max(0.0, min(1.0, (self._content_height - self._scroll_top_position - self._content_clip.height) / 50.0 ))
  97. self._scroll_shadow_top.color = (1,1,1,top_alpha)
  98. self._scroll_shadow_bottom.color = (1,1,1,bottom_alpha)
  99. if self._content_height <= self.height:
  100. self._scrollbar_handle.hide()
  101. else:
  102. self._scrollbar_handle.show()
  103. def on_element_added(self):
  104. taskMgr.doMethodLater(0.05, lambda task: self._update(), "update_layout")
  105. def get_scroll_percentage(self):
  106. """ Returns the current scroll height in percentage from 0 to 1 """
  107. return self._scroll_top_position / max(1, self._content_height - self._content_clip.height)
  108. def set_scroll_percentage(self, percentage):
  109. """ Sets the scroll position in percentage, 0 means top and 1 means bottom """
  110. percentage = max(0.0, min(1.0, percentage))
  111. pixels = max(0.0, self._content_height - self._content_clip.height) * percentage
  112. self._scroll_top_position = pixels
  113. self._update()
  114. scroll_percentage = property(get_scroll_percentage, set_scroll_percentage)
  115. def scroll_to_bottom(self):
  116. """ Scrolls to the bottom of the frame """
  117. taskMgr.doMethodLater(0.07, lambda task: self.set_scroll_percentage(1.0), "scroll_to_bottom")
  118. def scroll_to_top(self):
  119. """ Scrolls to the top of the frame """
  120. taskMgr.doMethodLater(0.07, lambda task: self.set_scroll_percentage(0.0), "scroll_to_top")