Allow user to specify a custom to_str
function for the ScrollMenu
(and other similar widgets) that would be used to determine the line
of text to display on the screen. Currently, only strings are allowed to be added to the widget.
Motivation? Imagine we had a list of Pet
objects that might be defined as:
class Pet:
def __init__(self, name, breed, age):
self.name = name
self.breed = breed
self.age = age
def purchase(self):
print(f"You purchased a {self}")
def __str__(self):
return f"name={self.name} breed={self.breed} age={self.age}"
To display a list of pets and add an event handler for one that is selected in a ScrollMenu
, I'd have to do the following:
pets = [Pet("Baloo", "Dog", 4), Pet("Wilbur", "Horse", 7)]
menu = root.add_scroll_menu("Pets", 0, 0)
menu.add_item_list(str(p) for p in pets) # explicitly convert to string
def process_selection():
selected_pet_text = menu.get()
for p in pets:
if str(p) == selected_pet_text:
p.purchase()
menu.add_key_command(py_cui.keys.KEY_ENTER, process_selection)
With a very small change to the code, users wouldn't have to look up which Pet
was selected because the return value from ScrollMenu.get
now returns the actual Pet
object itself:
pets = [Pet("Baloo", "Dog", 4), Pet("Wilbur", "Horse", 7)]
menu = root.add_scroll_menu("Pets", 0, 0)
menu.add_item_list(pets) # Allow any object to be added
def process_selection():
selected_pet_object = menu.get() # this returns the object in the list
selected_pet_object.purchase() # so we can use it directly
menu.add_key_command(py_cui.keys.KEY_ENTER, process_selection)
Here is a custom MyScrollMenu
that I built to add the above, but it the change is so minor and backwards compatible, that I think it should be added to the widget directly. In the custom widget, I've added one new parameter to the constructor: to_str
. It has a default value of str
. And then in the body of the ScrollMenu.draw
method, only one line is changed—the in
clause of the for
loop:
class MyScrollMenu(py_cui.widgets.ScrollMenu):
def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, to_str=str,
):
super().__init__(id, title, grid, row, column, row_span, column_span, padx, pady)
self.to_str = to_str
def draw(self):
super(py_cui.widgets.ScrollMenu, self).draw()
self.renderer.set_color_mode(
self.selected_color if self.selected else self.color
)
self.renderer.draw_border(self)
counter = self.pady + 1
line_counter = 0
for line in (self.to_str(i) for i in self.view_items):
if line_counter < self.top_view:
line_counter = line_counter + 1
else:
if counter >= self.height - self.pady - 1:
break
if line_counter == self.selected_item:
self.renderer.draw_text(
self, line, self.start_y + counter, selected=True
)
else:
self.renderer.draw_text(self, line, self.start_y + counter)
counter = counter + 1
line_counter = line_counter + 1
self.renderer.unset_color_mode(
self.selected_color if self.selected else self.color
)
self.renderer.reset_cursor(self)
Another nice item with this approach is that one can have multiple ScrollMenu
widgets all containing the same Pet
list, but each could be displayed differently just by passing a custom to_str
function:
pets = [Pet("Baloo", "Dog", 4), Pet("Wilbur", "Horse", 7)]
menu1 = root.add_scroll_menu("Pets Detail", 0, 0)
menu1.add_item_list(pets) # Allow any object to be added
menu2 = root.add_scroll_menu("Pet Names", 0, 0, to_str=lambda p: p.name)
menu2.add_item_list(pets) # Allow any object to be added