EG/ui/Builtin/LUISelectbox.py
2026-02-25 11:49:31 +08:00

208 lines
7.2 KiB
Python

from LUIObject import LUIObject
from LUISprite import LUISprite
from LUILabel import LUILabel
from LUILayouts import LUICornerLayout, LUIHorizontalStretchedLayout
from LUIInitialState import LUIInitialState
from functools import partial
__all__ = ["LUISelectbox"]
class LUISelectbox(LUIObject):
""" Selectbox widget, showing several options whereas the user can select
only one. """
def __init__(self, width=200, options=None, selected_option=None, **kwargs):
""" Constructs a new selectbox with a given width """
LUIObject.__init__(self, x=0, y=0, w=width+4, solid=True)
LUIInitialState.init(self, kwargs)
# The selectbox has a small border, to correct this we move it
self.margin.left = -2
self._bg_layout = LUIHorizontalStretchedLayout(parent=self, prefix="Selectbox", width="100%")
self._label_container = LUIObject(self, x=10, y=0)
self._label_container.set_size("100%", "100%")
self._label_container.clip_bounds = (0,0,0,0)
self._label = LUILabel(parent=self._label_container, text=u"Select an option ..")
self._label.center_vertical = True
self._drop_menu = LUISelectdrop(parent=self, width=width)
self._drop_menu.top = self._bg_layout._sprite_right.height - 7
self._drop_menu.topmost = True
self._drop_open = False
self._drop_menu.hide()
self._options = []
self._current_option_id = None
if options is not None:
self._options = options
self._select_option(selected_option)
def get_selected_option(self):
""" Returns the selected option """
return self._current_option_id
def set_selected_option(self, option_id):
""" Sets the selected option """
raise NotImplementedError()
selected_option = property(get_selected_option, set_selected_option)
def _render_options(self):
""" Internal method to render all available options """
self._drop_menu._render_options(self._options)
def get_options(self):
""" Returns the list of options """
return self._options
def set_options(self, options):
""" Sets the list of options, options should be a list containing entries
whereas each entry is a tuple in the format (option_id, option_label).
The option ID can be an arbitrary object, and will not get modified. """
self._options = options
self._current_option_id = None
self._render_options()
options = property(get_options, set_options)
def _select_option(self, opt_id):
""" Internal method to select an option """
self._label.alpha = 1.0
for elem_opt_id, opt_val in self._options:
if opt_id == elem_opt_id:
self._label.text = opt_val
self._current_option_id = opt_id
return
self._label.alpha = 0.3
# def on_mouseover(self, event):
# """ Internal handle when the select-knob was hovered """
# self._bg_layout.color = (0.9,0.9,0.9,1.0)
# def on_mouseout(self, event):
# """ Internal handle when the select-knob was no longer hovered """
# self._bg_layout.color = (1,1,1,1.0)
def on_click(self, event):
""" On-Click handler """
self.request_focus()
if self._drop_open:
self._close_drop()
else:
self._open_drop()
def on_mousedown(self, event):
""" Mousedown handler """
self._bg_layout.alpha = 0.9
def on_mouseup(self, event):
""" Mouseup handler """
self._bg_layout.alpha = 1
def on_blur(self, event):
""" Internal handler when the selectbox lost focus """
if not self._drop_menu.focused:
self._close_drop()
def _open_drop(self):
""" Internal method to show the dropdown menu """
if not self._drop_open:
self._render_options()
self._drop_menu.show()
self.request_focus()
self._drop_open = True
def _close_drop(self):
""" Internal method to close the dropdown menu """
if self._drop_open:
self._drop_menu.hide()
self._drop_open = False
def _on_option_selected(self, opt_id):
""" Internal method when an option got selected """
self._select_option(opt_id)
self._close_drop()
class LUISelectdrop(LUIObject):
""" Internal class used by the selectbox, representing the dropdown menu """
def __init__(self, parent, width=200):
LUIObject.__init__(self, x=0, y=0, w=width, h=1, solid=True)
self._layout = LUICornerLayout(parent=self, image_prefix="Selectdrop_",
width=width + 10, height=100)
self._layout.margin.left = -3
self._opener = LUISprite(self, "SelectboxOpen_Right", "skin")
self._opener.right = -4
self._opener.top = -25
self._opener.z_offset = 3
self._container = LUIObject(self._layout, 0, 0, 0, 0)
self._container.width = self.width
self._container.clip_bounds = (0,0,0,0)
self._container.left = 5
self._container.solid = True
self._container.bind("mousedown", lambda *args: self.request_focus())
self._selectbox = parent
self._option_focus = False
self.parent = self._selectbox
def _on_opt_over(self, event):
""" Inernal handler when an option got hovered """
event.sender.color = (0,0,0,0.1)
def _on_opt_out(self, event):
""" Inernal handler when an option got no longer hovered """
event.sender.color = (0,0,0,0)
def _on_opt_click(self, opt_id, event):
""" Internal handler when an option got clicked """
self._selectbox._on_option_selected(opt_id)
def _render_options(self, options):
""" Internal method to update the options """
num_visible_options = min(30, len(options))
offset_top = 6
self._layout.height = num_visible_options * 30 + offset_top + 11
self._container.height = num_visible_options * 30 + offset_top + 1
self._container.remove_all_children()
current_y = offset_top
for opt_id, opt_val in options:
opt_container = LUIObject(self._container, x=0, y=current_y, w=self._container.width - 30, h=30)
opt_bg = LUISprite(opt_container, "blank", "skin")
opt_bg.width = self._container.width
opt_bg.height = opt_container.height
opt_bg.color = (0,0,0,0)
opt_bg.bind("mouseover", self._on_opt_over)
opt_bg.bind("mouseout", self._on_opt_out)
opt_bg.bind("mousedown", lambda *args: self.request_focus())
opt_bg.bind("click", partial(self._on_opt_click, opt_id))
opt_bg.solid = True
opt_label = LUILabel(parent=opt_container, text=opt_val)
opt_label.top = 8
opt_label.left = 8
if opt_id == self._selectbox.selected_option:
opt_label.color = (0.6, 0.9, 0.4, 1.0)
divider = LUISprite(opt_container, "SelectdropDivider", "skin")
divider.top = 30 - divider.height / 2
divider.width = self._container.width
current_y += 30