Source code for quest.contrib.submodal

from quest.text_label import TextLabelStack

# A submodal should return True from `choose_option` when it has completed.
# That might be confusing for other readers. So instead, import CLOSE_SUBMODAL
# and return that.
CLOSE_SUBMODAL = True

[docs]class SubmodalMixin: """Extends `quest.modal.Modal` so that it can support submodals. Sometimes a modal needs to go into a deeper level of detail. For example an inventory list might need to present more details about a specific item. This could all be implemented within one modal, but it would get complicated quickly. Instead, `SubmodalMixin` allows a `Modal` to open its own submodal using `open_submodal(modal)`. The top-level modal remains in control and is the only modal which actually gets rendered; any others are just used to access data and to figure out how to respond to choices. A modal which mixes in `SubmodalMixin` will check to see whether it has the property `submodal` assigned before getting its text labels, option labels, or choosing an option. When a submodal is present, that modal's behavior is called instead of the modal. (That submodal could have its own submodals!) When a submodal is ready to be closed, it should call `self.close()` from `choose_option` as usual. Instead of closing all the modals, `close` will signal the parent modal that it should resume control. In case you want to close more than one layer of modals, you can call `self.parent.close()`. Submodal uses two elegant and complex computer science ideas. The first is recursion, or having a function call itself. Recursion is useful when you can break a task into a simpler version of itself, or when you can accomplish a task by asking someone else to do it. (Asking yourself to do a task by asking yourself to do it leads to infinite recursion and a headache.) Check out `get_active_modal` to see an example of recursion. The second elegant idea is virtualization, the idea that you never know if you're at the top level. A submodal acts just like the main modal, attached to a game. Either way, the modal calls `close()` when it is finished. If it was a submodal, control passes back to the parent, but the modal calling `close()` doesn't know or care. A lot of stories have been written about virtualization--the idea of being in a dream and not knowing whether you're in a dream (or whether this reality is itself a dream, or maybe even a dream within a dream! Attributes: parent (Modal): This submodal's parent modal (if it has one). submodal (Modal): The current submodal (if any). """ parent = None submodal = None
[docs] def open_submodal(self, modal): "Attaches a submodal and sets the submodal's parent to self." modal.parent = self self.submodal = modal
[docs] def close(self, close_all=False): """Closes this submodal, handing control back to the parent if there is one. Arguments: close_all (bool): If True, closes all modals, not just this one. """ if close_all or self.parent is None: self.game.close_modal() else: self.parent.submodal = None
[docs] def get_active_modal(self): """Finds the modal who should control rendering and handling input. If a modal has a submodal, the submodal should be in charge. (But that submodal might have a sub-sub-modal.) So `Submodal.get_active_modal` uses recursion to find a modal that doesn't have a submodal. Instead of doing the whole task, it just asks its submodal to find the active submodal--using exactly the same method. """ if self.submodal: try: return self.submodal.get_active_modal() except AttributeError: return self.submodal else: return self
[docs] def set_text_labels(self): """Creates text labels, using the active submodal if there is one. """ m = self.get_active_modal() self.text_labels = TextLabelStack( m.text_label_contents(), self.x_center, self.y_center + self.height / 2 )
[docs] def set_option_labels(self): """Creates option labels, using the active submodal if there is one. """ m = self.get_active_modal() options = m.option_label_contents() disposable_stack = TextLabelStack(options, 0, 0) self.option_labels = TextLabelStack( options, self.x_center, self.y_center - self.height / 2 + disposable_stack.height() ) m.current_option = 0 self.option_labels.set_highlight(m.current_option)
[docs] def handle_change_option(self, change): """Handles a change option, using the active submodal if there is one. """ m = self.get_active_modal() m.current_option = (m.current_option + change) % len(m.option_labels) self.option_labels.set_highlight(m.current_option)
[docs] def handle_choice(self): "Lets submodal choose option, if set" m = self.get_active_modal() m.choose_option(m.current_option) self.set_text_labels() self.set_option_labels()