LUISelectbox.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. from LUIObject import LUIObject
  2. from LUISprite import LUISprite
  3. from LUILabel import LUILabel
  4. from LUILayouts import LUICornerLayout, LUIHorizontalStretchedLayout
  5. from LUIInitialState import LUIInitialState
  6. from functools import partial
  7. __all__ = ["LUISelectbox"]
  8. class LUISelectbox(LUIObject):
  9. """ Selectbox widget, showing several options whereas the user can select
  10. only one. """
  11. def __init__(self, width=200, options=None, selected_option=None, **kwargs):
  12. """ Constructs a new selectbox with a given width """
  13. LUIObject.__init__(self, x=0, y=0, w=width+4, solid=True)
  14. LUIInitialState.init(self, kwargs)
  15. # The selectbox has a small border, to correct this we move it
  16. self.margin.left = -2
  17. self._bg_layout = LUIHorizontalStretchedLayout(parent=self, prefix="Selectbox", width="100%")
  18. self._label_container = LUIObject(self, x=10, y=0)
  19. self._label_container.set_size("100%", "100%")
  20. self._label_container.clip_bounds = (0,0,0,0)
  21. self._label = LUILabel(parent=self._label_container, text=u"Select an option ..")
  22. self._label.center_vertical = True
  23. self._drop_menu = LUISelectdrop(parent=self, width=width)
  24. self._drop_menu.top = self._bg_layout._sprite_right.height - 7
  25. self._drop_menu.topmost = True
  26. self._drop_open = False
  27. self._drop_menu.hide()
  28. self._options = []
  29. self._current_option_id = None
  30. if options is not None:
  31. self._options = options
  32. self._select_option(selected_option)
  33. def get_selected_option(self):
  34. """ Returns the selected option """
  35. return self._current_option_id
  36. def set_selected_option(self, option_id):
  37. """ Sets the selected option """
  38. raise NotImplementedError()
  39. selected_option = property(get_selected_option, set_selected_option)
  40. def _render_options(self):
  41. """ Internal method to render all available options """
  42. self._drop_menu._render_options(self._options)
  43. def get_options(self):
  44. """ Returns the list of options """
  45. return self._options
  46. def set_options(self, options):
  47. """ Sets the list of options, options should be a list containing entries
  48. whereas each entry is a tuple in the format (option_id, option_label).
  49. The option ID can be an arbitrary object, and will not get modified. """
  50. self._options = options
  51. self._current_option_id = None
  52. self._render_options()
  53. options = property(get_options, set_options)
  54. def _select_option(self, opt_id):
  55. """ Internal method to select an option """
  56. self._label.alpha = 1.0
  57. for elem_opt_id, opt_val in self._options:
  58. if opt_id == elem_opt_id:
  59. self._label.text = opt_val
  60. self._current_option_id = opt_id
  61. return
  62. self._label.alpha = 0.3
  63. # def on_mouseover(self, event):
  64. # """ Internal handle when the select-knob was hovered """
  65. # self._bg_layout.color = (0.9,0.9,0.9,1.0)
  66. # def on_mouseout(self, event):
  67. # """ Internal handle when the select-knob was no longer hovered """
  68. # self._bg_layout.color = (1,1,1,1.0)
  69. def on_click(self, event):
  70. """ On-Click handler """
  71. self.request_focus()
  72. if self._drop_open:
  73. self._close_drop()
  74. else:
  75. self._open_drop()
  76. def on_mousedown(self, event):
  77. """ Mousedown handler """
  78. self._bg_layout.alpha = 0.9
  79. def on_mouseup(self, event):
  80. """ Mouseup handler """
  81. self._bg_layout.alpha = 1
  82. def on_blur(self, event):
  83. """ Internal handler when the selectbox lost focus """
  84. if not self._drop_menu.focused:
  85. self._close_drop()
  86. def _open_drop(self):
  87. """ Internal method to show the dropdown menu """
  88. if not self._drop_open:
  89. self._render_options()
  90. self._drop_menu.show()
  91. self.request_focus()
  92. self._drop_open = True
  93. def _close_drop(self):
  94. """ Internal method to close the dropdown menu """
  95. if self._drop_open:
  96. self._drop_menu.hide()
  97. self._drop_open = False
  98. def _on_option_selected(self, opt_id):
  99. """ Internal method when an option got selected """
  100. self._select_option(opt_id)
  101. self._close_drop()
  102. class LUISelectdrop(LUIObject):
  103. """ Internal class used by the selectbox, representing the dropdown menu """
  104. def __init__(self, parent, width=200):
  105. LUIObject.__init__(self, x=0, y=0, w=width, h=1, solid=True)
  106. self._layout = LUICornerLayout(parent=self, image_prefix="Selectdrop_",
  107. width=width + 10, height=100)
  108. self._layout.margin.left = -3
  109. self._opener = LUISprite(self, "SelectboxOpen_Right", "skin")
  110. self._opener.right = -4
  111. self._opener.top = -25
  112. self._opener.z_offset = 3
  113. self._container = LUIObject(self._layout, 0, 0, 0, 0)
  114. self._container.width = self.width
  115. self._container.clip_bounds = (0,0,0,0)
  116. self._container.left = 5
  117. self._container.solid = True
  118. self._container.bind("mousedown", lambda *args: self.request_focus())
  119. self._selectbox = parent
  120. self._option_focus = False
  121. self.parent = self._selectbox
  122. def _on_opt_over(self, event):
  123. """ Inernal handler when an option got hovered """
  124. event.sender.color = (0,0,0,0.1)
  125. def _on_opt_out(self, event):
  126. """ Inernal handler when an option got no longer hovered """
  127. event.sender.color = (0,0,0,0)
  128. def _on_opt_click(self, opt_id, event):
  129. """ Internal handler when an option got clicked """
  130. self._selectbox._on_option_selected(opt_id)
  131. def _render_options(self, options):
  132. """ Internal method to update the options """
  133. num_visible_options = min(30, len(options))
  134. offset_top = 6
  135. self._layout.height = num_visible_options * 30 + offset_top + 11
  136. self._container.height = num_visible_options * 30 + offset_top + 1
  137. self._container.remove_all_children()
  138. current_y = offset_top
  139. for opt_id, opt_val in options:
  140. opt_container = LUIObject(self._container, x=0, y=current_y, w=self._container.width - 30, h=30)
  141. opt_bg = LUISprite(opt_container, "blank", "skin")
  142. opt_bg.width = self._container.width
  143. opt_bg.height = opt_container.height
  144. opt_bg.color = (0,0,0,0)
  145. opt_bg.bind("mouseover", self._on_opt_over)
  146. opt_bg.bind("mouseout", self._on_opt_out)
  147. opt_bg.bind("mousedown", lambda *args: self.request_focus())
  148. opt_bg.bind("click", partial(self._on_opt_click, opt_id))
  149. opt_bg.solid = True
  150. opt_label = LUILabel(parent=opt_container, text=opt_val.encode('utf-8'))
  151. opt_label.top = 8
  152. opt_label.left = 8
  153. if opt_id == self._selectbox.selected_option:
  154. opt_label.color = (0.6, 0.9, 0.4, 1.0)
  155. divider = LUISprite(opt_container, "SelectdropDivider", "skin")
  156. divider.top = 30 - divider.height / 2
  157. divider.width = self._container.width
  158. current_y += 30