Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to provide custom function to generate a line in ScrollMenu #45

Closed
pkazmier opened this issue Apr 5, 2020 · 2 comments
Closed
Labels
Feature Request Add for a new feature request Widgets Add for issues having to do with widgets
Milestone

Comments

@pkazmier
Copy link

pkazmier commented Apr 5, 2020

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
@pkazmier pkazmier added the Feature Request Add for a new feature request label Apr 5, 2020
@jwlodek
Copy link
Owner

jwlodek commented Apr 5, 2020

I like this idea. I want to finish some existing changes I am working on first that make pretty major structural changes to the internals of widgets/popups, and merge those in, and then we can take a look at adding this in the next set of changes.

@jwlodek jwlodek added the Widgets Add for issues having to do with widgets label Apr 6, 2020
@jwlodek jwlodek added this to the v0.1.1 milestone Apr 6, 2020
This was referenced Apr 22, 2020
@jwlodek
Copy link
Owner

jwlodek commented May 24, 2020

I included this in the v0.1.1 release. At the moment it only supports using the __str__ function, though I may considering the ability to specify a different function in the future.

@jwlodek jwlodek closed this as completed May 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Request Add for a new feature request Widgets Add for issues having to do with widgets
Projects
None yet
Development

No branches or pull requests

2 participants